Skip to content

Commit

Permalink
Added unit tests for executing moved funds sweep action
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszslabon committed Mar 7, 2024
1 parent 1c4037a commit 59e5d4a
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 49 deletions.
86 changes: 73 additions & 13 deletions pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ type localChain struct {
movingFundsProposalValidationsMutex sync.Mutex
movingFundsProposalValidations map[[32]byte]bool

movedFundsSweepProposalValidationsMutex sync.Mutex
movedFundsSweepProposalValidations map[[32]byte]bool

heartbeatProposalValidationsMutex sync.Mutex
heartbeatProposalValidations map[[16]byte]bool

Expand Down Expand Up @@ -999,7 +1002,63 @@ func (lc *localChain) ValidateMovedFundsSweepProposal(
walletPublicKeyHash [20]byte,
proposal *MovedFundsSweepProposal,
) error {
panic("unsupported")
lc.movedFundsSweepProposalValidationsMutex.Lock()
defer lc.movedFundsSweepProposalValidationsMutex.Unlock()

key, err := buildMovedFundsSweepProposalValidationKey(
walletPublicKeyHash,
proposal,
)
if err != nil {
return err
}

result, ok := lc.movedFundsSweepProposalValidations[key]
if !ok {
return fmt.Errorf("validation result unknown")
}

if !result {
return fmt.Errorf("validation failed")
}

return nil
}

func (lc *localChain) setMovedFundsSweepProposalValidationResult(
walletPublicKeyHash [20]byte,
proposal *MovedFundsSweepProposal,
result bool,
) error {
lc.movedFundsSweepProposalValidationsMutex.Lock()
defer lc.movedFundsSweepProposalValidationsMutex.Unlock()

key, err := buildMovedFundsSweepProposalValidationKey(
walletPublicKeyHash,
proposal,
)
if err != nil {
return err
}

lc.movedFundsSweepProposalValidations[key] = result

return nil
}

func buildMovedFundsSweepProposalValidationKey(
walletPublicKeyHash [20]byte,
proposal *MovedFundsSweepProposal,
) ([32]byte, error) {
var buffer bytes.Buffer

buffer.Write(walletPublicKeyHash[:])

buffer.Write(proposal.MovingFundsTxHash[:])
binary.Write(&buffer, binary.BigEndian, proposal.MovingFundsTxOutputIndex)
buffer.Write(proposal.SweepTxFee.Bytes())

return sha256.Sum256(buffer.Bytes()), nil
}

// Connect sets up the local chain.
Expand Down Expand Up @@ -1030,18 +1089,19 @@ func ConnectWithKey(
dkgResultChallengeHandlers: make(
map[int]func(submission *DKGResultChallengedEvent),
),
wallets: make(map[[20]byte]*WalletChainData),
blocksByTimestamp: make(map[uint64]uint64),
blocksHashesByNumber: make(map[uint64][32]byte),
pastDepositRevealedEvents: make(map[[32]byte][]*DepositRevealedEvent),
depositSweepProposalValidations: make(map[[32]byte]bool),
pendingRedemptionRequests: make(map[[32]byte]*RedemptionRequest),
redemptionProposalValidations: make(map[[32]byte]bool),
movingFundsProposalValidations: make(map[[32]byte]bool),
heartbeatProposalValidations: make(map[[16]byte]bool),
depositRequests: make(map[[32]byte]*DepositChainRequest),
blockCounter: blockCounter,
operatorPrivateKey: operatorPrivateKey,
wallets: make(map[[20]byte]*WalletChainData),
blocksByTimestamp: make(map[uint64]uint64),
blocksHashesByNumber: make(map[uint64][32]byte),
pastDepositRevealedEvents: make(map[[32]byte][]*DepositRevealedEvent),
depositSweepProposalValidations: make(map[[32]byte]bool),
pendingRedemptionRequests: make(map[[32]byte]*RedemptionRequest),
redemptionProposalValidations: make(map[[32]byte]bool),
movingFundsProposalValidations: make(map[[32]byte]bool),
movedFundsSweepProposalValidations: make(map[[32]byte]bool),
heartbeatProposalValidations: make(map[[16]byte]bool),
depositRequests: make(map[[32]byte]*DepositChainRequest),
blockCounter: blockCounter,
operatorPrivateKey: operatorPrivateKey,
}

return localChain
Expand Down
42 changes: 21 additions & 21 deletions pkg/tbtc/internal/test/marshaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,17 +367,17 @@ func (mfts *MovingFundsTestScenario) UnmarshalJSON(data []byte) error {
// proper MovedFundsSweepTestScenario.
func (mfts *MovedFundsSweepTestScenario) UnmarshalJSON(data []byte) error {
type movedFundsSweepTestScenario struct {
Title string
WalletPublicKey string
MovedFundsUtxo *utxo
WalletMainUtxo *utxo
InputTransactions []string
Fee int64
Signatures []signature
ExpectedSigHashes []string
ExpectedMovingFundsTransaction string
ExpectedMovingFundsTransactionHash string
ExpectedMovingFundsTransactionWitnessHash string
Title string
WalletPublicKey string
MovedFundsUtxo *utxo
WalletMainUtxo *utxo
InputTransactions []string
Fee int64
Signatures []signature
ExpectedSigHashes []string
ExpectedMovedFundsSweepTransaction string
ExpectedMovedFundsSweepTransactionHash string
ExpectedMovedFundsSweepTransactionWitnessHash string
}

var unmarshaled movedFundsSweepTestScenario
Expand Down Expand Up @@ -439,27 +439,27 @@ func (mfts *MovedFundsSweepTestScenario) UnmarshalJSON(data []byte) error {
)
}

// Unmarshal expected moving funds transaction.
mfts.ExpectedMovingFundsTransaction = new(bitcoin.Transaction)
err = mfts.ExpectedMovingFundsTransaction.Deserialize(
hexToSlice(unmarshaled.ExpectedMovingFundsTransaction),
// Unmarshal expected moved funds sweep transaction.
mfts.ExpectedMovedFundsSweepTransaction = new(bitcoin.Transaction)
err = mfts.ExpectedMovedFundsSweepTransaction.Deserialize(
hexToSlice(unmarshaled.ExpectedMovedFundsSweepTransaction),
)
if err != nil {
return err
}

// Unmarshal expected moving funds transaction hash.
mfts.ExpectedMovingFundsTransactionHash, err = bitcoin.NewHashFromString(
unmarshaled.ExpectedMovingFundsTransactionHash,
// Unmarshal expected moved funds sweep transaction hash.
mfts.ExpectedMovedFundsSweepTransactionHash, err = bitcoin.NewHashFromString(
unmarshaled.ExpectedMovedFundsSweepTransactionHash,
bitcoin.ReversedByteOrder,
)
if err != nil {
return err
}

// Unmarshal expected moving funds transaction witness hash.
mfts.ExpectedMovingFundsTransactionWitnessHash, err = bitcoin.NewHashFromString(
unmarshaled.ExpectedMovingFundsTransactionWitnessHash,
// Unmarshal expected moved funds sweep transaction witness hash.
mfts.ExpectedMovedFundsSweepTransactionWitnessHash, err = bitcoin.NewHashFromString(
unmarshaled.ExpectedMovedFundsSweepTransactionWitnessHash,
bitcoin.ReversedByteOrder,
)
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions pkg/tbtc/internal/test/tbtctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ type MovedFundsSweepTestScenario struct {
Fee int64
Signatures []*bitcoin.SignatureContainer

ExpectedSigHashes []*big.Int
ExpectedMovingFundsTransaction *bitcoin.Transaction
ExpectedMovingFundsTransactionHash bitcoin.Hash
ExpectedMovingFundsTransactionWitnessHash bitcoin.Hash
ExpectedSigHashes []*big.Int
ExpectedMovedFundsSweepTransaction *bitcoin.Transaction
ExpectedMovedFundsSweepTransactionHash bitcoin.Hash
ExpectedMovedFundsSweepTransactionWitnessHash bitcoin.Hash
}

// LoadMovedFundsSweepTestScenarios loads all scenarios related with moved funds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"ExpectedSigHashes": [
"1e6e803f3394a17346212fc561acfe5265e5d8f478cf2e1d1a21869686adcbfc"
],
"ExpectedMovingFundsTransaction": "010000000001019b1b33bdd3c44404544991889d63afe6caa875983b705106f1d988251d1459200000000000ffffffff011c700000000000001600148db50eb52063ea9d98b3eac91489a90f738986f6024730440220242dbac95ab8e632cd2791e99d3048b96e6e042bcd902f30fbae7e942a24ea3e02201b7416e6d7d36ea142521eb80e0bc29d118f62ab6b8a64a062cc5812cfdb8c89012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d900000000",
"ExpectedMovingFundsTransactionHash": "a586427d66f8ccca1ed8f7e40a2c82aae99a1f85dfce62ffe2f3657350b6fd84",
"ExpectedMovingFundsTransactionWitnessHash": "c37312351f8c0346c6b878d35571a8015b3bc6dcc035c3535e29b79f389acce4"
"ExpectedMovedFundsSweepTransaction": "010000000001019b1b33bdd3c44404544991889d63afe6caa875983b705106f1d988251d1459200000000000ffffffff011c700000000000001600148db50eb52063ea9d98b3eac91489a90f738986f6024730440220242dbac95ab8e632cd2791e99d3048b96e6e042bcd902f30fbae7e942a24ea3e02201b7416e6d7d36ea142521eb80e0bc29d118f62ab6b8a64a062cc5812cfdb8c89012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d900000000",
"ExpectedMovedFundsSweepTransactionHash": "a586427d66f8ccca1ed8f7e40a2c82aae99a1f85dfce62ffe2f3657350b6fd84",
"ExpectedMovedFundsSweepTransactionWitnessHash": "c37312351f8c0346c6b878d35571a8015b3bc6dcc035c3535e29b79f389acce4"
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"4fe61de7a1031db9b8e5dfa08cec8b3fb502409ff1f5b9d9ac2da0684ac34e64",
"15bb75ef4483c44e072f5f35d6c5fcacd63f904ff9913d31500003dd3781fabf"
],
"ExpectedMovingFundsTransaction": "0100000000010218201d563e43a926f5f9fd4498af5c513a3ea284373308aeda39b0a0d57585780000000000ffffffff84fdb6507365f3e2ff62cedf851f9ae9aa822c0ae4f7d81ecaccf8667d4286a50000000000ffffffff0104a60000000000001600148db50eb52063ea9d98b3eac91489a90f738986f60248304502210089dfa958867b2265d0fc08d996af82a9a731bd972f20e0530d37937f38d9ec1002200cbc820a696b99747aed39aeed5d848367773cf6d4e24aaa12fe2ad714a1ff99012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d902473044022063dc201589b1f7810247eaa569baf5e3dda8717a10e77a2ad95661fef643bdc602203bee4dd0c4a24291523bb7542394df4e6008c0c7ddfdd30c41d55036b32a8999012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d900000000",
"ExpectedMovingFundsTransactionHash": "fc78f52ab4094b5c0bf8a782750c24f31b5db2667425fbddccc29d64f89baf9b",
"ExpectedMovingFundsTransactionWitnessHash": "94bdcc1758967e8f54ff54b9edb96974e900097ee626bdc69743f949c4053226"
"ExpectedMovedFundsSweepTransaction": "0100000000010218201d563e43a926f5f9fd4498af5c513a3ea284373308aeda39b0a0d57585780000000000ffffffff84fdb6507365f3e2ff62cedf851f9ae9aa822c0ae4f7d81ecaccf8667d4286a50000000000ffffffff0104a60000000000001600148db50eb52063ea9d98b3eac91489a90f738986f60248304502210089dfa958867b2265d0fc08d996af82a9a731bd972f20e0530d37937f38d9ec1002200cbc820a696b99747aed39aeed5d848367773cf6d4e24aaa12fe2ad714a1ff99012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d902473044022063dc201589b1f7810247eaa569baf5e3dda8717a10e77a2ad95661fef643bdc602203bee4dd0c4a24291523bb7542394df4e6008c0c7ddfdd30c41d55036b32a8999012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d900000000",
"ExpectedMovedFundsSweepTransactionHash": "fc78f52ab4094b5c0bf8a782750c24f31b5db2667425fbddccc29d64f89baf9b",
"ExpectedMovedFundsSweepTransactionWitnessHash": "94bdcc1758967e8f54ff54b9edb96974e900097ee626bdc69743f949c4053226"
}
144 changes: 139 additions & 5 deletions pkg/tbtc/moved_funds_sweep_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,148 @@
package tbtc

import (
"context"
"fmt"
"math/big"
"testing"
"time"

"github.com/keep-network/keep-core/pkg/tecdsa"

"github.com/keep-network/keep-core/internal/testutils"
"github.com/keep-network/keep-core/pkg/bitcoin"
"github.com/keep-network/keep-core/pkg/tbtc/internal/test"
)

// TODO: Think about covering unhappy paths for specific steps of the moved funds sweep action.
func TestMovedFundsSweepAction_Execute(t *testing.T) {
scenarios, err := test.LoadMovedFundsSweepTestScenarios()
if err != nil {
t.Fatal(err)
}

for _, scenario := range scenarios {
t.Run(scenario.Title, func(t *testing.T) {
hostChain := Connect()
bitcoinChain := newLocalBitcoinChain()

wallet := wallet{
// Set only relevant fields.
publicKey: scenario.WalletPublicKey,
}
walletPublicKeyHash := bitcoin.PublicKeyHash(wallet.publicKey)

// Record the transactions that will serve as moved funds sweep
// transaction's inputs in the Bitcoin local chain.
for _, transaction := range scenario.InputTransactions {
err := bitcoinChain.BroadcastTransaction(transaction)
if err != nil {
t.Fatal(err)
}
}

// Build the moved funds sweep proposal based on the scenario data.
proposal := &MovedFundsSweepProposal{
SweepTxFee: big.NewInt(scenario.Fee),
MovingFundsTxHash: scenario.MovedFundsUtxo.Outpoint.TransactionHash,
MovingFundsTxOutputIndex: scenario.MovedFundsUtxo.Outpoint.OutputIndex,
}

// Choose an arbitrary start block and expiration time.
proposalProcessingStartBlock := uint64(100)
proposalExpiryBlock := proposalProcessingStartBlock +
movedFundsSweepProposalValidityBlocks

// Simulate the on-chain proposal validation passes with success.
err = hostChain.setMovedFundsSweepProposalValidationResult(
walletPublicKeyHash,
proposal,
true,
)
if err != nil {
t.Fatal(err)
}

// Record the wallet main UTXO hash in the local host chain so the
// moved funds sweep action can detect it.
var walletMainUtxoHash [32]byte
if scenario.WalletMainUtxo != nil {
walletMainUtxoHash = hostChain.ComputeMainUtxoHash(
scenario.WalletMainUtxo,
)
}
hostChain.setWallet(walletPublicKeyHash, &WalletChainData{
MainUtxoHash: walletMainUtxoHash,
})

// Create a signing executor mock instance.
signingExecutor := newMockWalletSigningExecutor()

// The signatures within the scenario fixture are in the format
// suitable for applying them directly to a Bitcoin transaction.
// However, the signing executor operates on raw tECDSA signatures
// so, we need to unpack them first.
rawSignatures := make([]*tecdsa.Signature, len(scenario.Signatures))
for i, signature := range scenario.Signatures {
rawSignatures[i] = &tecdsa.Signature{
R: signature.R,
S: signature.S,
}
}

// Set up the signing executor mock to return the signatures from
// the test fixture when called with the expected parameters.
// Note that the start block is set based on the proposal
// processing start block as done within the action.
signingExecutor.setSignatures(
scenario.ExpectedSigHashes,
proposalProcessingStartBlock,
rawSignatures,
)

action := newMovedFundsSweepAction(
logger.With(),
hostChain,
bitcoinChain,
wallet,
signingExecutor,
proposal,
proposalProcessingStartBlock,
proposalExpiryBlock,
func(ctx context.Context, blockHeight uint64) error {
return nil
},
)

// Modify the default parameters of the action to make
// it possible to execute in the current test environment.
action.broadcastCheckDelay = 1 * time.Second

err = action.execute()
if err != nil {
t.Fatal(err)
}

// Action execution that completes without an error is a sign of
// success. However, just in case, make an additional check that
// the expected moved funds sweep transaction was actually
// broadcasted on the local Bitcoin chain.
broadcastedMovedFundsSweepTransaction, err := bitcoinChain.GetTransaction(
scenario.ExpectedMovedFundsSweepTransactionHash,
)
if err != nil {
t.Fatal(err)
}

testutils.AssertBytesEqual(
t,
scenario.ExpectedMovedFundsSweepTransaction.Serialize(),
broadcastedMovedFundsSweepTransaction.Serialize(),
)
})
}
}

func TestAssembleMovedFundsSweepTransaction(t *testing.T) {
scenarios, err := test.LoadMovedFundsSweepTestScenarios()
if err != nil {
Expand Down Expand Up @@ -59,19 +193,19 @@ func TestAssembleMovedFundsSweepTransaction(t *testing.T) {

testutils.AssertBytesEqual(
t,
scenario.ExpectedMovingFundsTransaction.Serialize(),
scenario.ExpectedMovedFundsSweepTransaction.Serialize(),
transaction.Serialize(),
)
testutils.AssertStringsEqual(
t,
"moving funds transaction hash",
scenario.ExpectedMovingFundsTransactionHash.Hex(bitcoin.InternalByteOrder),
"moved funds sweep transaction hash",
scenario.ExpectedMovedFundsSweepTransactionHash.Hex(bitcoin.InternalByteOrder),
transaction.Hash().Hex(bitcoin.InternalByteOrder),
)
testutils.AssertStringsEqual(
t,
"moving funds transaction witness hash",
scenario.ExpectedMovingFundsTransactionWitnessHash.Hex(bitcoin.InternalByteOrder),
"moved funds sweep transaction witness hash",
scenario.ExpectedMovedFundsSweepTransactionWitnessHash.Hex(bitcoin.InternalByteOrder),
transaction.WitnessHash().Hex(bitcoin.InternalByteOrder),
)
})
Expand Down

0 comments on commit 59e5d4a

Please sign in to comment.