Skip to content

Commit

Permalink
feat: testing that a legacy register btc transaction with multisig se…
Browse files Browse the repository at this point in the history
…nder is invalid and throws the right event
  • Loading branch information
julianlen committed Dec 20, 2024
1 parent ea055ed commit c2c5caa
Showing 1 changed file with 123 additions and 16 deletions.
139 changes: 123 additions & 16 deletions rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

import co.rsk.bitcoinj.core.*;
import co.rsk.bitcoinj.script.ScriptBuilder;
import co.rsk.bitcoinj.store.BlockStoreException;
import co.rsk.core.RskAddress;
import co.rsk.net.utils.TransactionUtils;
import co.rsk.peg.bitcoin.BitcoinTestUtils;
import co.rsk.peg.bitcoin.UtxoUtils;
import co.rsk.peg.btcLockSender.BtcLockSenderProvider;
import co.rsk.peg.constants.BridgeConstants;
import co.rsk.peg.constants.BridgeMainNetConstants;
Expand All @@ -17,6 +19,7 @@
import co.rsk.peg.feeperkb.FeePerKbStorageProviderImpl;
import co.rsk.peg.feeperkb.FeePerKbSupport;
import co.rsk.peg.feeperkb.FeePerKbSupportImpl;
import co.rsk.peg.pegin.RejectedPeginReason;
import co.rsk.peg.storage.BridgeStorageAccessorImpl;
import co.rsk.peg.storage.StorageAccessor;
import co.rsk.peg.utils.BridgeEventLoggerImpl;
Expand Down Expand Up @@ -46,19 +49,21 @@ class RegisterBtcTransactionIT {
private Repository repository;
private FederationSupport federationSupport;
private BridgeStorageProvider bridgeStorageProvider;
private BtcTransaction bitcoinTransaction;
private PartialMerkleTree pmtWithTransactions;
private int btcBlockWithPmtHeight;
private RskAddress rskReceiver;
private BridgeSupport bridgeSupport;
private ArrayList<LogInfo> logs;
private BtcBlockStoreWithCache btcBlockStoreWithCache;
private BtcECKey btcPublicKey;
private BtcLockSenderProvider btcLockSenderProvider;


@BeforeEach
void setUp() throws Exception{
repository = BridgeSupportTestUtil.createRepository().startTracking();

BtcLockSenderProvider btcLockSenderProvider = new BtcLockSenderProvider();
btcLockSenderProvider = new BtcLockSenderProvider();
StorageAccessor bridgeStorageAccessor = new BridgeStorageAccessorImpl(repository);

FeePerKbStorageProvider feePerKbStorageProvider = new FeePerKbStorageProviderImpl(bridgeStorageAccessor);
Expand All @@ -76,23 +81,15 @@ void setUp() throws Exception{

bridgeStorageProvider = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, btcNetworkParams, activations);
BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(btcNetworkParams, 100, 100);
BtcBlockStoreWithCache btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(repository, bridgeConstants, bridgeStorageProvider, activations);
btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(repository, bridgeConstants, bridgeStorageProvider, activations);

BtcECKey btcPublicKey = BitcoinTestUtils.getBtcEcKeyFromSeed("seed");
btcPublicKey = BitcoinTestUtils.getBtcEcKeyFromSeed("seed");
ECKey ecKey = ECKey.fromPublicOnly(btcPublicKey.getPubKey());
rskReceiver = new RskAddress(ecKey.getAddress());
bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey);

pmtWithTransactions = createValidPmtForTransactions(List.of(bitcoinTransaction.getHash()), btcNetworkParams);
btcBlockWithPmtHeight = bridgeConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeConstants.getPegoutTxIndexGracePeriodInBtcBlocks();
int chainHeight = btcBlockWithPmtHeight + bridgeConstants.getBtc2RskMinimumAcceptableConfirmations();

logs = new ArrayList<>();
BridgeEventLoggerImpl bridgeEventLogger = new BridgeEventLoggerImpl(bridgeConstants, activations, logs);

recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, btcNetworkParams);
bridgeStorageProvider.save();

bridgeSupport = bridgeSupportBuilder
.withBridgeConstants(bridgeConstants)
.withProvider(bridgeStorageProvider)
Expand All @@ -105,11 +102,15 @@ void setUp() throws Exception{
.withRepository(repository)
.withBtcLockSenderProvider(btcLockSenderProvider)
.build();
bridgeStorageProvider.save();
}

@Test
void registerBtcTransaction_forALegacyBtcTransaction_shouldRegisterTheNewUtxoAndTransferTheRbtcBalance() throws Exception {
// Act
BtcTransaction bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey);
setupChainWithBtcTransaction(bitcoinTransaction);
bridgeStorageProvider.save();
bridgeSupport.registerBtcTransaction(rskTx, bitcoinTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize());
bridgeSupport.save();

Expand All @@ -126,12 +127,16 @@ void registerBtcTransaction_forALegacyBtcTransaction_shouldRegisterTheNewUtxoAnd
co.rsk.core.Coin expectedReceiverBalance = co.rsk.core.Coin.fromBitcoin(output.getValue());
assertEquals(expectedReceiverBalance, repository.getBalance(rskReceiver));

assertLogPegInBtc();
assertLogPegInBtc(bitcoinTransaction);
}

@Test
void registerBtcTransaction_forARepeatedLegacyBtcTransaction_shouldNotPerformAnyChange() throws Exception {
// Arrange
BtcTransaction bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey);
setupChainWithBtcTransaction(bitcoinTransaction);

bridgeStorageProvider.save();
bridgeSupport.registerBtcTransaction(rskTx, bitcoinTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize());
bridgeSupport.save();

Expand All @@ -150,6 +155,10 @@ void registerBtcTransaction_forARepeatedLegacyBtcTransaction_shouldNotPerformAny
@Test
void registerBtcTransaction_whenLegacyBtcTransactionWithNegativeHeight_shouldNotPerformAnyChange() throws Exception {
// Arrange
BtcTransaction bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey);
setupChainWithBtcTransaction(bitcoinTransaction);
bridgeStorageProvider.save();

co.rsk.core.Coin expectedReceiverBalance = repository.getBalance(rskReceiver);
List<UTXO> expectedFederationUTXOs = List.copyOf(federationSupport.getActiveFederationBtcUTXOs());

Expand All @@ -163,6 +172,51 @@ void registerBtcTransaction_whenLegacyBtcTransactionWithNegativeHeight_shouldNot
assertEquals(expectedReceiverBalance, repository.getBalance(rskReceiver));
}

@Test
void whenRegisterALegacyBtcTransactionFromAMultiSig_shouldRefundTheFunds() throws Exception {
// Arrange
BtcTransaction bitcoinTransaction = createMultiSigPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue);
setupChainWithBtcTransaction(bitcoinTransaction);
bridgeSupport.save();

co.rsk.core.Coin expectedReceiverBalance = repository.getBalance(rskReceiver);
List<UTXO> expectedFederationUTXOs = List.copyOf(federationSupport.getActiveFederationBtcUTXOs());

// Act
bridgeSupport.registerBtcTransaction(rskTx, bitcoinTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize());
bridgeSupport.save();

// Assert
Set<PegoutsWaitingForConfirmations.Entry> pegoutEntries = bridgeStorageProvider.getPegoutsWaitingForConfirmations().getEntries();
int expectedPegoutsWaitingForConfirmations = 1;
assertEquals(expectedPegoutsWaitingForConfirmations, pegoutEntries.size());

Optional<PegoutsWaitingForConfirmations.Entry> pegOutWaitingForConfirmationOptional = pegoutEntries.stream().findFirst();
assertTrue(pegOutWaitingForConfirmationOptional.isPresent());

PegoutsWaitingForConfirmations.Entry pegOutWaitingForConfirmationEntry = pegOutWaitingForConfirmationOptional.get();
assertEquals(rskTx.getHash() ,pegOutWaitingForConfirmationEntry.getPegoutCreationRskTxHash());
assertEquals(rskExecutionBlock.getNumber() ,pegOutWaitingForConfirmationEntry.getPegoutCreationRskBlockNumber());

//Pegout value + fee == Pegin value
BtcTransaction pegOut = pegOutWaitingForConfirmationEntry.getBtcTransaction();
int outputIndex = 0;
TransactionOutput pegOutOutput = pegOut.getOutput(outputIndex);
Coin pegOutTotalValue = pegOutOutput.getValue().add(pegOut.getFee());
assertEquals(minimumPeginValue, pegOutTotalValue);

Address pegInTxSender = btcLockSenderProvider.tryGetBtcLockSender(bitcoinTransaction).get().getBTCAddress();
Address pegoutReceiver = pegOutOutput.getAddressFromP2SH(btcNetworkParams);
assertEquals(pegInTxSender, pegoutReceiver);

assertRejectedPeginTransaction(bitcoinTransaction);
assertReleaseBtcRequested(rskTx.getHash().getBytes(), pegOut, minimumPeginValue);
assertPegoutTransactioCreated(pegOut.getHash(), UtxoUtils.extractOutpointValues(pegOut));

assertEquals(expectedFederationUTXOs, federationSupport.getActiveFederationBtcUTXOs());
assertEquals(expectedReceiverBalance, repository.getBalance(rskReceiver));
}

private static UTXO utxoOf(BtcTransaction bitcoinTransaction, TransactionOutput output) {
int height = 0;
return new UTXO(
Expand All @@ -175,6 +229,13 @@ private static UTXO utxoOf(BtcTransaction bitcoinTransaction, TransactionOutput
);
}

private void setupChainWithBtcTransaction(BtcTransaction bitcoinTransaction) throws BlockStoreException {
pmtWithTransactions = createValidPmtForTransactions(List.of(bitcoinTransaction.getHash()), btcNetworkParams);
btcBlockWithPmtHeight = bridgeConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeConstants.getPegoutTxIndexGracePeriodInBtcBlocks();
int chainHeight = btcBlockWithPmtHeight + bridgeConstants.getBtc2RskMinimumAcceptableConfirmations();
recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, btcNetworkParams);
}

private BtcTransaction createPegInTransaction(Address federationAddress, Coin coin, BtcECKey pubKey) {
BtcTransaction btcTx = new BtcTransaction(btcNetworkParams);
int outputIndex = 0;
Expand All @@ -185,11 +246,57 @@ private BtcTransaction createPegInTransaction(Address federationAddress, Coin co
return btcTx;
}

private void assertLogPegInBtc() {
private BtcTransaction createMultiSigPegInTransaction(Address federationAddress, Coin coin) {
BtcTransaction btcTx = new BtcTransaction(btcNetworkParams);
btcTx.addInput(
BitcoinTestUtils.createHash(1),
0,
ScriptBuilder.createP2SHMultiSigInputScript(null, federationSupport.getActiveFederation().getRedeemScript())
);
btcTx.addOutput(new TransactionOutput(btcNetworkParams, btcTx, coin, federationAddress));

return btcTx;
}

private void assertLogPegInBtc(BtcTransaction bitcoinTransaction) {
CallTransaction.Function pegInBtcEvent = BridgeEvents.PEGIN_BTC.getEvent();
Sha256Hash peginTransactionHash = bitcoinTransaction.getHash();
List<DataWord> encodedTopics = getEncodedTopics(BridgeEvents.PEGIN_BTC.getEvent(), rskReceiver.toString(), peginTransactionHash.getBytes());

List<DataWord> encodedTopics = getEncodedTopics(pegInBtcEvent, rskReceiver.toString(), peginTransactionHash.getBytes());

int protocolVersion = 0;
byte[] encodedData = getEncodedData(BridgeEvents.PEGIN_BTC.getEvent(), minimumPeginValue.getValue(), protocolVersion);
byte[] encodedData = getEncodedData(pegInBtcEvent, minimumPeginValue.getValue(), protocolVersion);

assertEventWasEmittedWithExpectedTopics(encodedTopics, logs);
assertEventWasEmittedWithExpectedData(encodedData, logs);
}

private void assertRejectedPeginTransaction(BtcTransaction bitcoinTransaction) {
CallTransaction.Function rejectedPeginEvent = BridgeEvents.REJECTED_PEGIN.getEvent();
Sha256Hash peginTransactionHash = bitcoinTransaction.getHash();
List<DataWord> encodedTopics = getEncodedTopics(rejectedPeginEvent, peginTransactionHash.getBytes());
byte[] encodedData = getEncodedData(rejectedPeginEvent, RejectedPeginReason.LEGACY_PEGIN_MULTISIG_SENDER.getValue());

assertEventWasEmittedWithExpectedTopics(encodedTopics, logs);
assertEventWasEmittedWithExpectedData(encodedData, logs);
}

private void assertReleaseBtcRequested(byte[] rskTransactionHash, BtcTransaction pegoutTransaction, Coin amount) {
CallTransaction.Function rejectedPeginEvent = BridgeEvents.RELEASE_REQUESTED.getEvent();
byte[] pegoutTransactionHash = pegoutTransaction.getHash().getBytes();
List<DataWord> encodedTopics = getEncodedTopics(rejectedPeginEvent, rskTransactionHash, pegoutTransactionHash);
byte[] encodedData = getEncodedData(rejectedPeginEvent, amount.getValue());

assertEventWasEmittedWithExpectedTopics(encodedTopics, logs);
assertEventWasEmittedWithExpectedData(encodedData, logs);
}

private void assertPegoutTransactioCreated(Sha256Hash pegoutTransactionHash, List<Coin> outpointValues) {
CallTransaction.Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent();

List<DataWord> encodedTopics = getEncodedTopics(pegoutTransactionCreatedEvent, pegoutTransactionHash.getBytes());
byte[] serializedOutpointValues = UtxoUtils.encodeOutpointValues(outpointValues);
byte[] encodedData = getEncodedData(pegoutTransactionCreatedEvent, serializedOutpointValues);

assertEventWasEmittedWithExpectedTopics(encodedTopics, logs);
assertEventWasEmittedWithExpectedData(encodedData, logs);
Expand Down

0 comments on commit c2c5caa

Please sign in to comment.