diff --git a/rskj-core/src/main/java/co/rsk/config/BridgeMainNetConstants.java b/rskj-core/src/main/java/co/rsk/config/BridgeMainNetConstants.java index 97b59c0a6ee..95e3e981219 100644 --- a/rskj-core/src/main/java/co/rsk/config/BridgeMainNetConstants.java +++ b/rskj-core/src/main/java/co/rsk/config/BridgeMainNetConstants.java @@ -148,7 +148,7 @@ public class BridgeMainNetConstants extends BridgeConstants { numberOfBlocksBetweenPegouts = 360; // 3 hours of RSK blocks (considering 1 block every 30 seconds) - btcHeightWhenPegoutTxIndexActivates = 100; // TODO: TBD and change current mock value. This is an estimation of the btc block number once RSKIP379 is activated. + btcHeightWhenPegoutTxIndexActivates = 837_589; // Estimated date Wed, 03 Apr 2024 15:00:00 GMT. 832,430 was the block number at time of calculation pegoutTxIndexGracePeriodInBtcBlocks = 4_320; // 30 days in BTC blocks (considering 1 block every 10 minutes) } diff --git a/rskj-core/src/main/java/co/rsk/config/BridgeTestNetConstants.java b/rskj-core/src/main/java/co/rsk/config/BridgeTestNetConstants.java index 027c4ce9613..19fbc10b12e 100644 --- a/rskj-core/src/main/java/co/rsk/config/BridgeTestNetConstants.java +++ b/rskj-core/src/main/java/co/rsk/config/BridgeTestNetConstants.java @@ -166,8 +166,8 @@ public class BridgeTestNetConstants extends BridgeConstants { numberOfBlocksBetweenPegouts = 360; // 3 hours of RSK blocks (considering 1 block every 30 seconds) - btcHeightWhenPegoutTxIndexActivates = 150; // TODO: TBD and change current mock value. This is an estimation of the btc block number once RSKIP379 is activated. - pegoutTxIndexGracePeriodInBtcBlocks = 4_320; // 30 days in BTC blocks (considering 1 block every 10 minutes) + btcHeightWhenPegoutTxIndexActivates = 2_589_553; // Estimated date Wed, 20 Mar 2024 15:00:00 GMT. 2,579,823 was the block number at time of calculation + pegoutTxIndexGracePeriodInBtcBlocks = 1_440; // 10 days in BTC blocks (considering 1 block every 10 minutes) } public static BridgeTestNetConstants getInstance() { diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index 22ac4b197e8..68f9e4d5cba 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -17,6 +17,8 @@ */ package co.rsk.peg; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP417; + import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.Script; import co.rsk.bitcoinj.store.BlockStoreException; @@ -24,6 +26,7 @@ import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; import co.rsk.panic.PanicProcessor; +import co.rsk.peg.BridgeMethods.BridgeMethodExecutor; import co.rsk.peg.bitcoin.MerkleBranch; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationMember; @@ -39,6 +42,7 @@ import org.ethereum.core.*; import org.ethereum.util.ByteUtil; import org.ethereum.vm.DataWord; +import org.ethereum.vm.MessageCall.MsgType; import org.ethereum.vm.PrecompiledContractArgs; import org.ethereum.vm.PrecompiledContracts; import org.ethereum.vm.exception.VMException; @@ -225,15 +229,34 @@ public class Bridge extends PrecompiledContracts.PrecompiledContract { private final BiFunction, Integer, MerkleBranch> merkleBranchFactory; private final SignatureCache signatureCache; - - public Bridge(RskAddress contractAddress, Constants constants, ActivationConfig activationConfig, - BridgeSupportFactory bridgeSupportFactory, SignatureCache signatureCache) { - this(contractAddress, constants, activationConfig, bridgeSupportFactory, MerkleBranch::new, signatureCache); + private MsgType msgType; + + public Bridge( + RskAddress contractAddress, + Constants constants, + ActivationConfig activationConfig, + BridgeSupportFactory bridgeSupportFactory, + SignatureCache signatureCache) { + + this( + contractAddress, + constants, + activationConfig, + bridgeSupportFactory, + MerkleBranch::new, + signatureCache + ); } @VisibleForTesting - Bridge(RskAddress contractAddress, Constants constants, ActivationConfig activationConfig, - BridgeSupportFactory bridgeSupportFactory, BiFunction, Integer, MerkleBranch> merkleBranchFactory, SignatureCache signatureCache) { + Bridge( + RskAddress contractAddress, + Constants constants, + ActivationConfig activationConfig, + BridgeSupportFactory bridgeSupportFactory, + BiFunction, Integer, MerkleBranch> merkleBranchFactory, + SignatureCache signatureCache) { + this.bridgeSupportFactory = bridgeSupportFactory; this.contractAddress = contractAddress; this.constants = constants; @@ -318,12 +341,14 @@ public void init(PrecompiledContractArgs args) { Block rskExecutionBlock = args.getExecutionBlock(); this.activations = activationConfig.forBlock(rskExecutionBlock.getNumber()); this.rskTx = args.getTransaction(); + this.msgType = args.getMsgType(); this.bridgeSupport = bridgeSupportFactory.newInstance( - args.getRepository(), - rskExecutionBlock, - contractAddress, - args.getLogs()); + args.getRepository(), + rskExecutionBlock, + contractAddress, + args.getLogs() + ); } @Override @@ -344,43 +369,28 @@ public byte[] execute(byte[] data) throws VMException { // Function parsing from data returned null => invalid function selected, halt! if (bridgeParsedData == null) { String errorMessage = String.format("Invalid data given: %s.", ByteUtil.toHexString(data)); - logger.info(errorMessage); - if (activations.isActive(ConsensusRule.RSKIP88)) { - throw new BridgeIllegalArgumentException(errorMessage); + logger.info("[execute] {}", errorMessage); + if (!activations.isActive(ConsensusRule.RSKIP88)) { + return null; } - return null; - } - - // If this is not a local call, then first check whether the function - // allows for non-local calls - if (activations.isActive(ConsensusRule.RSKIP88) && - !isLocalCall() && - bridgeParsedData.bridgeMethod.onlyAllowsLocalCalls(this, bridgeParsedData.args)) { - - String errorMessage = String.format("Non-local-call to %s. Returning without execution.", bridgeParsedData.bridgeMethod.getFunction().name); - logger.info(errorMessage); throw new BridgeIllegalArgumentException(errorMessage); } + validateCall(bridgeParsedData); Optional result; try { - // bridgeParsedData.function should be one of the CallTransaction.Function declared above. - // If the user tries to call an non-existent function, parseData() will return null. - result = bridgeParsedData.bridgeMethod.getExecutor().execute(this, bridgeParsedData.args); + result = executeBridgeMethod(bridgeParsedData); } catch (BridgeIllegalArgumentException ex) { - String errorMessage = String.format("Error executing: %s", bridgeParsedData.bridgeMethod); - logger.warn(errorMessage, ex); - if (activations.isActive(ConsensusRule.RSKIP88)) { - throw new BridgeIllegalArgumentException(errorMessage); + if (shouldReturnNullInsteadOfException()) { + return null; } - - return null; + throw ex; } - teardown(); - return result.map(bridgeParsedData.bridgeMethod.getFunction()::encodeOutputs).orElse(null); + byte[] voidReturnValue = calculateVoidReturnValue(); + return result.map(bridgeParsedData.bridgeMethod.getFunction()::encodeOutputs).orElse(voidReturnValue); } catch (Exception ex) { logger.error(ex.getMessage(), ex); panicProcessor.panic("bridgeexecute", ex.getMessage()); @@ -388,6 +398,69 @@ public byte[] execute(byte[] data) throws VMException { } } + private void validateCall(BridgeParsedData bridgeParsedData) throws BridgeIllegalArgumentException { + validateLocalCall(bridgeParsedData); + validateCallMessageType(bridgeParsedData); + } + + private void validateLocalCall(BridgeParsedData bridgeParsedData) throws BridgeIllegalArgumentException { + // If this is not a local call, then check whether the function allows for non-local calls + if (activations.isActive(ConsensusRule.RSKIP88) && + !isLocalCall() && + bridgeParsedData.bridgeMethod.onlyAllowsLocalCalls(this, bridgeParsedData.args)) { + + String errorMessage = String.format( + "Non-local-call to %s. Returning without execution.", + bridgeParsedData.bridgeMethod.getFunction().name + ); + logger.info("[validateLocalCall] {}", errorMessage); + throw new BridgeIllegalArgumentException(errorMessage); + } + } + + private void validateCallMessageType(BridgeParsedData bridgeParsedData) throws BridgeIllegalArgumentException { + if (activations.isActive(RSKIP417) && + !bridgeParsedData.bridgeMethod.acceptsThisTypeOfCall(this.msgType)) { + String errorMessage = String.format( + "Call type (%s) not accepted by %s. Returning without execution.", + this.msgType.name(), + bridgeParsedData.bridgeMethod.getFunction().name + ); + logger.info("[validateCallMessageType] {}", errorMessage); + + throw new BridgeIllegalArgumentException(errorMessage); + } + } + + private Optional executeBridgeMethod(BridgeParsedData bridgeParsedData) throws Exception { + try { + // bridgeParsedData.function should be one of the CallTransaction.Function declared above. + // If the user tries to call an non-existent function, parseData() will return null. + BridgeMethodExecutor executor = bridgeParsedData.bridgeMethod.getExecutor(); + return executor.execute(this, bridgeParsedData.args); + } catch (BridgeIllegalArgumentException ex) { + String errorMessage = String.format("Error executing: %s", bridgeParsedData.bridgeMethod); + logger.warn(errorMessage, ex); + + throw new BridgeIllegalArgumentException(errorMessage); + } + } + + private boolean shouldReturnNullInsteadOfException() { + return !activations.isActive(ConsensusRule.RSKIP88); + } + + private byte[] calculateVoidReturnValue() { + if (shouldReturnNullOnVoidMethods()) { + return null; + } + return new byte[]{}; + } + + private boolean shouldReturnNullOnVoidMethods() { + return !activations.isActive(RSKIP417); + } + private void teardown() throws IOException { bridgeSupport.save(); } @@ -630,7 +703,7 @@ public byte[] getBtcBlockchainBlockHashAtDepth(Object[] args) throws VMException logger.trace("getBtcBlockchainBlockHashAtDepth"); int depth = ((BigInteger) args[0]).intValue(); - Sha256Hash blockHash = null; + Sha256Hash blockHash; try { blockHash = bridgeSupport.getBtcBlockchainBlockHashAtDepth(depth); } catch (Exception e) { @@ -833,8 +906,8 @@ public Integer createFederation(Object[] args) throws BridgeIllegalArgumentExcep logger.trace("createFederation"); return bridgeSupport.voteFederationChange( - rskTx, - new ABICallSpec("create", new byte[][]{}) + rskTx, + new ABICallSpec("create", new byte[][]{}) ); } @@ -850,8 +923,8 @@ public Integer addFederatorPublicKey(Object[] args) throws BridgeIllegalArgument } return bridgeSupport.voteFederationChange( - rskTx, - new ABICallSpec("add", new byte[][]{ publicKeyBytes }) + rskTx, + new ABICallSpec("add", new byte[][]{ publicKeyBytes }) ); } @@ -863,8 +936,11 @@ public Integer addFederatorPublicKeyMultikey(Object[] args) throws BridgeIllegal byte[] mstPublicKeyBytes = (byte[]) args[2]; return bridgeSupport.voteFederationChange( - rskTx, - new ABICallSpec("add-multi", new byte[][]{ btcPublicKeyBytes, rskPublicKeyBytes, mstPublicKeyBytes }) + rskTx, + new ABICallSpec( + "add-multi", + new byte[][]{ btcPublicKeyBytes, rskPublicKeyBytes, mstPublicKeyBytes } + ) ); } @@ -880,8 +956,8 @@ public Integer commitFederation(Object[] args) throws BridgeIllegalArgumentExcep } return bridgeSupport.voteFederationChange( - rskTx, - new ABICallSpec("commit", new byte[][]{ hash }) + rskTx, + new ABICallSpec("commit", new byte[][]{ hash }) ); } @@ -1102,7 +1178,14 @@ public void registerBtcCoinbaseTransaction(Object[] args) throws VMException { byte[] pmtSerialized = (byte[]) args[2]; Sha256Hash witnessMerkleRoot = Sha256Hash.wrap((byte[]) args[3]); byte[] witnessReservedValue = (byte[]) args[4]; - bridgeSupport.registerBtcCoinbaseTransaction(btcTxSerialized, blockHash, pmtSerialized, witnessMerkleRoot, witnessReservedValue); + + bridgeSupport.registerBtcCoinbaseTransaction( + btcTxSerialized, + blockHash, + pmtSerialized, + witnessMerkleRoot, + witnessReservedValue + ); } public boolean hasBtcBlockCoinbaseTransactionInformation(Object[] args) { diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java index dfbeb4923ac..ca08460e20c 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java @@ -17,635 +17,674 @@ */ package co.rsk.peg; -import org.ethereum.config.blockchain.upgrades.ActivationConfig; -import org.ethereum.core.CallTransaction; -import org.ethereum.db.ByteArrayWrapper; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; - -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.core.CallTransaction; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.vm.MessageCall.MsgType; /** * This enum holds the basic information of the Bridge contract methods: the ABI, the cost and the implementation. */ public enum BridgeMethods { ADD_FEDERATOR_PUBLIC_KEY( - CallTransaction.Function.fromSignature( - "addFederatorPublicKey", - new String[]{"bytes"}, - new String[]{"int256"} - ), - fixedCost(13000L), - (BridgeMethodExecutorTyped) Bridge::addFederatorPublicKey, - activations -> !activations.isActive(RSKIP123), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "addFederatorPublicKey", + new String[]{"bytes"}, + new String[]{"int256"} + ), + fixedCost(13000L), + (BridgeMethodExecutorTyped) Bridge::addFederatorPublicKey, + activations -> !activations.isActive(RSKIP123), + fixedPermission(false) ), ADD_FEDERATOR_PUBLIC_KEY_MULTIKEY( - CallTransaction.Function.fromSignature( - "addFederatorPublicKeyMultikey", - new String[]{"bytes", "bytes", "bytes"}, - new String[]{"int256"} - ), - fixedCost(13000L), - (BridgeMethodExecutorTyped) Bridge::addFederatorPublicKeyMultikey, - activations -> activations.isActive(RSKIP123), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "addFederatorPublicKeyMultikey", + new String[]{"bytes", "bytes", "bytes"}, + new String[]{"int256"} + ), + fixedCost(13000L), + (BridgeMethodExecutorTyped) Bridge::addFederatorPublicKeyMultikey, + activations -> activations.isActive(RSKIP123), + fixedPermission(false) ), ADD_LOCK_WHITELIST_ADDRESS( - CallTransaction.Function.fromSignature( - "addLockWhitelistAddress", - new String[]{"string", "int256"}, - new String[]{"int256"} - ), - fixedCost(25000L), - (BridgeMethodExecutorTyped) Bridge::addOneOffLockWhitelistAddress, - activations -> !activations.isActive(RSKIP87), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "addLockWhitelistAddress", + new String[]{"string", "int256"}, + new String[]{"int256"} + ), + fixedCost(25000L), + (BridgeMethodExecutorTyped) Bridge::addOneOffLockWhitelistAddress, + activations -> !activations.isActive(RSKIP87), + fixedPermission(false) ), ADD_ONE_OFF_LOCK_WHITELIST_ADDRESS( - CallTransaction.Function.fromSignature( - "addOneOffLockWhitelistAddress", - new String[]{"string", "int256"}, - new String[]{"int256"} - ), - fixedCost(25000L), // using same gas estimation as ADD_LOCK_WHITELIST_ADDRESS - (BridgeMethodExecutorTyped) Bridge::addOneOffLockWhitelistAddress, - activations -> activations.isActive(RSKIP87), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "addOneOffLockWhitelistAddress", + new String[]{"string", "int256"}, + new String[]{"int256"} + ), + fixedCost(25000L), // using same gas estimation as ADD_LOCK_WHITELIST_ADDRESS + (BridgeMethodExecutorTyped) Bridge::addOneOffLockWhitelistAddress, + activations -> activations.isActive(RSKIP87), + fixedPermission(false) ), ADD_UNLIMITED_LOCK_WHITELIST_ADDRESS( - CallTransaction.Function.fromSignature( - "addUnlimitedLockWhitelistAddress", - new String[]{"string"}, - new String[]{"int256"} - ), - fixedCost(25000L), // using same gas estimation as ADD_LOCK_WHITELIST_ADDRESS - (BridgeMethodExecutorTyped) Bridge::addUnlimitedLockWhitelistAddress, - activations -> activations.isActive(RSKIP87), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "addUnlimitedLockWhitelistAddress", + new String[]{"string"}, + new String[]{"int256"} + ), + fixedCost(25000L), // using same gas estimation as ADD_LOCK_WHITELIST_ADDRESS + (BridgeMethodExecutorTyped) Bridge::addUnlimitedLockWhitelistAddress, + activations -> activations.isActive(RSKIP87), + fixedPermission(false) ), ADD_SIGNATURE( - CallTransaction.Function.fromSignature( - "addSignature", - new String[]{"bytes", "bytes[]", "bytes"}, - new String[]{} - ), - fixedCost(70000L), - Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::addSignature, "addSignature"), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "addSignature", + new String[]{"bytes", "bytes[]", "bytes"}, + new String[]{} + ), + fixedCost(70000L), + Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::addSignature, "addSignature"), + fixedPermission(false) ), COMMIT_FEDERATION( - CallTransaction.Function.fromSignature( - "commitFederation", - new String[]{"bytes"}, - new String[]{"int256"} - ), - fixedCost(38000L), - (BridgeMethodExecutorTyped) Bridge::commitFederation, - fixedPermission(false) + CallTransaction.Function.fromSignature( + "commitFederation", + new String[]{"bytes"}, + new String[]{"int256"} + ), + fixedCost(38000L), + (BridgeMethodExecutorTyped) Bridge::commitFederation, + fixedPermission(false) ), CREATE_FEDERATION( - CallTransaction.Function.fromSignature( - "createFederation", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(11000L), - (BridgeMethodExecutorTyped) Bridge::createFederation, - fixedPermission(false) + CallTransaction.Function.fromSignature( + "createFederation", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(11000L), + (BridgeMethodExecutorTyped) Bridge::createFederation, + fixedPermission(false) ), GET_BTC_BLOCKCHAIN_BEST_CHAIN_HEIGHT( - CallTransaction.Function.fromSignature( - "getBtcBlockchainBestChainHeight", - new String[]{}, - new String[]{"int"} - ), - fixedCost(19000L), - (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBestChainHeight, - fromMethod(Bridge::getBtcBlockchainBestChainHeightOnlyAllowsLocalCalls) + CallTransaction.Function.fromSignature( + "getBtcBlockchainBestChainHeight", + new String[]{}, + new String[]{"int"} + ), + fixedCost(19000L), + (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBestChainHeight, + fromMethod(Bridge::getBtcBlockchainBestChainHeightOnlyAllowsLocalCalls), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_BLOCKCHAIN_INITIAL_BLOCK_HEIGHT( - CallTransaction.Function.fromSignature( - "getBtcBlockchainInitialBlockHeight", - new String[]{}, - new String[]{"int"} - ), - fixedCost(20000L), - (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainInitialBlockHeight, - activations -> activations.isActive(RSKIP89), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getBtcBlockchainInitialBlockHeight", + new String[]{}, + new String[]{"int"} + ), + fixedCost(20000L), + (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainInitialBlockHeight, + activations -> activations.isActive(RSKIP89), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_BLOCKCHAIN_BLOCK_LOCATOR( - CallTransaction.Function.fromSignature( - "getBtcBlockchainBlockLocator", - new String[]{}, - new String[]{"string[]"} - ), - fixedCost(76000L), - (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBlockLocator, - activations -> !activations.isActive(RSKIP89), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getBtcBlockchainBlockLocator", + new String[]{}, + new String[]{"string[]"} + ), + fixedCost(76000L), + (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBlockLocator, + activations -> !activations.isActive(RSKIP89), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_BLOCKCHAIN_BLOCK_HASH_AT_DEPTH( - CallTransaction.Function.fromSignature( - "getBtcBlockchainBlockHashAtDepth", - new String[]{"int256"}, - new String[]{"bytes"} - ), - fixedCost(20000L), - (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBlockHashAtDepth, - activations -> activations.isActive(RSKIP89), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getBtcBlockchainBlockHashAtDepth", + new String[]{"int256"}, + new String[]{"bytes"} + ), + fixedCost(20000L), + (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBlockHashAtDepth, + activations -> activations.isActive(RSKIP89), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_TRANSACTION_CONFIRMATIONS( - CallTransaction.Function.fromSignature( - "getBtcTransactionConfirmations", - new String[]{"bytes32", "bytes32", "uint256", "bytes32[]"}, - new String[]{"int256"} - ), - fromMethod(Bridge::getBtcTransactionConfirmationsGetCost), - (BridgeMethodExecutorTyped) Bridge::getBtcTransactionConfirmations, - activations -> activations.isActive(RSKIP122), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getBtcTransactionConfirmations", + new String[]{"bytes32", "bytes32", "uint256", "bytes32[]"}, + new String[]{"int256"} + ), + fromMethod(Bridge::getBtcTransactionConfirmationsGetCost), + (BridgeMethodExecutorTyped) Bridge::getBtcTransactionConfirmations, + activations -> activations.isActive(RSKIP122), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_TX_HASH_PROCESSED_HEIGHT( - CallTransaction.Function.fromSignature( - "getBtcTxHashProcessedHeight", - new String[]{"string"}, - new String[]{"int64"} - ), - fixedCost(22000L), - (BridgeMethodExecutorTyped) Bridge::getBtcTxHashProcessedHeight, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getBtcTxHashProcessedHeight", + new String[]{"string"}, + new String[]{"int64"} + ), + fixedCost(22000L), + (BridgeMethodExecutorTyped) Bridge::getBtcTxHashProcessedHeight, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEDERATION_ADDRESS( - CallTransaction.Function.fromSignature( - "getFederationAddress", - new String[]{}, - new String[]{"string"} - ), - fixedCost(11000L), - (BridgeMethodExecutorTyped) Bridge::getFederationAddress, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFederationAddress", + new String[]{}, + new String[]{"string"} + ), + fixedCost(11000L), + (BridgeMethodExecutorTyped) Bridge::getFederationAddress, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEDERATION_CREATION_BLOCK_NUMBER( - CallTransaction.Function.fromSignature( - "getFederationCreationBlockNumber", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(10000L), - (BridgeMethodExecutorTyped) Bridge::getFederationCreationBlockNumber, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFederationCreationBlockNumber", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(10000L), + (BridgeMethodExecutorTyped) Bridge::getFederationCreationBlockNumber, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEDERATION_CREATION_TIME( - CallTransaction.Function.fromSignature( - "getFederationCreationTime", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(10000L), - (BridgeMethodExecutorTyped) Bridge::getFederationCreationTime, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFederationCreationTime", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(10000L), + (BridgeMethodExecutorTyped) Bridge::getFederationCreationTime, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEDERATION_SIZE( - CallTransaction.Function.fromSignature( - "getFederationSize", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(10000L), - (BridgeMethodExecutorTyped) Bridge::getFederationSize, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFederationSize", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(10000L), + (BridgeMethodExecutorTyped) Bridge::getFederationSize, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEDERATION_THRESHOLD( - CallTransaction.Function.fromSignature( - "getFederationThreshold", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(11000L), - (BridgeMethodExecutorTyped) Bridge::getFederationThreshold, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFederationThreshold", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(11000L), + (BridgeMethodExecutorTyped) Bridge::getFederationThreshold, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEDERATOR_PUBLIC_KEY( - CallTransaction.Function.fromSignature( - "getFederatorPublicKey", - new String[]{"int256"}, - new String[]{"bytes"} - ), - fixedCost(10000L), - (BridgeMethodExecutorTyped) Bridge::getFederatorPublicKey, - activations -> !activations.isActive(RSKIP123), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFederatorPublicKey", + new String[]{"int256"}, + new String[]{"bytes"} + ), + fixedCost(10000L), + (BridgeMethodExecutorTyped) Bridge::getFederatorPublicKey, + activations -> !activations.isActive(RSKIP123), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEDERATOR_PUBLIC_KEY_OF_TYPE( - CallTransaction.Function.fromSignature( - "getFederatorPublicKeyOfType", - new String[]{"int256", "string"}, - new String[]{"bytes"} - ), - fixedCost(10000L), - (BridgeMethodExecutorTyped) Bridge::getFederatorPublicKeyOfType, - activations -> activations.isActive(RSKIP123), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFederatorPublicKeyOfType", + new String[]{"int256", "string"}, + new String[]{"bytes"} + ), + fixedCost(10000L), + (BridgeMethodExecutorTyped) Bridge::getFederatorPublicKeyOfType, + activations -> activations.isActive(RSKIP123), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_FEE_PER_KB( - CallTransaction.Function.fromSignature( - "getFeePerKb", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(2000L), - (BridgeMethodExecutorTyped) Bridge::getFeePerKb, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getFeePerKb", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(2000L), + (BridgeMethodExecutorTyped) Bridge::getFeePerKb, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_LOCK_WHITELIST_ADDRESS( - CallTransaction.Function.fromSignature( - "getLockWhitelistAddress", - new String[]{"int256"}, - new String[]{"string"} - ), - fixedCost(16000L), - (BridgeMethodExecutorTyped) Bridge::getLockWhitelistAddress, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getLockWhitelistAddress", + new String[]{"int256"}, + new String[]{"string"} + ), + fixedCost(16000L), + (BridgeMethodExecutorTyped) Bridge::getLockWhitelistAddress, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_LOCK_WHITELIST_ENTRY_BY_ADDRESS( - CallTransaction.Function.fromSignature( - "getLockWhitelistEntryByAddress", - new String[]{"string"}, - new String[]{"int256"} - ), - fixedCost(16000L), - (BridgeMethodExecutorTyped) Bridge::getLockWhitelistEntryByAddress, - activations -> activations.isActive(RSKIP87), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getLockWhitelistEntryByAddress", + new String[]{"string"}, + new String[]{"int256"} + ), + fixedCost(16000L), + (BridgeMethodExecutorTyped) Bridge::getLockWhitelistEntryByAddress, + activations -> activations.isActive(RSKIP87), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_LOCK_WHITELIST_SIZE( - CallTransaction.Function.fromSignature( - "getLockWhitelistSize", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(16000L), - (BridgeMethodExecutorTyped) Bridge::getLockWhitelistSize, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getLockWhitelistSize", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(16000L), + (BridgeMethodExecutorTyped) Bridge::getLockWhitelistSize, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_MINIMUM_LOCK_TX_VALUE( - CallTransaction.Function.fromSignature( - "getMinimumLockTxValue", - new String[]{}, - new String[]{"int"} - ), - fixedCost(2000L), - (BridgeMethodExecutorTyped) Bridge::getMinimumLockTxValue, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getMinimumLockTxValue", + new String[]{}, + new String[]{"int"} + ), + fixedCost(2000L), + (BridgeMethodExecutorTyped) Bridge::getMinimumLockTxValue, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_PENDING_FEDERATION_HASH( - CallTransaction.Function.fromSignature( - "getPendingFederationHash", - new String[]{}, - new String[]{"bytes"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getPendingFederationHash, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getPendingFederationHash", + new String[]{}, + new String[]{"bytes"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getPendingFederationHash, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_PENDING_FEDERATION_SIZE( - CallTransaction.Function.fromSignature( - "getPendingFederationSize", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getPendingFederationSize, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getPendingFederationSize", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getPendingFederationSize, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_PENDING_FEDERATOR_PUBLIC_KEY( - CallTransaction.Function.fromSignature( - "getPendingFederatorPublicKey", - new String[]{"int256"}, - new String[]{"bytes"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getPendingFederatorPublicKey, - activations -> !activations.isActive(RSKIP123), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getPendingFederatorPublicKey", + new String[]{"int256"}, + new String[]{"bytes"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getPendingFederatorPublicKey, + activations -> !activations.isActive(RSKIP123), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_PENDING_FEDERATOR_PUBLIC_KEY_OF_TYPE( - CallTransaction.Function.fromSignature( - "getPendingFederatorPublicKeyOfType", - new String[]{"int256", "string"}, - new String[]{"bytes"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getPendingFederatorPublicKeyOfType, - activations -> activations.isActive(RSKIP123), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getPendingFederatorPublicKeyOfType", + new String[]{"int256", "string"}, + new String[]{"bytes"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getPendingFederatorPublicKeyOfType, + activations -> activations.isActive(RSKIP123), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_RETIRING_FEDERATION_ADDRESS( - CallTransaction.Function.fromSignature( - "getRetiringFederationAddress", - new String[]{}, - new String[]{"string"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getRetiringFederationAddress, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getRetiringFederationAddress", + new String[]{}, + new String[]{"string"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getRetiringFederationAddress, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_RETIRING_FEDERATION_CREATION_BLOCK_NUMBER( - CallTransaction.Function.fromSignature( - "getRetiringFederationCreationBlockNumber", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getRetiringFederationCreationBlockNumber, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getRetiringFederationCreationBlockNumber", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getRetiringFederationCreationBlockNumber, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_RETIRING_FEDERATION_CREATION_TIME( - CallTransaction.Function.fromSignature( - "getRetiringFederationCreationTime", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getRetiringFederationCreationTime, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getRetiringFederationCreationTime", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getRetiringFederationCreationTime, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_RETIRING_FEDERATION_SIZE( - CallTransaction.Function.fromSignature( - "getRetiringFederationSize", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getRetiringFederationSize, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getRetiringFederationSize", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getRetiringFederationSize, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_RETIRING_FEDERATION_THRESHOLD( - CallTransaction.Function.fromSignature( - "getRetiringFederationThreshold", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getRetiringFederationThreshold, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getRetiringFederationThreshold", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getRetiringFederationThreshold, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_RETIRING_FEDERATOR_PUBLIC_KEY( - CallTransaction.Function.fromSignature( - "getRetiringFederatorPublicKey", - new String[]{"int256"}, - new String[]{"bytes"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getRetiringFederatorPublicKey, - activations -> !activations.isActive(RSKIP123), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getRetiringFederatorPublicKey", + new String[]{"int256"}, + new String[]{"bytes"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getRetiringFederatorPublicKey, + activations -> !activations.isActive(RSKIP123), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_RETIRING_FEDERATOR_PUBLIC_KEY_OF_TYPE( - CallTransaction.Function.fromSignature( - "getRetiringFederatorPublicKeyOfType", - new String[]{"int256", "string"}, - new String[]{"bytes"} - ), - fixedCost(3000L), - (BridgeMethodExecutorTyped) Bridge::getRetiringFederatorPublicKeyOfType, - activations -> activations.isActive(RSKIP123), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getRetiringFederatorPublicKeyOfType", + new String[]{"int256", "string"}, + new String[]{"bytes"} + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getRetiringFederatorPublicKeyOfType, + activations -> activations.isActive(RSKIP123), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_STATE_FOR_BTC_RELEASE_CLIENT( - CallTransaction.Function.fromSignature( - "getStateForBtcReleaseClient", - new String[]{}, - new String[]{"bytes"} - ), - fixedCost(4000L), - (BridgeMethodExecutorTyped) Bridge::getStateForBtcReleaseClient, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getStateForBtcReleaseClient", + new String[]{}, + new String[]{"bytes"} + ), + fixedCost(4000L), + (BridgeMethodExecutorTyped) Bridge::getStateForBtcReleaseClient, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_STATE_FOR_DEBUGGING( - CallTransaction.Function.fromSignature( - "getStateForDebugging", - new String[]{}, - new String[]{"bytes"} - ), - fixedCost(3_000_000L), - (BridgeMethodExecutorTyped) Bridge::getStateForDebugging, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getStateForDebugging", + new String[]{}, + new String[]{"bytes"} + ), + fixedCost(3_000_000L), + (BridgeMethodExecutorTyped) Bridge::getStateForDebugging, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_LOCKING_CAP( - CallTransaction.Function.fromSignature( - "getLockingCap", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(3_000L), - (BridgeMethodExecutorTyped) Bridge::getLockingCap, - activations -> activations.isActive(RSKIP134), - fixedPermission(true) + CallTransaction.Function.fromSignature( + "getLockingCap", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(3_000L), + (BridgeMethodExecutorTyped) Bridge::getLockingCap, + activations -> activations.isActive(RSKIP134), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_ACTIVE_POWPEG_REDEEM_SCRIPT( - CallTransaction.Function.fromSignature( - "getActivePowpegRedeemScript", - new String[]{}, - new String[]{"bytes"} - ), - fixedCost(30_000L), - (BridgeMethodExecutorTyped) Bridge::getActivePowpegRedeemScript, - activations -> activations.isActive(RSKIP293), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getActivePowpegRedeemScript", + new String[]{}, + new String[]{"bytes"} + ), + fixedCost(30_000L), + (BridgeMethodExecutorTyped) Bridge::getActivePowpegRedeemScript, + activations -> activations.isActive(RSKIP293), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_ACTIVE_FEDERATION_CREATION_BLOCK_HEIGHT( - CallTransaction.Function.fromSignature( - "getActiveFederationCreationBlockHeight", - new String[]{}, - new String[]{"uint256"} - ), - fixedCost(3_000L), - (BridgeMethodExecutorTyped) Bridge::getActiveFederationCreationBlockHeight, - activations -> activations.isActive(RSKIP186), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getActiveFederationCreationBlockHeight", + new String[]{}, + new String[]{"uint256"} + ), + fixedCost(3_000L), + (BridgeMethodExecutorTyped) Bridge::getActiveFederationCreationBlockHeight, + activations -> activations.isActive(RSKIP186), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), INCREASE_LOCKING_CAP( - CallTransaction.Function.fromSignature( - "increaseLockingCap", - new String[]{"int256"}, - new String[]{"bool"} - ), - fixedCost(8_000L), - (BridgeMethodExecutorTyped) Bridge::increaseLockingCap, - activations -> activations.isActive(RSKIP134), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "increaseLockingCap", + new String[]{"int256"}, + new String[]{"bool"} + ), + fixedCost(8_000L), + (BridgeMethodExecutorTyped) Bridge::increaseLockingCap, + activations -> activations.isActive(RSKIP134), + fixedPermission(false) ), IS_BTC_TX_HASH_ALREADY_PROCESSED( - CallTransaction.Function.fromSignature( - "isBtcTxHashAlreadyProcessed", - new String[]{"string"}, - new String[]{"bool"} - ), - fixedCost(23000L), - (BridgeMethodExecutorTyped) Bridge::isBtcTxHashAlreadyProcessed, - fixedPermission(true) + CallTransaction.Function.fromSignature( + "isBtcTxHashAlreadyProcessed", + new String[]{"string"}, + new String[]{"bool"} + ), + fixedCost(23000L), + (BridgeMethodExecutorTyped) Bridge::isBtcTxHashAlreadyProcessed, + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL ), RECEIVE_HEADERS( - CallTransaction.Function.fromSignature( - "receiveHeaders", - new String[]{"bytes[]"}, - new String[]{} - ), - fromMethod(Bridge::receiveHeadersGetCost), - Bridge.executeIfElse( - Bridge::receiveHeadersIsPublic, - (BridgeMethodExecutorVoid) Bridge::receiveHeaders, - Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::receiveHeaders, "receiveHeaders") - ), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "receiveHeaders", + new String[]{"bytes[]"}, + new String[]{} + ), + fromMethod(Bridge::receiveHeadersGetCost), + Bridge.executeIfElse( + Bridge::receiveHeadersIsPublic, + (BridgeMethodExecutorVoid) Bridge::receiveHeaders, + Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::receiveHeaders, "receiveHeaders") + ), + fixedPermission(false) ), RECEIVE_HEADER( - CallTransaction.Function.fromSignature( - "receiveHeader", - new String[]{"bytes"}, - new String[]{"int256"} - ), - fixedCost(10_600L), - (BridgeMethodExecutorTyped) Bridge::receiveHeader, - activations -> activations.isActive(RSKIP200), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "receiveHeader", + new String[]{"bytes"}, + new String[]{"int256"} + ), + fixedCost(10_600L), + (BridgeMethodExecutorTyped) Bridge::receiveHeader, + activations -> activations.isActive(RSKIP200), + fixedPermission(false) ), REGISTER_BTC_TRANSACTION( - CallTransaction.Function.fromSignature( - "registerBtcTransaction", - new String[]{"bytes", "int", "bytes"}, - new String[]{} - ), - fixedCost(22000L), - Bridge.executeIfElse( - Bridge::registerBtcTransactionIsPublic, - (BridgeMethodExecutorVoid) Bridge::registerBtcTransaction, - Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::registerBtcTransaction, "registerBtcTransaction") - ), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "registerBtcTransaction", + new String[]{"bytes", "int", "bytes"}, + new String[]{} + ), + fixedCost(22000L), + Bridge.executeIfElse( + Bridge::registerBtcTransactionIsPublic, + (BridgeMethodExecutorVoid) Bridge::registerBtcTransaction, + Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::registerBtcTransaction, "registerBtcTransaction") + ), + fixedPermission(false) ), RELEASE_BTC( - CallTransaction.Function.fromSignature( - "releaseBtc", - new String[]{}, - new String[]{} - ), - fixedCost(23000L), - (BridgeMethodExecutorVoid) Bridge::releaseBtc, - fixedPermission(false) + CallTransaction.Function.fromSignature( + "releaseBtc", + new String[]{}, + new String[]{} + ), + fixedCost(23000L), + (BridgeMethodExecutorVoid) Bridge::releaseBtc, + fixedPermission(false) ), REMOVE_LOCK_WHITELIST_ADDRESS( - CallTransaction.Function.fromSignature( - "removeLockWhitelistAddress", - new String[]{"string"}, - new String[]{"int256"} - ), - fixedCost(24000L), - (BridgeMethodExecutorTyped) Bridge::removeLockWhitelistAddress, - fixedPermission(false) + CallTransaction.Function.fromSignature( + "removeLockWhitelistAddress", + new String[]{"string"}, + new String[]{"int256"} + ), + fixedCost(24000L), + (BridgeMethodExecutorTyped) Bridge::removeLockWhitelistAddress, + fixedPermission(false) ), ROLLBACK_FEDERATION( - CallTransaction.Function.fromSignature( - "rollbackFederation", - new String[]{}, - new String[]{"int256"} - ), - fixedCost(12000L), - (BridgeMethodExecutorTyped) Bridge::rollbackFederation, - fixedPermission(false) + CallTransaction.Function.fromSignature( + "rollbackFederation", + new String[]{}, + new String[]{"int256"} + ), + fixedCost(12000L), + (BridgeMethodExecutorTyped) Bridge::rollbackFederation, + fixedPermission(false) ), SET_LOCK_WHITELIST_DISABLE_BLOCK_DELAY( - CallTransaction.Function.fromSignature( - "setLockWhitelistDisableBlockDelay", - new String[]{"int256"}, - new String[]{"int256"} - ), - fixedCost(24000L), - (BridgeMethodExecutorTyped) Bridge::setLockWhitelistDisableBlockDelay, - fixedPermission(false) + CallTransaction.Function.fromSignature( + "setLockWhitelistDisableBlockDelay", + new String[]{"int256"}, + new String[]{"int256"} + ), + fixedCost(24000L), + (BridgeMethodExecutorTyped) Bridge::setLockWhitelistDisableBlockDelay, + fixedPermission(false) ), UPDATE_COLLECTIONS( - CallTransaction.Function.fromSignature( - "updateCollections", - new String[]{}, - new String[]{} - ), - fixedCost(48000L), - Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::updateCollections, "updateCollections"), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "updateCollections", + new String[]{}, + new String[]{} + ), + fixedCost(48000L), + Bridge.activeAndRetiringFederationOnly((BridgeMethodExecutorVoid) Bridge::updateCollections, "updateCollections"), + fixedPermission(false) ), VOTE_FEE_PER_KB( - CallTransaction.Function.fromSignature( - "voteFeePerKbChange", - new String[]{"int256"}, - new String[]{"int256"} - ), - fixedCost(10000L), - (BridgeMethodExecutorTyped) Bridge::voteFeePerKbChange, - fixedPermission(false) + CallTransaction.Function.fromSignature( + "voteFeePerKbChange", + new String[]{"int256"}, + new String[]{"int256"} + ), + fixedCost(10000L), + (BridgeMethodExecutorTyped) Bridge::voteFeePerKbChange, + fixedPermission(false) ), REGISTER_BTC_COINBASE_TRANSACTION( - CallTransaction.Function.fromSignature( - "registerBtcCoinbaseTransaction", - new String[]{"bytes", "bytes32", "bytes", "bytes32", "bytes32"}, - new String[]{} - ), - fixedCost(10000L), - (BridgeMethodExecutorVoid) Bridge::registerBtcCoinbaseTransaction, - activations -> activations.isActive(RSKIP143), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "registerBtcCoinbaseTransaction", + new String[]{"bytes", "bytes32", "bytes", "bytes32", "bytes32"}, + new String[]{} + ), + fixedCost(10000L), + (BridgeMethodExecutorVoid) Bridge::registerBtcCoinbaseTransaction, + activations -> activations.isActive(RSKIP143), + fixedPermission(false) ), HAS_BTC_BLOCK_COINBASE_TRANSACTION_INFORMATION( - CallTransaction.Function.fromSignature( - "hasBtcBlockCoinbaseTransactionInformation", - new String[]{"bytes32"}, - new String[]{"bool"} - ), - fixedCost(5000L), - (BridgeMethodExecutorTyped) Bridge::hasBtcBlockCoinbaseTransactionInformation, - activations -> activations.isActive(RSKIP143), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "hasBtcBlockCoinbaseTransactionInformation", + new String[]{"bytes32"}, + new String[]{"bool"} + ), + fixedCost(5000L), + (BridgeMethodExecutorTyped) Bridge::hasBtcBlockCoinbaseTransactionInformation, + activations -> activations.isActive(RSKIP143), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), REGISTER_FAST_BRIDGE_BTC_TRANSACTION( - CallTransaction.Function.fromSignature( - "registerFastBridgeBtcTransaction", - new String[]{"bytes", "uint256", "bytes", "bytes32", "bytes", "address", "bytes", "bool"}, - new String[]{"int256"} - ), - fixedCost(25_000L), - (BridgeMethodExecutorTyped) Bridge::registerFlyoverBtcTransaction, - activations -> activations.isActive(RSKIP176), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "registerFastBridgeBtcTransaction", + new String[]{"bytes", "uint256", "bytes", "bytes32", "bytes", "address", "bytes", "bool"}, + new String[]{"int256"} + ), + fixedCost(25_000L), + (BridgeMethodExecutorTyped) Bridge::registerFlyoverBtcTransaction, + activations -> activations.isActive(RSKIP176), + fixedPermission(false) ), GET_BTC_BLOCKCHAIN_BEST_BLOCK_HEADER( - CallTransaction.Function.fromSignature( - "getBtcBlockchainBestBlockHeader", - new String[0], - new String[]{"bytes"} - ), - fixedCost(3_800L), - (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBestBlockHeader, - activations -> activations.isActive(RSKIP220), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getBtcBlockchainBestBlockHeader", + new String[0], + new String[]{"bytes"} + ), + fixedCost(3_800L), + (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBestBlockHeader, + activations -> activations.isActive(RSKIP220), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HASH( - CallTransaction.Function.fromSignature( - "getBtcBlockchainBlockHeaderByHash", - new String[]{"bytes32"}, - new String[]{"bytes"} - ), - fixedCost(4_600L), - (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBlockHeaderByHash, - activations -> activations.isActive(RSKIP220), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getBtcBlockchainBlockHeaderByHash", + new String[]{"bytes32"}, + new String[]{"bytes"} + ), + fixedCost(4_600L), + (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBlockHeaderByHash, + activations -> activations.isActive(RSKIP220), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HEIGHT( CallTransaction.Function.fromSignature( @@ -656,58 +695,70 @@ public enum BridgeMethods { fixedCost(5_000L), (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainBlockHeaderByHeight, activations -> activations.isActive(RSKIP220), - fixedPermission(false) + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_BTC_BLOCKCHAIN_PARENT_BLOCK_HEADER_BY_HASH( - CallTransaction.Function.fromSignature( - "getBtcBlockchainParentBlockHeaderByHash", - new String[]{"bytes32"}, - new String[]{"bytes"} - ), - fixedCost(4_900L), - (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainParentBlockHeaderByHash, - activations -> activations.isActive(RSKIP220), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getBtcBlockchainParentBlockHeaderByHash", + new String[]{"bytes32"}, + new String[]{"bytes"} + ), + fixedCost(4_900L), + (BridgeMethodExecutorTyped) Bridge::getBtcBlockchainParentBlockHeaderByHash, + activations -> activations.isActive(RSKIP220), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_NEXT_PEGOUT_CREATION_BLOCK_NUMBER( - CallTransaction.Function.fromSignature( - "getNextPegoutCreationBlockNumber", - new String[]{}, - new String[]{"uint256"} - ), - fixedCost(3_000L), - (BridgeMethodExecutorTyped) Bridge::getNextPegoutCreationBlockNumber, - activations -> activations.isActive(RSKIP271), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getNextPegoutCreationBlockNumber", + new String[]{}, + new String[]{"uint256"} + ), + fixedCost(3_000L), + (BridgeMethodExecutorTyped) Bridge::getNextPegoutCreationBlockNumber, + activations -> activations.isActive(RSKIP271), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_QUEUED_PEGOUTS_COUNT( - CallTransaction.Function.fromSignature( - "getQueuedPegoutsCount", - new String[]{}, - new String[]{"uint256"} - ), - fixedCost(3_000L), - (BridgeMethodExecutorTyped) Bridge::getQueuedPegoutsCount, - activations -> activations.isActive(RSKIP271), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getQueuedPegoutsCount", + new String[]{}, + new String[]{"uint256"} + ), + fixedCost(3_000L), + (BridgeMethodExecutorTyped) Bridge::getQueuedPegoutsCount, + activations -> activations.isActive(RSKIP271), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ), GET_ESTIMATED_FEES_FOR_NEXT_PEGOUT_EVENT( - CallTransaction.Function.fromSignature( - "getEstimatedFeesForNextPegOutEvent", - new String[]{}, - new String[]{"uint256"} - ), - fixedCost(10_000L), - (BridgeMethodExecutorTyped) Bridge::getEstimatedFeesForNextPegOutEvent, - activations -> activations.isActive(RSKIP271), - fixedPermission(false) + CallTransaction.Function.fromSignature( + "getEstimatedFeesForNextPegOutEvent", + new String[]{}, + new String[]{"uint256"} + ), + fixedCost(10_000L), + (BridgeMethodExecutorTyped) Bridge::getEstimatedFeesForNextPegOutEvent, + activations -> activations.isActive(RSKIP271), + fixedPermission(false), + CallTypeHelper.ALLOW_STATIC_CALL ); + private static class CallTypeHelper { + private static final Predicate ALLOW_STATIC_CALL = callType -> + callType == MsgType.CALL || callType == MsgType.STATICCALL; + private static final Predicate RESTRICTED_TO_CALL = callType -> callType == MsgType.CALL; + } + private final CallTransaction.Function function; private final CostProvider costProvider; private final Function isEnabledFunction; private final BridgeMethodExecutor executor; private final BridgeCallPermissionProvider callPermissionProvider; + private final Predicate callTypeVerifier; BridgeMethods( CallTransaction.Function function, @@ -715,7 +766,31 @@ public enum BridgeMethods { BridgeMethodExecutor executor, BridgeCallPermissionProvider callPermissionProvider) { - this(function, costProvider, executor, activations -> Boolean.TRUE, callPermissionProvider); + this( + function, + costProvider, + executor, + activations -> Boolean.TRUE, + callPermissionProvider, + CallTypeHelper.RESTRICTED_TO_CALL + ); + } + + BridgeMethods( + CallTransaction.Function function, + CostProvider costProvider, + BridgeMethodExecutor executor, + BridgeCallPermissionProvider callPermissionProvider, + Predicate callTypeVerifier) { + + this( + function, + costProvider, + executor, + activations -> Boolean.TRUE, + callPermissionProvider, + callTypeVerifier + ); } BridgeMethods( @@ -723,13 +798,32 @@ public enum BridgeMethods { CostProvider costProvider, BridgeMethodExecutor executor, Function isEnabled, - BridgeCallPermissionProvider callPermissionProvider) { + BridgeCallPermissionProvider callPermissionProvider, + Predicate callTypeVerifier) { this.function = function; this.costProvider = costProvider; this.executor = executor; this.isEnabledFunction = isEnabled; this.callPermissionProvider = callPermissionProvider; + this.callTypeVerifier = callTypeVerifier; + } + + BridgeMethods( + CallTransaction.Function function, + CostProvider costProvider, + BridgeMethodExecutor executor, + Function isEnabled, + BridgeCallPermissionProvider callPermissionProvider) { + + this( + function, + costProvider, + executor, + isEnabled, + callPermissionProvider, + CallTypeHelper.RESTRICTED_TO_CALL + ); } public static Optional findBySignature(byte[] encoding) { @@ -756,6 +850,10 @@ public boolean onlyAllowsLocalCalls(Bridge bridge, Object[] args) { return callPermissionProvider.getOnlyAllowLocalCallsPermission(bridge, args); } + public boolean acceptsThisTypeOfCall(MsgType callType) { + return callTypeVerifier.test(callType); + } + public interface BridgeCondition { boolean isTrue(Bridge bridge); } @@ -812,9 +910,8 @@ private static BridgeCallPermissionProvider fromMethod(BridgeCallPermissionProvi } private static final Map SIGNATURES = Stream.of(BridgeMethods.values()) - .collect(Collectors.toMap( - m -> new ByteArrayWrapper(m.getFunction().encodeSignature()), - Function.identity() - )); - + .collect(Collectors.toMap( + m -> new ByteArrayWrapper(m.getFunction().encodeSignature()), + Function.identity() + )); } diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeStorageProvider.java b/rskj-core/src/main/java/co/rsk/peg/BridgeStorageProvider.java index b4e4ae3ab47..e1891e4850b 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeStorageProvider.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeStorageProvider.java @@ -947,7 +947,7 @@ public void setPegoutTxSigHash(Sha256Hash sigHash) { pegoutTxSigHashes.add(sigHash); } - private void savePegoutTxSigHashes() { + protected void savePegoutTxSigHashes() { if (!activations.isActive(RSKIP379) || pegoutTxSigHashes == null) { return; } diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java index 0f2523b2aa0..9a037612fc2 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -834,39 +834,56 @@ private void saveNewUTXOs(BtcTransaction btcTx) throws IOException { * @throws IOException */ public void releaseBtc(Transaction rskTx) throws IOException { - Coin value = rskTx.getValue().toBitcoin(); + Coin pegoutValue = rskTx.getValue().toBitcoin(); final RskAddress senderAddress = rskTx.getSender(signatureCache); - //as we can't send btc from contracts we want to send them back to the senderAddressStr + logger.debug( + "[releaseBtc] Releasing {} RBTC from RSK address {} in tx {}", + pegoutValue, + senderAddress, + rskTx.getHash() + ); + + // Peg-out from a smart contract not allowed since it's not possible to derive a BTC address from it if (BridgeUtils.isContractTx(rskTx)) { - logger.trace("Contract {} tried to release funds. Release is just allowed from standard accounts.", rskTx); + logger.trace( + "[releaseBtc] Contract {} tried to release funds. Release is just allowed from EOA", + senderAddress + ); if (activations.isActive(ConsensusRule.RSKIP185)) { - emitRejectEvent(value, senderAddress.toHexString(), RejectedPegoutReason.CALLER_CONTRACT); + emitRejectEvent(pegoutValue, senderAddress, RejectedPegoutReason.CALLER_CONTRACT); return; } else { - throw new Program.OutOfGasException("Contract calling releaseBTC"); + String message = "Contract calling releaseBTC"; + logger.debug("[releaseBtc] {}", message); + throw new Program.OutOfGasException(message); } } Context.propagate(btcContext); NetworkParameters btcParams = bridgeConstants.getBtcParams(); Address btcDestinationAddress = BridgeUtils.recoverBtcAddressFromEthTransaction(rskTx, btcParams); + logger.debug("[releaseBtc] BTC destination address: {}", btcDestinationAddress); - requestRelease(btcDestinationAddress, value, rskTx); + requestRelease(btcDestinationAddress, pegoutValue, rskTx); } private void refundAndEmitRejectEvent(Coin value, RskAddress senderAddress, RejectedPegoutReason reason) { - String senderAddressStr = senderAddress.toHexString(); - logger.trace("Executing a refund of {} to {}. Reason: {}", value, senderAddressStr, reason); + logger.trace( + "[refundAndEmitRejectEvent] Executing a refund of {} to {}. Reason: {}", + value, + senderAddress, + reason + ); rskRepository.transfer( - PrecompiledContracts.BRIDGE_ADDR, - senderAddress, - co.rsk.core.Coin.fromBitcoin(value) + PrecompiledContracts.BRIDGE_ADDR, + senderAddress, + co.rsk.core.Coin.fromBitcoin(value) ); - emitRejectEvent(value, senderAddressStr, reason); + emitRejectEvent(value, senderAddress, reason); } - private void emitRejectEvent(Coin value, String senderAddressStr, RejectedPegoutReason reason) { - eventLogger.logReleaseBtcRequestRejected(senderAddressStr, value, reason); + private void emitRejectEvent(Coin value, RskAddress senderAddress, RejectedPegoutReason reason) { + eventLogger.logReleaseBtcRequestRejected(senderAddress.toHexString(), value, reason); } /** @@ -916,7 +933,7 @@ private void requestRelease(Address destinationAddress, Coin value, Transaction if (optionalRejectedPegoutReason.isPresent()) { logger.warn( - "releaseBtc ignored. To {}. Tx {}. Value {}. Reason: {}", + "[requestRelease] releaseBtc ignored. To {}. Tx {}. Value {}. Reason: {}", destinationAddress, rskTx, value, @@ -939,7 +956,7 @@ private void requestRelease(Address destinationAddress, Coin value, Transaction if (activations.isActive(ConsensusRule.RSKIP185)) { eventLogger.logReleaseBtcRequestReceived(rskTx.getSender(signatureCache).toHexString(), destinationAddress, value); } - logger.info("releaseBtc successful to {}. Tx {}. Value {}.", destinationAddress, rskTx, value); + logger.info("[requestRelease] releaseBtc successful to {}. Tx {}. Value {}.", destinationAddress, rskTx, value); } } @@ -1429,13 +1446,23 @@ public void addSignature(BtcECKey federatorPublicKey, List signatures, b Optional optionalFederation = getFederationFromPublicKey(federatorPublicKey); if (!optionalFederation.isPresent()) { logger.warn( - "Supplied federator public key {} does not belong to any of the federators.", + "[addSignature] Supplied federator btc public key {} does not belong to any of the federators.", federatorPublicKey ); return; } Federation federation = optionalFederation.get(); + Optional federationMember = federation.getMemberByBtcPublicKey(federatorPublicKey); + if (!federationMember.isPresent()){ + logger.warn( + "[addSignature] Supplied federator btc public key {} doest not match any of the federator member btc public keys {}.", + federatorPublicKey, federation.getBtcPublicKeys() + ); + return; + } + FederationMember signingFederationMember = federationMember.get(); + BtcTransaction btcTx = provider.getPegoutsWaitingForSignatures().get(new Keccak256(rskTxHash)); if (btcTx == null) { logger.warn( @@ -1454,10 +1481,10 @@ public void addSignature(BtcECKey federatorPublicKey, List signatures, b } if (!activations.isActive(ConsensusRule.RSKIP326)) { - eventLogger.logAddSignature(federatorPublicKey, btcTx, rskTxHash); + eventLogger.logAddSignature(signingFederationMember, btcTx, rskTxHash); } - processSigning(federatorPublicKey, signatures, rskTxHash, btcTx, federation); + processSigning(signingFederationMember, signatures, rskTxHash, btcTx, federation); } private Optional getFederationFromPublicKey(BtcECKey federatorPublicKey) { @@ -1475,11 +1502,13 @@ private Optional getFederationFromPublicKey(BtcECKey federatorPublic } private void processSigning( - BtcECKey federatorPublicKey, + FederationMember federatorMember, List signatures, byte[] rskTxHash, BtcTransaction btcTx, Federation federation) throws IOException { + + BtcECKey federatorBtcPublicKey = federatorMember.getBtcPublicKey(); // Build input hashes for signatures int numInputs = btcTx.getInputs().size(); @@ -1511,13 +1540,13 @@ private void processSigning( Sha256Hash sighash = sighashes.get(i); - if (!federatorPublicKey.verify(sighash, sig)) { + if (!federatorBtcPublicKey.verify(sighash, sig)) { logger.warn( "Signature {} {} is not valid for hash {} and public key {}", i, ByteUtil.toHexString(sig.encodeToDER()), sighash, - federatorPublicKey + federatorBtcPublicKey ); return; } @@ -1539,24 +1568,24 @@ private void processSigning( Script inputScript = input.getScriptSig(); boolean alreadySignedByThisFederator = BridgeUtils.isInputSignedByThisFederator( - federatorPublicKey, + federatorBtcPublicKey, sighash, input); // Sign the input if it wasn't already if (!alreadySignedByThisFederator) { try { - int sigIndex = inputScript.getSigInsertionIndex(sighash, federatorPublicKey); + int sigIndex = inputScript.getSigInsertionIndex(sighash, federatorBtcPublicKey); inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex, 1, 1); input.setScriptSig(inputScript); logger.debug("Tx input {} for tx {} signed.", i, new Keccak256(rskTxHash)); signed = true; } catch (IllegalStateException e) { Federation retiringFederation = getRetiringFederation(); - if (getActiveFederation().hasBtcPublicKey(federatorPublicKey)) { + if (getActiveFederation().hasBtcPublicKey(federatorBtcPublicKey)) { logger.debug("A member of the active federation is trying to sign a tx of the retiring one"); return; - } else if (retiringFederation != null && retiringFederation.hasBtcPublicKey(federatorPublicKey)) { + } else if (retiringFederation != null && retiringFederation.hasBtcPublicKey(federatorBtcPublicKey)) { logger.debug("A member of the retiring federation is trying to sign a tx of the active one"); return; } @@ -1569,7 +1598,7 @@ private void processSigning( } if(signed && activations.isActive(ConsensusRule.RSKIP326)) { - eventLogger.logAddSignature(federatorPublicKey, btcTx, rskTxHash); + eventLogger.logAddSignature(federatorMember, btcTx, rskTxHash); } if (BridgeUtils.hasEnoughSignatures(btcContext, btcTx)) { diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java index dcc9b9b8235..4c68f041258 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java @@ -25,9 +25,8 @@ import co.rsk.peg.pegininstructions.PeginInstructionsProvider; import co.rsk.peg.utils.BridgeEventLogger; import co.rsk.peg.utils.BridgeEventLoggerImpl; -import java.util.List; - import co.rsk.peg.utils.BrigeEventLoggerLegacyImpl; +import java.util.List; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.Block; diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java b/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java index 0a92ca0fe75..9ded4b80afc 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java @@ -412,7 +412,7 @@ public static boolean isFreeBridgeTx(Transaction rskTx, Constants constants, Act * @return */ public static boolean isContractTx(Transaction rskTx) { - // TODO: this should be refactored to provide a more robust way of checking the transaction origin + // Calls between contracts are done through internal transactions return rskTx.getClass() == org.ethereum.vm.program.InternalTransaction.class; } diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/Federation.java b/rskj-core/src/main/java/co/rsk/peg/federation/Federation.java index 288d8458390..03ca1ad9853 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/Federation.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/Federation.java @@ -76,6 +76,14 @@ public List getMembers() { return members; } + public Optional getMemberByBtcPublicKey(BtcECKey btcPublicKey) { + if (btcPublicKey == null){ + return Optional.empty(); + } + final BtcECKey btcPublicKeyOnly = BtcECKey.fromPublicOnly(btcPublicKey.getPubKey()); + return members.stream().filter(federationMember -> federationMember.getBtcPublicKey().equals(btcPublicKeyOnly)).findAny(); + } + public List getBtcPublicKeys() { // Copy instances since we don't control // immutability of BtcECKey instances diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java index d96e4268c10..7893b704b5a 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java @@ -22,6 +22,7 @@ import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationMember; import co.rsk.peg.pegin.RejectedPeginReason; import org.ethereum.core.Block; import org.ethereum.core.Transaction; @@ -36,7 +37,7 @@ public interface BridgeEventLogger { void logUpdateCollections(Transaction rskTx); - void logAddSignature(BtcECKey federatorPublicKey, BtcTransaction btcTx, byte[] rskTxHash); + void logAddSignature(FederationMember federationMember, BtcTransaction btcTx, byte[] rskTxHash); void logReleaseBtc(BtcTransaction btcTx, byte[] rskTxHash); @@ -77,5 +78,4 @@ default void logBatchPegoutCreated(Sha256Hash btcTxHash, List rskTxHa default void logPegoutConfirmed(Sha256Hash btcTxHash, long pegoutCreationRskBlockNumber) { throw new UnsupportedOperationException(); } - } diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java index 13b92f5596b..7ecbd408971 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java @@ -23,6 +23,7 @@ import co.rsk.crypto.Keccak256; import co.rsk.peg.BridgeEvents; import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationMember; import co.rsk.peg.pegin.RejectedPeginReason; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; @@ -50,12 +51,9 @@ public class BridgeEventLoggerImpl implements BridgeEventLogger { private static final byte[] BRIDGE_CONTRACT_ADDRESS = PrecompiledContracts.BRIDGE_ADDR.getBytes(); private final BridgeConstants bridgeConstants; - private final SignatureCache signatureCache; - - private List logs; - - private ActivationConfig.ForBlock activations; + private final List logs; + private final ActivationConfig.ForBlock activations; public BridgeEventLoggerImpl(BridgeConstants bridgeConstants, ActivationConfig.ForBlock activations, List logs, SignatureCache signatureCache) { this.activations = activations; @@ -75,10 +73,22 @@ public void logUpdateCollections(Transaction rskTx) { } @Override - public void logAddSignature(BtcECKey federatorPublicKey, BtcTransaction btcTx, byte[] rskTxHash) { - ECKey key = ECKey.fromPublicOnly(federatorPublicKey.getPubKey()); - String federatorRskAddress = ByteUtil.toHexString(key.getAddress()); - logAddSignatureInSolidityFormat(rskTxHash, federatorRskAddress, federatorPublicKey); + public void logAddSignature(FederationMember federationMember, BtcTransaction btcTx, byte[] rskTxHash) { + ECKey federatorPublicKey = getFederatorPublicKey(federationMember); + String federatorRskAddress = ByteUtil.toHexString(federatorPublicKey.getAddress()); + logAddSignatureInSolidityFormat(rskTxHash, federatorRskAddress, federationMember.getBtcPublicKey()); + } + + private ECKey getFederatorPublicKey(FederationMember federationMember) { + if (!shouldUseRskPublicKey()) { + return ECKey.fromPublicOnly(federationMember.getBtcPublicKey().getPubKey()); + } + + return federationMember.getRskPublicKey(); + } + + private boolean shouldUseRskPublicKey() { + return activations.isActive(ConsensusRule.RSKIP415); } private void logAddSignatureInSolidityFormat(byte[] rskTxHash, String federatorRskAddress, BtcECKey federatorPublicKey) { diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java index 18a62fbb12d..1c9e520f53f 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java @@ -23,6 +23,7 @@ import co.rsk.peg.Bridge; import co.rsk.peg.DeprecatedMethodCallException; import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationMember; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.Block; @@ -52,7 +53,7 @@ public class BrigeEventLoggerLegacyImpl implements BridgeEventLogger { private final BridgeConstants bridgeConstants; private final ActivationConfig.ForBlock activations; private final SignatureCache signatureCache; - private List logs; + private final List logs; public BrigeEventLoggerLegacyImpl(BridgeConstants bridgeConstants, ActivationConfig.ForBlock activations, List logs, SignatureCache signatureCache) { this.bridgeConstants = bridgeConstants; @@ -77,7 +78,7 @@ public void logUpdateCollections(Transaction rskTx) { } @Override - public void logAddSignature(BtcECKey federatorPublicKey, BtcTransaction btcTx, byte[] rskTxHash) { + public void logAddSignature(FederationMember federationMember, BtcTransaction btcTx, byte[] rskTxHash) { if (activations.isActive(ConsensusRule.RSKIP146)) { throw new DeprecatedMethodCallException( "Calling BrigeEventLoggerLegacyImpl.logAddSignature method after RSKIP146 activation" @@ -85,7 +86,7 @@ public void logAddSignature(BtcECKey federatorPublicKey, BtcTransaction btcTx, b } List topics = Collections.singletonList(Bridge.ADD_SIGNATURE_TOPIC); byte[] data = RLP.encodeList(RLP.encodeString(btcTx.getHashAsString()), - RLP.encodeElement(federatorPublicKey.getPubKeyHash()), + RLP.encodeElement(federationMember.getBtcPublicKey().getPubKeyHash()), RLP.encodeElement(rskTxHash)); this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, topics, data)); diff --git a/rskj-core/src/main/java/co/rsk/remasc/Remasc.java b/rskj-core/src/main/java/co/rsk/remasc/Remasc.java index db430403df6..e59291f21d3 100644 --- a/rskj-core/src/main/java/co/rsk/remasc/Remasc.java +++ b/rskj-core/src/main/java/co/rsk/remasc/Remasc.java @@ -18,10 +18,13 @@ package co.rsk.remasc; +import co.rsk.config.BridgeConstants; import co.rsk.config.RemascConfig; import co.rsk.core.Coin; import co.rsk.core.RskAddress; import co.rsk.core.bc.SelectionRule; +import co.rsk.peg.BridgeStorageProvider; +import co.rsk.peg.FederationSupport; import co.rsk.rpc.modules.trace.ProgramSubtrace; import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; @@ -32,6 +35,7 @@ import org.ethereum.core.Transaction; import org.ethereum.db.BlockStore; import org.ethereum.vm.LogInfo; +import org.ethereum.vm.PrecompiledContracts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +51,7 @@ public class Remasc { private static final Logger logger = LoggerFactory.getLogger(Remasc.class); private final Constants constants; - private final ActivationConfig activationConfig; + private final ActivationConfig.ForBlock activations; private final Repository repository; private final BlockStore blockStore; private final RemascConfig remascConstants; @@ -78,7 +82,7 @@ public Remasc( this.provider = new RemascStorageProvider(repository, contractAddress); this.feesPayer = new RemascFeesPayer(repository, contractAddress); this.constants = constants; - this.activationConfig = activationConfig; + this.activations = activationConfig.forBlock(executionBlock.getNumber()); } public void save() { @@ -109,11 +113,11 @@ void processMinersFees() { throw new RemascInvalidInvocationException("Invoked Remasc outside last tx of the block"); } - long blockNbr = executionBlock.getNumber(); + long executionBlockNumber = executionBlock.getNumber(); - long processingBlockNumber = blockNbr - remascConstants.getMaturity(); - if (processingBlockNumber < 1 ) { - logger.debug("First block has not reached maturity yet, current block is {}", blockNbr); + long candidateBlockNumberToReward = executionBlockNumber - remascConstants.getMaturity(); + if (candidateBlockNumberToReward < 1 ) { + logger.debug("First block has not reached maturity yet, current block is {}", executionBlockNumber); return; } @@ -145,20 +149,20 @@ void processMinersFees() { rewardBalance = rewardBalance.add(processingBlockReward); provider.setRewardBalance(rewardBalance); - if (processingBlockNumber - remascConstants.getSyntheticSpan() < 0 ) { + if (candidateBlockNumberToReward - remascConstants.getSyntheticSpan() < 0 ) { logger.debug("First block has not reached maturity+syntheticSpan yet, current block is {}", executionBlock.getNumber()); return; } - List siblings = getSiblingsToReward(descendantsBlocks, processingBlockNumber); + List siblings = getSiblingsToReward(descendantsBlocks, candidateBlockNumberToReward); boolean previousBrokenSelectionRule = provider.getBrokenSelectionRule(); boolean brokenSelectionRule = SelectionRule.isBrokenSelectionRule(processingBlockHeader, siblings); provider.setBrokenSelectionRule(!siblings.isEmpty() && brokenSelectionRule); // Takes from rewardBalance this block's height reward. Coin syntheticReward = rewardBalance.divide(BigInteger.valueOf(remascConstants.getSyntheticSpan())); - boolean isRskip85Enabled = activationConfig.isActive(ConsensusRule.RSKIP85, blockNbr); - if (isRskip85Enabled) { + + if (shouldRewardBeAboveMinimumPayableGas()) { BigInteger minimumPayableGas = constants.getMinimumPayableGas(); Coin minPayableFees = executionBlock.getMinimumGasPrice().multiply(minimumPayableGas); if (syntheticReward.compareTo(minPayableFees) < 0) { @@ -176,7 +180,7 @@ void processMinersFees() { Coin payToRskLabs = syntheticReward.divide(BigInteger.valueOf(remascConstants.getRskLabsDivisor())); feesPayer.payMiningFees(processingBlockHeader.getHash().getBytes(), payToRskLabs, rskLabsAddress, logs); syntheticReward = syntheticReward.subtract(payToRskLabs); - Coin payToFederation = payToFederation(constants, isRskip85Enabled, processingBlock, processingBlockHeader, syntheticReward); + Coin payToFederation = payToFederation(constants, processingBlock, syntheticReward); syntheticReward = syntheticReward.subtract(payToFederation); if (!siblings.isEmpty()) { @@ -193,23 +197,38 @@ void processMinersFees() { } } + private boolean shouldRewardBeAboveMinimumPayableGas() { + return activations.isActive(ConsensusRule.RSKIP85); + } + RskAddress getRskLabsAddress() { - boolean isRskip218Enabled = activationConfig.isActive(ConsensusRule.RSKIP218, executionBlock.getNumber()); + boolean isRskip218Enabled = activations.isActive(ConsensusRule.RSKIP218); return isRskip218Enabled ? remascConstants.getRskLabsAddressRskip218() : remascConstants.getRskLabsAddress(); } - private Coin payToFederation(Constants constants, boolean isRskip85Enabled, Block processingBlock, BlockHeader processingBlockHeader, Coin syntheticReward) { - RemascFederationProvider federationProvider = new RemascFederationProvider(activationConfig, constants.getBridgeConstants(), repository, processingBlock); + private Coin payToFederation(Constants constants, Block processingBlock, Coin syntheticReward) { + BridgeConstants bridgeConstants = constants.getBridgeConstants(); + + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( + repository, + PrecompiledContracts.BRIDGE_ADDR, + bridgeConstants, + activations + ); + + FederationSupport federationSupport = new FederationSupport(bridgeConstants, bridgeStorageProvider, processingBlock, activations); + + RemascFederationProvider federationProvider = new RemascFederationProvider(activations, federationSupport); Coin federationReward = syntheticReward.divide(BigInteger.valueOf(remascConstants.getFederationDivisor())); Coin payToFederation = provider.getFederationBalance().add(federationReward); - byte[] processingBlockHash = processingBlockHeader.getHash().getBytes(); + byte[] processingBlockHash = processingBlock.getHeader().getHash().getBytes(); int nfederators = federationProvider.getFederationSize(); Coin[] payAndRemainderToFederator = payToFederation.divideAndRemainder(BigInteger.valueOf(nfederators)); Coin payToFederator = payAndRemainderToFederator[0]; Coin restToLastFederator = payAndRemainderToFederator[1]; - if (isRskip85Enabled) { + if (shouldRewardBeAboveMinimumPayableGas()) { BigInteger minimumFederatorPayableGas = constants.getFederatorMinimumPayableGas(); Coin minPayableFederatorFees = executionBlock.getMinimumGasPrice().multiply(minimumFederatorPayableGas); if (payToFederator.compareTo(minPayableFederatorFees) < 0) { diff --git a/rskj-core/src/main/java/co/rsk/remasc/RemascFederationProvider.java b/rskj-core/src/main/java/co/rsk/remasc/RemascFederationProvider.java index ae5c8fcc37b..de2b294947d 100644 --- a/rskj-core/src/main/java/co/rsk/remasc/RemascFederationProvider.java +++ b/rskj-core/src/main/java/co/rsk/remasc/RemascFederationProvider.java @@ -17,36 +17,26 @@ */ package co.rsk.remasc; -import co.rsk.config.BridgeConstants; import co.rsk.core.RskAddress; -import co.rsk.peg.BridgeStorageProvider; import co.rsk.peg.FederationSupport; +import co.rsk.peg.federation.FederationMember; import org.ethereum.config.blockchain.upgrades.ActivationConfig; -import org.ethereum.core.Block; -import org.ethereum.core.Repository; +import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.crypto.ECKey; -import org.ethereum.vm.PrecompiledContracts; /** * Created by ajlopez on 14/11/2017. */ public class RemascFederationProvider { + private final FederationSupport federationSupport; + private final ActivationConfig.ForBlock activations; public RemascFederationProvider( - ActivationConfig activationConfig, - BridgeConstants bridgeConstants, - Repository repository, - Block processingBlock) { - - ActivationConfig.ForBlock activations = activationConfig.forBlock(processingBlock.getNumber()); - BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( - repository, - PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants, - activations - ); - this.federationSupport = new FederationSupport(bridgeConstants, bridgeStorageProvider, processingBlock, activations); + ActivationConfig.ForBlock activations, + FederationSupport federationSupport) { + this.federationSupport = federationSupport; + this.activations = activations; } public int getFederationSize() { @@ -54,7 +44,24 @@ public int getFederationSize() { } public RskAddress getFederatorAddress(int n) { - byte[] publicKey = this.federationSupport.getFederatorBtcPublicKey(n); + if(!activations.isActive(ConsensusRule.RSKIP415)) { + return getRskAddressFromBtcKey(n); + } + return getRskAddressFromRskKey(n); + } + + private RskAddress getRskAddressFromBtcKey(int n) { + byte[] btcPublicKey = this.federationSupport.getFederatorBtcPublicKey(n); + return getRskAddressFromKey(btcPublicKey); + } + + private RskAddress getRskAddressFromRskKey(int n) { + byte[] rskPublicKey = this.federationSupport.getFederatorPublicKeyOfType(n, FederationMember.KeyType.RSK); + return getRskAddressFromKey(rskPublicKey); + } + + private RskAddress getRskAddressFromKey(byte[] publicKey) { return new RskAddress(ECKey.fromPublicOnly(publicKey).getAddress()); } + } diff --git a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java index d5af5f544ae..a0641854f13 100644 --- a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java +++ b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java @@ -36,8 +36,8 @@ public ActivationConfig(Map activationHeights) { List missing = new ArrayList<>(Arrays.asList(ConsensusRule.values())); missing.removeAll(activationHeights.keySet()); throw new IllegalArgumentException(String.format( - "The configuration must contain all consensus rule values but is missing [%s]", - missing.stream().map(ConsensusRule::getConfigKey).collect(Collectors.joining(", ")) + "The configuration must contain all consensus rule values but is missing [%s]", + missing.stream().map(ConsensusRule::getConfigKey).collect(Collectors.joining(", ")) )); } diff --git a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java index 5ae8580a070..9a9efecf657 100644 --- a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java +++ b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java @@ -91,6 +91,8 @@ public enum ConsensusRule { RSKIP398("rskip398"), RSKIP400("rskip400"), // From EIP-2028 calldata gas cost reduction RSKIP412("rskip412"), // From EIP-3198 BASEFEE opcode + RSKIP415("rskip415"), + RSKIP417("rskip417"), ; private String configKey; diff --git a/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgs.java b/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgs.java index ccbd160bc9e..9a395805e6d 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgs.java +++ b/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgs.java @@ -44,9 +44,7 @@ public class PrecompiledContractArgs { */ @Nullable private ProgramInvoke programInvoke; - - public PrecompiledContractArgs() { - } + private MessageCall.MsgType msgType; void setTransaction(Transaction transaction) { this.transaction = transaction; @@ -76,6 +74,10 @@ void setProgramInvoke(ProgramInvoke programInvoke) { this.programInvoke = programInvoke; } + public void setMsgType(MessageCall.MsgType msgType) { + this.msgType = msgType; + } + public Transaction getTransaction() { return transaction; } @@ -103,4 +105,8 @@ public List getLogs() { public ProgramInvoke getProgramInvoke() { return programInvoke; } + + public MessageCall.MsgType getMsgType() { + return msgType; + } } diff --git a/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgsBuilder.java b/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgsBuilder.java index 75d41347eb6..5acdbe87ff4 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgsBuilder.java +++ b/rskj-core/src/main/java/org/ethereum/vm/PrecompiledContractArgsBuilder.java @@ -25,7 +25,9 @@ import org.ethereum.db.ReceiptStore; import org.ethereum.vm.program.invoke.ProgramInvoke; +import javax.annotation.Nonnull; import java.util.List; +import java.util.Objects; public class PrecompiledContractArgsBuilder { private Transaction transaction; @@ -35,8 +37,11 @@ public class PrecompiledContractArgsBuilder { private ReceiptStore receiptStore; private List logs; private ProgramInvoke programInvoke; + private MessageCall.MsgType msgType; - private PrecompiledContractArgsBuilder() {} + private PrecompiledContractArgsBuilder() { + msgType = MessageCall.MsgType.CALL; + } public static PrecompiledContractArgsBuilder builder() { return new PrecompiledContractArgsBuilder(); @@ -77,6 +82,11 @@ public PrecompiledContractArgsBuilder programInvoke(ProgramInvoke programInvoke) return this; } + public PrecompiledContractArgsBuilder msgType(@Nonnull MessageCall.MsgType msgType) { + this.msgType = Objects.requireNonNull(msgType); + return this; + } + public PrecompiledContractArgs build() { PrecompiledContractArgs args = new PrecompiledContractArgs(); args.setTransaction(this.transaction); @@ -86,6 +96,7 @@ public PrecompiledContractArgs build() { args.setReceiptStore(this.receiptStore); args.setLogs(this.logs); args.setProgramInvoke(this.programInvoke); + args.setMsgType(this.msgType); return args; } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java index 2effc052c13..5a799524d32 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -1422,6 +1422,7 @@ public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contra .blockStore(this.invoke.getBlockStore()) .logs(result.getLogInfoList()) .programInvoke(this.invoke) + .msgType(msg.getType()) .build(); contract.init(args); diff --git a/rskj-core/src/main/resources/config/main.conf b/rskj-core/src/main/resources/config/main.conf index 6714121446c..d502c49382a 100644 --- a/rskj-core/src/main/resources/config/main.conf +++ b/rskj-core/src/main/resources/config/main.conf @@ -12,7 +12,7 @@ blockchain.config { hop400 = 4598500, hop401 = 4976300, fingerroot500 = 5468000, - arrowhead600 = -1 + arrowhead600 = 6223700 } } diff --git a/rskj-core/src/main/resources/config/testnet.conf b/rskj-core/src/main/resources/config/testnet.conf index fa1956af5ee..b07f13706f0 100644 --- a/rskj-core/src/main/resources/config/testnet.conf +++ b/rskj-core/src/main/resources/config/testnet.conf @@ -12,7 +12,7 @@ blockchain.config { hop400 = 3103000, hop401 = 3362200, fingerroot500 = 4015800, - arrowhead600 = -1 + arrowhead600 = 4927100 }, consensusRules = { rskip97 = -1, # disable orchid difficulty drop diff --git a/rskj-core/src/main/resources/expected.conf b/rskj-core/src/main/resources/expected.conf index b48f4432a46..bd1651f9365 100644 --- a/rskj-core/src/main/resources/expected.conf +++ b/rskj-core/src/main/resources/expected.conf @@ -90,6 +90,8 @@ blockchain = { rskip398 = rskip400 = rskip412 = + rskip415 = + rskip417 = } } gc = { diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index f82fa66a6d9..3de9eba1319 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -77,6 +77,8 @@ blockchain = { rskip398 = arrowhead600 rskip400 = arrowhead600 rskip412 = arrowhead600 + rskip415 = arrowhead600 + rskip417 = arrowhead600 } } gc = { diff --git a/rskj-core/src/test/java/co/rsk/config/BridgeConstantsTest.java b/rskj-core/src/test/java/co/rsk/config/BridgeConstantsTest.java index a31c3d9e948..0a47296d80c 100644 --- a/rskj-core/src/test/java/co/rsk/config/BridgeConstantsTest.java +++ b/rskj-core/src/test/java/co/rsk/config/BridgeConstantsTest.java @@ -124,8 +124,8 @@ void test_getMinimumPeginTxValue(BridgeConstants bridgeConstants, boolean isRSKI private static Stream getBtcHeightWhenPegoutTxIndexActivatesArgProvider() { return Stream.of( - Arguments.of(BridgeMainNetConstants.getInstance(), 100), - Arguments.of(BridgeTestNetConstants.getInstance(), 150), + Arguments.of(BridgeMainNetConstants.getInstance(), 837589), + Arguments.of(BridgeTestNetConstants.getInstance(), 2589553), Arguments.of(BridgeRegTestConstants.getInstance(), 250) ); } @@ -143,7 +143,7 @@ void test_getBtcHeightWhenPegoutTxIndexActivates(BridgeConstants bridgeConstants private static Stream getPegoutTxIndexGracePeriodInBtcBlocksArgProvider() { return Stream.of( Arguments.of(BridgeMainNetConstants.getInstance(), 4_320), - Arguments.of(BridgeTestNetConstants.getInstance(), 4_320), + Arguments.of(BridgeTestNetConstants.getInstance(), 1_440), Arguments.of(BridgeRegTestConstants.getInstance(), 100) ); } diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeStorageProviderPegoutTxIndexTests.java b/rskj-core/src/test/java/co/rsk/peg/BridgeStorageProviderPegoutTxIndexTests.java index 2f5b725423b..668bc9427d5 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeStorageProviderPegoutTxIndexTests.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeStorageProviderPegoutTxIndexTests.java @@ -18,9 +18,11 @@ import org.mockito.Mockito; import java.io.IOException; +import java.util.Collections; import java.util.stream.Stream; import static co.rsk.peg.BridgeStorageIndexKey.PEGOUT_TX_SIG_HASH; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP134; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -299,7 +301,7 @@ void setPegoutTxSigHash_null(boolean isRskip379HardForkActive) throws IOExceptio // Act provider.setPegoutTxSigHash(null); - provider.save(); + provider.savePegoutTxSigHashes(); // Assert verify(repository, never()).getStorageBytes( @@ -332,7 +334,7 @@ void setPegoutTxSigHash_non_null(boolean isRskip379HardForkActive, Sha256Hash si // Act provider.setPegoutTxSigHash(sigHash); - provider.save(); + provider.savePegoutTxSigHashes(); // Assert if (isRskip379HardForkActive) { diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java index d1775014b4c..1eb1abd5917 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java @@ -3,12 +3,15 @@ import java.time.Instant; import java.math.BigInteger; import java.util.*; +import java.util.stream.Stream; import co.rsk.config.BridgeConstants; +import co.rsk.config.BridgeMainNetConstants; import co.rsk.peg.federation.*; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.*; +import org.ethereum.crypto.ECKey; import org.ethereum.util.RLP; import org.ethereum.util.RLPList; import org.ethereum.vm.LogInfo; @@ -39,6 +42,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static co.rsk.peg.PegTestUtils.*; import static org.hamcrest.Matchers.hasItem; @@ -50,15 +56,17 @@ class BridgeSupportAddSignatureTest { - private ActivationConfig.ForBlock activationsBeforeForks; - private ActivationConfig.ForBlock activationsAfterForks; private static final RskAddress contractAddress = PrecompiledContracts.BRIDGE_ADDR; - private BridgeSupportBuilder bridgeSupportBuilder; + private final BridgeConstants bridgeConstantsRegtest = BridgeRegTestConstants.getInstance(); private final NetworkParameters btcRegTestParams = bridgeConstantsRegtest.getBtcParams(); private final Instant creationTime = Instant.ofEpochMilli(1000L); private final long creationBlockNumber = 0L; + private ActivationConfig.ForBlock activationsBeforeForks; + private ActivationConfig.ForBlock activationsAfterForks; + private BridgeSupportBuilder bridgeSupportBuilder; + @BeforeEach void setUpOnEachTest() { activationsBeforeForks = ActivationConfigsForTest.genesis().forBlock(0); @@ -79,31 +87,38 @@ void addSignature_fedPubKey_belongs_to_active_federation() throws Exception { activeFedKeys.sort(BtcECKey.PUBKEY_COMPARATOR); List activeFedMembers = FederationTestUtils.getFederationMembersWithBtcKeys(activeFedKeys); - FederationArgs activeFedArgs = - new FederationArgs(activeFedMembers, creationTime, creationBlockNumber, btcRegTestParams); + FederationArgs activeFedArgs = new FederationArgs( + activeFedMembers, + creationTime, + creationBlockNumber, + btcRegTestParams + ); Federation activeFederation = FederationFactory.buildStandardMultiSigFederation(activeFedArgs); BridgeStorageProvider provider = mock(BridgeStorageProvider.class); BridgeSupport bridgeSupport = new BridgeSupport( - bridgeConstantsRegtest, - provider, - mock(BridgeEventLogger.class), - new BtcLockSenderProvider(), - new PeginInstructionsProvider(), - mock(Repository.class), - mock(Block.class), - new Context(bridgeConstantsRegtest.getBtcParams()), - mockFederationSupport, - null, - null, - null + bridgeConstantsRegtest, + provider, + mock(BridgeEventLogger.class), + new BtcLockSenderProvider(), + new PeginInstructionsProvider(), + mock(Repository.class), + mock(Block.class), + new Context(bridgeConstantsRegtest.getBtcParams()), + mockFederationSupport, + null, + null, + null ); when(mockFederationSupport.getActiveFederation()).thenReturn(activeFederation); when(provider.getPegoutsWaitingForSignatures()).thenReturn(new TreeMap<>()); - bridgeSupport.addSignature(BtcECKey.fromPrivate(Hex.decode("fa01")), null, - createHash3(1).getBytes()); + bridgeSupport.addSignature( + BtcECKey.fromPrivate(Hex.decode("fa01")), + null, + createHash3(1).getBytes() + ); verify(provider, times(1)).getPegoutsWaitingForSignatures(); } @@ -114,18 +129,18 @@ void addSignature_fedPubKey_belongs_to_retiring_federation() throws Exception { FederationSupport mockFederationSupport = mock(FederationSupport.class); BridgeStorageProvider provider = mock(BridgeStorageProvider.class); BridgeSupport bridgeSupport = new BridgeSupport( - bridgeConstantsRegtest, - provider, - mock(BridgeEventLogger.class), - new BtcLockSenderProvider(), - new PeginInstructionsProvider(), - mock(Repository.class), - mock(Block.class), - new Context(bridgeConstantsRegtest.getBtcParams()), - mockFederationSupport, - null, - null, - null + bridgeConstantsRegtest, + provider, + mock(BridgeEventLogger.class), + new BtcLockSenderProvider(), + new PeginInstructionsProvider(), + mock(Repository.class), + mock(Block.class), + new Context(bridgeConstantsRegtest.getBtcParams()), + mockFederationSupport, + null, + null, + null ); // Creates retiring federation @@ -135,8 +150,12 @@ void addSignature_fedPubKey_belongs_to_retiring_federation() throws Exception { retiringFedKeys.sort(BtcECKey.PUBKEY_COMPARATOR); List retiringFedMembers = FederationTestUtils.getFederationMembersWithBtcKeys(retiringFedKeys); - FederationArgs retiringFedArgs = - new FederationArgs(retiringFedMembers, creationTime, creationBlockNumber, btcRegTestParams); + FederationArgs retiringFedArgs = new FederationArgs( + retiringFedMembers, + creationTime, + creationBlockNumber, + btcRegTestParams + ); Federation retiringFederation = FederationFactory.buildStandardMultiSigFederation(retiringFedArgs); // Creates active federation @@ -146,16 +165,23 @@ void addSignature_fedPubKey_belongs_to_retiring_federation() throws Exception { ); activeFederationKeys.sort(BtcECKey.PUBKEY_COMPARATOR); List activeFedMembers = FederationTestUtils.getFederationMembersWithBtcKeys(activeFederationKeys); - FederationArgs activeFedArgs = - new FederationArgs(activeFedMembers, creationTime, creationBlockNumber, btcRegTestParams); + FederationArgs activeFedArgs = new FederationArgs( + activeFedMembers, + creationTime, + creationBlockNumber, + btcRegTestParams + ); Federation activeFederation = FederationFactory.buildStandardMultiSigFederation(activeFedArgs); when(mockFederationSupport.getActiveFederation()).thenReturn(activeFederation); when(mockFederationSupport.getRetiringFederation()).thenReturn(retiringFederation); when(provider.getPegoutsWaitingForSignatures()).thenReturn(new TreeMap<>()); - bridgeSupport.addSignature(BtcECKey.fromPrivate(Hex.decode("fa01")), null, - createHash3(1).getBytes()); + bridgeSupport.addSignature( + BtcECKey.fromPrivate(Hex.decode("fa01")), + null, + createHash3(1).getBytes() + ); verify(provider, times(1)).getPegoutsWaitingForSignatures(); } @@ -166,18 +192,18 @@ void addSignature_fedPubKey_no_belong_to_retiring_or_active_federation() throws FederationSupport mockFederationSupport = mock(FederationSupport.class); BridgeStorageProvider provider = mock(BridgeStorageProvider.class); BridgeSupport bridgeSupport = new BridgeSupport( - bridgeConstantsRegtest, - provider, - mock(BridgeEventLogger.class), - new BtcLockSenderProvider(), - new PeginInstructionsProvider(), - mock(Repository.class), - mock(Block.class), - new Context(bridgeConstantsRegtest.getBtcParams()), - mockFederationSupport, - null, - null, - null + bridgeConstantsRegtest, + provider, + mock(BridgeEventLogger.class), + new BtcLockSenderProvider(), + new PeginInstructionsProvider(), + mock(Repository.class), + mock(Block.class), + new Context(bridgeConstantsRegtest.getBtcParams()), + mockFederationSupport, + null, + null, + null ); // Creates retiring federation @@ -187,27 +213,38 @@ void addSignature_fedPubKey_no_belong_to_retiring_or_active_federation() throws ); retiringFedKeys.sort(BtcECKey.PUBKEY_COMPARATOR); List retiringFedMembers = FederationTestUtils.getFederationMembersWithBtcKeys(retiringFedKeys); - FederationArgs retiringFedArgs = - new FederationArgs(retiringFedMembers, creationTime, creationBlockNumber, btcRegTestParams); + FederationArgs retiringFedArgs = new FederationArgs( + retiringFedMembers, + creationTime, + creationBlockNumber, + btcRegTestParams + ); Federation retiringFederation = FederationFactory.buildStandardMultiSigFederation(retiringFedArgs); // Creates active federation List activeFederationKeys = Arrays.asList( - BtcECKey.fromPrivate(Hex.decode("fa03")), - BtcECKey.fromPrivate(Hex.decode("fa04")) + BtcECKey.fromPrivate(Hex.decode("fa03")), + BtcECKey.fromPrivate(Hex.decode("fa04")) ); activeFederationKeys.sort(BtcECKey.PUBKEY_COMPARATOR); List activeFedMembers = FederationTestUtils.getFederationMembersWithBtcKeys(activeFederationKeys); - FederationArgs activeFedArgs = - new FederationArgs(activeFedMembers, creationTime, creationBlockNumber, btcRegTestParams); + FederationArgs activeFedArgs = new FederationArgs( + activeFedMembers, + creationTime, + creationBlockNumber, + btcRegTestParams + ); Federation activeFederation = FederationFactory.buildStandardMultiSigFederation(activeFedArgs); when(mockFederationSupport.getActiveFederation()).thenReturn(activeFederation); when(mockFederationSupport.getRetiringFederation()).thenReturn(retiringFederation); - bridgeSupport.addSignature(BtcECKey.fromPrivate(Hex.decode("fa05")), null, - createHash3(1).getBytes()); + bridgeSupport.addSignature( + BtcECKey.fromPrivate(Hex.decode("fa05")), + null, + createHash3(1).getBytes() + ); verify(provider, times(0)).getPegoutsWaitingForSignatures(); } @@ -217,13 +254,16 @@ void addSignature_fedPubKey_no_belong_to_active_federation_no_existing_retiring_ BridgeStorageProvider provider = mock(BridgeStorageProvider.class); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstantsRegtest) - .withProvider(provider) - .withEventLogger(mock(BridgeEventLogger.class)) - .build(); - - bridgeSupport.addSignature(BtcECKey.fromPrivate(Hex.decode("fa03")), null, - createHash3(1).getBytes()); + .withBridgeConstants(bridgeConstantsRegtest) + .withProvider(provider) + .withEventLogger(mock(BridgeEventLogger.class)) + .build(); + + bridgeSupport.addSignature( + BtcECKey.fromPrivate(Hex.decode("fa03")), + null, + createHash3(1).getBytes() + ); verify(provider, times(0)).getPegoutsWaitingForSignatures(); } @@ -235,23 +275,31 @@ void addSignatureToMissingTransaction() throws Exception { Repository repository = createRepository(); BridgeStorageProvider providerForSupport = new BridgeStorageProvider( - repository, - PrecompiledContracts.BRIDGE_ADDR, - bridgeConstantsRegtest, - activationsBeforeForks + repository, + PrecompiledContracts.BRIDGE_ADDR, + bridgeConstantsRegtest, + activationsBeforeForks ); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstantsRegtest) - .withProvider(providerForSupport) - .withRepository(repository) - .build(); - - bridgeSupport.addSignature(federation.getBtcPublicKeys().get(0), null, createHash().getBytes()); + .withBridgeConstants(bridgeConstantsRegtest) + .withProvider(providerForSupport) + .withRepository(repository) + .build(); + + bridgeSupport.addSignature( + federation.getBtcPublicKeys().get(0), + null, + createHash().getBytes() + ); bridgeSupport.save(); - BridgeStorageProvider provider = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstantsRegtest, activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider( + repository, + PrecompiledContracts.BRIDGE_ADDR, + bridgeConstantsRegtest, + activationsBeforeForks + ); assertTrue(provider.getPegoutsWaitingForSignatures().isEmpty()); } @@ -369,12 +417,13 @@ private void test_addSignature_EventEmitted(boolean rskip326Active, boolean useV } BtcECKey federatorPubKey = BridgeRegTestConstants.REGTEST_FEDERATION_PUBLIC_KEYS.get(indexOfKeyToSignWith); + FederationMember federationMember = FederationTestUtils.getFederationMemberWithKey(federatorPubKey); bridgeSupport.addSignature(federatorPubKey, derEncodedSigs, rskTxHash.getBytes()); if(shouldSignTwice) { bridgeSupport.addSignature(federatorPubKey, derEncodedSigs, rskTxHash.getBytes()); } - verify(eventLogger, times(wantedNumberOfInvocations)).logAddSignature(federatorPubKey, btcTx, rskTxHash.getBytes()); + verify(eventLogger, times(wantedNumberOfInvocations)).logAddSignature(federationMember, btcTx, rskTxHash.getBytes()); } @Test @@ -534,6 +583,128 @@ void addSignatureMultipleInputsPartiallyValid() throws Exception { } } + private static Stream addSignatureArgProvider() { + ActivationConfig.ForBlock fingerrootActivations = ActivationConfigsForTest.fingerroot500().forBlock(0); + ActivationConfig.ForBlock arrowheadActivations = ActivationConfigsForTest.arrowhead600().forBlock(0); + + BtcECKey btcKey = BtcECKey.fromPrivate(Hex.decode("000000000000000000000000000000000000000000000000000000000000015e")); // 350 + ECKey rskKey = ECKey.fromPrivate(Hex.decode("000000000000000000000000000000000000000000000000000000000000015f")); // 351 + ECKey mstKey = ECKey.fromPrivate(Hex.decode("0000000000000000000000000000000000000000000000000000000000000160")); // 352 + + String addressDerivedFromBtcKey = "dbc29273d4de3d5645e308c7e629d28d4499b3d3"; + String addressDerivedFromRskKey = "74891a05ad4d7ec87c1cffe9bd00bb4e1382b586"; + + FederationMember singleKeyFedMember = new FederationMember( + btcKey, + ECKey.fromPublicOnly(btcKey.getPubKey()), + ECKey.fromPublicOnly(btcKey.getPubKey()) + ); + + FederationMember multiKeyFedMember = new FederationMember( + btcKey, + rskKey, + mstKey + ); + + return Stream.of( + Arguments.of(fingerrootActivations, singleKeyFedMember, btcKey, addressDerivedFromBtcKey), + Arguments.of(fingerrootActivations, multiKeyFedMember, btcKey, addressDerivedFromBtcKey), + Arguments.of(arrowheadActivations, singleKeyFedMember, btcKey, addressDerivedFromBtcKey), // Given this is a single key fed member, the rsk address is equal to the one obtained from btc key + Arguments.of(arrowheadActivations, multiKeyFedMember, btcKey, addressDerivedFromRskKey) + ); + } + + @ParameterizedTest + @MethodSource("addSignatureArgProvider") + void addSignature(ActivationConfig.ForBlock activations, FederationMember federationMemberToSignWith, BtcECKey privateKeyToSignWith, String expectedRskAddress) throws Exception { + // Arrange + BridgeMainNetConstants bridgeMainNetConstants = BridgeMainNetConstants.getInstance(); + NetworkParameters btcParams = bridgeMainNetConstants.getBtcParams(); + + List federationMembers = FederationTestUtils.getFederationMembersFromPks(150, 250); + federationMembers.add(federationMemberToSignWith); + + FederationArgs federationArgs = new FederationArgs( + federationMembers, + Instant.EPOCH, + 0, + btcParams + ); + Federation federation = FederationFactory.buildStandardMultiSigFederation(federationArgs); + + BridgeStorageProvider provider = mock(BridgeStorageProvider.class); + when(provider.getNewFederation()) + .thenReturn(federation); + LinkedList eventLogs = new LinkedList<>(); + BlockTxSignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); + BridgeEventLogger eventLogger = new BridgeEventLoggerImpl(bridgeMainNetConstants, activations, eventLogs, signatureCache); + + // Build prev btc tx + BtcTransaction prevTx = new BtcTransaction(btcParams); + TransactionOutput prevOut = new TransactionOutput(btcParams, prevTx, Coin.FIFTY_COINS, federation.getAddress()); + prevTx.addOutput(prevOut); + + // Build btc tx to be signed + BtcTransaction btcTx = new BtcTransaction(btcParams); + btcTx.addInput(prevOut).setScriptSig(createBaseInputScriptThatSpendsFromTheFederation(federation)); + TransactionOutput output = new TransactionOutput(btcParams, btcTx, Coin.COIN, new BtcECKey().toAddress(btcParams)); + btcTx.addOutput(output); + + // Save btc tx to be signed + SortedMap pegoutWaitingForSignatures = new TreeMap<>(); + + final Keccak256 rskTxHash = createHash3(1); + pegoutWaitingForSignatures.put(rskTxHash, btcTx); + when(provider.getPegoutsWaitingForSignatures()) + .thenReturn(pegoutWaitingForSignatures); + + BridgeSupport bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeMainNetConstants) + .withProvider(provider) + .withEventLogger(eventLogger) + .withBtcLockSenderProvider(new BtcLockSenderProvider()) + .withActivations(activations) + .build(); + + BtcECKey federatorBtcPubKey = federationMemberToSignWith.getBtcPublicKey(); + + Script inputScript = btcTx.getInputs().get(0).getScriptSig(); + List chunks = inputScript.getChunks(); + byte[] program = chunks.get(chunks.size() - 1).data; + Script redeemScript = new Script(program); + Sha256Hash sigHash = btcTx.hashForSignature(0, redeemScript, BtcTransaction.SigHash.ALL, false); + BtcECKey.ECDSASignature sig = privateKeyToSignWith.sign(sigHash); + List derEncodedSigs = Collections.singletonList(sig.encodeToDER()); + + // Act + bridgeSupport.addSignature(federationMemberToSignWith.getBtcPublicKey(), derEncodedSigs, rskTxHash.getBytes()); + + // Assert + commonAssertLogs(eventLogs); + assertTopics(3, eventLogs); + + assertEvent(eventLogs, 0, BridgeEvents.ADD_SIGNATURE.getEvent(), new Object[]{rskTxHash.getBytes(), expectedRskAddress}, new Object[]{federatorBtcPubKey.getPubKey()}); + } + + private static void assertEvent(List logs, int index, CallTransaction.Function event, Object[] topics, Object[] params) { + final LogInfo log = logs.get(index); + assertEquals(LogInfo.byteArrayToList(event.encodeEventTopics(topics)), log.getTopics()); + assertArrayEquals(event.encodeEventData(params), log.getData()); + } + + private void assertTopics(int topics, List logs) { + assertEquals(topics, logs.get(0).getTopics().size()); + } + + private void commonAssertLogs(List logs) { + assertEquals(1, logs.size()); + LogInfo entry = logs.get(0); + + // Assert address that made the log + assertEquals(PrecompiledContracts.BRIDGE_ADDR, new RskAddress(entry.getAddress())); + assertArrayEquals(PrecompiledContracts.BRIDGE_ADDR.getBytes(), entry.getAddress()); + } + /** * Helper method to test addSignature() with a valid federatorPublicKey parameter and both valid/invalid signatures * @@ -657,5 +828,4 @@ private BtcECKey findPublicKeySignedBy(List pubs, BtcECKey pk) { } return pk; } - } diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java index 646f5fce84a..38a2b0f0e80 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java @@ -471,9 +471,14 @@ private PartialMerkleTree createPmtAndMockBlockStore(BtcTransaction btcTransacti } private BridgeSupport buildBridgeSupport(ActivationConfig.ForBlock activations) { + Repository repository = mock(Repository.class); + when(repository.getBalance(PrecompiledContracts.BRIDGE_ADDR)).thenReturn(co.rsk.core.Coin.fromBitcoin(bridgeMainnetConstants.getMaxRbtc())); + when(provider.getLockingCap()).thenReturn(bridgeMainnetConstants.getMaxRbtc()); + return new BridgeSupportBuilder() .withBtcBlockStoreFactory(mockFactory) .withBridgeConstants(bridgeMainnetConstants) + .withRepository(repository) .withProvider(provider) .withActivations(activations) .withSignatureCache(signatureCache) @@ -503,8 +508,6 @@ void registering_btc_transaction_sending_funds_to_unknown_address( btcTransaction.addInput(BitcoinTestUtils.createHash(1), FIRST_OUTPUT_INDEX, ScriptBuilder.createInputScript(null, senderBtcKey)); - - Coin amountToSend = shouldSendAmountBelowMinimum ? belowMinimumPeginTxValue : minimumPeginTxValue; btcTransaction.addOutput(amountToSend, userAddress); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java index 1bce4b85561..6b5e92d3693 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java @@ -1,264 +1,307 @@ package co.rsk.peg; +import static co.rsk.peg.PegTestUtils.createHash3; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + import co.rsk.bitcoinj.core.*; +import co.rsk.bitcoinj.script.Script; import co.rsk.bitcoinj.store.BlockStoreException; -import co.rsk.blockchain.utils.BlockGenerator; +import co.rsk.config.BridgeConstants; +import co.rsk.config.BridgeMainNetConstants; import co.rsk.config.BridgeRegTestConstants; -import co.rsk.config.TestSystemProperties; +import co.rsk.config.BridgeTestNetConstants; import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; +import co.rsk.peg.bitcoin.BitcoinTestUtils; 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 co.rsk.peg.federation.FederationMember.KeyType; import co.rsk.peg.federation.FederationTestUtils; import co.rsk.peg.flyover.FlyoverTxResponseCodes; +import co.rsk.test.builders.BridgeBuilder; +import java.io.IOException; +import java.math.BigInteger; +import java.time.Instant; +import java.util.*; +import java.util.stream.Stream; import org.bouncycastle.util.encoders.Hex; import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; +import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.*; import org.ethereum.crypto.ECKey; import org.ethereum.util.ByteUtil; -import org.ethereum.vm.PrecompiledContracts; +import org.ethereum.vm.MessageCall; import org.ethereum.vm.exception.VMException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.math.BigInteger; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class BridgeTest { + private static final byte[] EMPTY_BYTE_ARRAY = new byte[]{}; - private TestSystemProperties config = new TestSystemProperties(); - private Constants constants; - private ActivationConfig activationConfig; - private SignatureCache signatureCache; + private NetworkParameters networkParameters; + private BridgeBuilder bridgeBuilder; @BeforeEach - void resetConfigToRegTest() { - config = spy(new TestSystemProperties()); - constants = Constants.regtest(); - when(config.getNetworkConstants()).thenReturn(constants); - activationConfig = spy(ActivationConfigsForTest.genesis()); - when(config.getActivationConfig()).thenReturn(activationConfig); - signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); + void resetConfigToMainnet() { + networkParameters = BridgeMainNetConstants.getInstance().getBtcParams(); + bridgeBuilder = new BridgeBuilder(); } @Test - void getActivePowpegRedeemScript_before_RSKIP293_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP293), anyLong()); + void getActivePowpegRedeemScript_before_RSKIP293_activation() { + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock, activationConfig); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = BridgeMethods.GET_ACTIVE_POWPEG_REDEEM_SCRIPT.getFunction().encode(new Object[]{}); + byte[] data = BridgeMethods.GET_ACTIVE_POWPEG_REDEEM_SCRIPT.getFunction().encode(); - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void getActivePowpegRedeemScript_after_RSKIP293_activation() throws VMException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP293), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.hop400(); + CallTransaction.Function getActivePowpegRedeemScriptFunction = BridgeMethods.GET_ACTIVE_POWPEG_REDEEM_SCRIPT.getFunction(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Script activePowpegRedeemScript = BridgeRegTestConstants.getInstance().getGenesisFederation().getRedeemScript(); when(bridgeSupportMock.getActivePowpegRedeemScript()).thenReturn( - Optional.of(BridgeRegTestConstants.getInstance().getGenesisFederation().getRedeemScript()) + Optional.of(activePowpegRedeemScript) ); - Bridge bridge = getBridgeInstance(bridgeSupportMock, activationConfig); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); - byte[] data = BridgeMethods.GET_ACTIVE_POWPEG_REDEEM_SCRIPT.getFunction().encode(new Object[]{}); - byte[] result = (byte[]) BridgeMethods.GET_ACTIVE_POWPEG_REDEEM_SCRIPT.getFunction().decodeResult(bridge.execute(data))[0]; + byte[] data = getActivePowpegRedeemScriptFunction.encode(); + byte[] result = bridge.execute(data); + byte[] decodedResult = (byte[]) getActivePowpegRedeemScriptFunction.decodeResult(result)[0]; + Script obtainedRedeemScript = new Script(decodedResult); - assertArrayEquals(constants.bridgeConstants.getGenesisFederation().getRedeemScript().getProgram(), result); + assertEquals(activePowpegRedeemScript, obtainedRedeemScript); } @Test - void getLockingCap_before_RSKIP134_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP134), anyLong()); + void getLockingCap_before_RSKIP134_activation() { + ActivationConfig activationConfig = ActivationConfigsForTest.wasabi100(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = BridgeMethods.GET_LOCKING_CAP.getFunction().encode(new Object[]{}); + byte[] data = BridgeMethods.GET_LOCKING_CAP.getFunction().encode(); - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void getLockingCap_after_RSKIP134_activation() throws VMException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP134), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + CallTransaction.Function getLockingCapFunction = Bridge.GET_LOCKING_CAP; BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Coin lockingCap = Coin.COIN; + when(bridgeSupportMock.getLockingCap()).thenReturn(lockingCap); - // Don't really care about the internal logic, just checking if the method is active - when(bridgeSupportMock.getLockingCap()).thenReturn(Coin.COIN); + Transaction tx = mock(Transaction.class); + when(tx.isLocalCallTransaction()).thenReturn(true); - byte[] data = Bridge.GET_LOCKING_CAP.encode(new Object[]{}); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .transaction(tx) + .bridgeSupport(bridgeSupportMock) + .build(); + + byte[] data = getLockingCapFunction.encode(); byte[] result = bridge.execute(data); - assertEquals(Coin.COIN.getValue(), ((BigInteger) Bridge.GET_LOCKING_CAP.decodeResult(result)[0]).longValue()); + BigInteger decodedResult = (BigInteger) getLockingCapFunction.decodeResult(result)[0]; + Coin obtainedLockingCap = Coin.valueOf(decodedResult.longValue()); + + assertEquals(lockingCap, obtainedLockingCap); + // Also test the method itself - assertEquals(Coin.COIN.getValue(), bridge.getLockingCap(new Object[]{})); + long lockingCapFromTheBridge = bridge.getLockingCap(new Object[]{}); + assertEquals(lockingCap.getValue(), lockingCapFromTheBridge); } @Test - void increaseLockingCap_before_RSKIP134_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP134), anyLong()); - - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + void increaseLockingCap_before_RSKIP134_activation() { + ActivationConfig activationConfig = ActivationConfigsForTest.wasabi100(); - byte[] data = BridgeMethods.INCREASE_LOCKING_CAP.getFunction().encode(new Object[]{}); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - assertNull(bridge.execute(data)); + byte[] data = BridgeMethods.INCREASE_LOCKING_CAP.getFunction().encode(); + assertThrows(VMException.class, () -> bridge.execute(data)); } - @Test - void increaseLockingCap_after_RSKIP134_activation() throws VMException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP134), anyLong()); + @ParameterizedTest() + @MethodSource("lockingCapValues") + void increaseLockingCap_after_RSKIP134_activation(long newLockingCapValue) throws VMException { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + CallTransaction.Function increaseLockingCapFunction = Bridge.INCREASE_LOCKING_CAP; BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); - - // Don't really care about the internal logic, just checking if the method is active when(bridgeSupportMock.increaseLockingCap(any(), any())).thenReturn(true); - byte[] data = Bridge.INCREASE_LOCKING_CAP.encode(new Object[]{1}); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); + + byte[] data = increaseLockingCapFunction.encode(newLockingCapValue); byte[] result = bridge.execute(data); - assertTrue((boolean) Bridge.INCREASE_LOCKING_CAP.decodeResult(result)[0]); - // Also test the method itself - assertEquals(true, bridge.increaseLockingCap(new Object[]{BigInteger.valueOf(1)})); + boolean decodedResult = (boolean) increaseLockingCapFunction.decodeResult(result)[0]; + + assertTrue(decodedResult); - data = Bridge.INCREASE_LOCKING_CAP.encode(new Object[]{21_000_000}); - result = bridge.execute(data); - assertTrue((boolean) Bridge.INCREASE_LOCKING_CAP.decodeResult(result)[0]); // Also test the method itself - assertEquals(true, bridge.increaseLockingCap(new Object[]{BigInteger.valueOf(21_000_000)})); + boolean resultFromTheBridge = bridge.increaseLockingCap(new Object[]{BigInteger.valueOf(newLockingCapValue)}); + assertTrue(resultFromTheBridge); + } + + private static Stream lockingCapValues() { + return Stream.of( + Arguments.of(1), + Arguments.of(21_000_0000) + ); } @Test - void increaseLockingCap_invalidParameter() throws VMException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP134), anyLong()); + void increaseLockingCap_invalidParameter() { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + CallTransaction.Function increaseLockingCapFunction = BridgeMethods.INCREASE_LOCKING_CAP.getFunction(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); // Uses the proper signature but with no argument - // The solidity decoder in the Bridge will convert the undefined argument as 0, but the initial validation in the method will reject said value - byte[] data = Bridge.INCREASE_LOCKING_CAP.encodeSignature(); - byte[] result = bridge.execute(data); - assertNull(result); + // The solidity decoder in the Bridge will convert the undefined argument as 0, + // but the initial validation in the method will reject said value + final byte[] noArgumentData = increaseLockingCapFunction.encodeSignature(); + assertThrows(VMException.class, () -> bridge.execute(noArgumentData)); // Uses the proper signature but appends invalid data type // This will be rejected by the solidity decoder in the Bridge directly - data = ByteUtil.merge(Bridge.INCREASE_LOCKING_CAP.encodeSignature(), Hex.decode("ab")); - result = bridge.execute(data); - assertNull(result); + final byte[] invalidTypeData = ByteUtil.merge(increaseLockingCapFunction.encodeSignature(), Hex.decode("ab")); + assertThrows(VMException.class, () -> bridge.execute(invalidTypeData)); // Uses the proper signature and data type, but with an invalid value // This will be rejected by the initial validation in the method - data = Bridge.INCREASE_LOCKING_CAP.encode(new Object[]{-1}); - result = bridge.execute(data); - assertNull(result); + final byte[] invalidValueData = increaseLockingCapFunction.encode(-1); + assertThrows(VMException.class, () -> bridge.execute(invalidValueData)); // Uses the proper signature and data type, but with a value that exceeds the long max value - data = ByteUtil.merge(Bridge.INCREASE_LOCKING_CAP.encodeSignature(), Hex.decode("0000000000000000000000000000000000000000000000080000000000000000")); - result = bridge.execute(data); - assertNull(result); + final byte[] aboveMaxLengthData = ByteUtil.merge( + increaseLockingCapFunction.encodeSignature(), + Hex.decode("0000000000000000000000000000000000000000000000080000000000000000") + ); + assertThrows(VMException.class, () -> bridge.execute(aboveMaxLengthData)); } @Test - void registerBtcCoinbaseTransaction_before_RSKIP143_activation() throws VMException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(false).when(activations).isActive(eq(RSKIP143), anyLong()); + void registerBtcCoinbaseTransaction_before_RSKIP143_activation() { + ActivationConfig activationConfig = ActivationConfigsForTest.wasabi100(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock, activations); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); byte[] value = Sha256Hash.ZERO_HASH.getBytes(); - Integer zero = new Integer(0); - - byte[] data = Bridge.REGISTER_BTC_COINBASE_TRANSACTION.encode(new Object[]{value, zero, value, zero, zero}); + Integer zero = 0; + byte[] data = Bridge.REGISTER_BTC_COINBASE_TRANSACTION.encode(value, zero, value, zero, zero); - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void registerBtcCoinbaseTransaction_after_RSKIP143_activation() throws VMException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP143), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock, activations); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); byte[] value = Sha256Hash.ZERO_HASH.getBytes(); - Integer zero = new Integer(0); + Integer zero = 0; - byte[] data = Bridge.REGISTER_BTC_COINBASE_TRANSACTION.encode(new Object[]{value, zero, value, zero, zero}); + byte[] data = Bridge.REGISTER_BTC_COINBASE_TRANSACTION.encode(value, zero, value, zero, zero); bridge.execute(data); - verify(bridgeSupportMock, times(1)).registerBtcCoinbaseTransaction(value, Sha256Hash.wrap(value), value, Sha256Hash.wrap(value), value); + verify(bridgeSupportMock, times(1)).registerBtcCoinbaseTransaction( + value, + Sha256Hash.wrap(value), + value, + Sha256Hash.wrap(value), + value + ); } @Test - void registerBtcCoinbaseTransaction_after_RSKIP143_activation_null_data() throws VMException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP143), anyLong()); + void registerBtcCoinbaseTransaction_after_RSKIP143_activation_null_data() { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + CallTransaction.Function registerBtcCoinbaseTransactionFunction = Bridge.REGISTER_BTC_COINBASE_TRANSACTION; - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock, activations); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = Bridge.REGISTER_BTC_COINBASE_TRANSACTION.encodeSignature(); - byte[] result = bridge.execute(data); - assertNull(result); + final byte[] emptyData = registerBtcCoinbaseTransactionFunction.encodeSignature(); + assertThrows(VMException.class, () -> bridge.execute(emptyData)); - data = ByteUtil.merge(Bridge.REGISTER_BTC_COINBASE_TRANSACTION.encodeSignature(), Hex.decode("ab")); - result = bridge.execute(data); - assertNull(result); + final byte[] invalidStringData = ByteUtil.merge(registerBtcCoinbaseTransactionFunction.encodeSignature(), Hex.decode("ab")); + assertThrows(VMException.class, () -> bridge.execute(invalidStringData)); - data = ByteUtil.merge(Bridge.REGISTER_BTC_COINBASE_TRANSACTION.encodeSignature(), Hex.decode("0000000000000000000000000000000000000000000000080000000000000000")); - result = bridge.execute(data); - assertNull(result); + final byte[] invalidHexData = ByteUtil.merge(registerBtcCoinbaseTransactionFunction.encodeSignature(), Hex.decode("0000000000000000000000000000000000000000000000080000000000000000")); + assertThrows(VMException.class, () -> bridge.execute(invalidHexData)); } @Test - void registerBtcTransaction_beforeRskip199_rejectsExternalCalls() - throws VMException, IOException, BlockStoreException { - - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(false).when(activations).isActive(eq(RSKIP199), anyLong()); + void registerBtcTransaction_beforeRskip199_rejectsExternalCalls() throws VMException, IOException, BlockStoreException { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); NetworkParameters btcParams = NetworkParameters.fromID(NetworkParameters.ID_REGTEST); - FederationArgs activeFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembers(3), + FederationArgs activeFederationArgs = new FederationArgs( + FederationTestUtils.getFederationMembers(3), Instant.ofEpochMilli(1000), - 0L, btcParams); + 0L, + btcParams + ); Federation activeFederation = FederationFactory.buildStandardMultiSigFederation(activeFederationArgs); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); when(bridgeSupportMock.getActiveFederation()).thenReturn(activeFederation); Transaction rskTx = mock(Transaction.class); - when(rskTx.getSender(any(SignatureCache.class))).thenReturn(new RskAddress("0000000000000000000000000000000000000001")); + RskAddress senderAddress = new RskAddress("0000000000000000000000000000000000000001"); + when(rskTx.getSender(any(SignatureCache.class))).thenReturn(senderAddress); - Bridge bridge = getBridgeInstance(rskTx, bridgeSupportMock, activations, signatureCache); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .transaction(rskTx) + .build(); byte[] value = Sha256Hash.ZERO_HASH.getBytes(); int zero = 0; - byte[] data = Bridge.REGISTER_BTC_TRANSACTION.encode(new Object[]{ value, zero, value }); + byte[] data = Bridge.REGISTER_BTC_TRANSACTION.encode(value, zero, value); try { bridge.execute(data); @@ -276,11 +319,8 @@ void registerBtcTransaction_beforeRskip199_rejectsExternalCalls() } @Test - void registerBtcTransaction_beforeRskip199_acceptsCallFromFederationMember() - throws VMException, IOException, BlockStoreException { - - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(false).when(activations).isActive(eq(RSKIP199), anyLong()); + void registerBtcTransaction_beforeRskip199_acceptsCallFromFederationMember() throws VMException, IOException, BlockStoreException { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); NetworkParameters btcParams = NetworkParameters.fromID(NetworkParameters.ID_REGTEST); BtcECKey fed1Key = new BtcECKey(); @@ -291,7 +331,8 @@ void registerBtcTransaction_beforeRskip199_acceptsCallFromFederationMember() FederationArgs activeFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembersWithKeys(federationKeys), Instant.ofEpochMilli(1000), 0L, - btcParams); + btcParams + ); Federation activeFederation = FederationFactory.buildStandardMultiSigFederation(activeFederationArgs); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); @@ -300,11 +341,15 @@ void registerBtcTransaction_beforeRskip199_acceptsCallFromFederationMember() Transaction rskTx = mock(Transaction.class); when(rskTx.getSender(any(SignatureCache.class))).thenReturn(fed1Address); - Bridge bridge = getBridgeInstance(rskTx, bridgeSupportMock, activations, signatureCache); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .transaction(rskTx) + .build(); byte[] value = Sha256Hash.ZERO_HASH.getBytes(); int zero = 0; - byte[] data = Bridge.REGISTER_BTC_TRANSACTION.encode(new Object[]{ value, zero, value }); + byte[] data = Bridge.REGISTER_BTC_TRANSACTION.encode(value, zero, value); bridge.execute(data); @@ -317,18 +362,18 @@ void registerBtcTransaction_beforeRskip199_acceptsCallFromFederationMember() } @Test - void registerBtcTransaction_afterRskip199_acceptsExternalCalls() - throws VMException, IOException, BlockStoreException { - - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP199), anyLong()); - + void registerBtcTransaction_afterRskip199_acceptsExternalCalls() throws VMException, IOException, BlockStoreException { + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock, activations); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); byte[] value = Sha256Hash.ZERO_HASH.getBytes(); int zero = 0; - byte[] data = Bridge.REGISTER_BTC_TRANSACTION.encode(new Object[]{ value, zero, value }); + byte[] data = Bridge.REGISTER_BTC_TRANSACTION.encode(value, zero, value); bridge.execute(data); @@ -341,41 +386,50 @@ void registerBtcTransaction_afterRskip199_acceptsExternalCalls() } @Test - void getActiveFederationCreationBlockHeight_before_RSKIP186_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP186), anyLong()); + void getActiveFederationCreationBlockHeight_before_RSKIP186_activation() { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = BridgeMethods.GET_ACTIVE_FEDERATION_CREATION_BLOCK_HEIGHT.getFunction().encode(new Object[]{}); + byte[] data = BridgeMethods.GET_ACTIVE_FEDERATION_CREATION_BLOCK_HEIGHT.getFunction().encode(); - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void getActiveFederationCreationBlockHeight_after_RSKIP186_activation() throws VMException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP186), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); + long activeFederationCreationBlockHeight = 1L; BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + when(bridgeSupportMock.getActiveFederationCreationBlockHeight()).thenReturn(activeFederationCreationBlockHeight); - // Don't really care about the internal logic, just checking if the method is active - when(bridgeSupportMock.getActiveFederationCreationBlockHeight()).thenReturn(1L); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); CallTransaction.Function function = BridgeMethods.GET_ACTIVE_FEDERATION_CREATION_BLOCK_HEIGHT.getFunction(); - byte[] data = function.encode(new Object[]{ }); + byte[] data = function.encode(); byte[] result = bridge.execute(data); - assertEquals(1L, ((BigInteger)function.decodeResult(result)[0]).longValue()); + BigInteger decodedResult = (BigInteger)function.decodeResult(result)[0]; + + assertEquals(activeFederationCreationBlockHeight, decodedResult.longValue()); + // Also test the method itself - assertEquals(1L, bridge.getActiveFederationCreationBlockHeight(new Object[]{ })); + long resultFromTheBridge = bridge.getActiveFederationCreationBlockHeight(new Object[]{}); + assertEquals(activeFederationCreationBlockHeight, resultFromTheBridge); } @Test - void registerFlyoverBtcTransaction_before_RSKIP176_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP176), anyLong()); + void registerFlyoverBtcTransaction_before_RSKIP176_activation() { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); byte[] value = Sha256Hash.ZERO_HASH.getBytes(); byte[] pubKeyHash = new BtcECKey().getPubKeyHash(); @@ -392,19 +446,16 @@ void registerFlyoverBtcTransaction_before_RSKIP176_activation() throws VMExcepti ); //Assert - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test - void registerFlyoverBtcTransaction_after_RSKIP176_activation_p2sh_refund_address_before_RSKIP284_activation_fails() + void registerFlyoverBtcTransaction_after_RSKIP176_activation_testnet_p2sh_refund_address_before_RSKIP284_activation_fails() throws VMException, IOException, BlockStoreException { - NetworkParameters networkParameters = constants.getBridgeConstants().getBtcParams(); - doReturn(true).when(activationConfig).isActive(eq(RSKIP176), anyLong()); - doReturn(false).when(activationConfig).isActive(eq(RSKIP284), anyLong()); + NetworkParameters testnetNetworkParameters = BridgeTestNetConstants.getInstance().getBtcParams(); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); - when(bridgeSupportMock.registerFlyoverBtcTransaction( any(Transaction.class), any(byte[].class), @@ -417,18 +468,27 @@ void registerFlyoverBtcTransaction_after_RSKIP176_activation_p2sh_refund_address anyBoolean() )).thenReturn(BigInteger.valueOf(2)); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .constants(Constants.regtest()) + .build(); + byte[] value = Sha256Hash.ZERO_HASH.getBytes(); - Address refundBtcAddress = Address.fromBase58(networkParameters, "2MyEXHyt2fXqdFm3r4xXEkTdbwdZm7qFiDP"); + Address refundBtcAddress = Address.fromBase58( + testnetNetworkParameters, + "2MyEXHyt2fXqdFm3r4xXEkTdbwdZm7qFiDP" + ); byte[] refundBtcAddressBytes = BridgeUtils.serializeBtcAddressWithVersion( - activationConfig.forBlock(anyLong()), + activationConfig.forBlock(0), refundBtcAddress ); BtcECKey btcECKeyLp = new BtcECKey(); - Address lpBtcAddress = btcECKeyLp.toAddress(networkParameters); + Address lpBtcAddress = btcECKeyLp.toAddress(testnetNetworkParameters); byte[] lpBtcAddressBytes = BridgeUtils.serializeBtcAddressWithVersion( - activationConfig.forBlock(anyLong()), + activationConfig.forBlock(0), lpBtcAddress ); @@ -447,9 +507,10 @@ void registerFlyoverBtcTransaction_after_RSKIP176_activation_p2sh_refund_address ); byte[] result = bridge.execute(data); + BigInteger decodedResult = (BigInteger) Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.decodeResult(result)[0]; //Assert - assertEquals(BigInteger.valueOf(-900), Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.decodeResult(result)[0]); + assertEquals(FlyoverTxResponseCodes.GENERIC_ERROR.value(), decodedResult.longValue()); verify(bridgeSupportMock, times(0)).registerFlyoverBtcTransaction( any(Transaction.class), eq(value), @@ -464,15 +525,12 @@ void registerFlyoverBtcTransaction_after_RSKIP176_activation_p2sh_refund_address } @Test - void registerFlyoverBtcTransaction_after_RSKIP176_activation_p2sh_refund_address_after_RSKIP284_activation_ok() + void registerFlyoverBtcTransaction_after_RSKIP176_activation_testnet_p2sh_refund_address_after_RSKIP284_activation_ok() throws VMException, IOException, BlockStoreException { - NetworkParameters networkParameters = constants.getBridgeConstants().getBtcParams(); - doReturn(true).when(activationConfig).isActive(eq(RSKIP176), anyLong()); - doReturn(true).when(activationConfig).isActive(eq(RSKIP284), anyLong()); + NetworkParameters testnetNetworkParameters = BridgeTestNetConstants.getInstance().getBtcParams(); + ActivationConfig activationConfig = ActivationConfigsForTest.hop400(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); - when(bridgeSupportMock.registerFlyoverBtcTransaction( any(Transaction.class), any(byte[].class), @@ -485,18 +543,27 @@ void registerFlyoverBtcTransaction_after_RSKIP176_activation_p2sh_refund_address anyBoolean() )).thenReturn(BigInteger.valueOf(2)); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .constants(Constants.regtest()) + .build(); + byte[] value = Sha256Hash.ZERO_HASH.getBytes(); - Address refundBtcAddress = Address.fromBase58(networkParameters, "2MyEXHyt2fXqdFm3r4xXEkTdbwdZm7qFiDP"); + Address refundBtcAddress = Address.fromBase58( + testnetNetworkParameters, + "2MyEXHyt2fXqdFm3r4xXEkTdbwdZm7qFiDP" + ); byte[] refundBtcAddressBytes = BridgeUtils.serializeBtcAddressWithVersion( - activationConfig.forBlock(anyLong()), + activationConfig.forBlock(0), refundBtcAddress ); BtcECKey btcECKeyLp = new BtcECKey(); - Address lpBtcAddress = btcECKeyLp.toAddress(networkParameters); + Address lpBtcAddress = btcECKeyLp.toAddress(testnetNetworkParameters); byte[] lpBtcAddressBytes = BridgeUtils.serializeBtcAddressWithVersion( - activationConfig.forBlock(anyLong()), + activationConfig.forBlock(0), lpBtcAddress ); @@ -532,13 +599,10 @@ void registerFlyoverBtcTransaction_after_RSKIP176_activation_p2sh_refund_address } @Test - void registerFlyoverBtcTransaction_after_RSKIP176_activation_generic_error() - throws VMException, IOException, BlockStoreException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP176), anyLong()); + void registerFlyoverBtcTransaction_after_RSKIP176_activation_generic_error() throws VMException, IOException, BlockStoreException { + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); - when(bridgeSupportMock.registerFlyoverBtcTransaction( any(Transaction.class), any(byte[].class), @@ -551,6 +615,11 @@ void registerFlyoverBtcTransaction_after_RSKIP176_activation_generic_error() anyBoolean() )).thenReturn(BigInteger.valueOf(FlyoverTxResponseCodes.GENERIC_ERROR.value())); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); + byte[] value = Sha256Hash.ZERO_HASH.getBytes(); BtcECKey btcECKeyRefund = new BtcECKey(); byte[] pubKeyHashRefund = btcECKeyRefund.getPubKeyHash(); @@ -570,174 +639,183 @@ void registerFlyoverBtcTransaction_after_RSKIP176_activation_generic_error() true ); byte[] result = bridge.execute(data); + BigInteger decodedResult = (BigInteger)Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.decodeResult(result)[0]; - assertEquals(FlyoverTxResponseCodes.GENERIC_ERROR.value(), - ((BigInteger)Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.decodeResult(result)[0]).longValue()); + assertEquals(FlyoverTxResponseCodes.GENERIC_ERROR.value(), decodedResult.longValue()); } @Test void registerFlyoverBtcTransaction_after_RSKIP176_null_parameter() throws VMException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP176), anyLong()); - - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); - byte[] value = Sha256Hash.ZERO_HASH.getBytes(); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.encodeSignature(); - byte[] result = bridge.execute(data); - assertNull(result); + byte[] noArgumentData = Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.encodeSignature(); + assertThrows(VMException.class, () -> bridge.execute(noArgumentData)); - data = ByteUtil.merge(Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.encodeSignature(), value); - result = bridge.execute(data); - assertNull(result); + byte[] value = Sha256Hash.ZERO_HASH.getBytes(); + byte[] zeroValueData = ByteUtil.merge(Bridge.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.encodeSignature(), value); + assertThrows(VMException.class, () -> bridge.execute(zeroValueData)); } @Test - void receiveHeader_before_RSKIP200() throws VMException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(false).when(activations).isActive(eq(RSKIP200), anyLong()); + void receiveHeader_before_RSKIP200() { + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = spy(getBridgeInstance(bridgeSupportMock, activations)); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - NetworkParameters networkParameters = constants.bridgeConstants.getBtcParams(); co.rsk.bitcoinj.core.BtcBlock block = new co.rsk.bitcoinj.core.BtcBlock( - networkParameters, - 1, - PegTestUtils.createHash(1), - PegTestUtils.createHash(1), - 1, - Utils.encodeCompactBits(networkParameters.getMaxTarget() - ), 1, new ArrayList<>()).cloneAsHeader(); + networkParameters, + 1, + PegTestUtils.createHash(1), + PegTestUtils.createHash(1), + 1, + Utils.encodeCompactBits(networkParameters.getMaxTarget()), + 1, + new ArrayList<>() + ).cloneAsHeader(); Object[] parameters = new Object[]{block.bitcoinSerialize()}; byte[] data = Bridge.RECEIVE_HEADER.encode(parameters); - bridge.execute(data); - assertNull(bridge.execute(data)); - verify(bridge, never()).receiveHeader(any(Object[].class)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test - void receiveHeader_empty_parameter() throws VMException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP200), anyLong()); - + void receiveHeader_empty_parameter() { + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = spy(getBridgeInstance(bridgeSupportMock, activations)); - byte[] data = Bridge.RECEIVE_HEADER.encode(new Object[]{}); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); - assertNull(bridge.execute(data)); - verify(bridge, never()).receiveHeader(any(Object[].class)); + byte[] data = Bridge.RECEIVE_HEADER.encode(); + + assertThrows(VMException.class, () -> bridge.execute(data)); verifyNoInteractions(bridgeSupportMock); } @Test void receiveHeader_after_RSKIP200_Ok() throws VMException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP200), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = spy(getBridgeInstance(mock(BridgeSupport.class), activations)); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); - NetworkParameters networkParameters = constants.bridgeConstants.getBtcParams(); co.rsk.bitcoinj.core.BtcBlock block = new co.rsk.bitcoinj.core.BtcBlock( - networkParameters, - 1, - PegTestUtils.createHash(1), - PegTestUtils.createHash(1), - 1, - Utils.encodeCompactBits(networkParameters.getMaxTarget() - ), 1, new ArrayList<>()).cloneAsHeader(); + networkParameters, + 1, + BitcoinTestUtils.createHash(1), + BitcoinTestUtils.createHash(2), + 1L, + 100L, + 1L, + new ArrayList<>() + ).cloneAsHeader(); - Object[] parameters = new Object[]{block.bitcoinSerialize()}; - byte[] data = Bridge.RECEIVE_HEADER.encode(parameters); + CallTransaction.Function function = BridgeMethods.RECEIVE_HEADER.getFunction(); + byte[] data = function.encode(block.bitcoinSerialize()); byte[] result = bridge.execute(data); - verify(bridge, times(1)).receiveHeader(eq(parameters)); // NOSONAR: eq is needed - assertEquals(BigInteger.valueOf(0), Bridge.RECEIVE_HEADER.decodeResult(result)[0]); + BigInteger decodedResult = (BigInteger) function.decodeResult(result)[0]; + + assertEquals(BigInteger.valueOf(0), decodedResult); } @Test void receiveHeader_bridgeSupport_Exception() throws IOException, BlockStoreException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP200), anyLong()); - + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - doThrow(new IOException()).when(bridgeSupportMock).receiveHeader(any()); - Bridge bridge = getBridgeInstance(bridgeSupportMock, activations); + doThrow(new IOException()).when(bridgeSupportMock).receiveHeader(any()) + ; + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); - NetworkParameters networkParameters = constants.bridgeConstants.getBtcParams(); co.rsk.bitcoinj.core.BtcBlock block = new co.rsk.bitcoinj.core.BtcBlock( - networkParameters, - 1, - PegTestUtils.createHash(1), - PegTestUtils.createHash(1), - 1, - Utils.encodeCompactBits(networkParameters.getMaxTarget() - ), 1, new ArrayList<>()).cloneAsHeader(); + networkParameters, + 1, + PegTestUtils.createHash(1), + PegTestUtils.createHash(1), + 1, + Utils.encodeCompactBits(networkParameters.getMaxTarget()), + 1, + new ArrayList<>() + ).cloneAsHeader(); Object[] parameters = new Object[]{block.bitcoinSerialize()}; byte[] data = Bridge.RECEIVE_HEADER.encode(parameters); - Assertions.assertThrows(VMException.class, () -> bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void receiveHeaders_after_RSKIP200_notFederation() { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP200), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); when(bridgeSupportMock.getRetiringFederation()).thenReturn(null); when(bridgeSupportMock.getActiveFederation()).thenReturn(BridgeRegTestConstants.getInstance().getGenesisFederation()); Transaction txMock = mock(Transaction.class); - when(txMock.getSender(any(SignatureCache.class))).thenReturn(new RskAddress(new ECKey().getAddress())); //acces for anyone + RskAddress txSender = new RskAddress(new ECKey().getAddress()); + when(txMock.getSender(any(SignatureCache.class))).thenReturn(txSender); //access for anyone - Bridge bridge = getBridgeInstance(txMock, bridgeSupportMock, activations, signatureCache); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .transaction(txMock) + .bridgeSupport(bridgeSupportMock) + .build(); - try { - bridge.execute(Bridge.RECEIVE_HEADERS.encode()); - fail(); - } catch (Exception ex) { - assertTrue(ex.getMessage().contains("Sender is not part of the active or retiring federation")); - } + byte[] data = Bridge.RECEIVE_HEADERS.encode(); + + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test - void receiveHeaders_after_RSKIP200_header_wrong_size() throws VMException, IOException { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP200), anyLong()); - // It is used to check the size of the header - doReturn(true).when(activations).isActive(eq(RSKIP124), anyLong()); + void receiveHeaders_after_RSKIP200_header_wrong_size() throws VMException { + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); - Bridge bridge = getBridgeInstance(mock(BridgeSupport.class), activations); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); Object[] parameters = new Object[]{Sha256Hash.ZERO_HASH.getBytes()}; byte[] data = Bridge.RECEIVE_HEADER.encode(parameters); byte[] result = bridge.execute(data); - assertEquals(BigInteger.valueOf(-20), Bridge.RECEIVE_HEADER.decodeResult(result)[0]); + BigInteger decodedResult = (BigInteger) Bridge.RECEIVE_HEADER.decodeResult(result)[0]; + assertEquals(BigInteger.valueOf(-20), decodedResult); } @Test void getBtcBlockchainBestChainHeightOnlyAllowsLocalCalls_afterRskip220() { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP220), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); - Bridge bridge = getBridgeInstance(mock(BridgeSupport.class), activations); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); assertFalse(bridge.getBtcBlockchainBestChainHeightOnlyAllowsLocalCalls(new Object[0])); } @Test void getBtcBlockchainBestChainHeightOnlyAllowsLocalCalls_beforeRskip220() { - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(false).when(activations).isActive(eq(RSKIP220), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); - Bridge bridge = getBridgeInstance(mock(BridgeSupport.class), activations); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); assertTrue(bridge.getBtcBlockchainBestChainHeightOnlyAllowsLocalCalls(new Object[0])); } @@ -746,75 +824,100 @@ void getBtcBlockchainBestChainHeightOnlyAllowsLocalCalls_beforeRskip220() { void activeAndRetiringFederationOnly_activeFederationIsNotFromFederateMember_retiringFederationIsNull_throwsVMException() throws Exception { // Given BridgeMethods.BridgeMethodExecutor executor = Bridge.activeAndRetiringFederationOnly( - null, - null + null, + null ); int senderPK = 999; // Sender PK does not belong to Member PKs - Integer[] memberPKs = new Integer[]{100, 200, 300, 400, 500, 600}; + Integer[] memberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; Federation activeFederation = FederationTestUtils.getFederation(memberPKs); - Bridge bridge = getBridgeInstance(activeFederation, null, senderPK); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + doReturn(null).when(bridgeSupportMock).getRetiringFederation(); - // Then - try { - executor.execute(bridge, null); - fail("VMException should be thrown!"); - } catch (VMException vme) { - assertEquals( - "Sender is not part of the active or retiring federations, so he is not enabled to call the function 'null'", - vme.getMessage() - ); - } + ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); + Transaction rskTxMock = mock(Transaction.class); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); + + assertThrows(VMException.class, () -> executor.execute(bridge, null)); } @Test void activeAndRetiringFederationOnly_activeFederationIsNotFromFederateMember_retiringFederationIsNotNull_retiringFederationIsNotFromFederateMember_throwsVMException() throws Exception { // Given BridgeMethods.BridgeMethodExecutor executor = Bridge.activeAndRetiringFederationOnly( - null, - null + null, + null ); int senderPK = 999; // Sender PK does not belong to Member PKs of active nor retiring fed - Integer[] activeMemberPKs = new Integer[]{100, 200, 300, 400, 500, 600}; - Integer[] retiringMemberPKs = new Integer[]{101, 202, 303, 404, 505, 606}; + Integer[] activeMemberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; + Integer[] retiringMemberPKs = new Integer[]{ 101, 202, 303, 404, 505, 606 }; Federation activeFederation = FederationTestUtils.getFederation(activeMemberPKs); Federation retiringFederation = FederationTestUtils.getFederation(retiringMemberPKs); - Bridge bridge = getBridgeInstance(activeFederation, retiringFederation, senderPK); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + doReturn(retiringFederation).when(bridgeSupportMock).getRetiringFederation(); - // Then - try { - executor.execute(bridge, null); - fail("VMException should be thrown!"); - } catch (VMException vme) { - assertEquals( - "Sender is not part of the active or retiring federations, so he is not enabled to call the function 'null'", - vme.getMessage() - ); - } + ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); + Transaction rskTxMock = mock(Transaction.class); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); + + assertThrows(VMException.class, () -> executor.execute(bridge, null)); } @Test void activeAndRetiringFederationOnly_activeFederationIsFromFederateMember_OK() throws Exception { // Given BridgeMethods.BridgeMethodExecutor decorate = mock( - BridgeMethods.BridgeMethodExecutor.class + BridgeMethods.BridgeMethodExecutor.class ); BridgeMethods.BridgeMethodExecutor executor = Bridge.activeAndRetiringFederationOnly( - decorate, - null + decorate, + null ); int senderPK = 101; // Sender PK belongs to active federation member PKs - Integer[] memberPKs = new Integer[]{100, 200, 300, 400, 500, 600}; - + Integer[] memberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; Federation activeFederation = FederationTestUtils.getFederation(memberPKs); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(activeFederation, null, senderPK, bridgeSupportMock); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + doReturn(null).when(bridgeSupportMock).getRetiringFederation(); + + ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); + Transaction rskTxMock = mock(Transaction.class); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); // When executor.execute(bridge, null); @@ -829,22 +932,36 @@ void activeAndRetiringFederationOnly_activeFederationIsFromFederateMember_OK() t void activeAndRetiringFederationOnly_activeFederationIsNotFromFederateMember_retiringFederationIsNotNull_retiringFederationIsFromFederateMember_OK() throws Exception { // Given BridgeMethods.BridgeMethodExecutor decorate = mock( - BridgeMethods.BridgeMethodExecutor.class + BridgeMethods.BridgeMethodExecutor.class ); BridgeMethods.BridgeMethodExecutor executor = Bridge.activeAndRetiringFederationOnly( - decorate, - null + decorate, + null ); int senderPK = 405; // Sender PK belongs to retiring federation member PKs - Integer[] activeMemberPKs = new Integer[]{100, 200, 300, 400, 500, 600}; - Integer[] retiringMemberPKs = new Integer[]{101, 202, 303, 404, 505, 606}; + Integer[] activeMemberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; + Integer[] retiringMemberPKs = new Integer[]{ 101, 202, 303, 404, 505, 606 }; Federation activeFederation = FederationTestUtils.getFederation(activeMemberPKs); Federation retiringFederation = FederationTestUtils.getFederation(retiringMemberPKs); BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(activeFederation, retiringFederation, senderPK, bridgeSupportMock); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + doReturn(retiringFederation).when(bridgeSupportMock).getRetiringFederation(); + + ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); + Transaction rskTxMock = mock(Transaction.class); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + + ActivationConfig activationConfig = ActivationConfigsForTest.papyrus200(); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); // When executor.execute(bridge, null); @@ -857,150 +974,2098 @@ void activeAndRetiringFederationOnly_activeFederationIsNotFromFederateMember_ret @Test void getNextPegoutCreationBlockNumber_before_RSKIP271_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP271), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = BridgeMethods.GET_NEXT_PEGOUT_CREATION_BLOCK_NUMBER.getFunction().encode(new Object[]{}); + byte[] data = BridgeMethods.GET_NEXT_PEGOUT_CREATION_BLOCK_NUMBER.getFunction().encode(); - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void getNextPegoutCreationBlockNumber_after_RSKIP271_activation() throws VMException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP271), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.hop400(); + long nextPegoutCreationHeight = 1L; BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + when(bridgeSupportMock.getNextPegoutCreationBlockNumber()).thenReturn(nextPegoutCreationHeight); - when(bridgeSupportMock.getNextPegoutCreationBlockNumber()).thenReturn(1L); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); CallTransaction.Function function = BridgeMethods.GET_NEXT_PEGOUT_CREATION_BLOCK_NUMBER.getFunction(); - byte[] data = function.encode(new Object[]{ }); + byte[] data = function.encode(); byte[] result = bridge.execute(data); + BigInteger decodedResult = (BigInteger) function.decodeResult(result)[0]; + + assertEquals(nextPegoutCreationHeight, decodedResult.longValue()); - assertEquals(1L, ((BigInteger)function.decodeResult(result)[0]).longValue()); // Also test the method itself - assertEquals(1L, bridge.getNextPegoutCreationBlockNumber(new Object[]{ })); + long resultFromTheBridge = bridge.getNextPegoutCreationBlockNumber(new Object[]{}); + assertEquals(nextPegoutCreationHeight, resultFromTheBridge); } @Test void getQueuedPegoutsCount_before_RSKIP271_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP271), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = BridgeMethods.GET_QUEUED_PEGOUTS_COUNT.getFunction().encode(new Object[]{}); + byte[] data = BridgeMethods.GET_QUEUED_PEGOUTS_COUNT.getFunction().encode(); - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void getQueuedPegoutsCount_after_RSKIP271_activation() throws VMException, IOException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP271), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.hop400(); + int queuedPegoutsCount = 1; BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); - - when(bridgeSupportMock.getQueuedPegoutsCount()).thenReturn(1); + when(bridgeSupportMock.getQueuedPegoutsCount()).thenReturn(queuedPegoutsCount); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); CallTransaction.Function function = BridgeMethods.GET_QUEUED_PEGOUTS_COUNT.getFunction(); - byte[] data = function.encode(new Object[]{ }); + byte[] data = function.encode(); byte[] result = bridge.execute(data); + BigInteger decodedResult = (BigInteger) function.decodeResult(result)[0]; + + assertEquals(queuedPegoutsCount, decodedResult.intValue()); - assertEquals(1, ((BigInteger)function.decodeResult(result)[0]).intValue()); // Also test the method itself - assertEquals(1, bridge.getQueuedPegoutsCount(new Object[]{ })); + int resultFromTheBridge = bridge.getQueuedPegoutsCount(new Object[]{}); + assertEquals(queuedPegoutsCount, resultFromTheBridge); } @Test void getEstimatedFeesForNextPegOutEvent_before_RSKIP271_activation() throws VMException { - doReturn(false).when(activationConfig).isActive(eq(RSKIP271), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.iris300(); - BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .build(); - byte[] data = BridgeMethods.GET_ESTIMATED_FEES_FOR_NEXT_PEGOUT_EVENT.getFunction().encode(new Object[]{}); + byte[] data = BridgeMethods.GET_ESTIMATED_FEES_FOR_NEXT_PEGOUT_EVENT.getFunction().encode(); - assertNull(bridge.execute(data)); + assertThrows(VMException.class, () -> bridge.execute(data)); } @Test void getEstimatedFeesForNextPegOutEvent_after_RSKIP271_activation() throws VMException, IOException { - doReturn(true).when(activationConfig).isActive(eq(RSKIP271), anyLong()); + ActivationConfig activationConfig = ActivationConfigsForTest.hop400(); + Coin estimatedFeesForNextPegout = Coin.SATOSHI; BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - Bridge bridge = getBridgeInstance(bridgeSupportMock); + when(bridgeSupportMock.getEstimatedFeesForNextPegOutEvent()).thenReturn(estimatedFeesForNextPegout); - when(bridgeSupportMock.getEstimatedFeesForNextPegOutEvent()).thenReturn(Coin.SATOSHI); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .build(); CallTransaction.Function function = BridgeMethods.GET_ESTIMATED_FEES_FOR_NEXT_PEGOUT_EVENT.getFunction(); - byte[] data = function.encode(new Object[]{ }); + byte[] data = function.encode(); byte[] result = bridge.execute(data); + BigInteger decodedResult = (BigInteger) function.decodeResult(result)[0]; + + assertEquals(estimatedFeesForNextPegout.getValue(), decodedResult.longValue()); - assertEquals(Coin.SATOSHI.value, ((BigInteger)function.decodeResult(result)[0]).intValue()); // Also test the method itself - assertEquals(Coin.SATOSHI.value, bridge.getEstimatedFeesForNextPegOutEvent(new Object[]{ })); + long resultFromTheBridge = bridge.getEstimatedFeesForNextPegOutEvent(new Object[]{}); + assertEquals(estimatedFeesForNextPegout.getValue(), resultFromTheBridge); } - private Bridge getBridgeInstance(Federation activeFederation, Federation retiringFederation, int senderPK) { + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void addFederatorPublicKey(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + String publicKey = "039a060badbeb24bee49eb2063f616c0f0f0765d4ca646b20a88ce828f259fcdb9"; BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); - doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); - doReturn(retiringFederation).when(bridgeSupportMock).getRetiringFederation(); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.ADD_FEDERATOR_PUBLIC_KEY.getFunction(); + byte[] data = function.encode(Hex.decode(publicKey)); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + // Post RSKIP123 this method is no longer enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).voteFederationChange(any(), any()); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void addFederatorPublicKeyMultikey(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + String publicKey = "039a060badbeb24bee49eb2063f616c0f0f0765d4ca646b20a88ce828f259fcdb9"; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.ADD_FEDERATOR_PUBLIC_KEY_MULTIKEY.getFunction(); + byte[] data = function.encode(Hex.decode(publicKey), Hex.decode(publicKey), Hex.decode(publicKey)); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).voteFederationChange(any(), any()); + } + } else { + // Pre RSKIP123 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void addLockWhitelistAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + String addressBase58 = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; + long maxTransferValue = 100_000L; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.ADD_LOCK_WHITELIST_ADDRESS.getFunction(); + byte[] data = function.encode(addressBase58, maxTransferValue); + + if (activationConfig.isActive(ConsensusRule.RSKIP87, 0)) { + // Post RSKIP87 this method is no longer enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).addOneOffLockWhitelistAddress( + any(), + any(), + any() + ); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void addOneOffLockWhitelistAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + String addressBase58 = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; + long maxTransferValue = 100_000L; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.ADD_ONE_OFF_LOCK_WHITELIST_ADDRESS.getFunction(); + byte[] data = function.encode(addressBase58, maxTransferValue); + + if (activationConfig.isActive(ConsensusRule.RSKIP87, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).addOneOffLockWhitelistAddress( + any(), + any(), + any() + ); + } + } else { + // Pre RSKIP87 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void addUnlimitedLockWhitelistAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + String addressBase58 = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.ADD_UNLIMITED_LOCK_WHITELIST_ADDRESS.getFunction(); + byte[] data = function.encode(addressBase58); + + if (activationConfig.isActive(ConsensusRule.RSKIP87, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).addUnlimitedLockWhitelistAddress( + any(), + any() + ); + } + } else { + // Pre RSKIP87 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void addSignatures(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws Exception { + String pegnatoryPublicKey = "039a060badbeb24bee49eb2063f616c0f0f0765d4ca646b20a88ce828f259fcdb9"; + String signature = "3045022100a0963cea7551eb3174a3470c6ed25cda901c4b1093818d4d54792b87508820220220325f93b5aecc98385a664328e68d1cec7a2a2fe81810a7692358bd870aeecb74"; + List derEncodedSigs = Collections.singletonList(Hex.decode(signature)); + Keccak256 rskTxHash = createHash3(1); + + int senderPK = 101; // Sender PK belongs to active federation member PKs + Integer[] activeMemberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; + Federation activeFederation = FederationTestUtils.getFederation(activeMemberPKs); ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); Transaction rskTxMock = mock(Transaction.class); - doReturn(new RskAddress(key.getAddress())).when(rskTxMock).getSender(any(SignatureCache.class)); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.ADD_SIGNATURE.getFunction(); + byte[] data = function.encode(Hex.decode(pegnatoryPublicKey), derEncodedSigs, rskTxHash.getBytes()); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + byte[] result = bridge.execute(data); + assertVoidMethodResult(activationConfig, result); + verify(bridgeSupportMock, times(1)).addSignature( + any(), + any(), + any() + ); + } + } - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP143), anyLong()); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void commitFederation(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Keccak256 commitTransactionHash = createHash3(2); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.COMMIT_FEDERATION.getFunction(); + byte[] data = function.encode(commitTransactionHash.getBytes()); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).voteFederationChange( + any(), + any() + ); + } + } - return getBridgeInstance(rskTxMock, bridgeSupportMock, activations, signatureCache); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void createFederation(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.CREATE_FEDERATION.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).voteFederationChange( + any(), + any() + ); + } } - private Bridge getBridgeInstance(Federation activeFederation, Federation retiringFederation, int senderPK, BridgeSupport bridgeSupportMock) { - doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); - doReturn(retiringFederation).when(bridgeSupportMock).getRetiringFederation(); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainBestChainHeight(MessageCall.MsgType msgType, ActivationConfig activationConfig) + throws VMException, BlockStoreException, IOException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); - ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_BEST_CHAIN_HEIGHT.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainBestChainHeight(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainInitialBlockHeight(MessageCall.MsgType msgType, ActivationConfig activationConfig) + throws VMException, IOException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_INITIAL_BLOCK_HEIGHT.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainInitialBlockHeight(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainBlockLocator(MessageCall.MsgType msgType, ActivationConfig activationConfig) + throws VMException, IOException, BlockStoreException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_BLOCK_LOCATOR.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP89, 0)) { + // Post RSKIP89 this method is no longer enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainBlockLocator(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainBlockHashAtDepth(MessageCall.MsgType msgType, ActivationConfig activationConfig) + throws VMException, IOException, BlockStoreException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + int depth = 1000; + Sha256Hash blockHashAtDepth = BitcoinTestUtils.createHash(1); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + when(bridgeSupportMock.getBtcBlockchainBlockHashAtDepth(depth)).thenReturn(blockHashAtDepth); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_BLOCK_HASH_AT_DEPTH.getFunction(); + byte[] data = function.encode(depth); + + if (activationConfig.isActive(ConsensusRule.RSKIP89, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainBlockHashAtDepth(depth); + } + } else { + // Pre RSKIP89 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcTransactionConfirmations(MessageCall.MsgType msgType, ActivationConfig activationConfig) + throws VMException, IOException, BlockStoreException { + + Sha256Hash btcTxHash = BitcoinTestUtils.createHash(1); + Sha256Hash btcBlockHash = BitcoinTestUtils.createHash(2); + int merkleBranchPath = 1; + List merkleBranchHashes = Arrays.asList( + BitcoinTestUtils.createHash(10), + BitcoinTestUtils.createHash(11), + BitcoinTestUtils.createHash(12) + ); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_TRANSACTION_CONFIRMATIONS.getFunction(); + byte[] data = function.encode( + btcTxHash.getBytes(), + btcBlockHash.getBytes(), + merkleBranchPath, + merkleBranchHashes.stream().map(Sha256Hash::getBytes).toArray() + ); + + if (activationConfig.isActive(ConsensusRule.RSKIP122, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcTransactionConfirmations( + any(), + any(), + any() + ); + } + } else { + // Pre RSKIP122 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcTxHashProcessedHeight(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + Sha256Hash btcTxHash = BitcoinTestUtils.createHash(1); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_TX_HASH_PROCESSED_HEIGHT.getFunction(); + byte[] data = function.encode(btcTxHash.toString()); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcTxHashProcessedHeight(btcTxHash); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFederationAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + Address federationAddress = Address.fromBase58(networkParameters, "32Bhwee9FzQbuaG29RcXpdrvYnvZeMk11M"); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + when(bridgeSupportMock.getFederationAddress()).thenReturn(federationAddress); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEDERATION_ADDRESS.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFederationAddress(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFederationCreationBlockNumber(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { Transaction rskTxMock = mock(Transaction.class); - doReturn(new RskAddress(key.getAddress())).when(rskTxMock).getSender(any(SignatureCache.class)); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEDERATION_CREATION_BLOCK_NUMBER.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFederationCreationBlockNumber(); + } + } - ActivationConfig activations = spy(ActivationConfigsForTest.genesis()); - doReturn(true).when(activations).isActive(eq(RSKIP143), anyLong()); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFederationCreationTime(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); - return getBridgeInstance(rskTxMock, bridgeSupportMock, activations, signatureCache); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + when(bridgeSupportMock.getFederationCreationTime()).thenReturn(Instant.ofEpochSecond(100_000L)); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEDERATION_CREATION_TIME.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFederationCreationTime(); + } } - /** - * Gets a bridge instance mocking the transaction and BridgeSupportFactory - * - * @param txMock Provide the transaction to be used - * @param bridgeSupportInstance Provide the bridgeSupport to be used - * @param activationConfig Provide the activationConfig to be used - * @return Bridge instance - */ - private Bridge getBridgeInstance(Transaction txMock, BridgeSupport bridgeSupportInstance, ActivationConfig activationConfig, SignatureCache signatureCache) { - BridgeSupportFactory bridgeSupportFactoryMock = mock(BridgeSupportFactory.class); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFederationSize(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); - when(bridgeSupportFactoryMock.newInstance(any(), any(), any(), any())).thenReturn(bridgeSupportInstance); - Bridge bridge = new Bridge(PrecompiledContracts.BRIDGE_ADDR, constants, activationConfig, bridgeSupportFactoryMock, signatureCache); - bridge.init(txMock, getGenesisBlock(), null, null, null, null); - return bridge; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEDERATION_SIZE.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFederationSize(); + } } - private Bridge getBridgeInstance(BridgeSupport bridgeSupportInstance, ActivationConfig activationConfig) { - return getBridgeInstance(mock(Transaction.class), bridgeSupportInstance, activationConfig, signatureCache); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFederationThreshold(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEDERATION_THRESHOLD.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFederationThreshold(); + } } - @Deprecated - private Bridge getBridgeInstance(BridgeSupport bridgeSupportInstance) { - return getBridgeInstance(bridgeSupportInstance, activationConfig); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFederatorPublicKey(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + int federatorIndex = 1; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEDERATOR_PUBLIC_KEY.getFunction(); + byte[] data = function.encode(federatorIndex); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + // Post RSKIP123 this method is no longer enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFederatorPublicKey(federatorIndex); + } } - private Block getGenesisBlock() { - return new BlockGenerator().getGenesisBlock(); + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFederatorPublicKeyOfType(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + int federatorIndex = 1; + KeyType keyType = KeyType.BTC; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEDERATOR_PUBLIC_KEY_OF_TYPE.getFunction(); + byte[] data = function.encode(federatorIndex, keyType.getValue()); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFederatorPublicKeyOfType( + federatorIndex, + keyType + ); + } + } else { + // Pre RSKIP123 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getFeePerKb(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + Coin feePerKb = Coin.COIN; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + when(bridgeSupportMock.getFeePerKb()).thenReturn(feePerKb); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_FEE_PER_KB.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getFeePerKb(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getLockWhitelistAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + int index = 1; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_LOCK_WHITELIST_ADDRESS.getFunction(); + byte[] data = function.encode(index); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getLockWhitelistEntryByIndex(index); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getLockWhitelistEntryByAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + String addressBase58 = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_LOCK_WHITELIST_ENTRY_BY_ADDRESS.getFunction(); + byte[] data = function.encode(addressBase58); + + if (activationConfig.isActive(ConsensusRule.RSKIP87, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getLockWhitelistEntryByAddress(addressBase58); + } + } else { + // Pre RSKIP87 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getLockWhitelistSize(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_LOCK_WHITELIST_SIZE.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getLockWhitelistSize(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getMinimumLockTxValue(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + Coin minimumPeginTxValue = Coin.COIN; + BridgeConstants bridgeConstants = mock(BridgeConstants.class); + when(bridgeConstants.getMinimumPeginTxValue(any())).thenReturn(minimumPeginTxValue); + Constants constants = mock(Constants.class); + when(constants.getBridgeConstants()).thenReturn(bridgeConstants); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .constants(constants) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_MINIMUM_LOCK_TX_VALUE.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeConstants, times(1)).getMinimumPeginTxValue(any()); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getPendingFederationHash(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PENDING_FEDERATION_HASH.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getPendingFederationHash(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getPendingFederationSize(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException + { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PENDING_FEDERATION_SIZE.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getPendingFederationSize(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getPendingFederatorPublicKey(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PENDING_FEDERATOR_PUBLIC_KEY.getFunction(); + + int federatorIndex = 1; + byte[] data = function.encode(federatorIndex); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + // Post RSKIP123 this method is no longer enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getPendingFederatorPublicKey(federatorIndex); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getPendingFederatorPublicKeyOfType(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PENDING_FEDERATOR_PUBLIC_KEY_OF_TYPE.getFunction(); + + int federatorIndex = 1; + FederationMember.KeyType keyType = FederationMember.KeyType.BTC; + byte[] data = function.encode(federatorIndex, keyType.getValue()); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getPendingFederatorPublicKeyOfType( + federatorIndex, + keyType + ); + } + } else { + // Pre RSKIP123 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getRetiringFederationAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Address retiringFederationAddress = Address.fromBase58(networkParameters, "32Bhwee9FzQbuaG29RcXpdrvYnvZeMk11M"); + when(bridgeSupportMock.getRetiringFederationAddress()).thenReturn(retiringFederationAddress); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_RETIRING_FEDERATION_ADDRESS.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getRetiringFederationAddress(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getRetiringFederationCreationBlockNumber(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_RETIRING_FEDERATION_CREATION_BLOCK_NUMBER.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getRetiringFederationCreationBlockNumber(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getRetiringFederationCreationTime(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + when(bridgeSupportMock.getFederationCreationTime()).thenReturn(Instant.ofEpochSecond(100_000L)); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_RETIRING_FEDERATION_CREATION_TIME.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getRetiringFederationCreationTime(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getRetiringFederationSize(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_RETIRING_FEDERATION_SIZE.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getRetiringFederationSize(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getRetiringFederationThreshold(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_RETIRING_FEDERATION_THRESHOLD.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getRetiringFederationThreshold(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getRetiringFederatorPublicKey(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + int federatorIndex = 1; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_RETIRING_FEDERATOR_PUBLIC_KEY.getFunction(); + byte[] data = function.encode(federatorIndex); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + // Post RSKIP123 this method is no longer enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getRetiringFederatorPublicKey(federatorIndex); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getRetiringFederatorPublicKeyOfType(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_RETIRING_FEDERATOR_PUBLIC_KEY_OF_TYPE.getFunction(); + + int federatorIndex = 1; + FederationMember.KeyType keyType = FederationMember.KeyType.BTC; + byte[] data = function.encode(federatorIndex, keyType.getValue()); + + if (activationConfig.isActive(ConsensusRule.RSKIP123, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getRetiringFederatorPublicKeyOfType( + federatorIndex, + keyType + ); + } + } else { + // Pre RSKIP123 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getStateForBtcReleaseClient(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_STATE_FOR_BTC_RELEASE_CLIENT.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getStateForBtcReleaseClient(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getStateForDebugging(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_STATE_FOR_DEBUGGING.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getStateForDebugging(); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getLockingCap(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Coin lockingCap = Coin.COIN; + when(bridgeSupportMock.getLockingCap()).thenReturn(lockingCap); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_LOCKING_CAP.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP134, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getLockingCap(); + } + } else { + // Pre RSKIP134 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getActivePowpegRedeemScript(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Integer[] activeMemberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; + Federation activeFederation = FederationTestUtils.getFederation(activeMemberPKs); + Script activePowpegRedeemScript = activeFederation.getRedeemScript(); + when(bridgeSupportMock.getActivePowpegRedeemScript()).thenReturn(Optional.of(activePowpegRedeemScript)); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_ACTIVE_POWPEG_REDEEM_SCRIPT.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP293, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getActivePowpegRedeemScript(); + } + } else { + // Pre RSKIP293 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getActiveFederationCreationBlockHeight(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_ACTIVE_FEDERATION_CREATION_BLOCK_HEIGHT.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP186, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getActiveFederationCreationBlockHeight(); + } + } + else { + // Pre RSKIP186 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void increaseLockingCap(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + when(bridgeSupportMock.increaseLockingCap(any(), any())).thenReturn(true); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.INCREASE_LOCKING_CAP.getFunction(); + + long newLockingCap = 1; + byte[] data = function.encode(newLockingCap); + + if (activationConfig.isActive(ConsensusRule.RSKIP134, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).increaseLockingCap(any(), any()); + } + } + else { + // Pre RSKIP134 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void isBtcTxHashAlreadyProcessed(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Sha256Hash btcTxHash = Sha256Hash.of("btcTxHash".getBytes()); + when(bridgeSupportMock.isBtcTxHashAlreadyProcessed(btcTxHash)).thenReturn(true); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.IS_BTC_TX_HASH_ALREADY_PROCESSED.getFunction(); + byte[] data = function.encode(btcTxHash.toString()); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).isBtcTxHashAlreadyProcessed(btcTxHash); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void receiveHeaders(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + Transaction rskTxMock = mock(Transaction.class); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + boolean receiveHeadersIsPublic = + activationConfig.isActive(ConsensusRule.RSKIP124, 0) + && !activationConfig.isActive(ConsensusRule.RSKIP200, 0); + + // Pre RSKIP124 and post RSKIP200 receiveHeaders is callable only from active or retiring federation fed members + if (!receiveHeadersIsPublic) { + int senderPK = 101; // Sender PK belongs to active federation member PKs + Integer[] activeMemberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; + Federation activeFederation = FederationTestUtils.getFederation(activeMemberPKs); + + ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + } + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.RECEIVE_HEADERS.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + byte[] result = bridge.execute(data); + assertVoidMethodResult(activationConfig, result); + verify(bridgeSupportMock, times(1)).receiveHeaders(new BtcBlock[]{}); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void receiveHeader(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.RECEIVE_HEADER.getFunction(); + + BtcBlock btcBlock = new BtcBlock( + networkParameters, + 1, + BitcoinTestUtils.createHash(1), + BitcoinTestUtils.createHash(2), + 1, + 100L, + 1, + new ArrayList<>() + ).cloneAsHeader(); + + byte[] serializedBlockHeader = btcBlock.bitcoinSerialize(); + byte[] data = function.encode(serializedBlockHeader); + + if (activationConfig.isActive(ConsensusRule.RSKIP200, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).receiveHeader(any()); + } + } else { + // Pre RSKIP200 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void registerBtcTransaction(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + Transaction rskTxMock = mock(Transaction.class); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + boolean registerBtcTransactionIsPublic = activationConfig.isActive(ConsensusRule.RSKIP199, 0); + // Before RSKIP199 registerBtcTransaction was callable only from active or retiring federation fed members + if (!registerBtcTransactionIsPublic) { + int senderPK = 101; // Sender PK belongs to active federation member PKs + Integer[] activeMemberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; + Federation activeFederation = FederationTestUtils.getFederation(activeMemberPKs); + + ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + } + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.REGISTER_BTC_TRANSACTION.getFunction(); + + byte[] btcTxSerialized = new byte[]{1}; + int height = 0; + byte[] pmtSerialized = new byte[]{2}; + byte[] data = function.encode(btcTxSerialized, height, pmtSerialized); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + byte[] result = bridge.execute(data); + assertVoidMethodResult(activationConfig, result); + verify(bridgeSupportMock, times(1)).registerBtcTransaction( + any(Transaction.class), + any(byte[].class), + anyInt(), + any(byte[].class) + ); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void releaseBtc(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.RELEASE_BTC.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + byte[] result = bridge.execute(data); + assertVoidMethodResult(activationConfig, result); + verify(bridgeSupportMock, times(1)).releaseBtc(any()); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void removeLockWhitelistAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.REMOVE_LOCK_WHITELIST_ADDRESS.getFunction(); + + String addressBase58 = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; + byte[] data = function.encode(addressBase58); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).removeLockWhitelistAddress(any(), any()); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void rollbackFederation(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.ROLLBACK_FEDERATION.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).voteFederationChange(any(), any()); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void setLockWhiteListDisableBlockDelay(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.SET_LOCK_WHITELIST_DISABLE_BLOCK_DELAY.getFunction(); + + BigInteger disableBlockDelay = BigInteger.valueOf(100); + byte[] data = function.encode(disableBlockDelay); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).setLockWhitelistDisableBlockDelay(any(), any()); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void updateCollections(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + Transaction rskTxMock = mock(Transaction.class); + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + // updateCollections is only callable from active or retiring federation + int senderPK = 101; // Sender PK belongs to active federation member PKs + Integer[] activeMemberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600 }; + Federation activeFederation = FederationTestUtils.getFederation(activeMemberPKs); + + ECKey key = ECKey.fromPrivate(BigInteger.valueOf(senderPK)); + RskAddress txSender = new RskAddress(key.getAddress()); + doReturn(txSender).when(rskTxMock).getSender(any(SignatureCache.class)); + doReturn(activeFederation).when(bridgeSupportMock).getActiveFederation(); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.UPDATE_COLLECTIONS.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + byte[] result = bridge.execute(data); + assertVoidMethodResult(activationConfig, result); + verify(bridgeSupportMock, times(1)).updateCollections(rskTxMock); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void voteFeePerKb(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.VOTE_FEE_PER_KB.getFunction(); + + long feePerKB = 10_000; + byte[] data = function.encode(feePerKB); + + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).voteFeePerKbChange(any(), any()); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void registerBtcCoinbaseTransaction(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.REGISTER_BTC_COINBASE_TRANSACTION.getFunction(); + + byte[] btcTxSerialized = EMPTY_BYTE_ARRAY; + Sha256Hash blockHash = BitcoinTestUtils.createHash(1); + byte[] pmtSerialized = EMPTY_BYTE_ARRAY; + Sha256Hash witnessMerkleRoot = BitcoinTestUtils.createHash(2); + byte[] witnessReservedValue = new byte[32]; + byte[] data = function.encode( + btcTxSerialized, + blockHash.getBytes(), + pmtSerialized, + witnessMerkleRoot.getBytes(), + witnessReservedValue + ); + + if (activationConfig.isActive(ConsensusRule.RSKIP143, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + byte[] result = bridge.execute(data); + assertVoidMethodResult(activationConfig, result); + verify(bridgeSupportMock, times(1)).registerBtcCoinbaseTransaction( + btcTxSerialized, + blockHash, + pmtSerialized, + witnessMerkleRoot, + witnessReservedValue + ); + } + } else { + // Pre RSKIP143 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void hasBtcBlockCoinbaseTransactionInformation(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.HAS_BTC_BLOCK_COINBASE_TRANSACTION_INFORMATION.getFunction(); + + Sha256Hash blockHash = BitcoinTestUtils.createHash(2); + byte[] data = function.encode(blockHash.getBytes()); + + if (activationConfig.isActive(ConsensusRule.RSKIP143, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATICCALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).hasBtcBlockCoinbaseTransactionInformation(any()); + } + } else { + // Pre RSKIP143 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void registerFastBridgeBtcTransaction(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.REGISTER_FAST_BRIDGE_BTC_TRANSACTION.getFunction(); + + byte[] btcTxSerialized = new byte[]{1}; + int height = 1; + byte[] pmtSerialized = new byte[]{2}; + Keccak256 derivationArgumentsHash = PegTestUtils.createHash3(2); + byte[] refundAddressSerialized = new byte[21]; + RskAddress lbcAddress = new RskAddress("0xFcE93641243D1EFB6131277cCD1c0a60460d5610"); + byte[] lpAddressSerialized = new byte[21]; + boolean shouldTransferToContract = true; + byte[] data = function.encode( + btcTxSerialized, + height, + pmtSerialized, + derivationArgumentsHash.getBytes(), + refundAddressSerialized, + lbcAddress.toHexString(), + lpAddressSerialized, + shouldTransferToContract + ); + + if (activationConfig.isActive(ConsensusRule.RSKIP176, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && !msgType.equals(MessageCall.MsgType.CALL)) { + // Post arrowhead should fail for any msg type != CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).registerFlyoverBtcTransaction( + any(Transaction.class), + any(byte[].class), + anyInt(), + any(byte[].class), + any(Keccak256.class), + any(Address.class), + any(RskAddress.class), + any(Address.class), + any(boolean.class) + ); + } + } else { + // Pre RSKIP176 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainBestBlockHeader(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_BEST_BLOCK_HEADER.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP220, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainBestBlockHeader(); + } + } else { + // Pre RSKIP220 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainBlockHeaderByHash(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HASH.getFunction(); + + byte[] hashBytes = new byte[32]; + byte[] data = function.encode(hashBytes); + + if (activationConfig.isActive(ConsensusRule.RSKIP220, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainBlockHeaderByHash(Sha256Hash.wrap(hashBytes)); + } + } else { + // Pre RSKIP220 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainBlockHeaderByHeight(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HEIGHT.getFunction(); + + int height = 20; + byte[] data = function.encode(height); + + if (activationConfig.isActive(ConsensusRule.RSKIP220, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainBlockHeaderByHeight(height); + } + } else { + // Pre RSKIP220 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getBtcBlockchainParentBlockHeaderByHash(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_BTC_BLOCKCHAIN_PARENT_BLOCK_HEADER_BY_HASH.getFunction(); + + byte[] hashBytes = new byte[32]; + byte[] data = function.encode(hashBytes); + + if (activationConfig.isActive(ConsensusRule.RSKIP220, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getBtcBlockchainParentBlockHeaderByHash(any()); + } + } else { + // Pre RSKIP220 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getNextPegoutCreationBlockNumber(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException, BlockStoreException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_NEXT_PEGOUT_CREATION_BLOCK_NUMBER.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP271, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getNextPegoutCreationBlockNumber(); + } + } else { + // Pre RSKIP271 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getQueuedPegoutsCount(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_QUEUED_PEGOUTS_COUNT.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP271, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getQueuedPegoutsCount(); + } + } else { + // Pre RSKIP271 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getEstimatedFeesForNextPegoutEvent(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Coin estimatedFeesForNextPegout = Coin.SATOSHI; + when(bridgeSupportMock.getEstimatedFeesForNextPegOutEvent()).thenReturn(estimatedFeesForNextPegout); + + Bridge bridge = bridgeBuilder + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_ESTIMATED_FEES_FOR_NEXT_PEGOUT_EVENT.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP271, 0)) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0) && + !(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock, times(1)).getEstimatedFeesForNextPegOutEvent(); + } + } else { + // Pre RSKIP271 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + + private static Stream msgTypesAndActivations() { + List argumentsList = new ArrayList<>(); + List activationConfigs = Arrays.asList( + ActivationConfigsForTest.orchid(), + ActivationConfigsForTest.wasabi100(), + ActivationConfigsForTest.papyrus200(), + ActivationConfigsForTest.iris300(), + ActivationConfigsForTest.hop400(), + ActivationConfigsForTest.fingerroot500(), + ActivationConfigsForTest.arrowhead600() + ); + + for (MessageCall.MsgType msgType : MessageCall.MsgType.values()) { + for(ActivationConfig activationConfig : activationConfigs) { + argumentsList.add(Arguments.of(msgType, activationConfig)); + } + } + + return argumentsList.stream(); + } + + private void assertVoidMethodResult(ActivationConfig activationConfig, byte[] result) { + if (activationConfig.isActive(ConsensusRule.RSKIP417, 0)) { + assertArrayEquals(EMPTY_BYTE_ARRAY, result); + } else { + assertNull(result); + } } } diff --git a/rskj-core/src/test/java/co/rsk/peg/LegacyErpFederationTest.java b/rskj-core/src/test/java/co/rsk/peg/LegacyErpFederationTest.java new file mode 100644 index 00000000000..e69de29bb2d diff --git a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java index c91d6c0d746..77d9b07f412 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java @@ -85,6 +85,9 @@ private void testChangePowpeg( activations ); + repository.addBalance(PrecompiledContracts.BRIDGE_ADDR, co.rsk.core.Coin.fromBitcoin(bridgeConstants.getMaxRbtc())); + bridgeStorageProvider.setLockingCap(bridgeConstants.getMaxRbtc()); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( bridgeConstants.getBtcParams(), 100, diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationTest.java index 65c7bdd9185..5fcace9599d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationTest.java @@ -1,35 +1,28 @@ package co.rsk.peg.federation; import static co.rsk.bitcoinj.script.Script.MAX_SCRIPT_ELEMENT_SIZE; -import static co.rsk.peg.federation.ErpFederationCreationException.Reason.NULL_OR_EMPTY_EMERGENCY_KEYS; -import static co.rsk.peg.federation.ErpFederationCreationException.Reason.REDEEM_SCRIPT_CREATION_FAILED; import static co.rsk.peg.bitcoin.RedeemScriptCreationException.Reason.INVALID_CSV_VALUE; import static co.rsk.peg.bitcoin.ScriptCreationException.Reason.ABOVE_MAX_SCRIPT_ELEMENT_SIZE; +import static co.rsk.peg.federation.ErpFederationCreationException.Reason.NULL_OR_EMPTY_EMERGENCY_KEYS; +import static co.rsk.peg.federation.ErpFederationCreationException.Reason.REDEEM_SCRIPT_CREATION_FAILED; import static org.junit.jupiter.api.Assertions.*; import co.rsk.bitcoinj.core.*; -import co.rsk.bitcoinj.script.*; -import co.rsk.config.BridgeConstants; -import co.rsk.config.BridgeMainNetConstants; -import co.rsk.config.BridgeRegTestConstants; -import co.rsk.config.BridgeTestNetConstants; +import co.rsk.bitcoinj.script.Script; +import co.rsk.bitcoinj.script.ScriptOpCodes; +import co.rsk.config.*; import co.rsk.peg.bitcoin.*; - +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.time.Instant; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -63,9 +56,16 @@ void setup() { BtcECKey federator8PublicKey = BtcECKey.fromPublicOnly(Hex.decode("031aabbeb9b27258f98c2bf21f36677ae7bae09eb2d8c958ef41a20a6e88626d26")); BtcECKey federator9PublicKey = BtcECKey.fromPublicOnly(Hex.decode("0245ef34f5ee218005c9c21227133e8568a4f3f11aeab919c66ff7b816ae1ffeea")); defaultKeys = Arrays.asList( - federator0PublicKey, federator1PublicKey, federator2PublicKey, - federator3PublicKey, federator4PublicKey, federator5PublicKey, - federator6PublicKey, federator7PublicKey, federator8PublicKey, federator9PublicKey + federator0PublicKey, + federator1PublicKey, + federator2PublicKey, + federator3PublicKey, + federator4PublicKey, + federator5PublicKey, + federator6PublicKey, + federator7PublicKey, + federator8PublicKey, + federator9PublicKey ); defaultThreshold = defaultKeys.size() / 2 + 1; emergencyKeys = bridgeConstants.getErpFedPubKeysList(); @@ -526,6 +526,27 @@ void spendFromP2shErpFed( )); } + @Test + void getMemberByBtcPublicKey_passing_existing_btcPublicKey_should_return_found_member(){ + BtcECKey existingMemberBtcPublicKey = defaultKeys.get(0); + Optional foundMember = federation.getMemberByBtcPublicKey(existingMemberBtcPublicKey); + Assertions.assertTrue(foundMember.isPresent()); + Assertions.assertEquals(existingMemberBtcPublicKey, foundMember.get().getBtcPublicKey()); + } + + @Test + void getMemberByBtcPublicKey_passing_non_existing_btcPublicKey_should_return_empty(){ + BtcECKey noExistingBtcPublicKey = new BtcECKey(); + Optional foundMember = federation.getMemberByBtcPublicKey(noExistingBtcPublicKey); + Assertions.assertFalse(foundMember.isPresent()); + } + + @Test + void getMemberByBtcPublicKey_passing_null_btcPublicKey_should_return_empty(){ + Optional foundMember = federation.getMemberByBtcPublicKey(null); + Assertions.assertFalse(foundMember.isPresent()); + } + private void validateP2shErpRedeemScript( Script redeemScript, List defaultKeys, diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/StandardMultisigFederationTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/StandardMultisigFederationTest.java index 244fb990078..3b2529961bd 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/StandardMultisigFederationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/StandardMultisigFederationTest.java @@ -361,4 +361,25 @@ void isMember(){ FederationMember invalidPubKeys = new FederationMember(invalidBtcKey, invalidRskKey, invalidRskKey); assertFalse(federation.isMember(invalidPubKeys)); } + + @Test + void getMemberByBtcPublicKey_passing_existing_btcPublicKey_should_return_found_member(){ + BtcECKey existingMemberBtcPublicKey = sortedPublicKeys.get(0); + Optional foundMember = federation.getMemberByBtcPublicKey(existingMemberBtcPublicKey); + Assertions.assertTrue(foundMember.isPresent()); + Assertions.assertEquals(existingMemberBtcPublicKey, foundMember.get().getBtcPublicKey()); + } + + @Test + void getMemberByBtcPublicKey_passing_non_existing_btcPublicKey_should_return_empty(){ + BtcECKey noExistingBtcPublicKey = new BtcECKey(); + Optional foundMember = federation.getMemberByBtcPublicKey(noExistingBtcPublicKey); + Assertions.assertFalse(foundMember.isPresent()); + } + + @Test + void getMemberByBtcPublicKey_passing_null_btcPublicKey_should_return_empty(){ + Optional foundMember = federation.getMemberByBtcPublicKey(null); + Assertions.assertFalse(foundMember.isPresent()); + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index 3c5998c905d..d666f35c50b 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -19,25 +19,29 @@ import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.ScriptBuilder; +import co.rsk.config.BridgeMainNetConstants; import co.rsk.config.BridgeRegTestConstants; import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; import co.rsk.peg.*; +import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.federation.*; import co.rsk.peg.PegTestUtils; import co.rsk.peg.pegin.RejectedPeginReason; import org.bouncycastle.util.encoders.Hex; import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.*; import org.ethereum.crypto.ECKey; -import org.ethereum.util.ByteUtil; import org.ethereum.vm.LogInfo; import org.ethereum.vm.PrecompiledContracts; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import java.math.BigInteger; @@ -46,6 +50,7 @@ import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -63,12 +68,13 @@ class BridgeEventLoggerImplTest { private static final BridgeRegTestConstants CONSTANTS = BridgeRegTestConstants.getInstance(); + private final ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); + private List eventLogs; private BridgeEventLogger eventLogger; private BtcTransaction btcTxMock; private BtcTransaction btcTx; private SignatureCache signatureCache; - private ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); @BeforeEach void setup() { @@ -176,21 +182,66 @@ void logUpdateCollections() { assertEvent(eventLogs, 0, BridgeEvents.UPDATE_COLLECTIONS.getEvent(), new Object[]{}, new Object[]{tx.getSender(signatureCache).toString()}); } - @Test - void logAddSignature() { - // Setup logAddSignature params - BtcECKey federatorPubKey = BtcECKey.fromPrivate(BigInteger.valueOf(2L)); + private static Stream logAddSignatureArgProvider() { + ActivationConfig.ForBlock fingerrootActivations = ActivationConfigsForTest.fingerroot500().forBlock(0); + ActivationConfig.ForBlock arrowheadActivations = ActivationConfigsForTest.arrowhead600().forBlock(0); + + BtcECKey btcKey = BtcECKey.fromPrivate(Hex.decode("000000000000000000000000000000000000000000000000000000000000015e")); // 350 + ECKey rskKey = ECKey.fromPrivate(Hex.decode("000000000000000000000000000000000000000000000000000000000000015f")); // 351 + ECKey mstKey = ECKey.fromPrivate(Hex.decode("0000000000000000000000000000000000000000000000000000000000000160")); // 352 + + String addressDerivedFromBtcKey = "dbc29273d4de3d5645e308c7e629d28d4499b3d3"; + String addressDerivedFromRskKey = "74891a05ad4d7ec87c1cffe9bd00bb4e1382b586"; + + FederationMember singleKeyFedMember = new FederationMember( + btcKey, + ECKey.fromPublicOnly(btcKey.getPubKey()), + ECKey.fromPublicOnly(btcKey.getPubKey()) + ); + + FederationMember multiKeyFedMember = new FederationMember( + btcKey, + rskKey, + mstKey + ); + + return Stream.of( + Arguments.of(fingerrootActivations, singleKeyFedMember, addressDerivedFromBtcKey), + Arguments.of(fingerrootActivations, multiKeyFedMember, addressDerivedFromBtcKey), + Arguments.of(arrowheadActivations, singleKeyFedMember, addressDerivedFromBtcKey), // Given this is a single key fed member, the rsk address is equal to the one obtained from btc key + Arguments.of(arrowheadActivations, multiKeyFedMember, addressDerivedFromRskKey) + ); + } + + @ParameterizedTest() + @MethodSource("logAddSignatureArgProvider") + void logAddSignature(ActivationConfig.ForBlock activations, FederationMember federationMember, String expectedRskAddress) { + // Arrange + BridgeMainNetConstants bridgeMainNetConstants = BridgeMainNetConstants.getInstance(); + BlockTxSignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); + LinkedList eventLogs = new LinkedList<>(); + BridgeEventLogger eventLogger = new BridgeEventLoggerImpl(bridgeMainNetConstants, activations, eventLogs, signatureCache); + + BtcECKey federatorBtcPubKey = federationMember.getBtcPublicKey(); + Keccak256 rskTxHash = PegTestUtils.createHash3(1); - when(btcTxMock.getHashAsString()).thenReturn("3e72fdbae7bbd103f08e876c765e3d5ba35db30ea46cb45ab52803f987ead9fb"); + + BtcTransaction btcTxMock = mock(BtcTransaction.class); + when(btcTxMock.getHash()).thenReturn(BitcoinTestUtils.createHash(1)); + when(btcTxMock.getHashAsString()).thenReturn(rskTxHash.toHexString()); // Act - eventLogger.logAddSignature(federatorPubKey, btcTxMock, rskTxHash.getBytes()); + eventLogger.logAddSignature(federationMember, btcTxMock, rskTxHash.getBytes()); + // Assert commonAssertLogs(eventLogs); assertTopics(3, eventLogs); - ECKey key = ECKey.fromPublicOnly(federatorPubKey.getPubKey()); - String federatorRskAddress = ByteUtil.toHexString(key.getAddress()); - assertEvent(eventLogs, 0, BridgeEvents.ADD_SIGNATURE.getEvent(), new Object[]{rskTxHash.getBytes(), federatorRskAddress}, new Object[]{federatorPubKey.getPubKey()}); + + CallTransaction.Function bridgeEvent = BridgeEvents.ADD_SIGNATURE.getEvent(); + Object[] eventTopics = new Object[]{rskTxHash.getBytes(), expectedRskAddress}; + Object[] eventParams = new Object[]{federatorBtcPubKey.getPubKey()}; + + assertEvent(eventLogs, 0, bridgeEvent, eventTopics, eventParams); } @Test diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java index 52c8419f8a5..23e9ad54133 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java @@ -117,10 +117,11 @@ void testLogAddSignatureBeforeRskip146() { // Setup logAddSignature params BtcECKey federatorPubKey = BtcECKey.fromPrivate(BigInteger.valueOf(2L)); + FederationMember federationMember = FederationTestUtils.getFederationMemberWithKey(federatorPubKey); when(btcTxMock.getHashAsString()).thenReturn("3e72fdbae7bbd103f08e876c765e3d5ba35db30ea46cb45ab52803f987ead9fb"); // Act - eventLogger.logAddSignature(federatorPubKey, btcTxMock, rskTxHash.getBytes()); + eventLogger.logAddSignature(federationMember, btcTxMock, rskTxHash.getBytes()); // Assert log size Assertions.assertEquals(1, eventLogs.size()); @@ -149,8 +150,9 @@ void testLogAddSignatureBeforeRskip146() { void testLogAddSignatureAfterRskip146() { when(activations.isActive(ConsensusRule.RSKIP146)).thenReturn(true); BtcECKey federatorPublicKey = new BtcECKey(); + FederationMember federationMember = FederationTestUtils.getFederationMemberWithKey(federatorPublicKey); byte[] bytes = rskTxHash.getBytes(); - assertThrows(DeprecatedMethodCallException.class, () -> eventLogger.logAddSignature(federatorPublicKey, btcTxMock, bytes)); + assertThrows(DeprecatedMethodCallException.class, () -> eventLogger.logAddSignature(federationMember, btcTxMock, bytes)); } @Test diff --git a/rskj-core/src/test/java/co/rsk/remasc/RemascFederationProviderTest.java b/rskj-core/src/test/java/co/rsk/remasc/RemascFederationProviderTest.java index b2d318d71f5..ec8e4b6df22 100644 --- a/rskj-core/src/test/java/co/rsk/remasc/RemascFederationProviderTest.java +++ b/rskj-core/src/test/java/co/rsk/remasc/RemascFederationProviderTest.java @@ -1,11 +1,19 @@ package co.rsk.remasc; +import static org.mockito.Mockito.*; + import co.rsk.blockchain.utils.BlockGenerator; -import co.rsk.config.BridgeRegTestConstants; +import co.rsk.config.BridgeMainNetConstants; +import co.rsk.core.RskAddress; +import co.rsk.peg.BridgeStorageProvider; +import co.rsk.peg.FederationSupport; +import co.rsk.peg.federation.FederationMember; import co.rsk.test.builders.BlockChainBuilder; -import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; +import co.rsk.util.HexUtils; +import org.ethereum.config.blockchain.upgrades.*; import org.ethereum.core.Blockchain; import org.ethereum.core.Genesis; +import org.ethereum.vm.PrecompiledContracts; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -13,32 +21,90 @@ * Created by ajlopez on 14/11/2017. */ class RemascFederationProviderTest { + + private static final String BTC_PUBLIC_KEY = "0x03a5e32151f974c35c5d08af36a8d6af0593248e8e4adca7ed0148192eb2f5d1bf"; + private static final String RSK_ADDRESS_FROM_BTC_PUBLIC_KEY = "131c7c821b0e4a1ab570feed952d9304e03fddd1"; + private static final String RSK_PUBLIC_KEY = "0x02f3b5fb3121932db9450121b0a37f3f051dc8b3fd5f1ea5e7cf726947d8f69c28"; + private static final String RSK_ADDRESS_FROM_RSK_PUBLIC_KEY = "54a948b3d76ce84e5c7b4b3cd01f2af1f18d41e0"; + @Test void getDefaultFederationSize() { RemascFederationProvider provider = getRemascFederationProvider(); - Assertions.assertEquals(3, provider.getFederationSize()); + Assertions.assertEquals(15, provider.getFederationSize()); } @Test - void getFederatorAddress() { - RemascFederationProvider provider = getRemascFederationProvider(); + void getFederatorAddress_preRSKIP415_returnsRskAddressDerivedFromBtcPublicKey() { + + int federatorIndex = 0; + + ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); + when(activations.isActive(ConsensusRule.RSKIP415)).thenReturn(false); + + FederationSupport federationSupport = mock(FederationSupport.class); + + byte[] btcPublicKey = HexUtils.strHexOrStrNumberToByteArray(BTC_PUBLIC_KEY); - byte[] address = provider.getFederatorAddress(0).getBytes(); + when(federationSupport.getFederatorBtcPublicKey(federatorIndex)) + .thenReturn(btcPublicKey); + + RemascFederationProvider provider = new RemascFederationProvider(activations, federationSupport); + + RskAddress actualRskAddress = provider.getFederatorAddress(federatorIndex); + + Assertions.assertEquals(RSK_ADDRESS_FROM_BTC_PUBLIC_KEY, actualRskAddress.toHexString()); + verify(federationSupport, never()).getFederatorPublicKeyOfType(federatorIndex, FederationMember.KeyType.RSK); + verify(federationSupport, times(1)).getFederatorBtcPublicKey(federatorIndex); + + } + + @Test + void getFederatorAddress_postRSKIP415_returnsRskAddressDerivedFromRskPublicKey() { + + int federatorIndex = 0; + + ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); + when(activations.isActive(ConsensusRule.RSKIP415)).thenReturn(true); + + FederationSupport federationSupport = mock(FederationSupport.class); + + byte[] rskPublicKey = HexUtils.strHexOrStrNumberToByteArray(RSK_PUBLIC_KEY); + + when(federationSupport.getFederatorPublicKeyOfType(federatorIndex, FederationMember.KeyType.RSK)) + .thenReturn(rskPublicKey); + + RemascFederationProvider provider = new RemascFederationProvider(activations, federationSupport); + + RskAddress actualRskAddress = provider.getFederatorAddress(federatorIndex); + + Assertions.assertEquals(RSK_ADDRESS_FROM_RSK_PUBLIC_KEY, actualRskAddress.toHexString()); + verify(federationSupport, times(1)).getFederatorPublicKeyOfType(federatorIndex, FederationMember.KeyType.RSK); + verify(federationSupport, never()).getFederatorBtcPublicKey(federatorIndex); - Assertions.assertNotNull(address); - Assertions.assertEquals(20, address.length); } private static RemascFederationProvider getRemascFederationProvider() { + Genesis genesisBlock = new BlockGenerator().getGenesisBlock(); BlockChainBuilder builder = new BlockChainBuilder().setTesting(true).setGenesis(genesisBlock); Blockchain blockchain = builder.build(); - return new RemascFederationProvider( - ActivationConfigsForTest.all(), - BridgeRegTestConstants.getInstance(), + ActivationConfig.ForBlock activations = ActivationConfigsForTest.all().forBlock(blockchain.getBestBlock().getNumber()); + + BridgeMainNetConstants bridgeMainNetConstants = BridgeMainNetConstants.getInstance(); + + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( builder.getRepository(), - blockchain.getBestBlock() + PrecompiledContracts.BRIDGE_ADDR, + bridgeMainNetConstants, + activations + ); + + FederationSupport federationSupport = new FederationSupport(bridgeMainNetConstants, bridgeStorageProvider, blockchain.getBestBlock(), activations); + + return new RemascFederationProvider( + activations, + federationSupport ); } } diff --git a/rskj-core/src/test/java/co/rsk/remasc/RemascProcessMinerFeesTest.java b/rskj-core/src/test/java/co/rsk/remasc/RemascProcessMinerFeesTest.java index 4dc1ebf7f17..572aff76f27 100644 --- a/rskj-core/src/test/java/co/rsk/remasc/RemascProcessMinerFeesTest.java +++ b/rskj-core/src/test/java/co/rsk/remasc/RemascProcessMinerFeesTest.java @@ -32,12 +32,11 @@ import co.rsk.db.RepositorySnapshot; import co.rsk.db.StateRootHandler; import co.rsk.db.StateRootsStoreImpl; -import co.rsk.peg.BridgeSupportFactory; -import co.rsk.peg.PegTestUtils; -import co.rsk.peg.RepositoryBtcBlockStoreWithCache; +import co.rsk.peg.*; import co.rsk.test.builders.BlockChainBuilder; import org.bouncycastle.util.encoders.Hex; import org.ethereum.TestUtils; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.*; @@ -966,7 +965,19 @@ private HashMap getAccountsWithExpectedBalance(List otherAcc } private void validateFederatorsBalanceIsCorrect(RepositorySnapshot repository, long federationReward, Block executionBlock) { - RemascFederationProvider provider = new RemascFederationProvider(config.getActivationConfig(), config.getNetworkConstants().getBridgeConstants(), repository.startTracking(), executionBlock); + + ActivationConfig.ForBlock activations = config.getActivationConfig().forBlock(executionBlock.getNumber()); + + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( + repository.startTracking(), + PrecompiledContracts.BRIDGE_ADDR, + config.getNetworkConstants().getBridgeConstants(), + activations + ); + + FederationSupport federationSupport = new FederationSupport(config.getNetworkConstants().getBridgeConstants(), bridgeStorageProvider, executionBlock, activations); + + RemascFederationProvider provider = new RemascFederationProvider(config.getActivationConfig().forBlock(executionBlock.getNumber()), federationSupport); int nfederators = provider.getFederationSize(); diff --git a/rskj-core/src/test/java/co/rsk/remasc/RemascRskAddressActivationTest.java b/rskj-core/src/test/java/co/rsk/remasc/RemascRskAddressActivationTest.java index ffbce207cdb..60dde908511 100644 --- a/rskj-core/src/test/java/co/rsk/remasc/RemascRskAddressActivationTest.java +++ b/rskj-core/src/test/java/co/rsk/remasc/RemascRskAddressActivationTest.java @@ -53,31 +53,40 @@ void testActivation() { final RemascConfig remascConfig = spy(new RemascConfigFactory(RemascContract.REMASC_CONFIG) .createRemascConfig("regtest")); - final Remasc remasc = new Remasc(Constants.regtest(), activationConfig, repositoryMock, - blockStoreMock, remascConfig, txMock, PrecompiledContracts.REMASC_ADDR, blockMock, logs); - when(remascConfig.getRskLabsAddress()).thenReturn(rskLabsAddress); when(remascConfig.getRskLabsAddressRskip218()).thenReturn(rskLabsAddressRskip218); - when(activationConfig.isActive(ConsensusRule.RSKIP218, 1)).thenReturn(false); - when(activationConfig.isActive(ConsensusRule.RSKIP218, 2)).thenReturn(true); + ActivationConfig.ForBlock activationsBeforeRSKIP218 = mock(ActivationConfig.ForBlock.class); + when(activationsBeforeRSKIP218.isActive(ConsensusRule.RSKIP218)).thenReturn(false); + + ActivationConfig.ForBlock activationsAfterRSKIP218 = mock(ActivationConfig.ForBlock.class); + when(activationsAfterRSKIP218.isActive(ConsensusRule.RSKIP218)).thenReturn(true); + + when(activationConfig.forBlock(1)).thenReturn(activationsBeforeRSKIP218); + when(activationConfig.forBlock(2)).thenReturn(activationsAfterRSKIP218); when(blockMock.getNumber()).thenReturn(1L); + Remasc remasc = new Remasc(Constants.regtest(), activationConfig, repositoryMock, + blockStoreMock, remascConfig, txMock, PrecompiledContracts.REMASC_ADDR, blockMock, logs); + RskAddress actualAddress = remasc.getRskLabsAddress(); - Assertions.assertEquals(rskLabsAddress, actualAddress); Assertions.assertEquals(1L, blockMock.getNumber()); + Assertions.assertEquals(rskLabsAddress, actualAddress); + Assertions.assertFalse(activationConfig.isActive(ConsensusRule.RSKIP218, blockMock.getNumber())); verify(remascConfig).getRskLabsAddress(); when(blockMock.getNumber()).thenReturn(2L); + remasc = new Remasc(Constants.regtest(), activationConfig, repositoryMock, + blockStoreMock, remascConfig, txMock, PrecompiledContracts.REMASC_ADDR, blockMock, logs); + actualAddress = remasc.getRskLabsAddress(); Assertions.assertEquals(rskLabsAddressRskip218, actualAddress); Assertions.assertEquals(2L, blockMock.getNumber()); - Assertions.assertTrue(activationConfig.isActive(ConsensusRule.RSKIP218, blockMock.getNumber())); verify(remascConfig).getRskLabsAddressRskip218(); } } diff --git a/rskj-core/src/test/java/co/rsk/remasc/RemascStorageProviderTest.java b/rskj-core/src/test/java/co/rsk/remasc/RemascStorageProviderTest.java index 14ab83b2a37..c29b63d5f8a 100644 --- a/rskj-core/src/test/java/co/rsk/remasc/RemascStorageProviderTest.java +++ b/rskj-core/src/test/java/co/rsk/remasc/RemascStorageProviderTest.java @@ -29,13 +29,12 @@ import co.rsk.db.MutableTrieImpl; import co.rsk.db.RepositoryLocator; import co.rsk.db.RepositorySnapshot; -import co.rsk.peg.BridgeSupportFactory; -import co.rsk.peg.PegTestUtils; -import co.rsk.peg.RepositoryBtcBlockStoreWithCache; +import co.rsk.peg.*; import co.rsk.test.builders.BlockChainBuilder; import co.rsk.trie.Trie; import org.ethereum.TestUtils; import org.ethereum.config.Constants; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.*; @@ -293,7 +292,20 @@ void alwaysPaysFedBeforeRFS() throws IOException { Blockchain blockchain = testRunner.getBlockChain(); RepositoryLocator repositoryLocator = builder.getRepositoryLocator(); RepositorySnapshot repository = repositoryLocator.snapshotAt(blockchain.getBestBlock().getHeader()); - RemascFederationProvider federationProvider = new RemascFederationProvider(config.getActivationConfig(), config.getNetworkConstants().getBridgeConstants(), repository.startTracking(), testRunner.getBlockChain().getBestBlock()); + Block executionBlock = testRunner.getBlockChain().getBestBlock(); + + ActivationConfig.ForBlock activations = config.getActivationConfig().forBlock(executionBlock.getNumber()); + + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( + repository.startTracking(), + PrecompiledContracts.BRIDGE_ADDR, + config.getNetworkConstants().getBridgeConstants(), + activations + ); + + FederationSupport federationSupport = new FederationSupport(config.getNetworkConstants().getBridgeConstants(), bridgeStorageProvider, executionBlock, activations); + + RemascFederationProvider federationProvider = new RemascFederationProvider(config.getActivationConfig().forBlock(executionBlock.getNumber()), federationSupport); assertEquals(Coin.valueOf(0), this.getRemascStorageProvider(repository).getFederationBalance()); long federatorBalance = (168 / federationProvider.getFederationSize()) * 2; assertEquals(Coin.valueOf(federatorBalance), RemascTestRunner.getAccountBalance(repository, federationProvider.getFederatorAddress(0))); @@ -320,7 +332,20 @@ void doesntPayFedBelowMinimumRewardAfterRFS() throws IOException { Blockchain blockchain = testRunner.getBlockChain(); RepositoryLocator repositoryLocator = builder.getRepositoryLocator(); RepositorySnapshot repository = repositoryLocator.snapshotAt(blockchain.getBestBlock().getHeader()); - RemascFederationProvider federationProvider = new RemascFederationProvider(config.getActivationConfig(), config.getNetworkConstants().getBridgeConstants(), repository.startTracking(), testRunner.getBlockChain().getBestBlock()); + Block executionBlock = testRunner.getBlockChain().getBestBlock(); + + ActivationConfig.ForBlock activations = config.getActivationConfig().forBlock(executionBlock.getNumber()); + + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( + repository.startTracking(), + PrecompiledContracts.BRIDGE_ADDR, + config.getNetworkConstants().getBridgeConstants(), + activations + ); + + FederationSupport federationSupport = new FederationSupport(config.getNetworkConstants().getBridgeConstants(), bridgeStorageProvider, executionBlock, activations); + + RemascFederationProvider federationProvider = new RemascFederationProvider(config.getActivationConfig().forBlock(executionBlock.getNumber()), federationSupport); assertEquals(Coin.valueOf(336), this.getRemascStorageProvider(repository).getFederationBalance()); assertEquals(null, RemascTestRunner.getAccountBalance(repository, federationProvider.getFederatorAddress(0))); } @@ -370,7 +395,20 @@ void paysFedWhenHigherThanMinimumRewardAfterRFS() throws IOException { Blockchain blockchain = testRunner.getBlockChain(); RepositoryLocator repositoryLocator = builder.getRepositoryLocator(); RepositorySnapshot repository = repositoryLocator.snapshotAt(blockchain.getBestBlock().getHeader()); - RemascFederationProvider federationProvider = new RemascFederationProvider(config.getActivationConfig(), config.getNetworkConstants().getBridgeConstants(), repository.startTracking(), testRunner.getBlockChain().getBestBlock()); + Block executionBlock = testRunner.getBlockChain().getBestBlock(); + + ActivationConfig.ForBlock activations = config.getActivationConfig().forBlock(executionBlock.getNumber()); + + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( + repository.startTracking(), + PrecompiledContracts.BRIDGE_ADDR, + config.getNetworkConstants().getBridgeConstants(), + activations + ); + + FederationSupport federationSupport = new FederationSupport(config.getNetworkConstants().getBridgeConstants(), bridgeStorageProvider, executionBlock, activations); + + RemascFederationProvider federationProvider = new RemascFederationProvider(config.getActivationConfig().forBlock(executionBlock.getNumber()), federationSupport); long federatorBalance = (1680 / federationProvider.getFederationSize()) * 2; assertEquals(Coin.valueOf(0), this.getRemascStorageProvider(repository).getFederationBalance()); assertEquals(Coin.valueOf(federatorBalance), RemascTestRunner.getAccountBalance(repository, federationProvider.getFederatorAddress(0))); diff --git a/rskj-core/src/test/java/co/rsk/remasc/RemascTest.java b/rskj-core/src/test/java/co/rsk/remasc/RemascTest.java new file mode 100644 index 00000000000..896d2744285 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/remasc/RemascTest.java @@ -0,0 +1,163 @@ +package co.rsk.remasc; + +import co.rsk.config.RemascConfig; +import co.rsk.config.RemascConfigFactory; +import co.rsk.core.Coin; +import co.rsk.core.RskAddress; +import co.rsk.peg.PegTestUtils; +import org.ethereum.config.Constants; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.Repository; +import org.ethereum.db.BlockStore; +import org.ethereum.vm.DataWord; +import org.ethereum.vm.LogInfo; +import org.ethereum.vm.PrecompiledContracts; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.when; + +class RemascTest { + + private final static ActivationConfig genesisActivations = ActivationConfigsForTest.genesis(); + private final static ActivationConfig orchidActivations = ActivationConfigsForTest.orchid(); + private final static Constants mainnet = Constants.mainnet(); + private final static Coin minimumGasPrice = Coin.valueOf(100L); + + Repository repository; + BlockStore blockStore; + Block nextBlockToReward; + + private final static RemascConfig remascConfig = new RemascConfigFactory(RemascContract.REMASC_CONFIG).createRemascConfig("main"); + + RemascTransaction executionTx; + + RskAddress remascAddr; + + Block executionBlock; + + BlockHeader blockHeader; + + List logs; + + List blocks; + + DataWord rewardBalanceKey = DataWord.fromString("rewardBalance"); + + @BeforeEach + void setUp() { + repository = mock(Repository.class); + blockStore = mock(BlockStore.class); + nextBlockToReward = mock(Block.class); + long blockNumber = 3992L; + when(nextBlockToReward.getParentHash()).thenReturn(PegTestUtils.createHash3((int) (blockNumber-1))); + when(nextBlockToReward.getHash()).thenReturn(PegTestUtils.createHash3((int) blockNumber)); + when(nextBlockToReward.getNumber()).thenReturn(blockNumber); + when(blockStore.getBlockByHash(nextBlockToReward.getParentHash().getBytes())).thenReturn(nextBlockToReward); + + when(blockStore.getBlockAtDepthStartingAt(anyLong(), any(byte[].class))).thenReturn(nextBlockToReward); + + executionTx = mock(RemascTransaction.class); + + remascAddr = PrecompiledContracts.REMASC_ADDR; + + executionBlock = mock(Block.class); + when(executionBlock.getMinimumGasPrice()).thenReturn(minimumGasPrice); + when(executionBlock.getNumber()).thenReturn(4100L); + when(executionBlock.getHash()).thenReturn(PegTestUtils.createHash3(4100)); + + blockHeader = mock(BlockHeader.class); + when(executionBlock.getHeader()).thenReturn(blockHeader); + when(executionBlock.getParentHash()).thenReturn(PegTestUtils.createHash3(1)); + + logs = new ArrayList<>(); + } + + private void mockBlockStore(Coin feesPerBlock) { + blocks = new ArrayList<>(); + int uncleGenerationLimit = mainnet.getUncleGenerationLimit(); + + for (int i = 0; i < uncleGenerationLimit + 1; i++) { + Block currentBlock = mock(Block.class); + int currentBlockNumber = (int) (nextBlockToReward.getNumber() - i); + when(currentBlock.getParentHash()).thenReturn(PegTestUtils.createHash3(currentBlockNumber - 1)); + when(currentBlock.getHash()).thenReturn(PegTestUtils.createHash3(currentBlockNumber)); + when(currentBlock.getNumber()).thenReturn((long) currentBlockNumber); + + BlockHeader blockHeader = mock(BlockHeader.class); + + when(blockHeader.getPaidFees()).thenReturn(feesPerBlock); + when(blockHeader.getHash()).thenReturn(PegTestUtils.createHash3(currentBlockNumber)); + when(blockHeader.getCoinbase()).thenReturn(PegTestUtils.createRandomRskAddress()); + + when(currentBlock.getHeader()).thenReturn(blockHeader); + + when(blockStore.getBlockByHash(currentBlock.getHash().getBytes())).thenReturn(currentBlock); + blocks.add(currentBlock); + } + } + + private static Stream processMinersFeesArgProvider() { + Coin minimumPayableGas = Coin.valueOf(mainnet.getMinimumPayableGas().longValue()); + Coin minPayableFees = minimumGasPrice.multiply(minimumPayableGas.asBigInteger()); + + Coin equalToMinPayableFees = minPayableFees.multiply(BigInteger.valueOf(remascConfig.getSyntheticSpan())); + Coin belowMinPayableFees = equalToMinPayableFees.subtract(Coin.valueOf(1L)); + + return Stream.of( + Arguments.of(genesisActivations, belowMinPayableFees, true), + Arguments.of(orchidActivations, belowMinPayableFees, false), + Arguments.of(orchidActivations, equalToMinPayableFees, true) + ); + } + + @ParameterizedTest + @MethodSource("processMinersFeesArgProvider") + void test_processMinersFees(ActivationConfig activationConfig, Coin feesPerBlock, boolean success) { + mockBlockStore(feesPerBlock); + + // Arrange + Remasc remasc = new Remasc( + mainnet, + activationConfig, + repository, + blockStore, + remascConfig, + executionTx, + remascAddr, + executionBlock, + logs + ); + + // Act + remasc.processMinersFees(); + remasc.save(); + + // Assert + Coin syntheticReward = feesPerBlock.divide(BigInteger.valueOf(remascConfig.getSyntheticSpan())); + Coin expectedRemascPayment = feesPerBlock.subtract(syntheticReward); + Coin expectedRskLabPayment = syntheticReward.divide(BigInteger.valueOf(remascConfig.getRskLabsDivisor())); + RskAddress iovLabsAddress = remasc.getRskLabsAddress(); + + if (success) { + verify(this.repository, times(1)).addStorageRow(remascAddr, rewardBalanceKey, DataWord.valueOf(expectedRemascPayment.getBytes())); + verify(this.repository, times(1)).addBalance(remascAddr, expectedRskLabPayment.negate()); + verify(this.repository, times(1)).addBalance(iovLabsAddress, expectedRskLabPayment); + } else { + verify(this.repository, times(1)).addStorageRow(remascAddr, rewardBalanceKey, DataWord.valueOf(feesPerBlock.getBytes())); + verify(this.repository, never()).addBalance(any(), any()); + verify(this.repository, never()).addBalance(any(),any()); + } + } +} diff --git a/rskj-core/src/test/java/co/rsk/test/DslFilesTest.java b/rskj-core/src/test/java/co/rsk/test/DslFilesTest.java index 121f38fc4bb..cdac94ab829 100644 --- a/rskj-core/src/test/java/co/rsk/test/DslFilesTest.java +++ b/rskj-core/src/test/java/co/rsk/test/DslFilesTest.java @@ -89,6 +89,37 @@ void runCreate01Resource() throws FileNotFoundException, DslProcessorException { Assertions.assertEquals(BigIntegers.fromUnsignedByteArray(Hex.decode("fd59")), gasUsed); } + @Test + void runBridgeDelegateCallResource() throws FileNotFoundException, DslProcessorException { + DslParser parser = DslParser.fromResource("dsl/bridgeDelegateCall.txt"); + World world = new World(); + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + /// In this test we call the bridge with different methods + /// CALL, DELEGATECALL, CALLCODE, STATICCALL + /// The only one having to produce a releaseBtc blog is the CALL opcode which is the first one + /// This test relies on the current bridge behavior of emitting logs + Transaction transaction = world.getTransactionByName("tx01"); + Assertions.assertNotNull(transaction); + TransactionInfo txinfo = world.getBlockChain().getTransactionInfo(transaction.getHash().getBytes()); + Assertions.assertEquals(1, txinfo.getReceipt().getLogInfoList().size()); + + transaction = world.getTransactionByName("tx02"); + Assertions.assertNotNull(transaction); + txinfo = world.getBlockChain().getTransactionInfo(transaction.getHash().getBytes()); + Assertions.assertEquals(0, txinfo.getReceipt().getLogInfoList().size()); + + transaction = world.getTransactionByName("tx03"); + Assertions.assertNotNull(transaction); + txinfo = world.getBlockChain().getTransactionInfo(transaction.getHash().getBytes()); + Assertions.assertEquals(0, txinfo.getReceipt().getLogInfoList().size()); + + transaction = world.getTransactionByName("tx04"); + Assertions.assertNotNull(transaction); + txinfo = world.getBlockChain().getTransactionInfo(transaction.getHash().getBytes()); + Assertions.assertEquals(0, txinfo.getReceipt().getLogInfoList().size()); + } @Test void runCreate02Resource() throws FileNotFoundException, DslProcessorException { //byte[] code2 =Hex.decode("608060405234801561001057600080fd5b504361001a61017b565b80828152602001915050604051809103906000f080158015610040573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f80ae3ec8027d0c5d1f3e47fb4bf1d9fc28225e7f4bcb1971b36efb81fe40574d6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663209652556040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15801561012657600080fd5b505af115801561013a573d6000803e3d6000fd5b505050506040513d602081101561015057600080fd5b81019080805190602001909291905050506040518082815260200191505060405180910390a161018b565b6040516101e38061028283390190565b60e9806101996000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806361bc221a146044575b600080fd5b348015604f57600080fd5b5060566098565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a7230582091284634a2c8e5cbd0e4153a1422a41670914d8ef8b4f7dc71bd54cf80baf8f50029608060405234801561001057600080fd5b506040516020806101e383398101806040528101908080519060200190929190505050806000819055507f06acbfb32bcf8383f3b0a768b70ac9ec234ea0f2d3b9c77fa6a2de69b919aad16000546040518082815260200191505060405180910390a150610160806100836000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632096525514610051578063d09de08a1461007c575b600080fd5b34801561005d57600080fd5b50610066610093565b6040518082815260200191505060405180910390f35b34801561008857600080fd5b506100916100d6565b005b60007f1ee041944547858a75ebef916083b6d4f5ae04bea9cd809334469dd07dbf441b6000546040518082815260200191505060405180910390a1600054905090565b600080815460010191905081905550600160026000548115156100f557fe5b061415157f6e61ef44ac2747ff8b84d353a908eb8bd5c3fb118334d57698c5cfc7041196ad6000546040518082815260200191505060405180910390a25600a165627a7a7230582041617f72986040ac8590888e68e070d9d05aeb99361c0c77d1f67540db5ff6b10029"); diff --git a/rskj-core/src/test/java/co/rsk/test/builders/BridgeBuilder.java b/rskj-core/src/test/java/co/rsk/test/builders/BridgeBuilder.java new file mode 100644 index 00000000000..a11b6f05284 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/test/builders/BridgeBuilder.java @@ -0,0 +1,109 @@ +package co.rsk.test.builders; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import co.rsk.blockchain.utils.BlockGenerator; +import co.rsk.core.RskAddress; +import co.rsk.peg.Bridge; +import co.rsk.peg.BridgeSupport; +import co.rsk.peg.BridgeSupportFactory; +import org.ethereum.config.Constants; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.core.Block; +import org.ethereum.core.BlockTxSignatureCache; +import org.ethereum.core.ReceivedTxSignatureCache; +import org.ethereum.core.SignatureCache; +import org.ethereum.core.Transaction; +import org.ethereum.vm.MessageCall; +import org.ethereum.vm.MessageCall.MsgType; +import org.ethereum.vm.PrecompiledContractArgs; +import org.ethereum.vm.PrecompiledContractArgsBuilder; +import org.ethereum.vm.PrecompiledContracts; + +public class BridgeBuilder { + private RskAddress contractAddress; + private Constants constants; + private ActivationConfig activationConfig; + private BridgeSupport bridgeSupport; + private SignatureCache signatureCache; + + // Precompiled contract args + private Transaction transaction; + private Block executionBlock; + private MessageCall.MsgType msgType; + + public BridgeBuilder() { + contractAddress = PrecompiledContracts.BRIDGE_ADDR; + constants = Constants.mainnet(); + bridgeSupport = new BridgeSupportBuilder().build(); + signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); + + transaction = mock(Transaction.class); + executionBlock = new BlockGenerator().getGenesisBlock(); + msgType = MsgType.CALL; + } + + public BridgeBuilder contractAddress(RskAddress contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public BridgeBuilder constants(Constants constants) { + this.constants = constants; + return this; + } + + public BridgeBuilder activationConfig(ActivationConfig activationConfig) { + this.activationConfig = activationConfig; + return this; + } + + public BridgeBuilder signatureCache(SignatureCache signatureCache) { + this.signatureCache = signatureCache; + return this; + } + + public BridgeBuilder bridgeSupport(BridgeSupport bridgeSupport) { + this.bridgeSupport = bridgeSupport; + return this; + } + + public BridgeBuilder transaction(Transaction transaction) { + this.transaction = transaction; + return this; + } + + public BridgeBuilder executionBlock(Block executionBlock) { + this.executionBlock = executionBlock; + return this; + } + + public BridgeBuilder msgType(MsgType msgType) { + this.msgType = msgType; + return this; + } + + public Bridge build() { + BridgeSupportFactory bridgeSupportFactory = mock(BridgeSupportFactory.class); + when(bridgeSupportFactory.newInstance(any(), any(), any(), any())).thenReturn(bridgeSupport); + + Bridge bridge = new Bridge( + contractAddress, + constants, + activationConfig, + bridgeSupportFactory, + signatureCache + ); + + PrecompiledContractArgs args = PrecompiledContractArgsBuilder.builder() + .transaction(transaction) + .executionBlock(executionBlock) + .msgType(msgType) + .build(); + bridge.init(args); + + return bridge; + } +} diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java index 1ab4a39d9cb..68951cfbf64 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java @@ -117,6 +117,8 @@ class ActivationConfigTest { " rskip398: arrowhead600", " rskip400: arrowhead600", " rskip412: arrowhead600", + " rskip415: arrowhead600", + " rskip417: arrowhead600", "}" )); @@ -132,8 +134,8 @@ void readBaseConfig() { @Test void readWithTwoUpgradesInOrchid060() { ActivationConfig config = ActivationConfig.read(BASE_CONFIG - .withValue("hardforkActivationHeights.orchid060", ConfigValueFactory.fromAnyRef(200)) - .withValue("consensusRules.rskip98", ConfigValueFactory.fromAnyRef("orchid060")) + .withValue("hardforkActivationHeights.orchid060", ConfigValueFactory.fromAnyRef(200)) + .withValue("consensusRules.rskip98", ConfigValueFactory.fromAnyRef("orchid060")) ); for (ConsensusRule value : ConsensusRule.values()) { @@ -147,8 +149,8 @@ void readWithTwoUpgradesInOrchid060() { @Test void readWithOneHardcodedActivationNumber() { - ActivationConfig config = ActivationConfig.read(BASE_CONFIG - .withValue("consensusRules.rskip85", ConfigValueFactory.fromAnyRef(200)) + ActivationConfig config = ActivationConfig.read( + BASE_CONFIG.withValue("consensusRules.rskip85", ConfigValueFactory.fromAnyRef(200)) ); for (ConsensusRule value : ConsensusRule.values()) { diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java index a80a3cdf0ca..8318e2ff6d0 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java @@ -95,6 +95,7 @@ private static List getTwoToThreeRskips() { private static List getPapyrus200Rskips() { List rskips = new ArrayList<>(); rskips.addAll(Arrays.asList( + ConsensusRule.RSKIP134, ConsensusRule.RSKIP137, ConsensusRule.RSKIP140, ConsensusRule.RSKIP143, @@ -178,11 +179,13 @@ private static List getFingerroot500Rskips() { private static List getArrowhead600Rskips() { List rskips = new ArrayList<>(); rskips.addAll(Arrays.asList( + ConsensusRule.RSKIP203, ConsensusRule.RSKIP376, ConsensusRule.RSKIP379, ConsensusRule.RSKIP398, ConsensusRule.RSKIP400, - ConsensusRule.RSKIP203 + ConsensusRule.RSKIP415, + ConsensusRule.RSKIP417 )); return rskips; diff --git a/rskj-core/src/test/resources/dsl/bridgeDelegateCall.txt b/rskj-core/src/test/resources/dsl/bridgeDelegateCall.txt new file mode 100644 index 00000000000..610b47b2ad1 --- /dev/null +++ b/rskj-core/src/test/resources/dsl/bridgeDelegateCall.txt @@ -0,0 +1,43 @@ +account_new acc1 10000000 + +transaction_build tx01 + sender acc1 + receiverAddress 00 + value 0 + data 5f600060006000600073000000000000000000000000000000000100000663005b8d80f1 + gas 1200000 + build + +transaction_build tx02 + sender acc1 + receiverAddress 00 + value 0 + data 5f600060006000600073000000000000000000000000000000000100000663005b8d80f4 + gas 1200000 + nonce 1 + build + +transaction_build tx03 + sender acc1 + receiverAddress 00 + value 0 + data 5f600060006000600073000000000000000000000000000000000100000663005b8d80f2 + gas 1200000 + nonce 2 + build + +transaction_build tx04 + sender acc1 + receiverAddress 00 + value 0 + data 5f600060006000600073000000000000000000000000000000000100000663005b8d80fa + gas 1200000 + nonce 3 + build + +block_build b01 + parent g00 + transactions tx01 tx02 tx03 tx04 + build + +block_connect b01 \ No newline at end of file