From 48808784f5a6b8f76f632193f066bb82c01afd71 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:41:04 +0000 Subject: [PATCH 001/112] refactor(fed): federation watcher tests --- .../co/rsk/federate/FederationWatcher.java | 2 +- .../rsk/federate/FederationWatcherTest.java | 556 +++++++++--------- 2 files changed, 273 insertions(+), 285 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationWatcher.java b/src/main/java/co/rsk/federate/FederationWatcher.java index 7d47379fa..6a130039f 100644 --- a/src/main/java/co/rsk/federate/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/FederationWatcher.java @@ -48,7 +48,7 @@ public void addListener(Listener listener) { listeners.add(listener); } - private class FederationWatcherRskListener extends EthereumListenerAdapter { + public class FederationWatcherRskListener extends EthereumListenerAdapter { @Override public void onBestBlock(org.ethereum.core.Block block, List receipts) { // Updating state only when the best block changes still "works", diff --git a/src/test/java/co/rsk/federate/FederationWatcherTest.java b/src/test/java/co/rsk/federate/FederationWatcherTest.java index 7c9119778..e52f17f2a 100644 --- a/src/test/java/co/rsk/federate/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/FederationWatcherTest.java @@ -4,9 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -22,354 +22,342 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.ethereum.crypto.ECKey; import org.ethereum.facade.Ethereum; import org.ethereum.listener.EthereumListenerAdapter; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; class FederationWatcherTest { - List federation1Members = getFederationMembersFromPksForBtc(1000, 2000, 3000, 4000); - Instant federation1CreationTime = Instant.ofEpochMilli(5005L); - long creationBlockNumber = 0L; - NetworkParameters btcParams = NetworkParameters.fromID(NetworkParameters.ID_REGTEST); - - FederationArgs federation1Args = new FederationArgs(federation1Members, federation1CreationTime, creationBlockNumber, btcParams); - private final Federation federation1 = FederationFactory.buildStandardMultiSigFederation(federation1Args); - - List federation2Members = getFederationMembersFromPksForBtc(2000, 3000, 4000, 5000, 6000, 7000); - Instant federation2CreationTime = Instant.ofEpochMilli(15300L); - FederationArgs federation2Args = new FederationArgs(federation2Members, federation2CreationTime, creationBlockNumber, btcParams); - private final Federation federation2 = FederationFactory.buildStandardMultiSigFederation(federation2Args); - - List federation3Members = getFederationMembersFromPksForBtc(5000, 6000, 7000); - Instant federation3CreationTime = Instant.ofEpochMilli(7400L); - FederationArgs federation3Args = new FederationArgs(federation3Members, federation3CreationTime, creationBlockNumber, btcParams); - private final Federation federation3 = FederationFactory.buildStandardMultiSigFederation(federation3Args); - - private FederationProvider federationProvider; - private Ethereum ethereumMock; - private FederationWatcher watcher; - - @BeforeEach - void createMocksAndWatcher() { - federationProvider = mock(FederationProvider.class); - ethereumMock = mock(Ethereum.class); - watcher = new FederationWatcher(ethereumMock); - } + + // Constants for network and block information + private static final NetworkParameters NETWORK_PARAMETERS = NetworkParameters.fromID(NetworkParameters.ID_REGTEST); + private static final long CREATION_BLOCK_NUMBER = 0L; + + // First federation constants + private static final List FIRST_FEDERATION_MEMBERS = + getFederationMembersFromPksForBtc(1000, 2000, 3000, 4000); + private static final Instant FIRST_FEDERATION_CREATION_TIME = Instant.ofEpochMilli(5005L); + private static final FederationArgs FIRST_FEDERATION_ARGS = new FederationArgs( + FIRST_FEDERATION_MEMBERS, FIRST_FEDERATION_CREATION_TIME, CREATION_BLOCK_NUMBER, NETWORK_PARAMETERS); + private static final Federation FIRST_FEDERATION = FederationFactory.buildStandardMultiSigFederation(FIRST_FEDERATION_ARGS); + + // Second federation constants + private static final List SECOND_FEDERATION_MEMBERS = + getFederationMembersFromPksForBtc(2000, 3000, 4000, 5000, 6000, 7000); + private static final Instant SECOND_FEDERATION_CREATION_TIME = Instant.ofEpochMilli(15300L); + private static final FederationArgs SECOND_FEDERATION_ARGS = new FederationArgs( + SECOND_FEDERATION_MEMBERS, SECOND_FEDERATION_CREATION_TIME, CREATION_BLOCK_NUMBER, NETWORK_PARAMETERS); + private static final Federation SECOND_FEDERATION = FederationFactory.buildStandardMultiSigFederation(SECOND_FEDERATION_ARGS); + + // Third federation constants + private static final List THIRD_FEDERATION_MEMBERS = + getFederationMembersFromPksForBtc(5000, 6000, 7000); + private static final Instant THIRD_FEDERATION_CREATION_TIME = Instant.ofEpochMilli(7400L); + private static final FederationArgs THIRD_FEDERATION_ARGS = new FederationArgs( + THIRD_FEDERATION_MEMBERS, THIRD_FEDERATION_CREATION_TIME, CREATION_BLOCK_NUMBER, NETWORK_PARAMETERS); + private static final Federation THIRD_FEDERATION = FederationFactory.buildStandardMultiSigFederation(THIRD_FEDERATION_ARGS); + + private final FederationProvider federationProvider = mock(FederationProvider.class); + private final Ethereum ethereum = mock(Ethereum.class); + private final FederationWatcher federationWatcher = new FederationWatcher(ethereum); @Test - void setsListenerUp() throws Exception { - Mockito.doAnswer((InvocationOnMock m) -> { + void whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { + // Arrange + doAnswer((InvocationOnMock m) -> { Object listener = m.getArgument(0); - assertEquals("co.rsk.federate.FederationWatcher$FederationWatcherRskListener", listener.getClass().getName()); - assertSame(TestUtils.getInternalState(watcher, "federationProvider"), federationProvider); + assertEquals(FederationWatcher.FederationWatcherRskListener.class, listener.getClass()); return null; - }).when(ethereumMock).addListener(any()); + }).when(ethereum).addListener(any()); - watcher.setup(federationProvider); - verify(ethereumMock).addListener(any()); + // Act + federationWatcher.setup(federationProvider); + + // Assert + verify(ethereum).addListener(any()); + assertSame(TestUtils.getInternalState(federationWatcher, "federationProvider"), federationProvider); } @Test - void triggersActiveFederationChange_none_to_active() throws Exception { - EthereumListenerAdapter rskListener = setupAndGetRskListener(Optional.empty(), Optional.empty()); - class EventsLogger - { - public int activeCalls = 0; - public int retiringCalls = 0; - } - - when(federationProvider.getActiveFederationAddress()).thenReturn(federation1.getAddress()); - when(federationProvider.getActiveFederation()).thenReturn(federation1); + void whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(Optional.empty(), Optional.empty()); + var activeCalls = new AtomicInteger(0); + var retiringCalls = new AtomicInteger(0); + + when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); + when(federationProvider.getActiveFederation()).thenReturn(FIRST_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); - EventsLogger logger = new EventsLogger(); - - for (int i = 0; i < 2; i++) { - watcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - assertEquals(Optional.empty(), oldFederation); - assertEquals(federation1, newFederation); - logger.activeCalls++; - } - - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - logger.retiringCalls++; - } - }); - } - + federationWatcher.addListener(new FederationWatcher.Listener() { + @Override + public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + assertEquals(Optional.empty(), oldFederation); + assertEquals(FIRST_FEDERATION, newFederation); + activeCalls.incrementAndGet(); + } + + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + retiringCalls.incrementAndGet(); + } + }); + + // Act rskListener.onBestBlock(null, null); - assertEquals(2, logger.activeCalls); - assertEquals(0, logger.retiringCalls); - verify(federationProvider, times(1)).getActiveFederationAddress(); - verify(federationProvider, times(1)).getRetiringFederationAddress(); - verify(federationProvider, times(1)).getActiveFederation(); + + // Assert + assertEquals(1, activeCalls.get()); + assertEquals(0, retiringCalls.get()); + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getRetiringFederationAddress(); + verify(federationProvider).getActiveFederation(); verify(federationProvider, never()).getRetiringFederation(); } @Test - void triggersActiveFederationChange_active_to_otherActive() throws Exception { - EthereumListenerAdapter rskListener = setupAndGetRskListener(Optional.of(federation1), Optional.empty()); - class EventsLogger - { - public int activeCalls = 0; - public int retiringCalls = 0; - } - - when(federationProvider.getActiveFederationAddress()).thenReturn(federation2.getAddress()); - when(federationProvider.getActiveFederation()).thenReturn(federation2); + void whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(Optional.of(FIRST_FEDERATION), Optional.empty()); + var activeCalls = new AtomicInteger(0); + var retiringCalls = new AtomicInteger(0); + + when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); + when(federationProvider.getActiveFederation()).thenReturn(SECOND_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); - EventsLogger logger = new EventsLogger(); - - for (int i = 0; i < 2; i++) { - watcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - assertEquals(Optional.of(federation1), oldFederation); - assertEquals(federation2, newFederation); - logger.activeCalls++; - } - - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - logger.retiringCalls++; - } - }); - } - + federationWatcher.addListener(new FederationWatcher.Listener() { + @Override + public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + assertEquals(Optional.of(FIRST_FEDERATION), oldFederation); + assertEquals(SECOND_FEDERATION, newFederation); + activeCalls.incrementAndGet(); + } + + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + retiringCalls.incrementAndGet(); + } + }); + + // Act rskListener.onBestBlock(null, null); - assertEquals(2, logger.activeCalls); - assertEquals(0, logger.retiringCalls); - verify(federationProvider, times(1)).getActiveFederationAddress(); - verify(federationProvider, times(1)).getRetiringFederationAddress(); - verify(federationProvider, times(1)).getActiveFederation(); + + // Assert + assertEquals(1, activeCalls.get()); + assertEquals(0, retiringCalls.get()); + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getRetiringFederationAddress(); + verify(federationProvider).getActiveFederation(); verify(federationProvider, never()).getRetiringFederation(); } @Test - void doesntTriggerActiveOrRetiringFederationChange_none() throws Exception { - EthereumListenerAdapter rskListener = setupAndGetRskListener(Optional.of(federation1), Optional.empty()); - class EventsLogger - { - public int activeCalls = 0; - public int retiringCalls = 0; - } - - when(federationProvider.getActiveFederationAddress()).thenReturn(federation1.getAddress()); - when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + void whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(Optional.of(FIRST_FEDERATION), Optional.empty()); + var activeCalls = new AtomicInteger(0); + var retiringCalls = new AtomicInteger(0); - EventsLogger logger = new EventsLogger(); + when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); - for (int i = 0; i < 2; i++) { - watcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - logger.activeCalls++; - } + federationWatcher.addListener(new FederationWatcher.Listener() { + @Override + public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + activeCalls.incrementAndGet(); + } - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - logger.retiringCalls++; - } - }); - } + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + retiringCalls.incrementAndGet(); + } + }); + // Act rskListener.onBestBlock(null, null); - assertEquals(0, logger.activeCalls); - assertEquals(0, logger.retiringCalls); - verify(federationProvider, times(1)).getActiveFederationAddress(); - verify(federationProvider, times(1)).getRetiringFederationAddress(); + + // Assert + assertEquals(0, activeCalls.get()); + assertEquals(0, retiringCalls.get()); + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); verify(federationProvider, never()).getRetiringFederation(); } @Test - void doesntTriggerActiveOrRetiringFederationChange_noChange() throws Exception { - EthereumListenerAdapter rskListener = setupAndGetRskListener(Optional.of(federation1), Optional.of(federation2)); - class EventsLogger - { - public int activeCalls = 0; - public int retiringCalls = 0; - } - - when(federationProvider.getActiveFederationAddress()).thenReturn(federation1.getAddress()); - when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(federation2.getAddress())); - - EventsLogger logger = new EventsLogger(); - - for (int i = 0; i < 2; i++) { - watcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - logger.activeCalls++; - } - - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - logger.retiringCalls++; - } - }); - } - + void whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(Optional.of(FIRST_FEDERATION), Optional.of(SECOND_FEDERATION)); + var activeCalls = new AtomicInteger(0); + var retiringCalls = new AtomicInteger(0); + + when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); + + federationWatcher.addListener(new FederationWatcher.Listener() { + @Override + public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + activeCalls.incrementAndGet(); + } + + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + retiringCalls.incrementAndGet(); + } + }); + + // Act rskListener.onBestBlock(null, null); - assertEquals(0, logger.activeCalls); - assertEquals(0, logger.retiringCalls); - verify(federationProvider, times(1)).getActiveFederationAddress(); - verify(federationProvider, times(1)).getRetiringFederationAddress(); + + // Assert + assertEquals(0, activeCalls.get()); + assertEquals(0, retiringCalls.get()); + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); verify(federationProvider, never()).getRetiringFederation(); } @Test - void triggersRetiringFederationChange_none_to_retiring() throws Exception { - EthereumListenerAdapter rskListener = setupAndGetRskListener(Optional.of(federation2), Optional.empty()); - class EventsLogger - { - public int activeCalls = 0; - public int retiringCalls = 0; - } - - when(federationProvider.getActiveFederationAddress()).thenReturn(federation2.getAddress()); - when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(federation1.getAddress())); - when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(federation1)); - - EventsLogger logger = new EventsLogger(); - - for (int i = 0; i < 2; i++) { - watcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - logger.activeCalls++; - } - - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.empty(), oldFederation); - assertEquals(Optional.of(federation1), newFederation); - logger.retiringCalls++; - } - }); - } - + void whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(Optional.of(SECOND_FEDERATION), Optional.empty()); + var activeCalls = new AtomicInteger(0); + var retiringCalls = new AtomicInteger(0); + + when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(FIRST_FEDERATION.getAddress())); + when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(FIRST_FEDERATION)); + + federationWatcher.addListener(new FederationWatcher.Listener() { + @Override + public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + activeCalls.incrementAndGet(); + } + + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + assertEquals(Optional.empty(), oldFederation); + assertEquals(Optional.of(FIRST_FEDERATION), newFederation); + retiringCalls.incrementAndGet(); + } + }); + + // Act rskListener.onBestBlock(null, null); - assertEquals(0, logger.activeCalls); - assertEquals(2, logger.retiringCalls); - verify(federationProvider, times(1)).getActiveFederationAddress(); - verify(federationProvider, times(1)).getRetiringFederationAddress(); + + // Assert + assertEquals(0, activeCalls.get()); + assertEquals(1, retiringCalls.get()); + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); - verify(federationProvider, times(1)).getRetiringFederation(); + verify(federationProvider).getRetiringFederation(); } @Test - void triggersRetiringFederationChange_retiring_to_none() throws Exception { - EthereumListenerAdapter rskListener = setupAndGetRskListener(Optional.of(federation2), Optional.of(federation1)); - class EventsLogger - { - public int activeCalls = 0; - public int retiringCalls = 0; - } - - when(federationProvider.getActiveFederationAddress()).thenReturn(federation2.getAddress()); + void whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(Optional.of(SECOND_FEDERATION), Optional.of(FIRST_FEDERATION)); + var activeCalls = new AtomicInteger(0); + var retiringCalls = new AtomicInteger(0); + + when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getRetiringFederation()).thenReturn(Optional.empty()); - EventsLogger logger = new EventsLogger(); - - for (int i = 0; i < 2; i++) { - watcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - logger.activeCalls++; - } - - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.of(federation1), oldFederation); - assertEquals(Optional.empty(), newFederation); - logger.retiringCalls++; - } - }); - } - + federationWatcher.addListener(new FederationWatcher.Listener() { + @Override + public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + activeCalls.incrementAndGet(); + } + + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + assertEquals(Optional.of(FIRST_FEDERATION), oldFederation); + assertEquals(Optional.empty(), newFederation); + retiringCalls.incrementAndGet(); + } + }); + + // Act rskListener.onBestBlock(null, null); - assertEquals(0, logger.activeCalls); - assertEquals(2, logger.retiringCalls); - verify(federationProvider, times(1)).getActiveFederationAddress(); - verify(federationProvider, times(1)).getRetiringFederationAddress(); + + // Assert + assertEquals(0, activeCalls.get()); + assertEquals(1, retiringCalls.get()); + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); - verify(federationProvider, times(1)).getRetiringFederation(); + verify(federationProvider).getRetiringFederation(); } @Test - void triggersRetiringFederationChange_retiring_to_otherRetiring() throws Exception { - EthereumListenerAdapter rskListener = setupAndGetRskListener(Optional.of(federation3), Optional.of(federation1)); - class EventsLogger - { - public int activeCalls = 0; - public int retiringCalls = 0; - } - - when(federationProvider.getActiveFederationAddress()).thenReturn(federation3.getAddress()); - when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(federation2.getAddress())); - when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(federation2)); - - EventsLogger logger = new EventsLogger(); - - for (int i = 0; i < 2; i++) { - watcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - logger.activeCalls++; - } - - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.of(federation1), oldFederation); - assertEquals(Optional.of(federation2), newFederation); - logger.retiringCalls++; - } - }); - } - + void whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(Optional.of(THIRD_FEDERATION), Optional.of(FIRST_FEDERATION)); + var activeCalls = new AtomicInteger(0); + var retiringCalls = new AtomicInteger(0); + + when(federationProvider.getActiveFederationAddress()).thenReturn(THIRD_FEDERATION.getAddress()); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); + when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(SECOND_FEDERATION)); + + federationWatcher.addListener(new FederationWatcher.Listener() { + @Override + public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + activeCalls.incrementAndGet(); + } + + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + assertEquals(Optional.of(FIRST_FEDERATION), oldFederation); + assertEquals(Optional.of(SECOND_FEDERATION), newFederation); + retiringCalls.incrementAndGet(); + } + }); + + // Act rskListener.onBestBlock(null, null); - assertEquals(0, logger.activeCalls); - assertEquals(2, logger.retiringCalls); - verify(federationProvider, times(1)).getActiveFederationAddress(); - verify(federationProvider, times(1)).getRetiringFederationAddress(); + + // Assert + assertEquals(0, activeCalls.get()); + assertEquals(1, retiringCalls.get()); + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); - verify(federationProvider, times(1)).getRetiringFederation(); + verify(federationProvider).getRetiringFederation(); } - private EthereumListenerAdapter setupAndGetRskListener(Optional activeFederation, Optional retiringFederation) throws Exception { - class ListenerHolder { - public EthereumListenerAdapter listener = null; - } - - final ListenerHolder holder = new ListenerHolder(); - Mockito.doAnswer((InvocationOnMock m) -> { - holder.listener = m.getArgument(0); + private EthereumListenerAdapter setupAndGetRskListener( + Optional activeFederation, Optional retiringFederation) throws Exception { + // Mock the behavior of adding a listener + AtomicReference listenerRef = new AtomicReference<>(); + doAnswer((InvocationOnMock m) -> { + listenerRef.set(m.getArgument(0)); return null; - }).when(ethereumMock).addListener(any()); - watcher.setup(federationProvider); - TestUtils.setInternalState(watcher, "activeFederation", activeFederation); - TestUtils.setInternalState(watcher, "retiringFederation", retiringFederation); - assertNotNull(holder.listener); - return holder.listener; + }).when(ethereum).addListener(any()); + + // Set up federationWatcher and internal states + federationWatcher.setup(federationProvider); + TestUtils.setInternalState(federationWatcher, "activeFederation", activeFederation); + TestUtils.setInternalState(federationWatcher, "retiringFederation", retiringFederation); + + // Retrieve and return the listener + EthereumListenerAdapter listener = listenerRef.get(); + assertNotNull(listener); + return listener; } - private List getFederationMembersFromPksForBtc(Integer... pks) { + private static List getFederationMembersFromPksForBtc(Integer... pks) { return Arrays.stream(pks).map(n -> new FederationMember( - BtcECKey.fromPrivate(BigInteger.valueOf(n)), - new ECKey(), - new ECKey() - )).collect(Collectors.toList()); + BtcECKey.fromPrivate(BigInteger.valueOf(n)), + new ECKey(), + new ECKey())).toList(); } } From 2ad52bd7336f590bb0af8e1f086c6783f1a5e5d3 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:53:40 +0200 Subject: [PATCH 002/112] feat(federate): change access modifier for federation watcher listerner --- src/main/java/co/rsk/federate/FederationWatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/FederationWatcher.java b/src/main/java/co/rsk/federate/FederationWatcher.java index 6a130039f..ef5df50df 100644 --- a/src/main/java/co/rsk/federate/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/FederationWatcher.java @@ -48,7 +48,7 @@ public void addListener(Listener listener) { listeners.add(listener); } - public class FederationWatcherRskListener extends EthereumListenerAdapter { + class FederationWatcherRskListener extends EthereumListenerAdapter { @Override public void onBestBlock(org.ethereum.core.Block block, List receipts) { // Updating state only when the best block changes still "works", From 2f64531ca980383b4b69aa12995ebe7b4a99b6a6 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:37:37 +0000 Subject: [PATCH 003/112] fix(btcreleaseclient): remove signature cache dependency from BridgeEventLoggerImpl in tests --- .../BtcReleaseClientStorageSynchronizerTest.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientStorageSynchronizerTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientStorageSynchronizerTest.java index 4eb8bc252..5c2679542 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientStorageSynchronizerTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientStorageSynchronizerTest.java @@ -186,13 +186,10 @@ void processBlock_ok() { TransactionReceipt receipt = mock(TransactionReceipt.class); List logs = new ArrayList<>(); - SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); - BridgeEventLoggerImpl bridgeEventLogger = new BridgeEventLoggerImpl( new BridgeRegTestConstants(), activations, - logs, - signatureCache + logs ); Keccak256 value = createHash(3); @@ -282,13 +279,10 @@ void accepts_transaction_with_two_release_requested() { TransactionReceipt receipt = mock(TransactionReceipt.class); List logs = new ArrayList<>(); - SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); - BridgeEventLoggerImpl bridgeEventLogger = new BridgeEventLoggerImpl( new BridgeRegTestConstants(), activations, - logs, - signatureCache + logs ); Keccak256 releaseRequestTxHash = createHash(3); From 44d4e581abf7bba9c6197b70f7a78ef1092de5cc Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:38:16 +0000 Subject: [PATCH 004/112] feat(federate): implement getProposedFederationAddress in federation support --- .../co/rsk/federate/FederationProvider.java | 5 +- ...ederationProviderFromFederatorSupport.java | 10 ++- .../co/rsk/federate/FederatorSupport.java | 10 ++- ...ationProviderFromFederatorSupportTest.java | 44 ++++++++++-- .../co/rsk/federate/FederatorSupportTest.java | 71 +++++++++++++++++-- .../federate/bitcoin/BitcoinTestUtils.java | 2 + 6 files changed, 126 insertions(+), 16 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProvider.java b/src/main/java/co/rsk/federate/FederationProvider.java index 244bf2749..11d8a750c 100644 --- a/src/main/java/co/rsk/federate/FederationProvider.java +++ b/src/main/java/co/rsk/federate/FederationProvider.java @@ -15,12 +15,10 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package co.rsk.federate; import co.rsk.bitcoinj.core.Address; import co.rsk.peg.federation.Federation; - import java.util.List; import java.util.Optional; @@ -41,6 +39,9 @@ public interface FederationProvider { // The currently "retiring" federation's address Optional
getRetiringFederationAddress(); + // The currently "proposed" federation's address + Optional
getProposedFederationAddress(); + // The federations that are "live", that is, are still // operational. This should be the active federation // plus the retiring federation, if one exists diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 56e4a8ed7..173034a32 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -15,9 +15,10 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package co.rsk.federate; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; + import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; import co.rsk.bitcoinj.core.NetworkParameters; @@ -25,12 +26,10 @@ import co.rsk.peg.federation.constants.FederationConstants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.crypto.ECKey; - import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; /** * Provides a federation using a FederatorSupport instance, which in turn @@ -136,6 +135,11 @@ public Optional
getRetiringFederationAddress() { return federatorSupport.getRetiringFederationAddress(); } + @Override + public Optional
getProposedFederationAddress() { + return federatorSupport.getProposedFederationAddress(); + } + @Override public List getLiveFederations() { List result = new ArrayList<>(); diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index 47b746c6f..f75da5bfc 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -19,7 +19,6 @@ import org.ethereum.crypto.ECKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.math.BigInteger; import java.net.UnknownHostException; import java.time.Instant; @@ -253,6 +252,15 @@ public Instant getRetiringFederationCreationTime() { return Instant.ofEpochMilli(creationTime.longValue()); } + public Optional
getProposedFederationAddress() { + String proposedFederationAddress = bridgeTransactionSender.callTx( + federatorAddress, Bridge.GET_PROPOSED_FEDERATION_ADDRESS); + + return Optional.ofNullable(proposedFederationAddress) + .filter(addr -> !addr.isEmpty()) + .map(addr -> Address.fromBase58(getBtcParams(), addr)); + } + public int getBtcBlockchainBestChainHeight() { BigInteger btcBlockchainBestChainHeight = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_BTC_BLOCKCHAIN_BEST_CHAIN_HEIGHT); return btcBlockchainBestChainHeight.intValue(); diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 60aa1788e..f3ce9769f 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -3,6 +3,7 @@ import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP284; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -13,8 +14,9 @@ import co.rsk.bitcoinj.core.BtcECKey; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.bitcoinj.script.Script; +import co.rsk.federate.bitcoin.BitcoinTestUtils; +import co.rsk.peg.constants.BridgeMainNetConstants; import co.rsk.peg.federation.*; - import co.rsk.peg.federation.constants.FederationConstants; import co.rsk.peg.federation.constants.FederationTestNetConstants; import java.math.BigInteger; @@ -30,16 +32,15 @@ import org.junit.jupiter.api.Test; class FederationProviderFromFederatorSupportTest { - private FederatorSupport federatorSupportMock; - private FederationProvider federationProvider; - private FederationConstants federationConstants; - private NetworkParameters testnetParams; - private Instant creationTime; private static final int STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION = FederationFormatVersion.STANDARD_MULTISIG_FEDERATION.getFormatVersion(); private static final int NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION = FederationFormatVersion.NON_STANDARD_ERP_FEDERATION.getFormatVersion(); private static final int P2SH_ERP_FEDERATION_FORMAT_VERSION = FederationFormatVersion.P2SH_ERP_FEDERATION.getFormatVersion(); + private static final NetworkParameters NETWORK_PARAMETERS = BridgeMainNetConstants.getInstance().getBtcParams(); + private static final List KEYS = BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"k1", "k2", "k3"}, true); + private static final Address DEFAULT_ADDRESS = BitcoinTestUtils.createP2SHMultisigAddress(NETWORK_PARAMETERS, KEYS); + private static final Address HARDCODED_TESTNET_FED_ADDRESS = Address.fromBase58( NetworkParameters.fromID(NetworkParameters.ID_TESTNET), "2Mw6KM642fbkypTzbgFi6DTgTFPRWZUD4BA" @@ -48,6 +49,12 @@ class FederationProviderFromFederatorSupportTest { Hex.decode("6453210208f40073a9e43b3e9103acec79767a6de9b0409749884e989960fee578012fce210225e892391625854128c5c4ea4340de0c2a70570f33db53426fc9c746597a03f42102afc230c2d355b1a577682b07bc2646041b5d0177af0f98395a46018da699b6da210344a3c38cd59afcba3edcebe143e025574594b001700dec41e59409bdbd0f2a0921039a060badbeb24bee49eb2063f616c0f0f0765d4ca646b20a88ce828f259fcdb955670300cd50b27552210216c23b2ea8e4f11c3f9e22711addb1d16a93964796913830856b568cc3ea21d3210275562901dd8faae20de0a4166362a4f82188db77dbed4ca887422ea1ec185f1421034db69f2112f4fb1bb6141bf6e2bd6631f0484d0bd95b16767902c9fe219d4a6f5368ae") ); + private FederatorSupport federatorSupportMock; + private FederationProvider federationProvider; + private FederationConstants federationConstants; + private NetworkParameters testnetParams; + private Instant creationTime; + @BeforeEach void createProvider() { federationConstants = FederationTestNetConstants.getInstance(); @@ -363,6 +370,31 @@ void getRetiringFederation_present_p2sh_erp_federation() { assertEquals(expectedFederationAddress, obtainedFederation.getAddress()); } + @Test + void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { + // Arrange + when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(DEFAULT_ADDRESS)); + + // Act + Optional
result = federationProvider.getProposedFederationAddress(); + + // Assert + assertTrue(result.isPresent()); + assertEquals(DEFAULT_ADDRESS, result.get()); + } + + @Test + void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional() { + // Arrange + when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.empty()); + + // Act + Optional
result = federationProvider.getProposedFederationAddress(); + + // Assert + assertFalse(result.isPresent()); + } + @Test void getLiveFederations_onlyActive_beforeMultikey() { ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index a9701b960..ab2be771d 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -4,24 +4,30 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import co.rsk.peg.constants.BridgeMainNetConstants; import co.rsk.peg.constants.BridgeRegTestConstants; +import co.rsk.bitcoinj.core.Address; +import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.federate.adapter.ThinConverter; import co.rsk.federate.config.TestSystemProperties; import co.rsk.peg.Bridge; import co.rsk.peg.BridgeMethods; +import co.rsk.federate.bitcoin.BitcoinTestUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.bitcoinj.core.Address; +import java.util.Optional; import org.bitcoinj.core.Block; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.PartialMerkleTree; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.TransactionInput; @@ -29,15 +35,30 @@ import org.bitcoinj.core.TransactionWitness; import org.bouncycastle.util.encoders.Hex; import org.ethereum.core.Blockchain; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.stubbing.Answer; class FederatorSupportTest { - private final NetworkParameters networkParameters = ThinConverter.toOriginalInstance(new BridgeRegTestConstants().getBtcParamsString()); + private static final NetworkParameters NETWORK_PARAMETERS = BridgeMainNetConstants.getInstance().getBtcParams(); + private static final List KEYS = BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"k1", "k2", "k3"}, true); + private static final Address DEFAULT_ADDRESS = BitcoinTestUtils.createP2SHMultisigAddress(NETWORK_PARAMETERS, KEYS); + + private BridgeTransactionSender bridgeTransactionSender; + private FederatorSupport federatorSupport; + + @BeforeEach + void setup() { + bridgeTransactionSender = mock(BridgeTransactionSender.class); + federatorSupport = new FederatorSupport( + mock(Blockchain.class), new TestSystemProperties(), bridgeTransactionSender); + } @Test void sendReceiveHeadersSendsBlockHeaders() { + org.bitcoinj.core.NetworkParameters networkParameters = + ThinConverter.toOriginalInstance(BridgeMainNetConstants.getInstance().getBtcParamsString()); BridgeTransactionSender bridgeTransactionSender = mock(BridgeTransactionSender.class); FederatorSupport instance = new FederatorSupport( @@ -67,6 +88,8 @@ void sendReceiveHeadersSendsBlockHeaders() { @Test void sendRegisterCoinbaseTransaction() throws Exception { BridgeTransactionSender bridgeTransactionSender = mock(BridgeTransactionSender.class); + org.bitcoinj.core.NetworkParameters networkParameters = + ThinConverter.toOriginalInstance(BridgeMainNetConstants.getInstance().getBtcParamsString()); FederatorSupport fs = new FederatorSupport( mock(Blockchain.class), @@ -81,7 +104,7 @@ void sendRegisterCoinbaseTransaction() throws Exception { input.setWitness(witness); coinbaseTx.addInput(input); TransactionOutput output = new TransactionOutput(networkParameters, null, Coin.COIN, - Address.fromString(networkParameters, "mvbnrCX3bg1cDRUu8pkecrvP6vQkSLDSou")); + org.bitcoinj.core.Address.fromString(networkParameters, DEFAULT_ADDRESS.toBase58())); coinbaseTx.addOutput(output); List hashes = Collections.singletonList(Sha256Hash.ZERO_HASH); @@ -122,6 +145,46 @@ void hasBlockCoinbaseInformed() { assertTrue(fs.hasBlockCoinbaseInformed(Sha256Hash.ZERO_HASH)); } + + @Test + void getProposedFederationAddress_whenAddressStringIsEmpty_shouldReturnEmptyOptional() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_ADDRESS))) + .thenReturn(""); + + // Act + Optional
result = federatorSupport.getProposedFederationAddress(); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getProposedFederation_whenAddressStringIsNull_shouldReturnEmptyOptional() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_ADDRESS))) + .thenReturn(null); + + // Act + Optional
result = federatorSupport.getProposedFederationAddress(); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getProposedFederation_whenAddressStringIsValid_shouldReturnAddress() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_ADDRESS))) + .thenReturn(DEFAULT_ADDRESS.toBase58()); + + // Act + Optional
result = federatorSupport.getProposedFederationAddress(); + + // Assert + assertTrue(result.isPresent()); + assertEquals(DEFAULT_ADDRESS.toString(), result.get().toString()); + } private Sha256Hash createHash() { byte[] bytes = new byte[32]; diff --git a/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java b/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java index 4eeaf92c0..fe04e4e20 100644 --- a/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java +++ b/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java @@ -5,6 +5,8 @@ import co.rsk.bitcoinj.core.Coin; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.bitcoinj.core.Sha256Hash; +import co.rsk.bitcoinj.script.Script; +import co.rsk.bitcoinj.script.ScriptBuilder; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; From 0d1fc7684147457f4e59ae50a784417ec11b6291 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:23:47 +0000 Subject: [PATCH 005/112] feat(bitcoin): add bitcoin test util methods --- .../federate/bitcoin/BitcoinTestUtils.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java b/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java index fe04e4e20..968e00ce7 100644 --- a/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java +++ b/src/test/java/co/rsk/federate/bitcoin/BitcoinTestUtils.java @@ -33,9 +33,32 @@ public static Sha256Hash createHash(int nHash) { return Sha256Hash.wrap(bytes); } + public static BtcECKey getBtcEcKeyFromSeed(String seed) { + byte[] serializedSeed = HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8)); + return BtcECKey.fromPrivate(serializedSeed); + } + + public static List getBtcEcKeysFromSeeds(String[] seeds, boolean sorted) { + List keys = Arrays.stream(seeds) + .map(BitcoinTestUtils::getBtcEcKeyFromSeed) + .collect(Collectors.toList()); + + if (sorted) { + keys.sort(BtcECKey.PUBKEY_COMPARATOR); + } + + return keys; + } + public static Address createP2PKHAddress(NetworkParameters networkParameters, String seed) { BtcECKey key = BtcECKey.fromPrivate( HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8))); return key.toAddress(networkParameters); } + + public static Address createP2SHMultisigAddress(NetworkParameters networkParameters, List keys) { + Script redeemScript = ScriptBuilder.createRedeemScript((keys.size() / 2) + 1, keys); + Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); + return Address.fromP2SHScript(networkParameters, outputScript); + } } From 014129636d222ff7f26a768d3c5adc2061860709 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 21 Oct 2024 09:45:48 +0000 Subject: [PATCH 006/112] refactor(watcher): refactor federation watcher to its own package --- .../java/co/rsk/federate/FedNodeContext.java | 1 + .../java/co/rsk/federate/FedNodeRunner.java | 50 ++------ .../co/rsk/federate/FederationWatcher.java | 104 ---------------- .../federate/watcher/FederationWatcher.java | 113 ++++++++++++++++++ .../watcher/FederationWatcherListener.java | 31 +++++ .../FederationWatcherListenerImpl.java | 78 ++++++++++++ .../co/rsk/federate/FedNodeRunnerTest.java | 1 + .../{ => watcher}/FederationWatcherTest.java | 79 ++++++------ 8 files changed, 269 insertions(+), 188 deletions(-) delete mode 100644 src/main/java/co/rsk/federate/FederationWatcher.java create mode 100644 src/main/java/co/rsk/federate/watcher/FederationWatcher.java create mode 100644 src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java create mode 100644 src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java rename src/test/java/co/rsk/federate/{ => watcher}/FederationWatcherTest.java (89%) diff --git a/src/main/java/co/rsk/federate/FedNodeContext.java b/src/main/java/co/rsk/federate/FedNodeContext.java index 88a76fe83..cb7abecfd 100644 --- a/src/main/java/co/rsk/federate/FedNodeContext.java +++ b/src/main/java/co/rsk/federate/FedNodeContext.java @@ -29,6 +29,7 @@ import co.rsk.federate.signing.hsm.advanceblockchain.HSMBookKeepingClientProvider; import co.rsk.federate.signing.hsm.client.HSMClientProtocolFactory; import co.rsk.federate.solidity.DummySolidityCompiler; +import co.rsk.federate.watcher.FederationWatcher; import java.util.concurrent.TimeUnit; import org.ethereum.rpc.Web3; import org.ethereum.solidity.compiler.SolidityCompiler; diff --git a/src/main/java/co/rsk/federate/FedNodeRunner.java b/src/main/java/co/rsk/federate/FedNodeRunner.java index 705922c83..7e1305879 100644 --- a/src/main/java/co/rsk/federate/FedNodeRunner.java +++ b/src/main/java/co/rsk/federate/FedNodeRunner.java @@ -52,7 +52,9 @@ import co.rsk.federate.signing.hsm.message.SignerMessageBuilderFactory; import co.rsk.federate.signing.hsm.requirements.AncestorBlockUpdater; import co.rsk.federate.signing.hsm.requirements.ReleaseRequirementsEnforcer; -import co.rsk.peg.federation.Federation; +import co.rsk.federate.watcher.FederationWatcher; +import co.rsk.federate.watcher.FederationWatcherListener; +import co.rsk.federate.watcher.FederationWatcherListenerImpl; import co.rsk.peg.federation.FederationMember; import co.rsk.peg.btcLockSender.BtcLockSenderProvider; import co.rsk.peg.pegininstructions.PeginInstructionsProvider; @@ -65,7 +67,6 @@ import java.net.UnknownHostException; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.stream.Stream; /** @@ -342,27 +343,14 @@ private void startFederate() throws Exception { config.getBtcReleaseClientInitializationMaxDepth() ) ); - federationWatcher.setup(federationProvider); - - federationWatcher.addListener(new FederationWatcher.Listener() { - @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { - String oldFederationAddress = oldFederation.map(f -> f.getAddress().toString()).orElse("NONE"); - String newFederationAddress = newFederation.getAddress().toString(); - logger.debug(String.format("[onActiveFederationChange] Active federation change: from %s to %s", oldFederationAddress, newFederationAddress)); - triggerClientChange(btcToRskClientActive, Optional.of(newFederation)); - } - - @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - String oldFederationAddress = oldFederation.map(f -> f.getAddress().toString()).orElse("NONE"); - String newFederationAddress = newFederation.map(f -> f.getAddress().toString()).orElse("NONE"); - logger.debug(String.format("[onRetiringFederationChange] Retiring federation change: from %s to %s", oldFederationAddress, newFederationAddress)); - triggerClientChange(btcToRskClientRetiring, newFederation); - } - }); - // Trigger the first events - federationWatcher.updateState(); + + FederationWatcherListener federationWatcherListener = new FederationWatcherListenerImpl( + member, + btcToRskClientActive, + btcToRskClientRetiring, + btcReleaseClient); + + federationWatcher.start(federationProvider, federationWatcherListener); } } @@ -384,22 +372,6 @@ private void shutdown() { System.exit(-1); } - // TODO: This this method (and this whole class) - private void triggerClientChange(BtcToRskClient client, Optional federation) { - client.stop(); - federation.ifPresent(btcReleaseClient::stop); - // Only start if this federator is part of the new federation - if (federation.isPresent() && federation.get().isMember(this.member)) { - String federationAddress = federation.get().getAddress().toString(); - logger.debug("[triggerClientChange] Starting lock and release clients since I belong to federation {}", federationAddress); - logger.info("[triggerClientChange] Joined to {} federation", federationAddress); - client.start(federation.get()); - btcReleaseClient.start(federation.get()); - } else { - logger.warn("[triggerClientChange] This federator node is not part of the new federation. Check your configuration for signers BTC, RSK and MST keys"); - } - } - @Override public void stop() { logger.info("[stop] Shutting down Federation node"); diff --git a/src/main/java/co/rsk/federate/FederationWatcher.java b/src/main/java/co/rsk/federate/FederationWatcher.java deleted file mode 100644 index ef5df50df..000000000 --- a/src/main/java/co/rsk/federate/FederationWatcher.java +++ /dev/null @@ -1,104 +0,0 @@ -package co.rsk.federate; - -import co.rsk.bitcoinj.core.Address; -import co.rsk.peg.federation.Federation; -import org.ethereum.core.TransactionReceipt; -import org.ethereum.facade.Ethereum; -import org.ethereum.listener.EthereumListenerAdapter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * Watches the RSK blockchain for federation changes, and informs - * when a federation changes. - * @author Ariel Mendelzon - */ -public class FederationWatcher { - private static final Logger logger = LoggerFactory.getLogger("FederationWatcher"); - - private final Ethereum rsk; - - private FederationProvider federationProvider; - - private List listeners = new ArrayList<>(); - - // Previous recorded federations - private Optional activeFederation = Optional.empty(); - private Optional retiringFederation = Optional.empty(); - - public FederationWatcher(Ethereum rsk) { - this.rsk = rsk; - } - - public interface Listener { - void onActiveFederationChange(Optional oldFederation, Federation newFederation); - void onRetiringFederationChange(Optional oldFederation, Optional newFederation); - } - - public void setup(FederationProvider federationProvider) throws Exception { - this.federationProvider = federationProvider; - rsk.addListener(new FederationWatcherRskListener()); - } - - public void addListener(Listener listener) { - listeners.add(listener); - } - - class FederationWatcherRskListener extends EthereumListenerAdapter { - @Override - public void onBestBlock(org.ethereum.core.Block block, List receipts) { - // Updating state only when the best block changes still "works", - // since we're interested in finding out only when the active or retiring federation(s) changed. - // If there was a side chain in which any of these changed in, say, block 4500, but - // that side chain became main chain in block 7800, it would still be ok to - // start monitoring the new federation(s) on that block. - // The main reasons for this is that RSK nodes would have never reported the active or - // retiring federation(s) being different *before* the best chain change. Therefore, - // there should be no new Bitcoin transactions directed to these new addresses - // until this change effectively becomes a part of RSK's "reality". - // The case in which we go back and forth to and from a sidechain in which the - // federation effectively changed is still to be explored deeply, but the same reasoning - // should apply since going back and forth would trigger two federation changes. - // A client trying to send bitcoins to the new federation without waiting - // a good number of confirmations would be, essentially, "playing with fire". - logger.info("New best block, updating state"); - updateState(); - } - } - - public void updateState() { - // Active federation changed? - // We compare addresses first so as not to do innecessary calls to the bridge - Address currentlyActiveFederationAddress = federationProvider.getActiveFederationAddress(); - if (!currentlyActiveFederationAddress.equals(activeFederation.map(Federation::getAddress).orElse(null))) { - // Gather the active federation and inform listeners of the change - Federation currentlyActiveFederation = federationProvider.getActiveFederation(); - String oldActive = activeFederation.map(f -> f.getAddress().toString()).orElse("NONE"); - String newActive = currentlyActiveFederation.getAddress().toString(); - logger.info(String.format("Active federation changed from %s to %s", oldActive, newActive)); - for (Listener l : listeners) { - l.onActiveFederationChange(activeFederation, currentlyActiveFederation); - } - activeFederation = Optional.of(currentlyActiveFederation); - } - - // Retiring federation changed? - // We compare addresses first so as not to do innecessary calls to the bridge - Optional
currentlyRetiringFederationAddress = federationProvider.getRetiringFederationAddress(); - if (!retiringFederation.map(Federation::getAddress).equals(currentlyRetiringFederationAddress)) { - // Gather the retiring federation and inform listeners of the change - Optional currentlyRetiringFederation = federationProvider.getRetiringFederation(); - String oldRetiring = retiringFederation.map(f -> f.getAddress().toString()).orElse("NONE"); - String newRetiring = currentlyRetiringFederation.map(f -> f.getAddress().toString()).orElse("NONE"); - logger.info(String.format("Retiring federation changed from %s to %s", oldRetiring, newRetiring)); - for (Listener l : listeners) { - l.onRetiringFederationChange(retiringFederation, currentlyRetiringFederation); - } - retiringFederation = currentlyRetiringFederation; - } - } -} diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java new file mode 100644 index 000000000..e54340ad3 --- /dev/null +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -0,0 +1,113 @@ +package co.rsk.federate.watcher; + +import co.rsk.bitcoinj.core.Address; +import co.rsk.federate.FederationProvider; +import co.rsk.peg.federation.Federation; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.facade.Ethereum; +import org.ethereum.listener.EthereumListenerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.Optional; + +/** + * Watches the RSK blockchain for changes to the active and retiring federations. + * This class listens for new blocks in the RSK blockchain and checks if the active or + * retiring federations have changed, notifying listeners when such changes occur. + */ +public class FederationWatcher { + + private static final Logger logger = LoggerFactory.getLogger(FederationWatcher.class); + + private final Ethereum rsk; + + private FederationProvider federationProvider; + private FederationWatcherListener federationWatcherListener; + + private Optional activeFederation = Optional.empty(); + private Optional retiringFederation = Optional.empty(); + + /** + * Constructs a new {@code FederationWatcher} with the specified RSK client. + * + * @param rsk the Ethereum client used to listen for new blocks on the RSK blockchain + */ + public FederationWatcher(Ethereum rsk) { + this.rsk = rsk; + } + + /** + * Starts the {@code FederationWatcher} by setting the {@code FederationProvider} + * and {@code FederationWatcherListener}, and begins listening for new blocks on + * the RSK blockchain. + * + * @param federationProvider the provider used to obtain federation information (active and retiring federations) + * @param federationWatcherListener the listener that will be notified when the federations change + */ + public void start(FederationProvider federationProvider, FederationWatcherListener federationWatcherListener) { + this.federationProvider = federationProvider; + this.federationWatcherListener = federationWatcherListener; + + rsk.addListener(new EthereumListenerAdapter() { + @Override + public void onBestBlock(org.ethereum.core.Block block, List receipts) { + // Updating state only when the best block changes still "works", + // since we're interested in finding out only when the active or retiring federation(s) changed. + // If there was a side chain in which any of these changed in, say, block 4500, but + // that side chain became main chain in block 7800, it would still be ok to + // start monitoring the new federation(s) on that block. + // The main reasons for this is that RSK nodes would have never reported the active or + // retiring federation(s) being different *before* the best chain change. Therefore, + // there should be no new Bitcoin transactions directed to these new addresses + // until this change effectively becomes a part of RSK's "reality". + // The case in which we go back and forth to and from a sidechain in which the + // federation effectively changed is still to be explored deeply, but the same reasoning + // should apply since going back and forth would trigger two federation changes. + // A client trying to send bitcoins to the new federation without waiting + // a good number of confirmations would be, essentially, "playing with fire". + logger.info("[updateState] New best block, updating state"); + updateState(); + } + }); + } + + /** + * Updates the current state of the federations by checking if the active or + * retiring federations have changed. If a federation change is detected, it notifies + * the {@code FederationWatcherListener}. + */ + public void updateState() { + // Check if active federation has changed + Address currentlyActiveFederationAddress = federationProvider.getActiveFederationAddress(); + Address oldActiveFederationAddress = activeFederation.map(Federation::getAddress).orElse(null); + + if (!currentlyActiveFederationAddress.equals(oldActiveFederationAddress)) { + // Active federation changed + Federation currentlyActiveFederation = federationProvider.getActiveFederation(); + logger.info("[updateState] Active federation changed from {} to {}", + oldActiveFederationAddress != null ? oldActiveFederationAddress.toString() : "NONE", + currentlyActiveFederation.getAddress().toString()); + + // Notify listener and update state + federationWatcherListener.onActiveFederationChange(activeFederation, Optional.of(currentlyActiveFederation)); + activeFederation = Optional.of(currentlyActiveFederation); + } + + // Check if retiring federation has changed + Optional
currentlyRetiringFederationAddress = federationProvider.getRetiringFederationAddress(); + Optional
oldRetiringFederationAddress = retiringFederation.map(Federation::getAddress); + + if (!oldRetiringFederationAddress.equals(currentlyRetiringFederationAddress)) { + // Retiring federation changed + Optional currentlyRetiringFederation = federationProvider.getRetiringFederation(); + logger.info("[updateState] Retiring federation changed from {} to {}", + oldRetiringFederationAddress.map(Address::toString).orElse("NONE"), + currentlyRetiringFederation.map(federation -> federation.getAddress().toString()).orElse("NONE")); + + // Notify listener and update state + federationWatcherListener.onRetiringFederationChange(retiringFederation, currentlyRetiringFederation); + retiringFederation = currentlyRetiringFederation; + } + } +} diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java new file mode 100644 index 000000000..3e0c008ea --- /dev/null +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java @@ -0,0 +1,31 @@ +package co.rsk.federate.watcher; + +import co.rsk.peg.federation.Federation; +import java.util.Optional; + +/** + * A listener interface for receiving notifications about changes in federations. + * Implementers of this interface will be notified when the active or retiring federation changes. + */ +public interface FederationWatcherListener { + + /** + * Invoked when the active federation changes. + * + * @param oldFederation an {@code Optional} containing the previous active federation. + * This will be empty if there was no active federation before. + * @param newFederation an {@code Optional} containing the new active federation. + * This will be empty if there is no active federation after the change. + */ + void onActiveFederationChange(Optional oldFederation, Optional newFederation); + + /** + * Invoked when the retiring federation changes. + * + * @param oldFederation an {@code Optional} containing the previous retiring federation. + * This will be empty if there was no retiring federation before. + * @param newFederation an {@code Optional} containing the new retiring federation. + * This will be empty if there is no retiring federation after the change. + */ + void onRetiringFederationChange(Optional oldFederation, Optional newFederation); +} diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java new file mode 100644 index 000000000..ab2648c20 --- /dev/null +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -0,0 +1,78 @@ +package co.rsk.federate.watcher; + +import co.rsk.federate.BtcToRskClient; +import co.rsk.federate.btcreleaseclient.BtcReleaseClient; +import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationMember; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Objects; +import java.util.Optional; + +public class FederationWatcherListenerImpl implements FederationWatcherListener { + + private static final Logger logger = LoggerFactory.getLogger(FederationWatcherListenerImpl.class); + + private final FederationMember federationMember; + private final BtcToRskClient btcToRskClientActive; + private final BtcToRskClient btcToRskClientRetiring; + private final BtcReleaseClient btcReleaseClient; + + public FederationWatcherListenerImpl( + FederationMember federationMember, + BtcToRskClient btcToRskClientActive, + BtcToRskClient btcToRskClientRetiring, + BtcReleaseClient btcReleaseClient) { + this.federationMember = Objects.requireNonNull(federationMember); + this.btcToRskClientActive = Objects.requireNonNull(btcToRskClientActive); + this.btcToRskClientRetiring = Objects.requireNonNull(btcToRskClientRetiring); + this.btcReleaseClient = Objects.requireNonNull(btcReleaseClient); + } + + @Override + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { + String oldFederationAddress = oldFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); + String newFederationAddress = oldFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); + logger.debug("[onActiveFederationChange] Active federation change: from {} to {}", oldFederationAddress, newFederationAddress); + triggerClientChange(btcToRskClientActive, newFederation); + } + + @Override + public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + String oldFederationAddress = oldFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); + String newFederationAddress = newFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); + logger.debug("[onRetiringFederationChange] Retiring federation change: from {} to {}", oldFederationAddress, newFederationAddress); + triggerClientChange(btcToRskClientRetiring, newFederation); + } + + private void triggerClientChange(BtcToRskClient client, Optional federation) { + // Stop the current clients + client.stop(); + federation.ifPresent(btcReleaseClient::stop); + + // Exit early if federation is not present + if (federation.isEmpty()) { + logger.warn("[triggerClientChange] No federation available to join."); + return; + } + + Federation newFederation = federation.get(); + + // Check if this federator is part of the new federation + if (!newFederation.isMember(federationMember)) { + logger.warn( + "[triggerClientChange] This federator node is not part of the new federation. Check your configuration for signers BTC, RSK, and MST keys"); + return; + } + + // Start clients if federator is part of the new federation + String federationAddress = newFederation.getAddress().toString(); + logger.debug( + "[triggerClientChange] Starting lock and release clients since I belong to federation {}", federationAddress); + logger.info( + "[triggerClientChange] Joined to {} federation", federationAddress); + + client.start(newFederation); + btcReleaseClient.start(newFederation); + } +} diff --git a/src/test/java/co/rsk/federate/FedNodeRunnerTest.java b/src/test/java/co/rsk/federate/FedNodeRunnerTest.java index 93ade6460..b4116c876 100644 --- a/src/test/java/co/rsk/federate/FedNodeRunnerTest.java +++ b/src/test/java/co/rsk/federate/FedNodeRunnerTest.java @@ -39,6 +39,7 @@ import co.rsk.federate.signing.hsm.client.HSMClientProtocolFactory; import co.rsk.federate.signing.hsm.message.PowHSMBlockchainParameters; import co.rsk.federate.signing.utils.TestUtils; +import co.rsk.federate.watcher.FederationWatcher; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import java.io.IOException; diff --git a/src/test/java/co/rsk/federate/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java similarity index 89% rename from src/test/java/co/rsk/federate/FederationWatcherTest.java rename to src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index e52f17f2a..5948a4295 100644 --- a/src/test/java/co/rsk/federate/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -1,4 +1,4 @@ -package co.rsk.federate; +package co.rsk.federate.watcher; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -12,6 +12,7 @@ import co.rsk.bitcoinj.core.BtcECKey; import co.rsk.bitcoinj.core.NetworkParameters; +import co.rsk.federate.FederationProvider; import co.rsk.federate.signing.utils.TestUtils; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationArgs; @@ -60,25 +61,20 @@ class FederationWatcherTest { THIRD_FEDERATION_MEMBERS, THIRD_FEDERATION_CREATION_TIME, CREATION_BLOCK_NUMBER, NETWORK_PARAMETERS); private static final Federation THIRD_FEDERATION = FederationFactory.buildStandardMultiSigFederation(THIRD_FEDERATION_ARGS); + private final Ethereum rsk = mock(Ethereum.class); private final FederationProvider federationProvider = mock(FederationProvider.class); - private final Ethereum ethereum = mock(Ethereum.class); - private final FederationWatcher federationWatcher = new FederationWatcher(ethereum); + private final FederationWatcherListener federationWatcherListener = mock(FederationWatcherListener.class); + private final FederationWatcher federationWatcher = new FederationWatcher(rsk); @Test void whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { - // Arrange - doAnswer((InvocationOnMock m) -> { - Object listener = m.getArgument(0); - assertEquals(FederationWatcher.FederationWatcherRskListener.class, listener.getClass()); - return null; - }).when(ethereum).addListener(any()); - // Act - federationWatcher.setup(federationProvider); + federationWatcher.start(federationProvider, federationWatcherListener); // Assert - verify(ethereum).addListener(any()); + verify(rsk).addListener(any()); assertSame(TestUtils.getInternalState(federationWatcher, "federationProvider"), federationProvider); + assertSame(TestUtils.getInternalState(federationWatcher, "federationWatcherListener"), federationWatcherListener); } @Test @@ -92,11 +88,12 @@ void whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Excepti when(federationProvider.getActiveFederation()).thenReturn(FIRST_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); - federationWatcher.addListener(new FederationWatcher.Listener() { + // Act + federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { assertEquals(Optional.empty(), oldFederation); - assertEquals(FIRST_FEDERATION, newFederation); + assertEquals(Optional.of(FIRST_FEDERATION), newFederation); activeCalls.incrementAndGet(); } @@ -105,8 +102,6 @@ public void onRetiringFederationChange(Optional oldFederation, Optio retiringCalls.incrementAndGet(); } }); - - // Act rskListener.onBestBlock(null, null); // Assert @@ -129,11 +124,12 @@ void whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFeder when(federationProvider.getActiveFederation()).thenReturn(SECOND_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); - federationWatcher.addListener(new FederationWatcher.Listener() { + // Act + federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { assertEquals(Optional.of(FIRST_FEDERATION), oldFederation); - assertEquals(SECOND_FEDERATION, newFederation); + assertEquals(Optional.of(SECOND_FEDERATION), newFederation); activeCalls.incrementAndGet(); } @@ -142,8 +138,6 @@ public void onRetiringFederationChange(Optional oldFederation, Optio retiringCalls.incrementAndGet(); } }); - - // Act rskListener.onBestBlock(null, null); // Assert @@ -165,9 +159,10 @@ void whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChan when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); - federationWatcher.addListener(new FederationWatcher.Listener() { + // Act + federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { activeCalls.incrementAndGet(); } @@ -176,8 +171,6 @@ public void onRetiringFederationChange(Optional oldFederation, Optio retiringCalls.incrementAndGet(); } }); - - // Act rskListener.onBestBlock(null, null); // Assert @@ -199,9 +192,10 @@ void whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiveOrRetiringF when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); - federationWatcher.addListener(new FederationWatcher.Listener() { + // Act + federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { activeCalls.incrementAndGet(); } @@ -210,8 +204,6 @@ public void onRetiringFederationChange(Optional oldFederation, Optio retiringCalls.incrementAndGet(); } }); - - // Act rskListener.onBestBlock(null, null); // Assert @@ -234,9 +226,10 @@ void whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(FIRST_FEDERATION.getAddress())); when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(FIRST_FEDERATION)); - federationWatcher.addListener(new FederationWatcher.Listener() { + // Act + federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { activeCalls.incrementAndGet(); } @@ -247,8 +240,6 @@ public void onRetiringFederationChange(Optional oldFederation, Optio retiringCalls.incrementAndGet(); } }); - - // Act rskListener.onBestBlock(null, null); // Assert @@ -271,9 +262,10 @@ void whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getRetiringFederation()).thenReturn(Optional.empty()); - federationWatcher.addListener(new FederationWatcher.Listener() { + // Act + federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { activeCalls.incrementAndGet(); } @@ -284,8 +276,6 @@ public void onRetiringFederationChange(Optional oldFederation, Optio retiringCalls.incrementAndGet(); } }); - - // Act rskListener.onBestBlock(null, null); // Assert @@ -308,9 +298,10 @@ void whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederatio when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(SECOND_FEDERATION)); - federationWatcher.addListener(new FederationWatcher.Listener() { + // Act + federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Federation newFederation) { + public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { activeCalls.incrementAndGet(); } @@ -321,8 +312,6 @@ public void onRetiringFederationChange(Optional oldFederation, Optio retiringCalls.incrementAndGet(); } }); - - // Act rskListener.onBestBlock(null, null); // Assert @@ -341,10 +330,10 @@ private EthereumListenerAdapter setupAndGetRskListener( doAnswer((InvocationOnMock m) -> { listenerRef.set(m.getArgument(0)); return null; - }).when(ethereum).addListener(any()); - + }).when(rsk).addListener(any()); + + federationWatcher.start(federationProvider, null); // Set up federationWatcher and internal states - federationWatcher.setup(federationProvider); TestUtils.setInternalState(federationWatcher, "activeFederation", activeFederation); TestUtils.setInternalState(federationWatcher, "retiringFederation", retiringFederation); From feb04fca7ed9e9ce3303ceab6032c6354017f355 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:08:27 +0000 Subject: [PATCH 007/112] refactor(watcher): simplify FederationWatcherListener interface and avoid using Optional as parameters --- .../federate/watcher/FederationWatcher.java | 55 ++++++++++------- .../watcher/FederationWatcherListener.java | 17 ++---- .../FederationWatcherListenerImpl.java | 38 ++++-------- .../watcher/FederationWatcherTest.java | 60 +++++++++---------- 4 files changed, 80 insertions(+), 90 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java index e54340ad3..58d7e6c14 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -25,8 +25,8 @@ public class FederationWatcher { private FederationProvider federationProvider; private FederationWatcherListener federationWatcherListener; - private Optional activeFederation = Optional.empty(); - private Optional retiringFederation = Optional.empty(); + private Federation activeFederation; + private Federation retiringFederation; /** * Constructs a new {@code FederationWatcher} with the specified RSK client. @@ -77,37 +77,48 @@ public void onBestBlock(org.ethereum.core.Block block, List * retiring federations have changed. If a federation change is detected, it notifies * the {@code FederationWatcherListener}. */ - public void updateState() { + private void updateState() { + updateActiveFederation(); + updateRetiringFederation(); + } + + private void updateActiveFederation() { // Check if active federation has changed - Address currentlyActiveFederationAddress = federationProvider.getActiveFederationAddress(); - Address oldActiveFederationAddress = activeFederation.map(Federation::getAddress).orElse(null); - - if (!currentlyActiveFederationAddress.equals(oldActiveFederationAddress)) { + Optional
currentlyActiveFederationAddress = Optional.ofNullable(federationProvider.getActiveFederationAddress()); + Optional
oldActiveFederationAddress = Optional.ofNullable(activeFederation).map(Federation::getAddress); + boolean hasActiveFederationChanged = !currentlyActiveFederationAddress.equals(oldActiveFederationAddress); + + if (hasActiveFederationChanged) { // Active federation changed + logger.info("[updateActiveFederation] Active federation changed from {} to {}", + oldActiveFederationAddress.orElse(null), + currentlyActiveFederationAddress.orElse(null)); + Federation currentlyActiveFederation = federationProvider.getActiveFederation(); - logger.info("[updateState] Active federation changed from {} to {}", - oldActiveFederationAddress != null ? oldActiveFederationAddress.toString() : "NONE", - currentlyActiveFederation.getAddress().toString()); // Notify listener and update state - federationWatcherListener.onActiveFederationChange(activeFederation, Optional.of(currentlyActiveFederation)); - activeFederation = Optional.of(currentlyActiveFederation); + federationWatcherListener.onActiveFederationChange(currentlyActiveFederation); + this.activeFederation = currentlyActiveFederation; } + } + private void updateRetiringFederation() { // Check if retiring federation has changed Optional
currentlyRetiringFederationAddress = federationProvider.getRetiringFederationAddress(); - Optional
oldRetiringFederationAddress = retiringFederation.map(Federation::getAddress); - - if (!oldRetiringFederationAddress.equals(currentlyRetiringFederationAddress)) { + Optional
oldRetiringFederationAddress = Optional.ofNullable(retiringFederation).map(Federation::getAddress); + boolean hasRetiringFederationChanged = !currentlyRetiringFederationAddress.equals(oldRetiringFederationAddress); + + if (hasRetiringFederationChanged) { // Retiring federation changed - Optional currentlyRetiringFederation = federationProvider.getRetiringFederation(); - logger.info("[updateState] Retiring federation changed from {} to {}", - oldRetiringFederationAddress.map(Address::toString).orElse("NONE"), - currentlyRetiringFederation.map(federation -> federation.getAddress().toString()).orElse("NONE")); + logger.info("[updateRetiringFederation] Retiring federation changed from {} to {}", + oldRetiringFederationAddress.orElse(null), + currentlyRetiringFederationAddress.orElse(null)); + + Federation currentlyRetiringFederation = federationProvider.getRetiringFederation().orElse(null); // Notify listener and update state - federationWatcherListener.onRetiringFederationChange(retiringFederation, currentlyRetiringFederation); - retiringFederation = currentlyRetiringFederation; + federationWatcherListener.onRetiringFederationChange(currentlyRetiringFederation); + this.retiringFederation = currentlyRetiringFederation; } - } + } } diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java index 3e0c008ea..7eac5cff9 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java @@ -1,7 +1,6 @@ package co.rsk.federate.watcher; import co.rsk.peg.federation.Federation; -import java.util.Optional; /** * A listener interface for receiving notifications about changes in federations. @@ -12,20 +11,16 @@ public interface FederationWatcherListener { /** * Invoked when the active federation changes. * - * @param oldFederation an {@code Optional} containing the previous active federation. - * This will be empty if there was no active federation before. - * @param newFederation an {@code Optional} containing the new active federation. - * This will be empty if there is no active federation after the change. + * @param newFederation the new active federation after the change. + * This will never be {@code null}; the active federation is always present. */ - void onActiveFederationChange(Optional oldFederation, Optional newFederation); + void onActiveFederationChange(Federation newFederation); /** * Invoked when the retiring federation changes. * - * @param oldFederation an {@code Optional} containing the previous retiring federation. - * This will be empty if there was no retiring federation before. - * @param newFederation an {@code Optional} containing the new retiring federation. - * This will be empty if there is no retiring federation after the change. + * @param newFederation the new retiring federation after the change. + * This can be {@code null}; the retiring federation is not always present. */ - void onRetiringFederationChange(Optional oldFederation, Optional newFederation); + void onRetiringFederationChange(Federation newFederation); } diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index ab2648c20..32aac66f7 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -24,44 +24,32 @@ public FederationWatcherListenerImpl( BtcToRskClient btcToRskClientRetiring, BtcReleaseClient btcReleaseClient) { this.federationMember = Objects.requireNonNull(federationMember); - this.btcToRskClientActive = Objects.requireNonNull(btcToRskClientActive); - this.btcToRskClientRetiring = Objects.requireNonNull(btcToRskClientRetiring); - this.btcReleaseClient = Objects.requireNonNull(btcReleaseClient); + this.btcToRskClientActive = btcToRskClientActive; + this.btcToRskClientRetiring = btcToRskClientRetiring; + this.btcReleaseClient = btcReleaseClient; } @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { - String oldFederationAddress = oldFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); - String newFederationAddress = oldFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); - logger.debug("[onActiveFederationChange] Active federation change: from {} to {}", oldFederationAddress, newFederationAddress); + public void onActiveFederationChange(Federation newFederation) { triggerClientChange(btcToRskClientActive, newFederation); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - String oldFederationAddress = oldFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); - String newFederationAddress = newFederation.map(federation -> federation.getAddress().toString()).orElse("NONE"); - logger.debug("[onRetiringFederationChange] Retiring federation change: from {} to {}", oldFederationAddress, newFederationAddress); + public void onRetiringFederationChange(Federation newFederation) { triggerClientChange(btcToRskClientRetiring, newFederation); } - private void triggerClientChange(BtcToRskClient client, Optional federation) { + private void triggerClientChange(BtcToRskClient btcToRskClient, Federation newFederation) { // Stop the current clients - client.stop(); - federation.ifPresent(btcReleaseClient::stop); - - // Exit early if federation is not present - if (federation.isEmpty()) { - logger.warn("[triggerClientChange] No federation available to join."); - return; - } - - Federation newFederation = federation.get(); + btcToRskClient.stop(); + Optional.ofNullable(newFederation).ifPresent(btcReleaseClient::stop); // Check if this federator is part of the new federation - if (!newFederation.isMember(federationMember)) { + if (newFederation == null || !newFederation.isMember(federationMember)) { logger.warn( - "[triggerClientChange] This federator node is not part of the new federation. Check your configuration for signers BTC, RSK, and MST keys"); + "[triggerClientChange] This federator node ({}) is not part of the new federation ({}). Check your configuration for signers BTC, RSK, and MST keys", + federationMember, + Optional.ofNullable(newFederation).map(Federation::getAddress).orElse(null)); return; } @@ -72,7 +60,7 @@ private void triggerClientChange(BtcToRskClient client, Optional fed logger.info( "[triggerClientChange] Joined to {} federation", federationAddress); - client.start(newFederation); + btcToRskClient.start(newFederation); btcReleaseClient.start(newFederation); } } diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index 5948a4295..81b9a57a4 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -80,7 +81,7 @@ void whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { @Test void whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(Optional.empty(), Optional.empty()); + var rskListener = setupAndGetRskListener(null, null); var activeCalls = new AtomicInteger(0); var retiringCalls = new AtomicInteger(0); @@ -91,14 +92,13 @@ void whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Excepti // Act federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.empty(), oldFederation); - assertEquals(Optional.of(FIRST_FEDERATION), newFederation); + public void onActiveFederationChange(Federation newFederation) { + assertEquals(FIRST_FEDERATION, newFederation); activeCalls.incrementAndGet(); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } }); @@ -116,7 +116,7 @@ public void onRetiringFederationChange(Optional oldFederation, Optio @Test void whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(Optional.of(FIRST_FEDERATION), Optional.empty()); + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null); var activeCalls = new AtomicInteger(0); var retiringCalls = new AtomicInteger(0); @@ -127,14 +127,13 @@ void whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFeder // Act federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.of(FIRST_FEDERATION), oldFederation); - assertEquals(Optional.of(SECOND_FEDERATION), newFederation); + public void onActiveFederationChange(Federation newFederation) { + assertEquals(SECOND_FEDERATION, newFederation); activeCalls.incrementAndGet(); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } }); @@ -152,7 +151,7 @@ public void onRetiringFederationChange(Optional oldFederation, Optio @Test void whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(Optional.of(FIRST_FEDERATION), Optional.empty()); + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null); var activeCalls = new AtomicInteger(0); var retiringCalls = new AtomicInteger(0); @@ -162,12 +161,12 @@ void whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChan // Act federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { + public void onActiveFederationChange(Federation newFederation) { activeCalls.incrementAndGet(); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } }); @@ -185,7 +184,7 @@ public void onRetiringFederationChange(Optional oldFederation, Optio @Test void whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(Optional.of(FIRST_FEDERATION), Optional.of(SECOND_FEDERATION)); + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, SECOND_FEDERATION); var activeCalls = new AtomicInteger(0); var retiringCalls = new AtomicInteger(0); @@ -195,12 +194,12 @@ void whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiveOrRetiringF // Act federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { + public void onActiveFederationChange(Federation newFederation) { activeCalls.incrementAndGet(); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { + public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } }); @@ -218,7 +217,7 @@ public void onRetiringFederationChange(Optional oldFederation, Optio @Test void whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(Optional.of(SECOND_FEDERATION), Optional.empty()); + var rskListener = setupAndGetRskListener(SECOND_FEDERATION, null); var activeCalls = new AtomicInteger(0); var retiringCalls = new AtomicInteger(0); @@ -229,14 +228,13 @@ void whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange // Act federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { + public void onActiveFederationChange(Federation newFederation) { activeCalls.incrementAndGet(); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.empty(), oldFederation); - assertEquals(Optional.of(FIRST_FEDERATION), newFederation); + public void onRetiringFederationChange(Federation newFederation) { + assertEquals(FIRST_FEDERATION, newFederation); retiringCalls.incrementAndGet(); } }); @@ -254,7 +252,7 @@ public void onRetiringFederationChange(Optional oldFederation, Optio @Test void whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(Optional.of(SECOND_FEDERATION), Optional.of(FIRST_FEDERATION)); + var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION); var activeCalls = new AtomicInteger(0); var retiringCalls = new AtomicInteger(0); @@ -265,14 +263,13 @@ void whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() // Act federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { + public void onActiveFederationChange(Federation newFederation) { activeCalls.incrementAndGet(); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.of(FIRST_FEDERATION), oldFederation); - assertEquals(Optional.empty(), newFederation); + public void onRetiringFederationChange(Federation newFederation) { + assertNull(newFederation); retiringCalls.incrementAndGet(); } }); @@ -290,7 +287,7 @@ public void onRetiringFederationChange(Optional oldFederation, Optio @Test void whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(Optional.of(THIRD_FEDERATION), Optional.of(FIRST_FEDERATION)); + var rskListener = setupAndGetRskListener(THIRD_FEDERATION, FIRST_FEDERATION); var activeCalls = new AtomicInteger(0); var retiringCalls = new AtomicInteger(0); @@ -301,14 +298,13 @@ void whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederatio // Act federationWatcher.start(federationProvider, new FederationWatcherListener() { @Override - public void onActiveFederationChange(Optional oldFederation, Optional newFederation) { + public void onActiveFederationChange(Federation newFederation) { activeCalls.incrementAndGet(); } @Override - public void onRetiringFederationChange(Optional oldFederation, Optional newFederation) { - assertEquals(Optional.of(FIRST_FEDERATION), oldFederation); - assertEquals(Optional.of(SECOND_FEDERATION), newFederation); + public void onRetiringFederationChange(Federation newFederation) { + assertEquals(SECOND_FEDERATION, newFederation); retiringCalls.incrementAndGet(); } }); @@ -324,7 +320,7 @@ public void onRetiringFederationChange(Optional oldFederation, Optio } private EthereumListenerAdapter setupAndGetRskListener( - Optional activeFederation, Optional retiringFederation) throws Exception { + Federation activeFederation, Federation retiringFederation) throws Exception { // Mock the behavior of adding a listener AtomicReference listenerRef = new AtomicReference<>(); doAnswer((InvocationOnMock m) -> { From 970bd25c89c747155dde7a8ed39e02606bcd8452 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 23 Oct 2024 09:03:39 +0000 Subject: [PATCH 008/112] refactor(watcher): organize and remove some comments for FederationWatcher --- .../co/rsk/federate/watcher/FederationWatcher.java | 14 +++++++------- .../federate/watcher/FederationWatcherTest.java | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java index 58d7e6c14..0e3994e04 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -54,19 +54,23 @@ public void start(FederationProvider federationProvider, FederationWatcherListen public void onBestBlock(org.ethereum.core.Block block, List receipts) { // Updating state only when the best block changes still "works", // since we're interested in finding out only when the active or retiring federation(s) changed. + // // If there was a side chain in which any of these changed in, say, block 4500, but // that side chain became main chain in block 7800, it would still be ok to // start monitoring the new federation(s) on that block. + // // The main reasons for this is that RSK nodes would have never reported the active or // retiring federation(s) being different *before* the best chain change. Therefore, // there should be no new Bitcoin transactions directed to these new addresses // until this change effectively becomes a part of RSK's "reality". + // // The case in which we go back and forth to and from a sidechain in which the // federation effectively changed is still to be explored deeply, but the same reasoning // should apply since going back and forth would trigger two federation changes. + // // A client trying to send bitcoins to the new federation without waiting // a good number of confirmations would be, essentially, "playing with fire". - logger.info("[updateState] New best block, updating state"); + logger.info("[onBestBlock] New best block, updating state"); updateState(); } }); @@ -83,40 +87,36 @@ private void updateState() { } private void updateActiveFederation() { - // Check if active federation has changed Optional
currentlyActiveFederationAddress = Optional.ofNullable(federationProvider.getActiveFederationAddress()); Optional
oldActiveFederationAddress = Optional.ofNullable(activeFederation).map(Federation::getAddress); + boolean hasActiveFederationChanged = !currentlyActiveFederationAddress.equals(oldActiveFederationAddress); if (hasActiveFederationChanged) { - // Active federation changed logger.info("[updateActiveFederation] Active federation changed from {} to {}", oldActiveFederationAddress.orElse(null), currentlyActiveFederationAddress.orElse(null)); Federation currentlyActiveFederation = federationProvider.getActiveFederation(); - // Notify listener and update state federationWatcherListener.onActiveFederationChange(currentlyActiveFederation); this.activeFederation = currentlyActiveFederation; } } private void updateRetiringFederation() { - // Check if retiring federation has changed Optional
currentlyRetiringFederationAddress = federationProvider.getRetiringFederationAddress(); Optional
oldRetiringFederationAddress = Optional.ofNullable(retiringFederation).map(Federation::getAddress); + boolean hasRetiringFederationChanged = !currentlyRetiringFederationAddress.equals(oldRetiringFederationAddress); if (hasRetiringFederationChanged) { - // Retiring federation changed logger.info("[updateRetiringFederation] Retiring federation changed from {} to {}", oldRetiringFederationAddress.orElse(null), currentlyRetiringFederationAddress.orElse(null)); Federation currentlyRetiringFederation = federationProvider.getRetiringFederation().orElse(null); - // Notify listener and update state federationWatcherListener.onRetiringFederationChange(currentlyRetiringFederation); this.retiringFederation = currentlyRetiringFederation; } diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index 81b9a57a4..31387a8d7 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -336,6 +336,7 @@ private EthereumListenerAdapter setupAndGetRskListener( // Retrieve and return the listener EthereumListenerAdapter listener = listenerRef.get(); assertNotNull(listener); + return listener; } From 959d509d6bda886c1c891c886af79abf6ae64a40 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:20:33 +0000 Subject: [PATCH 009/112] feat(btcreleaseclient): federator should be part of the federation when starting --- .../btcreleaseclient/BtcReleaseClient.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index 154cb8b10..ab92069e6 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -36,6 +36,7 @@ import co.rsk.peg.BridgeEvents; import co.rsk.peg.BridgeUtils; import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationMember; import co.rsk.peg.federation.ErpFederation; import co.rsk.peg.StateForFederator; import java.time.Clock; @@ -160,10 +161,21 @@ public void setup( } public void start(Federation federation) { + FederationMember federationMember = federatorSupport.getFederationMember(); + if (federation.isMember(federationMember)) { + String message = String.format( + "Member %s is no part of the federation %s", + federationMember.getBtcPublicKey(), + federation.getAddress()); + logger.error("[start] {}", message); + throw new IllegalStateException(message); + } + if (!observedFederations.contains(federation)) { observedFederations.add(federation); - logger.debug("[start] observing Federation {}", federation.getAddress()); + logger.debug("[start] Observing federation {}", federation.getAddress()); } + if (observedFederations.size() == 1) { // If there is just one observed Federation, it means the btcReleaseClient wasn't started logger.debug("[start] Starting"); From 9b8c820ab82e27f2f44c1e4dd3dd8d106354fb87 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:21:08 +0000 Subject: [PATCH 010/112] feat(federate): throw an exception if federator is not a member of the federation when starting --- src/main/java/co/rsk/federate/BtcToRskClient.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/co/rsk/federate/BtcToRskClient.java b/src/main/java/co/rsk/federate/BtcToRskClient.java index 598d4f105..7add9ae4b 100644 --- a/src/main/java/co/rsk/federate/BtcToRskClient.java +++ b/src/main/java/co/rsk/federate/BtcToRskClient.java @@ -115,10 +115,12 @@ public void start(Federation federation) { boolean isMember = federation.isMember(federator); if (!isMember) { - logger.info("[start] member {} is no part of the federation {} ", + String message = String.format( + "Member %s is no part of the federation %s", federator.getBtcPublicKey(), federation.getAddress()); - return; + logger.error("[start] {}", message); + throw new IllegalStateException(message); } logger.info("[start] {} is member of the federation {}", @@ -129,6 +131,7 @@ public void start(Federation federation) { Optional federatorIndex = federation.getBtcPublicKeyIndex( federatorSupport.getFederationMember().getBtcPublicKey() ); + if (!federatorIndex.isPresent()) { String message = String.format( "Federator %s is a member of the federation %s but could not find the btcPublicKeyIndex", @@ -138,6 +141,7 @@ public void start(Federation federation) { logger.error("[start] {}", message); throw new IllegalStateException(message); } + TurnScheduler scheduler = new TurnScheduler( bridgeConstants.getUpdateBridgeExecutionPeriod(), federation.getSize() @@ -152,8 +156,7 @@ public void start(Federation federation) { scheduler.getInterval(), TimeUnit.MILLISECONDS ); - } - else { + } else { logger.info("[start] updateBridgeTimer is disabled"); } } From f42ddb462e34b3cea5a6c64e9203e29847091737 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:21:37 +0000 Subject: [PATCH 011/112] feat(watcher): change stop and starting logic when active and retiring federation --- .../java/co/rsk/federate/FedNodeRunner.java | 1 - .../FederationWatcherListenerImpl.java | 53 ++++++++++--------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/co/rsk/federate/FedNodeRunner.java b/src/main/java/co/rsk/federate/FedNodeRunner.java index 7e1305879..88ab1dc25 100644 --- a/src/main/java/co/rsk/federate/FedNodeRunner.java +++ b/src/main/java/co/rsk/federate/FedNodeRunner.java @@ -345,7 +345,6 @@ private void startFederate() throws Exception { ); FederationWatcherListener federationWatcherListener = new FederationWatcherListenerImpl( - member, btcToRskClientActive, btcToRskClientRetiring, btcReleaseClient); diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index 32aac66f7..4c76672a0 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -3,27 +3,22 @@ import co.rsk.federate.BtcToRskClient; import co.rsk.federate.btcreleaseclient.BtcReleaseClient; import co.rsk.peg.federation.Federation; -import co.rsk.peg.federation.FederationMember; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Objects; -import java.util.Optional; public class FederationWatcherListenerImpl implements FederationWatcherListener { private static final Logger logger = LoggerFactory.getLogger(FederationWatcherListenerImpl.class); - private final FederationMember federationMember; private final BtcToRskClient btcToRskClientActive; private final BtcToRskClient btcToRskClientRetiring; private final BtcReleaseClient btcReleaseClient; public FederationWatcherListenerImpl( - FederationMember federationMember, BtcToRskClient btcToRskClientActive, BtcToRskClient btcToRskClientRetiring, BtcReleaseClient btcReleaseClient) { - this.federationMember = Objects.requireNonNull(federationMember); this.btcToRskClientActive = btcToRskClientActive; this.btcToRskClientRetiring = btcToRskClientRetiring; this.btcReleaseClient = btcReleaseClient; @@ -36,31 +31,37 @@ public void onActiveFederationChange(Federation newFederation) { @Override public void onRetiringFederationChange(Federation newFederation) { - triggerClientChange(btcToRskClientRetiring, newFederation); + if (newFederation == null) { + triggerClearingRetiringFederationClient(); + } else { + triggerClientChange(btcToRskClientRetiring, newFederation); + } } private void triggerClientChange(BtcToRskClient btcToRskClient, Federation newFederation) { - // Stop the current clients - btcToRskClient.stop(); - Optional.ofNullable(newFederation).ifPresent(btcReleaseClient::stop); - - // Check if this federator is part of the new federation - if (newFederation == null || !newFederation.isMember(federationMember)) { - logger.warn( - "[triggerClientChange] This federator node ({}) is not part of the new federation ({}). Check your configuration for signers BTC, RSK, and MST keys", - federationMember, - Optional.ofNullable(newFederation).map(Federation::getAddress).orElse(null)); - return; - } + // This method assumes that the new federation cannot be null + Objects.requireNonNull(newFederation); + + try { + // Stop the current clients + btcToRskClient.stop(); + btcReleaseClient.stop(newFederation); - // Start clients if federator is part of the new federation - String federationAddress = newFederation.getAddress().toString(); - logger.debug( - "[triggerClientChange] Starting lock and release clients since I belong to federation {}", federationAddress); - logger.info( - "[triggerClientChange] Joined to {} federation", federationAddress); + // Start the current clients + btcToRskClient.start(newFederation); + btcReleaseClient.start(newFederation); - btcToRskClient.start(newFederation); - btcReleaseClient.start(newFederation); + logger.info( + "[triggerClientChange] Joined {} federation", newFederation.getAddress()); + } catch (Exception e) { + logger.error( + "[triggerClientChange] This federation ({}) cannot be started: {}", + newFederation.getAddress(), + e.getMessage()); + } + } + + private void triggerClearingRetiringFederationClient() { + btcToRskClientRetiring.stop(); } } From fd35e17d47815a94f5531554188d4b565d4c83f1 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 08:32:10 +0000 Subject: [PATCH 012/112] refactor(watcher): rename interface variables to be more explicit and avoid confusion --- .../federate/watcher/FederationWatcherListener.java | 8 ++++---- .../watcher/FederationWatcherListenerImpl.java | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java index 7eac5cff9..927b272e1 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java @@ -11,16 +11,16 @@ public interface FederationWatcherListener { /** * Invoked when the active federation changes. * - * @param newFederation the new active federation after the change. + * @param newActiveFederation the new active federation after the change. * This will never be {@code null}; the active federation is always present. */ - void onActiveFederationChange(Federation newFederation); + void onActiveFederationChange(Federation newActiveFederation); /** * Invoked when the retiring federation changes. * - * @param newFederation the new retiring federation after the change. + * @param newRetiringFederation the new retiring federation after the change. * This can be {@code null}; the retiring federation is not always present. */ - void onRetiringFederationChange(Federation newFederation); + void onRetiringFederationChange(Federation newRetiringFederation); } diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index 4c76672a0..3a5ed22f0 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -25,16 +25,16 @@ public FederationWatcherListenerImpl( } @Override - public void onActiveFederationChange(Federation newFederation) { - triggerClientChange(btcToRskClientActive, newFederation); + public void onActiveFederationChange(Federation newActiveFederation) { + triggerClientChange(btcToRskClientActive, newActiveFederation); } @Override - public void onRetiringFederationChange(Federation newFederation) { - if (newFederation == null) { + public void onRetiringFederationChange(Federation newRetiringFederation) { + if (newRetiringFederation == null) { triggerClearingRetiringFederationClient(); } else { - triggerClientChange(btcToRskClientRetiring, newFederation); + triggerClientChange(btcToRskClientRetiring, newRetiringFederation); } } From ec8b42bf9a88fc3d0c440023b4c1375b9a90369b Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 08:53:19 +0000 Subject: [PATCH 013/112] fix(federate): solve unit tests since was using a member not part of the federation --- .../co/rsk/federate/BtcToRskClientTest.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/test/java/co/rsk/federate/BtcToRskClientTest.java b/src/test/java/co/rsk/federate/BtcToRskClientTest.java index c735db2e3..77ccc5ab6 100644 --- a/src/test/java/co/rsk/federate/BtcToRskClientTest.java +++ b/src/test/java/co/rsk/federate/BtcToRskClientTest.java @@ -57,7 +57,7 @@ class BtcToRskClientTest { private ActivationConfig activationConfig; private BridgeConstants bridgeRegTestConstants; private Federation activeFederation; - private FederationMember fakeMember; + private FederationMember activeFederationMember; private BtcToRskClientBuilder btcToRskClientBuilder; private List federationPrivateKeys; private NetworkParameters networkParameters; @@ -72,9 +72,7 @@ void setup() throws PeginInstructionsException, IOException { networkParameters = ThinConverter.toOriginalInstance(bridgeRegTestConstants.getBtcParamsString()); federationPrivateKeys = TestUtils.getFederationPrivateKeys(9); activeFederation = TestUtils.createFederation(bridgeRegTestConstants.getBtcParams(), federationPrivateKeys); - fakeMember = FederationMember.getFederationMemberFromKey( - BtcECKey.fromPrivate(HashUtil.keccak256("00".getBytes(StandardCharsets.UTF_8))) - ); + activeFederationMember = FederationMember.getFederationMemberFromKey(federationPrivateKeys.get(0)); btcToRskClientBuilder = new BtcToRskClientBuilder(); } @@ -94,7 +92,7 @@ void start_withNoFederationMember_doesntThrowError() throws Exception { BitcoinWrapper bw = new SimpleBitcoinWrapper(); SimpleFederatorSupport fh = new SimpleFederatorSupport(); - fh.setMember(fakeMember); + fh.setMember(activeFederationMember); BtcToRskClient client = createClientWithMocks(bw, fh); assertDoesNotThrow(() -> client.start(activeFederation)); } @@ -480,7 +478,7 @@ void onBlock_including_segwit_tx_registers_coinbase() throws Exception { when(mockedActivationConfig.isActive(eq(ConsensusRule.RSKIP143), anyLong())).thenReturn(true); FederatorSupport federatorSupport = mock(FederatorSupport.class); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BtcToRskClient client = spy(buildWithFactoryAndSetup( federatorSupport, @@ -1752,7 +1750,7 @@ void updateTransaction_with_release_before_rskip143() throws Exception { SimpleFederatorSupport federatorSupport = new SimpleFederatorSupport(); // set a fake member to federatorSupport to recreate a not-null runner - federatorSupport.setMember(fakeMember); + federatorSupport.setMember(activeFederationMember); BtcToRskClient client = buildWithFactoryAndSetup( federatorSupport, @@ -2242,7 +2240,7 @@ void updateBridgeBtcCoinbaseTransactions_when_coinbase_map_has_readyToBeInformed when(btcToRskClientFileStorageMock.read(any())).thenReturn(new BtcToRskClientFileReadResult(true, btcToRskClientFileData)); FederatorSupport federatorSupport = mock(FederatorSupport.class); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BtcToRskClient client = buildWithFactoryAndSetup( federatorSupport, @@ -2282,7 +2280,7 @@ void updateBridgeBtcCoinbaseTransactions_when_coinbase_map_has_readyToBeInformed FederatorSupport federatorSupport = mock(FederatorSupport.class); // mocking that the coinbase was already informed when(federatorSupport.hasBlockCoinbaseInformed(any())).thenReturn(true); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BtcToRskClient client = buildWithFactoryAndSetup( federatorSupport, @@ -2326,7 +2324,7 @@ void updateBridgeBtcCoinbaseTransactions_when_coinbase_map_has_readyToBeInformed FederatorSupport federatorSupport = mock(FederatorSupport.class); // Mocking the Bridge to indicate the coinbase was not informed, and then it was when(federatorSupport.hasBlockCoinbaseInformed(any())).thenReturn(false, true); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BtcToRskClient client = buildWithFactoryAndSetup( federatorSupport, @@ -2373,7 +2371,7 @@ void updateBridgeBtcCoinbaseTransactions_not_removing_from_storage_until_confirm FederatorSupport federatorSupport = mock(FederatorSupport.class); // mocking that the coinbase was not informed when(federatorSupport.hasBlockCoinbaseInformed(any())).thenReturn(false); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BtcToRskClient client = buildWithFactoryAndSetup( federatorSupport, @@ -2415,7 +2413,7 @@ void updateBridge_when_does_not_hasBetterBlockToSync_updates_headers_coinbase_tr FederatorSupport federatorSupport = mock(FederatorSupport.class); when(federatorSupport.getBtcBestBlockChainHeight()).thenReturn(1); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BitcoinWrapper bitcoinWrapper = mock(BitcoinWrapper.class); when(bitcoinWrapper.getBestChainHeight()).thenReturn(1); @@ -2447,7 +2445,7 @@ void updateBridge_whenUpdateBridgeConfigAreFalse_shouldNotCallAny() throws Excep FederatorSupport federatorSupport = mock(FederatorSupport.class); when(federatorSupport.getBtcBestBlockChainHeight()).thenReturn(1); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BitcoinWrapper bitcoinWrapper = mock(BitcoinWrapper.class); when(bitcoinWrapper.getBestChainHeight()).thenReturn(1); @@ -2481,7 +2479,7 @@ void updateBridge_noUpdateBridgeConfigDefined_shouldTriggerBridgeUpdates() throw FederatorSupport federatorSupport = mock(FederatorSupport.class); when(federatorSupport.getBtcBestBlockChainHeight()).thenReturn(1); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BitcoinWrapper bitcoinWrapper = mock(BitcoinWrapper.class); when(bitcoinWrapper.getBestChainHeight()).thenReturn(1); @@ -2525,7 +2523,7 @@ void updateBridgeBtcTransactions_tx_with_witness_already_informed() throws Excep when(federatorSupport.getBtcBestBlockChainHeight()).thenReturn(1); when(federatorSupport.isBtcTxHashAlreadyProcessed(peginTx.getTxId())).thenReturn(true); when(federatorSupport.getBtcTxHashProcessedHeight(peginTx.getTxId())).thenReturn(1L); - when(federatorSupport.getFederationMember()).thenReturn(fakeMember); + when(federatorSupport.getFederationMember()).thenReturn(activeFederationMember); BitcoinWrapper bitcoinWrapper = mock(BitcoinWrapper.class); when(bitcoinWrapper.getBestChainHeight()).thenReturn(1); @@ -2600,7 +2598,7 @@ private BtcToRskClient buildWithFactoryAndSetup( PowpegNodeSystemProperties config = nonNull(fedNodeSystemProperties) ? fedNodeSystemProperties : getMockedFedNodeSystemProperties(true); - if(MockUtil.isMock(config)) { + if (MockUtil.isMock(config)) { when(config.getActivationConfig()).thenReturn(activationConfig); } From 225a1ff68be2b8dcf392c5ca4801cda20c814a50 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:07:12 +0000 Subject: [PATCH 014/112] refactor(watcher): change updateActiveFederation to use the original implementation without Optional --- .../java/co/rsk/federate/watcher/FederationWatcher.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java index 0e3994e04..2a05cb901 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -87,15 +87,15 @@ private void updateState() { } private void updateActiveFederation() { - Optional
currentlyActiveFederationAddress = Optional.ofNullable(federationProvider.getActiveFederationAddress()); - Optional
oldActiveFederationAddress = Optional.ofNullable(activeFederation).map(Federation::getAddress); + Address currentlyActiveFederationAddress = federationProvider.getActiveFederationAddress(); + Address oldActiveFederationAddress = Optional.ofNullable(activeFederation).map(Federation::getAddress).orElse(null); boolean hasActiveFederationChanged = !currentlyActiveFederationAddress.equals(oldActiveFederationAddress); if (hasActiveFederationChanged) { logger.info("[updateActiveFederation] Active federation changed from {} to {}", - oldActiveFederationAddress.orElse(null), - currentlyActiveFederationAddress.orElse(null)); + oldActiveFederationAddress, + currentlyActiveFederationAddress); Federation currentlyActiveFederation = federationProvider.getActiveFederation(); From 9aef1c1c15f5d450e0d5fd3dfd58b84c974d0d59 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:07:36 +0000 Subject: [PATCH 015/112] refactor(watcher): small code structure change and better logging --- .../watcher/FederationWatcherListenerImpl.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index 3a5ed22f0..9a809fd3b 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -32,10 +32,11 @@ public void onActiveFederationChange(Federation newActiveFederation) { @Override public void onRetiringFederationChange(Federation newRetiringFederation) { if (newRetiringFederation == null) { - triggerClearingRetiringFederationClient(); - } else { - triggerClientChange(btcToRskClientRetiring, newRetiringFederation); + clearRetiringFederationClient(); + return; } + + triggerClientChange(btcToRskClientRetiring, newRetiringFederation); } private void triggerClientChange(BtcToRskClient btcToRskClient, Federation newFederation) { @@ -52,16 +53,19 @@ private void triggerClientChange(BtcToRskClient btcToRskClient, Federation newFe btcReleaseClient.start(newFederation); logger.info( - "[triggerClientChange] Joined {} federation", newFederation.getAddress()); + "[triggerClientChange] Clients for federation [{}] changed with success", + newFederation.getAddress()); } catch (Exception e) { logger.error( - "[triggerClientChange] This federation ({}) cannot be started: {}", + "[triggerClientChange] Clients for federation [{}] cannot be changed: {}", newFederation.getAddress(), e.getMessage()); } } - private void triggerClearingRetiringFederationClient() { + private void clearRetiringFederationClient() { + logger.info("[triggerClientChange] Clearing retiring federation client"); + btcToRskClientRetiring.stop(); } } From a5adc16ee08fc67fabc69be3eb6f4b818e0e7036 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:34:57 +0000 Subject: [PATCH 016/112] feat(watcher): make the active federation be required to be non-null --- .../co/rsk/federate/watcher/FederationWatcher.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java index 2a05cb901..71cc5b0ab 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -9,6 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Objects; import java.util.Optional; /** @@ -87,8 +88,11 @@ private void updateState() { } private void updateActiveFederation() { - Address currentlyActiveFederationAddress = federationProvider.getActiveFederationAddress(); - Address oldActiveFederationAddress = Optional.ofNullable(activeFederation).map(Federation::getAddress).orElse(null); + Address currentlyActiveFederationAddress = Objects.requireNonNull( + federationProvider.getActiveFederationAddress(), "The current active federation should always exist"); + Address oldActiveFederationAddress = Optional.ofNullable(activeFederation) + .map(Federation::getAddress) + .orElse(null); boolean hasActiveFederationChanged = !currentlyActiveFederationAddress.equals(oldActiveFederationAddress); @@ -106,7 +110,8 @@ private void updateActiveFederation() { private void updateRetiringFederation() { Optional
currentlyRetiringFederationAddress = federationProvider.getRetiringFederationAddress(); - Optional
oldRetiringFederationAddress = Optional.ofNullable(retiringFederation).map(Federation::getAddress); + Optional
oldRetiringFederationAddress = Optional.ofNullable(retiringFederation) + .map(Federation::getAddress); boolean hasRetiringFederationChanged = !currentlyRetiringFederationAddress.equals(oldRetiringFederationAddress); From 7243352aff10e204788bcfebd07bf2af383e3fb8 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:05:56 +0000 Subject: [PATCH 017/112] refactor(btcreleaseclient): make better log statements for start and stop client methods --- .../federate/btcreleaseclient/BtcReleaseClient.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index ab92069e6..daf437945 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -173,25 +173,26 @@ public void start(Federation federation) { if (!observedFederations.contains(federation)) { observedFederations.add(federation); - logger.debug("[start] Observing federation {}", federation.getAddress()); + logger.info("[start] Observing federation {}", federation.getAddress()); } if (observedFederations.size() == 1) { // If there is just one observed Federation, it means the btcReleaseClient wasn't started - logger.debug("[start] Starting"); - ethereum.addListener(this.blockListener); + logger.info("[start] Starting block listener"); + ethereum.addListener(blockListener); } } public void stop(Federation federation) { if (observedFederations.contains(federation)) { observedFederations.remove(federation); - logger.debug("[stop] not observing Federation {}", federation.getAddress()); + logger.info("[stop] Stopping observing federation {}", federation.getAddress()); } + if (observedFederations.isEmpty()) { // If there are no more observed Federations, the btcReleaseClient should stop - logger.debug("[stop] Stopping"); - ethereum.removeListener(this.blockListener); + logger.info("[stop] Stopping block listener"); + ethereum.removeListener(blockListener); } } From 7c1e1c2b38c2359a4ab7af1a429dbab599ae63cf Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:07:19 +0000 Subject: [PATCH 018/112] fix(btcreleaseclient): if the member running the node is not part of the new federation should throw exception --- .../java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index daf437945..ce512b73f 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -162,7 +162,7 @@ public void setup( public void start(Federation federation) { FederationMember federationMember = federatorSupport.getFederationMember(); - if (federation.isMember(federationMember)) { + if (!federation.isMember(federationMember)) { String message = String.format( "Member %s is no part of the federation %s", federationMember.getBtcPublicKey(), From 0daf69c4d4aeac121065c06741aea739024d274e Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:39:30 +0000 Subject: [PATCH 019/112] feat(btcreleaseclient): add unit test for federation member is not part of new federationa and fix the rest of exisiting tests to accomdate new condition --- .../BtcReleaseClientTest.java | 87 +++++++++++++++++-- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index d8d1603a9..19fd299cb 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -114,6 +114,31 @@ void setup() { bridgeConstants = Constants.regtest().bridgeConstants; } + @Test + void start_whenFederationMemberNotPartOfDesiredFederation_shouldThrowException() { + // Arrange + PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) + .thenReturn(PEGOUT_SIGNED_CACHE_TTL); + + FederatorSupport federatorSupport = mock(FederatorSupport.class); + Federation federation = TestUtils.createFederation(params, 1); + Federation otherFederation = TestUtils.createFederation(params, 2); + FederationMember federationMember = otherFederation.getMembers().get(1); + doReturn(federationMember).when(federatorSupport).getFederationMember(); + + BtcReleaseClient btcReleaseClient = new BtcReleaseClient( + mock(Ethereum.class), + federatorSupport, + powpegNodeSystemProperties, + mock(NodeBlockProcessor.class) + ); + + // Act & Assert + assertThrows(IllegalStateException.class, () -> btcReleaseClient.start(federation)); + } + @Test void if_start_not_called_rsk_blockchain_not_listened() { Ethereum ethereum = mock(Ethereum.class); @@ -136,21 +161,26 @@ void if_start_not_called_rsk_blockchain_not_listened() { void when_start_called_rsk_blockchain_is_listened() { Ethereum ethereum = mock(Ethereum.class); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); + FederatorSupport federatorSupport = mock(FederatorSupport.class); Mockito.doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - mock(FederatorSupport.class), + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); Federation fed1 = TestUtils.createFederation(params, 1); + FederationMember federationMember1 = fed1.getMembers().get(0); + doReturn(federationMember1).when(federatorSupport).getFederationMember(); btcReleaseClient.start(fed1); Federation fed2 = TestUtils.createFederation(params, 1); + FederationMember federationMember2 = fed2.getMembers().get(0); + doReturn(federationMember2).when(federatorSupport).getFederationMember(); btcReleaseClient.start(fed2); verify(ethereum, Mockito.times(1)).addListener(ArgumentMatchers.any(EthereumListener.class)); @@ -160,21 +190,26 @@ void when_start_called_rsk_blockchain_is_listened() { void if_stop_called_with_just_one_federation_rsk_blockchain_is_still_listened() { Ethereum ethereum = mock(Ethereum.class); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); + FederatorSupport federatorSupport = mock(FederatorSupport.class); Mockito.doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); - + BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - mock(FederatorSupport.class), + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); Federation fed1 = TestUtils.createFederation(params, 1); + FederationMember federationMember1 = fed1.getMembers().get(0); + doReturn(federationMember1).when(federatorSupport).getFederationMember(); btcReleaseClient.start(fed1); Federation fed2 = TestUtils.createFederation(params, 1); + FederationMember federationMember2 = fed2.getMembers().get(0); + doReturn(federationMember2).when(federatorSupport).getFederationMember(); btcReleaseClient.start(fed2); Mockito.verify(ethereum, Mockito.times(1)).addListener(ArgumentMatchers.any(EthereumListener.class)); @@ -186,6 +221,7 @@ void if_stop_called_with_just_one_federation_rsk_blockchain_is_still_listened() @Test void if_stop_called_with_federations_rsk_blockchain_is_not_listened() { Ethereum ethereum = mock(Ethereum.class); + FederatorSupport federatorSupport = mock(FederatorSupport.class); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); Mockito.doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) @@ -193,15 +229,19 @@ void if_stop_called_with_federations_rsk_blockchain_is_not_listened() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - mock(FederatorSupport.class), + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); Federation fed1 = TestUtils.createFederation(params, 1); + FederationMember federationMember1 = fed1.getMembers().get(0); + doReturn(federationMember1).when(federatorSupport).getFederationMember(); btcReleaseClient.start(fed1); Federation fed2 = TestUtils.createFederation(params, 1); + FederationMember federationMember2 = fed2.getMembers().get(0); + doReturn(federationMember2).when(federatorSupport).getFederationMember(); btcReleaseClient.start(fed2); Mockito.verify(ethereum, Mockito.times(1)).addListener(ArgumentMatchers.any(EthereumListener.class)); @@ -215,6 +255,7 @@ void if_stop_called_with_federations_rsk_blockchain_is_not_listened() { void processReleases_ok() throws Exception { // Arrange Federation federation = TestUtils.createFederation(params, 1); + FederationMember federationMember = federation.getMembers().get(0); // Create a tx from the Fed to a random btc address BtcTransaction releaseTx = new BtcTransaction(params); @@ -245,9 +286,12 @@ void processReleases_ok() throws Exception { .any(ReleaseCreationInformation.class))) .thenReturn(messageBuilder); + FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); + BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - mock(FederatorSupport.class), + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -296,6 +340,7 @@ void processReleases_ok() throws Exception { void having_two_pegouts_signs_only_one() throws Exception { // Arrange Federation federation = TestUtils.createFederation(params, 1); + FederationMember federationMember = federation.getMembers().get(0); BtcTransaction tx1 = TestUtils.createBtcTransaction(params, federation); BtcTransaction tx2 = TestUtils.createBtcTransaction(params, federation); @@ -322,6 +367,7 @@ void having_two_pegouts_signs_only_one() throws Exception { any(byte[].class) ); doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); + doReturn(federationMember).when(federatorSupport).getFederationMember(); ECKey ecKey = new ECKey(); ECKey.ECDSASignature ethSig = ecKey.doSign(new byte[]{}); @@ -405,6 +451,7 @@ void having_two_pegouts_signs_only_one() throws Exception { @Test void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Exception { Federation federation = TestUtils.createFederation(params, 9); + FederationMember federationMember = federation.getMembers().get(0); BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); Keccak256 pegoutCreationRskTxHash = createHash(0); SortedMap rskTxsWaitingForSignatures = new TreeMap<>(); @@ -420,6 +467,7 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex FederatorSupport federatorSupport = mock(FederatorSupport.class); doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); + doReturn(federationMember).when(federatorSupport).getFederationMember(); ECKey ecKey = new ECKey(); BtcECKey fedKey = new BtcECKey(); @@ -508,6 +556,7 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex @Test void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTxAgain() throws Exception { Federation federation = TestUtils.createFederation(params, 9); + FederationMember federationMember = federation.getMembers().get(0); BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); Keccak256 pegoutCreationRskTxHash = createHash(0); SortedMap rskTxsWaitingForSignatures = new TreeMap<>(); @@ -523,6 +572,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx FederatorSupport federatorSupport = mock(FederatorSupport.class); doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); + doReturn(federationMember).when(federatorSupport).getFederationMember(); ECKey ecKey = new ECKey(); BtcECKey fedKey = new BtcECKey(); @@ -618,6 +668,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException { // Arrange Federation federation = TestUtils.createFederation(params, 1); + FederationMember federationMember = federation.getMembers().get(0); Ethereum ethereum = mock(Ethereum.class); AtomicReference ethereumListener = new AtomicReference<>(); @@ -628,6 +679,7 @@ void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException }).when(ethereum).addListener(ArgumentMatchers.any(EthereumListener.class)); FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); @@ -666,6 +718,7 @@ void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientException { // Arrange Federation federation = TestUtils.createFederation(params, 1); + FederationMember federationMember = federation.getMembers().get(0); Ethereum ethereum = mock(Ethereum.class); AtomicReference ethereumListener = new AtomicReference<>(); @@ -676,6 +729,7 @@ void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientExcepti }).when(ethereum).addListener(any(EthereumListener.class)); FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); @@ -714,6 +768,7 @@ void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientExcepti void onBlock_return_when_node_is_syncing() { // Arrange Federation federation = TestUtils.createFederation(params, 1); + FederationMember federationMember = federation.getMembers().get(0); Ethereum ethereum = mock(Ethereum.class); AtomicReference ethereumListener = new AtomicReference<>(); @@ -724,6 +779,7 @@ void onBlock_return_when_node_is_syncing() { }).when(ethereum).addListener(any(EthereumListener.class)); FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); @@ -758,6 +814,7 @@ void onBlock_return_when_node_is_syncing() { void onBlock_return_when_pegout_is_disabled() { // Arrange Federation federation = TestUtils.createFederation(params, 1); + FederationMember federationMember = federation.getMembers().get(0); Ethereum ethereum = mock(Ethereum.class); AtomicReference ethereumListener = new AtomicReference<>(); @@ -768,6 +825,7 @@ void onBlock_return_when_pegout_is_disabled() { }).when(ethereum).addListener(any(EthereumListener.class)); FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); @@ -862,6 +920,7 @@ void validateTxCanBeSigned_federatorAlreadySigned() throws Exception { FederationArgs federationArgs = new FederationArgs(fedMembers, Instant.now(), 0, params); Federation federation = FederationFactory.buildStandardMultiSigFederation(federationArgs); + FederationMember federationMember = federation.getMembers().get(0); // Create a tx from the Fed to a random btc address BtcTransaction releaseTx = new BtcTransaction(params); @@ -891,13 +950,17 @@ void validateTxCanBeSigned_federatorAlreadySigned() throws Exception { ECPublicKey signerPublicKey = new ECPublicKey(federator1PrivKey.getPubKey()); ECDSASigner signer = mock(ECDSASigner.class); Mockito.doReturn(signerPublicKey).when(signer).getPublicKey(ArgumentMatchers.any(KeyId.class)); + + FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - mock(FederatorSupport.class), + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); + client.setup( signer, mock(ActivationConfig.class), @@ -907,6 +970,7 @@ void validateTxCanBeSigned_federatorAlreadySigned() throws Exception { mock(BtcReleaseClientStorageAccessor.class), mock(BtcReleaseClientStorageSynchronizer.class) ); + client.start(federation); // Act @@ -1065,6 +1129,11 @@ private void test_validateTxCanBeSigned( BtcTransaction releaseTx, ECPublicKey signerPublicKey ) throws Exception { + FederationMember federationMember = federation.getMembers().get(0); + + FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); + PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) @@ -1075,10 +1144,11 @@ private void test_validateTxCanBeSigned( BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - mock(FederatorSupport.class), + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); + client.setup( signer, mock(ActivationConfig.class), @@ -1088,6 +1158,7 @@ private void test_validateTxCanBeSigned( mock(BtcReleaseClientStorageAccessor.class), mock(BtcReleaseClientStorageSynchronizer.class) ); + client.start(federation); // Act @@ -1185,6 +1256,7 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) BtcECKey key3 = new BtcECKey(); List keys = Arrays.asList(key1, key2, key3); Federation federation = createFederation(keys); + FederationMember federationMember = federation.getMembers().get(0); // Release info Keccak256 rskTxHash = createHash(0); @@ -1207,6 +1279,7 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) when(federatorSupport.getStateForFederator()).thenReturn( new StateForFederator(rskTxsWaitingForSignatures) // Only return the confirmed release ); + when(federatorSupport.getFederationMember()).thenReturn(federationMember); ECDSASigner signer = mock(ECDSASigner.class); when(signer.getVersionForKeyId(any())).thenReturn(2); From 8b39470a4f0f81b220773417f75c8ea06a8cfba0 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:01:41 +0000 Subject: [PATCH 020/112] feat(watcher): add unit tests for FederationWatcherListenerImpl --- .../FederationWatcherListenerImplTest.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java new file mode 100644 index 000000000..64d19c4d5 --- /dev/null +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java @@ -0,0 +1,99 @@ +package co.rsk.federate.watcher; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.NetworkParameters; +import co.rsk.federate.BtcToRskClient; +import co.rsk.federate.btcreleaseclient.BtcReleaseClient; +import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationArgs; +import co.rsk.peg.federation.FederationFactory; +import co.rsk.peg.federation.FederationMember; +import java.math.BigInteger; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import org.ethereum.crypto.ECKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FederationWatcherListenerImplTest { + + private static final NetworkParameters NETWORK_PARAMETERS = NetworkParameters.fromID(NetworkParameters.ID_REGTEST); + + private static final List FIRST_FEDERATION_MEMBERS = + getFederationMembersFromPksForBtc(1000, 2000, 3000, 4000); + private static final long CREATION_BLOCK_NUMBER = 0L; + private static final Instant FIRST_FEDERATION_CREATION_TIME = Instant.ofEpochMilli(5005L); + private static final FederationArgs FIRST_FEDERATION_ARGS = new FederationArgs( + FIRST_FEDERATION_MEMBERS, FIRST_FEDERATION_CREATION_TIME, CREATION_BLOCK_NUMBER, NETWORK_PARAMETERS); + private static final Federation FIRST_FEDERATION = FederationFactory.buildStandardMultiSigFederation(FIRST_FEDERATION_ARGS); + + private BtcToRskClient btcToRskClientActive; + private BtcToRskClient btcToRskClientRetiring; + private BtcReleaseClient btcReleaseClient; + private FederationWatcherListener federationWatcherListener; + + @BeforeEach + void setUp() { + btcToRskClientActive = mock(BtcToRskClient.class); + btcToRskClientRetiring = mock(BtcToRskClient.class); + btcReleaseClient = mock(BtcReleaseClient.class); + federationWatcherListener = new FederationWatcherListenerImpl( + btcToRskClientActive, btcToRskClientRetiring, btcReleaseClient); + } + + @Test + void onActiveFederationChange_whenFederationIsValid_shouldTriggerClientChange() { + // Act + federationWatcherListener.onActiveFederationChange(FIRST_FEDERATION); + + // Assert + verify(btcToRskClientActive).stop(); + verify(btcReleaseClient).stop(FIRST_FEDERATION); + verify(btcToRskClientActive).start(FIRST_FEDERATION); + verify(btcReleaseClient).start(FIRST_FEDERATION); + } + + @Test + void onRetiringFederationChange_whenFederationIsNull_shouldClearRetiringFederationClient() { + // Act + federationWatcherListener.onRetiringFederationChange(null); + + // Assert + verify(btcToRskClientRetiring).stop(); + } + + @Test + void onRetiringFederationChange_whenFederationIsValid_shouldTriggerClientChange() { + // Act + federationWatcherListener.onRetiringFederationChange(FIRST_FEDERATION); + + // Assert + verify(btcToRskClientRetiring).stop(); + verify(btcReleaseClient).stop(FIRST_FEDERATION); + verify(btcToRskClientRetiring).start(FIRST_FEDERATION); + verify(btcReleaseClient).start(FIRST_FEDERATION); + } + + @Test + void triggerClientChange_whenExceptionOccurs_shouldHandleException() { + // Arrange + // Simulate an exception in one of the called methods + doThrow(new RuntimeException("Simulated exception")).when(btcToRskClientActive).stop(); + + // Act & Assert + assertDoesNotThrow(() -> federationWatcherListener.onActiveFederationChange(FIRST_FEDERATION)); + } + + private static List getFederationMembersFromPksForBtc(Integer... pks) { + return Arrays.stream(pks).map(n -> new FederationMember( + BtcECKey.fromPrivate(BigInteger.valueOf(n)), + new ECKey(), + new ECKey())).toList(); + } +} From 21114f8ab923ef5ef9d51308b64e06ce5cae6e00 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:00:59 +0000 Subject: [PATCH 021/112] feat(config): add description for isPegoutEnabled parameter --- .../config/PowpegNodeConfigParameter.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/co/rsk/federate/config/PowpegNodeConfigParameter.java b/src/main/java/co/rsk/federate/config/PowpegNodeConfigParameter.java index d57e7a18c..317d59615 100644 --- a/src/main/java/co/rsk/federate/config/PowpegNodeConfigParameter.java +++ b/src/main/java/co/rsk/federate/config/PowpegNodeConfigParameter.java @@ -4,13 +4,15 @@ import java.util.function.Function; public enum PowpegNodeConfigParameter { - FEDERATOR_ENABLED("federator.enabled", Boolean.toString(true)), - PEGOUT_ENABLED("federator.pegout.enabled", Boolean.toString(true)), - UPDATE_BRIDGE_TIMER_ENABLED("federator.updateBridgeTimerEnabled", Boolean.toString(true)), - UPDATE_BRIDGE_BTC_BLOCKCHAIN("federator.updateBridgeBtcBlockchain", Boolean.toString(true)), - UPDATE_BRIDGE_BTC_COINBASE_TRANSACTIONS("federator.updateBridgeBtcCoinbaseTransactions", Boolean.toString(true)), - UPDATE_BRIDGE_BTC_TRANSACTIONS("federator.updateBridgeBtcTransactions", Boolean.toString(true)), - UPDATE_COLLECTIONS("federator.updateCollections", Boolean.toString(true)), + FEDERATOR_ENABLED("federator.enabled", Boolean.TRUE.toString()), + // when enabled the federator will be able to attempt signing + // rsk transactions waiting for signatures reported by the Bridge + PEGOUT_ENABLED("federator.pegout.enabled", Boolean.TRUE.toString()), + UPDATE_BRIDGE_TIMER_ENABLED("federator.updateBridgeTimerEnabled", Boolean.TRUE.toString()), + UPDATE_BRIDGE_BTC_BLOCKCHAIN("federator.updateBridgeBtcBlockchain", Boolean.TRUE.toString()), + UPDATE_BRIDGE_BTC_COINBASE_TRANSACTIONS("federator.updateBridgeBtcCoinbaseTransactions", Boolean.TRUE.toString()), + UPDATE_BRIDGE_BTC_TRANSACTIONS("federator.updateBridgeBtcTransactions", Boolean.TRUE.toString()), + UPDATE_COLLECTIONS("federator.updateCollections", Boolean.TRUE.toString()), GAS_PRICE("federator.gasPrice", "0"), GAS_PRICE_PROVIDER("federator.gasPriceProvider", ""), AMOUNT_HEADERS("federator.amountOfHeadersToSend", "25"), From ca648965ed9e8cf5f79579bf8b8eccaf3aa84f38 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:12:15 +0000 Subject: [PATCH 022/112] refactor(federate): remove unused method getLiveFederations --- src/main/java/co/rsk/federate/FederationProvider.java | 6 ------ .../FederationProviderFromFederatorSupport.java | 11 ----------- 2 files changed, 17 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProvider.java b/src/main/java/co/rsk/federate/FederationProvider.java index 11d8a750c..6900bef09 100644 --- a/src/main/java/co/rsk/federate/FederationProvider.java +++ b/src/main/java/co/rsk/federate/FederationProvider.java @@ -19,7 +19,6 @@ import co.rsk.bitcoinj.core.Address; import co.rsk.peg.federation.Federation; -import java.util.List; import java.util.Optional; /** @@ -41,9 +40,4 @@ public interface FederationProvider { // The currently "proposed" federation's address Optional
getProposedFederationAddress(); - - // The federations that are "live", that is, are still - // operational. This should be the active federation - // plus the retiring federation, if one exists - List getLiveFederations(); } diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 173034a32..ba411ee06 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -140,17 +140,6 @@ public Optional
getProposedFederationAddress() { return federatorSupport.getProposedFederationAddress(); } - @Override - public List getLiveFederations() { - List result = new ArrayList<>(); - result.add(getActiveFederation()); - - Optional retiringFederation = getRetiringFederation(); - retiringFederation.ifPresent(result::add); - - return result; - } - private Federation getExpectedFederation(Federation initialFederation, Address expectedFederationAddress) { // First check if the initial federation address matches the expected one if (initialFederation.getAddress().equals(expectedFederationAddress)) { From 1724576eb1d7b454971fb036d0a71e997a10073f Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:12:46 +0000 Subject: [PATCH 023/112] refactor(federate): remove unused unit tests for getLiveFederations method --- ...ationProviderFromFederatorSupportTest.java | 439 ------------------ 1 file changed, 439 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index f3ce9769f..75dc201e0 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -395,445 +395,6 @@ void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional( assertFalse(result.isPresent()); } - @Test - void getLiveFederations_onlyActive_beforeMultikey() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(false); - - Federation expectedActiveFederation = createFederation( - getFederationMembersFromPks(0, 1000, 2000, 3000, 4000) - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(4); - when(federatorSupportMock.getFederationThreshold()).thenReturn(2); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 4; i++) { - when(federatorSupportMock.getFederatorPublicKey(i)).thenReturn(BtcECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - } - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(-1); - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(1, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - } - - @Test - void getLiveFederations_onlyActive_afterMultikey() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createFederation( - getFederationMembersFromPks(1,1000, 2000, 3000, 4000) - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(4); - when(federatorSupportMock.getFederationThreshold()).thenReturn(2); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 4; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(-1); - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(1, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - } - - @Test - void getLiveFederations_onlyActive_erp_federation() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createNonStandardErpFederation( - getFederationMembersFromPks(1,1000, 2000, 3000, 4000), - configMock - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(4); - when(federatorSupportMock.getFederationThreshold()).thenReturn(2); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 4; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(-1); - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(1, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - } - - @Test - void getLiveFederations_onlyActive_p2sh_erp_federation() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createP2shErpFederation( - getFederationMembersFromPks(1,1000, 2000, 3000, 4000) - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(4); - when(federatorSupportMock.getFederationThreshold()).thenReturn(2); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 4; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(-1); - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(1, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(P2SH_ERP_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - } - - @Test - void getLiveFederations_both_beforeMultikey() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(false); - - Federation expectedActiveFederation = createFederation( - getFederationMembersFromPks(0,1000, 2000, 3000, 4000) - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - Federation expectedRetiringFederation = createFederation( - getFederationMembersFromPks(0, 2000, 4000, 6000, 8000, 10000, 12000) - ); - Address expectedRetiringFederationAddress = expectedRetiringFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(4); - when(federatorSupportMock.getFederationThreshold()).thenReturn(2); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 4; i++) { - when(federatorSupportMock.getFederatorPublicKey(i)).thenReturn(BtcECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - } - - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(6); - when(federatorSupportMock.getRetiringFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getRetiringFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.of(expectedRetiringFederationAddress)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 6; i++) { - when(federatorSupportMock.getRetiringFederatorPublicKey(i)).thenReturn(BtcECKey.fromPrivate(BigInteger.valueOf((i+1)*2000))); - } - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(2, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - - Federation retiringFederation = liveFederations.get(1); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, retiringFederation.getFormatVersion()); - assertEquals(expectedRetiringFederation, retiringFederation); - assertEquals(expectedRetiringFederationAddress, retiringFederation.getAddress()); - } - - @Test - void getLiveFederations_both_afterMultikey() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createFederation( - getFederationMembersFromPks(1,1000, 2000, 3000, 4000) - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - Federation expectedRetiringFederation = createFederation( - getFederationMembersFromPks(1,2000, 4000, 6000, 8000, 10000, 12000) - ); - Address expectedRetiringFederationAddress = expectedRetiringFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(4); - when(federatorSupportMock.getFederationThreshold()).thenReturn(2); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 4; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(6); - when(federatorSupportMock.getRetiringFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getRetiringFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.of(expectedRetiringFederationAddress)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 6; i++) { - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+1))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+2))); - } - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(2, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - - Federation retiringFederation = liveFederations.get(1); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, retiringFederation.getFormatVersion()); - assertEquals(expectedRetiringFederation, retiringFederation); - assertEquals(expectedRetiringFederationAddress, retiringFederation.getAddress()); - } - - @Test - void getLiveFederations_both_erp_federations() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createNonStandardErpFederation( - getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000), - configMock - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - Federation expectedRetiringFederation = createNonStandardErpFederation( - getFederationMembersFromPks(1,2000, 4000, 6000, 8000, 10000), - configMock - ); - Address expectedRetiringFederationAddress = expectedRetiringFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(5); - when(federatorSupportMock.getFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(5); - when(federatorSupportMock.getRetiringFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getRetiringFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.of(expectedRetiringFederationAddress)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+1))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+2))); - } - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(2, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - - Federation retiringFederation = liveFederations.get(1); - assertEquals(NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION, retiringFederation.getFormatVersion()); - assertEquals(expectedRetiringFederation, retiringFederation); - assertEquals(expectedRetiringFederationAddress, retiringFederation.getAddress()); - } - - @Test - void getLiveFederations_retiring_multikey_active_erp() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createNonStandardErpFederation( - getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000), - configMock - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - Federation expectedRetiringFederation = createFederation( - getFederationMembersFromPks(1,2000, 4000, 6000, 8000, 10000) - ); - Address expectedRetiringFederationAddress = expectedRetiringFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(5); - when(federatorSupportMock.getFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(5); - when(federatorSupportMock.getRetiringFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getRetiringFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.of(expectedRetiringFederationAddress)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+1))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+2))); - } - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(2, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - - Federation retiringFederation = liveFederations.get(1); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, retiringFederation.getFormatVersion()); - assertEquals(expectedRetiringFederation, retiringFederation); - assertEquals(expectedRetiringFederationAddress, retiringFederation.getAddress()); - } - - @Test - void getLiveFederations_retiring_erp_active_p2sh_erp() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createP2shErpFederation( - getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000) - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - Federation expectedRetiringFederation = createNonStandardErpFederation( - getFederationMembersFromPks(1,2000, 4000, 6000, 8000, 10000), - configMock - ); - Address expectedRetiringFederationAddress = expectedRetiringFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(5); - when(federatorSupportMock.getFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(5); - when(federatorSupportMock.getRetiringFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getRetiringFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.of(expectedRetiringFederationAddress)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+1))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+2))); - } - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(2, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(P2SH_ERP_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - - Federation retiringFederation = liveFederations.get(1); - assertEquals(NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION, retiringFederation.getFormatVersion()); - assertEquals(expectedRetiringFederation, retiringFederation); - assertEquals(expectedRetiringFederationAddress, retiringFederation.getAddress()); - } - - @Test - void getLiveFederations_both_p2sh_erp_federations() { - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP123)).thenReturn(true); - - Federation expectedActiveFederation = createP2shErpFederation( - getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000) - ); - Address expectedActiveFederationAddress = expectedActiveFederation.getAddress(); - - Federation expectedRetiringFederation = createP2shErpFederation( - getFederationMembersFromPks(1,2000, 4000, 6000, 8000, 10000) - ); - Address expectedRetiringFederationAddress = expectedRetiringFederation.getAddress(); - - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getFederationSize()).thenReturn(5); - when(federatorSupportMock.getFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getFederationAddress()).thenReturn(expectedActiveFederationAddress); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1))); - when(federatorSupportMock.getFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2))); - } - - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(5); - when(federatorSupportMock.getRetiringFederationThreshold()).thenReturn(3); - when(federatorSupportMock.getRetiringFederationCreationTime()).thenReturn(creationTime); - when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.of(expectedRetiringFederationAddress)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < 5; i++) { - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+1))); - when(federatorSupportMock.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)).thenReturn(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+2))); - } - - List liveFederations = federationProvider.getLiveFederations(); - assertEquals(2, liveFederations.size()); - - Federation activeFederation = liveFederations.get(0); - assertEquals(P2SH_ERP_FEDERATION_FORMAT_VERSION, activeFederation.getFormatVersion()); - assertEquals(expectedActiveFederation, activeFederation); - assertEquals(expectedActiveFederationAddress, activeFederation.getAddress()); - - Federation retiringFederation = liveFederations.get(1); - assertEquals(P2SH_ERP_FEDERATION_FORMAT_VERSION, retiringFederation.getFormatVersion()); - assertEquals(expectedRetiringFederation, retiringFederation); - assertEquals(expectedRetiringFederationAddress, retiringFederation.getAddress()); - } - private Federation createFederation(List members) { FederationArgs federationArgs = new FederationArgs(members, creationTime, 0L, testnetParams); return FederationFactory.buildStandardMultiSigFederation(federationArgs); From 0931b4c4905787cd55d1e0da5adda59f81d63251 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:55:19 +0000 Subject: [PATCH 024/112] refactor(btcreleaseclient): enhance javadoc for class --- .../btcreleaseclient/BtcReleaseClient.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index ce512b73f..e6ac66e35 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -1,5 +1,7 @@ package co.rsk.federate.btcreleaseclient; +import static co.rsk.federate.signing.PowPegNodeKeyId.BTC; + import co.rsk.bitcoinj.core.BtcECKey; import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.TransactionInput; @@ -72,11 +74,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static co.rsk.federate.signing.PowPegNodeKeyId.BTC; - /** - * Manages signing and broadcasting pegouts - * @author Oscar Guindzberg + * Responsible for managing the signing and broadcasting of pegout transactions + * to the Bitcoin network in a federated bridge environment. The BtcReleaseClient + * coordinates the execution of pegout operations, ensuring transactions are + * correctly signed and propagated. + * + *

Key responsibilities include:

+ *
    + *
  • Assembling transaction data and managing signing processes
  • + *
  • Validating transaction information before broadcast
  • + *
  • Ensuring successful pegout transaction broadcast to the Bitcoin network
  • + *
*/ public class BtcReleaseClient { private static final Logger logger = LoggerFactory.getLogger(BtcReleaseClient.class); From 42ba67fc196fdfa137f13f6fc107cfb5bb7bd8f4 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:58:13 +0000 Subject: [PATCH 025/112] refactor(btcreleaseclient): small refactor to include proper spaces and identation --- .../btcreleaseclient/BtcReleaseClient.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index e6ac66e35..193baa5dd 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -88,14 +88,12 @@ * */ public class BtcReleaseClient { + private static final Logger logger = LoggerFactory.getLogger(BtcReleaseClient.class); private static final PanicProcessor panicProcessor = new PanicProcessor(); private static final List SINGLE_RELEASE_BTC_TOPIC_RLP = Collections.singletonList(Bridge.RELEASE_BTC_TOPIC); private static final DataWord SINGLE_RELEASE_BTC_TOPIC_SOLIDITY = DataWord.valueOf(BridgeEvents.RELEASE_BTC.getEvent().encodeSignatureLong()); - private ActivationConfig activationConfig; - private PeerGroup peerGroup; - private final Ethereum ethereum; private final FederatorSupport federatorSupport; private final Set observedFederations; @@ -104,13 +102,13 @@ public class BtcReleaseClient { private final boolean isPegoutEnabled; private final PegoutSignedCache pegoutSignedCache; + private ActivationConfig activationConfig; + private PeerGroup peerGroup; private ECDSASigner signer; private BtcReleaseEthereumListener blockListener; private SignerMessageBuilderFactory signerMessageBuilderFactory; - private ReleaseCreationInformationGetter releaseCreationInformationGetter; private ReleaseRequirementsEnforcer releaseRequirementsEnforcer; - private BtcReleaseClientStorageAccessor storageAccessor; private BtcReleaseClientStorageSynchronizer storageSynchronizer; @@ -144,7 +142,8 @@ public void setup( this.activationConfig = activationConfig; logger.debug("[setup] Signer: {}", signer.getClass()); - org.bitcoinj.core.Context btcContext = new org.bitcoinj.core.Context(ThinConverter.toOriginalInstance(bridgeConstants.getBtcParamsString())); + org.bitcoinj.core.Context btcContext = new org.bitcoinj.core.Context( + ThinConverter.toOriginalInstance(bridgeConstants.getBtcParamsString())); peerGroup = new PeerGroup(btcContext); try { if (!federatorSupport.getBitcoinPeerAddresses().isEmpty()) { @@ -153,7 +152,7 @@ public void setup( } peerGroup.setMaxConnections(federatorSupport.getBitcoinPeerAddresses().size()); } - } catch(Exception e) { + } catch (Exception e) { throw new BtcReleaseClientException("Error configuring peerSupport", e); } peerGroup.start(); @@ -207,7 +206,9 @@ public void stop(Federation federation) { @PreDestroy public void tearDown() { - org.bitcoinj.core.Context.propagate(new org.bitcoinj.core.Context(ThinConverter.toOriginalInstance(bridgeConstants.getBtcParamsString()))); + org.bitcoinj.core.Context.propagate( + new org.bitcoinj.core.Context( + ThinConverter.toOriginalInstance(bridgeConstants.getBtcParamsString()))); peerGroup.stop(); peerGroup = null; } @@ -225,6 +226,7 @@ public void onBestBlock(org.ethereum.core.Block block, List ); return; } + // Processing transactions waiting for signatures on best block only still "works", // since it all lies within RSK's blockchain and normal rules apply. I.e., this // process works on a block-by-block basis. @@ -243,6 +245,7 @@ public void onBlock(org.ethereum.core.Block block, List rece if (!isPegoutEnabled || nodeBlockProcessor.hasBetterBlockToSync()) { return; } + /* Pegout events must be processed on an every-single-block basis, since otherwise we could be missing pegouts potentially mined on what originally were side-chains and then turned into best-chains.*/ From ffc0e5ea71f60fffe4c19635aa567b916e2e79e7 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:14:13 +0000 Subject: [PATCH 026/112] feat(federate): add bridge call to retrieve svp spend tx waiting for signatures --- src/main/java/co/rsk/federate/FederatorSupport.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index f75da5bfc..f5e5b970c 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -11,6 +11,7 @@ import co.rsk.peg.Bridge; import co.rsk.peg.federation.FederationMember; import co.rsk.peg.StateForFederator; +import co.rsk.peg.StateForProposedFederator; import org.bitcoinj.core.PartialMerkleTree; import org.bitcoinj.core.PeerAddress; import org.bitcoinj.core.Sha256Hash; @@ -150,6 +151,13 @@ public StateForFederator getStateForFederator() { return new StateForFederator(result, this.parameters); } + public Optional getStateForProposedFederator() { + byte[] result = bridgeTransactionSender.callTx( + federatorAddress, Bridge.GET_STATE_FOR_SVP_CLIENT); + + return Optional.ofNullable(result) + .map(rlpData -> new StateForProposedFederator(rlpData, parameters)); + } public void addSignature(List signatures, byte[] rskTxHash) { byte[] federatorPublicKeyBytes = federationMember.getBtcPublicKey().getPubKey(); From 1b3008c4cd935e9a9d0772ce9ff373a18992c19d Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:17:42 +0000 Subject: [PATCH 027/112] refactor(federate): remove unnecessary blank lines in FederateSupport class --- src/main/java/co/rsk/federate/FederatorSupport.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index f5e5b970c..1429f7878 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -36,13 +36,10 @@ public class FederatorSupport { private final Blockchain blockchain; private final PowpegNodeSystemProperties config; - private final NetworkParameters parameters; - private final BridgeTransactionSender bridgeTransactionSender; private ECDSASigner signer; - private FederationMember federationMember; private RskAddress federatorAddress; @@ -52,9 +49,7 @@ public FederatorSupport( BridgeTransactionSender bridgeTransactionSender) { this.blockchain = blockchain; this.config = config; - this.parameters = config.getNetworkConstants().getBridgeConstants().getBtcParams(); - this.bridgeTransactionSender = bridgeTransactionSender; } From d9c6b63bc87642a9d18e62c3de151505a5ed1bbb Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:51:53 +0000 Subject: [PATCH 028/112] feat(btcreleaseclient): add call to getStateForProposedFederator when processing a best block and add precondition method isReadyToBeSigned --- .../java/co/rsk/federate/FedNodeContext.java | 1 + .../btcreleaseclient/BtcReleaseClient.java | 65 ++++++++++++++++--- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/main/java/co/rsk/federate/FedNodeContext.java b/src/main/java/co/rsk/federate/FedNodeContext.java index cb7abecfd..2335cf758 100644 --- a/src/main/java/co/rsk/federate/FedNodeContext.java +++ b/src/main/java/co/rsk/federate/FedNodeContext.java @@ -59,6 +59,7 @@ public NodeRunner buildNodeRunner() { getBtcToRskClientRetiring(), new BtcReleaseClient( getRsk(), + getBlockStore(), getFederatorSupport(), getPowpegNodeSystemProperties(), getNodeBlockProcessor() diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index 193baa5dd..e03c4c9a6 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -41,6 +41,7 @@ import co.rsk.peg.federation.FederationMember; import co.rsk.peg.federation.ErpFederation; import co.rsk.peg.StateForFederator; +import co.rsk.peg.StateForProposedFederator; import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; @@ -62,8 +63,10 @@ import org.bitcoinj.script.ScriptPattern; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; +import org.ethereum.core.Block; import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; +import org.ethereum.db.BlockStore; import org.ethereum.facade.Ethereum; import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.util.RLP; @@ -95,6 +98,7 @@ public class BtcReleaseClient { private static final DataWord SINGLE_RELEASE_BTC_TOPIC_SOLIDITY = DataWord.valueOf(BridgeEvents.RELEASE_BTC.getEvent().encodeSignatureLong()); private final Ethereum ethereum; + private final BlockStore blockStore; private final FederatorSupport federatorSupport; private final Set observedFederations; private final NodeBlockProcessor nodeBlockProcessor; @@ -114,11 +118,13 @@ public class BtcReleaseClient { public BtcReleaseClient( Ethereum ethereum, + BlockStore blockStore, FederatorSupport federatorSupport, PowpegNodeSystemProperties systemProperties, NodeBlockProcessor nodeBlockProcessor ) { this.ethereum = ethereum; + this.blockStore = blockStore; this.federatorSupport = federatorSupport; this.observedFederations = new HashSet<>(); this.blockListener = new BtcReleaseEthereumListener(); @@ -216,28 +222,37 @@ public void tearDown() { private class BtcReleaseEthereumListener extends EthereumListenerAdapter { @Override public void onBestBlock(org.ethereum.core.Block block, List receipts) { + if (!isPegoutEnabled) { + return; + } + boolean hasBetterBlockToSync = nodeBlockProcessor.hasBetterBlockToSync(); boolean isStorageSynced = storageSynchronizer.isSynced(); if (hasBetterBlockToSync || !isStorageSynced) { logger.trace( - "[onBestBlock] Node is not ready to process pegouts. hasBetterBlockToSync: {} isStorageSynced: {}", + "[onBestBlock] Node is not ready to process pegouts. hasBetterBlockToSync: {} - isStorageSynced: {}", hasBetterBlockToSync, isStorageSynced ); return; } + storageSynchronizer.processBlock(block, receipts); + + // Check if svp spend tx waiting for signatures is available to be signed + // before attempting to sign any pegouts. + federatorSupport.getStateForProposedFederator() + .map(StateForProposedFederator::getSvpSpendTxWaitingForSignatures) + .filter(svpSpendTxWaitingForSignatures -> isReadyToSign(block.getNumber(), svpSpendTxWaitingForSignatures.getKey())) + .ifPresent(svpSpendTxReadyToBeSigned -> processReleases(Set.of(svpSpendTxReadyToBeSigned))); // Processing transactions waiting for signatures on best block only still "works", // since it all lies within RSK's blockchain and normal rules apply. I.e., this // process works on a block-by-block basis. StateForFederator stateForFederator = federatorSupport.getStateForFederator(); - storageSynchronizer.processBlock(block, receipts); - - // Delegate processing to our own method - logger.trace("[onBestBlock] Got {} pegouts", stateForFederator.getRskTxsWaitingForSignatures().entrySet().size()); - if (isPegoutEnabled) { - processReleases(stateForFederator.getRskTxsWaitingForSignatures().entrySet()); - } + Set> rskTxsReadyToBeSigned = stateForFederator.getRskTxsWaitingForSignatures().entrySet().stream() + .filter(rskTxWaitingForSignatures -> isReadyToSign(block.getNumber(), rskTxWaitingForSignatures.getKey())) + .collect(Collectors.toUnmodifiableSet()); + processReleases(rskTxsReadyToBeSigned); } @Override @@ -264,6 +279,38 @@ public void onBlock(org.ethereum.core.Block block, List rece pegoutTxs.forEach(BtcReleaseClient.this::onBtcRelease); } + /** + * Determines if a transaction hash is ready to be signed based on its block confirmations. + * + *

+ * This method retrieves the block associated with the given transaction hash and calculates + * the difference in block numbers between the current block and the block containing the transaction. + * If the difference meets or exceeds the required confirmation threshold defined in the bridge constants, + * the transaction is considered ready for signing. + *

+ * + * @param currentBlockNumber the current block number in the blockchain + * @param txHashWaitingForSignatures the Keccak256 hash of the transaction waiting to be signed + * @return {@code true} if the transaction has the required number of confirmations and is ready to be signed; + * {@code false} otherwise + */ + private boolean isReadyToSign(long currentBlockNumber, Keccak256 txHashWaitingForSignatures) { + boolean isReadyToSign = Optional.ofNullable(txHashWaitingForSignatures) + .map(Keccak256::getBytes) + .map(blockStore::getBlockByHash) + .map(Block::getNumber) + .map(blockNumberWithTxWaitingForSignatures -> currentBlockNumber - blockNumberWithTxWaitingForSignatures) + .filter(confirmationDifference -> confirmationDifference >= bridgeConstants.getRsk2BtcMinimumAcceptableConfirmations()) + .isPresent(); + + logger.info("[isReadyToSign] Readiness check for signing: Tx hash [{}], Current block [{}], Ready to sign? [{}]", + txHashWaitingForSignatures, + currentBlockNumber, + isReadyToSign ? "YES" : "NO"); + + return isReadyToSign; + } + private BtcTransaction convertToBtcTxFromRLPData(byte[] dataFromBtcReleaseTopic) { RLPList dataElements = (RLPList)RLP.decode2(dataFromBtcReleaseTopic).get(0); @@ -278,7 +325,7 @@ private BtcTransaction convertToBtcTxFromSolidityData(byte[] dataFromBtcReleaseT protected void processReleases(Set> pegouts) { try { - logger.debug("[processReleases] Starting process with {} pegouts", pegouts.size()); + logger.info("[processReleases] Starting signing process with {} pegouts", pegouts.size()); int version = signer.getVersionForKeyId(BTC.getKeyId()); // Get pegout information and store it in a new list List pegoutsReadyToSign = new ArrayList<>(); From 7f5453c54492ff1434db8c4047f1a86c6f5818e9 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:13:57 +0000 Subject: [PATCH 029/112] refactor(btcreleaseclient): small change to code structure for rsk tx wfs stream --- .../co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index e03c4c9a6..1bbcf4031 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -249,7 +249,9 @@ public void onBestBlock(org.ethereum.core.Block block, List // since it all lies within RSK's blockchain and normal rules apply. I.e., this // process works on a block-by-block basis. StateForFederator stateForFederator = federatorSupport.getStateForFederator(); - Set> rskTxsReadyToBeSigned = stateForFederator.getRskTxsWaitingForSignatures().entrySet().stream() + Set> rskTxsReadyToBeSigned = stateForFederator.getRskTxsWaitingForSignatures() + .entrySet() + .stream() .filter(rskTxWaitingForSignatures -> isReadyToSign(block.getNumber(), rskTxWaitingForSignatures.getKey())) .collect(Collectors.toUnmodifiableSet()); processReleases(rskTxsReadyToBeSigned); From c45e3f485d86452e4ce02ca261c6a82f426c2dc7 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:26:45 +0000 Subject: [PATCH 030/112] feat(btcreleaseclient): add unit tests for svp tx waiting for signatures and pegout is not ready to be signed --- .../BtcReleaseClientTest.java | 252 ++++++++++++++++-- 1 file changed, 235 insertions(+), 17 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 19fd299cb..fcf91285d 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -61,6 +61,7 @@ import co.rsk.net.NodeBlockProcessor; import co.rsk.peg.federation.*; import co.rsk.peg.StateForFederator; +import co.rsk.peg.StateForProposedFederator; import java.lang.reflect.Field; import java.math.BigInteger; @@ -68,16 +69,17 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneId; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; - import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.core.Block; @@ -97,10 +99,12 @@ class BtcReleaseClientTest { - private final static Duration PEGOUT_SIGNED_CACHE_TTL = Duration.ofMinutes(30); + private static final Duration PEGOUT_SIGNED_CACHE_TTL = Duration.ofMinutes(30); - private NetworkParameters params; - private BridgeConstants bridgeConstants; + private final BlockStore blockStore = mock(BlockStore.class); + private final Block bestBlock = mock(Block.class); + private final NetworkParameters params = RegTestParams.get(); + private final BridgeConstants bridgeConstants = Constants.regtest().bridgeConstants; private static final List erpFedKeys = Arrays.stream(new String[]{ "03b9fc46657cf72a1afa007ecf431de1cd27ff5cc8829fa625b66ca47b967e6b24", @@ -110,8 +114,11 @@ class BtcReleaseClientTest { @BeforeEach void setup() { - params = RegTestParams.get(); - bridgeConstants = Constants.regtest().bridgeConstants; + // ensure confirmation difference always passes + Block blockWithTxWaitingForSignatures = mock(Block.class); + when(blockWithTxWaitingForSignatures.getNumber()).thenReturn(0L); + when(blockStore.getBlockByHash(any())).thenReturn(blockWithTxWaitingForSignatures); + when(bestBlock.getNumber()).thenReturn(5_000L); } @Test @@ -149,6 +156,7 @@ void if_start_not_called_rsk_blockchain_not_listened() { new BtcReleaseClient( ethereum, + blockStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -169,6 +177,8 @@ void when_start_called_rsk_blockchain_is_listened() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, federatorSupport, + blockStore, + mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -198,6 +208,8 @@ void if_stop_called_with_just_one_federation_rsk_blockchain_is_still_listened() BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, federatorSupport, + blockStore, + mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -230,6 +242,8 @@ void if_stop_called_with_federations_rsk_blockchain_is_not_listened() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, federatorSupport, + blockStore, + mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -292,6 +306,8 @@ void processReleases_ok() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), federatorSupport, + blockStore, + mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -389,7 +405,6 @@ void having_two_pegouts_signs_only_one() throws Exception { mock(ReceiptStore.class) ); - BlockStore blockStore = mock(BlockStore.class); ReceiptStore receiptStore = mock(ReceiptStore.class); Keccak256 blockHash1 = createHash(2); @@ -422,6 +437,7 @@ void having_two_pegouts_signs_only_one() throws Exception { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, + blockStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -439,7 +455,7 @@ void having_two_pegouts_signs_only_one() throws Exception { btcReleaseClient.start(federation); // Act - ethereumListener.get().onBestBlock(null, Collections.emptyList()); + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); // Assert verify(federatorSupport, times(1)).addSignature( @@ -488,7 +504,6 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex mock(ReceiptStore.class) ); - BlockStore blockStore = mock(BlockStore.class); ReceiptStore receiptStore = mock(ReceiptStore.class); Keccak256 blockHash = createHash(2); @@ -512,6 +527,7 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, + blockStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -535,7 +551,7 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex () -> btcReleaseClient.validateTxIsNotCached(pegoutCreationRskTxHash)); // Start first round of execution - ethereumListener.get().onBestBlock(null, Collections.emptyList()); + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); // After the first round of execution, we should throw an exception // since we have signed the pegout and sent it to the bridge @@ -543,7 +559,7 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex () -> btcReleaseClient.validateTxIsNotCached(pegoutCreationRskTxHash)); // Execute second round of execution - ethereumListener.get().onBestBlock(null, Collections.emptyList()); + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); // Verify we only send the add_signature tx to the bridge once // throughout both rounds of execution @@ -593,7 +609,6 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx mock(ReceiptStore.class) ); - BlockStore blockStore = mock(BlockStore.class); ReceiptStore receiptStore = mock(ReceiptStore.class); Keccak256 blockHash = createHash(2); @@ -617,6 +632,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, + blockStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -641,7 +657,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx btcReleaseClient.start(federation); // Start first round of execution - ethereumListener.get().onBestBlock(null, Collections.emptyList()); + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); // Ensure the pegout tx becomes invalid by advancing the clock 1 hour field = pegoutSignedCache.getClass().getDeclaredField("clock"); @@ -654,7 +670,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx () -> btcReleaseClient.validateTxIsNotCached(pegoutCreationRskTxHash)); // Execute second round of execution - ethereumListener.get().onBestBlock(null, Collections.emptyList()); + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); // Verify we send the add_signature tx to the bridge twice // throughout both rounds of execution @@ -664,6 +680,195 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx ); } + @Test + void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSignature() throws Exception { + // Arrange + Federation federation = TestUtils.createFederation(params, 9); + BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, federation); + Keccak256 svpSpendCreationRskTxHash = createHash(0); + Map.Entry entry = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); + StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(entry); + + Ethereum ethereum = mock(Ethereum.class); + AtomicReference ethereumListener = new AtomicReference<>(); + doAnswer((InvocationOnMock invocation) -> { + ethereumListener.set((EthereumListener) invocation.getArguments()[0]); + return null; + }).when(ethereum).addListener(any(EthereumListener.class)); + + FederatorSupport federatorSupport = mock(FederatorSupport.class); + // return svp spend tx waiting for signatures + doReturn(Optional.of(stateForProposedFederator)).when(federatorSupport).getStateForProposedFederator(); + // returns zero pegouts waiting for signatures + doReturn(mock(StateForFederator.class)).when(federatorSupport).getStateForFederator(); + + ECKey ecKey = new ECKey(); + BtcECKey fedKey = new BtcECKey(); + ECPublicKey signerPublicKey = new ECPublicKey(fedKey.getPubKey()); + + ECDSASigner signer = mock(ECDSASigner.class); + doReturn(signerPublicKey).when(signer).getPublicKey(BTC.getKeyId()); + doReturn(1).when(signer).getVersionForKeyId(ArgumentMatchers.any(KeyId.class)); + doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); + + PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); + doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); + when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) + .thenReturn(PEGOUT_SIGNED_CACHE_TTL); + + SignerMessageBuilderFactory signerMessageBuilderFactory = new SignerMessageBuilderFactory( + mock(ReceiptStore.class) + ); + + ReceiptStore receiptStore = mock(ReceiptStore.class); + + Keccak256 blockHash = createHash(2); + Block block = mock(Block.class); + TransactionReceipt txReceipt = mock(TransactionReceipt.class); + TransactionInfo txInfo = mock(TransactionInfo.class); + when(block.getHash()).thenReturn(blockHash); + when(blockStore.getBlockByHash(blockHash.getBytes())).thenReturn(block); + when(txInfo.getReceipt()).thenReturn(txReceipt); + when(txInfo.getBlockHash()).thenReturn(blockHash.getBytes()); + when(receiptStore.getInMainChain(svpSpendCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(txInfo)); + + ReleaseCreationInformationGetter releaseCreationInformationGetter = + new ReleaseCreationInformationGetter( + receiptStore, blockStore + ); + + BtcReleaseClientStorageSynchronizer storageSynchronizer = + mock(BtcReleaseClientStorageSynchronizer.class); + when(storageSynchronizer.isSynced()).thenReturn(true); + + BtcReleaseClient btcReleaseClient = new BtcReleaseClient( + ethereum, + blockStore, + federatorSupport, + powpegNodeSystemProperties, + mock(NodeBlockProcessor.class) + ); + + btcReleaseClient.setup( + signer, + mock(ActivationConfig.class), + signerMessageBuilderFactory, + releaseCreationInformationGetter, + mock(ReleaseRequirementsEnforcer.class), + mock(BtcReleaseClientStorageAccessor.class), + storageSynchronizer + ); + + btcReleaseClient.start(federation); + + // Act + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); + + // Assert + verify(federatorSupport).addSignature( + anyList(), + any(byte[].class) + ); + } + + @Test + void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws Exception { + // Arrange + Federation federation = TestUtils.createFederation(params, 9); + BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); + Keccak256 pegoutCreationRskTxHash = createHash(0); + SortedMap rskTxsWaitingForSignatures = new TreeMap<>(); + rskTxsWaitingForSignatures.put(pegoutCreationRskTxHash, pegout); + StateForFederator stateForFederator = new StateForFederator(rskTxsWaitingForSignatures); + + Ethereum ethereum = mock(Ethereum.class); + AtomicReference ethereumListener = new AtomicReference<>(); + doAnswer((InvocationOnMock invocation) -> { + ethereumListener.set((EthereumListener) invocation.getArguments()[0]); + return null; + }).when(ethereum).addListener(any(EthereumListener.class)); + + FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); + + ECKey ecKey = new ECKey(); + BtcECKey fedKey = new BtcECKey(); + ECPublicKey signerPublicKey = new ECPublicKey(fedKey.getPubKey()); + + ECDSASigner signer = mock(ECDSASigner.class); + doReturn(signerPublicKey).when(signer).getPublicKey(BTC.getKeyId()); + doReturn(1).when(signer).getVersionForKeyId(ArgumentMatchers.any(KeyId.class)); + doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); + + PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); + doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); + when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) + .thenReturn(PEGOUT_SIGNED_CACHE_TTL); + + SignerMessageBuilderFactory signerMessageBuilderFactory = new SignerMessageBuilderFactory( + mock(ReceiptStore.class) + ); + + ReceiptStore receiptStore = mock(ReceiptStore.class); + + Keccak256 blockHash = createHash(2); + Block block = mock(Block.class); + TransactionReceipt txReceipt = mock(TransactionReceipt.class); + TransactionInfo txInfo = mock(TransactionInfo.class); + when(block.getHash()).thenReturn(blockHash); + when(blockStore.getBlockByHash(blockHash.getBytes())).thenReturn(block); + when(txInfo.getReceipt()).thenReturn(txReceipt); + when(txInfo.getBlockHash()).thenReturn(blockHash.getBytes()); + when(receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(txInfo)); + + ReleaseCreationInformationGetter releaseCreationInformationGetter = + new ReleaseCreationInformationGetter( + receiptStore, blockStore + ); + + BtcReleaseClientStorageSynchronizer storageSynchronizer = + mock(BtcReleaseClientStorageSynchronizer.class); + when(storageSynchronizer.isSynced()).thenReturn(true); + + BtcReleaseClient btcReleaseClient = new BtcReleaseClient( + ethereum, + blockStore, + federatorSupport, + powpegNodeSystemProperties, + mock(NodeBlockProcessor.class) + ); + + btcReleaseClient.setup( + signer, + mock(ActivationConfig.class), + signerMessageBuilderFactory, + releaseCreationInformationGetter, + mock(ReleaseRequirementsEnforcer.class), + mock(BtcReleaseClientStorageAccessor.class), + storageSynchronizer + ); + + btcReleaseClient.start(federation); + + // Since the current best block will also be the block that + // contains the pegout tx waiting for signatures hash then + // the confirmation difference will never be enough for the + // tx to be considered ready to be signed + when(blockStore.getBlockByHash(any())).thenReturn(bestBlock); + + // Act + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); + + // Assert + verify(federatorSupport, never()).addSignature( + anyList(), + any(byte[].class) + ); + } + + @Test void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException { // Arrange @@ -692,6 +897,7 @@ void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, + blockStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -708,7 +914,7 @@ void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException btcReleaseClient.start(federation); // Act - ethereumListener.get().onBestBlock(null, null); + ethereumListener.get().onBestBlock(bestBlock, null); // Assert verify(federatorSupport, never()).getStateForFederator(); @@ -742,6 +948,7 @@ void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientExcepti BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, + blockStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -758,7 +965,7 @@ void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientExcepti btcReleaseClient.start(federation); // Act - ethereumListener.get().onBestBlock(null, null); + ethereumListener.get().onBestBlock(bestBlock, null); // Assert verify(federatorSupport, never()).getStateForFederator(); @@ -792,6 +999,7 @@ void onBlock_return_when_node_is_syncing() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, + blockStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -838,6 +1046,7 @@ void onBlock_return_when_pegout_is_disabled() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, + blockStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -957,6 +1166,8 @@ void validateTxCanBeSigned_federatorAlreadySigned() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), federatorSupport, + blockStore, + mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -999,6 +1210,7 @@ void validateTxCanBeSigned_federationCantSign() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), + blockStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1057,6 +1269,7 @@ void removeSignaturesFromTransaction() { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), + blockStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1145,6 +1358,8 @@ private void test_validateTxCanBeSigned( BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), federatorSupport, + blockStore, + mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -1176,6 +1391,7 @@ private void test_extractStandardRedeemScript( BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), + blockStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1311,6 +1527,7 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereumImpl, + blockStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1336,7 +1553,7 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) btcReleaseClient.start(federation); // Release "confirmed" - ethereumImpl.addBestBlockWithReceipts(mock(Block.class), new ArrayList<>()); + ethereumImpl.addBestBlockWithReceipts(bestBlock, new ArrayList<>()); // Verify the rsk tx hash was updated verify(releaseCreationInformationGetter, times(1)).getTxInfoToSign( @@ -1359,6 +1576,7 @@ private BtcReleaseClient createBtcClient() { return new BtcReleaseClient( mock(Ethereum.class), + blockStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) From 2ffa484b6a569395cc910c054d170f2eb0d41790 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:05:38 +0000 Subject: [PATCH 031/112] refactor(btcreleaseclient): reword svp spend comment --- .../java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index 1bbcf4031..4203edb44 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -238,7 +238,7 @@ public void onBestBlock(org.ethereum.core.Block block, List } storageSynchronizer.processBlock(block, receipts); - // Check if svp spend tx waiting for signatures is available to be signed + // Sign svp spend tx waiting for signatures, if it exists, // before attempting to sign any pegouts. federatorSupport.getStateForProposedFederator() .map(StateForProposedFederator::getSvpSpendTxWaitingForSignatures) From d99362b4d461f06883adfc0dba22ebccb30568f6 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:10:24 +0000 Subject: [PATCH 032/112] refactor(btcreleaseclient): propper code formatting --- .../co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index 4203edb44..ec5356f28 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -314,14 +314,14 @@ private boolean isReadyToSign(long currentBlockNumber, Keccak256 txHashWaitingFo } private BtcTransaction convertToBtcTxFromRLPData(byte[] dataFromBtcReleaseTopic) { - RLPList dataElements = (RLPList)RLP.decode2(dataFromBtcReleaseTopic).get(0); + RLPList dataElements = (RLPList) RLP.decode2(dataFromBtcReleaseTopic).get(0); return new BtcTransaction(bridgeConstants.getBtcParams(), dataElements.get(1).getRLPData()); } private BtcTransaction convertToBtcTxFromSolidityData(byte[] dataFromBtcReleaseTopic) { return new BtcTransaction(bridgeConstants.getBtcParams(), - (byte[])BridgeEvents.RELEASE_BTC.getEvent().decodeEventData(dataFromBtcReleaseTopic)[0]); + (byte[]) BridgeEvents.RELEASE_BTC.getEvent().decodeEventData(dataFromBtcReleaseTopic)[0]); } } From 6c78faafb8aa656e6188463960846d3f86afdd19 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:06:02 +0000 Subject: [PATCH 033/112] feat(btcreleaseclient): add receipt store logic to find the block hash for each rsk tx waiting for signatures --- .../co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index ec5356f28..a9f4acc8b 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -67,6 +67,8 @@ import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; import org.ethereum.db.BlockStore; +import org.ethereum.db.ReceiptStore; +import org.ethereum.db.TransactionInfo; import org.ethereum.facade.Ethereum; import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.util.RLP; @@ -99,6 +101,7 @@ public class BtcReleaseClient { private final Ethereum ethereum; private final BlockStore blockStore; + private final ReceiptStore receiptStore; private final FederatorSupport federatorSupport; private final Set observedFederations; private final NodeBlockProcessor nodeBlockProcessor; @@ -119,12 +122,14 @@ public class BtcReleaseClient { public BtcReleaseClient( Ethereum ethereum, BlockStore blockStore, + ReceiptStore receiptStore, FederatorSupport federatorSupport, PowpegNodeSystemProperties systemProperties, NodeBlockProcessor nodeBlockProcessor ) { this.ethereum = ethereum; this.blockStore = blockStore; + this.receiptStore = receiptStore; this.federatorSupport = federatorSupport; this.observedFederations = new HashSet<>(); this.blockListener = new BtcReleaseEthereumListener(); @@ -299,6 +304,8 @@ public void onBlock(org.ethereum.core.Block block, List rece private boolean isReadyToSign(long currentBlockNumber, Keccak256 txHashWaitingForSignatures) { boolean isReadyToSign = Optional.ofNullable(txHashWaitingForSignatures) .map(Keccak256::getBytes) + .flatMap(txHash -> receiptStore.getInMainChain(txHash, blockStore)) + .map(TransactionInfo::getBlockHash) .map(blockStore::getBlockByHash) .map(Block::getNumber) .map(blockNumberWithTxWaitingForSignatures -> currentBlockNumber - blockNumberWithTxWaitingForSignatures) From 78caae8fcb568ff2f8399e20e7f593fd8d3e0ed1 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:06:28 +0000 Subject: [PATCH 034/112] feat(federate): add receipt store dependency to BtcReleaseClient class --- src/main/java/co/rsk/federate/FedNodeContext.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/co/rsk/federate/FedNodeContext.java b/src/main/java/co/rsk/federate/FedNodeContext.java index 2335cf758..ead5bdc96 100644 --- a/src/main/java/co/rsk/federate/FedNodeContext.java +++ b/src/main/java/co/rsk/federate/FedNodeContext.java @@ -60,6 +60,7 @@ public NodeRunner buildNodeRunner() { new BtcReleaseClient( getRsk(), getBlockStore(), + getReceiptStore(), getFederatorSupport(), getPowpegNodeSystemProperties(), getNodeBlockProcessor() From 9e3098d9c2076a61fa82a96d31d5953de96b0dfb Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:07:02 +0000 Subject: [PATCH 035/112] feat(btcreleaseclient): add confirmation diff logic using the receipt store for unit tests --- .../BtcReleaseClientTest.java | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index fcf91285d..5f11fa4e2 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -62,7 +62,6 @@ import co.rsk.peg.federation.*; import co.rsk.peg.StateForFederator; import co.rsk.peg.StateForProposedFederator; - import java.lang.reflect.Field; import java.math.BigInteger; import java.time.Clock; @@ -102,6 +101,7 @@ class BtcReleaseClientTest { private static final Duration PEGOUT_SIGNED_CACHE_TTL = Duration.ofMinutes(30); private final BlockStore blockStore = mock(BlockStore.class); + private final ReceiptStore receiptStore = mock(ReceiptStore.class); private final Block bestBlock = mock(Block.class); private final NetworkParameters params = RegTestParams.get(); private final BridgeConstants bridgeConstants = Constants.regtest().bridgeConstants; @@ -115,9 +115,21 @@ class BtcReleaseClientTest { @BeforeEach void setup() { // ensure confirmation difference always passes + Keccak256 rskTxHash = createHash(1); + Keccak256 blockHash = createHash(2); + TransactionInfo transactionInfoForTxWaitingForSignatures = mock(TransactionInfo.class); Block blockWithTxWaitingForSignatures = mock(Block.class); - when(blockWithTxWaitingForSignatures.getNumber()).thenReturn(0L); - when(blockStore.getBlockByHash(any())).thenReturn(blockWithTxWaitingForSignatures); + when(transactionInfoForTxWaitingForSignatures.getBlockHash()) + .thenReturn(blockHash.getBytes()); + when(blockWithTxWaitingForSignatures.getHash()) + .thenReturn(blockHash); + when(blockWithTxWaitingForSignatures.getNumber()) + .thenReturn(0L); + + when(receiptStore.getInMainChain(rskTxHash.getBytes(), blockStore)) + .thenReturn(Optional.of(transactionInfoForTxWaitingForSignatures)); + when(blockStore.getBlockByHash(blockHash.getBytes())) + .thenReturn(blockWithTxWaitingForSignatures); when(bestBlock.getNumber()).thenReturn(5_000L); } @@ -137,6 +149,8 @@ void start_whenFederationMemberNotPartOfDesiredFederation_shouldThrowException() BtcReleaseClient btcReleaseClient = new BtcReleaseClient( mock(Ethereum.class), + blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -157,6 +171,7 @@ void if_start_not_called_rsk_blockchain_not_listened() { new BtcReleaseClient( ethereum, blockStore, + receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -176,9 +191,9 @@ void when_start_called_rsk_blockchain_is_listened() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - federatorSupport, blockStore, - mock(FederatorSupport.class), + receiptStore, + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -207,9 +222,9 @@ void if_stop_called_with_just_one_federation_rsk_blockchain_is_still_listened() BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - federatorSupport, blockStore, - mock(FederatorSupport.class), + receiptStore, + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -241,9 +256,9 @@ void if_stop_called_with_federations_rsk_blockchain_is_not_listened() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - federatorSupport, blockStore, - mock(FederatorSupport.class), + receiptStore, + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -305,9 +320,9 @@ void processReleases_ok() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - federatorSupport, blockStore, - mock(FederatorSupport.class), + receiptStore, + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -438,6 +453,7 @@ void having_two_pegouts_signs_only_one() throws Exception { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -528,6 +544,7 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -633,6 +650,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -684,6 +702,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSignature() throws Exception { // Arrange Federation federation = TestUtils.createFederation(params, 9); + FederationMember federationMember = federation.getMembers().get(0); BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, federation); Keccak256 svpSpendCreationRskTxHash = createHash(0); Map.Entry entry = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); @@ -697,6 +716,7 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign }).when(ethereum).addListener(any(EthereumListener.class)); FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); // return svp spend tx waiting for signatures doReturn(Optional.of(stateForProposedFederator)).when(federatorSupport).getStateForProposedFederator(); // returns zero pegouts waiting for signatures @@ -745,6 +765,7 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -776,6 +797,7 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws Exception { // Arrange Federation federation = TestUtils.createFederation(params, 9); + FederationMember federationMember = federation.getMembers().get(0); BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); Keccak256 pegoutCreationRskTxHash = createHash(0); SortedMap rskTxsWaitingForSignatures = new TreeMap<>(); @@ -790,6 +812,7 @@ void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws }).when(ethereum).addListener(any(EthereumListener.class)); FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); ECKey ecKey = new ECKey(); @@ -835,6 +858,7 @@ void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -898,6 +922,7 @@ void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -949,6 +974,7 @@ void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientExcepti BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1000,6 +1026,7 @@ void onBlock_return_when_node_is_syncing() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1047,6 +1074,7 @@ void onBlock_return_when_pegout_is_disabled() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1165,9 +1193,9 @@ void validateTxCanBeSigned_federatorAlreadySigned() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - federatorSupport, blockStore, - mock(FederatorSupport.class), + receiptStore, + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -1211,6 +1239,7 @@ void validateTxCanBeSigned_federationCantSign() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), blockStore, + receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1270,6 +1299,7 @@ void removeSignaturesFromTransaction() { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), blockStore, + receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1357,9 +1387,9 @@ private void test_validateTxCanBeSigned( BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - federatorSupport, blockStore, - mock(FederatorSupport.class), + receiptStore, + federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) ); @@ -1392,6 +1422,7 @@ private void test_extractStandardRedeemScript( BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), blockStore, + receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1528,6 +1559,7 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereumImpl, blockStore, + receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1577,6 +1609,7 @@ private BtcReleaseClient createBtcClient() { return new BtcReleaseClient( mock(Ethereum.class), blockStore, + receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) From 8c3c5376cfe520bf43ec750bc8306edb23dcf65c Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:36:26 +0000 Subject: [PATCH 036/112] fix(btcreleaseclient): avoid shadowing receipt store instance variable --- .../rsk/federate/btcreleaseclient/BtcReleaseClientTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 5f11fa4e2..d91f2d609 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -741,8 +741,6 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign mock(ReceiptStore.class) ); - ReceiptStore receiptStore = mock(ReceiptStore.class); - Keccak256 blockHash = createHash(2); Block block = mock(Block.class); TransactionReceipt txReceipt = mock(TransactionReceipt.class); @@ -834,8 +832,6 @@ void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws mock(ReceiptStore.class) ); - ReceiptStore receiptStore = mock(ReceiptStore.class); - Keccak256 blockHash = createHash(2); Block block = mock(Block.class); TransactionReceipt txReceipt = mock(TransactionReceipt.class); From ad7f04adc78dcd5421f63da0c9522312e18a202d Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:05:21 +0000 Subject: [PATCH 037/112] feat(btcreleaseclient): include is ready to sign method only for the svp spend tx --- .../btcreleaseclient/BtcReleaseClient.java | 23 ++++++++----------- .../BtcReleaseClientTest.java | 20 ++++++++-------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index a9f4acc8b..112848688 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -247,19 +247,14 @@ public void onBestBlock(org.ethereum.core.Block block, List // before attempting to sign any pegouts. federatorSupport.getStateForProposedFederator() .map(StateForProposedFederator::getSvpSpendTxWaitingForSignatures) - .filter(svpSpendTxWaitingForSignatures -> isReadyToSign(block.getNumber(), svpSpendTxWaitingForSignatures.getKey())) + .filter(svpSpendTxWaitingForSignatures -> isSVPSpendTxReadyToSign(block.getNumber(), svpSpendTxWaitingForSignatures.getKey())) .ifPresent(svpSpendTxReadyToBeSigned -> processReleases(Set.of(svpSpendTxReadyToBeSigned))); // Processing transactions waiting for signatures on best block only still "works", // since it all lies within RSK's blockchain and normal rules apply. I.e., this // process works on a block-by-block basis. StateForFederator stateForFederator = federatorSupport.getStateForFederator(); - Set> rskTxsReadyToBeSigned = stateForFederator.getRskTxsWaitingForSignatures() - .entrySet() - .stream() - .filter(rskTxWaitingForSignatures -> isReadyToSign(block.getNumber(), rskTxWaitingForSignatures.getKey())) - .collect(Collectors.toUnmodifiableSet()); - processReleases(rskTxsReadyToBeSigned); + processReleases(stateForFederator.getRskTxsWaitingForSignatures().entrySet()); } @Override @@ -287,7 +282,7 @@ public void onBlock(org.ethereum.core.Block block, List rece } /** - * Determines if a transaction hash is ready to be signed based on its block confirmations. + * Determines if the svp spend transaction hash is ready to be signed based on its block confirmations. * *

* This method retrieves the block associated with the given transaction hash and calculates @@ -297,23 +292,23 @@ public void onBlock(org.ethereum.core.Block block, List rece *

* * @param currentBlockNumber the current block number in the blockchain - * @param txHashWaitingForSignatures the Keccak256 hash of the transaction waiting to be signed + * @param svpTxHash the Keccak256 hash of the svp spend transaction waiting to be signed * @return {@code true} if the transaction has the required number of confirmations and is ready to be signed; * {@code false} otherwise */ - private boolean isReadyToSign(long currentBlockNumber, Keccak256 txHashWaitingForSignatures) { - boolean isReadyToSign = Optional.ofNullable(txHashWaitingForSignatures) + private boolean isSVPSpendTxReadyToSign(long currentBlockNumber, Keccak256 svpTxHash) { + boolean isReadyToSign = Optional.ofNullable(svpTxHash) .map(Keccak256::getBytes) .flatMap(txHash -> receiptStore.getInMainChain(txHash, blockStore)) .map(TransactionInfo::getBlockHash) .map(blockStore::getBlockByHash) .map(Block::getNumber) - .map(blockNumberWithTxWaitingForSignatures -> currentBlockNumber - blockNumberWithTxWaitingForSignatures) + .map(blockNumberWithSvpSpendTx -> currentBlockNumber - blockNumberWithSvpSpendTx) .filter(confirmationDifference -> confirmationDifference >= bridgeConstants.getRsk2BtcMinimumAcceptableConfirmations()) .isPresent(); - logger.info("[isReadyToSign] Readiness check for signing: Tx hash [{}], Current block [{}], Ready to sign? [{}]", - txHashWaitingForSignatures, + logger.info("[isReadyToSign] SVP spend tx readiness check for signing: tx hash [{}], Current block [{}], Ready to sign? [{}]", + svpTxHash, currentBlockNumber, isReadyToSign ? "YES" : "NO"); diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index d91f2d609..62fb3519f 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -792,15 +792,14 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign } @Test - void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws Exception { + void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() throws Exception { // Arrange Federation federation = TestUtils.createFederation(params, 9); FederationMember federationMember = federation.getMembers().get(0); - BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); - Keccak256 pegoutCreationRskTxHash = createHash(0); - SortedMap rskTxsWaitingForSignatures = new TreeMap<>(); - rskTxsWaitingForSignatures.put(pegoutCreationRskTxHash, pegout); - StateForFederator stateForFederator = new StateForFederator(rskTxsWaitingForSignatures); + BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, federation); + Keccak256 svpSpendCreationRskTxHash = createHash(0); + Map.Entry entry = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); + StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(entry); Ethereum ethereum = mock(Ethereum.class); AtomicReference ethereumListener = new AtomicReference<>(); @@ -811,7 +810,10 @@ void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws FederatorSupport federatorSupport = mock(FederatorSupport.class); doReturn(federationMember).when(federatorSupport).getFederationMember(); - doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); + // return svp spend tx waiting for signatures + doReturn(Optional.of(stateForProposedFederator)).when(federatorSupport).getStateForProposedFederator(); + // returns zero pegouts waiting for signatures + doReturn(mock(StateForFederator.class)).when(federatorSupport).getStateForFederator(); ECKey ecKey = new ECKey(); BtcECKey fedKey = new BtcECKey(); @@ -840,7 +842,7 @@ void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws when(blockStore.getBlockByHash(blockHash.getBytes())).thenReturn(block); when(txInfo.getReceipt()).thenReturn(txReceipt); when(txInfo.getBlockHash()).thenReturn(blockHash.getBytes()); - when(receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(txInfo)); + when(receiptStore.getInMainChain(svpSpendCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(txInfo)); ReleaseCreationInformationGetter releaseCreationInformationGetter = new ReleaseCreationInformationGetter( @@ -873,7 +875,7 @@ void onBestBlock_whenPegoutTxIsNotReadyToBeSigned_shouldNotAddSignature() throws btcReleaseClient.start(federation); // Since the current best block will also be the block that - // contains the pegout tx waiting for signatures hash then + // contains the svp spend tx waiting for signatures hash then // the confirmation difference will never be enough for the // tx to be considered ready to be signed when(blockStore.getBlockByHash(any())).thenReturn(bestBlock); From cb3afbbef2bc99cabe8602f34ac5566dd72d1a3f Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:52:15 +0100 Subject: [PATCH 038/112] refactor(btcreleaseclient): update log statement --- .../java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index 112848688..ba74941e3 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -307,7 +307,7 @@ private boolean isSVPSpendTxReadyToSign(long currentBlockNumber, Keccak256 svpTx .filter(confirmationDifference -> confirmationDifference >= bridgeConstants.getRsk2BtcMinimumAcceptableConfirmations()) .isPresent(); - logger.info("[isReadyToSign] SVP spend tx readiness check for signing: tx hash [{}], Current block [{}], Ready to sign? [{}]", + logger.info("[isSvpSpendTxReadyToSign] SVP spend tx readiness check for signing: tx hash [{}], Current block [{}], Ready to sign? [{}]", svpTxHash, currentBlockNumber, isReadyToSign ? "YES" : "NO"); From ce1c42f072ee91d725f814933e16d4f1152fed60 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:42:55 +0000 Subject: [PATCH 039/112] feat(btcreleaseclient): use mainnet params for unit tests --- .../rsk/federate/btcreleaseclient/BtcReleaseClientTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 62fb3519f..b0f0deacb 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -28,6 +28,7 @@ import co.rsk.bitcoinj.core.Sha256Hash; import co.rsk.bitcoinj.core.TransactionInput; import co.rsk.bitcoinj.crypto.TransactionSignature; +import co.rsk.bitcoinj.params.MainNetParams; import co.rsk.bitcoinj.params.RegTestParams; import co.rsk.bitcoinj.script.Script; import co.rsk.bitcoinj.script.ScriptBuilder; @@ -103,8 +104,8 @@ class BtcReleaseClientTest { private final BlockStore blockStore = mock(BlockStore.class); private final ReceiptStore receiptStore = mock(ReceiptStore.class); private final Block bestBlock = mock(Block.class); - private final NetworkParameters params = RegTestParams.get(); - private final BridgeConstants bridgeConstants = Constants.regtest().bridgeConstants; + private final NetworkParameters params = MainNetParams.get(); + private final BridgeConstants bridgeConstants = Constants.mainnet().bridgeConstants; private static final List erpFedKeys = Arrays.stream(new String[]{ "03b9fc46657cf72a1afa007ecf431de1cd27ff5cc8829fa625b66ca47b967e6b24", From ad74ca0ab272c8d4c7b6423dd13d4cb7b80143eb Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:47:51 +0000 Subject: [PATCH 040/112] feat(btcreleaseclient): add better naming for unit tests vars --- .../BtcReleaseClientTest.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index b0f0deacb..02a8458d9 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -138,7 +138,7 @@ void setup() { void start_whenFederationMemberNotPartOfDesiredFederation_shouldThrowException() { // Arrange PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -165,7 +165,7 @@ void start_whenFederationMemberNotPartOfDesiredFederation_shouldThrowException() void if_start_not_called_rsk_blockchain_not_listened() { Ethereum ethereum = mock(Ethereum.class); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - Mockito.doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + Mockito.doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -186,7 +186,7 @@ void when_start_called_rsk_blockchain_is_listened() { Ethereum ethereum = mock(Ethereum.class); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); FederatorSupport federatorSupport = mock(FederatorSupport.class); - Mockito.doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + Mockito.doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -217,7 +217,7 @@ void if_stop_called_with_just_one_federation_rsk_blockchain_is_still_listened() Ethereum ethereum = mock(Ethereum.class); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); FederatorSupport federatorSupport = mock(FederatorSupport.class); - Mockito.doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + Mockito.doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -251,7 +251,7 @@ void if_stop_called_with_federations_rsk_blockchain_is_not_listened() { Ethereum ethereum = mock(Ethereum.class); FederatorSupport federatorSupport = mock(FederatorSupport.class); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - Mockito.doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + Mockito.doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -306,7 +306,7 @@ void processReleases_ok() throws Exception { when(signer.sign(eq(BTC.getKeyId()), ArgumentMatchers.any())).thenReturn(ethSig); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -412,7 +412,7 @@ void having_two_pegouts_signs_only_one() throws Exception { doReturn(ethSig).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -512,7 +512,7 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -618,7 +618,7 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -702,12 +702,12 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx @Test void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSignature() throws Exception { // Arrange - Federation federation = TestUtils.createFederation(params, 9); - FederationMember federationMember = federation.getMembers().get(0); - BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, federation); + Federation proposedFederation = TestUtils.createFederation(params, 9); + FederationMember federationMember = proposedFederation.getMembers().get(0); + BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, proposedFederation); Keccak256 svpSpendCreationRskTxHash = createHash(0); - Map.Entry entry = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); - StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(entry); + Map.Entry svpSpendTxWFS = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); + StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(svpSpendTxWFS); Ethereum ethereum = mock(Ethereum.class); AtomicReference ethereumListener = new AtomicReference<>(); @@ -733,7 +733,7 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -780,7 +780,7 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign storageSynchronizer ); - btcReleaseClient.start(federation); + btcReleaseClient.start(proposedFederation); // Act ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); @@ -795,12 +795,12 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign @Test void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() throws Exception { // Arrange - Federation federation = TestUtils.createFederation(params, 9); - FederationMember federationMember = federation.getMembers().get(0); - BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, federation); + Federation proposedFederation = TestUtils.createFederation(params, 9); + FederationMember federationMember = proposedFederation.getMembers().get(0); + BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, proposedFederation); Keccak256 svpSpendCreationRskTxHash = createHash(0); - Map.Entry entry = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); - StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(entry); + Map.Entry svpSpendTxWFS = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); + StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(svpSpendTxWFS); Ethereum ethereum = mock(Ethereum.class); AtomicReference ethereumListener = new AtomicReference<>(); @@ -826,7 +826,7 @@ void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() thro doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -873,7 +873,7 @@ void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() thro storageSynchronizer ); - btcReleaseClient.start(federation); + btcReleaseClient.start(proposedFederation); // Since the current best block will also be the block that // contains the svp spend tx waiting for signatures hash then @@ -910,7 +910,7 @@ void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -962,7 +962,7 @@ void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientExcepti doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(false).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1014,7 +1014,7 @@ void onBlock_return_when_node_is_syncing() { doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1062,7 +1062,7 @@ void onBlock_return_when_pegout_is_disabled() { doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - doReturn(Constants.regtest()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); doReturn(false).when(powpegNodeSystemProperties).isPegoutEnabled(); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1179,7 +1179,7 @@ void validateTxCanBeSigned_federatorAlreadySigned() throws Exception { releaseInput.setScriptSig(inputScript); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1226,7 +1226,7 @@ void validateTxCanBeSigned_federationCantSign() throws Exception { releaseTx.addInput(releaseInput); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1291,7 +1291,7 @@ void removeSignaturesFromTransaction() { releaseInput.setScriptSig(inputScript); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1377,7 +1377,7 @@ private void test_validateTxCanBeSigned( doReturn(federationMember).when(federatorSupport).getFederationMember(); PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1414,7 +1414,7 @@ private void test_extractStandardRedeemScript( Script redeemScriptToExtract) { PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1489,7 +1489,7 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) HSMReleaseCreationInformationException, ReleaseRequirementsEnforcerException, HSMUnsupportedVersionException, SignerMessageBuilderException { PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.isPegoutEnabled()).thenReturn(true); when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); @@ -1600,7 +1600,7 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) private BtcReleaseClient createBtcClient() { PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); - when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.regtest()); + when(powpegNodeSystemProperties.getNetworkConstants()).thenReturn(Constants.mainnet()); when(powpegNodeSystemProperties.isPegoutEnabled()).thenReturn(true); // Enabled by default when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) .thenReturn(PEGOUT_SIGNED_CACHE_TTL); From 2c84ecb0ba19c485ea941aa45a62310d477c5e10 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:38:44 +0000 Subject: [PATCH 041/112] feat(btcreleaseclient): add both svp spend tx and pegout unit test and simplify svp spend ready to sign testing logic --- .../BtcReleaseClientTest.java | 159 +++++++++++++++--- 1 file changed, 134 insertions(+), 25 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 02a8458d9..904ec323d 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -29,7 +29,6 @@ import co.rsk.bitcoinj.core.TransactionInput; import co.rsk.bitcoinj.crypto.TransactionSignature; import co.rsk.bitcoinj.params.MainNetParams; -import co.rsk.bitcoinj.params.RegTestParams; import co.rsk.bitcoinj.script.Script; import co.rsk.bitcoinj.script.ScriptBuilder; import co.rsk.bitcoinj.script.ScriptChunk; @@ -80,6 +79,8 @@ import java.util.TreeMap; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.core.Block; @@ -115,23 +116,11 @@ class BtcReleaseClientTest { @BeforeEach void setup() { - // ensure confirmation difference always passes - Keccak256 rskTxHash = createHash(1); - Keccak256 blockHash = createHash(2); - TransactionInfo transactionInfoForTxWaitingForSignatures = mock(TransactionInfo.class); - Block blockWithTxWaitingForSignatures = mock(Block.class); - when(transactionInfoForTxWaitingForSignatures.getBlockHash()) - .thenReturn(blockHash.getBytes()); - when(blockWithTxWaitingForSignatures.getHash()) - .thenReturn(blockHash); - when(blockWithTxWaitingForSignatures.getNumber()) - .thenReturn(0L); - - when(receiptStore.getInMainChain(rskTxHash.getBytes(), blockStore)) - .thenReturn(Optional.of(transactionInfoForTxWaitingForSignatures)); - when(blockStore.getBlockByHash(blockHash.getBytes())) - .thenReturn(blockWithTxWaitingForSignatures); + Keccak256 blockHash = createHash(123); + when(bestBlock.getHash()).thenReturn(blockHash); when(bestBlock.getNumber()).thenReturn(5_000L); + when(blockStore.getBlockByHash(blockHash.getBytes())) + .thenReturn(bestBlock); } @Test @@ -743,10 +732,12 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign ); Keccak256 blockHash = createHash(2); + Long blockNumber = 0L; Block block = mock(Block.class); TransactionReceipt txReceipt = mock(TransactionReceipt.class); TransactionInfo txInfo = mock(TransactionInfo.class); when(block.getHash()).thenReturn(blockHash); + when(block.getNumber()).thenReturn(blockNumber); when(blockStore.getBlockByHash(blockHash.getBytes())).thenReturn(block); when(txInfo.getReceipt()).thenReturn(txReceipt); when(txInfo.getBlockHash()).thenReturn(blockHash.getBytes()); @@ -792,6 +783,127 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign ); } + @Test + void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSignatureForBoth() throws Exception { + // Arrange + List keys = Stream.generate(BtcECKey::new).limit(9).toList(); + BtcECKey federationKey = keys.get(0); + FederationMember federationMember = FederationMember.getFederationMembersFromKeys(keys).get(0); + Federation federation = TestUtils.createFederation(params, keys); + BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); + Keccak256 pegoutCreationRskTxHash = createHash(0); + SortedMap rskTxsWaitingForSignatures = new TreeMap<>(); + rskTxsWaitingForSignatures.put(pegoutCreationRskTxHash, pegout); + StateForFederator stateForFederator = new StateForFederator(rskTxsWaitingForSignatures); + + List proposedKeys = Stream.generate(BtcECKey::new).limit(8).collect(Collectors.toList()); + proposedKeys.add(federationKey); + Federation proposedFederation = TestUtils.createFederation(params, proposedKeys); + BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, proposedFederation); + Keccak256 svpSpendCreationRskTxHash = createHash(1); + Map.Entry svpSpendTxWFS = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); + StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(svpSpendTxWFS); + + Ethereum ethereum = mock(Ethereum.class); + AtomicReference ethereumListener = new AtomicReference<>(); + doAnswer((InvocationOnMock invocation) -> { + ethereumListener.set((EthereumListener) invocation.getArguments()[0]); + return null; + }).when(ethereum).addListener(any(EthereumListener.class)); + + FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); + // returns pegout waiting for signatures + doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); + // return svp spend tx waiting for signatures + doReturn(Optional.of(stateForProposedFederator)).when(federatorSupport).getStateForProposedFederator(); + + ECKey ecKey = new ECKey(); + BtcECKey fedKey = new BtcECKey(); + ECPublicKey signerPublicKey = new ECPublicKey(fedKey.getPubKey()); + + ECDSASigner signer = mock(ECDSASigner.class); + doReturn(signerPublicKey).when(signer).getPublicKey(BTC.getKeyId()); + doReturn(1).when(signer).getVersionForKeyId(ArgumentMatchers.any(KeyId.class)); + doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); + + PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); + when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) + .thenReturn(PEGOUT_SIGNED_CACHE_TTL); + + SignerMessageBuilderFactory signerMessageBuilderFactory = new SignerMessageBuilderFactory( + mock(ReceiptStore.class) + ); + + // pegout + Keccak256 blockHash = createHash(2); + Long blockNumber = 0L; + Block block = mock(Block.class); + TransactionReceipt txReceipt = mock(TransactionReceipt.class); + TransactionInfo txInfo = mock(TransactionInfo.class); + when(block.getHash()).thenReturn(blockHash); + when(block.getNumber()).thenReturn(blockNumber); + when(blockStore.getBlockByHash(blockHash.getBytes())).thenReturn(block); + when(txInfo.getReceipt()).thenReturn(txReceipt); + when(txInfo.getBlockHash()).thenReturn(blockHash.getBytes()); + when(receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(txInfo)); + + // svp spend tx + Keccak256 svpSpendBlockHash = createHash(3); + Long svpSpendBlockNumber = 1L; + Block svpSpendBlock = mock(Block.class); + TransactionReceipt svpSpendTxReceipt = mock(TransactionReceipt.class); + TransactionInfo svpSpendTxInfo = mock(TransactionInfo.class); + when(svpSpendBlock.getHash()).thenReturn(svpSpendBlockHash); + when(svpSpendBlock.getNumber()).thenReturn(svpSpendBlockNumber); + when(blockStore.getBlockByHash(svpSpendBlockHash.getBytes())).thenReturn(svpSpendBlock); + when(svpSpendTxInfo.getReceipt()).thenReturn(svpSpendTxReceipt); + when(svpSpendTxInfo.getBlockHash()).thenReturn(svpSpendBlockHash.getBytes()); + when(receiptStore.getInMainChain(svpSpendCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(svpSpendTxInfo)); + + ReleaseCreationInformationGetter releaseCreationInformationGetter = + new ReleaseCreationInformationGetter( + receiptStore, blockStore + ); + + BtcReleaseClientStorageSynchronizer storageSynchronizer = + mock(BtcReleaseClientStorageSynchronizer.class); + when(storageSynchronizer.isSynced()).thenReturn(true); + + BtcReleaseClient btcReleaseClient = new BtcReleaseClient( + ethereum, + blockStore, + receiptStore, + federatorSupport, + powpegNodeSystemProperties, + mock(NodeBlockProcessor.class) + ); + + btcReleaseClient.setup( + signer, + mock(ActivationConfig.class), + signerMessageBuilderFactory, + releaseCreationInformationGetter, + mock(ReleaseRequirementsEnforcer.class), + mock(BtcReleaseClientStorageAccessor.class), + storageSynchronizer + ); + + btcReleaseClient.start(federation); + btcReleaseClient.start(proposedFederation); + + // Act + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); + + // Assert + verify(federatorSupport, times(2)).addSignature( + anyList(), + any(byte[].class) + ); + } + @Test void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() throws Exception { // Arrange @@ -835,11 +947,14 @@ void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() thro mock(ReceiptStore.class) ); - Keccak256 blockHash = createHash(2); - Block block = mock(Block.class); + // block is the best block, which will not pass the confirmation difference + Keccak256 blockHash = bestBlock.getHash(); + Long blockNumber = bestBlock.getNumber(); + Block block = bestBlock; TransactionReceipt txReceipt = mock(TransactionReceipt.class); TransactionInfo txInfo = mock(TransactionInfo.class); when(block.getHash()).thenReturn(blockHash); + when(block.getNumber()).thenReturn(blockNumber); when(blockStore.getBlockByHash(blockHash.getBytes())).thenReturn(block); when(txInfo.getReceipt()).thenReturn(txReceipt); when(txInfo.getBlockHash()).thenReturn(blockHash.getBytes()); @@ -875,12 +990,6 @@ void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() thro btcReleaseClient.start(proposedFederation); - // Since the current best block will also be the block that - // contains the svp spend tx waiting for signatures hash then - // the confirmation difference will never be enough for the - // tx to be considered ready to be signed - when(blockStore.getBlockByHash(any())).thenReturn(bestBlock); - // Act ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); From 8f1116c0b88d5dd5ecc4e90e73623d2f95e6d782 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:44:45 +0000 Subject: [PATCH 042/112] refactor(btcreleaseclient): reuse fed key for unit test --- .../rsk/federate/btcreleaseclient/BtcReleaseClientTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 904ec323d..38e050b1c 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -787,7 +787,7 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSignatureForBoth() throws Exception { // Arrange List keys = Stream.generate(BtcECKey::new).limit(9).toList(); - BtcECKey federationKey = keys.get(0); + BtcECKey fedKey = keys.get(0); FederationMember federationMember = FederationMember.getFederationMembersFromKeys(keys).get(0); Federation federation = TestUtils.createFederation(params, keys); BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); @@ -797,7 +797,7 @@ void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesIsAvailable_shou StateForFederator stateForFederator = new StateForFederator(rskTxsWaitingForSignatures); List proposedKeys = Stream.generate(BtcECKey::new).limit(8).collect(Collectors.toList()); - proposedKeys.add(federationKey); + proposedKeys.add(fedKey); Federation proposedFederation = TestUtils.createFederation(params, proposedKeys); BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, proposedFederation); Keccak256 svpSpendCreationRskTxHash = createHash(1); @@ -819,7 +819,6 @@ void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesIsAvailable_shou doReturn(Optional.of(stateForProposedFederator)).when(federatorSupport).getStateForProposedFederator(); ECKey ecKey = new ECKey(); - BtcECKey fedKey = new BtcECKey(); ECPublicKey signerPublicKey = new ECPublicKey(fedKey.getPubKey()); ECDSASigner signer = mock(ECDSASigner.class); From b2c6091dd549df2c303ccee6d12d0a70b39bc46f Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:09:13 +0000 Subject: [PATCH 043/112] feat(btcreleaseclient) add unit test for when pegouts and svp spend tx are available but federator is only part of proposed --- .../BtcReleaseClientTest.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 38e050b1c..7852c08ab 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -783,6 +783,122 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign ); } + @Test + void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesAreAvailableAndFederatorIsOnlyPartOfProposedFederation_shouldOnlyAddOneSignature() throws Exception { + // Arrange + Federation federation = TestUtils.createFederation(params, 9); + BtcTransaction pegout = TestUtils.createBtcTransaction(params, federation); + Keccak256 pegoutCreationRskTxHash = createHash(0); + SortedMap rskTxsWaitingForSignatures = new TreeMap<>(); + rskTxsWaitingForSignatures.put(pegoutCreationRskTxHash, pegout); + StateForFederator stateForFederator = new StateForFederator(rskTxsWaitingForSignatures); + + Federation proposedFederation = TestUtils.createFederation(params, 9); + FederationMember federationMember = proposedFederation.getMembers().get(0); + BtcTransaction svpSpendTx = TestUtils.createBtcTransaction(params, proposedFederation); + Keccak256 svpSpendCreationRskTxHash = createHash(1); + Map.Entry svpSpendTxWFS = new AbstractMap.SimpleEntry<>(svpSpendCreationRskTxHash, svpSpendTx); + StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(svpSpendTxWFS); + + Ethereum ethereum = mock(Ethereum.class); + AtomicReference ethereumListener = new AtomicReference<>(); + doAnswer((InvocationOnMock invocation) -> { + ethereumListener.set((EthereumListener) invocation.getArguments()[0]); + return null; + }).when(ethereum).addListener(any(EthereumListener.class)); + + FederatorSupport federatorSupport = mock(FederatorSupport.class); + doReturn(federationMember).when(federatorSupport).getFederationMember(); + // returns pegout waiting for signatures + doReturn(stateForFederator).when(federatorSupport).getStateForFederator(); + // return svp spend tx waiting for signatures + doReturn(Optional.of(stateForProposedFederator)).when(federatorSupport).getStateForProposedFederator(); + + ECKey ecKey = new ECKey(); + ECPublicKey signerPublicKey = new ECPublicKey(federationMember.getBtcPublicKey().getPubKey()); + + ECDSASigner signer = mock(ECDSASigner.class); + doReturn(signerPublicKey).when(signer).getPublicKey(BTC.getKeyId()); + doReturn(1).when(signer).getVersionForKeyId(ArgumentMatchers.any(KeyId.class)); + doReturn(ecKey.doSign(new byte[]{})).when(signer).sign(any(KeyId.class), any(SignerMessage.class)); + + PowpegNodeSystemProperties powpegNodeSystemProperties = mock(PowpegNodeSystemProperties.class); + doReturn(Constants.mainnet()).when(powpegNodeSystemProperties).getNetworkConstants(); + doReturn(true).when(powpegNodeSystemProperties).isPegoutEnabled(); + when(powpegNodeSystemProperties.getPegoutSignedCacheTtl()) + .thenReturn(PEGOUT_SIGNED_CACHE_TTL); + + SignerMessageBuilderFactory signerMessageBuilderFactory = new SignerMessageBuilderFactory( + mock(ReceiptStore.class) + ); + + // pegout + Keccak256 blockHash = createHash(2); + Long blockNumber = 0L; + Block block = mock(Block.class); + TransactionReceipt txReceipt = mock(TransactionReceipt.class); + TransactionInfo txInfo = mock(TransactionInfo.class); + when(block.getHash()).thenReturn(blockHash); + when(block.getNumber()).thenReturn(blockNumber); + when(blockStore.getBlockByHash(blockHash.getBytes())).thenReturn(block); + when(txInfo.getReceipt()).thenReturn(txReceipt); + when(txInfo.getBlockHash()).thenReturn(blockHash.getBytes()); + when(receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(txInfo)); + + // svp spend tx + Keccak256 svpSpendBlockHash = createHash(3); + Long svpSpendBlockNumber = 1L; + Block svpSpendBlock = mock(Block.class); + TransactionReceipt svpSpendTxReceipt = mock(TransactionReceipt.class); + TransactionInfo svpSpendTxInfo = mock(TransactionInfo.class); + when(svpSpendBlock.getHash()).thenReturn(svpSpendBlockHash); + when(svpSpendBlock.getNumber()).thenReturn(svpSpendBlockNumber); + when(blockStore.getBlockByHash(svpSpendBlockHash.getBytes())).thenReturn(svpSpendBlock); + when(svpSpendTxInfo.getReceipt()).thenReturn(svpSpendTxReceipt); + when(svpSpendTxInfo.getBlockHash()).thenReturn(svpSpendBlockHash.getBytes()); + when(receiptStore.getInMainChain(svpSpendCreationRskTxHash.getBytes(), blockStore)).thenReturn(Optional.of(svpSpendTxInfo)); + + ReleaseCreationInformationGetter releaseCreationInformationGetter = + new ReleaseCreationInformationGetter( + receiptStore, blockStore + ); + + BtcReleaseClientStorageSynchronizer storageSynchronizer = + mock(BtcReleaseClientStorageSynchronizer.class); + when(storageSynchronizer.isSynced()).thenReturn(true); + + BtcReleaseClient btcReleaseClient = new BtcReleaseClient( + ethereum, + blockStore, + receiptStore, + federatorSupport, + powpegNodeSystemProperties, + mock(NodeBlockProcessor.class) + ); + + btcReleaseClient.setup( + signer, + mock(ActivationConfig.class), + signerMessageBuilderFactory, + releaseCreationInformationGetter, + mock(ReleaseRequirementsEnforcer.class), + mock(BtcReleaseClientStorageAccessor.class), + storageSynchronizer + ); + + btcReleaseClient.start(proposedFederation); + + // Act + ethereumListener.get().onBestBlock(bestBlock, Collections.emptyList()); + + // Assert + verify(federatorSupport).addSignature( + anyList(), + any(byte[].class) + ); + } + + @Test void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSignatureForBoth() throws Exception { // Arrange From 8be36eb7932dbb6292fb97b37108fb29df196725 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:28:25 +0000 Subject: [PATCH 044/112] feat(btcreleaseclient): update all fed ekys for unit tests --- .../rsk/federate/btcreleaseclient/BtcReleaseClientTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 7852c08ab..d12c71b72 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -713,8 +713,7 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign doReturn(mock(StateForFederator.class)).when(federatorSupport).getStateForFederator(); ECKey ecKey = new ECKey(); - BtcECKey fedKey = new BtcECKey(); - ECPublicKey signerPublicKey = new ECPublicKey(fedKey.getPubKey()); + ECPublicKey signerPublicKey = new ECPublicKey(federationMember.getBtcPublicKey().getPubKey()); ECDSASigner signer = mock(ECDSASigner.class); doReturn(signerPublicKey).when(signer).getPublicKey(BTC.getKeyId()); @@ -1044,8 +1043,7 @@ void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() thro doReturn(mock(StateForFederator.class)).when(federatorSupport).getStateForFederator(); ECKey ecKey = new ECKey(); - BtcECKey fedKey = new BtcECKey(); - ECPublicKey signerPublicKey = new ECPublicKey(fedKey.getPubKey()); + ECPublicKey signerPublicKey = new ECPublicKey(federationMember.getBtcPublicKey().getPubKey()); ECDSASigner signer = mock(ECDSASigner.class); doReturn(signerPublicKey).when(signer).getPublicKey(BTC.getKeyId()); From 51937cc9b1730d37e10d9d545cce7e0725c7232b Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:46:05 +0000 Subject: [PATCH 045/112] feat(btcreleaseclient): add warning log in case isPegoutEnabled is disabled in config --- .../java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index ba74941e3..f8d485d78 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -228,6 +228,7 @@ private class BtcReleaseEthereumListener extends EthereumListenerAdapter { @Override public void onBestBlock(org.ethereum.core.Block block, List receipts) { if (!isPegoutEnabled) { + logger.warn("[onBestBlock] Processing of RSK transactions waiting for signatures is disabled"); return; } From e7999da0d0276d25e8d62325f3c6910a85b43a12 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:12:16 +0000 Subject: [PATCH 046/112] feat(federate): add unit tests for getStateForProposedFederator --- .../co/rsk/federate/FederatorSupportTest.java | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index ab2be771d..678506535 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -4,27 +4,32 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import co.rsk.peg.constants.BridgeMainNetConstants; -import co.rsk.peg.constants.BridgeRegTestConstants; import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.NetworkParameters; +import co.rsk.crypto.Keccak256; import co.rsk.federate.adapter.ThinConverter; +import co.rsk.federate.bitcoin.BitcoinTestUtils; import co.rsk.federate.config.TestSystemProperties; +import co.rsk.federate.signing.utils.TestUtils; import co.rsk.peg.Bridge; import co.rsk.peg.BridgeMethods; -import co.rsk.federate.bitcoin.BitcoinTestUtils; +import co.rsk.peg.StateForProposedFederator; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import org.bitcoinj.core.Block; import org.bitcoinj.core.Coin; @@ -145,6 +150,37 @@ void hasBlockCoinbaseInformed() { assertTrue(fs.hasBlockCoinbaseInformed(Sha256Hash.ZERO_HASH)); } + + @Test + void getStateForProposedFederator_whenCallTxReturnsNull_shouldReturnEmptyOptional() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_STATE_FOR_SVP_CLIENT))) + .thenReturn(null); + + // Act + Optional result = federatorSupport.getStateForProposedFederator(); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getStateForProposedFederator_whenCallTxReturnsValidData_shouldReturnStateForProposedFederator() { + // Arrange + Keccak256 rskTxHash = TestUtils.createHash(1); + BtcTransaction btcTx = new BtcTransaction(NETWORK_PARAMETERS); + Map.Entry svpSpendTx = new AbstractMap.SimpleEntry<>(rskTxHash, btcTx); + StateForProposedFederator stateForProposedFederator = new StateForProposedFederator(svpSpendTx); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_STATE_FOR_SVP_CLIENT))) + .thenReturn(stateForProposedFederator.encodeToRlp()); + + // Act + Optional result = federatorSupport.getStateForProposedFederator(); + + // Assert + assertTrue(result.isPresent()); + assertEquals(svpSpendTx, result.get().getSvpSpendTxWaitingForSignatures()); + } @Test void getProposedFederationAddress_whenAddressStringIsEmpty_shouldReturnEmptyOptional() { @@ -160,7 +196,7 @@ void getProposedFederationAddress_whenAddressStringIsEmpty_shouldReturnEmptyOpti } @Test - void getProposedFederation_whenAddressStringIsNull_shouldReturnEmptyOptional() { + void getProposedFederationAddress_whenAddressStringIsNull_shouldReturnEmptyOptional() { // Arrange when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_ADDRESS))) .thenReturn(null); @@ -173,7 +209,7 @@ void getProposedFederation_whenAddressStringIsNull_shouldReturnEmptyOptional() { } @Test - void getProposedFederation_whenAddressStringIsValid_shouldReturnAddress() { + void getProposedFederationAddress_whenAddressStringIsValid_shouldReturnAddress() { // Arrange when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_ADDRESS))) .thenReturn(DEFAULT_ADDRESS.toBase58()); From b5e3fc6342d90a8c84c0d36efc000774687e540c Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:41:21 +0000 Subject: [PATCH 047/112] feat(btcreleaseclient): use ReleaseCreationInformationGetter to retrieve block data about the svp spend tx --- .../java/co/rsk/federate/FedNodeContext.java | 2 - .../btcreleaseclient/BtcReleaseClient.java | 49 ++++++++++--------- .../BtcReleaseClientTest.java | 48 ------------------ 3 files changed, 25 insertions(+), 74 deletions(-) diff --git a/src/main/java/co/rsk/federate/FedNodeContext.java b/src/main/java/co/rsk/federate/FedNodeContext.java index ead5bdc96..cb7abecfd 100644 --- a/src/main/java/co/rsk/federate/FedNodeContext.java +++ b/src/main/java/co/rsk/federate/FedNodeContext.java @@ -59,8 +59,6 @@ public NodeRunner buildNodeRunner() { getBtcToRskClientRetiring(), new BtcReleaseClient( getRsk(), - getBlockStore(), - getReceiptStore(), getFederatorSupport(), getPowpegNodeSystemProperties(), getNodeBlockProcessor() diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index f8d485d78..70b3b564a 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -100,8 +100,6 @@ public class BtcReleaseClient { private static final DataWord SINGLE_RELEASE_BTC_TOPIC_SOLIDITY = DataWord.valueOf(BridgeEvents.RELEASE_BTC.getEvent().encodeSignatureLong()); private final Ethereum ethereum; - private final BlockStore blockStore; - private final ReceiptStore receiptStore; private final FederatorSupport federatorSupport; private final Set observedFederations; private final NodeBlockProcessor nodeBlockProcessor; @@ -121,15 +119,11 @@ public class BtcReleaseClient { public BtcReleaseClient( Ethereum ethereum, - BlockStore blockStore, - ReceiptStore receiptStore, FederatorSupport federatorSupport, PowpegNodeSystemProperties systemProperties, NodeBlockProcessor nodeBlockProcessor ) { this.ethereum = ethereum; - this.blockStore = blockStore; - this.receiptStore = receiptStore; this.federatorSupport = federatorSupport; this.observedFederations = new HashSet<>(); this.blockListener = new BtcReleaseEthereumListener(); @@ -248,7 +242,7 @@ public void onBestBlock(org.ethereum.core.Block block, List // before attempting to sign any pegouts. federatorSupport.getStateForProposedFederator() .map(StateForProposedFederator::getSvpSpendTxWaitingForSignatures) - .filter(svpSpendTxWaitingForSignatures -> isSVPSpendTxReadyToSign(block.getNumber(), svpSpendTxWaitingForSignatures.getKey())) + .filter(svpSpendTxWaitingForSignatures -> isSVPSpendTxReadyToSign(block.getNumber(), svpSpendTxWaitingForSignatures)) .ifPresent(svpSpendTxReadyToBeSigned -> processReleases(Set.of(svpSpendTxReadyToBeSigned))); // Processing transactions waiting for signatures on best block only still "works", @@ -297,23 +291,30 @@ public void onBlock(org.ethereum.core.Block block, List rece * @return {@code true} if the transaction has the required number of confirmations and is ready to be signed; * {@code false} otherwise */ - private boolean isSVPSpendTxReadyToSign(long currentBlockNumber, Keccak256 svpTxHash) { - boolean isReadyToSign = Optional.ofNullable(svpTxHash) - .map(Keccak256::getBytes) - .flatMap(txHash -> receiptStore.getInMainChain(txHash, blockStore)) - .map(TransactionInfo::getBlockHash) - .map(blockStore::getBlockByHash) - .map(Block::getNumber) - .map(blockNumberWithSvpSpendTx -> currentBlockNumber - blockNumberWithSvpSpendTx) - .filter(confirmationDifference -> confirmationDifference >= bridgeConstants.getRsk2BtcMinimumAcceptableConfirmations()) - .isPresent(); - - logger.info("[isSvpSpendTxReadyToSign] SVP spend tx readiness check for signing: tx hash [{}], Current block [{}], Ready to sign? [{}]", - svpTxHash, - currentBlockNumber, - isReadyToSign ? "YES" : "NO"); - - return isReadyToSign; + private boolean isSVPSpendTxReadyToSign(long currentBlockNumber, Map.Entry svpSpendTx) { + try { + int version = signer.getVersionForKeyId(BTC.getKeyId()); + ReleaseCreationInformation releaseCreationInformation = releaseCreationInformationGetter.getTxInfoToSign( + version, svpSpendTx.getKey(), svpSpendTx.getValue()); + + boolean isReadyToSign = Optional.ofNullable(releaseCreationInformation) + .map(ReleaseCreationInformation::getPegoutCreationBlock) + .map(Block::getNumber) + .map(blockNumberWithSvpSpendTx -> currentBlockNumber - blockNumberWithSvpSpendTx) + .filter(confirmationDifference -> confirmationDifference >= bridgeConstants.getRsk2BtcMinimumAcceptableConfirmations()) + .isPresent(); + + logger.info("[isSvpSpendTxReadyToSign] SVP spend tx readiness check for signing: tx hash [{}], Current block [{}], Ready to sign? [{}]", + svpSpendTx.getKey(), + currentBlockNumber, + isReadyToSign ? "YES" : "NO"); + + return isReadyToSign; + } catch (Exception e) { + logger.error("[isSvpSpendTxReadyToSign] Error ocurred while checking if SVP spend tx is ready to be signed", e); + + return false; + } } private BtcTransaction convertToBtcTxFromRLPData(byte[] dataFromBtcReleaseTopic) { diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index d12c71b72..5b4c76968 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -139,8 +139,6 @@ void start_whenFederationMemberNotPartOfDesiredFederation_shouldThrowException() BtcReleaseClient btcReleaseClient = new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -160,8 +158,6 @@ void if_start_not_called_rsk_blockchain_not_listened() { new BtcReleaseClient( ethereum, - blockStore, - receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -181,8 +177,6 @@ void when_start_called_rsk_blockchain_is_listened() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -212,8 +206,6 @@ void if_stop_called_with_just_one_federation_rsk_blockchain_is_still_listened() BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -246,8 +238,6 @@ void if_stop_called_with_federations_rsk_blockchain_is_not_listened() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -310,8 +300,6 @@ void processReleases_ok() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -442,8 +430,6 @@ void having_two_pegouts_signs_only_one() throws Exception { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -533,8 +519,6 @@ void onBestBlock_whenPegoutTxIsCached_shouldNotSignSamePegoutTxAgain() throws Ex BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -639,8 +623,6 @@ void onBestBlock_whenPegoutTxIsCachedWithInvalidTimestamp_shouldSignSamePegoutTx BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -753,8 +735,6 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -868,8 +848,6 @@ void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesAreAvailableAndF BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -988,8 +966,6 @@ void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesIsAvailable_shou BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1084,8 +1060,6 @@ void onBestBlock_whenSvpSpendTxIsNotReadyToBeSigned_shouldNotAddSignature() thro BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1142,8 +1116,6 @@ void onBestBlock_return_when_node_is_syncing() throws BtcReleaseClientException BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1194,8 +1166,6 @@ void onBestBlock_return_when_pegout_is_disabled() throws BtcReleaseClientExcepti BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1246,8 +1216,6 @@ void onBlock_return_when_node_is_syncing() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1294,8 +1262,6 @@ void onBlock_return_when_pegout_is_disabled() { BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereum, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1414,8 +1380,6 @@ void validateTxCanBeSigned_federatorAlreadySigned() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1459,8 +1423,6 @@ void validateTxCanBeSigned_federationCantSign() throws Exception { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1519,8 +1481,6 @@ void removeSignaturesFromTransaction() { BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1608,8 +1568,6 @@ private void test_validateTxCanBeSigned( BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1642,8 +1600,6 @@ private void test_extractStandardRedeemScript( BtcReleaseClient client = new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) @@ -1779,8 +1735,6 @@ private void testUsageOfStorageWhenSigning(boolean shouldHaveDataInFile) BtcReleaseClient btcReleaseClient = new BtcReleaseClient( ethereumImpl, - blockStore, - receiptStore, federatorSupport, powpegNodeSystemProperties, nodeBlockProcessor @@ -1829,8 +1783,6 @@ private BtcReleaseClient createBtcClient() { return new BtcReleaseClient( mock(Ethereum.class), - blockStore, - receiptStore, mock(FederatorSupport.class), powpegNodeSystemProperties, mock(NodeBlockProcessor.class) From fb271531f46ad8cf5280c2e6b38054ca325e067e Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:56:25 +0000 Subject: [PATCH 048/112] feat(federate): add getProposedFederationSize to FederatorSupport --- src/main/java/co/rsk/federate/FederatorSupport.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index 1429f7878..d96bb9656 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -24,6 +24,7 @@ import java.net.UnknownHostException; import java.time.Instant; import java.util.List; +import java.util.Objects; import java.util.Optional; /** @@ -264,6 +265,14 @@ public Optional
getProposedFederationAddress() { .map(addr -> Address.fromBase58(getBtcParams(), addr)); } + public Optional getProposedFederationSize() { + BigInteger size = bridgeTransactionSender.callTx( + federatorAddress, Bridge.GET_PROPOSED_FEDERATION_SIZE); + + return Optional.ofNullable(size) + .map(BigInteger::intValue); + } + public int getBtcBlockchainBestChainHeight() { BigInteger btcBlockchainBestChainHeight = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_BTC_BLOCKCHAIN_BEST_CHAIN_HEIGHT); return btcBlockchainBestChainHeight.intValue(); From 6fec3f0f9c1f2ddc5e6b5830e251cb317a8a868c Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:57:03 +0000 Subject: [PATCH 049/112] feat(federate): add getProposedFederatorPublicKeyOfType to FederatorSupport --- src/main/java/co/rsk/federate/FederatorSupport.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index d96bb9656..ab102da39 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -273,6 +273,18 @@ public Optional getProposedFederationSize() { .map(BigInteger::intValue); } + public Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType) { + Objects.requireNonNull(keyType); + + byte[] publicKeyBytes = bridgeTransactionSender.callTx( + federatorAddress, + Bridge.GET_PROPOSED_FEDERATOR_PUBLIC_KEY_OF_TYPE, + new Object[]{ index, keyType.getValue() }); + + return Optional.ofNullable(publicKeyBytes) + .map(ECKey::fromPublicOnly); + } + public int getBtcBlockchainBestChainHeight() { BigInteger btcBlockchainBestChainHeight = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_BTC_BLOCKCHAIN_BEST_CHAIN_HEIGHT); return btcBlockchainBestChainHeight.intValue(); From 5c26e357eda7e17fbae4c259cbc4e42f725fbec1 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:57:31 +0000 Subject: [PATCH 050/112] feat(federate): add getProposedFederationCreationTime to FederatorSupport --- src/main/java/co/rsk/federate/FederatorSupport.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index ab102da39..ea05566c2 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -285,6 +285,15 @@ public Optional getProposedFederatorPublicKeyOfType(int index, Federation .map(ECKey::fromPublicOnly); } + public Optional getProposedFederationCreationTime() { + BigInteger creationTime = bridgeTransactionSender.callTx( + federatorAddress, Bridge.GET_PROPOSED_FEDERATION_CREATION_TIME); + + return Optional.ofNullable(creationTime) + .map(BigInteger::longValue) + .map(Instant::ofEpochMilli); + } + public int getBtcBlockchainBestChainHeight() { BigInteger btcBlockchainBestChainHeight = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_BTC_BLOCKCHAIN_BEST_CHAIN_HEIGHT); return btcBlockchainBestChainHeight.intValue(); From 4311475cce0ad7f3672ef990de6f038d7601aef8 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:57:57 +0000 Subject: [PATCH 051/112] feat(federate): add getProposedFederationCreationBlockNumber to FederatorSupport --- src/main/java/co/rsk/federate/FederatorSupport.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index ea05566c2..1841ee7db 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -294,6 +294,14 @@ public Optional getProposedFederationCreationTime() { .map(Instant::ofEpochMilli); } + public Optional getProposedFederationCreationBlockNumber() { + BigInteger creationBlockNumber = + bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_PROPOSED_FEDERATION_CREATION_BLOCK_NUMBER); + + return Optional.ofNullable(creationBlockNumber) + .map(BigInteger::longValue); + } + public int getBtcBlockchainBestChainHeight() { BigInteger btcBlockchainBestChainHeight = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_BTC_BLOCKCHAIN_BEST_CHAIN_HEIGHT); return btcBlockchainBestChainHeight.intValue(); From 67923176356de15922e73922157c9d25c9db23fd Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:59:30 +0000 Subject: [PATCH 052/112] feat(federate): add getProposedFederation to FederationProvider interface --- src/main/java/co/rsk/federate/FederationProvider.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/co/rsk/federate/FederationProvider.java b/src/main/java/co/rsk/federate/FederationProvider.java index 6900bef09..0c1993931 100644 --- a/src/main/java/co/rsk/federate/FederationProvider.java +++ b/src/main/java/co/rsk/federate/FederationProvider.java @@ -38,6 +38,8 @@ public interface FederationProvider { // The currently "retiring" federation's address Optional
getRetiringFederationAddress(); + // The currently "proposed" federation + Optional getProposedFederation(); // The currently "proposed" federation's address Optional
getProposedFederationAddress(); } From 2555f76a4c520c3752e113ab50b6b7058b04720c Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:00:18 +0000 Subject: [PATCH 053/112] feat(federate): add implementation of getProposedFederation to FederationProviderFromFederatorSupport --- ...ederationProviderFromFederatorSupport.java | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index ba411ee06..9e54a030c 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -17,7 +17,10 @@ */ package co.rsk.federate; +import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; +import static co.rsk.peg.federation.FederationMember.KeyType; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP417; import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; @@ -30,6 +33,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.IntStream; /** * Provides a federation using a FederatorSupport instance, which in turn @@ -135,9 +139,54 @@ public Optional
getRetiringFederationAddress() { return federatorSupport.getRetiringFederationAddress(); } + @Override + public Optional getProposedFederation() { + if (!federatorSupport.getConfigForBestBlock().isActive(RSKIP417)) { + return Optional.empty(); + } + + Optional
proposedFederationAddress = getProposedFederationAddress(); + if (proposedFederationAddress.isEmpty()) { + return Optional.empty(); + } + + Integer federationSize = federatorSupport.getProposedFederationSize() + .orElse(FEDERATION_NON_EXISTENT.getCode()); + if (federationSize == FEDERATION_NON_EXISTENT.getCode()) { + return Optional.empty(); + } + + List members = IntStream.range(0, federationSize) + .mapToObj(i -> new FederationMember( + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) + .map(ECKey::getPubKey) + .map(BtcECKey::fromPublicOnly) + .orElse(null), + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.RSK) + .orElse(null), + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.MST) + .orElse(null) + )) + .toList(); + + FederationArgs federationArgs = new FederationArgs( + members, + federatorSupport.getProposedFederationCreationTime().orElse(null), + federatorSupport.getProposedFederationCreationBlockNumber().orElse(null), + federatorSupport.getBtcParams() + ); + + Federation initialFederation = FederationFactory.buildStandardMultiSigFederation(federationArgs); + Federation expectedFederation = getExpectedFederation(initialFederation, proposedFederationAddress.get()); + + return Optional.of(expectedFederation); + } + @Override public Optional
getProposedFederationAddress() { - return federatorSupport.getProposedFederationAddress(); + return Optional.of(federatorSupport) + .filter(federatorSupport -> federatorSupport.getConfigForBestBlock().isActive(RSKIP417)) + .flatMap(FederatorSupport::getProposedFederationAddress); } private Federation getExpectedFederation(Federation initialFederation, Address expectedFederationAddress) { From b54b217de54c9ec2a88d62048a599a7ebe65ebe2 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:01:38 +0000 Subject: [PATCH 054/112] refactor(federate): use FEDERATION_NON_EXISTENT enum code in conditional check for building retiring federation --- .../rsk/federate/FederationProviderFromFederatorSupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 9e54a030c..a06963c4e 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -43,13 +43,13 @@ * @author Ariel Mendelzon */ public class FederationProviderFromFederatorSupport implements FederationProvider { + private final FederatorSupport federatorSupport; private final FederationConstants federationConstants; public FederationProviderFromFederatorSupport( FederatorSupport federatorSupport, FederationConstants federationConstants) { - this.federatorSupport = federatorSupport; this.federationConstants = federationConstants; } @@ -97,7 +97,7 @@ public Optional getRetiringFederation() { Integer federationSize = federatorSupport.getRetiringFederationSize(); Optional
optionalRetiringFederationAddress = getRetiringFederationAddress(); - if (federationSize == -1 || !optionalRetiringFederationAddress.isPresent()) { + if (federationSize == FEDERATION_NON_EXISTENT.getCode() || !optionalRetiringFederationAddress.isPresent()) { return Optional.empty(); } From d1f52c867f607cd14c12dabc01add42bf9108a1a Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:07:23 +0000 Subject: [PATCH 055/112] feat(federate): add unit tests for getProposedFederationSize --- .../co/rsk/federate/FederatorSupportTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index 678506535..ac0beceb4 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -26,6 +26,8 @@ import co.rsk.peg.BridgeMethods; import co.rsk.peg.StateForProposedFederator; import java.util.AbstractMap; +import co.rsk.federate.bitcoin.BitcoinTestUtils; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -222,6 +224,34 @@ void getProposedFederationAddress_whenAddressStringIsValid_shouldReturnAddress() assertEquals(DEFAULT_ADDRESS.toString(), result.get().toString()); } + @Test + void getProposedFederationSize_whenSizeIsNull_shouldReturnEmptyOptional() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_SIZE))) + .thenReturn(null); + + // Act + Optional result = federatorSupport.getProposedFederationSize(); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getProposedFederationSize_whenSizeIsPresent_shouldReturnInteger() { + // Arrange + BigInteger expectedSize = BigInteger.valueOf(9); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_SIZE))) + .thenReturn(expectedSize); + + // Act + Optional result = federatorSupport.getProposedFederationSize(); + + // Assert + assertTrue(result.isPresent()); + assertEquals(expectedSize.intValue(), result.get()); + } + private Sha256Hash createHash() { byte[] bytes = new byte[32]; bytes[0] = (byte) 1; From 340e680d44b83f1856ee984b0b5d7c5270b7c145 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:25:44 +0000 Subject: [PATCH 056/112] feat(federate): add unit tests for getProposedFederatorPublicKeyOfType --- .../co/rsk/federate/FederatorSupportTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index ac0beceb4..122e15f13 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; @@ -13,6 +14,7 @@ import static org.mockito.Mockito.when; import co.rsk.peg.constants.BridgeMainNetConstants; +import co.rsk.peg.federation.FederationMember; import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; import co.rsk.bitcoinj.core.BtcTransaction; @@ -42,6 +44,7 @@ import org.bitcoinj.core.TransactionWitness; import org.bouncycastle.util.encoders.Hex; import org.ethereum.core.Blockchain; +import org.ethereum.crypto.ECKey; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.stubbing.Answer; @@ -51,6 +54,7 @@ class FederatorSupportTest { private static final NetworkParameters NETWORK_PARAMETERS = BridgeMainNetConstants.getInstance().getBtcParams(); private static final List KEYS = BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"k1", "k2", "k3"}, true); private static final Address DEFAULT_ADDRESS = BitcoinTestUtils.createP2SHMultisigAddress(NETWORK_PARAMETERS, KEYS); + private static final byte[] PUBLIC_KEY = Hex.decode("0497466f2b32bc3bb76d4741ae51cd1d8578b48d3f1e68da206d47321aec267ce78549b514e4453d74ef11b0cd5e4e4c364effddac8b51bcfc8de80682f952896f"); private BridgeTransactionSender bridgeTransactionSender; private FederatorSupport federatorSupport; @@ -252,6 +256,56 @@ void getProposedFederationSize_whenSizeIsPresent_shouldReturnInteger() { assertEquals(expectedSize.intValue(), result.get()); } + @Test + void getProposedFederatorPublicKeyOfType_whenPublicKeyBytesIsNull_shouldReturnEmptyOptional() { + // Arrange + int index = 0; + FederationMember.KeyType keyType = FederationMember.KeyType.BTC; + when(bridgeTransactionSender.callTx( + any(), + eq(Bridge.GET_PROPOSED_FEDERATOR_PUBLIC_KEY_OF_TYPE), + eq(new Object[]{ index, keyType.getValue() }))) + .thenReturn(null); + + // Act + Optional result = federatorSupport.getProposedFederatorPublicKeyOfType(index, keyType); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getProposedFederatorPublicKeyOfType_whenPublicKeyBytesArePresent_shouldReturnECKey() { + // Arrange + int index = 0; + FederationMember.KeyType keyType = FederationMember.KeyType.BTC; + ECKey expectedKey = ECKey.fromPublicOnly(PUBLIC_KEY); + when(bridgeTransactionSender.callTx( + any(), + eq(Bridge.GET_PROPOSED_FEDERATOR_PUBLIC_KEY_OF_TYPE), + eq(new Object[]{ index, keyType.getValue() }))) + .thenReturn(PUBLIC_KEY); + + // Act + Optional result = federatorSupport.getProposedFederatorPublicKeyOfType(index, keyType); + + // Assert + assertTrue(result.isPresent()); + assertArrayEquals(expectedKey.getPubKey(), result.get().getPubKey()); + } + + @Test + void getProposedFederatorPublicKeyOfType_whenKeyTypeIsNull_shouldThrowNullPointerException() { + // Arrange + int index = 0; + + // Act & Assert + assertThrows(NullPointerException.class, () -> { + federatorSupport.getProposedFederatorPublicKeyOfType(index, null); + }); + } + + private Sha256Hash createHash() { byte[] bytes = new byte[32]; bytes[0] = (byte) 1; From 98430bc9cf2fa9c3934a5497a16ef54c50b09283 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:28:27 +0000 Subject: [PATCH 057/112] feat(federate): add unit tests for getProposedFederationCreationTime --- .../co/rsk/federate/FederatorSupportTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index 122e15f13..cdbc95aa1 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -30,6 +30,7 @@ import java.util.AbstractMap; import co.rsk.federate.bitcoin.BitcoinTestUtils; import java.math.BigInteger; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -305,6 +306,34 @@ void getProposedFederatorPublicKeyOfType_whenKeyTypeIsNull_shouldThrowNullPointe }); } + @Test + void getProposedFederationCreationTime_whenCreationTimeIsNull_shouldReturnEmptyOptional() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_CREATION_TIME))) + .thenReturn(null); + + // Act + Optional result = federatorSupport.getProposedFederationCreationTime(); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getProposedFederationCreationTime_whenCreationTimeIsValid_shouldReturnInstant() { + // Arrange + BigInteger expectedCreationTime = BigInteger.valueOf(System.currentTimeMillis()); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_CREATION_TIME))) + .thenReturn(expectedCreationTime); + + // Act + Optional result = federatorSupport.getProposedFederationCreationTime(); + + // Assert + assertTrue(result.isPresent()); + assertEquals(expectedCreationTime.longValue(), result.get().toEpochMilli()); + } + private Sha256Hash createHash() { byte[] bytes = new byte[32]; From 08b0e4d877e558fd8a0956343fb003429dea07b7 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:30:49 +0000 Subject: [PATCH 058/112] feat(federate): add unit tests for getProposedFederationCreationBlockNumber --- .../co/rsk/federate/FederatorSupportTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index cdbc95aa1..25fab5f91 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -334,6 +334,33 @@ void getProposedFederationCreationTime_whenCreationTimeIsValid_shouldReturnInsta assertEquals(expectedCreationTime.longValue(), result.get().toEpochMilli()); } + @Test + void getProposedFederationCreationBlockNumber_whenBlockNumberIsNull_shouldReturnEmptyOptional() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_CREATION_BLOCK_NUMBER))) + .thenReturn(null); + + // Act + Optional result = federatorSupport.getProposedFederationCreationBlockNumber(); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getProposedFederationCreationBlockNumber_whenBlockNumberIsValid_shouldReturnLong() { + // Arrange + BigInteger expectedBlockNumber = BigInteger.valueOf(123456); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_PROPOSED_FEDERATION_CREATION_BLOCK_NUMBER))) + .thenReturn(expectedBlockNumber); + + // Act + Optional result = federatorSupport.getProposedFederationCreationBlockNumber(); + + // Assert + assertTrue(result.isPresent()); + assertEquals(expectedBlockNumber.longValue(), result.get()); + } private Sha256Hash createHash() { byte[] bytes = new byte[32]; From c2a6872cb4988bea879baeca05ec37f0cf2c2f0d Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:14:04 +0000 Subject: [PATCH 059/112] feat(federate): add unit tests for getProposedFederation --- ...ederationProviderFromFederatorSupport.java | 6 +- ...ationProviderFromFederatorSupportTest.java | 168 +++++++++++++++++- 2 files changed, 162 insertions(+), 12 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index a06963c4e..253e610e0 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -146,13 +146,9 @@ public Optional getProposedFederation() { } Optional
proposedFederationAddress = getProposedFederationAddress(); - if (proposedFederationAddress.isEmpty()) { - return Optional.empty(); - } - Integer federationSize = federatorSupport.getProposedFederationSize() .orElse(FEDERATION_NON_EXISTENT.getCode()); - if (federationSize == FEDERATION_NON_EXISTENT.getCode()) { + if (federationSize == FEDERATION_NON_EXISTENT.getCode() || proposedFederationAddress.isEmpty()) { return Optional.empty(); } diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 75dc201e0..85c94be2e 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -1,12 +1,13 @@ package co.rsk.federate; +import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP284; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP417; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -227,11 +228,11 @@ void getActiveFederationAddress() { @Test void getRetiringFederation_none() { - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(-1); + when(federatorSupportMock.getRetiringFederationSize()).thenReturn(FEDERATION_NON_EXISTENT.getCode()); assertEquals(Optional.empty(), federationProvider.getRetiringFederation()); - verify(federatorSupportMock, times(1)).getRetiringFederationSize(); - verify(federatorSupportMock, times(1)).getRetiringFederationAddress(); + verify(federatorSupportMock).getRetiringFederationSize(); + verify(federatorSupportMock).getRetiringFederationAddress(); } @Test @@ -239,8 +240,8 @@ void getRetiringFederation_no_address() { when(federatorSupportMock.getRetiringFederationSize()).thenReturn(5); assertEquals(Optional.empty(), federationProvider.getRetiringFederation()); - verify(federatorSupportMock, times(1)).getRetiringFederationSize(); - verify(federatorSupportMock, times(1)).getRetiringFederationAddress(); + verify(federatorSupportMock).getRetiringFederationSize(); + verify(federatorSupportMock).getRetiringFederationAddress(); } @Test @@ -279,7 +280,7 @@ void getRetiringFederation_present_afterMultikey() { when(configMock.isActive(RSKIP123)).thenReturn(true); Federation expectedFederation = createFederation( - getFederationMembersFromPks(1,2000, 4000, 6000, 8000, 10000, 12000) + getFederationMembersFromPks(1, 2000, 4000, 6000, 8000, 10000, 12000) ); Address expectedFederationAddress = expectedFederation.getAddress(); @@ -370,9 +371,159 @@ void getRetiringFederation_present_p2sh_erp_federation() { assertEquals(expectedFederationAddress, obtainedFederation.getAddress()); } + @Test + void getProposedFederation_whenRSKIP417IsNotActivated_shouldReturnEmptyOptional() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(false); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + + // Act & Assert + assertEquals(Optional.empty(), federationProvider.getProposedFederation()); + } + + @Test + void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnEmptyOptional() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + when(federatorSupportMock.getProposedFederationSize()) + .thenReturn(Optional.of(FEDERATION_NON_EXISTENT.getCode())); + + // Act & Assert + assertEquals(Optional.empty(), federationProvider.getProposedFederation()); + verify(federatorSupportMock).getProposedFederationSize(); + verify(federatorSupportMock).getProposedFederationAddress(); + } + + @Test + void getProposedFederation_whenProposedFederationAddressDoesNotExist_shouldReturnEmptyOptional() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + when(federatorSupportMock.getProposedFederationSize()) + .thenReturn(Optional.of(9)); + + // Act & Assert + assertEquals(Optional.empty(), federationProvider.getProposedFederation()); + verify(federatorSupportMock).getProposedFederationSize(); + verify(federatorSupportMock).getProposedFederationAddress(); + } + + @Test + void getProposedFederation_whenExistsAndIsStandardMultisigFederation_shouldReturnProposedFederation() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + + Federation expectedFederation = createFederation( + getFederationMembersFromPks(0, 2000, 4000, 6000, 8000, 10000, 12000)); + Address expectedFederationAddress = expectedFederation.getAddress(); + Integer federationSize = 6; + when(federatorSupportMock.getProposedFederationSize()).thenReturn(Optional.of(federationSize)); + when(federatorSupportMock.getProposedFederationCreationTime()).thenReturn(Optional.of(creationTime)); + when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(expectedFederationAddress)); + when(federatorSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(0L)); + when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); + for (int i = 0; i < federationSize; i++) { + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000)))); + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+1)))); + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+2)))); + } + + // Act + Optional proposedFederation = federationProvider.getProposedFederation(); + + // Assert + assertTrue(proposedFederation.isPresent()); + assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, proposedFederation.get().getFormatVersion()); + assertEquals(expectedFederation, proposedFederation.get()); + assertEquals(expectedFederationAddress, proposedFederation.get().getAddress()); + } + + @Test + void getProposedFederation_whenExistsAndIsNonStandardErpFederation_shouldReturnProposedFederation() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + + Federation expectedFederation = createNonStandardErpFederation( + getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000), + configMock); + Address expectedFederationAddress = expectedFederation.getAddress(); + Integer federationSize = 5; + when(federatorSupportMock.getProposedFederationSize()).thenReturn(Optional.of(federationSize)); + when(federatorSupportMock.getProposedFederationCreationTime()).thenReturn(Optional.of(creationTime)); + when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(expectedFederationAddress)); + when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); + when(federatorSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(0L)); + for (int i = 0; i < federationSize; i++) { + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000)))); + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1)))); + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2)))); + } + + // Act + Optional proposedFederation = federationProvider.getProposedFederation(); + + // Assert + assertTrue(proposedFederation.isPresent()); + assertEquals(NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION, proposedFederation.get().getFormatVersion()); + assertEquals(expectedFederation, proposedFederation.get()); + assertEquals(expectedFederationAddress, proposedFederation.get().getAddress()); + } + + @Test + void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposedFederation() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + + Federation expectedFederation = createP2shErpFederation( + getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000)); + Address expectedFederationAddress = expectedFederation.getAddress(); + Integer federationSize = 5; + when(federatorSupportMock.getProposedFederationSize()).thenReturn(Optional.of(federationSize)); + when(federatorSupportMock.getProposedFederationCreationTime()).thenReturn(Optional.of(creationTime)); + when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(expectedFederationAddress)); + when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); + when(federatorSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(0L)); + for (int i = 0; i < federationSize; i++) { + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000)))); + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1)))); + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)) + .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2)))); + } + + // Act + Optional proposedFederation = federationProvider.getProposedFederation(); + + // Assert + assertTrue(proposedFederation.isPresent()); + assertEquals(P2SH_ERP_FEDERATION_FORMAT_VERSION, proposedFederation.get().getFormatVersion()); + assertEquals(expectedFederation, proposedFederation.get()); + assertEquals(expectedFederationAddress, proposedFederation.get().getAddress()); + } + @Test void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(DEFAULT_ADDRESS)); // Act @@ -386,6 +537,9 @@ void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { @Test void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional() { // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP417)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.empty()); // Act From 3728215ad2c1dee597b8061c47e58696e4711872 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:26:48 +0000 Subject: [PATCH 060/112] fix(federate): use proper activation rule for svp --- ...FederationProviderFromFederatorSupport.java | 6 +++--- ...rationProviderFromFederatorSupportTest.java | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 253e610e0..db36c616c 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -20,7 +20,7 @@ import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; import static co.rsk.peg.federation.FederationMember.KeyType; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP417; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP419; import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; @@ -141,7 +141,7 @@ public Optional
getRetiringFederationAddress() { @Override public Optional getProposedFederation() { - if (!federatorSupport.getConfigForBestBlock().isActive(RSKIP417)) { + if (!federatorSupport.getConfigForBestBlock().isActive(RSKIP419)) { return Optional.empty(); } @@ -181,7 +181,7 @@ public Optional getProposedFederation() { @Override public Optional
getProposedFederationAddress() { return Optional.of(federatorSupport) - .filter(federatorSupport -> federatorSupport.getConfigForBestBlock().isActive(RSKIP417)) + .filter(federatorSupport -> federatorSupport.getConfigForBestBlock().isActive(RSKIP419)) .flatMap(FederatorSupport::getProposedFederationAddress); } diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 85c94be2e..114263089 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -3,7 +3,7 @@ import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP284; -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP417; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP419; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -375,7 +375,7 @@ void getRetiringFederation_present_p2sh_erp_federation() { void getProposedFederation_whenRSKIP417IsNotActivated_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(false); + when(configMock.isActive(RSKIP419)).thenReturn(false); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); // Act & Assert @@ -386,7 +386,7 @@ void getProposedFederation_whenRSKIP417IsNotActivated_shouldReturnEmptyOptional( void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(true); + when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationSize()) .thenReturn(Optional.of(FEDERATION_NON_EXISTENT.getCode())); @@ -401,7 +401,7 @@ void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnE void getProposedFederation_whenProposedFederationAddressDoesNotExist_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(true); + when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationSize()) .thenReturn(Optional.of(9)); @@ -416,7 +416,7 @@ void getProposedFederation_whenProposedFederationAddressDoesNotExist_shouldRetur void getProposedFederation_whenExistsAndIsStandardMultisigFederation_shouldReturnProposedFederation() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(true); + when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); Federation expectedFederation = createFederation( @@ -451,7 +451,7 @@ void getProposedFederation_whenExistsAndIsStandardMultisigFederation_shouldRetur void getProposedFederation_whenExistsAndIsNonStandardErpFederation_shouldReturnProposedFederation() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(true); + when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); Federation expectedFederation = createNonStandardErpFederation( @@ -487,7 +487,7 @@ void getProposedFederation_whenExistsAndIsNonStandardErpFederation_shouldReturnP void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposedFederation() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(true); + when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); Federation expectedFederation = createP2shErpFederation( @@ -522,7 +522,7 @@ void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposed void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(true); + when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(DEFAULT_ADDRESS)); @@ -538,7 +538,7 @@ void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP417)).thenReturn(true); + when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.empty()); From b17182cb7c5584bdcb06d8fef485dce95ffd6891 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:15:33 +0000 Subject: [PATCH 061/112] refactor(federate): improve javadocs for FederationProvider interface --- .../co/rsk/federate/FederationProvider.java | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProvider.java b/src/main/java/co/rsk/federate/FederationProvider.java index 0c1993931..5f9ec42f5 100644 --- a/src/main/java/co/rsk/federate/FederationProvider.java +++ b/src/main/java/co/rsk/federate/FederationProvider.java @@ -22,24 +22,52 @@ import java.util.Optional; /** - * Implementors of this interface must be able to provide - * federation instances. - * - * @author Ariel Mendelzon + * Provides access to various federation instances, including the active, retiring, and proposed federations. + * Implementations of this interface must define how to retrieve the federations and their respective addresses. + * These methods allow clients to manage and monitor federations in different lifecycle stages within the bridge. */ public interface FederationProvider { - // The currently "active" federation + + /** + * Retrieves the currently active federation, which is responsible for processing transactions. + * + * @return the active {@link Federation} instance + */ Federation getActiveFederation(); - // The currently "active" federation's address + + /** + * Retrieves the address of the currently active federation. + * + * @return the {@link Address} of the active federation + */ Address getActiveFederationAddress(); - // The currently "retiring" federation + /** + * Retrieves the currently retiring federation, if one exists. This federation is in transition + * and will soon be replaced by a new active federation. + * + * @return an {@link Optional} containing the retiring {@link Federation}, or {@link Optional#empty()} if none exists + */ Optional getRetiringFederation(); - // The currently "retiring" federation's address + + /** + * Retrieves the address of the currently retiring federation, if one exists. + * + * @return an {@link Optional} containing the {@link Address} of the retiring federation, or {@link Optional#empty()} if none exists + */ Optional
getRetiringFederationAddress(); - // The currently "proposed" federation + /** + * Retrieves the currently proposed federation, if one exists. This federation is awaiting activation. + * + * @return an {@link Optional} containing the proposed {@link Federation}, or {@link Optional#empty()} if none exists + */ Optional getProposedFederation(); - // The currently "proposed" federation's address + + /** + * Retrieves the address of the currently proposed federation, if one exists. + * + * @return an {@link Optional} containing the {@link Address} of the proposed federation, or {@link Optional#empty()} if none exists + */ Optional
getProposedFederationAddress(); } From 91d03ff694b117d80e61b033a45d96c1747ff7ee Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:20:42 +0000 Subject: [PATCH 062/112] refactor(federate): use isEmpty when checking if retiring federation exists --- .../co/rsk/federate/FederationProviderFromFederatorSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index db36c616c..d1b0ed0ea 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -97,7 +97,7 @@ public Optional getRetiringFederation() { Integer federationSize = federatorSupport.getRetiringFederationSize(); Optional
optionalRetiringFederationAddress = getRetiringFederationAddress(); - if (federationSize == FEDERATION_NON_EXISTENT.getCode() || !optionalRetiringFederationAddress.isPresent()) { + if (federationSize == FEDERATION_NON_EXISTENT.getCode() || optionalRetiringFederationAddress.isEmpty()) { return Optional.empty(); } From 7667e1754f0af0024e8c23fc6e0257386aaa5171 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:25:09 +0000 Subject: [PATCH 063/112] feat(federate): throw exception if after proving that the proposed federation exists any related data is missing --- .../FederationProviderFromFederatorSupport.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index d1b0ed0ea..bfb3838b6 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -157,18 +157,20 @@ public Optional getProposedFederation() { federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) .map(ECKey::getPubKey) .map(BtcECKey::fromPublicOnly) - .orElse(null), + .orElseThrow(() -> new IllegalStateException()), federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.RSK) - .orElse(null), + .orElseThrow(() -> new IllegalStateException()), federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.MST) - .orElse(null) + .orElseThrow(() -> new IllegalStateException()) )) .toList(); FederationArgs federationArgs = new FederationArgs( members, - federatorSupport.getProposedFederationCreationTime().orElse(null), - federatorSupport.getProposedFederationCreationBlockNumber().orElse(null), + federatorSupport.getProposedFederationCreationTime() + .orElseThrow(() -> new IllegalStateException()), + federatorSupport.getProposedFederationCreationBlockNumber() + .orElseThrow(() -> new IllegalStateException()), federatorSupport.getBtcParams() ); From 2b30329df676cbfa5d88af5aaeea22a0fc5ec0e3 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:26:24 +0000 Subject: [PATCH 064/112] refactor(federate): method call consistent with all the other proposed ones --- src/main/java/co/rsk/federate/FederatorSupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index 1841ee7db..5897487a7 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -295,8 +295,8 @@ public Optional getProposedFederationCreationTime() { } public Optional getProposedFederationCreationBlockNumber() { - BigInteger creationBlockNumber = - bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_PROPOSED_FEDERATION_CREATION_BLOCK_NUMBER); + BigInteger creationBlockNumber = bridgeTransactionSender.callTx( + federatorAddress, Bridge.GET_PROPOSED_FEDERATION_CREATION_BLOCK_NUMBER); return Optional.ofNullable(creationBlockNumber) .map(BigInteger::longValue); From cc386be7882165b3c4a1f6c9a2ea2299f75e1b5d Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:33:38 +0000 Subject: [PATCH 065/112] feat(federate): after rskip419 the only possible type of federation is a P2shErpFederation --- .../FederationProviderFromFederatorSupport.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index bfb3838b6..7084b4a8f 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -152,7 +152,7 @@ public Optional getProposedFederation() { return Optional.empty(); } - List members = IntStream.range(0, federationSize) + List federationMembers = IntStream.range(0, federationSize) .mapToObj(i -> new FederationMember( federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) .map(ECKey::getPubKey) @@ -166,7 +166,7 @@ public Optional getProposedFederation() { .toList(); FederationArgs federationArgs = new FederationArgs( - members, + federationMembers, federatorSupport.getProposedFederationCreationTime() .orElseThrow(() -> new IllegalStateException()), federatorSupport.getProposedFederationCreationBlockNumber() @@ -174,10 +174,12 @@ public Optional getProposedFederation() { federatorSupport.getBtcParams() ); - Federation initialFederation = FederationFactory.buildStandardMultiSigFederation(federationArgs); - Federation expectedFederation = getExpectedFederation(initialFederation, proposedFederationAddress.get()); + Federation federation = FederationFactory.buildP2shErpFederation( + federationArgs, + federationConstants.getErpFedPubKeysList(), + federationConstants.getErpFedActivationDelay()); - return Optional.of(expectedFederation); + return Optional.of(federation); } @Override From 3878f90ae576e3dc65bfc18072f5e03751476676 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:36:43 +0000 Subject: [PATCH 066/112] feat(federate): remove unit tests for proposed federation that are unrealistic --- ...ationProviderFromFederatorSupportTest.java | 73 +------------------ 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 114263089..9ac6431e8 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -372,7 +372,7 @@ void getRetiringFederation_present_p2sh_erp_federation() { } @Test - void getProposedFederation_whenRSKIP417IsNotActivated_shouldReturnEmptyOptional() { + void getProposedFederation_whenRSKIP419IsNotActivated_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); when(configMock.isActive(RSKIP419)).thenReturn(false); @@ -412,77 +412,6 @@ void getProposedFederation_whenProposedFederationAddressDoesNotExist_shouldRetur verify(federatorSupportMock).getProposedFederationAddress(); } - @Test - void getProposedFederation_whenExistsAndIsStandardMultisigFederation_shouldReturnProposedFederation() { - // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - - Federation expectedFederation = createFederation( - getFederationMembersFromPks(0, 2000, 4000, 6000, 8000, 10000, 12000)); - Address expectedFederationAddress = expectedFederation.getAddress(); - Integer federationSize = 6; - when(federatorSupportMock.getProposedFederationSize()).thenReturn(Optional.of(federationSize)); - when(federatorSupportMock.getProposedFederationCreationTime()).thenReturn(Optional.of(creationTime)); - when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(expectedFederationAddress)); - when(federatorSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(0L)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - for (int i = 0; i < federationSize; i++) { - when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)) - .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000)))); - when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)) - .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+1)))); - when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)) - .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*2000+2)))); - } - - // Act - Optional proposedFederation = federationProvider.getProposedFederation(); - - // Assert - assertTrue(proposedFederation.isPresent()); - assertEquals(STANDARD_MULTISIG_FEDERATION_FORMAT_VERSION, proposedFederation.get().getFormatVersion()); - assertEquals(expectedFederation, proposedFederation.get()); - assertEquals(expectedFederationAddress, proposedFederation.get().getAddress()); - } - - @Test - void getProposedFederation_whenExistsAndIsNonStandardErpFederation_shouldReturnProposedFederation() { - // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - - Federation expectedFederation = createNonStandardErpFederation( - getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000), - configMock); - Address expectedFederationAddress = expectedFederation.getAddress(); - Integer federationSize = 5; - when(federatorSupportMock.getProposedFederationSize()).thenReturn(Optional.of(federationSize)); - when(federatorSupportMock.getProposedFederationCreationTime()).thenReturn(Optional.of(creationTime)); - when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(expectedFederationAddress)); - when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); - when(federatorSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(0L)); - for (int i = 0; i < federationSize; i++) { - when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC)) - .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000)))); - when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK)) - .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+1)))); - when(federatorSupportMock.getProposedFederatorPublicKeyOfType(i, FederationMember.KeyType.MST)) - .thenReturn(Optional.of(ECKey.fromPrivate(BigInteger.valueOf((i+1)*1000+2)))); - } - - // Act - Optional proposedFederation = federationProvider.getProposedFederation(); - - // Assert - assertTrue(proposedFederation.isPresent()); - assertEquals(NON_STANDARD_ERP_FEDERATION_FORMAT_VERSION, proposedFederation.get().getFormatVersion()); - assertEquals(expectedFederation, proposedFederation.get()); - assertEquals(expectedFederationAddress, proposedFederation.get().getAddress()); - } - @Test void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposedFederation() { // Arrange From 33f81bafdaf24736fc2d1358b9e0cdb12e92480c Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:45:12 +0000 Subject: [PATCH 067/112] fix(federate): update var name to not shadow global federator support var --- .../co/rsk/federate/FederationProviderFromFederatorSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 7084b4a8f..23b6326bb 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -185,7 +185,7 @@ public Optional getProposedFederation() { @Override public Optional
getProposedFederationAddress() { return Optional.of(federatorSupport) - .filter(federatorSupport -> federatorSupport.getConfigForBestBlock().isActive(RSKIP419)) + .filter(fedSupport -> fedSupport.getConfigForBestBlock().isActive(RSKIP419)) .flatMap(FederatorSupport::getProposedFederationAddress); } From 356cfb09477e343e5107647baea3c5a0730cf791 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:47:09 +0000 Subject: [PATCH 068/112] refactor(federate): replace lambda with method reference 'IllegalStateException::new' --- .../FederationProviderFromFederatorSupport.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 23b6326bb..766134812 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -157,20 +157,20 @@ public Optional getProposedFederation() { federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) .map(ECKey::getPubKey) .map(BtcECKey::fromPublicOnly) - .orElseThrow(() -> new IllegalStateException()), + .orElseThrow(IllegalStateException::new), federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.RSK) - .orElseThrow(() -> new IllegalStateException()), + .orElseThrow(IllegalStateException::new), federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.MST) - .orElseThrow(() -> new IllegalStateException()) + .orElseThrow(IllegalStateException::new) )) .toList(); FederationArgs federationArgs = new FederationArgs( federationMembers, federatorSupport.getProposedFederationCreationTime() - .orElseThrow(() -> new IllegalStateException()), + .orElseThrow(IllegalStateException::new), federatorSupport.getProposedFederationCreationBlockNumber() - .orElseThrow(() -> new IllegalStateException()), + .orElseThrow(IllegalStateException::new), federatorSupport.getBtcParams() ); From 34a2fb9b76db4a120c84d9cedb8f6d4845e6cb44 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:06:02 +0000 Subject: [PATCH 069/112] refactor(federate): improve sonar complaints of FederateSupport --- src/main/java/co/rsk/federate/FederatorSupport.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index 5897487a7..e0d17dd46 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -33,7 +33,7 @@ */ public class FederatorSupport { - private static final Logger LOGGER = LoggerFactory.getLogger(FederatorSupport.class); + private static final Logger logger = LoggerFactory.getLogger(FederatorSupport.class); private final Blockchain blockchain; private final PowpegNodeSystemProperties config; @@ -95,7 +95,7 @@ public Long getRskBestChainHeight() { } public void sendReceiveHeaders(org.bitcoinj.core.Block[] headers) { - LOGGER.debug("About to send to the bridge headers from {} to {}", headers[0].getHash(), headers[headers.length - 1].getHash()); + logger.debug("About to send to the bridge headers from {} to {}", headers[0].getHash(), headers[headers.length - 1].getHash()); Object[] objectArray = new Object[headers.length]; @@ -115,7 +115,7 @@ public Long getBtcTxHashProcessedHeight(Sha256Hash btcTxHash) { } public void sendRegisterBtcTransaction(org.bitcoinj.core.Transaction tx, int blockHeight, PartialMerkleTree pmt) { - LOGGER.debug("About to send to the bridge btc tx hash {}. Block height {}", tx.getWTxId(), blockHeight); + logger.debug("About to send to the bridge btc tx hash {}. Block height {}", tx.getWTxId(), blockHeight); byte[] txSerialized = tx.bitcoinSerialize(); byte[] pmtSerialized = pmt.bitcoinSerialize(); @@ -123,7 +123,7 @@ public void sendRegisterBtcTransaction(org.bitcoinj.core.Transaction tx, int blo } public void sendRegisterCoinbaseTransaction(CoinbaseInformation coinbaseInformation) { - LOGGER.debug("About to send to the bridge btc coinbase tx hash {}. Block hash {}", coinbaseInformation.getCoinbaseTransaction().getTxId(), coinbaseInformation.getBlockHash()); + logger.debug("About to send to the bridge btc coinbase tx hash {}. Block hash {}", coinbaseInformation.getCoinbaseTransaction().getTxId(), coinbaseInformation.getBlockHash()); byte[] txSerialized = coinbaseInformation.getSerializedCoinbaseTransactionWithoutWitness(); byte[] pmtSerialized = coinbaseInformation.getPmt().bitcoinSerialize(); @@ -201,7 +201,7 @@ public long getFederationCreationBlockNumber() { public Optional
getRetiringFederationAddress() { String addressString = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_RETIRING_FEDERATION_ADDRESS); - if (addressString.equals("")) { + if (addressString.isEmpty()) { return Optional.empty(); } From 5b27cbb762d47ff8458e17f2e34de88fbea8ffd5 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:24:56 +0000 Subject: [PATCH 070/112] refactor(federate): make FederationProvider javadoc more concise --- src/main/java/co/rsk/federate/FederationProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProvider.java b/src/main/java/co/rsk/federate/FederationProvider.java index 5f9ec42f5..64b1e9584 100644 --- a/src/main/java/co/rsk/federate/FederationProvider.java +++ b/src/main/java/co/rsk/federate/FederationProvider.java @@ -29,7 +29,7 @@ public interface FederationProvider { /** - * Retrieves the currently active federation, which is responsible for processing transactions. + * Retrieves the currently active federation. * * @return the active {@link Federation} instance */ @@ -58,7 +58,7 @@ public interface FederationProvider { Optional
getRetiringFederationAddress(); /** - * Retrieves the currently proposed federation, if one exists. This federation is awaiting activation. + * Retrieves the currently proposed federation, if one exists. This federation is awaiting validation. * * @return an {@link Optional} containing the proposed {@link Federation}, or {@link Optional#empty()} if none exists */ From e7b2c6cf2a38c947fd6cf4d474e9e6147e6eda68 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:25:26 +0000 Subject: [PATCH 071/112] feat(federate): remove checking the proposed federation addr to validate exisiting fed --- .../rsk/federate/FederationProviderFromFederatorSupport.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 766134812..4514c188a 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -145,10 +145,9 @@ public Optional getProposedFederation() { return Optional.empty(); } - Optional
proposedFederationAddress = getProposedFederationAddress(); Integer federationSize = federatorSupport.getProposedFederationSize() .orElse(FEDERATION_NON_EXISTENT.getCode()); - if (federationSize == FEDERATION_NON_EXISTENT.getCode() || proposedFederationAddress.isEmpty()) { + if (federationSize == FEDERATION_NON_EXISTENT.getCode()) { return Optional.empty(); } From 3bdb4acd147248fc5360dbd00ba7de0848de931a Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:33:58 +0000 Subject: [PATCH 072/112] feat(federate): remove unit tests verifying if proposed fed addr is being called to check if proposed fed exists --- ...derationProviderFromFederatorSupportTest.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 9ac6431e8..d274cc67b 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -394,22 +394,6 @@ void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnE // Act & Assert assertEquals(Optional.empty(), federationProvider.getProposedFederation()); verify(federatorSupportMock).getProposedFederationSize(); - verify(federatorSupportMock).getProposedFederationAddress(); - } - - @Test - void getProposedFederation_whenProposedFederationAddressDoesNotExist_shouldReturnEmptyOptional() { - // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - when(federatorSupportMock.getProposedFederationSize()) - .thenReturn(Optional.of(9)); - - // Act & Assert - assertEquals(Optional.empty(), federationProvider.getProposedFederation()); - verify(federatorSupportMock).getProposedFederationSize(); - verify(federatorSupportMock).getProposedFederationAddress(); } @Test From 75869be24c807a4266b14c1a422a973bab26216e Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:18:33 +0000 Subject: [PATCH 073/112] feat(federate): add try-catch block in case proposed federation is unable to be built --- ...ederationProviderFromFederatorSupport.java | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 4514c188a..3cfaf24b5 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -27,13 +27,15 @@ import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.peg.federation.*; import co.rsk.peg.federation.constants.FederationConstants; -import org.ethereum.config.blockchain.upgrades.ActivationConfig; -import org.ethereum.crypto.ECKey; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.IntStream; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.crypto.ECKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Provides a federation using a FederatorSupport instance, which in turn @@ -44,6 +46,8 @@ */ public class FederationProviderFromFederatorSupport implements FederationProvider { + private static final Logger logger = LoggerFactory.getLogger(FederationProviderFromFederatorSupport.class); + private final FederatorSupport federatorSupport; private final FederationConstants federationConstants; @@ -151,34 +155,39 @@ public Optional getProposedFederation() { return Optional.empty(); } - List federationMembers = IntStream.range(0, federationSize) - .mapToObj(i -> new FederationMember( - federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) - .map(ECKey::getPubKey) - .map(BtcECKey::fromPublicOnly) + try { + List federationMembers = IntStream.range(0, federationSize) + .mapToObj(i -> new FederationMember( + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) + .map(ECKey::getPubKey) + .map(BtcECKey::fromPublicOnly) + .orElseThrow(IllegalStateException::new), + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.RSK) + .orElseThrow(IllegalStateException::new), + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.MST) + .orElseThrow(IllegalStateException::new) + )) + .toList(); + + FederationArgs federationArgs = new FederationArgs( + federationMembers, + federatorSupport.getProposedFederationCreationTime() .orElseThrow(IllegalStateException::new), - federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.RSK) + federatorSupport.getProposedFederationCreationBlockNumber() .orElseThrow(IllegalStateException::new), - federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.MST) - .orElseThrow(IllegalStateException::new) - )) - .toList(); - - FederationArgs federationArgs = new FederationArgs( - federationMembers, - federatorSupport.getProposedFederationCreationTime() - .orElseThrow(IllegalStateException::new), - federatorSupport.getProposedFederationCreationBlockNumber() - .orElseThrow(IllegalStateException::new), - federatorSupport.getBtcParams() - ); - - Federation federation = FederationFactory.buildP2shErpFederation( - federationArgs, - federationConstants.getErpFedPubKeysList(), - federationConstants.getErpFedActivationDelay()); - - return Optional.of(federation); + federatorSupport.getBtcParams() + ); + + Federation federation = FederationFactory.buildP2shErpFederation( + federationArgs, + federationConstants.getErpFedPubKeysList(), + federationConstants.getErpFedActivationDelay()); + + return Optional.of(federation); + } catch (IllegalStateException e) { + logger.error("[getProposedFederation] Unable to build the proposed federation", e); + return Optional.empty(); + } } @Override From 36c1a8565448c28f9a98bfe293daf55b22763d91 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:26:00 +0000 Subject: [PATCH 074/112] feat(federate): add unit test when proposed federation cannot be built because some data is missing from the Bridge --- ...ationProviderFromFederatorSupportTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index d274cc67b..00eca386f 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -396,6 +396,32 @@ void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnE verify(federatorSupportMock).getProposedFederationSize(); } + @Test + void getProposedFederation_whenSomeDataDoesNotExists_shouldReturnEmptyOptional() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + + Federation expectedFederation = createP2shErpFederation( + getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000)); + Address expectedFederationAddress = expectedFederation.getAddress(); + Integer federationSize = 5; + when(federatorSupportMock.getProposedFederationSize()).thenReturn(Optional.of(federationSize)); + when(federatorSupportMock.getProposedFederationCreationTime()).thenReturn(Optional.of(creationTime)); + when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(expectedFederationAddress)); + when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); + when(federatorSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(0L)); + when(federatorSupportMock.getProposedFederatorPublicKeyOfType(0, FederationMember.KeyType.BTC)) + .thenThrow(new IllegalStateException()); + + // Act + Optional proposedFederation = federationProvider.getProposedFederation(); + + // Assert + assertFalse(proposedFederation.isPresent()); + } + @Test void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposedFederation() { // Arrange From 69de28c27ca74c5f440af6878be2bb693949b8eb Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:55:57 +0000 Subject: [PATCH 075/112] refactor(federate): rename unit tests --- src/test/java/co/rsk/federate/FederatorSupportTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index 25fab5f91..f22971b36 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -258,7 +258,7 @@ void getProposedFederationSize_whenSizeIsPresent_shouldReturnInteger() { } @Test - void getProposedFederatorPublicKeyOfType_whenPublicKeyBytesIsNull_shouldReturnEmptyOptional() { + void getProposedFederatorPublicKeyOfType_whenPublicKeyIsNull_shouldReturnEmptyOptional() { // Arrange int index = 0; FederationMember.KeyType keyType = FederationMember.KeyType.BTC; @@ -276,7 +276,7 @@ void getProposedFederatorPublicKeyOfType_whenPublicKeyBytesIsNull_shouldReturnEm } @Test - void getProposedFederatorPublicKeyOfType_whenPublicKeyBytesArePresent_shouldReturnECKey() { + void getProposedFederatorPublicKeyOfType_whenPublicKeyIsPresent_shouldReturnECKey() { // Arrange int index = 0; FederationMember.KeyType keyType = FederationMember.KeyType.BTC; From 1e8fcaa50059761507d63ce282056a69f7b51694 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:57:43 +0000 Subject: [PATCH 076/112] refactor(federate): use ECKey class to build pub key in unit tests --- src/test/java/co/rsk/federate/FederatorSupportTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index f22971b36..85e1e962e 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -55,7 +55,7 @@ class FederatorSupportTest { private static final NetworkParameters NETWORK_PARAMETERS = BridgeMainNetConstants.getInstance().getBtcParams(); private static final List KEYS = BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"k1", "k2", "k3"}, true); private static final Address DEFAULT_ADDRESS = BitcoinTestUtils.createP2SHMultisigAddress(NETWORK_PARAMETERS, KEYS); - private static final byte[] PUBLIC_KEY = Hex.decode("0497466f2b32bc3bb76d4741ae51cd1d8578b48d3f1e68da206d47321aec267ce78549b514e4453d74ef11b0cd5e4e4c364effddac8b51bcfc8de80682f952896f"); + private static final byte[] PUBLIC_KEY = ECKey.fromPrivate(BigInteger.valueOf(100)).getPubKey(); private BridgeTransactionSender bridgeTransactionSender; private FederatorSupport federatorSupport; From 17f83dc82f5012bc206ad417b1e31d795945b768 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 4 Nov 2024 09:09:11 +0000 Subject: [PATCH 077/112] feat(federate): change catch exception class to catch all possible exception while building the proposed federation --- .../rsk/federate/FederationProviderFromFederatorSupport.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 3cfaf24b5..dbdf830b1 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -184,8 +184,9 @@ public Optional getProposedFederation() { federationConstants.getErpFedActivationDelay()); return Optional.of(federation); - } catch (IllegalStateException e) { + } catch (Exception e) { logger.error("[getProposedFederation] Unable to build the proposed federation", e); + return Optional.empty(); } } From 33f5b652df2d760beab22f2f50eee57eb5eb0655 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:26:32 +0000 Subject: [PATCH 078/112] refactor(federate): remove checking if RSKIP419 is activated for proposed federation --- .../federate/FederationProviderFromFederatorSupport.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index dbdf830b1..c5ac18ee4 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -20,7 +20,6 @@ import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; import static co.rsk.peg.federation.FederationMember.KeyType; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP419; import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; @@ -145,10 +144,6 @@ public Optional
getRetiringFederationAddress() { @Override public Optional getProposedFederation() { - if (!federatorSupport.getConfigForBestBlock().isActive(RSKIP419)) { - return Optional.empty(); - } - Integer federationSize = federatorSupport.getProposedFederationSize() .orElse(FEDERATION_NON_EXISTENT.getCode()); if (federationSize == FEDERATION_NON_EXISTENT.getCode()) { @@ -194,7 +189,6 @@ public Optional getProposedFederation() { @Override public Optional
getProposedFederationAddress() { return Optional.of(federatorSupport) - .filter(fedSupport -> fedSupport.getConfigForBestBlock().isActive(RSKIP419)) .flatMap(FederatorSupport::getProposedFederationAddress); } From 718fb0ccd07cf0fdacf8b9ef5d173913647f1250 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:56:12 +0000 Subject: [PATCH 079/112] feat(federate): remove RSKIP419 reference for unit tests --- .../FederationProviderFromFederatorSupportTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 00eca386f..5ce12a105 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -3,7 +3,6 @@ import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP284; -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP419; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -375,7 +374,6 @@ void getRetiringFederation_present_p2sh_erp_federation() { void getProposedFederation_whenRSKIP419IsNotActivated_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(false); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); // Act & Assert @@ -386,7 +384,6 @@ void getProposedFederation_whenRSKIP419IsNotActivated_shouldReturnEmptyOptional( void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationSize()) .thenReturn(Optional.of(FEDERATION_NON_EXISTENT.getCode())); @@ -400,7 +397,6 @@ void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnE void getProposedFederation_whenSomeDataDoesNotExists_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); Federation expectedFederation = createP2shErpFederation( @@ -426,7 +422,6 @@ void getProposedFederation_whenSomeDataDoesNotExists_shouldReturnEmptyOptional() void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposedFederation() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); Federation expectedFederation = createP2shErpFederation( @@ -461,7 +456,6 @@ void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposed void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(DEFAULT_ADDRESS)); @@ -477,7 +471,6 @@ void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional() { // Arrange ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(configMock.isActive(RSKIP419)).thenReturn(true); when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.empty()); From f55742a8cb56bea0b389c8964972c8678b9ae1d8 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:38:21 +0000 Subject: [PATCH 080/112] feat(federate): remove unit tests related with proposed fed and rskip419 --- .../FederationProviderFromFederatorSupportTest.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 5ce12a105..6faf345e8 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -370,16 +370,6 @@ void getRetiringFederation_present_p2sh_erp_federation() { assertEquals(expectedFederationAddress, obtainedFederation.getAddress()); } - @Test - void getProposedFederation_whenRSKIP419IsNotActivated_shouldReturnEmptyOptional() { - // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - - // Act & Assert - assertEquals(Optional.empty(), federationProvider.getProposedFederation()); - } - @Test void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnEmptyOptional() { // Arrange From f399fd81ed14979a8298f484868e836096218a5c Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:34:43 +0000 Subject: [PATCH 081/112] refactor(federate): remove Optional flat map to get the addr of the proposed fed --- .../rsk/federate/FederationProviderFromFederatorSupport.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index c5ac18ee4..05b8fdf75 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -188,8 +188,7 @@ public Optional getProposedFederation() { @Override public Optional
getProposedFederationAddress() { - return Optional.of(federatorSupport) - .flatMap(FederatorSupport::getProposedFederationAddress); + return federatorSupport.getProposedFederationAddress(); } private Federation getExpectedFederation(Federation initialFederation, Address expectedFederationAddress) { From 45a141242a59e740c7352fc948787319779e9418 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:22:29 +0000 Subject: [PATCH 082/112] feat(federate): remove unnecessary use of mocking activations in unit tests --- .../FederationProviderFromFederatorSupportTest.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 6faf345e8..77abb9e03 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -373,8 +373,6 @@ void getRetiringFederation_present_p2sh_erp_federation() { @Test void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnEmptyOptional() { // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationSize()) .thenReturn(Optional.of(FEDERATION_NON_EXISTENT.getCode())); @@ -386,9 +384,6 @@ void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnE @Test void getProposedFederation_whenSomeDataDoesNotExists_shouldReturnEmptyOptional() { // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - Federation expectedFederation = createP2shErpFederation( getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000)); Address expectedFederationAddress = expectedFederation.getAddress(); @@ -411,9 +406,6 @@ void getProposedFederation_whenSomeDataDoesNotExists_shouldReturnEmptyOptional() @Test void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposedFederation() { // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); - Federation expectedFederation = createP2shErpFederation( getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000)); Address expectedFederationAddress = expectedFederation.getAddress(); @@ -445,8 +437,6 @@ void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposed @Test void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(DEFAULT_ADDRESS)); // Act @@ -460,8 +450,6 @@ void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { @Test void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional() { // Arrange - ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); - when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.empty()); // Act From 396f55158b95ec897c47320a58ae9b7336e29b07 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:38:20 +0000 Subject: [PATCH 083/112] refactor(federate): removed checking if retiring fed addr is present when building retiring fed --- .../FederationProviderFromFederatorSupport.java | 5 ++--- .../FederationProviderFromFederatorSupportTest.java | 10 ---------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 05b8fdf75..ff602d7a2 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -98,13 +98,12 @@ public Address getActiveFederationAddress() { @Override public Optional getRetiringFederation() { Integer federationSize = federatorSupport.getRetiringFederationSize(); - Optional
optionalRetiringFederationAddress = getRetiringFederationAddress(); - if (federationSize == FEDERATION_NON_EXISTENT.getCode() || optionalRetiringFederationAddress.isEmpty()) { + if (federationSize == FEDERATION_NON_EXISTENT.getCode()) { return Optional.empty(); } - Address retiringFederationAddress = optionalRetiringFederationAddress.get(); + Address retiringFederationAddress = getRetiringFederationAddress().orElse(null); boolean useTypedPublicKeyGetter = federatorSupport.getConfigForBestBlock().isActive(RSKIP123); List members = new ArrayList<>(); for (int i = 0; i < federationSize; i++) { diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 77abb9e03..11ca31202 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -231,16 +231,6 @@ void getRetiringFederation_none() { assertEquals(Optional.empty(), federationProvider.getRetiringFederation()); verify(federatorSupportMock).getRetiringFederationSize(); - verify(federatorSupportMock).getRetiringFederationAddress(); - } - - @Test - void getRetiringFederation_no_address() { - when(federatorSupportMock.getRetiringFederationSize()).thenReturn(5); - - assertEquals(Optional.empty(), federationProvider.getRetiringFederation()); - verify(federatorSupportMock).getRetiringFederationSize(); - verify(federatorSupportMock).getRetiringFederationAddress(); } @Test From cd8b06fa38f272a661554c81ad8532fc0209d66f Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:53:47 +0000 Subject: [PATCH 084/112] feat(federate): Change getProposedFederationCreationTime to return Instant based on seconds --- src/main/java/co/rsk/federate/FederatorSupport.java | 2 +- src/test/java/co/rsk/federate/FederatorSupportTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index e0d17dd46..8f37e9d4e 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -291,7 +291,7 @@ public Optional getProposedFederationCreationTime() { return Optional.ofNullable(creationTime) .map(BigInteger::longValue) - .map(Instant::ofEpochMilli); + .map(Instant::ofEpochSecond); } public Optional getProposedFederationCreationBlockNumber() { diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index 85e1e962e..37e388736 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -331,7 +331,7 @@ void getProposedFederationCreationTime_whenCreationTimeIsValid_shouldReturnInsta // Assert assertTrue(result.isPresent()); - assertEquals(expectedCreationTime.longValue(), result.get().toEpochMilli()); + assertEquals(expectedCreationTime.longValue(), result.get().getEpochSecond()); } @Test From de828fb2ff142149fa425ecf8742ec9c7ea5245a Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:35:21 +0000 Subject: [PATCH 085/112] feat(federate): wrap logic for building the retiring fed in try-catch --- ...ederationProviderFromFederatorSupport.java | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index ff602d7a2..71d920266 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -103,37 +103,43 @@ public Optional getRetiringFederation() { return Optional.empty(); } - Address retiringFederationAddress = getRetiringFederationAddress().orElse(null); - boolean useTypedPublicKeyGetter = federatorSupport.getConfigForBestBlock().isActive(RSKIP123); - List members = new ArrayList<>(); - for (int i = 0; i < federationSize; i++) { - // Select method depending on network configuration for best block - FederationMember member; - if (useTypedPublicKeyGetter) { - BtcECKey btcKey = BtcECKey.fromPublicOnly(federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC).getPubKey()); - ECKey rskKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK); - ECKey mstKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST); - - member = new FederationMember(btcKey, rskKey, mstKey); - } else { - // Before the fork, all of BTC, RSK and MST keys are the same - BtcECKey btcKey = federatorSupport.getRetiringFederatorPublicKey(i); - ECKey rskMstKey = ECKey.fromPublicOnly(btcKey.getPubKey()); - - member = new FederationMember(btcKey, rskMstKey, rskMstKey); + try { + Address retiringFederationAddress = getRetiringFederationAddress().orElseThrow(IllegalStateException::new); + boolean useTypedPublicKeyGetter = federatorSupport.getConfigForBestBlock().isActive(RSKIP123); + List members = new ArrayList<>(); + for (int i = 0; i < federationSize; i++) { + // Select method depending on network configuration for best block + FederationMember member; + if (useTypedPublicKeyGetter) { + BtcECKey btcKey = BtcECKey.fromPublicOnly(federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC).getPubKey()); + ECKey rskKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK); + ECKey mstKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST); + + member = new FederationMember(btcKey, rskKey, mstKey); + } else { + // Before the fork, all of BTC, RSK and MST keys are the same + BtcECKey btcKey = federatorSupport.getRetiringFederatorPublicKey(i); + ECKey rskMstKey = ECKey.fromPublicOnly(btcKey.getPubKey()); + + member = new FederationMember(btcKey, rskMstKey, rskMstKey); + } + + members.add(member); } - members.add(member); - } + Instant creationTime = federatorSupport.getRetiringFederationCreationTime(); + long creationBlockNumber = federatorSupport.getRetiringFederationCreationBlockNumber(); + NetworkParameters btcParams = federatorSupport.getBtcParams(); + FederationArgs federationArgs = new FederationArgs(members, creationTime, creationBlockNumber, btcParams); - Instant creationTime = federatorSupport.getRetiringFederationCreationTime(); - long creationBlockNumber = federatorSupport.getRetiringFederationCreationBlockNumber(); - NetworkParameters btcParams = federatorSupport.getBtcParams(); - FederationArgs federationArgs = new FederationArgs(members, creationTime, creationBlockNumber, btcParams); + Federation initialFederation = FederationFactory.buildStandardMultiSigFederation(federationArgs); - Federation initialFederation = FederationFactory.buildStandardMultiSigFederation(federationArgs); + return Optional.of(getExpectedFederation(initialFederation, retiringFederationAddress)); + } catch (Exception e) { + logger.error("[getRetiringFederation] Unable to build the retiring federation", e); - return Optional.of(getExpectedFederation(initialFederation, retiringFederationAddress)); + return Optional.empty(); + } } @Override From 41a0d1a01616502ca904dc2b338a51d4c9ef4690 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:37:17 +0000 Subject: [PATCH 086/112] refactor(federate): enhance javadoc for FederationProviderFromFederatorSupport class --- .../FederationProviderFromFederatorSupport.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 71d920266..5d4d3c00c 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -37,11 +37,15 @@ import org.slf4j.LoggerFactory; /** - * Provides a federation using a FederatorSupport instance, which in turn - * gathers the federation from the bridge contract of the ethereum - * network it is attached to. + * A provider that supplies the current active, retiring, and proposed federations by + * using a {@link FederatorSupport} instance, which interacts with the Bridge contract. * - * @author Ariel Mendelzon + *

The {@code FederationProviderFromFederatorSupport} enables access to: + *

    + *
  • Active Federation + *
  • Retiring Federation + *
  • Proposed Federation + *
*/ public class FederationProviderFromFederatorSupport implements FederationProvider { From a02d3ed17179151b46e43ffc87d2c7df7bfa6761 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:50:14 +0000 Subject: [PATCH 087/112] feat(federate): add unit test when getting addr for retiring fed does not exist --- .../FederationProviderFromFederatorSupportTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 11ca31202..4b9b18bbf 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -233,6 +233,19 @@ void getRetiringFederation_none() { verify(federatorSupportMock).getRetiringFederationSize(); } + @Test + void getRetiringFederation_whenAddressNotPresent_shouldThrowIllegalStateException() { + // Arrange + when(federatorSupportMock.getRetiringFederationSize()).thenReturn(3); + when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.empty()); // Address is missing + + // Act + Optional retiringFederation = federationProvider.getRetiringFederation(); + + // Assert + assertTrue(retiringFederation.isEmpty()); + } + @Test void getRetiringFederation_present_beforeMultikey() { ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); From f41358b6d7532831aaedf8b7745189fdc1e4ccf6 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:21:33 +0000 Subject: [PATCH 088/112] feat(federate): allow exception to propagate in the listener if unable to build federation after validating the federation size --- ...ederationProviderFromFederatorSupport.java | 120 ++++++++---------- ...ationProviderFromFederatorSupportTest.java | 19 +-- 2 files changed, 59 insertions(+), 80 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 5d4d3c00c..9eb9357b9 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -33,8 +33,6 @@ import java.util.stream.IntStream; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.crypto.ECKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A provider that supplies the current active, retiring, and proposed federations by @@ -49,8 +47,6 @@ */ public class FederationProviderFromFederatorSupport implements FederationProvider { - private static final Logger logger = LoggerFactory.getLogger(FederationProviderFromFederatorSupport.class); - private final FederatorSupport federatorSupport; private final FederationConstants federationConstants; @@ -107,43 +103,37 @@ public Optional getRetiringFederation() { return Optional.empty(); } - try { - Address retiringFederationAddress = getRetiringFederationAddress().orElseThrow(IllegalStateException::new); - boolean useTypedPublicKeyGetter = federatorSupport.getConfigForBestBlock().isActive(RSKIP123); - List members = new ArrayList<>(); - for (int i = 0; i < federationSize; i++) { - // Select method depending on network configuration for best block - FederationMember member; - if (useTypedPublicKeyGetter) { - BtcECKey btcKey = BtcECKey.fromPublicOnly(federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC).getPubKey()); - ECKey rskKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK); - ECKey mstKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST); - - member = new FederationMember(btcKey, rskKey, mstKey); - } else { - // Before the fork, all of BTC, RSK and MST keys are the same - BtcECKey btcKey = federatorSupport.getRetiringFederatorPublicKey(i); - ECKey rskMstKey = ECKey.fromPublicOnly(btcKey.getPubKey()); - - member = new FederationMember(btcKey, rskMstKey, rskMstKey); - } - - members.add(member); + Address retiringFederationAddress = getRetiringFederationAddress().orElseThrow(IllegalStateException::new); + boolean useTypedPublicKeyGetter = federatorSupport.getConfigForBestBlock().isActive(RSKIP123); + List members = new ArrayList<>(); + for (int i = 0; i < federationSize; i++) { + // Select method depending on network configuration for best block + FederationMember member; + if (useTypedPublicKeyGetter) { + BtcECKey btcKey = BtcECKey.fromPublicOnly(federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.BTC).getPubKey()); + ECKey rskKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.RSK); + ECKey mstKey = federatorSupport.getRetiringFederatorPublicKeyOfType(i, FederationMember.KeyType.MST); + + member = new FederationMember(btcKey, rskKey, mstKey); + } else { + // Before the fork, all of BTC, RSK and MST keys are the same + BtcECKey btcKey = federatorSupport.getRetiringFederatorPublicKey(i); + ECKey rskMstKey = ECKey.fromPublicOnly(btcKey.getPubKey()); + + member = new FederationMember(btcKey, rskMstKey, rskMstKey); } - Instant creationTime = federatorSupport.getRetiringFederationCreationTime(); - long creationBlockNumber = federatorSupport.getRetiringFederationCreationBlockNumber(); - NetworkParameters btcParams = federatorSupport.getBtcParams(); - FederationArgs federationArgs = new FederationArgs(members, creationTime, creationBlockNumber, btcParams); + members.add(member); + } - Federation initialFederation = FederationFactory.buildStandardMultiSigFederation(federationArgs); + Instant creationTime = federatorSupport.getRetiringFederationCreationTime(); + long creationBlockNumber = federatorSupport.getRetiringFederationCreationBlockNumber(); + NetworkParameters btcParams = federatorSupport.getBtcParams(); + FederationArgs federationArgs = new FederationArgs(members, creationTime, creationBlockNumber, btcParams); - return Optional.of(getExpectedFederation(initialFederation, retiringFederationAddress)); - } catch (Exception e) { - logger.error("[getRetiringFederation] Unable to build the retiring federation", e); + Federation initialFederation = FederationFactory.buildStandardMultiSigFederation(federationArgs); - return Optional.empty(); - } + return Optional.of(getExpectedFederation(initialFederation, retiringFederationAddress)); } @Override @@ -159,40 +149,34 @@ public Optional getProposedFederation() { return Optional.empty(); } - try { - List federationMembers = IntStream.range(0, federationSize) - .mapToObj(i -> new FederationMember( - federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) - .map(ECKey::getPubKey) - .map(BtcECKey::fromPublicOnly) - .orElseThrow(IllegalStateException::new), - federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.RSK) - .orElseThrow(IllegalStateException::new), - federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.MST) - .orElseThrow(IllegalStateException::new) - )) - .toList(); - - FederationArgs federationArgs = new FederationArgs( - federationMembers, - federatorSupport.getProposedFederationCreationTime() + List federationMembers = IntStream.range(0, federationSize) + .mapToObj(i -> new FederationMember( + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.BTC) + .map(ECKey::getPubKey) + .map(BtcECKey::fromPublicOnly) .orElseThrow(IllegalStateException::new), - federatorSupport.getProposedFederationCreationBlockNumber() + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.RSK) .orElseThrow(IllegalStateException::new), - federatorSupport.getBtcParams() - ); - - Federation federation = FederationFactory.buildP2shErpFederation( - federationArgs, - federationConstants.getErpFedPubKeysList(), - federationConstants.getErpFedActivationDelay()); - - return Optional.of(federation); - } catch (Exception e) { - logger.error("[getProposedFederation] Unable to build the proposed federation", e); - - return Optional.empty(); - } + federatorSupport.getProposedFederatorPublicKeyOfType(i, KeyType.MST) + .orElseThrow(IllegalStateException::new) + )) + .toList(); + + FederationArgs federationArgs = new FederationArgs( + federationMembers, + federatorSupport.getProposedFederationCreationTime() + .orElseThrow(IllegalStateException::new), + federatorSupport.getProposedFederationCreationBlockNumber() + .orElseThrow(IllegalStateException::new), + federatorSupport.getBtcParams() + ); + + Federation federation = FederationFactory.buildP2shErpFederation( + federationArgs, + federationConstants.getErpFedPubKeysList(), + federationConstants.getErpFedActivationDelay()); + + return Optional.of(federation); } @Override diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 4b9b18bbf..50842c871 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -5,6 +5,7 @@ import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP284; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -239,11 +240,8 @@ void getRetiringFederation_whenAddressNotPresent_shouldThrowIllegalStateExceptio when(federatorSupportMock.getRetiringFederationSize()).thenReturn(3); when(federatorSupportMock.getRetiringFederationAddress()).thenReturn(Optional.empty()); // Address is missing - // Act - Optional retiringFederation = federationProvider.getRetiringFederation(); - - // Assert - assertTrue(retiringFederation.isEmpty()); + // Act & Assert + assertThrows(IllegalStateException.class, () -> federationProvider.getRetiringFederation()); } @Test @@ -385,7 +383,7 @@ void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnE } @Test - void getProposedFederation_whenSomeDataDoesNotExists_shouldReturnEmptyOptional() { + void getProposedFederation_whenSomeDataDoesNotExists_shouldThrowIllegalStateException() { // Arrange Federation expectedFederation = createP2shErpFederation( getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000)); @@ -397,13 +395,10 @@ void getProposedFederation_whenSomeDataDoesNotExists_shouldReturnEmptyOptional() when(federatorSupportMock.getBtcParams()).thenReturn(testnetParams); when(federatorSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(0L)); when(federatorSupportMock.getProposedFederatorPublicKeyOfType(0, FederationMember.KeyType.BTC)) - .thenThrow(new IllegalStateException()); + .thenReturn(Optional.empty()); - // Act - Optional proposedFederation = federationProvider.getProposedFederation(); - - // Assert - assertFalse(proposedFederation.isPresent()); + // Act & Assert + assertThrows(IllegalStateException.class, () -> federationProvider.getProposedFederation()); } @Test From aa420667ee80f65d2f4fb7f160997adca836c259 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:56:07 +0000 Subject: [PATCH 089/112] feat(watcher): add onProposedFederation method to interface --- .../rsk/federate/watcher/FederationWatcherListener.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java index 927b272e1..5d40eba79 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListener.java @@ -23,4 +23,12 @@ public interface FederationWatcherListener { * This can be {@code null}; the retiring federation is not always present. */ void onRetiringFederationChange(Federation newRetiringFederation); + + /** + * Invoked when the proposed federation changes. + * + * @param newProposedFederation the new proposed federation after the change. + * This can be {@code null}; the proposed federation is not always present. + */ + void onProposedFederationChange(Federation newProposedFederation); } From e466c1b44b2aaed5abff0990bcbf11c5bb18fe86 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:56:37 +0000 Subject: [PATCH 090/112] feat(watcher): intial implementation of onProposedFederationChange --- .../federate/watcher/FederationWatcherListenerImpl.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index 9a809fd3b..aa7324947 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -39,6 +39,15 @@ public void onRetiringFederationChange(Federation newRetiringFederation) { triggerClientChange(btcToRskClientRetiring, newRetiringFederation); } + @Override + public void onProposedFederationChange(Federation newProposedFederation) { + if (newProposedFederation == null) { + return; + } + + btcReleaseClient.start(newProposedFederation); + } + private void triggerClientChange(BtcToRskClient btcToRskClient, Federation newFederation) { // This method assumes that the new federation cannot be null Objects.requireNonNull(newFederation); From 24a26387b5e0024cbdaf09916204d612e04f9ac7 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:33:28 +0000 Subject: [PATCH 091/112] feat(watcher): enhance with try-catch and logging onProposedFederationChange --- .../FederationWatcherListenerImpl.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index aa7324947..9565c204d 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -42,10 +42,25 @@ public void onRetiringFederationChange(Federation newRetiringFederation) { @Override public void onProposedFederationChange(Federation newProposedFederation) { if (newProposedFederation == null) { + logger.info( + "[onProposedFederationChange] New proposed federation changed to null"); return; } - - btcReleaseClient.start(newProposedFederation); + + try { + // start BtcReleaseClient with proposed federation + // so it can sign svp spend tx + btcReleaseClient.start(newProposedFederation); + + logger.info( + "[onProposedFederationChange] Client for proposed federation [{}] started with success", + newProposedFederation.getAddress()); + } catch (Exception e) { + logger.error( + "[onProposedFederationChange] Client for proposed federation [{}] failed to start", + newProposedFederation.getAddress(), + e); + } } private void triggerClientChange(BtcToRskClient btcToRskClient, Federation newFederation) { @@ -66,9 +81,9 @@ private void triggerClientChange(BtcToRskClient btcToRskClient, Federation newFe newFederation.getAddress()); } catch (Exception e) { logger.error( - "[triggerClientChange] Clients for federation [{}] cannot be changed: {}", + "[triggerClientChange] Clients for federation [{}] cannot be changed", newFederation.getAddress(), - e.getMessage()); + e); } } From d031b7501ed8371cb70467785cf200284fcafb40 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:35:49 +0000 Subject: [PATCH 092/112] feat(watcher): use javadoc annotation for comment in onProposedFederationChange --- .../co/rsk/federate/watcher/FederationWatcherListenerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index 9565c204d..157f010dd 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -48,7 +48,7 @@ public void onProposedFederationChange(Federation newProposedFederation) { } try { - // start BtcReleaseClient with proposed federation + // start {@code BtcReleaseClient} with proposed federation // so it can sign svp spend tx btcReleaseClient.start(newProposedFederation); From 226033ed9ea247b6a6178e6bc51ff76873181ee9 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:44:12 +0000 Subject: [PATCH 093/112] feat(watcher): add unit tests for onProposedFederationChange --- .../FederationWatcherListenerImplTest.java | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java index 64d19c4d5..7e1ffde5b 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java @@ -1,8 +1,10 @@ package co.rsk.federate.watcher; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import co.rsk.bitcoinj.core.BtcECKey; @@ -25,13 +27,13 @@ class FederationWatcherListenerImplTest { private static final NetworkParameters NETWORK_PARAMETERS = NetworkParameters.fromID(NetworkParameters.ID_REGTEST); - private static final List FIRST_FEDERATION_MEMBERS = + private static final List FEDERATION_MEMBERS = getFederationMembersFromPksForBtc(1000, 2000, 3000, 4000); private static final long CREATION_BLOCK_NUMBER = 0L; - private static final Instant FIRST_FEDERATION_CREATION_TIME = Instant.ofEpochMilli(5005L); - private static final FederationArgs FIRST_FEDERATION_ARGS = new FederationArgs( - FIRST_FEDERATION_MEMBERS, FIRST_FEDERATION_CREATION_TIME, CREATION_BLOCK_NUMBER, NETWORK_PARAMETERS); - private static final Federation FIRST_FEDERATION = FederationFactory.buildStandardMultiSigFederation(FIRST_FEDERATION_ARGS); + private static final Instant FEDERATION_CREATION_TIME = Instant.ofEpochMilli(5005L); + private static final FederationArgs FEDERATION_ARGS = new FederationArgs( + FEDERATION_MEMBERS, FEDERATION_CREATION_TIME, CREATION_BLOCK_NUMBER, NETWORK_PARAMETERS); + private static final Federation FEDERATION = FederationFactory.buildStandardMultiSigFederation(FEDERATION_ARGS); private BtcToRskClient btcToRskClientActive; private BtcToRskClient btcToRskClientRetiring; @@ -50,13 +52,13 @@ void setUp() { @Test void onActiveFederationChange_whenFederationIsValid_shouldTriggerClientChange() { // Act - federationWatcherListener.onActiveFederationChange(FIRST_FEDERATION); + federationWatcherListener.onActiveFederationChange(FEDERATION); // Assert verify(btcToRskClientActive).stop(); - verify(btcReleaseClient).stop(FIRST_FEDERATION); - verify(btcToRskClientActive).start(FIRST_FEDERATION); - verify(btcReleaseClient).start(FIRST_FEDERATION); + verify(btcReleaseClient).stop(FEDERATION); + verify(btcToRskClientActive).start(FEDERATION); + verify(btcReleaseClient).start(FEDERATION); } @Test @@ -71,13 +73,13 @@ void onRetiringFederationChange_whenFederationIsNull_shouldClearRetiringFederati @Test void onRetiringFederationChange_whenFederationIsValid_shouldTriggerClientChange() { // Act - federationWatcherListener.onRetiringFederationChange(FIRST_FEDERATION); + federationWatcherListener.onRetiringFederationChange(FEDERATION); // Assert verify(btcToRskClientRetiring).stop(); - verify(btcReleaseClient).stop(FIRST_FEDERATION); - verify(btcToRskClientRetiring).start(FIRST_FEDERATION); - verify(btcReleaseClient).start(FIRST_FEDERATION); + verify(btcReleaseClient).stop(FEDERATION); + verify(btcToRskClientRetiring).start(FEDERATION); + verify(btcReleaseClient).start(FEDERATION); } @Test @@ -87,7 +89,34 @@ void triggerClientChange_whenExceptionOccurs_shouldHandleException() { doThrow(new RuntimeException("Simulated exception")).when(btcToRskClientActive).stop(); // Act & Assert - assertDoesNotThrow(() -> federationWatcherListener.onActiveFederationChange(FIRST_FEDERATION)); + assertDoesNotThrow(() -> federationWatcherListener.onActiveFederationChange(FEDERATION)); + } + + @Test + void onProposedFederationChange_whenNewProposedFederationIsNull_shouldNotStartClient() { + // Act + federationWatcherListener.onProposedFederationChange(null); + + // Assert + verify(btcReleaseClient, never()).start(any(Federation.class)); + } + + @Test + void onProposedFederationChange_whenNewProposedFederationIsValid_shouldStartClient() { + // Act + federationWatcherListener.onProposedFederationChange(FEDERATION); + + // Assert + verify(btcReleaseClient).start(FEDERATION); + } + + @Test + void onProposedFederationChange_whenClientStartThrowsException_shouldHandleException() { + // Arrange + doThrow(new RuntimeException("Start failed")).when(btcReleaseClient).start(FEDERATION); + + // Act & Assert + assertDoesNotThrow(() -> federationWatcherListener.onProposedFederationChange(FEDERATION)); } private static List getFederationMembersFromPksForBtc(Integer... pks) { From 5f4dea3ffaef44cd65f844a4337c1b7fce86ae3a Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:48:34 +0000 Subject: [PATCH 094/112] feat(watcher): implement onProposedFederationChange for anoymous classes in FederationWatcherTest --- .../watcher/FederationWatcherTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index 31387a8d7..4f9733323 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -101,6 +101,11 @@ public void onActiveFederationChange(Federation newFederation) { public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } + + @Override + public void onProposedFederationChange(Federation newFederation) { + throw new UnsupportedOperationException(); + } }); rskListener.onBestBlock(null, null); @@ -136,6 +141,11 @@ public void onActiveFederationChange(Federation newFederation) { public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } + + @Override + public void onProposedFederationChange(Federation newFederation) { + throw new UnsupportedOperationException(); + } }); rskListener.onBestBlock(null, null); @@ -169,6 +179,11 @@ public void onActiveFederationChange(Federation newFederation) { public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } + + @Override + public void onProposedFederationChange(Federation newFederation) { + throw new UnsupportedOperationException(); + } }); rskListener.onBestBlock(null, null); @@ -202,6 +217,11 @@ public void onActiveFederationChange(Federation newFederation) { public void onRetiringFederationChange(Federation newFederation) { retiringCalls.incrementAndGet(); } + + @Override + public void onProposedFederationChange(Federation newFederation) { + throw new UnsupportedOperationException(); + } }); rskListener.onBestBlock(null, null); @@ -237,6 +257,11 @@ public void onRetiringFederationChange(Federation newFederation) { assertEquals(FIRST_FEDERATION, newFederation); retiringCalls.incrementAndGet(); } + + @Override + public void onProposedFederationChange(Federation newFederation) { + throw new UnsupportedOperationException(); + } }); rskListener.onBestBlock(null, null); @@ -272,6 +297,11 @@ public void onRetiringFederationChange(Federation newFederation) { assertNull(newFederation); retiringCalls.incrementAndGet(); } + + @Override + public void onProposedFederationChange(Federation newFederation) { + throw new UnsupportedOperationException(); + } }); rskListener.onBestBlock(null, null); @@ -307,6 +337,11 @@ public void onRetiringFederationChange(Federation newFederation) { assertEquals(SECOND_FEDERATION, newFederation); retiringCalls.incrementAndGet(); } + + @Override + public void onProposedFederationChange(Federation newFederation) { + throw new UnsupportedOperationException(); + } }); rskListener.onBestBlock(null, null); From 52e7481875e00da19d7d37e0e4bbd8f8bf4d9925 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:05:15 +0000 Subject: [PATCH 095/112] feat(watcher): add logic for change in proposed fed in FederationWatcher class --- .../federate/watcher/FederationWatcher.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java index 71cc5b0ab..9f1c43b97 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -28,6 +28,7 @@ public class FederationWatcher { private Federation activeFederation; private Federation retiringFederation; + private Federation proposedFederation; /** * Constructs a new {@code FederationWatcher} with the specified RSK client. @@ -85,6 +86,7 @@ public void onBestBlock(org.ethereum.core.Block block, List private void updateState() { updateActiveFederation(); updateRetiringFederation(); + updateProposedFederation(); } private void updateActiveFederation() { @@ -126,4 +128,23 @@ private void updateRetiringFederation() { this.retiringFederation = currentlyRetiringFederation; } } + + private void updateProposedFederation() { + Optional
currentlyProposedFederationAddress = federationProvider.getProposedFederationAddress(); + Optional
oldProposedFederationAddress = Optional.ofNullable(proposedFederation) + .map(Federation::getAddress); + + boolean hasProposedFederationChanged = !currentlyProposedFederationAddress.equals(oldProposedFederationAddress); + + if (hasProposedFederationChanged) { + logger.info("[updateRetiringFederation] Proposed federation changed from {} to {}", + oldProposedFederationAddress.orElse(null), + currentlyProposedFederationAddress.orElse(null)); + + Federation currentlyProposedFederation = federationProvider.getProposedFederation().orElse(null); + + federationWatcherListener.onProposedFederationChange(currentlyProposedFederation); + this.proposedFederation = currentlyProposedFederation; + } + } } From 73b14bd871ad7f7e6e84605dc89a0c66856c26bd Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:26:38 +0000 Subject: [PATCH 096/112] refactor(watcher): use mock of federation watcher listener instead of implementing an anoymous class per test --- .../watcher/FederationWatcherTest.java | 215 ++++-------------- 1 file changed, 49 insertions(+), 166 deletions(-) diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index 4f9733323..c6cc303a8 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -68,7 +68,7 @@ class FederationWatcherTest { private final FederationWatcher federationWatcher = new FederationWatcher(rsk); @Test - void whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { + void onBestBlock_whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { // Act federationWatcher.start(federationProvider, federationWatcherListener); @@ -79,279 +79,162 @@ void whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { } @Test - void whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Exception { + void onBestBlock_whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Exception { // Arrange var rskListener = setupAndGetRskListener(null, null); - var activeCalls = new AtomicInteger(0); - var retiringCalls = new AtomicInteger(0); - when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getActiveFederation()).thenReturn(FIRST_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + federationWatcher.start(federationProvider, federationWatcherListener); + // Act - federationWatcher.start(federationProvider, new FederationWatcherListener() { - @Override - public void onActiveFederationChange(Federation newFederation) { - assertEquals(FIRST_FEDERATION, newFederation); - activeCalls.incrementAndGet(); - } - - @Override - public void onRetiringFederationChange(Federation newFederation) { - retiringCalls.incrementAndGet(); - } - - @Override - public void onProposedFederationChange(Federation newFederation) { - throw new UnsupportedOperationException(); - } - }); rskListener.onBestBlock(null, null); // Assert - assertEquals(1, activeCalls.get()); - assertEquals(0, retiringCalls.get()); verify(federationProvider).getActiveFederationAddress(); verify(federationProvider).getRetiringFederationAddress(); + verify(federationWatcherListener).onActiveFederationChange(FIRST_FEDERATION); + verify(federationProvider).getActiveFederation(); verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); } @Test - void whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFederationChange() throws Exception { + void onBestBlock_whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFederationChange() throws Exception { // Arrange var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null); - var activeCalls = new AtomicInteger(0); - var retiringCalls = new AtomicInteger(0); - when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getActiveFederation()).thenReturn(SECOND_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + federationWatcher.start(federationProvider, federationWatcherListener); + // Act - federationWatcher.start(federationProvider, new FederationWatcherListener() { - @Override - public void onActiveFederationChange(Federation newFederation) { - assertEquals(SECOND_FEDERATION, newFederation); - activeCalls.incrementAndGet(); - } - - @Override - public void onRetiringFederationChange(Federation newFederation) { - retiringCalls.incrementAndGet(); - } - - @Override - public void onProposedFederationChange(Federation newFederation) { - throw new UnsupportedOperationException(); - } - }); rskListener.onBestBlock(null, null); // Assert - assertEquals(1, activeCalls.get()); - assertEquals(0, retiringCalls.get()); verify(federationProvider).getActiveFederationAddress(); - verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider).getActiveFederation(); + verify(federationWatcherListener).onActiveFederationChange(SECOND_FEDERATION); + + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); } @Test - void whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { + void onBestBlock_whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { // Arrange var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null); - var activeCalls = new AtomicInteger(0); - var retiringCalls = new AtomicInteger(0); - when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + federationWatcher.start(federationProvider, federationWatcherListener); + // Act - federationWatcher.start(federationProvider, new FederationWatcherListener() { - @Override - public void onActiveFederationChange(Federation newFederation) { - activeCalls.incrementAndGet(); - } - - @Override - public void onRetiringFederationChange(Federation newFederation) { - retiringCalls.incrementAndGet(); - } - - @Override - public void onProposedFederationChange(Federation newFederation) { - throw new UnsupportedOperationException(); - } - }); rskListener.onBestBlock(null, null); // Assert - assertEquals(0, activeCalls.get()); - assertEquals(0, retiringCalls.get()); verify(federationProvider).getActiveFederationAddress(); - verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); + verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); + + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); } @Test - void whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { + void onBestBlock_whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { // Arrange var rskListener = setupAndGetRskListener(FIRST_FEDERATION, SECOND_FEDERATION); - var activeCalls = new AtomicInteger(0); - var retiringCalls = new AtomicInteger(0); - when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); + federationWatcher.start(federationProvider, federationWatcherListener); + // Act - federationWatcher.start(federationProvider, new FederationWatcherListener() { - @Override - public void onActiveFederationChange(Federation newFederation) { - activeCalls.incrementAndGet(); - } - - @Override - public void onRetiringFederationChange(Federation newFederation) { - retiringCalls.incrementAndGet(); - } - - @Override - public void onProposedFederationChange(Federation newFederation) { - throw new UnsupportedOperationException(); - } - }); rskListener.onBestBlock(null, null); // Assert - assertEquals(0, activeCalls.get()); - assertEquals(0, retiringCalls.get()); verify(federationProvider).getActiveFederationAddress(); - verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); + verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); + + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); } @Test - void whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange() throws Exception { + void onBestBlock_whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange var rskListener = setupAndGetRskListener(SECOND_FEDERATION, null); - var activeCalls = new AtomicInteger(0); - var retiringCalls = new AtomicInteger(0); - when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(FIRST_FEDERATION.getAddress())); when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(FIRST_FEDERATION)); + federationWatcher.start(federationProvider, federationWatcherListener); + // Act - federationWatcher.start(federationProvider, new FederationWatcherListener() { - @Override - public void onActiveFederationChange(Federation newFederation) { - activeCalls.incrementAndGet(); - } - - @Override - public void onRetiringFederationChange(Federation newFederation) { - assertEquals(FIRST_FEDERATION, newFederation); - retiringCalls.incrementAndGet(); - } - - @Override - public void onProposedFederationChange(Federation newFederation) { - throw new UnsupportedOperationException(); - } - }); rskListener.onBestBlock(null, null); // Assert - assertEquals(0, activeCalls.get()); - assertEquals(1, retiringCalls.get()); verify(federationProvider).getActiveFederationAddress(); - verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); + verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); + + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider).getRetiringFederation(); + verify(federationWatcherListener).onRetiringFederationChange(FIRST_FEDERATION); } @Test - void whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() throws Exception { + void onBestBlock_whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() throws Exception { // Arrange var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION); - var activeCalls = new AtomicInteger(0); - var retiringCalls = new AtomicInteger(0); - when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getRetiringFederation()).thenReturn(Optional.empty()); + federationWatcher.start(federationProvider, federationWatcherListener); + // Act - federationWatcher.start(federationProvider, new FederationWatcherListener() { - @Override - public void onActiveFederationChange(Federation newFederation) { - activeCalls.incrementAndGet(); - } - - @Override - public void onRetiringFederationChange(Federation newFederation) { - assertNull(newFederation); - retiringCalls.incrementAndGet(); - } - - @Override - public void onProposedFederationChange(Federation newFederation) { - throw new UnsupportedOperationException(); - } - }); rskListener.onBestBlock(null, null); // Assert - assertEquals(0, activeCalls.get()); - assertEquals(1, retiringCalls.get()); verify(federationProvider).getActiveFederationAddress(); - verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); + verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); + + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider).getRetiringFederation(); + verify(federationWatcherListener).onRetiringFederationChange(null); } @Test - void whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederationChange() throws Exception { + void onBestBlock_whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange var rskListener = setupAndGetRskListener(THIRD_FEDERATION, FIRST_FEDERATION); - var activeCalls = new AtomicInteger(0); - var retiringCalls = new AtomicInteger(0); - when(federationProvider.getActiveFederationAddress()).thenReturn(THIRD_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(SECOND_FEDERATION)); + federationWatcher.start(federationProvider, federationWatcherListener); + // Act - federationWatcher.start(federationProvider, new FederationWatcherListener() { - @Override - public void onActiveFederationChange(Federation newFederation) { - activeCalls.incrementAndGet(); - } - - @Override - public void onRetiringFederationChange(Federation newFederation) { - assertEquals(SECOND_FEDERATION, newFederation); - retiringCalls.incrementAndGet(); - } - - @Override - public void onProposedFederationChange(Federation newFederation) { - throw new UnsupportedOperationException(); - } - }); rskListener.onBestBlock(null, null); // Assert - assertEquals(0, activeCalls.get()); - assertEquals(1, retiringCalls.get()); verify(federationProvider).getActiveFederationAddress(); - verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getActiveFederation(); + verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); + + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider).getRetiringFederation(); + verify(federationWatcherListener).onRetiringFederationChange(SECOND_FEDERATION); } private EthereumListenerAdapter setupAndGetRskListener( From 4d27db9993ef2e8a1258b70d0621b4cfc4b4b6e6 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:42:55 +0000 Subject: [PATCH 097/112] feat(watcher): accomdate existing unit tests for proposed fed logic --- .../watcher/FederationWatcherTest.java | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index c6cc303a8..9143cbe35 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -1,8 +1,6 @@ package co.rsk.federate.watcher; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -24,7 +22,6 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.ethereum.crypto.ECKey; import org.ethereum.facade.Ethereum; @@ -68,7 +65,7 @@ class FederationWatcherTest { private final FederationWatcher federationWatcher = new FederationWatcher(rsk); @Test - void onBestBlock_whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { + void start_whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { // Act federationWatcher.start(federationProvider, federationWatcherListener); @@ -81,7 +78,8 @@ void onBestBlock_whenFederationWatcherIsSetUp_shouldAddListener() throws Excepti @Test void onBestBlock_whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(null, null); + var rskListener = setupAndGetRskListener(null, null, null); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getActiveFederation()).thenReturn(FIRST_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); @@ -92,6 +90,10 @@ void onBestBlock_whenNoActiveFederation_shouldTriggerActiveFederationChange() th rskListener.onBestBlock(null, null); // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider, never()).getProposedFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + verify(federationProvider).getActiveFederationAddress(); verify(federationProvider).getRetiringFederationAddress(); verify(federationWatcherListener).onActiveFederationChange(FIRST_FEDERATION); @@ -104,7 +106,8 @@ void onBestBlock_whenNoActiveFederation_shouldTriggerActiveFederationChange() th @Test void onBestBlock_whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null); + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, null); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getActiveFederation()).thenReturn(SECOND_FEDERATION); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); @@ -115,6 +118,10 @@ void onBestBlock_whenActiveFederationChangesFromActiveToOtherActive_shouldTrigge rskListener.onBestBlock(null, null); // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider, never()).getProposedFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + verify(federationProvider).getActiveFederationAddress(); verify(federationProvider).getActiveFederation(); verify(federationWatcherListener).onActiveFederationChange(SECOND_FEDERATION); @@ -127,7 +134,8 @@ void onBestBlock_whenActiveFederationChangesFromActiveToOtherActive_shouldTrigge @Test void onBestBlock_whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null); + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, null); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); @@ -137,6 +145,10 @@ void onBestBlock_whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFe rskListener.onBestBlock(null, null); // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider, never()).getProposedFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + verify(federationProvider).getActiveFederationAddress(); verify(federationProvider, never()).getActiveFederation(); verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); @@ -147,9 +159,10 @@ void onBestBlock_whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFe } @Test - void onBestBlock_whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { + void onBestBlock_whenNoActiveAndRetiringAndProposedChangeInFederation_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, SECOND_FEDERATION); + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, SECOND_FEDERATION, THIRD_FEDERATION); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.of(THIRD_FEDERATION.getAddress())); when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); @@ -159,6 +172,10 @@ void onBestBlock_whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiv rskListener.onBestBlock(null, null); // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider, never()).getProposedFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + verify(federationProvider).getActiveFederationAddress(); verify(federationProvider, never()).getActiveFederation(); verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); @@ -171,7 +188,8 @@ void onBestBlock_whenNoActiveAndRetiringChangeInFederation_shouldNotTriggerActiv @Test void onBestBlock_whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(SECOND_FEDERATION, null); + var rskListener = setupAndGetRskListener(SECOND_FEDERATION, null, null); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(FIRST_FEDERATION.getAddress())); when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(FIRST_FEDERATION)); @@ -182,6 +200,10 @@ void onBestBlock_whenActiveFederationChangesToRetiring_shouldTriggerRetiringFede rskListener.onBestBlock(null, null); // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider, never()).getProposedFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + verify(federationProvider).getActiveFederationAddress(); verify(federationProvider, never()).getActiveFederation(); verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); @@ -194,7 +216,8 @@ void onBestBlock_whenActiveFederationChangesToRetiring_shouldTriggerRetiringFede @Test void onBestBlock_whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION); + var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION, null); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getRetiringFederation()).thenReturn(Optional.empty()); @@ -205,6 +228,10 @@ void onBestBlock_whenRetiringFederationChangesToNone_shouldTriggerRetiringFedera rskListener.onBestBlock(null, null); // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider, never()).getProposedFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + verify(federationProvider).getActiveFederationAddress(); verify(federationProvider, never()).getActiveFederation(); verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); @@ -217,7 +244,8 @@ void onBestBlock_whenRetiringFederationChangesToNone_shouldTriggerRetiringFedera @Test void onBestBlock_whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(THIRD_FEDERATION, FIRST_FEDERATION); + var rskListener = setupAndGetRskListener(THIRD_FEDERATION, FIRST_FEDERATION, null); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(THIRD_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); when(federationProvider.getRetiringFederation()).thenReturn(Optional.of(SECOND_FEDERATION)); @@ -228,6 +256,10 @@ void onBestBlock_whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetir rskListener.onBestBlock(null, null); // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider, never()).getProposedFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + verify(federationProvider).getActiveFederationAddress(); verify(federationProvider, never()).getActiveFederation(); verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); @@ -238,7 +270,7 @@ void onBestBlock_whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetir } private EthereumListenerAdapter setupAndGetRskListener( - Federation activeFederation, Federation retiringFederation) throws Exception { + Federation activeFederation, Federation retiringFederation, Federation proposedFederation) throws Exception { // Mock the behavior of adding a listener AtomicReference listenerRef = new AtomicReference<>(); doAnswer((InvocationOnMock m) -> { @@ -247,9 +279,11 @@ private EthereumListenerAdapter setupAndGetRskListener( }).when(rsk).addListener(any()); federationWatcher.start(federationProvider, null); + // Set up federationWatcher and internal states TestUtils.setInternalState(federationWatcher, "activeFederation", activeFederation); TestUtils.setInternalState(federationWatcher, "retiringFederation", retiringFederation); + TestUtils.setInternalState(federationWatcher, "proposedFederation", proposedFederation); // Retrieve and return the listener EthereumListenerAdapter listener = listenerRef.get(); From 6f4bfb1dafc845c985ddb4c2f66b757b96fca951 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:47:29 +0000 Subject: [PATCH 098/112] feat(watcher): call updateProposedFederation before updateActiveFederation to follow chrono order --- .../federate/watcher/FederationWatcher.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java index 9f1c43b97..d4c01f458 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -84,11 +84,30 @@ public void onBestBlock(org.ethereum.core.Block block, List * the {@code FederationWatcherListener}. */ private void updateState() { + updateProposedFederation(); updateActiveFederation(); updateRetiringFederation(); - updateProposedFederation(); } + private void updateProposedFederation() { + Optional
currentlyProposedFederationAddress = federationProvider.getProposedFederationAddress(); + Optional
oldProposedFederationAddress = Optional.ofNullable(proposedFederation) + .map(Federation::getAddress); + + boolean hasProposedFederationChanged = !currentlyProposedFederationAddress.equals(oldProposedFederationAddress); + + if (hasProposedFederationChanged) { + logger.info("[updateRetiringFederation] Proposed federation changed from {} to {}", + oldProposedFederationAddress.orElse(null), + currentlyProposedFederationAddress.orElse(null)); + + Federation currentlyProposedFederation = federationProvider.getProposedFederation().orElse(null); + + federationWatcherListener.onProposedFederationChange(currentlyProposedFederation); + this.proposedFederation = currentlyProposedFederation; + } + } + private void updateActiveFederation() { Address currentlyActiveFederationAddress = Objects.requireNonNull( federationProvider.getActiveFederationAddress(), "The current active federation should always exist"); @@ -128,23 +147,4 @@ private void updateRetiringFederation() { this.retiringFederation = currentlyRetiringFederation; } } - - private void updateProposedFederation() { - Optional
currentlyProposedFederationAddress = federationProvider.getProposedFederationAddress(); - Optional
oldProposedFederationAddress = Optional.ofNullable(proposedFederation) - .map(Federation::getAddress); - - boolean hasProposedFederationChanged = !currentlyProposedFederationAddress.equals(oldProposedFederationAddress); - - if (hasProposedFederationChanged) { - logger.info("[updateRetiringFederation] Proposed federation changed from {} to {}", - oldProposedFederationAddress.orElse(null), - currentlyProposedFederationAddress.orElse(null)); - - Federation currentlyProposedFederation = federationProvider.getProposedFederation().orElse(null); - - federationWatcherListener.onProposedFederationChange(currentlyProposedFederation); - this.proposedFederation = currentlyProposedFederation; - } - } } From 232bbf7426870f7a3918b87bb498d4447e9105f7 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:19:38 +0000 Subject: [PATCH 099/112] feat(watcher): add unit test for proposed federation in FederationWatcherTest class --- .../watcher/FederationWatcherTest.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index 9143cbe35..3149c9ef1 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -75,6 +75,122 @@ void start_whenFederationWatcherIsSetUp_shouldAddListener() throws Exception { assertSame(TestUtils.getInternalState(federationWatcher, "federationWatcherListener"), federationWatcherListener); } + @Test + void onBestBlock_whenNoActiveFederationAndProposedFederationExists_shouldTriggerActiveAndProposedFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(null, null, null); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); + when(federationProvider.getProposedFederation()).thenReturn(Optional.of(SECOND_FEDERATION)); + when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); + when(federationProvider.getActiveFederation()).thenReturn(FIRST_FEDERATION); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + + federationWatcher.start(federationProvider, federationWatcherListener); + + // Act + rskListener.onBestBlock(null, null); + + // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider).getProposedFederation(); + verify(federationWatcherListener).onProposedFederationChange(SECOND_FEDERATION); + + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getActiveFederation(); + verify(federationWatcherListener).onActiveFederationChange(FIRST_FEDERATION); + + verify(federationProvider).getActiveFederation(); + verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); + } + + @Test + void onBestBlock_whenProposedFederationChangesFromProposedToOtherProposed_shouldTriggerProposedFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, SECOND_FEDERATION); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.of(THIRD_FEDERATION.getAddress())); + when(federationProvider.getProposedFederation()).thenReturn(Optional.of(THIRD_FEDERATION)); + when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + + federationWatcher.start(federationProvider, federationWatcherListener); + + // Act + rskListener.onBestBlock(null, null); + + // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider).getProposedFederation(); + verify(federationWatcherListener).onProposedFederationChange(THIRD_FEDERATION); + + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider, never()).getActiveFederation(); + verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); + + verify(federationProvider).getRetiringFederationAddress(); + verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); + } + + @Test + void onBestBlock_whenProposedFederationChangesFromProposedToNone_shouldTriggerProposedFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, SECOND_FEDERATION); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); + when(federationProvider.getProposedFederation()).thenReturn(Optional.empty()); + when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); + when(federationProvider.getActiveFederation()).thenReturn(FIRST_FEDERATION); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + + federationWatcher.start(federationProvider, federationWatcherListener); + + // Act + rskListener.onBestBlock(null, null); + + // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider).getProposedFederation(); + verify(federationWatcherListener).onProposedFederationChange(null); + + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider, never()).getActiveFederation(); + verify(federationWatcherListener, never()).onActiveFederationChange(any(Federation.class)); + + verify(federationProvider).getRetiringFederationAddress(); + verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); + } + + @Test + void onBestBlock_whenProposedFederationChangesToActive_shouldTriggerActiveAndProposedFederationChange() throws Exception { + // Arrange + var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, SECOND_FEDERATION); + when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); + when(federationProvider.getProposedFederation()).thenReturn(Optional.empty()); + when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); + when(federationProvider.getActiveFederation()).thenReturn(SECOND_FEDERATION); + when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); + when(federationProvider.getRetiringFederation()).thenReturn(Optional.empty()); + + federationWatcher.start(federationProvider, federationWatcherListener); + + // Act + rskListener.onBestBlock(null, null); + + // Assert + verify(federationProvider).getProposedFederationAddress(); + verify(federationProvider).getProposedFederation(); + verify(federationWatcherListener).onProposedFederationChange(null); + + verify(federationProvider).getActiveFederationAddress(); + verify(federationProvider).getActiveFederation(); + verify(federationWatcherListener).onActiveFederationChange(SECOND_FEDERATION); + + verify(federationProvider).getRetiringFederationAddress(); + verify(federationProvider, never()).getRetiringFederation(); + verify(federationWatcherListener, never()).onProposedFederationChange(any(Federation.class)); + } + @Test void onBestBlock_whenNoActiveFederation_shouldTriggerActiveFederationChange() throws Exception { // Arrange From 85dc6fc7c3cc2e93d3679ab3cd48d4955553391c Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:25:27 +0000 Subject: [PATCH 100/112] refactor(wathcer): log statement for onProposedFederationChange --- .../co/rsk/federate/watcher/FederationWatcherListenerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index 157f010dd..b4aa0c73a 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -43,7 +43,7 @@ public void onRetiringFederationChange(Federation newRetiringFederation) { public void onProposedFederationChange(Federation newProposedFederation) { if (newProposedFederation == null) { logger.info( - "[onProposedFederationChange] New proposed federation changed to null"); + "[onProposedFederationChange] New proposed federation changed to none"); return; } From 3a292fa6e65f8c1c02139e975665065a23cf9d90 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:41:30 +0000 Subject: [PATCH 101/112] refactor(watcher): test naming and log statements --- .../FederationWatcherListenerImpl.java | 2 +- .../watcher/FederationWatcherTest.java | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index b4aa0c73a..53a648d44 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -43,7 +43,7 @@ public void onRetiringFederationChange(Federation newRetiringFederation) { public void onProposedFederationChange(Federation newProposedFederation) { if (newProposedFederation == null) { logger.info( - "[onProposedFederationChange] New proposed federation changed to none"); + "[onProposedFederationChange] Proposed federation was cleared"); return; } diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java index 3149c9ef1..28b0b02ae 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherTest.java @@ -99,15 +99,15 @@ void onBestBlock_whenNoActiveFederationAndProposedFederationExists_shouldTrigger verify(federationProvider).getActiveFederation(); verify(federationWatcherListener).onActiveFederationChange(FIRST_FEDERATION); - verify(federationProvider).getActiveFederation(); + verify(federationProvider).getRetiringFederationAddress(); verify(federationProvider, never()).getRetiringFederation(); verify(federationWatcherListener, never()).onRetiringFederationChange(any(Federation.class)); } @Test - void onBestBlock_whenProposedFederationChangesFromProposedToOtherProposed_shouldTriggerProposedFederationChange() throws Exception { + void onBestBlock_whenProposedFederationChanged_shouldTriggerProposedFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, SECOND_FEDERATION); + var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION, null); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.of(THIRD_FEDERATION.getAddress())); when(federationProvider.getProposedFederation()).thenReturn(Optional.of(THIRD_FEDERATION)); when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); @@ -133,9 +133,9 @@ void onBestBlock_whenProposedFederationChangesFromProposedToOtherProposed_should } @Test - void onBestBlock_whenProposedFederationChangesFromProposedToNone_shouldTriggerProposedFederationChange() throws Exception { + void onBestBlock_whenProposedFederationIsCleared_shouldTriggerProposedFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, SECOND_FEDERATION); + var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION, null); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getProposedFederation()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); @@ -164,7 +164,7 @@ void onBestBlock_whenProposedFederationChangesFromProposedToNone_shouldTriggerPr @Test void onBestBlock_whenProposedFederationChangesToActive_shouldTriggerActiveAndProposedFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, SECOND_FEDERATION); + var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION, null); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getProposedFederation()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); @@ -222,7 +222,7 @@ void onBestBlock_whenNoActiveFederation_shouldTriggerActiveFederationChange() th @Test void onBestBlock_whenActiveFederationChangesFromActiveToOtherActive_shouldTriggerActiveFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, null); + var rskListener = setupAndGetRskListener(null, FIRST_FEDERATION, null); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getActiveFederation()).thenReturn(SECOND_FEDERATION); @@ -248,9 +248,9 @@ void onBestBlock_whenActiveFederationChangesFromActiveToOtherActive_shouldTrigge } @Test - void onBestBlock_whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { + void onBestBlock_whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringOrProposedFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, null, null); + var rskListener = setupAndGetRskListener(null, FIRST_FEDERATION, null); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); @@ -275,9 +275,9 @@ void onBestBlock_whenNoActiveFederationChange_shouldNotTriggerActiveOrRetiringFe } @Test - void onBestBlock_whenNoActiveAndRetiringAndProposedChangeInFederation_shouldNotTriggerActiveOrRetiringFederationChange() throws Exception { + void onBestBlock_whenNoActiveAndRetiringAndProposedChangeInFederation_shouldNotTriggerActiveOrRetiringOrProposedFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(FIRST_FEDERATION, SECOND_FEDERATION, THIRD_FEDERATION); + var rskListener = setupAndGetRskListener(THIRD_FEDERATION, FIRST_FEDERATION, SECOND_FEDERATION); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.of(THIRD_FEDERATION.getAddress())); when(federationProvider.getActiveFederationAddress()).thenReturn(FIRST_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); @@ -304,7 +304,7 @@ void onBestBlock_whenNoActiveAndRetiringAndProposedChangeInFederation_shouldNotT @Test void onBestBlock_whenActiveFederationChangesToRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(SECOND_FEDERATION, null, null); + var rskListener = setupAndGetRskListener(null, SECOND_FEDERATION, null); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(FIRST_FEDERATION.getAddress())); @@ -332,7 +332,7 @@ void onBestBlock_whenActiveFederationChangesToRetiring_shouldTriggerRetiringFede @Test void onBestBlock_whenRetiringFederationChangesToNone_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(SECOND_FEDERATION, FIRST_FEDERATION, null); + var rskListener = setupAndGetRskListener(null, SECOND_FEDERATION, FIRST_FEDERATION); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(SECOND_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.empty()); @@ -360,7 +360,7 @@ void onBestBlock_whenRetiringFederationChangesToNone_shouldTriggerRetiringFedera @Test void onBestBlock_whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetiringFederationChange() throws Exception { // Arrange - var rskListener = setupAndGetRskListener(THIRD_FEDERATION, FIRST_FEDERATION, null); + var rskListener = setupAndGetRskListener(null, THIRD_FEDERATION, FIRST_FEDERATION); when(federationProvider.getProposedFederationAddress()).thenReturn(Optional.empty()); when(federationProvider.getActiveFederationAddress()).thenReturn(THIRD_FEDERATION.getAddress()); when(federationProvider.getRetiringFederationAddress()).thenReturn(Optional.of(SECOND_FEDERATION.getAddress())); @@ -386,7 +386,7 @@ void onBestBlock_whenRetiringFederationChangesToOtherRetiring_shouldTriggerRetir } private EthereumListenerAdapter setupAndGetRskListener( - Federation activeFederation, Federation retiringFederation, Federation proposedFederation) throws Exception { + Federation proposedFederation, Federation activeFederation, Federation retiringFederation) throws Exception { // Mock the behavior of adding a listener AtomicReference listenerRef = new AtomicReference<>(); doAnswer((InvocationOnMock m) -> { @@ -397,9 +397,9 @@ private EthereumListenerAdapter setupAndGetRskListener( federationWatcher.start(federationProvider, null); // Set up federationWatcher and internal states + TestUtils.setInternalState(federationWatcher, "proposedFederation", proposedFederation); TestUtils.setInternalState(federationWatcher, "activeFederation", activeFederation); TestUtils.setInternalState(federationWatcher, "retiringFederation", retiringFederation); - TestUtils.setInternalState(federationWatcher, "proposedFederation", proposedFederation); // Retrieve and return the listener EthereumListenerAdapter listener = listenerRef.get(); From 3f5f5b92afa93781b853f9ecf7b9be70474b09eb Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:35:25 +0000 Subject: [PATCH 102/112] fix(federate): add RSKIP419 check for proposed federation methods --- ...ederationProviderFromFederatorSupport.java | 9 +++- ...ationProviderFromFederatorSupportTest.java | 44 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java index 9eb9357b9..9dbe78598 100644 --- a/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederationProviderFromFederatorSupport.java @@ -20,6 +20,7 @@ import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; import static co.rsk.peg.federation.FederationMember.KeyType; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP419; import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; @@ -143,6 +144,10 @@ public Optional
getRetiringFederationAddress() { @Override public Optional getProposedFederation() { + if (!federatorSupport.getConfigForBestBlock().isActive(RSKIP419)) { + return Optional.empty(); + } + Integer federationSize = federatorSupport.getProposedFederationSize() .orElse(FEDERATION_NON_EXISTENT.getCode()); if (federationSize == FEDERATION_NON_EXISTENT.getCode()) { @@ -181,7 +186,9 @@ public Optional getProposedFederation() { @Override public Optional
getProposedFederationAddress() { - return federatorSupport.getProposedFederationAddress(); + return Optional.of(federatorSupport) + .filter(fedSupport -> fedSupport.getConfigForBestBlock().isActive(RSKIP419)) + .flatMap(FederatorSupport::getProposedFederationAddress); } private Federation getExpectedFederation(Federation initialFederation, Address expectedFederationAddress) { diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 50842c871..643522f32 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -3,6 +3,7 @@ import static co.rsk.peg.federation.FederationChangeResponseCode.FEDERATION_NON_EXISTENT; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP123; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP284; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP419; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -374,6 +375,9 @@ void getRetiringFederation_present_p2sh_erp_federation() { @Test void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnEmptyOptional() { // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationSize()) .thenReturn(Optional.of(FEDERATION_NON_EXISTENT.getCode())); @@ -385,6 +389,9 @@ void getProposedFederation_whenProposedFederationSizeIsNonExistent_shouldReturnE @Test void getProposedFederation_whenSomeDataDoesNotExists_shouldThrowIllegalStateException() { // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); Federation expectedFederation = createP2shErpFederation( getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000)); Address expectedFederationAddress = expectedFederation.getAddress(); @@ -404,6 +411,9 @@ void getProposedFederation_whenSomeDataDoesNotExists_shouldThrowIllegalStateExce @Test void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposedFederation() { // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); Federation expectedFederation = createP2shErpFederation( getFederationMembersFromPks(1, 1000, 2000, 3000, 4000, 5000)); Address expectedFederationAddress = expectedFederation.getAddress(); @@ -432,9 +442,26 @@ void getProposedFederation_whenExistsAndIsP2shErpFederation_shouldReturnProposed assertEquals(expectedFederationAddress, proposedFederation.get().getAddress()); } + @Test + void getProposedFederation_whenRSKIP419IsNotActivated_shouldReturnEmptyOptional() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(false); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + + // Act + Optional result = federationProvider.getProposedFederation(); + + // Assert + assertFalse(result.isPresent()); + } + @Test void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(DEFAULT_ADDRESS)); // Act @@ -448,6 +475,9 @@ void getProposedFederationAddress_whenAddressExists_shouldReturnAddress() { @Test void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional() { // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(true); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); when(federatorSupportMock.getProposedFederationAddress()).thenReturn(Optional.empty()); // Act @@ -456,6 +486,20 @@ void getProposedFederationAddress_whenNoAddressExists_shouldReturnEmptyOptional( // Assert assertFalse(result.isPresent()); } + + @Test + void getProposedFederationAddress_whenRSKIP419IsNotActivated_shouldReturnEmptyOptional() { + // Arrange + ActivationConfig.ForBlock configMock = mock(ActivationConfig.ForBlock.class); + when(configMock.isActive(RSKIP419)).thenReturn(false); + when(federatorSupportMock.getConfigForBestBlock()).thenReturn(configMock); + + // Act + Optional
result = federationProvider.getProposedFederationAddress(); + + // Assert + assertFalse(result.isPresent()); + } private Federation createFederation(List members) { FederationArgs federationArgs = new FederationArgs(members, creationTime, 0L, testnetParams); From 72a5e058471a74a089d00fefcfcd09234c5bb709 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 5 Dec 2024 13:52:35 -0300 Subject: [PATCH 103/112] Adapt to time unit bridge method change --- .../co/rsk/federate/FederatorSupport.java | 14 ++++- ...ationProviderFromFederatorSupportTest.java | 2 +- .../co/rsk/federate/FederatorSupportTest.java | 63 +++++++++++++++++-- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/main/java/co/rsk/federate/FederatorSupport.java b/src/main/java/co/rsk/federate/FederatorSupport.java index 8f37e9d4e..a271797f1 100644 --- a/src/main/java/co/rsk/federate/FederatorSupport.java +++ b/src/main/java/co/rsk/federate/FederatorSupport.java @@ -16,6 +16,7 @@ import org.bitcoinj.core.PeerAddress; import org.bitcoinj.core.Sha256Hash; import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.Blockchain; import org.ethereum.crypto.ECKey; import org.slf4j.Logger; @@ -190,7 +191,11 @@ public ECKey getFederatorPublicKeyOfType(int index, FederationMember.KeyType key public Instant getFederationCreationTime() { BigInteger federationCreationTime = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_FEDERATION_CREATION_TIME); - return Instant.ofEpochMilli(federationCreationTime.longValue()); + + if (!getConfigForBestBlock().isActive(ConsensusRule.RSKIP419)) { + return Instant.ofEpochMilli(federationCreationTime.longValue()); + } + return Instant.ofEpochSecond(federationCreationTime.longValue()); } public long getFederationCreationBlockNumber() { @@ -248,12 +253,15 @@ public ECKey getRetiringFederatorPublicKeyOfType(int index, FederationMember.Key } public Instant getRetiringFederationCreationTime() { - BigInteger creationTime = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_FEDERATION_CREATION_TIME); + BigInteger creationTime = this.bridgeTransactionSender.callTx(federatorAddress, Bridge.GET_RETIRING_FEDERATION_CREATION_TIME); if (creationTime == null) { return null; } - return Instant.ofEpochMilli(creationTime.longValue()); + if (!getConfigForBestBlock().isActive(ConsensusRule.RSKIP419)) { + return Instant.ofEpochMilli(creationTime.longValue()); + } + return Instant.ofEpochSecond(creationTime.longValue()); } public Optional
getProposedFederationAddress() { diff --git a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java index 643522f32..8d2ee535c 100644 --- a/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederationProviderFromFederatorSupportTest.java @@ -66,7 +66,7 @@ void createProvider() { federationConstants ); testnetParams = NetworkParameters.fromID(NetworkParameters.ID_TESTNET); - creationTime = Instant.ofEpochMilli(5005L); + creationTime = Instant.ofEpochSecond(5); } @Test diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index 37e388736..b3bebc998 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -1,9 +1,6 @@ package co.rsk.federate; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; @@ -28,7 +25,6 @@ import co.rsk.peg.BridgeMethods; import co.rsk.peg.StateForProposedFederator; import java.util.AbstractMap; -import co.rsk.federate.bitcoin.BitcoinTestUtils; import java.math.BigInteger; import java.time.Instant; import java.util.ArrayList; @@ -362,6 +358,63 @@ void getProposedFederationCreationBlockNumber_whenBlockNumberIsValid_shouldRetur assertEquals(expectedBlockNumber.longValue(), result.get()); } + @Test + void getActiveFederationCreationTime_whenCreationTimeIsValid_shouldReturnInstantFromSeconds() { + // Arrange + BigInteger expectedCreationTime = BigInteger.valueOf(System.currentTimeMillis()); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_FEDERATION_CREATION_TIME))) + .thenReturn(expectedCreationTime); + + // all rskips are activated since block 0 + org.ethereum.core.Block mockBlock = mock(org.ethereum.core.Block.class); + when(mockBlock.getNumber()).thenReturn(1L); + Blockchain blockchain = mock(Blockchain.class); + when(blockchain.getBestBlock()).thenReturn(mockBlock); + + federatorSupport = new FederatorSupport(blockchain, new TestSystemProperties(), bridgeTransactionSender); + + // Act + Instant result = federatorSupport.getFederationCreationTime(); + + // Assert + assertEquals(expectedCreationTime.longValue(), result.getEpochSecond()); + } + + @Test + void getRetiringFederationCreationTime_whenCreationTimeIsNull_shouldReturnNull() { + // Arrange + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_RETIRING_FEDERATION_CREATION_TIME))) + .thenReturn(null); + + // Act + Instant result = federatorSupport.getRetiringFederationCreationTime(); + + // Assert + assertNull(result); + } + + @Test + void getRetiringFederationCreationTime_whenCreationTimeIsValid_shouldReturnInstantFromSeconds() { + // Arrange + BigInteger expectedCreationTime = BigInteger.valueOf(System.currentTimeMillis()); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_RETIRING_FEDERATION_CREATION_TIME))) + .thenReturn(expectedCreationTime); + + // all rskips are activated since block 0 + org.ethereum.core.Block mockBlock = mock(org.ethereum.core.Block.class); + when(mockBlock.getNumber()).thenReturn(1L); + Blockchain blockchain = mock(Blockchain.class); + when(blockchain.getBestBlock()).thenReturn(mockBlock); + + federatorSupport = new FederatorSupport(blockchain, new TestSystemProperties(), bridgeTransactionSender); + + // Act + Instant result = federatorSupport.getRetiringFederationCreationTime(); + + // Assert + assertEquals(expectedCreationTime.longValue(), result.getEpochSecond()); + } + private Sha256Hash createHash() { byte[] bytes = new byte[32]; bytes[0] = (byte) 1; From 35649ec17f4a21680ad289ad32c838d24193e980 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Fri, 6 Dec 2024 09:53:51 -0300 Subject: [PATCH 104/112] Add tests pre rskip 419 --- .../co/rsk/federate/FederatorSupportTest.java | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/test/java/co/rsk/federate/FederatorSupportTest.java b/src/test/java/co/rsk/federate/FederatorSupportTest.java index b3bebc998..ddcbd0e0b 100644 --- a/src/test/java/co/rsk/federate/FederatorSupportTest.java +++ b/src/test/java/co/rsk/federate/FederatorSupportTest.java @@ -359,7 +359,7 @@ void getProposedFederationCreationBlockNumber_whenBlockNumberIsValid_shouldRetur } @Test - void getActiveFederationCreationTime_whenCreationTimeIsValid_shouldReturnInstantFromSeconds() { + void getActiveFederationCreationTime_whenCreationTimeIsValid_preRSKIP419_shouldReturnInstantFromMillis() { // Arrange BigInteger expectedCreationTime = BigInteger.valueOf(System.currentTimeMillis()); when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_FEDERATION_CREATION_TIME))) @@ -367,7 +367,29 @@ void getActiveFederationCreationTime_whenCreationTimeIsValid_shouldReturnInstant // all rskips are activated since block 0 org.ethereum.core.Block mockBlock = mock(org.ethereum.core.Block.class); - when(mockBlock.getNumber()).thenReturn(1L); + when(mockBlock.getNumber()).thenReturn(-1L); + Blockchain blockchain = mock(Blockchain.class); + when(blockchain.getBestBlock()).thenReturn(mockBlock); + + federatorSupport = new FederatorSupport(blockchain, new TestSystemProperties(), bridgeTransactionSender); + + // Act + Instant result = federatorSupport.getFederationCreationTime(); + + // Assert + assertEquals(expectedCreationTime.longValue(), result.toEpochMilli()); + } + + @Test + void getActiveFederationCreationTime_whenCreationTimeIsValid_postRSKIP419_shouldReturnInstantFromSeconds() { + // Arrange + BigInteger expectedCreationTime = BigInteger.valueOf(System.currentTimeMillis()); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_FEDERATION_CREATION_TIME))) + .thenReturn(expectedCreationTime); + + // all rskips are activated since block 0 + org.ethereum.core.Block mockBlock = mock(org.ethereum.core.Block.class); + when(mockBlock.getNumber()).thenReturn(0L); Blockchain blockchain = mock(Blockchain.class); when(blockchain.getBestBlock()).thenReturn(mockBlock); @@ -394,7 +416,29 @@ void getRetiringFederationCreationTime_whenCreationTimeIsNull_shouldReturnNull() } @Test - void getRetiringFederationCreationTime_whenCreationTimeIsValid_shouldReturnInstantFromSeconds() { + void getRetiringFederationCreationTime_whenCreationTimeIsValid_preRSKIP419_shouldReturnInstantFromMillis() { + // Arrange + BigInteger expectedCreationTime = BigInteger.valueOf(System.currentTimeMillis()); + when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_RETIRING_FEDERATION_CREATION_TIME))) + .thenReturn(expectedCreationTime); + + // all rskips are activated since block 0 + org.ethereum.core.Block mockBlock = mock(org.ethereum.core.Block.class); + when(mockBlock.getNumber()).thenReturn(-1L); + Blockchain blockchain = mock(Blockchain.class); + when(blockchain.getBestBlock()).thenReturn(mockBlock); + + federatorSupport = new FederatorSupport(blockchain, new TestSystemProperties(), bridgeTransactionSender); + + // Act + Instant result = federatorSupport.getRetiringFederationCreationTime(); + + // Assert + assertEquals(expectedCreationTime.longValue(), result.toEpochMilli()); + } + + @Test + void getRetiringFederationCreationTime_whenCreationTimeIsValid_postRSKIP419_shouldReturnInstantFromSeconds() { // Arrange BigInteger expectedCreationTime = BigInteger.valueOf(System.currentTimeMillis()); when(bridgeTransactionSender.callTx(any(), eq(Bridge.GET_RETIRING_FEDERATION_CREATION_TIME))) @@ -402,7 +446,7 @@ void getRetiringFederationCreationTime_whenCreationTimeIsValid_shouldReturnInsta // all rskips are activated since block 0 org.ethereum.core.Block mockBlock = mock(org.ethereum.core.Block.class); - when(mockBlock.getNumber()).thenReturn(1L); + when(mockBlock.getNumber()).thenReturn(0L); Blockchain blockchain = mock(Blockchain.class); when(blockchain.getBestBlock()).thenReturn(mockBlock); From b19f6eeea5714efe3faf17c41c02e6da6aa9bbff Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:16:08 +0000 Subject: [PATCH 105/112] fix(btcreleaseclient): place call to get svp spend tx wfs behind activation check --- .../btcreleaseclient/BtcReleaseClient.java | 10 ++++++---- .../btcreleaseclient/BtcReleaseClientTest.java | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java index 70b3b564a..614bc449e 100644 --- a/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java +++ b/src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java @@ -240,10 +240,12 @@ public void onBestBlock(org.ethereum.core.Block block, List // Sign svp spend tx waiting for signatures, if it exists, // before attempting to sign any pegouts. - federatorSupport.getStateForProposedFederator() - .map(StateForProposedFederator::getSvpSpendTxWaitingForSignatures) - .filter(svpSpendTxWaitingForSignatures -> isSVPSpendTxReadyToSign(block.getNumber(), svpSpendTxWaitingForSignatures)) - .ifPresent(svpSpendTxReadyToBeSigned -> processReleases(Set.of(svpSpendTxReadyToBeSigned))); + if (activationConfig.isActive(ConsensusRule.RSKIP419, block.getNumber())) { + federatorSupport.getStateForProposedFederator() + .map(StateForProposedFederator::getSvpSpendTxWaitingForSignatures) + .filter(svpSpendTxWaitingForSignatures -> isSVPSpendTxReadyToSign(block.getNumber(), svpSpendTxWaitingForSignatures)) + .ifPresent(svpSpendTxReadyToBeSigned -> processReleases(Set.of(svpSpendTxReadyToBeSigned))); + } // Processing transactions waiting for signatures on best block only still "works", // since it all lies within RSK's blockchain and normal rules apply. I.e., this diff --git a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java index 5b4c76968..27315a936 100644 --- a/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java +++ b/src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java @@ -80,7 +80,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.Stream; - +import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.core.Block; @@ -740,9 +740,12 @@ void onBestBlock_whenOnlySvpSpendTxWaitingForSignaturesIsAvailable_shouldAddSign mock(NodeBlockProcessor.class) ); + ActivationConfig activationConfig = mock(ActivationConfig.class); + when(activationConfig.isActive(ConsensusRule.RSKIP419, bestBlock.getNumber())).thenReturn(true); + btcReleaseClient.setup( signer, - mock(ActivationConfig.class), + activationConfig, signerMessageBuilderFactory, releaseCreationInformationGetter, mock(ReleaseRequirementsEnforcer.class), @@ -853,9 +856,12 @@ void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesAreAvailableAndF mock(NodeBlockProcessor.class) ); + ActivationConfig activationConfig = mock(ActivationConfig.class); + when(activationConfig.isActive(ConsensusRule.RSKIP419, bestBlock.getNumber())).thenReturn(true); + btcReleaseClient.setup( signer, - mock(ActivationConfig.class), + activationConfig, signerMessageBuilderFactory, releaseCreationInformationGetter, mock(ReleaseRequirementsEnforcer.class), @@ -971,9 +977,12 @@ void onBestBlock_whenBothPegoutAndSvpSpendTxWaitingForSignaturesIsAvailable_shou mock(NodeBlockProcessor.class) ); + ActivationConfig activationConfig = mock(ActivationConfig.class); + when(activationConfig.isActive(ConsensusRule.RSKIP419, bestBlock.getNumber())).thenReturn(true); + btcReleaseClient.setup( signer, - mock(ActivationConfig.class), + activationConfig, signerMessageBuilderFactory, releaseCreationInformationGetter, mock(ReleaseRequirementsEnforcer.class), From 42745d45661c6328d5ea781ab5a63f009a350bea Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:53:40 +0000 Subject: [PATCH 106/112] fix(watcher): add correct log prefix --- src/main/java/co/rsk/federate/watcher/FederationWatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java index d4c01f458..99880ec75 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcher.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcher.java @@ -97,7 +97,7 @@ private void updateProposedFederation() { boolean hasProposedFederationChanged = !currentlyProposedFederationAddress.equals(oldProposedFederationAddress); if (hasProposedFederationChanged) { - logger.info("[updateRetiringFederation] Proposed federation changed from {} to {}", + logger.info("[updateProposedFederation] Proposed federation changed from {} to {}", oldProposedFederationAddress.orElse(null), currentlyProposedFederationAddress.orElse(null)); From 2c7efb89465ce431f5ed47ab55055c156205ce54 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:56:38 +0000 Subject: [PATCH 107/112] feat(bitcoin): add logic to detect when processing a svp spend tx --- .../federate/bitcoin/BitcoinWrapperImpl.java | 103 ++++++++++++------ 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java b/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java index c8665f919..194761012 100644 --- a/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java +++ b/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java @@ -1,6 +1,9 @@ package co.rsk.federate.bitcoin; import co.rsk.bitcoinj.core.BtcTransaction; +import co.rsk.bitcoinj.core.TransactionInput; +import co.rsk.bitcoinj.core.TransactionOutPoint; +import co.rsk.bitcoinj.core.TransactionOutput; import co.rsk.bitcoinj.wallet.Wallet; import co.rsk.peg.constants.BridgeConstants; import co.rsk.federate.FederatorSupport; @@ -20,6 +23,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import org.bitcoinj.core.Address; import org.bitcoinj.core.Context; @@ -316,43 +320,74 @@ public void removeNewBestBlockListener(NewBestBlockListener newBestBlockListener } protected void coinsReceivedOrSent(Transaction tx) { - if (watchedFederations.size() > 0) { - LOGGER.debug("[coinsReceivedOrSent] Received filtered transaction {}", tx.getWTxId().toString()); - Context.propagate(btcContext); - // Wrap tx in a co.rsk.bitcoinj.core.BtcTransaction - BtcTransaction btcTx = ThinConverter.toThinInstance(bridgeConstants.getBtcParams(), tx); - co.rsk.bitcoinj.core.Context btcContextThin = ThinConverter.toThinInstance(btcContext); - for (FederationListener watched : watchedFederations) { - Federation watchedFederation = watched.getFederation(); - TransactionListener listener = watched.getListener(); - Wallet watchedFederationWallet = new BridgeBtcWallet(btcContextThin, Collections.singletonList(watchedFederation)); - if (PegUtilsLegacy.isValidPegInTx(btcTx, watchedFederation, watchedFederationWallet, bridgeConstants, federatorSupport.getConfigForBestBlock())) { - - PeginInformation peginInformation = new PeginInformation( - btcLockSenderProvider, - peginInstructionsProvider, - federatorSupport.getConfigForBestBlock() - ); - try { - peginInformation.parse(btcTx); - } catch (PeginInstructionsException e) { - // If tx sender could be retrieved then let the Bridge process the tx and refund the sender - if (peginInformation.getSenderBtcAddress() != null) { - LOGGER.debug("[coinsReceivedOrSent] [btctx:{}] is not a valid lock tx, funds will be refunded to sender", tx.getWTxId()); - } else { - LOGGER.debug("[coinsReceivedOrSent] [btctx:{}] is not a valid lock tx and won't be processed!", tx.getWTxId()); - continue; - } - } + if (watchedFederations.isEmpty()) { + return; + } - LOGGER.debug("[coinsReceivedOrSent] [btctx:{}] is a lock", tx.getWTxId()); - listener.onTransaction(tx); - } - if (PegUtilsLegacy.isPegOutTx(btcTx, Collections.singletonList(watchedFederation), federatorSupport.getConfigForBestBlock())) { - LOGGER.debug("[coinsReceivedOrSent] [btctx with hash {} and witness hash {}] is a pegout", tx.getTxId(), tx.getWTxId()); - listener.onTransaction(tx); + LOGGER.debug("[coinsReceivedOrSent] Received filtered transaction {}", tx.getWTxId().toString()); + Context.propagate(btcContext); + + // Wrap tx in a co.rsk.bitcoinj.core.BtcTransaction + BtcTransaction btcTx = ThinConverter.toThinInstance(bridgeConstants.getBtcParams(), tx); + co.rsk.bitcoinj.core.Context btcContextThin = ThinConverter.toThinInstance(btcContext); + + for (FederationListener watched : watchedFederations) { + Federation watchedFederation = watched.getFederation(); + TransactionListener listener = watched.getListener(); + Wallet watchedFederationWallet = new BridgeBtcWallet(btcContextThin, Collections.singletonList(watchedFederation)); + + if (isTheSvpSpendTx(btcTx)) { + LOGGER.debug("[coinsReceivedOrSent] [btctx with hash {} and witness hash {}] is a svp spend tx", tx.getTxId(), tx.getWTxId()); + listener.onTransaction(tx); + } + + if (PegUtilsLegacy.isValidPegInTx(btcTx, watchedFederation, watchedFederationWallet, bridgeConstants, federatorSupport.getConfigForBestBlock())) { + PeginInformation peginInformation = new PeginInformation( + btcLockSenderProvider, + peginInstructionsProvider, + federatorSupport.getConfigForBestBlock() + ); + + try { + peginInformation.parse(btcTx); + } catch (PeginInstructionsException e) { + // If tx sender could be retrieved then let the Bridge process the tx and refund the sender + if (peginInformation.getSenderBtcAddress() != null) { + LOGGER.debug("[coinsReceivedOrSent] [btctx:{}] is not a valid lock tx, funds will be refunded to sender", tx.getWTxId()); + } else { + LOGGER.debug("[coinsReceivedOrSent] [btctx:{}] is not a valid lock tx and won't be processed!", tx.getWTxId()); + continue; + } } + + LOGGER.debug("[coinsReceivedOrSent] [btctx:{}] is a lock", tx.getWTxId()); + listener.onTransaction(tx); + } + + if (PegUtilsLegacy.isPegOutTx(btcTx, Collections.singletonList(watchedFederation), federatorSupport.getConfigForBestBlock())) { + LOGGER.debug("[coinsReceivedOrSent] [btctx with hash {} and witness hash {}] is a pegout", tx.getTxId(), tx.getWTxId()); + listener.onTransaction(tx); } } } + + private boolean isTheSvpSpendTx(BtcTransaction btcTx) { + return federatorSupport.getProposedFederationAddress() + .map(proposedFedAddress -> + btcTx.getInputs().stream() + .map(input -> getAddressFromInput(input)) + .filter(Optional::isPresent) + .map(Optional::get) + .anyMatch(proposedFedAddress::equals) + ) + .orElse(false); // Return false if no proposed address is present + } + + private Optional getAddressFromInput(TransactionInput input) { + return Optional.ofNullable(input) + .map(TransactionInput::getOutpoint) + .map(TransactionOutPoint::getConnectedOutput) + .map(TransactionOutput::getScriptPubKey) + .map(scriptPubKey -> scriptPubKey.getToAddress(bridgeConstants.getBtcParams())); + } } From cfbce557363b2484285ba1eb0d637307698ab987 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:04:09 +0000 Subject: [PATCH 108/112] refactor(federate): add proper spacing between code segments --- src/main/java/co/rsk/federate/BtcToRskClient.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/co/rsk/federate/BtcToRskClient.java b/src/main/java/co/rsk/federate/BtcToRskClient.java index 7add9ae4b..8b92e2caa 100644 --- a/src/main/java/co/rsk/federate/BtcToRskClient.java +++ b/src/main/java/co/rsk/federate/BtcToRskClient.java @@ -180,13 +180,15 @@ public void updateBridge() { if (federation == null) { logger.warn("[updateBridge] updateBridge skipped because no Federation is associated to this BtcToRskClient"); } + if (nodeBlockProcessor.hasBetterBlockToSync()) { logger.warn("[updateBridge] updateBridge skipped because the node is syncing blocks"); return; } + logger.debug("[updateBridge] Updating bridge"); - if(shouldUpdateBridgeBtcBlockchain) { + if (shouldUpdateBridgeBtcBlockchain) { // Call receiveHeaders try { int numberOfBlocksSent = updateBridgeBtcBlockchain(); @@ -197,7 +199,7 @@ public void updateBridge() { } } - if(shouldUpdateBridgeBtcCoinbaseTransactions) { + if (shouldUpdateBridgeBtcCoinbaseTransactions) { // Call registerBtcCoinbaseTransaction try { logger.debug("[updateBridge] Updating transactions and sending update"); @@ -208,7 +210,7 @@ public void updateBridge() { } } - if(shouldUpdateBridgeBtcTransactions) { + if (shouldUpdateBridgeBtcTransactions) { // Call registerBtcTransaction try { logger.debug("[updateBridge] Updating transactions and sending update"); @@ -219,7 +221,7 @@ public void updateBridge() { } } - if(shouldUpdateCollections) { + if (shouldUpdateCollections) { // Call updateCollections try { logger.debug("[updateBridge] Sending updateCollections"); From ad7dbb1b79fa57754a254efc8c3c75d49a9170c6 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:12:42 +0000 Subject: [PATCH 109/112] refactor(federate): move code segments in BtcToRskClient --- .../java/co/rsk/federate/BtcToRskClient.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/java/co/rsk/federate/BtcToRskClient.java b/src/main/java/co/rsk/federate/BtcToRskClient.java index 8b92e2caa..5ca0b42b3 100644 --- a/src/main/java/co/rsk/federate/BtcToRskClient.java +++ b/src/main/java/co/rsk/federate/BtcToRskClient.java @@ -111,9 +111,9 @@ public synchronized void setup( public void start(Federation federation) { logger.info("[start] Starting for Federation {}", federation.getAddress()); this.federation = federation; + FederationMember federator = federatorSupport.getFederationMember(); boolean isMember = federation.isMember(federator); - if (!isMember) { String message = String.format( "Member %s is no part of the federation %s", @@ -128,10 +128,9 @@ public void start(Federation federation) { logger.info("[start] Watching federation {} since I belong to it", federation.getAddress()); bitcoinWrapper.addFederationListener(federation, this); - Optional federatorIndex = federation.getBtcPublicKeyIndex( - federatorSupport.getFederationMember().getBtcPublicKey() - ); + Optional federatorIndex = federation.getBtcPublicKeyIndex( + federatorSupport.getFederationMember().getBtcPublicKey()); if (!federatorIndex.isPresent()) { String message = String.format( "Federator %s is a member of the federation %s but could not find the btcPublicKeyIndex", @@ -142,20 +141,19 @@ public void start(Federation federation) { throw new IllegalStateException(message); } - TurnScheduler scheduler = new TurnScheduler( - bridgeConstants.getUpdateBridgeExecutionPeriod(), - federation.getSize() - ); - long now = Clock.systemUTC().instant().toEpochMilli(); - if (isUpdateBridgeTimerEnabled) { - updateBridgeTimer = Executors.newSingleThreadScheduledExecutor(); + long now = Clock.systemUTC().instant().toEpochMilli(); + TurnScheduler scheduler = new TurnScheduler( + bridgeConstants.getUpdateBridgeExecutionPeriod(), + federation.getSize()); + + this.updateBridgeTimer = Executors.newSingleThreadScheduledExecutor(); + updateBridgeTimer.scheduleAtFixedRate( this::updateBridge, scheduler.getDelay(now, federatorIndex.get()), scheduler.getInterval(), - TimeUnit.MILLISECONDS - ); + TimeUnit.MILLISECONDS); } else { logger.info("[start] updateBridgeTimer is disabled"); } @@ -164,11 +162,11 @@ public void start(Federation federation) { public void stop() { logger.info("Stopping"); - federation = null; + this.federation = null; if (updateBridgeTimer != null) { updateBridgeTimer.shutdown(); - updateBridgeTimer = null; + this.updateBridgeTimer = null; } } From 13d4130f9a01ca20cd320a114ad7feef18901224 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:53:11 +0000 Subject: [PATCH 110/112] feat(bitcoin): remove svp spend tx handling in BitcoinWrapperImpl --- .../federate/bitcoin/BitcoinWrapperImpl.java | 36 +++---------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java b/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java index 194761012..af353de8b 100644 --- a/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java +++ b/src/main/java/co/rsk/federate/bitcoin/BitcoinWrapperImpl.java @@ -1,9 +1,6 @@ package co.rsk.federate.bitcoin; import co.rsk.bitcoinj.core.BtcTransaction; -import co.rsk.bitcoinj.core.TransactionInput; -import co.rsk.bitcoinj.core.TransactionOutPoint; -import co.rsk.bitcoinj.core.TransactionOutput; import co.rsk.bitcoinj.wallet.Wallet; import co.rsk.peg.constants.BridgeConstants; import co.rsk.federate.FederatorSupport; @@ -23,7 +20,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import org.bitcoinj.core.Address; import org.bitcoinj.core.Context; @@ -47,6 +43,7 @@ * @author Oscar Guindzberg */ public class BitcoinWrapperImpl implements BitcoinWrapper { + private class FederationListener { private Federation federation; private TransactionListener listener; @@ -77,6 +74,9 @@ public boolean equals(Object o) { } } + private static final int MAX_SIZE_MAP_STORED_BLOCKS = 10_000; + private static final Logger LOGGER = LoggerFactory.getLogger(BitcoinWrapperImpl.class); + private Context btcContext; private BridgeConstants bridgeConstants; private boolean running = false; @@ -90,9 +90,6 @@ public boolean equals(Object o) { private final FederatorSupport federatorSupport; private final Kit kit; - public static final int MAX_SIZE_MAP_STORED_BLOCKS = 10_000; - private static final Logger LOGGER = LoggerFactory.getLogger(BitcoinWrapperImpl.class); - public BitcoinWrapperImpl( Context btcContext, BridgeConstants bridgeConstants, @@ -336,11 +333,6 @@ protected void coinsReceivedOrSent(Transaction tx) { TransactionListener listener = watched.getListener(); Wallet watchedFederationWallet = new BridgeBtcWallet(btcContextThin, Collections.singletonList(watchedFederation)); - if (isTheSvpSpendTx(btcTx)) { - LOGGER.debug("[coinsReceivedOrSent] [btctx with hash {} and witness hash {}] is a svp spend tx", tx.getTxId(), tx.getWTxId()); - listener.onTransaction(tx); - } - if (PegUtilsLegacy.isValidPegInTx(btcTx, watchedFederation, watchedFederationWallet, bridgeConstants, federatorSupport.getConfigForBestBlock())) { PeginInformation peginInformation = new PeginInformation( btcLockSenderProvider, @@ -370,24 +362,4 @@ protected void coinsReceivedOrSent(Transaction tx) { } } } - - private boolean isTheSvpSpendTx(BtcTransaction btcTx) { - return federatorSupport.getProposedFederationAddress() - .map(proposedFedAddress -> - btcTx.getInputs().stream() - .map(input -> getAddressFromInput(input)) - .filter(Optional::isPresent) - .map(Optional::get) - .anyMatch(proposedFedAddress::equals) - ) - .orElse(false); // Return false if no proposed address is present - } - - private Optional getAddressFromInput(TransactionInput input) { - return Optional.ofNullable(input) - .map(TransactionInput::getOutpoint) - .map(TransactionOutPoint::getConnectedOutput) - .map(TransactionOutput::getScriptPubKey) - .map(scriptPubKey -> scriptPubKey.getToAddress(bridgeConstants.getBtcParams())); - } } From e243551a415492aab5fe1a0337d953e712e6ae41 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:53:49 +0000 Subject: [PATCH 111/112] feat(watcher): add BitcoinWrapper to onProposedFederationChange so it can register the svp spend tx --- src/main/java/co/rsk/federate/FedNodeRunner.java | 3 ++- .../watcher/FederationWatcherListenerImpl.java | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/co/rsk/federate/FedNodeRunner.java b/src/main/java/co/rsk/federate/FedNodeRunner.java index 88ab1dc25..67518a0e5 100644 --- a/src/main/java/co/rsk/federate/FedNodeRunner.java +++ b/src/main/java/co/rsk/federate/FedNodeRunner.java @@ -347,7 +347,8 @@ private void startFederate() throws Exception { FederationWatcherListener federationWatcherListener = new FederationWatcherListenerImpl( btcToRskClientActive, btcToRskClientRetiring, - btcReleaseClient); + btcReleaseClient, + bitcoinWrapper); federationWatcher.start(federationProvider, federationWatcherListener); } diff --git a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java index 53a648d44..c2e4d3dbb 100644 --- a/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java +++ b/src/main/java/co/rsk/federate/watcher/FederationWatcherListenerImpl.java @@ -1,6 +1,7 @@ package co.rsk.federate.watcher; import co.rsk.federate.BtcToRskClient; +import co.rsk.federate.bitcoin.BitcoinWrapper; import co.rsk.federate.btcreleaseclient.BtcReleaseClient; import co.rsk.peg.federation.Federation; import org.slf4j.Logger; @@ -14,14 +15,17 @@ public class FederationWatcherListenerImpl implements FederationWatcherListener private final BtcToRskClient btcToRskClientActive; private final BtcToRskClient btcToRskClientRetiring; private final BtcReleaseClient btcReleaseClient; + private final BitcoinWrapper bitcoinWrapper; public FederationWatcherListenerImpl( BtcToRskClient btcToRskClientActive, BtcToRskClient btcToRskClientRetiring, - BtcReleaseClient btcReleaseClient) { + BtcReleaseClient btcReleaseClient, + BitcoinWrapper bitcoinWrapper) { this.btcToRskClientActive = btcToRskClientActive; this.btcToRskClientRetiring = btcToRskClientRetiring; this.btcReleaseClient = btcReleaseClient; + this.bitcoinWrapper = bitcoinWrapper; } @Override @@ -51,13 +55,17 @@ public void onProposedFederationChange(Federation newProposedFederation) { // start {@code BtcReleaseClient} with proposed federation // so it can sign svp spend tx btcReleaseClient.start(newProposedFederation); + + // add proposed federation to active btc to rsk client so + // it can register svp spend tx in the bridge + bitcoinWrapper.addFederationListener(newProposedFederation, btcToRskClientActive); logger.info( - "[onProposedFederationChange] Client for proposed federation [{}] started with success", + "[onProposedFederationChange] Clients for proposed federation [{}] started with success", newProposedFederation.getAddress()); } catch (Exception e) { logger.error( - "[onProposedFederationChange] Client for proposed federation [{}] failed to start", + "[onProposedFederationChange] Clients for proposed federation [{}] failed to start", newProposedFederation.getAddress(), e); } From 7231fa9bb63322b7c052b8359cb14a31403f3b31 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:01:40 +0000 Subject: [PATCH 112/112] feat(watcher): update unit tests to accomdate BitcoinWrapper listener logic to register svp spend tx --- .../watcher/FederationWatcherListenerImplTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java b/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java index 7e1ffde5b..b7cd365fe 100644 --- a/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java +++ b/src/test/java/co/rsk/federate/watcher/FederationWatcherListenerImplTest.java @@ -10,6 +10,7 @@ import co.rsk.bitcoinj.core.BtcECKey; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.federate.BtcToRskClient; +import co.rsk.federate.bitcoin.BitcoinWrapper; import co.rsk.federate.btcreleaseclient.BtcReleaseClient; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationArgs; @@ -38,6 +39,7 @@ class FederationWatcherListenerImplTest { private BtcToRskClient btcToRskClientActive; private BtcToRskClient btcToRskClientRetiring; private BtcReleaseClient btcReleaseClient; + private BitcoinWrapper bitcoinWrapper; private FederationWatcherListener federationWatcherListener; @BeforeEach @@ -45,8 +47,9 @@ void setUp() { btcToRskClientActive = mock(BtcToRskClient.class); btcToRskClientRetiring = mock(BtcToRskClient.class); btcReleaseClient = mock(BtcReleaseClient.class); + bitcoinWrapper = mock(BitcoinWrapper.class); federationWatcherListener = new FederationWatcherListenerImpl( - btcToRskClientActive, btcToRskClientRetiring, btcReleaseClient); + btcToRskClientActive, btcToRskClientRetiring, btcReleaseClient, bitcoinWrapper); } @Test @@ -99,6 +102,7 @@ void onProposedFederationChange_whenNewProposedFederationIsNull_shouldNotStartCl // Assert verify(btcReleaseClient, never()).start(any(Federation.class)); + verify(bitcoinWrapper, never()).addFederationListener(any(Federation.class), any(BtcToRskClient.class)); } @Test @@ -108,6 +112,7 @@ void onProposedFederationChange_whenNewProposedFederationIsValid_shouldStartClie // Assert verify(btcReleaseClient).start(FEDERATION); + verify(bitcoinWrapper).addFederationListener(FEDERATION, btcToRskClientActive); } @Test