[Follow up] Add Transaction Permissioning Hook to PermissioningService Interface (#8365)

* fixing the issue of implementing the permission service class in pr when it already exists
Signed-off-by: vaidikcode <vaidikbhardwaj00@gmail.com>

* wiring transaction filtering for Permissioning Plugins

* revert modify deprated code for TransactionSmartContractPermissioning

* Improve logging for AccountPermissioningController

Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Cristiano Aguzzi <relu91@users.noreply.github.com>

* Improve logging messages for AccountPermissioningController

* Allign AccountPermissioningControllerFactoryTest with the latest api

Signed-off-by: reluc <relu.cri@gmail.com>

---------

Signed-off-by: Vaidik <vaidikbhardwaj00@gmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: reluc <relu.cri@gmail.com>
Signed-off-by: Cristiano Aguzzi <relu91@users.noreply.github.com>
Co-authored-by: vaidikcode <vaidikbhardwaj00@gmail.com>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Co-authored-by: Justin Florentine <justin+github@florentine.us>
This commit is contained in:
Cristiano Aguzzi
2025-03-12 08:41:09 +01:00
committed by GitHub
parent 35d12070db
commit 72ac7bdbe4
13 changed files with 219 additions and 18 deletions

View File

@@ -1,6 +1,7 @@
# Changelog
## Unreleased
### Breaking Changes
- k8s (KUBERNETES) Nat method is removed. Use docker or none instead. [#8289](https://github.com/hyperledger/besu/pull/8289)
- Change `Invalid block, unable to parse RLP` RPC error message to `Invalid block param (block not found)` [#8328](https://github.com/hyperledger/besu/pull/8328)
@@ -27,6 +28,7 @@
- Update Holesky and Sepolia deposit contract addresses [#8346](https://github.com/hyperledger/besu/pull/8346)
#### Plugins
- Allow plugins to propose transactions during block creation [#8268](https://github.com/hyperledger/besu/pull/8268)
- Add support for transaction permissioning rules in Plugin API [#8365](https://github.com/hyperledger/besu/pull/8365)
#### Parallelization
- Improve conflict detection by considering slots to reduce false positives [#7923](https://github.com/hyperledger/besu/pull/7923)
#### Dependencies

View File

@@ -75,6 +75,20 @@ public class TestPermissioningPlugin implements BesuPlugin {
}
return true;
});
service.registerTransactionPermissioningProvider(
transaction -> {
long configuredGasLimitThreshold = 22000L;
long gasLimit = transaction.getGasLimit();
LOG.info(
"Transaction gas limit: {} | Configured threshold: {} ",
gasLimit,
configuredGasLimitThreshold);
// Sample logic to testing. If the gas limit is exactly 21000 (intrinsic gas limit)
// we let the transaction pass. This is helpful for not making additional configuration
// options in PermssioningPluginTest
return gasLimit > configuredGasLimitThreshold || gasLimit == 21000;
});
}
}

View File

@@ -14,18 +14,27 @@
*/
package org.hyperledger.besu.tests.acceptance.plugins;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfigurationBuilder;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.SignUtil;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.account.TransferTransaction;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.RawTransaction;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;
public class PermissioningPluginTest extends AcceptanceTestBase {
private BesuNode minerNode;
@@ -34,6 +43,8 @@ public class PermissioningPluginTest extends AcceptanceTestBase {
private BesuNode bobNode;
private BesuNode charlieNode;
private static final long GAS_LIMIT_THRESHOLD = 22000L;
@BeforeEach
public void setUp() throws Exception {
minerNode = besu.create(createNodeBuilder().name("miner").build());
@@ -96,4 +107,54 @@ public class PermissioningPluginTest extends AcceptanceTestBase {
charlieNode.verify(txPoolConditions.notInTransactionPool(txHash));
minerNode.verify(txPoolConditions.inTransactionPool(txHash));
}
@Test
public void allowFilteredByGasLimit() {
final Account sender = accounts.getPrimaryBenefactor();
final Account recipient = accounts.createAccount("account-two");
final BigInteger GAS_LIMIT = BigInteger.valueOf(GAS_LIMIT_THRESHOLD + 100);
final BigInteger GAS_PRICE = BigInteger.valueOf(1000);
final Amount amount = Amount.wei(BigInteger.valueOf(29));
final RawTransaction tx =
RawTransaction.createEtherTransaction(
sender.getNextNonce(),
GAS_PRICE,
GAS_LIMIT,
recipient.getAddress(),
Convert.toWei(amount.getValue(), amount.getUnit()).toBigIntegerExact());
final String rawSigned =
Numeric.toHexString(
SignUtil.signTransaction(tx, sender, new SECP256K1(), Optional.empty()));
final String txHash = aliceNode.execute(ethTransactions.sendRawTransaction(rawSigned));
aliceNode.verify(txPoolConditions.inTransactionPool(Hash.fromHexString(txHash)));
}
@Test
public void blockedFilteredByGasLimit() {
final Account sender = accounts.getPrimaryBenefactor();
final Account recipient = accounts.createAccount("account-two");
final BigInteger GAS_LIMIT = BigInteger.valueOf(GAS_LIMIT_THRESHOLD - 100);
final BigInteger GAS_PRICE = BigInteger.valueOf(1000);
final Amount amount = Amount.wei(BigInteger.valueOf(29));
final RawTransaction tx =
RawTransaction.createEtherTransaction(
sender.getNextNonce(),
GAS_PRICE,
GAS_LIMIT,
recipient.getAddress(),
Convert.toWei(amount.getValue(), amount.getUnit()).toBigIntegerExact());
final String rawSigned =
Numeric.toHexString(
SignUtil.signTransaction(tx, sender, new SECP256K1(), Optional.empty()));
assertThatThrownBy(() -> aliceNode.execute(ethTransactions.sendRawTransaction(rawSigned)))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("not authorized");
}
}

View File

@@ -1177,10 +1177,17 @@ public class RunnerBuilder {
final BesuController besuController,
final TransactionSimulator transactionSimulator) {
if (permissioningConfiguration.isPresent()) {
if (permissioningConfiguration.isPresent()
|| permissioningService.getTransactionPermissioningProviders().size() > 0) {
final PermissioningConfiguration configuration =
permissioningConfiguration.orElse(
new PermissioningConfiguration(Optional.empty(), Optional.empty()));
final Optional<AccountPermissioningController> accountPermissioningController =
AccountPermissioningControllerFactory.create(
permissioningConfiguration.get(), transactionSimulator, metricsSystem);
configuration,
transactionSimulator,
metricsSystem,
permissioningService.getTransactionPermissioningProviders());
accountPermissioningController.ifPresent(
permissioningController ->

View File

@@ -17,17 +17,24 @@ package org.hyperledger.besu.services;
import org.hyperledger.besu.plugin.services.PermissioningService;
import org.hyperledger.besu.plugin.services.permissioning.NodeConnectionPermissioningProvider;
import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider;
import org.hyperledger.besu.plugin.services.permissioning.TransactionPermissioningProvider;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** The Permissioning service implementation. */
public class PermissioningServiceImpl implements PermissioningService {
private static final Logger LOG = LoggerFactory.getLogger(PermissioningServiceImpl.class);
private final List<NodeConnectionPermissioningProvider> connectionPermissioningProviders =
Lists.newArrayList();
private final List<TransactionPermissioningProvider> transactionPermissioningProviders =
new ArrayList<>();
/** Default Constructor. */
@Inject
@@ -39,6 +46,13 @@ public class PermissioningServiceImpl implements PermissioningService {
connectionPermissioningProviders.add(provider);
}
@Override
public void registerTransactionPermissioningProvider(
final TransactionPermissioningProvider provider) {
transactionPermissioningProviders.add(provider);
LOG.info("Registered new transaction permissioning provider.");
}
/**
* Gets connection permissioning providers.
*
@@ -65,4 +79,13 @@ public class PermissioningServiceImpl implements PermissioningService {
public List<NodeMessagePermissioningProvider> getMessagePermissioningProviders() {
return messagePermissioningProviders;
}
/**
* Gets transaction permissioning providers.
*
* @return the transaction permissioning providers
*/
public List<TransactionPermissioningProvider> getTransactionPermissioningProviders() {
return transactionPermissioningProviders;
}
}

View File

@@ -16,12 +16,12 @@ package org.hyperledger.besu.ethereum.permissioning;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.ethereum.permissioning.AllowlistPersistor.ALLOWLIST_TYPE;
import org.hyperledger.besu.ethereum.permissioning.account.TransactionPermissioningProvider;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.plugin.services.permissioning.TransactionPermissioningProvider;
import java.io.IOException;
import java.util.ArrayList;

View File

@@ -19,7 +19,9 @@ import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController;
import org.hyperledger.besu.ethereum.permissioning.TransactionSmartContractPermissioningController;
import org.hyperledger.besu.plugin.services.permissioning.TransactionPermissioningProvider;
import java.util.List;
import java.util.Optional;
import com.google.common.annotations.VisibleForTesting;
@@ -34,15 +36,18 @@ public class AccountPermissioningController {
accountLocalConfigPermissioningController;
private final Optional<TransactionSmartContractPermissioningController>
transactionSmartContractPermissioningController;
private final List<TransactionPermissioningProvider> pluginProviders;
public AccountPermissioningController(
final Optional<AccountLocalConfigPermissioningController>
accountLocalConfigPermissioningController,
final Optional<TransactionSmartContractPermissioningController>
transactionSmartContractPermissioningController) {
transactionSmartContractPermissioningController,
final List<TransactionPermissioningProvider> pluginProviders) {
this.accountLocalConfigPermissioningController = accountLocalConfigPermissioningController;
this.transactionSmartContractPermissioningController =
transactionSmartContractPermissioningController;
this.pluginProviders = pluginProviders;
}
public boolean isPermitted(
@@ -53,7 +58,7 @@ public class AccountPermissioningController {
final Hash transactionHash = transaction.getHash();
final Address sender = transaction.getSender();
LOG.trace("Account permissioning: Checking transaction {}", transactionHash);
LOG.trace("Transaction Rejector: Checking transaction {}", transactionHash);
boolean permittedLocal = true;
boolean permittedOnchain = true;
@@ -75,6 +80,16 @@ public class AccountPermissioningController {
final boolean permitted = permittedLocal && permittedOnchain;
if (permitted) {
for (TransactionPermissioningProvider provider : this.pluginProviders) {
if (!provider.isPermitted(transaction)) {
LOG.trace(
"Transaction Rejector - {}: Rejected transaction {} from {}",
provider.getClass().getSimpleName(),
transactionHash,
sender);
return false;
}
}
LOG.trace("Account permissioning: Permitted transaction {} from {}", transactionHash, sender);
} else {
LOG.trace("Account permissioning: Rejected transaction {} from {}", transactionHash, sender);

View File

@@ -28,7 +28,9 @@ import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningCon
import org.hyperledger.besu.ethereum.permissioning.TransactionSmartContractPermissioningController;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.permissioning.TransactionPermissioningProvider;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
@@ -43,7 +45,8 @@ public class AccountPermissioningControllerFactory {
public static Optional<AccountPermissioningController> create(
final PermissioningConfiguration permissioningConfiguration,
final TransactionSimulator transactionSimulator,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final List<TransactionPermissioningProvider> pluginProviders) {
if (permissioningConfiguration == null) {
return Optional.empty();
@@ -59,12 +62,14 @@ public class AccountPermissioningControllerFactory {
permissioningConfiguration, transactionSimulator, metricsSystem);
if (accountLocalConfigPermissioningController.isPresent()
|| transactionSmartContractPermissioningController.isPresent()) {
|| transactionSmartContractPermissioningController.isPresent()
|| pluginProviders.size() > 0) {
final AccountPermissioningController controller =
new AccountPermissioningController(
accountLocalConfigPermissioningController,
transactionSmartContractPermissioningController);
transactionSmartContractPermissioningController,
pluginProviders);
return Optional.of(controller);
} else {

View File

@@ -31,6 +31,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import org.assertj.core.api.Assertions;
@@ -49,7 +50,8 @@ public class AccountPermissioningControllerFactoryTest {
@Test
public void createWithNullPermissioningConfigShouldReturnEmpty() {
Optional<AccountPermissioningController> controller =
AccountPermissioningControllerFactory.create(null, transactionSimulator, metricsSystem);
AccountPermissioningControllerFactory.create(
null, transactionSimulator, metricsSystem, Collections.emptyList());
Assertions.assertThat(controller).isEmpty();
}
@@ -64,7 +66,10 @@ public class AccountPermissioningControllerFactoryTest {
Optional<AccountPermissioningController> controller =
AccountPermissioningControllerFactory.create(
permissioningConfiguration, transactionSimulator, metricsSystem);
permissioningConfiguration,
transactionSimulator,
metricsSystem,
Collections.emptyList());
Assertions.assertThat(controller).isEmpty();
}
@@ -79,7 +84,10 @@ public class AccountPermissioningControllerFactoryTest {
Optional<AccountPermissioningController> controller =
AccountPermissioningControllerFactory.create(
permissioningConfiguration, transactionSimulator, metricsSystem);
permissioningConfiguration,
transactionSimulator,
metricsSystem,
Collections.emptyList());
Assertions.assertThat(controller).isNotEmpty();
assertThat(controller.get().getAccountLocalConfigPermissioningController()).isNotEmpty();
@@ -97,7 +105,10 @@ public class AccountPermissioningControllerFactoryTest {
Optional<AccountPermissioningController> controller =
AccountPermissioningControllerFactory.create(
permissioningConfiguration, transactionSimulator, metricsSystem);
permissioningConfiguration,
transactionSimulator,
metricsSystem,
Collections.emptyList());
Assertions.assertThat(controller).isEmpty();
}
@@ -112,7 +123,10 @@ public class AccountPermissioningControllerFactoryTest {
Optional<AccountPermissioningController> controller =
AccountPermissioningControllerFactory.create(
permissioningConfiguration, transactionSimulator, metricsSystem);
permissioningConfiguration,
transactionSimulator,
metricsSystem,
Collections.emptyList());
Assertions.assertThat(controller).isNotEmpty();
assertThat(controller.get().getAccountLocalConfigPermissioningController()).isEmpty();
@@ -133,7 +147,10 @@ public class AccountPermissioningControllerFactoryTest {
catchThrowable(
() ->
AccountPermissioningControllerFactory.create(
permissioningConfiguration, transactionSimulator, metricsSystem));
permissioningConfiguration,
transactionSimulator,
metricsSystem,
Collections.emptyList()));
assertThat(thrown)
.isInstanceOf(IllegalStateException.class)
@@ -153,7 +170,10 @@ public class AccountPermissioningControllerFactoryTest {
Optional<AccountPermissioningController> controller =
AccountPermissioningControllerFactory.create(
permissioningConfiguration, transactionSimulator, metricsSystem);
permissioningConfiguration,
transactionSimulator,
metricsSystem,
Collections.emptyList());
Assertions.assertThat(controller).isNotEmpty();
assertThat(controller.get().getAccountLocalConfigPermissioningController()).isNotEmpty();

View File

@@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController;
import org.hyperledger.besu.ethereum.permissioning.TransactionSmartContractPermissioningController;
import java.util.Collections;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
@@ -45,7 +46,9 @@ public class AccountPermissioningControllerTest {
public void before() {
permissioningController =
new AccountPermissioningController(
Optional.of(localConfigController), Optional.of(smartContractController));
Optional.of(localConfigController),
Optional.of(smartContractController),
Collections.emptyList());
}
@Test

View File

@@ -71,7 +71,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'lsoaojUhm38o4QxfJW2fky/eEkrtUyprNrwAkfwebe8='
knownHash = 'Qq6tGiNFfAs1JVr7xXx07h/ANxPWcFKe3cBHER43FFQ='
}
check.dependsOn('checkAPIChanges')

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.plugin.services;
import org.hyperledger.besu.plugin.services.permissioning.NodeConnectionPermissioningProvider;
import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider;
import org.hyperledger.besu.plugin.services.permissioning.TransactionPermissioningProvider;
/**
* This service allows plugins to decide who you should connect to and what you should send them.
@@ -38,6 +39,13 @@ public interface PermissioningService extends BesuService {
*/
void registerNodePermissioningProvider(NodeConnectionPermissioningProvider provider);
/**
* Registers a callback for transaction permission.
*
* @param provider The provider to register
*/
void registerTransactionPermissioningProvider(final TransactionPermissioningProvider provider);
/**
* Registers a callback to allow the interception of a devp2p message sending request
*

View File

@@ -0,0 +1,43 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.plugin.services.permissioning;
import org.hyperledger.besu.datatypes.Transaction;
/**
* Allows you to register a provider that will decide if a transaction is permitted. <br>
* <br>
* A simple implementation can look like:
*
* <pre>{@code
* context
* .getService(PermissioningService.class)
* .get()
* .registerTransactionPermissioningProvider((tx) -> {
* // Your logic here
* return true;
* });
* }</pre>
*/
@FunctionalInterface
public interface TransactionPermissioningProvider {
/**
* Can be used to filter transactions according to an arbitrary criteria
*
* @param transaction current transaction to be added in the mempool
* @return if we can add transaction to the mempool
*/
boolean isPermitted(final Transaction transaction);
}