diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningController.java index ec66ea51eff..b5878d14de3 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningController.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -48,7 +49,7 @@ public class NodeLocalConfigPermissioningController implements NodeConnectionPer private LocalPermissioningConfiguration configuration; private final List fixedNodes; private final Bytes localNodeId; - private final List nodesAllowlist = new ArrayList<>(); + private final List nodesAllowlist = new CopyOnWriteArrayList<>(); private final AllowlistPersistor allowlistPersistor; private final Subscribers> nodeAllowlistUpdatedObservers = Subscribers.create(); diff --git a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java index 106f9c36100..5db372b63ac 100644 --- a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java +++ b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java @@ -46,6 +46,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import com.google.common.collect.Lists; @@ -316,6 +317,56 @@ public void whenCheckingIfNodeIsPermittedOrderDoesNotMatter() { .isTrue(); } + @Test + public void whenCallingIsPermittedAndRemovingEntryInAnotherThreadShouldNotThrowException() + throws InterruptedException { + // Add a node to the allowlist + controller.addNodes(Lists.newArrayList(enode1)); + + // Atomic flag to detect exceptions + AtomicBoolean exceptionOccurred = new AtomicBoolean(false); + + // Create a thread to call isPermitted + Thread isPermittedThread = + new Thread( + () -> { + try { + for (int i = 0; i < 1000; i++) { + controller.isPermitted(enode1); + } + } catch (Exception e) { + exceptionOccurred.set(true); + e.printStackTrace(); + } + }); + + // Create a thread to modify the allowlist + Thread modifyAllowlistThread = + new Thread( + () -> { + try { + for (int i = 0; i < 1000; i++) { + controller.removeNodes(Lists.newArrayList(enode1)); + controller.addNodes(Lists.newArrayList(enode1)); + } + } catch (Exception e) { + exceptionOccurred.set(true); + e.printStackTrace(); + } + }); + + // Start both threads + isPermittedThread.start(); + modifyAllowlistThread.start(); + + // Wait for both threads to complete + isPermittedThread.join(); + modifyAllowlistThread.join(); + + // Assert no exceptions were thrown + assert (!exceptionOccurred.get()); + } + @Test public void stateShouldRevertIfAllowlistPersistFails() throws IOException, AllowlistFileSyncException {