Skip to content

Commit

Permalink
Merge pull request #291 from rsksmart/refactor-powhsm-tests
Browse files Browse the repository at this point in the history
Refactor ReleaseCreationInformationGetter and PowHSMSignerMessage tests
  • Loading branch information
marcos-iov authored Aug 12, 2024
2 parents c95523b + ce199e9 commit 0a9d699
Show file tree
Hide file tree
Showing 5 changed files with 390 additions and 421 deletions.
112 changes: 112 additions & 0 deletions src/test/java/co/rsk/federate/bitcoin/BtcTransactionBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package co.rsk.federate.bitcoin;

import co.rsk.bitcoinj.core.Address;
import co.rsk.bitcoinj.core.BtcTransaction;
import co.rsk.bitcoinj.core.Coin;
import co.rsk.bitcoinj.core.NetworkParameters;
import co.rsk.bitcoinj.core.TransactionInput;
import co.rsk.bitcoinj.core.TransactionOutPoint;
import co.rsk.bitcoinj.core.TransactionOutput;
import co.rsk.bitcoinj.core.TransactionWitness;
import co.rsk.bitcoinj.script.Script;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class BtcTransactionBuilder {

private NetworkParameters networkParameters = NetworkParameters.fromID(NetworkParameters.ID_MAINNET);
private final Map<Integer, TransactionWitness> transactionWitnesses = new HashMap<>();
private final List<TransactionInput> inputs = new ArrayList<>();
private final List<TransactionOutput> outputs = new ArrayList<>();

public InputBuilder createInputBuilder() {
return new InputBuilder();
}

public BtcTransactionBuilder withNetworkParameters(NetworkParameters networkParameters) {
this.networkParameters = networkParameters;
return this;
}

public BtcTransactionBuilder withInput(TransactionInput transactionInput) {
inputs.add(transactionInput);
return this;
}

public BtcTransactionBuilder withInputFromOutput(TransactionOutput transactionOutput) {
inputs.add(new InputBuilder().withAmount(transactionOutput.getValue()).withOutpointIndex(
transactionOutput.getIndex()).build());
return this;
}

public BtcTransactionBuilder withWitness(int inputIndex,
TransactionWitness transactionWitness) {
transactionWitnesses.put(inputIndex, transactionWitness);
return this;
}

public BtcTransactionBuilder withOutput(Coin amount, Address address) {
outputs.add(
new TransactionOutput(networkParameters, null, amount, address)
);
return this;
}

public BtcTransaction build() {
BtcTransaction btcTransaction = new BtcTransaction(networkParameters);
addInputs(btcTransaction);
addOutputs(btcTransaction);
return btcTransaction;
}

private void addInputs(BtcTransaction btcTransaction) {
inputs.forEach(transactionInput -> {
int inputIndex = btcTransaction.getInputs().size();
btcTransaction.addInput(transactionInput);
if (transactionWitnesses.containsKey(inputIndex)) {
btcTransaction.setWitness(inputIndex, transactionWitnesses.get(inputIndex));
}
});
}

private void addOutputs(BtcTransaction btcTransaction) {
outputs.forEach(btcTransaction::addOutput);
}

public class InputBuilder {

private Coin amount;
private Script scriptSig;
private int outpointIndex = 0;

private InputBuilder() { }

public InputBuilder withAmount(Coin amount) {
this.amount = amount;
return this;
}

public InputBuilder withScriptSig(Script scriptSig) {
this.scriptSig = scriptSig;
return this;
}

public InputBuilder withOutpointIndex(int outpointIndex) {
this.outpointIndex = outpointIndex;
return this;
}

public TransactionInput build() {
TransactionOutPoint transactionOutpoint = new TransactionOutPoint(networkParameters,
outpointIndex,
BitcoinTestUtils.createHash(outpointIndex));
TransactionInput transactionInput = new TransactionInput(
networkParameters, null, new byte[]{}, transactionOutpoint, amount
);
transactionInput.setScriptSig(scriptSig);
return transactionInput;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import co.rsk.core.RskAddress;
import co.rsk.crypto.Keccak256;
import co.rsk.federate.bitcoin.BitcoinTestUtils;
import co.rsk.federate.bitcoin.BtcTransactionBuilder;
import co.rsk.federate.rpc.JsonRpcClient;
import co.rsk.federate.rpc.JsonRpcClientProvider;
import co.rsk.federate.rpc.JsonRpcException;
Expand Down Expand Up @@ -91,7 +92,6 @@ class PowHSMSigningClientBtcTest {
bridgeMainnetConstants.getBtcParams(), 9);
private static final Federation oldFederation = TestUtils.createFederation(
bridgeMainnetConstants.getBtcParams(), 5);
private static final int FIRST_OUTPUT_INDEX = 0;

private static final HSMSignature expectedSignature = createMockSignature();
private final ECKey signerPk = ECKey.fromPrivate(Hex.decode("fa01"));
Expand Down Expand Up @@ -266,7 +266,7 @@ void signNoS() throws Exception {

@ParameterizedTest
@MethodSource("legacyPegoutArgProvider")
void sign_whenBatchPegoutHasNotPegoutTransactionCreatedEvent_returnsOk(int hsmVersion,
void sign_whenBatchPegoutHasNotPegoutTransactionCreatedEvent_returnsOk(HSMVersion hsmVersion,
List<Coin> expectedOutpointValues)
throws JsonRpcException, SignerMessageBuilderException, HSMClientException {
// arrange
Expand All @@ -289,7 +289,7 @@ void sign_whenBatchPegoutHasNotPegoutTransactionCreatedEvent_returnsOk(int hsmVe
pegoutCreationBlock, pegoutCreationRskTxReceipt, pegoutCreationRskTx.getHash(),
pegoutBtcTx, pegoutConfirmationRskTx.getHash());

client = new PowHSMSigningClientBtc(hsmClientProtocol, hsmVersion);
client = new PowHSMSigningClientBtc(hsmClientProtocol, hsmVersion.getNumber());

PowHSMSignerMessageBuilder powHSMSignerMessageBuilder = new PowHSMSignerMessageBuilder(
receiptStore, releaseCreationInformation);
Expand All @@ -299,7 +299,7 @@ void sign_whenBatchPegoutHasNotPegoutTransactionCreatedEvent_returnsOk(int hsmVe

@ParameterizedTest
@MethodSource("signArgProvider")
void sign_whenSegwitBatchPegoutHasPegoutTransactionCreatedEvent_returnsOk(int hsmVersion,
void sign_whenSegwitBatchPegoutHasPegoutTransactionCreatedEvent_returnsOk(HSMVersion hsmVersion,
byte[] serializedOutpointValues, List<Coin> expectedOutpointValues)
throws JsonRpcException, SignerMessageBuilderException, HSMClientException {
// arrange
Expand All @@ -326,7 +326,7 @@ void sign_whenSegwitBatchPegoutHasPegoutTransactionCreatedEvent_returnsOk(int hs
pegoutCreationBlock, pegoutCreationRskTxReceipt, pegoutCreationRskTx.getHash(),
pegoutBtcTx, pegoutConfirmationRskTx.getHash());

client = new PowHSMSigningClientBtc(hsmClientProtocol, hsmVersion);
client = new PowHSMSigningClientBtc(hsmClientProtocol, hsmVersion.getNumber());

PowHSMSignerMessageBuilder powHSMSignerMessageBuilder = new PowHSMSignerMessageBuilder(
receiptStore, releaseCreationInformation);
Expand All @@ -336,7 +336,7 @@ void sign_whenSegwitBatchPegoutHasPegoutTransactionCreatedEvent_returnsOk(int hs

@ParameterizedTest
@MethodSource("signArgProvider")
void sign_whenSegwitMigrationPegoutHasPegoutTransactionCreatedEvent_returnsOk(int hsmVersion,
void sign_whenSegwitMigrationPegoutHasPegoutTransactionCreatedEvent_returnsOk(HSMVersion hsmVersion,
byte[] serializedOutpointValues, List<Coin> expectedOutpointValues)
throws JsonRpcException, SignerMessageBuilderException, HSMClientException {
// arrange
Expand All @@ -358,7 +358,7 @@ void sign_whenSegwitMigrationPegoutHasPegoutTransactionCreatedEvent_returnsOk(in
pegoutCreationBlock, pegoutCreationRskTxReceipt, pegoutCreationRskTx.getHash(),
pegoutBtcTx, pegoutConfirmationRskTx.getHash());

client = new PowHSMSigningClientBtc(hsmClientProtocol, hsmVersion);
client = new PowHSMSigningClientBtc(hsmClientProtocol, hsmVersion.getNumber());

PowHSMSignerMessageBuilder powHSMSignerMessageBuilder = new PowHSMSignerMessageBuilder(
receiptStore, releaseCreationInformation);
Expand All @@ -382,10 +382,10 @@ private static List<Arguments> legacyPegoutArgProvider() {
for (HSMVersion hsmVersion : powHSMVersions) {
// 50_000_000 = FE80F0FA02, 75_000_000 = FEC0687804, 100_000_000 = FE00E1F505
arguments.add(
Arguments.of(hsmVersion.getNumber(), coinListOf(50_000_000)));
Arguments.of(hsmVersion, coinListOf(50_000_000)));
arguments.add(
Arguments.of(hsmVersion.getNumber(), coinListOf(75_000_000)));
arguments.add(Arguments.of(hsmVersion.getNumber(),
Arguments.of(hsmVersion, coinListOf(75_000_000)));
arguments.add(Arguments.of(hsmVersion,
coinListOf(50_000_000, 75_000_000, 100_000_000)));
}

Expand All @@ -399,10 +399,10 @@ private static List<Arguments> signArgProvider() {
for (HSMVersion hsmVersion : powHSMVersions) {
// 50_000_000 = FE80F0FA02, 75_000_000 = FEC0687804, 100_000_000 = FE00E1F505
arguments.add(
Arguments.of(hsmVersion.getNumber(), Hex.decode("FE80F0FA02"), coinListOf(50_000_000)));
Arguments.of(hsmVersion, Hex.decode("FE80F0FA02"), coinListOf(50_000_000)));
arguments.add(
Arguments.of(hsmVersion.getNumber(), Hex.decode("FEC0687804"), coinListOf(75_000_000)));
arguments.add(Arguments.of(hsmVersion.getNumber(), Hex.decode("FE80F0FA02FEC0687804FE00E1F505"),
Arguments.of(hsmVersion, Hex.decode("FEC0687804"), coinListOf(75_000_000)));
arguments.add(Arguments.of(hsmVersion, Hex.decode("FE80F0FA02FEC0687804FE00E1F505"),
coinListOf(50_000_000, 75_000_000, 100_000_000)));
}

Expand All @@ -423,36 +423,44 @@ private void addCommonPegoutLogs(List<LogInfo> logs, BtcTransaction pegoutBtcTx)

private BtcTransaction createPegout(List<Coin> outpointValues,
List<Address> destinationAddresses, boolean segwit) {
BtcTransaction fundingTransaction = new BtcTransaction(btcMainnetParams);
fundingTransaction.addInput(BitcoinTestUtils.createHash(1), FIRST_OUTPUT_INDEX,
new Script(new byte[]{}));

for (Coin outpointValue : outpointValues) {
fundingTransaction.addOutput(outpointValue, oldFederation.getAddress());
}

BtcTransaction pegoutBtcTx = new BtcTransaction(btcMainnetParams);
BtcTransactionBuilder btcTransactionBuilder = new BtcTransactionBuilder();

// TODO: improve this method to create a more realistic btc segwit transaction
// once {@link SignerMessageBuilder#getSigHashByInputIndex(int)} is refactored to support segwit
Script inputScriptThatSpendsFromTheFederation = createBaseInputScriptThatSpendsFromTheFederation(
oldFederation);

Coin fee = Coin.MILLICOIN;
for (int outputIndex = 0; outputIndex < outpointValues.size(); outputIndex++) {
TransactionInput addedInput = pegoutBtcTx.addInput(
fundingTransaction.getOutput(outputIndex));

addedInput.setScriptSig(inputScriptThatSpendsFromTheFederation);

for (int inputIndex = 0; inputIndex < outpointValues.size(); inputIndex++) {
Coin outpointValue = outpointValues.get(inputIndex);
Coin amountToSend = outpointValue.minus(fee);
// Iterate over the addresses using inputIndex % addresses.size() to have outputs to different addresses
Address destinationAddress = destinationAddresses.get(
inputIndex % destinationAddresses.size());

TransactionInput txInput = btcTransactionBuilder.createInputBuilder()
.withAmount(outpointValue).withOutpointIndex(inputIndex)
.withScriptSig(inputScriptThatSpendsFromTheFederation)
.build();

btcTransactionBuilder
.withInput(
txInput
)
.withOutput(amountToSend, destinationAddress);

// TODO: change this dummy witness for a real witness once segwit is fully implemented in bitcoinj-thin
// make it a segwit tx by adding a single witness
if (segwit) {
addWitness(pegoutBtcTx, outputIndex);
}
TransactionWitness witness = new TransactionWitness(1);
witness.setPush(0, new byte[]{1});

Coin amountToSend = outpointValues.get(outputIndex).minus(fee);
pegoutBtcTx.addOutput(amountToSend,
destinationAddresses.get(outputIndex % destinationAddresses.size()));
btcTransactionBuilder.withWitness(inputIndex, witness);
}
}

return pegoutBtcTx;
return btcTransactionBuilder.build();
}

private List<Address> createDestinationAddresses(int numberOfAddresses) {
Expand All @@ -462,18 +470,12 @@ private List<Address> createDestinationAddresses(int numberOfAddresses) {
.collect(Collectors.toList());
}

private void addWitness(BtcTransaction pegoutBtcTx, int inputIndex) {
TransactionWitness txWitness = new TransactionWitness(1);
txWitness.setPush(0, new byte[]{0x1});
pegoutBtcTx.setWitness(inputIndex, txWitness);
}

private void signAndExecuteAssertions(int hsmVersion, List<Coin> expectedOutpointValues,
private void signAndExecuteAssertions(HSMVersion hsmVersion, List<Coin> expectedOutpointValues,
PowHSMSignerMessageBuilder powHSMSignerMessageBuilder)
throws JsonRpcException, SignerMessageBuilderException, HSMClientException {
final int numOfInputsToSign = expectedOutpointValues.size();

ObjectNode expectedPublicKeyRequest = buildGetPublicKeyCommand(hsmVersion);
ObjectNode expectedPublicKeyRequest = buildGetPublicKeyCommand(hsmVersion.getNumber());
ObjectNode publicKeyResponse = buildResponse(0);
publicKeyResponse.put(PUB_KEY.getFieldName(), Hex.toHexString(signerPk.getPubKey()));
when(jsonRpcClientMock.send(expectedPublicKeyRequest)).thenReturn(publicKeyResponse);
Expand All @@ -484,7 +486,7 @@ private void signAndExecuteAssertions(int hsmVersion, List<Coin> expectedOutpoin
PowHSMSignerMessage powHSMSignerMessage = (PowHSMSignerMessage) powHSMSignerMessageBuilder.buildMessageForIndex(
inputIndex);

ObjectNode expectedSignCommand = buildSignCommand(hsmVersion,
ObjectNode expectedSignCommand = buildSignCommand(hsmVersion.getNumber(),
powHSMSignerMessage);
when(jsonRpcClientMock.send(expectedSignCommand)).thenReturn(response);

Expand Down
Loading

0 comments on commit 0a9d699

Please sign in to comment.