diff --git a/rskj-core/src/main/java/co/rsk/config/BridgeConstants.java b/rskj-core/src/main/java/co/rsk/config/BridgeConstants.java index cda9026acd5..8f73bcae7d7 100644 --- a/rskj-core/src/main/java/co/rsk/config/BridgeConstants.java +++ b/rskj-core/src/main/java/co/rsk/config/BridgeConstants.java @@ -71,7 +71,7 @@ public abstract class BridgeConstants { protected int btcHeightWhenBlockIndexActivates; protected int maxDepthToSearchBlocksBelowIndexActivation; - protected long minSecondsBetweenCallsReceiveHeader; // (seconds) + protected long minSecondsBetweenCallsReceiveHeader; protected int maxDepthBlockchainAccepted; @@ -87,6 +87,9 @@ public abstract class BridgeConstants { protected int numberOfBlocksBetweenPegouts; + protected int btcHeightWhenPegoutTxIndexActivates; + protected int pegoutTxIndexGracePeriodInBtcBlocks; + public NetworkParameters getBtcParams() { return NetworkParameters.fromID(btcParamsString); } @@ -186,4 +189,12 @@ public int getMaxInputsPerPegoutTransaction() { public int getNumberOfBlocksBetweenPegouts() { return numberOfBlocksBetweenPegouts; } + + public int getBtcHeightWhenPegoutTxIndexActivates() { + return btcHeightWhenPegoutTxIndexActivates; + } + + public int getPegoutTxIndexGracePeriodInBtcBlocks() { + return pegoutTxIndexGracePeriodInBtcBlocks; + } } diff --git a/rskj-core/src/main/java/co/rsk/config/BridgeDevNetConstants.java b/rskj-core/src/main/java/co/rsk/config/BridgeDevNetConstants.java index 4b363336571..b07b22bfd2d 100644 --- a/rskj-core/src/main/java/co/rsk/config/BridgeDevNetConstants.java +++ b/rskj-core/src/main/java/co/rsk/config/BridgeDevNetConstants.java @@ -24,13 +24,12 @@ import co.rsk.peg.AddressBasedAuthorizer; import co.rsk.peg.FederationMember; import co.rsk.peg.StandardMultisigFederation; -import org.bouncycastle.util.encoders.Hex; -import org.ethereum.crypto.ECKey; - import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.bouncycastle.util.encoders.Hex; +import org.ethereum.crypto.ECKey; public class BridgeDevNetConstants extends BridgeConstants { // IMPORTANT: BTC, RSK and MST keys are the same. @@ -139,7 +138,7 @@ public BridgeDevNetConstants(List federationPublicKeys) { lockingCapIncrementsMultiplier = 2; - btcHeightWhenBlockIndexActivates = 700_000; //TODO define this value when Iris activation height in RSK is determined + btcHeightWhenBlockIndexActivates = 700_000; maxDepthToSearchBlocksBelowIndexActivation = 4_320; // 30 days in BTC blocks (considering 1 block every 10 minutes) erpFedActivationDelay = 52_560; // 1 year in BTC blocks (considering 1 block every 10 minutes) @@ -159,7 +158,7 @@ public BridgeDevNetConstants(List federationPublicKeys) { // e1b17fcd0ef1942465eee61b20561b16750191143d365e71de08b33dd84a9788 oldFederationAddress = "2N7ZgQyhFKm17RbaLqygYbS7KLrQfapyZzu"; - minSecondsBetweenCallsReceiveHeader = 300; // 5 minutes in seconds + minSecondsBetweenCallsReceiveHeader = 300; // 5 minutes maxDepthBlockchainAccepted = 25; @@ -168,5 +167,8 @@ public BridgeDevNetConstants(List federationPublicKeys) { maxInputsPerPegoutTransaction = 10; numberOfBlocksBetweenPegouts = 360; // 3 hours of RSK blocks (considering 1 block every 30 seconds) + + btcHeightWhenPegoutTxIndexActivates = 1_000_000; + 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/BridgeMainNetConstants.java b/rskj-core/src/main/java/co/rsk/config/BridgeMainNetConstants.java index b07ccb57250..fbc7e22470e 100644 --- a/rskj-core/src/main/java/co/rsk/config/BridgeMainNetConstants.java +++ b/rskj-core/src/main/java/co/rsk/config/BridgeMainNetConstants.java @@ -7,16 +7,15 @@ import co.rsk.peg.FederationMember; import co.rsk.peg.StandardMultisigFederation; import com.google.common.collect.Lists; -import org.bouncycastle.util.encoders.Hex; -import org.ethereum.crypto.ECKey; - import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.bouncycastle.util.encoders.Hex; +import org.ethereum.crypto.ECKey; public class BridgeMainNetConstants extends BridgeConstants { - private static BridgeMainNetConstants instance = new BridgeMainNetConstants(); + private static final BridgeMainNetConstants instance = new BridgeMainNetConstants(); BridgeMainNetConstants() { btcParamsString = NetworkParameters.ID_MAINNET; @@ -38,11 +37,11 @@ public class BridgeMainNetConstants extends BridgeConstants { BtcECKey federator14PublicKey = BtcECKey.fromPublicOnly(Hex.decode("03b65694ccccda83cbb1e56b31308acd08e993114c33f66a456b627c2c1c68bed6")); List genesisFederationPublicKeys = Lists.newArrayList( - federator0PublicKey, federator1PublicKey, federator2PublicKey, - federator3PublicKey, federator4PublicKey, federator5PublicKey, - federator6PublicKey, federator7PublicKey, federator8PublicKey, - federator9PublicKey, federator10PublicKey, federator11PublicKey, - federator12PublicKey, federator13PublicKey, federator14PublicKey + federator0PublicKey, federator1PublicKey, federator2PublicKey, + federator3PublicKey, federator4PublicKey, federator5PublicKey, + federator6PublicKey, federator7PublicKey, federator8PublicKey, + federator9PublicKey, federator10PublicKey, federator11PublicKey, + federator12PublicKey, federator13PublicKey, federator14PublicKey ); // IMPORTANT: BTC, RSK and MST keys are the same. @@ -51,7 +50,7 @@ public class BridgeMainNetConstants extends BridgeConstants { // Currently set to: // Wednesday, January 3, 2018 12:00:00 AM GMT-03:00 - Instant genesisFederationAddressCreatedAt = Instant.ofEpochMilli(1514948400l); + Instant genesisFederationAddressCreatedAt = Instant.ofEpochMilli(1514948400L); genesisFederation = new StandardMultisigFederation( federationMembers, @@ -107,8 +106,8 @@ public class BridgeMainNetConstants extends BridgeConstants { }).map(hex -> ECKey.fromPublicOnly(Hex.decode(hex))).collect(Collectors.toList()); feePerKbChangeAuthorizer = new AddressBasedAuthorizer( - feePerKbAuthorizedKeys, - AddressBasedAuthorizer.MinimumRequiredCalculation.MAJORITY + feePerKbAuthorizedKeys, + AddressBasedAuthorizer.MinimumRequiredCalculation.MAJORITY ); genesisFeePerKb = Coin.MILLICOIN.multiply(5); @@ -151,10 +150,12 @@ public class BridgeMainNetConstants extends BridgeConstants { maxInputsPerPegoutTransaction = 50; 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. + pegoutTxIndexGracePeriodInBtcBlocks = 4_320; // 30 days in BTC blocks (considering 1 block every 10 minutes) } public static BridgeMainNetConstants getInstance() { return instance; } - } diff --git a/rskj-core/src/main/java/co/rsk/config/BridgeRegTestConstants.java b/rskj-core/src/main/java/co/rsk/config/BridgeRegTestConstants.java index db7c4f3520f..f3d7ef3bdd9 100644 --- a/rskj-core/src/main/java/co/rsk/config/BridgeRegTestConstants.java +++ b/rskj-core/src/main/java/co/rsk/config/BridgeRegTestConstants.java @@ -24,16 +24,15 @@ import co.rsk.peg.AddressBasedAuthorizer; import co.rsk.peg.FederationMember; import co.rsk.peg.StandardMultisigFederation; -import org.bouncycastle.util.encoders.Hex; -import org.ethereum.crypto.ECKey; -import org.ethereum.crypto.HashUtil; - import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.bouncycastle.util.encoders.Hex; +import org.ethereum.crypto.ECKey; +import org.ethereum.crypto.HashUtil; public class BridgeRegTestConstants extends BridgeConstants { // IMPORTANT: BTC, RSK and MST keys are the same. @@ -67,7 +66,7 @@ public BridgeRegTestConstants(List federationPublicKeys) { btc2RskMinimumAcceptableConfirmationsOnRsk = 5; rsk2BtcMinimumAcceptableConfirmations = 3; - updateBridgeExecutionPeriod = 1 * 15 * 1000; //15 seconds in millis + updateBridgeExecutionPeriod = 15_000; //15 seconds in millis maxBtcHeadersPerRskBlock = 500; @@ -164,6 +163,9 @@ public BridgeRegTestConstants(List federationPublicKeys) { maxInputsPerPegoutTransaction = 10; numberOfBlocksBetweenPegouts = 50; // 25 Minutes of RSK blocks (considering 1 block every 30 seconds) + + btcHeightWhenPegoutTxIndexActivates = 250; + pegoutTxIndexGracePeriodInBtcBlocks = 100; } public static BridgeRegTestConstants getInstance() { 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 540fec54195..21e475843b3 100644 --- a/rskj-core/src/main/java/co/rsk/config/BridgeTestNetConstants.java +++ b/rskj-core/src/main/java/co/rsk/config/BridgeTestNetConstants.java @@ -24,16 +24,15 @@ import co.rsk.peg.AddressBasedAuthorizer; import co.rsk.peg.FederationMember; import co.rsk.peg.StandardMultisigFederation; -import org.bouncycastle.util.encoders.Hex; -import org.ethereum.crypto.ECKey; - import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.bouncycastle.util.encoders.Hex; +import org.ethereum.crypto.ECKey; public class BridgeTestNetConstants extends BridgeConstants { - private static BridgeTestNetConstants instance = new BridgeTestNetConstants(); + private static final BridgeTestNetConstants instance = new BridgeTestNetConstants(); BridgeTestNetConstants() { btcParamsString = NetworkParameters.ID_TESTNET; @@ -51,7 +50,12 @@ public class BridgeTestNetConstants extends BridgeConstants { Hex.decode("034844a99cd7028aa319476674cc381df006628be71bc5593b8b5fdb32bb42ef85") ); - List genesisFederationPublicKeys = Arrays.asList(federator0PublicKey, federator1PublicKey, federator2PublicKey, federator3PublicKey); + List genesisFederationPublicKeys = Arrays.asList( + federator0PublicKey, + federator1PublicKey, + federator2PublicKey, + federator3PublicKey + ); // IMPORTANT: BTC, RSK and MST keys are the same. // Change upon implementation of the fork. @@ -156,7 +160,7 @@ public class BridgeTestNetConstants extends BridgeConstants { // e1b17fcd0ef1942465eee61b20561b16750191143d365e71de08b33dd84a9788 oldFederationAddress = "2N7ZgQyhFKm17RbaLqygYbS7KLrQfapyZzu"; - minSecondsBetweenCallsReceiveHeader = 300; // 5 minutes in seconds + minSecondsBetweenCallsReceiveHeader = 300; // 5 minutes maxDepthBlockchainAccepted = 25; minimumPegoutValuePercentageToReceiveAfterFee = 80; @@ -164,6 +168,9 @@ public class BridgeTestNetConstants extends BridgeConstants { maxInputsPerPegoutTransaction = 50; 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) } public static BridgeTestNetConstants getInstance() { diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeStorageIndexKey.java b/rskj-core/src/main/java/co/rsk/peg/BridgeStorageIndexKey.java index 60b0c9a9212..5b2f30cf4e5 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeStorageIndexKey.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeStorageIndexKey.java @@ -39,9 +39,11 @@ public enum BridgeStorageIndexKey { BTC_BLOCK_HEIGHT("btcBlockHeight"), FAST_BRIDGE_HASH_USED_IN_BTC_TX("fastBridgeHashUsedInBtcTx"), FAST_BRIDGE_FEDERATION_INFORMATION("fastBridgeFederationInformation"), + + PEGOUT_TX_SIG_HASH("pegoutTxSigHash"), ; - private String key; + private final String key; BridgeStorageIndexKey(String key) { this.key = key; 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 75d2e211d36..256059905fa 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeStorageProvider.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeStorageProvider.java @@ -52,8 +52,8 @@ public class BridgeStorageProvider { protected static final int LEGACY_ERP_FEDERATION_FORMAT_VERSION = 2000; protected static final int P2SH_ERP_FEDERATION_FORMAT_VERSION = 3000; - // Dummy value to use when saved Fast Bridge Derivation Argument Hash - private static final byte FLYOVER_FEDERATION_DERIVATION_HASH_TRUE_VALUE = (byte) 1; + // Dummy value to use when saving key only indexes + private static final byte TRUE_VALUE = (byte) 1; private final Repository repository; private final RskAddress contractAddress; @@ -110,6 +110,8 @@ public class BridgeStorageProvider { private Long nextPegoutHeight; + private Set pegoutTxSigHashes; + public BridgeStorageProvider( Repository repository, RskAddress contractAddress, @@ -787,7 +789,7 @@ public boolean isFlyoverDerivationHashUsed(Sha256Hash btcTxHash, Keccak256 flyov return data != null && data.length == 1 && - data[0] == FLYOVER_FEDERATION_DERIVATION_HASH_TRUE_VALUE; + data[0] == TRUE_VALUE; } public void markFlyoverDerivationHashAsUsed(Sha256Hash btcTxHash, Keccak256 flyoverDerivationHash) { @@ -808,7 +810,7 @@ private void saveFlyoverDerivationHash() { flyoverBtcTxHash, flyoverDerivationHash ), - new byte[]{FLYOVER_FEDERATION_DERIVATION_HASH_TRUE_VALUE} + new byte[]{TRUE_VALUE} ); } @@ -923,6 +925,51 @@ protected int getReleaseRequestQueueSize() throws IOException { return getReleaseRequestQueue().getEntries().size(); } + public boolean hasPegoutTxSigHash(Sha256Hash sigHash) { + if (!activations.isActive(RSKIP379) || sigHash == null){ + return false; + } + + byte[] data = repository.getStorageBytes( + contractAddress, + getStorageKeyForPegoutTxSigHash(sigHash) + ); + + return data != null && + data.length == 1 && + data[0] == TRUE_VALUE; + } + + public void setPegoutTxSigHash(Sha256Hash sigHash) { + if (!activations.isActive(RSKIP379) || sigHash == null) { + return; + } + + if (hasPegoutTxSigHash(sigHash)){ + throw new IllegalStateException(String.format("Given pegout tx sigHash %s already exists in the index. Index entries are considered unique.", sigHash)); + } + + if (pegoutTxSigHashes == null){ + pegoutTxSigHashes = new HashSet<>(); + } + + pegoutTxSigHashes.add(sigHash); + } + + private void savePegoutTxSigHashes() { + if (!activations.isActive(RSKIP379) || pegoutTxSigHashes == null) { + return; + } + + pegoutTxSigHashes.forEach(pegoutTxSigHash -> repository.addStorageBytes( + contractAddress, + getStorageKeyForPegoutTxSigHash( + pegoutTxSigHash + ), + new byte[]{TRUE_VALUE} + )); + } + public void save() throws IOException { saveBtcTxHashesAlreadyProcessed(); @@ -964,6 +1011,8 @@ public void save() throws IOException { saveReceiveHeadersLastTimestamp(); saveNextPegoutHeight(); + + savePegoutTxSigHashes(); } private DataWord getStorageKeyForBtcTxHashAlreadyProcessed(Sha256Hash btcTxHash) { @@ -1000,6 +1049,10 @@ private DataWord getStorageKeyForNewFederationBtcUtxos() { return key; } + private DataWord getStorageKeyForPegoutTxSigHash(Sha256Hash sigHash) { + return PEGOUT_TX_SIG_HASH.getCompoundKey("-", sigHash.toString()); + } + private Optional getStorageVersion(DataWord versionKey) { if (!storageVersionEntries.containsKey(versionKey)) { Optional version = safeGetFromRepository(versionKey, data -> { 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 fe8c001d04a..7404569f792 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -19,6 +19,7 @@ import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; +import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP186; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP219; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP271; @@ -57,6 +58,7 @@ import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; import co.rsk.panic.PanicProcessor; +import co.rsk.peg.bitcoin.BitcoinUtils; import co.rsk.peg.bitcoin.CoinbaseInformation; import co.rsk.peg.bitcoin.MerkleBranch; import co.rsk.peg.bitcoin.RskAllowUnconfirmedCoinSelector; @@ -64,12 +66,14 @@ import co.rsk.peg.btcLockSender.BtcLockSenderProvider; import co.rsk.peg.flyover.FlyoverFederationInformation; import co.rsk.peg.flyover.FlyoverTxResponseCodes; +import co.rsk.peg.pegin.PeginEvaluationResult; +import co.rsk.peg.pegin.PeginProcessAction; +import co.rsk.peg.pegin.RejectedPeginReason; import co.rsk.peg.pegininstructions.PeginInstructionsException; import co.rsk.peg.pegininstructions.PeginInstructionsProvider; import co.rsk.peg.utils.BridgeEventLogger; import co.rsk.peg.utils.BtcTransactionFormatUtils; import co.rsk.peg.utils.PartialMerkleTreeFormatUtils; -import co.rsk.peg.utils.RejectedPeginReason; import co.rsk.peg.utils.RejectedPegoutReason; import co.rsk.peg.utils.UnrefundablePeginReason; import co.rsk.peg.whitelist.LockWhitelist; @@ -153,12 +157,13 @@ public class BridgeSupport { private static final String INVALID_ADDRESS_FORMAT_MESSAGE = "invalid address format"; - private final List FEDERATION_CHANGE_FUNCTIONS = Collections.unmodifiableList(Arrays.asList( - "create", - "add", - "add-multi", - "commit", - "rollback")); + private static final List FEDERATION_CHANGE_FUNCTIONS = Collections.unmodifiableList(Arrays.asList( + "create", + "add", + "add-multi", + "commit", + "rollback" + )); private final BridgeConstants bridgeConstants; private final BridgeStorageProvider provider; @@ -244,13 +249,13 @@ public void receiveHeaders(BtcBlock[] headers) throws IOException, BlockStoreExc Context.propagate(btcContext); this.ensureBtcBlockChain(); - for (int i = 0; i < headers.length; i++) { + for (BtcBlock header : headers) { try { - btcBlockChain.add(headers[i]); + btcBlockChain.add(header); } catch (Exception e) { - // If we tray to add an orphan header bitcoinj throws an exception + // If we try to add an orphan header bitcoinj throws an exception // This catches that case and any other exception that may be thrown - logger.warn("Exception adding btc header {}", headers[i].getHash(), e); + logger.warn("Exception adding btc header {}", header.getHash(), e); } } } @@ -290,7 +295,7 @@ public Integer receiveHeader(BtcBlock header) throws IOException, BlockStoreExce try { btcBlockChain.add(header); } catch (Exception e) { - // If we tray to add an orphan header bitcoinj throws an exception + // If we try to add an orphan header bitcoinj throws an exception // This catches that case and any other exception that may be thrown logger.warn("Exception adding btc header {}", header.getHash(), e); return RECEIVE_HEADER_UNEXPECTED_EXCEPTION; @@ -327,12 +332,12 @@ public Wallet getActiveFederationWallet(boolean shouldConsiderFlyoverUTXOs) thro * @throws IOException * @param shouldConsiderFlyoverUTXOs */ - public Wallet getRetiringFederationWallet(boolean shouldConsiderFlyoverUTXOs) throws IOException { + protected Wallet getRetiringFederationWallet(boolean shouldConsiderFlyoverUTXOs) throws IOException { List retiringFederationBtcUTXOs = getRetiringFederationBtcUTXOs(); return getRetiringFederationWallet(shouldConsiderFlyoverUTXOs, retiringFederationBtcUTXOs.size()); } - public Wallet getRetiringFederationWallet(boolean shouldConsiderFlyoverUTXOs, int utxosSizeLimit) throws IOException { + private Wallet getRetiringFederationWallet(boolean shouldConsiderFlyoverUTXOs, int utxosSizeLimit) throws IOException { Federation federation = getRetiringFederation(); if (federation == null) { logger.debug("[getRetiringFederationWallet] No retiring federation found"); @@ -391,10 +396,10 @@ public void registerBtcTransaction( int height, byte[] pmtSerialized ) throws IOException, BlockStoreException, BridgeIllegalArgumentException { - Context.propagate(btcContext); + Keccak256 rskTxHash = rskTx.getHash(); Sha256Hash btcTxHash = BtcTransactionFormatUtils.calculateBtcTxHash(btcTxSerialized); - logger.debug("[registerBtcTransaction][rsk tx {}] Processing btc tx {}", rskTx.getHash(), btcTxHash); + logger.debug("[registerBtcTransaction][rsk tx {}] Processing btc tx {}", rskTxHash, btcTxHash); try { // Check the tx was not already processed @@ -409,30 +414,41 @@ public void registerBtcTransaction( BtcTransaction btcTx = new BtcTransaction(bridgeConstants.getBtcParams(), btcTxSerialized); btcTx.verify(); - logger.debug("[registerBtcTransaction][rsk tx {}] Btc tx hash without witness {}", rskTx.getHash(), btcTx.getHash(false)); + logger.debug("[registerBtcTransaction][rsk tx {}] Btc tx hash without witness {}", rskTxHash, btcTx.getHash(false)); // Check again that the tx was not already processed but making sure to use the txid (no witness) if (isAlreadyBtcTxHashProcessed(btcTx.getHash(false))) { throw new RegisterBtcTransactionException("Transaction already processed"); } - // Specific code for pegin/pegout or migration/none txs - switch (getTransactionType(btcTx)) { + PegTxType pegTxType = PegUtils.getTransactionType( + activations, + provider, + bridgeConstants, + getActiveFederation(), + getRetiringFederation(), + btcTx, + height + ); + + switch (pegTxType) { case PEGIN: - processPegIn(btcTx, rskTx, height, btcTxHash); + logger.debug("[registerBtcTransaction] This is a peg-in tx {}", btcTx.getHash()); + processPegIn(btcTx, rskTxHash, height); break; case PEGOUT_OR_MIGRATION: + logger.debug("[registerBtcTransaction] This is a peg-out or migration tx {}", btcTx.getHash()); processPegoutOrMigration(btcTx); break; default: - String message = String.format("This is not a peg-in, a peg-out nor a migration tx %s", btcTxHash); - logger.warn("[registerBtcTransaction][rsk tx {}] {}", rskTx.getHash(), message); + String message = String.format("This is not a peg-in, a peg-out nor a migration tx %s", btcTx.getHash()); + logger.warn("[registerBtcTransaction][rsk tx {}] {}", rskTxHash, message); panicProcessor.panic("btclock", message); } } catch (RegisterBtcTransactionException e) { logger.warn( "[registerBtcTransaction][rsk tx {}] Could not register transaction {}. Message: {}", - rskTx.getHash(), + rskTxHash, btcTxHash, e.getMessage() ); @@ -444,72 +460,103 @@ BtcBlockStoreWithCache getBtcBlockStore() { return btcBlockStore; } - protected PegTxType getTransactionType(BtcTransaction btcTx) { - Script retiredFederationP2SHScript = provider.getLastRetiredFederationP2SHScript().orElse(null); + protected void processPegIn( + BtcTransaction btcTx, + Keccak256 rskTxHash, + int height + ) throws IOException, RegisterBtcTransactionException { + final String METHOD_NAME = "processPegIn"; - /************************************************************************/ - /** Special case to migrate funds from an old federation **/ - /************************************************************************/ - if (activations.isActive(ConsensusRule.RSKIP199) && txIsFromOldFederation(btcTx)) { - logger.debug("[getTransactionType][btc tx {}] is from the old federation, treated as a migration", btcTx.getHash()); - return PegTxType.PEGOUT_OR_MIGRATION; + if (!activations.isActive(ConsensusRule.RSKIP379)) { + legacyProcessPegin(btcTx, rskTxHash, height); + logger.info( + "[{}] BTC Tx {} processed in RSK transaction {} using legacy function", + METHOD_NAME, + btcTx.getHash(), + rskTxHash + ); + return; } - if (BridgeUtils.isValidPegInTx( - btcTx, - getLiveFederations(), - retiredFederationP2SHScript, - btcContext, - bridgeConstants.getMinimumPeginTxValue(activations), - activations - )) { - logger.debug("[getTransactionType][btc tx {}] is a peg-in", btcTx.getHash()); - return PegTxType.PEGIN; - } + Coin totalAmount = computeTotalAmountSent(btcTx); + logger.debug("[{}}] Total amount sent: {}", METHOD_NAME, totalAmount); - if (BridgeUtils.isMigrationTx( + PeginInformation peginInformation = new PeginInformation( + btcLockSenderProvider, + peginInstructionsProvider, + activations + ); + Coin minimumPeginTxValue = bridgeConstants.getMinimumPeginTxValue(activations); + Wallet fedWallet = getNoSpendWalletForLiveFederations(false); + PeginEvaluationResult peginEvaluationResult = PegUtils.evaluatePegin( btcTx, - getActiveFederation(), - getRetiringFederation(), - retiredFederationP2SHScript, - btcContext, - bridgeConstants, + peginInformation, + minimumPeginTxValue, + fedWallet, activations - )) { - logger.debug("[getTransactionType][btc tx {}] is a migration transaction", btcTx.getHash()); - return PegTxType.PEGOUT_OR_MIGRATION; - } - - if (BridgeUtils.isPegOutTx(btcTx, getLiveFederations(), activations)) { - logger.debug("[getTransactionType][btc tx {}] is a peg-out", btcTx.getHash()); - return PegTxType.PEGOUT_OR_MIGRATION; - } - - logger.debug("[getTransactionType][btc tx {}] is neither a peg-in, peg-out, nor migration", btcTx.getHash()); - return PegTxType.UNKNOWN; - } + ); - private boolean txIsFromOldFederation(BtcTransaction btcTx) { - Address oldFederationAddress = Address.fromBase58(bridgeConstants.getBtcParams(), bridgeConstants.getOldFederationAddress()); - Script p2shScript = ScriptBuilder.createP2SHOutputScript(oldFederationAddress.getHash160()); + PeginProcessAction peginProcessAction = peginEvaluationResult.getPeginProcessAction(); + if (peginProcessAction == PeginProcessAction.CAN_BE_REGISTERED) { + logger.debug("[{}] Peg-in is valid, going to register", METHOD_NAME); + executePegIn(btcTx, peginInformation, totalAmount); + markTxAsProcessed(btcTx); + } else { + Optional rejectedPeginReasonOptional = peginEvaluationResult.getRejectedPeginReason(); + if (!rejectedPeginReasonOptional.isPresent()) { + // This flow should never be reached. There should always be a rejected pegin reason. + String message = "Invalid state. No rejected reason was returned from evaluatePegin method"; + logger.error("[{}}] {}", METHOD_NAME, message); + throw new IllegalStateException(message); + } - for (int i = 0; i < btcTx.getInputs().size(); i++) { - if (BridgeUtils.scriptCorrectlySpendsTx(btcTx, i, p2shScript)) { - return true; + RejectedPeginReason rejectedPeginReason = rejectedPeginReasonOptional.get(); + logger.debug("[{}] Rejected peg-in, reason {}", METHOD_NAME, rejectedPeginReason); + eventLogger.logRejectedPegin(btcTx, rejectedPeginReason); + if (peginProcessAction == PeginProcessAction.CAN_BE_REFUNDED) { + logger.debug("[{}] Refunding to address {} ", METHOD_NAME, peginInformation.getBtcRefundAddress()); + generateRejectionRelease(btcTx, peginInformation.getBtcRefundAddress(), rskTxHash, totalAmount); + markTxAsProcessed(btcTx); + } else { + logger.debug("[{}] Unprocessable transaction {}.", METHOD_NAME, btcTx.getHash()); + handleUnprocessableBtcTx(btcTx, peginInformation.getProtocolVersion(), rejectedPeginReason); } } - - return false; } - protected void processPegIn( + private void handleUnprocessableBtcTx( BtcTransaction btcTx, - Transaction rskTx, - int height, - Sha256Hash btcTxHash) throws IOException, RegisterBtcTransactionException { + int protocolVersion, + RejectedPeginReason rejectedPeginReason + ) { + UnrefundablePeginReason unrefundablePeginReason; + if (rejectedPeginReason == INVALID_AMOUNT) { + unrefundablePeginReason = UnrefundablePeginReason.INVALID_AMOUNT; + } else { + unrefundablePeginReason = protocolVersion == 1 ? + UnrefundablePeginReason.PEGIN_V1_REFUND_ADDRESS_NOT_SET : + UnrefundablePeginReason.LEGACY_PEGIN_UNDETERMINED_SENDER; + } - logger.debug("[processPegIn] This is a lock tx {}", btcTx); + logger.debug("[handleUnprocessableBtcTx] Unprocessable tx {}. Reason {}", btcTx.getHash(), unrefundablePeginReason); + eventLogger.logUnrefundablePegin(btcTx, unrefundablePeginReason); + } + /** + * Legacy version for processing peg-ins + * Use instead {@link co.rsk.peg.BridgeSupport#processPegIn} + * + * @param btcTx Peg-in transaction to process + * @param rskTxHash Hash of the RSK transaction where the prg-in is being processed + * @param height Peg-in transaction height in Bitcoin network + * @deprecated + */ + @Deprecated + private void legacyProcessPegin( + BtcTransaction btcTx, + Keccak256 rskTxHash, + int height + ) throws IOException, RegisterBtcTransactionException { Coin totalAmount = computeTotalAmountSent(btcTx); PeginInformation peginInformation = new PeginInformation( @@ -526,7 +573,7 @@ protected void processPegIn( } // If possible to get the sender address, refund - refundTxSender(btcTx, rskTx, peginInformation, totalAmount); + refundTxSender(btcTx, rskTxHash, peginInformation, totalAmount); markTxAsProcessed(btcTx); } @@ -543,26 +590,24 @@ protected void processPegIn( logger.debug("[processPegIn] Protocol version: {}", protocolVersion); switch (protocolVersion) { case 0: - processPegInVersionLegacy(btcTx, rskTx, height, peginInformation, totalAmount); + processPegInVersionLegacy(btcTx, rskTxHash, height, peginInformation, totalAmount); break; case 1: - processPegInVersion1(btcTx, rskTx, peginInformation, totalAmount); + processPegInVersion1(btcTx, rskTxHash, peginInformation, totalAmount); break; default: markTxAsProcessed(btcTx); - String message = String.format("Invalid peg-in protocol version: %d", protocolVersion); logger.warn("[processPegIn] {}", message); throw new RegisterBtcTransactionException(message); } markTxAsProcessed(btcTx); - logger.info("[processPegIn] BTC Tx {} processed in RSK", btcTxHash); } private void processPegInVersionLegacy( BtcTransaction btcTx, - Transaction rskTx, + Keccak256 rskTxHash, int height, PeginInformation peginInformation, Coin totalAmount) throws IOException, RegisterBtcTransactionException { @@ -592,13 +637,13 @@ private void processPegInVersionLegacy( } } - generateRejectionRelease(btcTx, senderBtcAddress, rskTx, totalAmount); + generateRejectionRelease(btcTx, senderBtcAddress, rskTxHash, totalAmount); } } private void processPegInVersion1( BtcTransaction btcTx, - Transaction rskTx, + Keccak256 rskTxHash, PeginInformation peginInformation, Coin totalAmount) throws RegisterBtcTransactionException, IOException { @@ -616,7 +661,7 @@ private void processPegInVersion1( eventLogger.logRejectedPegin(btcTx, RejectedPeginReason.PEGIN_CAP_SURPASSED); } - refundTxSender(btcTx, rskTx, peginInformation, totalAmount); + refundTxSender(btcTx, rskTxHash, peginInformation, totalAmount); } } @@ -627,10 +672,10 @@ private void executePegIn(BtcTransaction btcTx, PeginInformation peginInformatio int protocolVersion = peginInformation.getProtocolVersion(); co.rsk.core.Coin amountInWeis = co.rsk.core.Coin.fromBitcoin(amount); - logger.debug("[executePegIn] [btcTx:{}] Is a lock from a {} sender", btcTx.getHash(), senderBtcAddressType); + logger.debug("[executePegIn] [btcTx:{}] Is a peg-in from a {} sender", btcTx.getHash(), senderBtcAddressType); this.transferTo(peginInformation.getRskDestinationAddress(), amountInWeis); logger.info( - "[executePegIn] Transferring from BTC Address {}. RSK Address: {}. Amount: {}", + "[executePegIn] Transferring from BTC address {}. RSK address: {}. Amount: {}", senderBtcAddress, rskDestinationAddress, amountInWeis @@ -650,13 +695,13 @@ private void executePegIn(BtcTransaction btcTx, PeginInformation peginInformatio private void refundTxSender( BtcTransaction btcTx, - Transaction rskTx, + Keccak256 rskTxHash, PeginInformation peginInformation, Coin amount) throws IOException { Address btcRefundAddress = peginInformation.getBtcRefundAddress(); if (btcRefundAddress != null) { - generateRejectionRelease(btcTx, btcRefundAddress, rskTx, amount); + generateRejectionRelease(btcTx, btcRefundAddress, rskTxHash, amount); } else { logger.debug("[refundTxSender] No btc refund address provided, couldn't get sender address either. Can't refund"); @@ -672,7 +717,13 @@ private void refundTxSender( private void markTxAsProcessed(BtcTransaction btcTx) throws IOException { // Mark tx as processed on this block (and use the txid without the witness) - provider.setHeightBtcTxhashAlreadyProcessed(btcTx.getHash(false), rskExecutionBlock.getNumber()); + long rskHeight = rskExecutionBlock.getNumber(); + provider.setHeightBtcTxhashAlreadyProcessed(btcTx.getHash(false), rskHeight); + logger.debug( + "[markTxAsProcessed] Mark btc transaction {} as processed at height {}", + btcTx.getHash(), + rskHeight + ); } protected void processPegoutOrMigration(BtcTransaction btcTx) throws IOException { @@ -737,21 +788,39 @@ Add the btcTx outputs that send btc to the federation(s) to the UTXO list */ private void saveNewUTXOs(BtcTransaction btcTx) throws IOException { // Outputs to the active federation - List outputsToTheActiveFederation = btcTx.getWalletOutputs(getActiveFederationWallet( - false)); + Wallet activeFederationWallet = getActiveFederationWallet(false); + List outputsToTheActiveFederation = btcTx.getWalletOutputs( + activeFederationWallet + ); for (TransactionOutput output : outputsToTheActiveFederation) { - UTXO utxo = new UTXO(btcTx.getHash(), output.getIndex(), output.getValue(), 0, btcTx.isCoinBase(), output.getScriptPubKey()); + UTXO utxo = new UTXO( + btcTx.getHash(), + output.getIndex(), + output.getValue(), + 0, + btcTx.isCoinBase(), + output.getScriptPubKey() + ); getActiveFederationBtcUTXOs().add(utxo); } + logger.debug("[saveNewUTXOs] Registered {} UTXOs sent to the active federation", outputsToTheActiveFederation.size()); // Outputs to the retiring federation (if any) Wallet retiringFederationWallet = getRetiringFederationWallet(false); if (retiringFederationWallet != null) { List outputsToTheRetiringFederation = btcTx.getWalletOutputs(retiringFederationWallet); for (TransactionOutput output : outputsToTheRetiringFederation) { - UTXO utxo = new UTXO(btcTx.getHash(), output.getIndex(), output.getValue(), 0, btcTx.isCoinBase(), output.getScriptPubKey()); + UTXO utxo = new UTXO( + btcTx.getHash(), + output.getIndex(), + output.getValue(), + 0, + btcTx.isCoinBase(), + output.getScriptPubKey() + ); getRetiringFederationBtcUTXOs().add(utxo); } + logger.debug("[saveNewUTXOs] Registered {} UTXOs sent to the retiring federation", outputsToTheRetiringFederation.size()); } } @@ -1025,6 +1094,9 @@ private void migrateFunds( pegoutsWaitingForConfirmations.add(btcTx, rskExecutionBlock.getNumber()); } + // Store pegoutTxSigHash to be able to identify the tx type + savePegoutTxSigHash(btcTx); + // Mark UTXOs as spent availableUTXOs.removeIf(utxo -> selectedUTXOs.stream().anyMatch(selectedUtxo -> utxo.getHash().equals(selectedUtxo.getHash()) && utxo.getIndex() == selectedUtxo.getIndex() @@ -1179,6 +1251,7 @@ private void processPegoutsInBatch( BtcTransaction generatedTransaction = result.getBtcTx(); addToPegoutsWaitingForConfirmations(generatedTransaction, pegoutsWaitingForConfirmations, rskTx.getHash(), totalPegoutValue); + savePegoutTxSigHash(generatedTransaction); // Remove batched requests from the queue after successfully batching pegouts pegoutRequests.removeEntries(pegoutEntries); @@ -1203,6 +1276,17 @@ private void processPegoutsInBatch( } } + private void savePegoutTxSigHash(BtcTransaction pegoutTx) { + if (!activations.isActive(ConsensusRule.RSKIP379)){ + return; + } + Optional pegoutTxSigHash = BitcoinUtils.getFirstInputSigHash(pegoutTx); + if (!pegoutTxSigHash.isPresent()){ + throw new IllegalStateException(String.format("SigHash could not be obtained from btc tx %s", pegoutTx.getHash())); + } + provider.setPegoutTxSigHash(pegoutTxSigHash.get()); + } + /** * Processes pegout waiting for confirmations. * It basically looks for pegout transactions with enough confirmations @@ -1762,9 +1846,12 @@ public Long getBtcTxHashProcessedHeight(Sha256Hash btcTxHash) throws IOException * @return true or false according * @throws IOException * */ - public boolean isAlreadyBtcTxHashProcessed(Sha256Hash btcTxHash) throws IOException { + protected boolean isAlreadyBtcTxHashProcessed(Sha256Hash btcTxHash) throws IOException { if (getBtcTxHashProcessedHeight(btcTxHash) > -1L) { - logger.warn("Supplied Btc Tx {} was already processed", btcTxHash); + logger.warn( + "[isAlreadyBtcTxHashProcessed] Supplied Btc Tx {} was already processed", + btcTxHash + ); return true; } @@ -2168,7 +2255,7 @@ public Integer voteFederationChange(Transaction tx, ABICallSpec callSpec) throws result = new ABICallVoteResult(false, FEDERATION_CHANGE_GENERIC_ERROR_CODE); } - // Return if the dry run failed or we are on a reversible execution + // Return if the dry run failed, or we are on a reversible execution if (!result.wasSuccessful()) { return (Integer) result.getResult(); } @@ -2222,7 +2309,8 @@ private ABICallVoteResult executeVoteFederationChangeFunction(boolean dryRun, AB break; case "add-multi": BtcECKey btcPublicKey; - ECKey rskPublicKey, mstPublicKey; + ECKey rskPublicKey; + ECKey mstPublicKey; try { btcPublicKey = BtcECKey.fromPublicOnly(callSpec.getArguments()[0]); } catch (Exception e) { @@ -2244,7 +2332,7 @@ private ABICallVoteResult executeVoteFederationChangeFunction(boolean dryRun, AB result = new ABICallVoteResult(executionResult == 1, executionResult); break; case "commit": - Keccak256 hash = new Keccak256((byte[]) callSpec.getArguments()[0]); + Keccak256 hash = new Keccak256(callSpec.getArguments()[0]); executionResult = commitFederation(dryRun, hash); result = new ABICallVoteResult(executionResult == 1, executionResult); break; @@ -2558,7 +2646,7 @@ public Optional