Skip to content

Commit c9746b5

Browse files
committed
Added unit tests for moving funds action
1 parent 2acc38a commit c9746b5

File tree

3 files changed

+216
-3
lines changed

3 files changed

+216
-3
lines changed

Diff for: pkg/tbtc/chain_test.go

+85-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"golang.org/x/crypto/sha3"
1818

19+
"github.com/ethereum/go-ethereum/crypto"
1920
"github.com/keep-network/keep-core/pkg/bitcoin"
2021
"github.com/keep-network/keep-core/pkg/chain"
2122
"github.com/keep-network/keep-core/pkg/chain/local_v1"
@@ -65,6 +66,9 @@ type localChain struct {
6566
redemptionProposalValidationsMutex sync.Mutex
6667
redemptionProposalValidations map[[32]byte]bool
6768

69+
movingFundsProposalValidationsMutex sync.Mutex
70+
movingFundsProposalValidations map[[32]byte]bool
71+
6872
heartbeatProposalValidationsMutex sync.Mutex
6973
heartbeatProposalValidations map[[16]byte]bool
7074

@@ -693,6 +697,19 @@ func (lc *localChain) ComputeMainUtxoHash(
693697
return mainUtxoHash
694698
}
695699

700+
func (lc *localChain) ComputeMovingFundsCommitmentHash(targetWallets [][20]byte) [32]byte {
701+
packedWallets := []byte{}
702+
703+
for _, wallet := range targetWallets {
704+
packedWallets = append(packedWallets, wallet[:]...)
705+
// Each wallet hash must be padded with 12 zero bytes following the
706+
// actual hash.
707+
packedWallets = append(packedWallets, make([]byte, 12)...)
708+
}
709+
710+
return crypto.Keccak256Hash(packedWallets)
711+
}
712+
696713
func (lc *localChain) operatorAddress() (chain.Address, error) {
697714
_, operatorPublicKey, err := lc.OperatorKeyPair()
698715
if err != nil {
@@ -909,7 +926,73 @@ func (lc *localChain) ValidateMovingFundsProposal(
909926
mainUTXO *bitcoin.UnspentTransactionOutput,
910927
proposal *MovingFundsProposal,
911928
) error {
912-
panic("unsupported")
929+
lc.movingFundsProposalValidationsMutex.Lock()
930+
defer lc.movingFundsProposalValidationsMutex.Unlock()
931+
932+
key, err := buildMovingFundsProposalValidationKey(
933+
walletPublicKeyHash,
934+
mainUTXO,
935+
proposal,
936+
)
937+
if err != nil {
938+
return err
939+
}
940+
941+
result, ok := lc.movingFundsProposalValidations[key]
942+
if !ok {
943+
return fmt.Errorf("validation result unknown")
944+
}
945+
946+
if !result {
947+
return fmt.Errorf("validation failed")
948+
}
949+
950+
return nil
951+
}
952+
953+
func (lc *localChain) setMovingFundsProposalValidationResult(
954+
walletPublicKeyHash [20]byte,
955+
mainUTXO *bitcoin.UnspentTransactionOutput,
956+
proposal *MovingFundsProposal,
957+
result bool,
958+
) error {
959+
lc.movingFundsProposalValidationsMutex.Lock()
960+
defer lc.movingFundsProposalValidationsMutex.Unlock()
961+
962+
key, err := buildMovingFundsProposalValidationKey(
963+
walletPublicKeyHash,
964+
mainUTXO,
965+
proposal,
966+
)
967+
if err != nil {
968+
return err
969+
}
970+
971+
lc.movingFundsProposalValidations[key] = result
972+
973+
return nil
974+
}
975+
976+
func buildMovingFundsProposalValidationKey(
977+
walletPublicKeyHash [20]byte,
978+
mainUTXO *bitcoin.UnspentTransactionOutput,
979+
proposal *MovingFundsProposal,
980+
) ([32]byte, error) {
981+
var buffer bytes.Buffer
982+
983+
buffer.Write(walletPublicKeyHash[:])
984+
985+
buffer.Write(mainUTXO.Outpoint.TransactionHash[:])
986+
binary.Write(&buffer, binary.BigEndian, mainUTXO.Outpoint.OutputIndex)
987+
binary.Write(&buffer, binary.BigEndian, mainUTXO.Value)
988+
989+
for _, wallet := range proposal.TargetWallets {
990+
buffer.Write(wallet[:])
991+
}
992+
993+
buffer.Write(proposal.MovingFundsTxFee.Bytes())
994+
995+
return sha256.Sum256(buffer.Bytes()), nil
913996
}
914997

915998
// Connect sets up the local chain.
@@ -947,6 +1030,7 @@ func ConnectWithKey(
9471030
depositSweepProposalValidations: make(map[[32]byte]bool),
9481031
pendingRedemptionRequests: make(map[[32]byte]*RedemptionRequest),
9491032
redemptionProposalValidations: make(map[[32]byte]bool),
1033+
movingFundsProposalValidations: make(map[[32]byte]bool),
9501034
heartbeatProposalValidations: make(map[[16]byte]bool),
9511035
depositRequests: make(map[[32]byte]*DepositChainRequest),
9521036
blockCounter: blockCounter,

Diff for: pkg/tbtc/moving_funds_test.go

+128
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,141 @@
11
package tbtc
22

33
import (
4+
"context"
5+
"math/big"
46
"testing"
7+
"time"
58

69
"github.com/keep-network/keep-core/internal/testutils"
710
"github.com/keep-network/keep-core/pkg/bitcoin"
811
"github.com/keep-network/keep-core/pkg/tbtc/internal/test"
12+
"github.com/keep-network/keep-core/pkg/tecdsa"
913
)
1014

15+
func TestMovingFundsAction_Execute(t *testing.T) {
16+
scenarios, err := test.LoadMovingFundsTestScenarios()
17+
if err != nil {
18+
t.Fatal(err)
19+
}
20+
21+
for _, scenario := range scenarios {
22+
t.Run(scenario.Title, func(t *testing.T) {
23+
hostChain := Connect()
24+
bitcoinChain := newLocalBitcoinChain()
25+
26+
wallet := wallet{
27+
// Set only relevant fields.
28+
publicKey: scenario.WalletPublicKey,
29+
}
30+
walletPublicKeyHash := bitcoin.PublicKeyHash(wallet.publicKey)
31+
32+
// Record the transaction that will serve as moving funds transaction's
33+
// input in the Bitcoin local chain.
34+
err := bitcoinChain.BroadcastTransaction(scenario.InputTransaction)
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
39+
// Build the moving funds proposal based on the scenario data.
40+
proposal := &MovingFundsProposal{
41+
TargetWallets: scenario.TargetWallets,
42+
MovingFundsTxFee: big.NewInt(scenario.Fee),
43+
}
44+
45+
// Choose an arbitrary start block and expiration time.
46+
proposalProcessingStartBlock := uint64(100)
47+
proposalExpiryBlock := proposalProcessingStartBlock +
48+
movingFundsProposalValidityBlocks
49+
50+
// Simulate the on-chain proposal validation passes with success.
51+
err = hostChain.setMovingFundsProposalValidationResult(
52+
walletPublicKeyHash,
53+
scenario.WalletMainUtxo,
54+
proposal,
55+
true,
56+
)
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
61+
// Record the wallet main UTXO hash and moving funds commitment
62+
// hash in the local host chain so the moving funds action can detect it.
63+
walletMainUtxoHash := hostChain.ComputeMainUtxoHash(
64+
scenario.WalletMainUtxo,
65+
)
66+
movingFundsCommitmentHash := hostChain.ComputeMovingFundsCommitmentHash(
67+
scenario.TargetWallets,
68+
)
69+
hostChain.setWallet(walletPublicKeyHash, &WalletChainData{
70+
MainUtxoHash: walletMainUtxoHash,
71+
MovingFundsTargetWalletsCommitmentHash: movingFundsCommitmentHash,
72+
})
73+
74+
// Create a signing executor mock instance.
75+
signingExecutor := newMockWalletSigningExecutor()
76+
77+
// The signature within the scenario fixture is in the format
78+
// suitable for applying them directly to a Bitcoin transaction.
79+
// However, the signing executor operates on raw tECDSA signatures
80+
// so, we need to unpack it first.
81+
rawSignature := &tecdsa.Signature{
82+
R: scenario.Signature.R,
83+
S: scenario.Signature.S,
84+
}
85+
86+
// Set up the signing executor mock to return the signature from
87+
// the test fixture when called with the expected parameters.
88+
// Note that the start block is set based on the proposal
89+
// processing start block as done within the action.
90+
signingExecutor.setSignatures(
91+
[]*big.Int{scenario.ExpectedSigHash},
92+
proposalProcessingStartBlock,
93+
[]*tecdsa.Signature{rawSignature},
94+
)
95+
96+
action := newMovingFundsAction(
97+
logger.With(),
98+
hostChain,
99+
bitcoinChain,
100+
wallet,
101+
signingExecutor,
102+
proposal,
103+
proposalProcessingStartBlock,
104+
proposalExpiryBlock,
105+
func(ctx context.Context, blockHeight uint64) error {
106+
return nil
107+
},
108+
)
109+
110+
// Modify the default parameters of the action to make
111+
// it possible to execute in the current test environment.
112+
action.broadcastCheckDelay = 1 * time.Second
113+
114+
err = action.execute()
115+
if err != nil {
116+
t.Fatal(err)
117+
}
118+
119+
// Action execution that completes without an error is a sign of
120+
// success. However, just in case, make an additional check that
121+
// the expected moving funds transaction was actually broadcasted
122+
// on the local Bitcoin chain.
123+
broadcastedMovingFundsTransaction, err := bitcoinChain.GetTransaction(
124+
scenario.ExpectedMovingFundsTransactionHash,
125+
)
126+
if err != nil {
127+
t.Fatal(err)
128+
}
129+
130+
testutils.AssertBytesEqual(
131+
t,
132+
scenario.ExpectedMovingFundsTransaction.Serialize(),
133+
broadcastedMovingFundsTransaction.Serialize(),
134+
)
135+
})
136+
}
137+
}
138+
11139
func TestAssembleMovingFundsTransaction(t *testing.T) {
12140
scenarios, err := test.LoadMovingFundsTestScenarios()
13141
if err != nil {

Diff for: pkg/tbtc/redemption_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package tbtc
22

33
import (
44
"context"
5-
"github.com/keep-network/keep-core/pkg/tecdsa"
65
"math/big"
76
"testing"
87
"time"
98

9+
"github.com/keep-network/keep-core/pkg/tecdsa"
10+
1011
"github.com/go-test/deep"
1112

1213
"github.com/keep-network/keep-core/internal/testutils"
@@ -89,7 +90,7 @@ func TestRedemptionAction_Execute(t *testing.T) {
8990
}
9091

9192
// Record the wallet main UTXO hash in the local host chain so
92-
// the deposit action can detect it.
93+
// the redemption action can detect it.
9394
var walletMainUtxoHash [32]byte
9495
if scenario.WalletMainUtxo != nil {
9596
walletMainUtxoHash = hostChain.ComputeMainUtxoHash(

0 commit comments

Comments
 (0)