Skip to content

Commit 5a7b9f7

Browse files
fab-10georgereuben
authored andcommitted
Support multiple transaction selector plugins (besu-eth#9139)
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net> Signed-off-by: georgereuben <reubengeorge101@gmail.com>
1 parent 3809569 commit 5a7b9f7

File tree

11 files changed

+645
-55
lines changed

11 files changed

+645
-55
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
### Additions and Improvements
1919
- Update log4j [#9131](https://github.com/hyperledger/besu/pull/9131)
2020
- Expose new method to query hardfork by block number Plugin API [#9115](https://github.com/hyperledger/besu/pull/9115)
21+
- Support loading multiple transaction selector plugins [#8743](https://github.com/hyperledger/besu/pull/9139)
2122

2223
#### Performance
2324
- Add jmh benchmarks for some compute-related opcodes [#9069](https://github.com/hyperledger/besu/pull/9069)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.tests.acceptance.plugins;
16+
17+
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED;
18+
19+
import org.hyperledger.besu.plugin.BesuPlugin;
20+
import org.hyperledger.besu.plugin.ServiceManager;
21+
import org.hyperledger.besu.plugin.data.TransactionProcessingResult;
22+
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
23+
import org.hyperledger.besu.plugin.services.PicoCLIOptions;
24+
import org.hyperledger.besu.plugin.services.TransactionSelectionService;
25+
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
26+
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory;
27+
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
28+
import org.hyperledger.besu.plugin.services.txselection.TransactionEvaluationContext;
29+
30+
public abstract class AbstractTestTransactionSelectorPlugin implements BesuPlugin {
31+
private ServiceManager serviceManager;
32+
33+
private final int pluginNum;
34+
private final int preMultiple;
35+
private final int postMultiple;
36+
37+
public AbstractTestTransactionSelectorPlugin(
38+
final int pluginNum, final int preMultiple, final int postMultiple) {
39+
this.pluginNum = pluginNum;
40+
this.preMultiple = preMultiple;
41+
this.postMultiple = postMultiple;
42+
}
43+
44+
@Override
45+
public void register(final ServiceManager serviceManager) {
46+
this.serviceManager = serviceManager;
47+
serviceManager
48+
.getService(PicoCLIOptions.class)
49+
.orElseThrow()
50+
.addPicoCLIOptions("tx-selector" + pluginNum, this);
51+
}
52+
53+
protected abstract boolean isEnabled();
54+
55+
@Override
56+
public void beforeExternalServices() {
57+
if (isEnabled()) {
58+
serviceManager
59+
.getService(TransactionSelectionService.class)
60+
.orElseThrow()
61+
.registerPluginTransactionSelectorFactory(
62+
new PluginTransactionSelectorFactory() {
63+
@Override
64+
public PluginTransactionSelector create(
65+
final SelectorsStateManager selectorsStateManager) {
66+
return new PluginTransactionSelector() {
67+
68+
@Override
69+
public TransactionSelectionResult evaluateTransactionPreProcessing(
70+
final TransactionEvaluationContext evaluationContext) {
71+
if (evaluationContext
72+
.getPendingTransaction()
73+
.getTransaction()
74+
.getValue()
75+
.getAsBigInteger()
76+
.longValue()
77+
% preMultiple
78+
== 0) {
79+
return TransactionSelectionResult.invalid(
80+
"value multiple of " + preMultiple);
81+
}
82+
return SELECTED;
83+
}
84+
85+
@Override
86+
public TransactionSelectionResult evaluateTransactionPostProcessing(
87+
final TransactionEvaluationContext evaluationContext,
88+
final TransactionProcessingResult processingResult) {
89+
if (evaluationContext
90+
.getPendingTransaction()
91+
.getTransaction()
92+
.getValue()
93+
.getAsBigInteger()
94+
.longValue()
95+
% postMultiple
96+
== 0) {
97+
return TransactionSelectionResult.invalid(
98+
"value multiple of " + postMultiple);
99+
}
100+
return SELECTED;
101+
}
102+
};
103+
}
104+
});
105+
}
106+
}
107+
108+
@Override
109+
public void start() {}
110+
111+
@Override
112+
public void stop() {}
113+
}

acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestBlockchainServicePlugin.java

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.hyperledger.besu.plugin.data.BlockHeader;
2323
import org.hyperledger.besu.plugin.services.BesuEvents;
2424
import org.hyperledger.besu.plugin.services.BlockchainService;
25+
import org.hyperledger.besu.plugin.services.PicoCLIOptions;
2526

2627
import java.io.File;
2728
import java.io.IOException;
@@ -33,6 +34,7 @@
3334
import com.google.auto.service.AutoService;
3435
import org.slf4j.Logger;
3536
import org.slf4j.LoggerFactory;
37+
import picocli.CommandLine;
3638

3739
@AutoService(BesuPlugin.class)
3840
public class TestBlockchainServicePlugin implements BesuPlugin {
@@ -41,35 +43,46 @@ public class TestBlockchainServicePlugin implements BesuPlugin {
4143
private ServiceManager serviceManager;
4244
private File callbackDir;
4345

46+
@CommandLine.Option(names = "--plugin-blockchain-service-test-enabled")
47+
boolean enabled = false;
48+
4449
@Override
4550
public void register(final ServiceManager serviceManager) {
4651
LOG.info("Registering TestBlockchainServicePlugin");
4752
this.serviceManager = serviceManager;
53+
serviceManager
54+
.getService(PicoCLIOptions.class)
55+
.orElseThrow()
56+
.addPicoCLIOptions("blockchain-service", this);
57+
4858
callbackDir = new File(System.getProperty("besu.plugins.dir", "plugins"));
4959
}
5060

5161
@Override
5262
public void start() {
53-
LOG.info("Starting TestBlockchainServicePlugin");
54-
final var blockchainService = serviceManager.getService(BlockchainService.class).orElseThrow();
55-
56-
serviceManager
57-
.getService(BesuEvents.class)
58-
.orElseThrow()
59-
.addBlockAddedListener(
60-
addedBlockContext -> {
61-
LOG.info("Block added: {}", addedBlockContext);
62-
final var hardforkSeen =
63-
queryHardfork(blockchainService, addedBlockContext.getBlockHeader());
64-
seenHardforks.add(
65-
queryHardfork(blockchainService, addedBlockContext.getBlockHeader()));
66-
if (hardforkSeen.current.equals(HardforkId.MainnetHardforkId.LONDON)) {
67-
LOG.info("Writing seen hardforks: {}", seenHardforks);
68-
writeSeenHardforks();
69-
}
70-
});
71-
72-
seenHardforks.add(queryHardfork(blockchainService, blockchainService.getChainHeadHeader()));
63+
if (enabled) {
64+
LOG.info("Starting TestBlockchainServicePlugin");
65+
final var blockchainService =
66+
serviceManager.getService(BlockchainService.class).orElseThrow();
67+
68+
serviceManager
69+
.getService(BesuEvents.class)
70+
.orElseThrow()
71+
.addBlockAddedListener(
72+
addedBlockContext -> {
73+
LOG.info("Block added: {}", addedBlockContext);
74+
final var hardforkSeen =
75+
queryHardfork(blockchainService, addedBlockContext.getBlockHeader());
76+
seenHardforks.add(
77+
queryHardfork(blockchainService, addedBlockContext.getBlockHeader()));
78+
if (hardforkSeen.current.equals(HardforkId.MainnetHardforkId.LONDON)) {
79+
LOG.info("Writing seen hardforks: {}", seenHardforks);
80+
writeSeenHardforks();
81+
}
82+
});
83+
84+
seenHardforks.add(queryHardfork(blockchainService, blockchainService.getChainHeadHeader()));
85+
}
7386
}
7487

7588
private HardforkSeen queryHardfork(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.tests.acceptance.plugins;
16+
17+
import org.hyperledger.besu.plugin.BesuPlugin;
18+
19+
import com.google.auto.service.AutoService;
20+
import picocli.CommandLine.Option;
21+
22+
@AutoService(BesuPlugin.class)
23+
public class TestTransactionSelectorPlugin1 extends AbstractTestTransactionSelectorPlugin {
24+
25+
@Option(names = "--plugin-tx-selector1-test-enabled")
26+
boolean enabled = false;
27+
28+
public TestTransactionSelectorPlugin1() {
29+
super(1, 3, 5);
30+
}
31+
32+
@Override
33+
public boolean isEnabled() {
34+
return enabled;
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.tests.acceptance.plugins;
16+
17+
import org.hyperledger.besu.plugin.BesuPlugin;
18+
19+
import com.google.auto.service.AutoService;
20+
import picocli.CommandLine.Option;
21+
22+
@AutoService(BesuPlugin.class)
23+
public class TestTransactionSelectorPlugin2 extends AbstractTestTransactionSelectorPlugin {
24+
25+
@Option(names = "--plugin-tx-selector2-test-enabled")
26+
boolean enabled = false;
27+
28+
public TestTransactionSelectorPlugin2() {
29+
super(2, 7, 11);
30+
}
31+
32+
@Override
33+
public boolean isEnabled() {
34+
return enabled;
35+
}
36+
}

acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BlockchainServicePluginTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void setUp() throws Exception {
3737
besu.createQbftPluginsNode(
3838
"pluginNode",
3939
Collections.singletonList("testPlugins"),
40-
Collections.singletonList("--plugin-tx-validator-test-enabled=true"),
40+
Collections.singletonList("--plugin-blockchain-service-test-enabled=true"),
4141
"DEBUG");
4242

4343
cluster.start(pluginNode);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.tests.acceptance.plugins;
16+
17+
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
18+
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
19+
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
20+
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
21+
22+
import java.math.BigInteger;
23+
import java.util.Collections;
24+
import java.util.List;
25+
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.ValueSource;
30+
31+
public class TransactionSelectorPluginTest extends AcceptanceTestBase {
32+
private BesuNode node;
33+
34+
@BeforeEach
35+
public void setUp() throws Exception {
36+
node =
37+
besu.createQbftPluginsNode(
38+
"node",
39+
Collections.singletonList("testPlugins"),
40+
List.of(
41+
"--plugin-tx-selector1-test-enabled=true",
42+
"--plugin-tx-selector2-test-enabled=true"),
43+
"DEBUG");
44+
cluster.start(node);
45+
}
46+
47+
@Test
48+
public void transactionIsMined() {
49+
final Account recipient = accounts.createAccount("recipient");
50+
51+
// selector plugins reject values that are multiple of: 3,5,7,11
52+
final var transferTx =
53+
accountTransactions.createTransfer(recipient, Amount.wei(BigInteger.ONE));
54+
55+
final var txHash = node.execute(transferTx);
56+
57+
node.verify(eth.expectSuccessfulTransactionReceipt(txHash.toHexString()));
58+
}
59+
60+
@ParameterizedTest
61+
// selector plugins reject amount that are multiple of: 3,5,7,11
62+
@ValueSource(longs = {3, 5, 7, 11, 3 * 5})
63+
public void badMultipleOfValueTransactionIsNotSelectedByPlugin(final long amount) {
64+
final Account recipient = accounts.createAccount("recipient");
65+
66+
final var badTx =
67+
accountTransactions.createTransfer(recipient, Amount.wei(BigInteger.valueOf(amount)));
68+
69+
final var sameSenderGoodTx =
70+
accountTransactions.createTransfer(recipient, Amount.wei(BigInteger.ONE));
71+
72+
final var anotherSenderGoodTx =
73+
accountTransactions.createTransfer(
74+
accounts.getSecondaryBenefactor(), recipient, Amount.wei(BigInteger.ONE));
75+
76+
final var badHash = node.execute(badTx);
77+
final var sameSenderGoodHash = node.execute(sameSenderGoodTx);
78+
final var anotherSenderGoodHash = node.execute(anotherSenderGoodTx);
79+
80+
node.verify(eth.expectSuccessfulTransactionReceipt(anotherSenderGoodHash.toHexString()));
81+
node.verify(eth.expectNoTransactionReceipt(badHash.toHexString()));
82+
// good tx from the same sender is not mined due to the nonce gap
83+
node.verify(eth.expectNoTransactionReceipt(sameSenderGoodHash.toHexString()));
84+
}
85+
}

0 commit comments

Comments
 (0)