Skip to content

Commit

Permalink
feat(pegout): integrate pegout signed cache with btc release client
Browse files Browse the repository at this point in the history
refactor(pegout): improve naming for btc release client and pegout signed cache integration test

feat(pegout): add btc release client test where a pegout is waited until it becomes invalid by pegouts signed cache

feat(pegout): add java time clock to pegouts signed cache

feat(refactor): use deterministic clock for pegout signed cache tests

refactor(pegout): remove unused imports for pegouts signed cache test
  • Loading branch information
apancorb authored and marcos-iov committed Jun 19, 2024
1 parent 3e8235f commit 6c78fe6
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import co.rsk.crypto.Keccak256;
import co.rsk.federate.FederatorSupport;
import co.rsk.federate.adapter.ThinConverter;
import co.rsk.federate.btcreleaseclient.cache.PegoutSignedCache;
import co.rsk.federate.btcreleaseclient.cache.PegoutSignedCacheImpl;
import co.rsk.federate.config.FedNodeSystemProperties;
import co.rsk.federate.signing.ECDSASigner;
import co.rsk.federate.signing.FederationCantSignException;
Expand All @@ -35,6 +37,7 @@
import co.rsk.peg.federation.Federation;
import co.rsk.peg.federation.ErpFederation;
import co.rsk.peg.StateForFederator;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -84,11 +87,11 @@ public class BtcReleaseClient {

private final Ethereum ethereum;
private final FederatorSupport federatorSupport;
private final FedNodeSystemProperties systemProperties;
private final Set<Federation> observedFederations;
private final NodeBlockProcessor nodeBlockProcessor;
private final BridgeConstants bridgeConstants;
private final boolean isPegoutEnabled;
private final PegoutSignedCache pegoutSignedCache;

private ECDSASigner signer;
private BtcReleaseEthereumListener blockListener;
Expand All @@ -108,12 +111,13 @@ public BtcReleaseClient(
) {
this.ethereum = ethereum;
this.federatorSupport = federatorSupport;
this.systemProperties = systemProperties;
this.observedFederations = new HashSet<>();
this.blockListener = new BtcReleaseEthereumListener();
this.bridgeConstants = this.systemProperties.getNetworkConstants().getBridgeConstants();
this.isPegoutEnabled = this.systemProperties.isPegoutEnabled();
this.bridgeConstants = systemProperties.getNetworkConstants().getBridgeConstants();
this.isPegoutEnabled = systemProperties.isPegoutEnabled();
this.nodeBlockProcessor = nodeBlockProcessor;
this.pegoutSignedCache = new PegoutSignedCacheImpl(
systemProperties.getPegoutSignedCacheTtl(), Clock.systemUTC());
}

public void setup(
Expand Down Expand Up @@ -143,7 +147,7 @@ public void setup(
}
peerGroup.start();

blockListener = new BtcReleaseEthereumListener();
this.blockListener = new BtcReleaseEthereumListener();
this.signerMessageBuilderFactory = signerMessageBuilderFactory;
this.releaseCreationInformationGetter = pegoutCreationInformationGetter;
this.releaseRequirementsEnforcer = releaseRequirementsEnforcer;
Expand Down Expand Up @@ -283,10 +287,15 @@ protected Optional<ReleaseCreationInformation> tryGetReleaseInformation(
BtcTransaction pegoutBtcTx
) {
try {
// Discard pegout tx if processed in a previous round of execution
logger.trace(
"[tryGetReleaseInformation] Checking if pegoutCreationTxHash {} has already been signed",
pegoutCreationRskTxHash);
validateTxIsNotCached(pegoutCreationRskTxHash);

// Discard pegout btc tx this fed already signed or cannot be signed by the observed federations
logger.trace("[tryGetReleaseInformation] Validating if pegoutBtcTxHash {} can be signed by observed federations and " +
"that it is not already signed by current fed", pegoutBtcTx.getHash());

validateTxCanBeSigned(pegoutBtcTx);

// IMPORTANT: As per the current behaviour of the bridge, no pegout should have inputs to be signed
Expand Down Expand Up @@ -326,6 +335,15 @@ protected Optional<ReleaseCreationInformation> tryGetReleaseInformation(
return Optional.empty();
}

void validateTxIsNotCached(Keccak256 pegoutCreationRskTxHash) throws FederatorAlreadySignedException {
if (pegoutSignedCache.hasAlreadyBeenSigned(pegoutCreationRskTxHash)) {
String message = String.format(
"Rsk pegout creation tx hash %s was found in the pegouts signed cache",
pegoutCreationRskTxHash);
throw new FederatorAlreadySignedException(message);
}
}

protected void validateTxCanBeSigned(BtcTransaction pegoutBtcTx) throws FederatorAlreadySignedException, FederationCantSignException {
try {
BtcECKey federatorPublicKey = signer.getPublicKey(BTC_KEY_ID.getKeyId()).toBtcKey();
Expand Down Expand Up @@ -400,6 +418,11 @@ protected void signRelease(int signerVersion, ReleaseCreationInformation pegoutC

logger.info("[signRelease] Signed pegout created in rsk transaction {}", pegoutCreationInformation.getPegoutConfirmationRskTxHash());
federatorSupport.addSignature(signatures, pegoutCreationInformation.getPegoutConfirmationRskTxHash().getBytes());

logger.trace("[signRelease] Put pegoutCreationRskTxHash {} in the pegouts signed cache",
pegoutCreationInformation.getPegoutCreationRskTxHash());
pegoutSignedCache.putIfAbsent(
pegoutCreationInformation.getPegoutCreationRskTxHash());
} catch (SignerException e) {
String message = String.format("Error signing pegout created in rsk transaction %s", pegoutCreationInformation.getPegoutCreationRskTxHash());
logger.error(message, e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package co.rsk.federate.btcreleaseclient.cache;

import co.rsk.crypto.Keccak256;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
Expand All @@ -20,10 +21,13 @@ public class PegoutSignedCacheImpl implements PegoutSignedCache {
private final Map<Keccak256, Instant> cache = new ConcurrentHashMap<>();
private final ScheduledExecutorService cleanupScheduler = Executors.newSingleThreadScheduledExecutor();
private final Duration ttl;
private final Clock clock;

public PegoutSignedCacheImpl(Duration ttl) {
public PegoutSignedCacheImpl(Duration ttl, Clock clock) {
validateTtl(ttl);

this.ttl = ttl;
this.clock = clock;

// Start a background thread for periodic cleanup
cleanupScheduler.scheduleAtFixedRate(
Expand All @@ -50,7 +54,7 @@ public void putIfAbsent(Keccak256 pegoutCreationRskTxHash) {
}

Optional.of(pegoutCreationRskTxHash)
.ifPresent(rskTxHash -> cache.putIfAbsent(rskTxHash, Instant.now()));
.ifPresent(rskTxHash -> cache.putIfAbsent(rskTxHash, clock.instant()));
}

void performCleanup() {
Expand All @@ -64,7 +68,7 @@ void performCleanup() {

private boolean isValidTimestamp(Instant timestampInCache) {
return Optional.ofNullable(timestampInCache)
.map(timestamp -> Instant.now().toEpochMilli() - timestamp.toEpochMilli())
.map(timestamp -> clock.instant().toEpochMilli() - timestamp.toEpochMilli())
.map(timeCachedInMillis -> timeCachedInMillis <= ttl.toMillis())
.orElse(false);
}
Expand Down
Loading

0 comments on commit 6c78fe6

Please sign in to comment.