From ed6b42662dd229230c74e3e92c5bcee493ddc33b Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Thu, 16 May 2024 17:09:10 +0200 Subject: [PATCH 01/17] Implemented functionalities for capturing WalletClosed events --- pkg/chain/ethereum/tbtc.go | 15 +++++++++++++++ pkg/tbtc/chain.go | 14 ++++++++++++++ pkg/tbtc/chain_test.go | 6 ++++++ pkg/tbtcpg/chain_test.go | 7 +++++++ 4 files changed, 42 insertions(+) diff --git a/pkg/chain/ethereum/tbtc.go b/pkg/chain/ethereum/tbtc.go index 3fd3ce6172..58c6058c4a 100644 --- a/pkg/chain/ethereum/tbtc.go +++ b/pkg/chain/ethereum/tbtc.go @@ -1453,6 +1453,21 @@ func (tc *TbtcChain) GetWallet( }, nil } +func (tc *TbtcChain) OnWalletClosed( + handler func(event *tbtc.WalletClosedEvent), +) subscription.EventSubscription { + onEvent := func( + walletID [32]byte, + blockNumber uint64, + ) { + handler(&tbtc.WalletClosedEvent{ + WalletID: walletID, + BlockNumber: blockNumber, + }) + } + return tc.walletRegistry.WalletClosedEvent(nil, nil).OnEvent(onEvent) +} + func (tc *TbtcChain) ComputeMainUtxoHash( mainUtxo *bitcoin.UnspentTransactionOutput, ) [32]byte { diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index 1b579e326b..2c4d25506c 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -235,6 +235,13 @@ type DKGParameters struct { ApprovePrecedencePeriodBlocks uint64 } +// WalletClosedEvent represents a wallet closed. It is emitted when the wallet +// is closed in the wallet registry. +type WalletClosedEvent struct { + WalletID [32]byte + BlockNumber uint64 +} + // BridgeChain defines the subset of the TBTC chain interface that pertains // specifically to the tBTC Bridge operations. type BridgeChain interface { @@ -242,6 +249,13 @@ type BridgeChain interface { // if the wallet was not found. GetWallet(walletPublicKeyHash [20]byte) (*WalletChainData, error) + // OnWalletClosed registers a callback that is invoked when an on-chain + // notification of the wallet closed is seen. The notification occurs when + // the wallet is closed or terminated. + OnWalletClosed( + func(event *WalletClosedEvent), + ) subscription.EventSubscription + // ComputeMainUtxoHash computes the hash of the provided main UTXO // according to the on-chain Bridge rules. ComputeMainUtxoHash(mainUtxo *bitcoin.UnspentTransactionOutput) [32]byte diff --git a/pkg/tbtc/chain_test.go b/pkg/tbtc/chain_test.go index 6e293f23dc..32e2998f54 100644 --- a/pkg/tbtc/chain_test.go +++ b/pkg/tbtc/chain_test.go @@ -868,6 +868,12 @@ func (lc *localChain) setWallet( lc.wallets[walletPublicKeyHash] = walletChainData } +func (lc *localChain) OnWalletClosed( + handler func(event *WalletClosedEvent), +) subscription.EventSubscription { + panic("unsupported") +} + func (lc *localChain) ComputeMainUtxoHash( mainUtxo *bitcoin.UnspentTransactionOutput, ) [32]byte { diff --git a/pkg/tbtcpg/chain_test.go b/pkg/tbtcpg/chain_test.go index 5ddae422df..cc0ddc48e2 100644 --- a/pkg/tbtcpg/chain_test.go +++ b/pkg/tbtcpg/chain_test.go @@ -16,6 +16,7 @@ import ( "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/chain" + "github.com/keep-network/keep-core/pkg/subscription" "github.com/keep-network/keep-core/pkg/tbtc" ) @@ -1045,6 +1046,12 @@ func (lc *LocalChain) SetWallet( lc.walletChainData[walletPublicKeyHash] = data } +func (lc *LocalChain) OnWalletClosed( + handler func(event *tbtc.WalletClosedEvent), +) subscription.EventSubscription { + panic("unsupported") +} + func (lc *LocalChain) GetWalletParameters() ( creationPeriod uint32, creationMinBtcBalance uint64, From 4e19c4d2ff41df670d33f2ec8353f0e7adea2598 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 4 Jun 2024 11:14:55 +0200 Subject: [PATCH 02/17] Added waiting for wallet closure confirmation --- pkg/chain/ethereum/tbtc.go | 15 ++++++++++ pkg/tbtc/chain.go | 2 ++ pkg/tbtc/chain_test.go | 4 +++ pkg/tbtc/node.go | 56 ++++++++++++++++++++++++++++++++++++++ pkg/tbtc/tbtc.go | 16 +++++++++++ pkg/tbtcpg/chain_test.go | 4 +++ 6 files changed, 97 insertions(+) diff --git a/pkg/chain/ethereum/tbtc.go b/pkg/chain/ethereum/tbtc.go index 58c6058c4a..a970bbff6e 100644 --- a/pkg/chain/ethereum/tbtc.go +++ b/pkg/chain/ethereum/tbtc.go @@ -1415,6 +1415,21 @@ func (tc *TbtcChain) PastNewWalletRegisteredEvents( return convertedEvents, err } +func (tc *TbtcChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { + isWalletRegistered, err := tc.walletRegistry.IsWalletRegistered( + EcdsaWalletID, + ) + if err != nil { + return false, fmt.Errorf( + "cannot check if wallet with ECDSA ID [0x%x] is registered: [%v]", + EcdsaWalletID, + err, + ) + } + + return isWalletRegistered, nil +} + func (tc *TbtcChain) GetWallet( walletPublicKeyHash [20]byte, ) (*tbtc.WalletChainData, error) { diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index 2c4d25506c..2a851d4264 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -245,6 +245,8 @@ type WalletClosedEvent struct { // BridgeChain defines the subset of the TBTC chain interface that pertains // specifically to the tBTC Bridge operations. type BridgeChain interface { + IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) + // GetWallet gets the on-chain data for the given wallet. Returns an error // if the wallet was not found. GetWallet(walletPublicKeyHash [20]byte) (*WalletChainData, error) diff --git a/pkg/tbtc/chain_test.go b/pkg/tbtc/chain_test.go index 32e2998f54..2f7f57e5dc 100644 --- a/pkg/tbtc/chain_test.go +++ b/pkg/tbtc/chain_test.go @@ -843,6 +843,10 @@ func buildDepositRequestKey( return sha256.Sum256(append(fundingTxHash[:], buffer...)) } +func (lc *localChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { + panic("unsupported") +} + func (lc *localChain) GetWallet(walletPublicKeyHash [20]byte) ( *WalletChainData, error, diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index af0c37293f..66574d83bc 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -8,6 +8,7 @@ import ( "math/big" "sync" + "github.com/keep-network/keep-common/pkg/chain/ethereum" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/chain" @@ -38,6 +39,12 @@ const ( // Moreover, the signature must be produced in the reasonable time. // That being said, the value `5` seems to be reasonable trade-off. signingAttemptsLimit = 5 + + // walletClosureConfirmationBlocks determines the period used when waiting + // for the wallet closure confirmation. This period ensures the wallet has + // been definitely closed and the closing transaction will not be removed by + // a chain reorganization. + walletClosureConfirmationBlocks = 32 ) // TODO: Unit tests for `node.go`. @@ -1075,6 +1082,55 @@ func processCoordinationResult(node *node, result *coordinationResult) { } } +// handleWalletClosure handles the wallet termination or closing process. +func (n *node) handleWalletClosure(walletID [32]byte) error { + blockCounter, err := n.chain.BlockCounter() + if err != nil { + return fmt.Errorf("error getting block counter [%w]", err) + } + + currentBlock, err := blockCounter.CurrentBlock() + if err != nil { + return fmt.Errorf("error getting current block [%w]", err) + } + + // To verify there was no chain reorg and the wallet is really closed check + // if it is registered. Both terminated and closed wallets are removed + // from the ECDSA registry. + stateCheck := func() (bool, error) { + isRegistered, err := n.chain.IsWalletRegistered(walletID) + if err != nil { + return false, err + } + + return !isRegistered, nil + } + + // Wait a significant number of blocks to make sure the transaction has not + // been reverted for some reason, e.g. due to a chain reorganization. + result, err := ethereum.WaitForBlockConfirmations( + blockCounter, + currentBlock, + walletClosureConfirmationBlocks, + stateCheck, + ) + if err != nil { + return fmt.Errorf( + "error while waiting for wallet closure confirmation [%w]", + err, + ) + } + + if !result { + return fmt.Errorf("wallet closure not confirmed") + } + + // TODO: Continue with wallet closure handling: save key material and remove + // the wallet from coordination mechanism. + + return nil +} + // waitForBlockFn represents a function blocking the execution until the given // block height. type waitForBlockFn func(context.Context, uint64) error diff --git a/pkg/tbtc/tbtc.go b/pkg/tbtc/tbtc.go index e90356b2e9..17fb285a99 100644 --- a/pkg/tbtc/tbtc.go +++ b/pkg/tbtc/tbtc.go @@ -264,6 +264,22 @@ func Initialize( }() }) + _ = chain.OnWalletClosed(func(event *WalletClosedEvent) { + go func() { + // TODO: Most likely event deduplication is needed. + + logger.Infof( + "Wallet with ID [0x%x] has been closed at block [%v]", + event.WalletID, + event.BlockNumber, + ) + + node.handleWalletClosure( + event.WalletID, + ) + }() + }) + return nil } diff --git a/pkg/tbtcpg/chain_test.go b/pkg/tbtcpg/chain_test.go index cc0ddc48e2..a87ba98f56 100644 --- a/pkg/tbtcpg/chain_test.go +++ b/pkg/tbtcpg/chain_test.go @@ -1020,6 +1020,10 @@ func (lc *LocalChain) SetAverageBlockTime(averageBlockTime time.Duration) { lc.averageBlockTime = averageBlockTime } +func (lc *LocalChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { + panic("unsupported") +} + func (lc *LocalChain) GetWallet(walletPublicKeyHash [20]byte) ( *tbtc.WalletChainData, error, From accb4b1190d4eb5e751a3070bf582874b1e6ccd3 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Thu, 6 Jun 2024 16:42:42 +0200 Subject: [PATCH 03/17] Added wallet archiving on wallet closed event --- pkg/tbtc/chain.go | 8 +++++++ pkg/tbtc/chain_test.go | 6 +++++ pkg/tbtc/node.go | 28 +++++++++++++++++++++-- pkg/tbtc/registry.go | 52 ++++++++++++++++++++++++++++++++++++++++++ pkg/tbtc/tbtc.go | 9 +++++++- 5 files changed, 100 insertions(+), 3 deletions(-) diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index 2a851d4264..9eaa645b86 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -262,6 +262,14 @@ type BridgeChain interface { // according to the on-chain Bridge rules. ComputeMainUtxoHash(mainUtxo *bitcoin.UnspentTransactionOutput) [32]byte + // PastNewWalletRegisteredEvents fetches past new wallet registered events + // according to the provided filter or unfiltered if the filter is nil. + // Returned events are sorted by the block number in the ascending order, + // i.e. the latest event is at the end of the slice. + PastNewWalletRegisteredEvents( + filter *NewWalletRegisteredEventFilter, + ) ([]*NewWalletRegisteredEvent, error) + // PastDepositRevealedEvents fetches past deposit reveal events according // to the provided filter or unfiltered if the filter is nil. Returned // events are sorted by the block number in the ascending order, i.e. the diff --git a/pkg/tbtc/chain_test.go b/pkg/tbtc/chain_test.go index 2f7f57e5dc..64effd2b3b 100644 --- a/pkg/tbtc/chain_test.go +++ b/pkg/tbtc/chain_test.go @@ -697,6 +697,12 @@ func (lc *localChain) GetInactivityClaimNonce(walletID [32]byte) (*big.Int, erro return big.NewInt(int64(nonce)), nil } +func (lc *localChain) PastNewWalletRegisteredEvents( + filter *NewWalletRegisteredEventFilter, +) ([]*NewWalletRegisteredEvent, error) { + panic("unsupported") +} + func (lc *localChain) PastDepositRevealedEvents( filter *DepositRevealedEventFilter, ) ([]*DepositRevealedEvent, error) { diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index 66574d83bc..1234449f9d 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -1125,8 +1125,32 @@ func (n *node) handleWalletClosure(walletID [32]byte) error { return fmt.Errorf("wallet closure not confirmed") } - // TODO: Continue with wallet closure handling: save key material and remove - // the wallet from coordination mechanism. + events, err := n.chain.PastNewWalletRegisteredEvents( + &NewWalletRegisteredEventFilter{ + EcdsaWalletID: [][32]byte{walletID}, + }, + ) + if err != nil { + return fmt.Errorf("could not get past new wallet registered events") + } + + // There should be only one event returned and the ECDSA wallet ID should + // match the requested wallet ID. These errors should never happen, but + // check just in case. + if len(events) != 1 { + return fmt.Errorf("wrong number of past new wallet registered events") + } + + if events[0].EcdsaWalletID != walletID { + return fmt.Errorf("wrong past new wallet registered event returned") + } + + walletPublicKeyHash := events[0].WalletPublicKeyHash + + err = n.walletRegistry.archiveWallet(walletPublicKeyHash) + if err != nil { + return fmt.Errorf("failed to archive the wallet: [%v]", err) + } return nil } diff --git a/pkg/tbtc/registry.go b/pkg/tbtc/registry.go index 80334f7475..6f469862de 100644 --- a/pkg/tbtc/registry.go +++ b/pkg/tbtc/registry.go @@ -156,6 +156,45 @@ func (wr *walletRegistry) getWalletByPublicKeyHash( return wallet{}, false } +func (wr *walletRegistry) archiveWallet( + walletPublicKeyHash [20]byte, +) error { + wr.mutex.Lock() + defer wr.mutex.Unlock() + + var walletPublicKey *ecdsa.PublicKey + + for _, value := range wr.walletCache { + if value.walletPublicKeyHash == walletPublicKeyHash { + // All signers belong to one wallet. Take the wallet public key from + // the first signer. + walletPublicKey = value.signers[0].wallet.publicKey + } + } + + if walletPublicKey == nil { + logger.Infof( + "node does not control wallet with public key hash [0x%x]; "+ + "quitting wallet archiving", + walletPublicKeyHash, + ) + return nil + } + + walletStorageKey := getWalletStorageKey(walletPublicKey) + + // Archive the entire wallet storage. + err := wr.walletStorage.archiveWallet(walletStorageKey) + if err != nil { + return fmt.Errorf("could not archive wallet: [%v]", err) + } + + // Remove the wallet from the wallet cache. + delete(wr.walletCache, walletStorageKey) + + return nil +} + // walletStorage is the component that persists data of the wallets managed // by the given node using the underlying persistence layer. It should be // used directly only by the walletRegistry. @@ -194,6 +233,19 @@ func (ws *walletStorage) saveSigner(signer *signer) error { return nil } +func (ws *walletStorage) archiveWallet(walletStoragePath string) error { + err := ws.persistence.Archive(walletStoragePath) + if err != nil { + return fmt.Errorf( + "could not archive wallet storage using the "+ + "underlying persistence layer: [%w]", + err, + ) + } + + return nil +} + // loadSigners loads all signers stored using the underlying persistence layer. // This function should not be called from any other place than walletRegistry. func (ws *walletStorage) loadSigners() map[string][]*signer { diff --git a/pkg/tbtc/tbtc.go b/pkg/tbtc/tbtc.go index 17fb285a99..937851e98a 100644 --- a/pkg/tbtc/tbtc.go +++ b/pkg/tbtc/tbtc.go @@ -274,9 +274,16 @@ func Initialize( event.BlockNumber, ) - node.handleWalletClosure( + err := node.handleWalletClosure( event.WalletID, ) + if err != nil { + logger.Errorf( + "Failure while handling wallet with ID [0x%x]: [%v]", + event.WalletID, + err, + ) + } }() }) From 2e83ac477af9b90345d8806c7af69610f8572f57 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Fri, 7 Jun 2024 12:38:57 +0200 Subject: [PATCH 04/17] Added wallet archiving on node start --- pkg/tbtc/node.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index 1234449f9d..a29facff2c 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -144,12 +144,19 @@ func newNode( proposalGenerator: proposalGenerator, } + // Archive any wallets that might have been closed or terminated while the + // client was turned off. + err := node.archiveClosedWallets() + if err != nil { + return nil, fmt.Errorf("cannot archive closed wallets: [%v]", err) + } + // Only the operator address is known at this point and can be pre-fetched. // The operator ID must be determined later as the operator may not be in // the sortition pool yet. operatorAddress, err := node.operatorAddress() if err != nil { - return nil, fmt.Errorf("cannot get node's operator adress: [%v]", err) + return nil, fmt.Errorf("cannot get node's operator address: [%v]", err) } // TODO: This chicken and egg problem should be solved when @@ -1082,6 +1089,98 @@ func processCoordinationResult(node *node, result *coordinationResult) { } } +func (n *node) archiveClosedWallets() error { + getClosedWallets := func(walletPublicKeyHashes [][20]byte) ( + closedWallets [][20]byte, + ) { + for _, walletPublicKeyHash := range walletPublicKeyHashes { + walletChainData, err := n.chain.GetWallet(walletPublicKeyHash) + if err != nil { + // Continue if there was an error getting wallet data. Try to + // get as many closed wallets as possible. + logger.Errorf( + "could not get wallet data for wallet [0x%x]: [%v]", + walletPublicKeyHash, + err, + ) + continue + } + + if walletChainData.State == StateClosed || + walletChainData.State == StateTerminated { + closedWallets = append(closedWallets, walletPublicKeyHash) + } + } + + return + } + + // Get all the wallets controlled by the node. + walletPublicKeys := n.walletRegistry.getWalletsPublicKeys() + + walletPublicKeyHashes := [][20]byte{} + for _, walletPublicKey := range walletPublicKeys { + walletPublicKeyHashes = append( + walletPublicKeyHashes, + bitcoin.PublicKeyHash(walletPublicKey), + ) + } + + // Find the wallets that are closed. + initialClosedWallets := getClosedWallets(walletPublicKeyHashes) + if len(initialClosedWallets) == 0 { + logger.Infof("there are no wallets to be archived") + return nil + } + + // Wait a significant number of blocks to make sure the transactions that + // caused the wallets to be closed have not been reverted for some reason, + // e.g. due to a chain reorganization. + ctx := context.Background() + + blockCounter, err := n.chain.BlockCounter() + if err != nil { + return fmt.Errorf("error getting block counter [%v]", err) + } + + currentBlock, err := blockCounter.CurrentBlock() + if err != nil { + return fmt.Errorf("error getting current block [%v]", err) + } + + confirmationBlock := currentBlock + walletClosureConfirmationBlocks + + err = n.waitForBlockHeight(ctx, confirmationBlock) + if err != nil { + return fmt.Errorf( + "error while waiting for confirmation block [%v]", + err, + ) + } + + // Filter the closed wallets again. + finalClosedWallets := getClosedWallets(initialClosedWallets) + + // Archive the closed wallets. + for _, walletPublicKeyHash := range finalClosedWallets { + err := n.walletRegistry.archiveWallet(walletPublicKeyHash) + if err != nil { + logger.Errorf( + "could not archive wallet with public key hash [0x%x]: [%v]", + walletPublicKeyHash, + err, + ) + } else { + logger.Infof( + "successfully archived wallet with public key hash [0x%x]", + walletPublicKeyHash, + ) + } + } + + return nil +} + // handleWalletClosure handles the wallet termination or closing process. func (n *node) handleWalletClosure(walletID [32]byte) error { blockCounter, err := n.chain.BlockCounter() From 5a27be6a23a9cddebffde39239c4bb57604bfe2f Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Fri, 7 Jun 2024 16:16:48 +0200 Subject: [PATCH 05/17] Added function descriptions --- pkg/tbtc/chain.go | 2 ++ pkg/tbtc/node.go | 1 + pkg/tbtc/registry.go | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index 9eaa645b86..d8bfec302e 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -245,6 +245,8 @@ type WalletClosedEvent struct { // BridgeChain defines the subset of the TBTC chain interface that pertains // specifically to the tBTC Bridge operations. type BridgeChain interface { + // IsWalletRegistered checks whether the given wallet is registered in the + // ECDSA wallet registry. IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) // GetWallet gets the on-chain data for the given wallet. Returns an error diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index a29facff2c..a4346397e5 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -1089,6 +1089,7 @@ func processCoordinationResult(node *node, result *coordinationResult) { } } +// archiveClosedWallets archives closed or terminated wallets. func (n *node) archiveClosedWallets() error { getClosedWallets := func(walletPublicKeyHashes [][20]byte) ( closedWallets [][20]byte, diff --git a/pkg/tbtc/registry.go b/pkg/tbtc/registry.go index 6f469862de..4c81938de9 100644 --- a/pkg/tbtc/registry.go +++ b/pkg/tbtc/registry.go @@ -156,6 +156,9 @@ func (wr *walletRegistry) getWalletByPublicKeyHash( return wallet{}, false } +// archiveWallet archives the wallet with the given public key hash. The wallet +// data is removed from the wallet cache and the entire wallet storage directory +// is moved to the archive directory. func (wr *walletRegistry) archiveWallet( walletPublicKeyHash [20]byte, ) error { @@ -233,6 +236,8 @@ func (ws *walletStorage) saveSigner(signer *signer) error { return nil } +// archiveWallet archives the given wallet data in the underlying persistence +// layer of the walletStorage. func (ws *walletStorage) archiveWallet(walletStoragePath string) error { err := ws.persistence.Archive(walletStoragePath) if err != nil { From 73da53d4a9f54599965731e183eaa855dee270b5 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Mon, 10 Jun 2024 08:59:17 +0200 Subject: [PATCH 06/17] Added deduplication of WalletClosed events --- pkg/tbtc/deduplicator.go | 26 +++++++++++++++++++++ pkg/tbtc/deduplicator_test.go | 43 +++++++++++++++++++++++++++++++++-- pkg/tbtc/tbtc.go | 12 +++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/pkg/tbtc/deduplicator.go b/pkg/tbtc/deduplicator.go index a674cea370..be16d990d0 100644 --- a/pkg/tbtc/deduplicator.go +++ b/pkg/tbtc/deduplicator.go @@ -16,6 +16,9 @@ const ( // DKGResultHashCachePeriod is the time period the cache maintains // the given DKG result hash. DKGResultHashCachePeriod = 7 * 24 * time.Hour + // WalletClosedCachePeriod is the time period the cache maintains + // the given wallet closed hash. + WalletClosedCachePeriod = 7 * 24 * time.Hour ) // deduplicator decides whether the given event should be handled by the @@ -31,15 +34,18 @@ const ( // Those events are supported: // - DKG started // - DKG result submitted +// - Wallet closed type deduplicator struct { dkgSeedCache *cache.TimeCache dkgResultHashCache *cache.TimeCache + walletClosedCache *cache.TimeCache } func newDeduplicator() *deduplicator { return &deduplicator{ dkgSeedCache: cache.NewTimeCache(DKGSeedCachePeriod), dkgResultHashCache: cache.NewTimeCache(DKGResultHashCachePeriod), + walletClosedCache: cache.NewTimeCache(WalletClosedCachePeriod), } } @@ -90,3 +96,23 @@ func (d *deduplicator) notifyDKGResultSubmitted( // proceed with the execution. return false } + +func (d *deduplicator) notifyWalletClosed( + WalletID [32]byte, +) bool { + d.walletClosedCache.Sweep() + + // Use wallet ID converted to string as the cache key. + cacheKey := hex.EncodeToString(WalletID[:]) + + // If the key is not in the cache, that means the wallet closure was not + // handled yet and the client should proceed with the execution. + if !d.walletClosedCache.Has(cacheKey) { + d.walletClosedCache.Add(cacheKey) + return true + } + + // Otherwise, the wallet closure is a duplicate and the client should not + // proceed with the execution. + return false +} diff --git a/pkg/tbtc/deduplicator_test.go b/pkg/tbtc/deduplicator_test.go index 4fdeb5ec7c..b75432a8c0 100644 --- a/pkg/tbtc/deduplicator_test.go +++ b/pkg/tbtc/deduplicator_test.go @@ -9,8 +9,11 @@ import ( "github.com/keep-network/keep-common/pkg/cache" ) -const testDKGSeedCachePeriod = 1 * time.Second -const testDKGResultHashCachePeriod = 1 * time.Second +const ( + testDKGSeedCachePeriod = 1 * time.Second + testDKGResultHashCachePeriod = 1 * time.Second + testWalletClosedCachePeriod = 1 * time.Second +) func TestNotifyDKGStarted(t *testing.T) { deduplicator := deduplicator{ @@ -112,3 +115,39 @@ func TestNotifyDKGResultSubmitted(t *testing.T) { t.Fatal("should be allowed to process") } } + +func TestNotifyWalletClosed(t *testing.T) { + deduplicator := deduplicator{ + walletClosedCache: cache.NewTimeCache(testWalletClosedCachePeriod), + } + + wallet1 := [32]byte{1} + wallet2 := [32]byte{2} + + // Add the first wallet ID. + canProcess := deduplicator.notifyWalletClosed(wallet1) + if !canProcess { + t.Fatal("should be allowed to process") + } + + // Add the second wallet ID. + canProcess = deduplicator.notifyWalletClosed(wallet2) + if !canProcess { + t.Fatal("should be allowed to process") + } + + // Add the first wallet ID before caching period elapses. + canProcess = deduplicator.notifyWalletClosed(wallet1) + if canProcess { + t.Fatal("should not be allowed to process") + } + + // Wait until caching period elapses. + time.Sleep(testWalletClosedCachePeriod) + + // Add the first wallet ID again. + canProcess = deduplicator.notifyWalletClosed(wallet1) + if !canProcess { + t.Fatal("should be allowed to process") + } +} diff --git a/pkg/tbtc/tbtc.go b/pkg/tbtc/tbtc.go index 937851e98a..a341bf24f0 100644 --- a/pkg/tbtc/tbtc.go +++ b/pkg/tbtc/tbtc.go @@ -266,7 +266,17 @@ func Initialize( _ = chain.OnWalletClosed(func(event *WalletClosedEvent) { go func() { - // TODO: Most likely event deduplication is needed. + if ok := deduplicator.notifyWalletClosed( + event.WalletID, + ); !ok { + logger.Warnf( + "Wallet closure for wallet with ID [0x%x] at block [%v] has"+ + "been already processed", + event.WalletID, + event.BlockNumber, + ) + return + } logger.Infof( "Wallet with ID [0x%x] has been closed at block [%v]", From c0d6f925dd6f12710836de4f62686ef2cd736c07 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Mon, 10 Jun 2024 09:01:31 +0200 Subject: [PATCH 07/17] Added unit tests for wallet registry and storage --- pkg/tbtc/registry_test.go | 117 +++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/pkg/tbtc/registry_test.go b/pkg/tbtc/registry_test.go index b0e81c14d8..ac4682587b 100644 --- a/pkg/tbtc/registry_test.go +++ b/pkg/tbtc/registry_test.go @@ -205,6 +205,83 @@ func TestWalletRegistry_GetWalletsPublicKeys(t *testing.T) { ) } +func TestWalletRegistry_ArchiveWallet(t *testing.T) { + persistenceHandle := &mockPersistenceHandle{} + + walletRegistry := newWalletRegistry(persistenceHandle) + + signer := createMockSigner(t) + + walletStorageKey := getWalletStorageKey(signer.wallet.publicKey) + walletPublicKeyHash := bitcoin.PublicKeyHash(signer.wallet.publicKey) + + err := walletRegistry.registerSigner(signer) + if err != nil { + t.Fatal(err) + } + + err = walletRegistry.archiveWallet(walletPublicKeyHash) + if err != nil { + t.Fatal(err) + } + + testutils.AssertIntsEqual( + t, + "registered wallets count", + 0, + len(walletRegistry.walletCache), + ) + + testutils.AssertIntsEqual( + t, + "archived wallets count", + 1, + len(persistenceHandle.archived), + ) + + testutils.AssertStringsEqual( + t, + "archived wallet", + walletStorageKey, + persistenceHandle.archived[0], + ) +} + +func TestWalletRegistry_ArchiveWallet_NotFound(t *testing.T) { + persistenceHandle := &mockPersistenceHandle{} + + walletRegistry := newWalletRegistry(persistenceHandle) + + signer := createMockSigner(t) + + err := walletRegistry.registerSigner(signer) + if err != nil { + t.Fatal(err) + } + + // Public key hash of a wallet that does not exist in the registry. + anotherWalletPublicKeyHash := [20]byte{1, 1, 2, 2, 3, 3} + + err = walletRegistry.archiveWallet(anotherWalletPublicKeyHash) + if err != nil { + t.Fatal(err) + } + + testutils.AssertIntsEqual( + t, + "registered wallets count", + 1, + len(walletRegistry.walletCache), + ) + + testutils.AssertIntsEqual( + t, + "archived wallets count", + 0, + len(persistenceHandle.archived), + ) +} + func TestWalletStorage_SaveSigner(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} @@ -267,8 +344,43 @@ func TestWalletStorage_LoadSigners(t *testing.T) { } } +func TestWalletStorage_ArchiveWallet(t *testing.T) { + persistenceHandle := &mockPersistenceHandle{} + + walletStorage := newWalletStorage(persistenceHandle) + + signer := createMockSigner(t) + + err := walletStorage.saveSigner(signer) + if err != nil { + t.Fatal(err) + } + + walletStorageKey := getWalletStorageKey(signer.wallet.publicKey) + + err = walletStorage.archiveWallet(walletStorageKey) + if err != nil { + t.Fatal(err) + } + + testutils.AssertIntsEqual( + t, + "archived wallets count", + 1, + len(persistenceHandle.archived), + ) + + testutils.AssertStringsEqual( + t, + "archived wallet", + walletStorageKey, + persistenceHandle.archived[0], + ) +} + type mockPersistenceHandle struct { - saved []persistence.DataDescriptor + saved []persistence.DataDescriptor + archived []string } func (mph *mockPersistenceHandle) Save( @@ -311,7 +423,8 @@ func (mph *mockPersistenceHandle) ReadAll() ( } func (mph *mockPersistenceHandle) Archive(directory string) error { - panic("not implemented") + mph.archived = append(mph.archived, directory) + return nil } func (mph *mockPersistenceHandle) Delete(directory string, name string) error { From cddb143a2e4ea1e1a6dc58b28466a751e6f4edb8 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Mon, 10 Jun 2024 10:42:22 +0200 Subject: [PATCH 08/17] Improved logs and descriptions --- pkg/tbtc/chain.go | 4 ++-- pkg/tbtc/deduplicator.go | 4 ++-- pkg/tbtc/tbtc.go | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index d8bfec302e..ad030b5bae 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -235,8 +235,8 @@ type DKGParameters struct { ApprovePrecedencePeriodBlocks uint64 } -// WalletClosedEvent represents a wallet closed. It is emitted when the wallet -// is closed in the wallet registry. +// WalletClosedEvent represents a wallet closed event. It is emitted when the +// wallet is closed in the wallet registry. type WalletClosedEvent struct { WalletID [32]byte BlockNumber uint64 diff --git a/pkg/tbtc/deduplicator.go b/pkg/tbtc/deduplicator.go index be16d990d0..37d0b1704f 100644 --- a/pkg/tbtc/deduplicator.go +++ b/pkg/tbtc/deduplicator.go @@ -16,8 +16,8 @@ const ( // DKGResultHashCachePeriod is the time period the cache maintains // the given DKG result hash. DKGResultHashCachePeriod = 7 * 24 * time.Hour - // WalletClosedCachePeriod is the time period the cache maintains - // the given wallet closed hash. + // WalletClosedCachePeriod is the time period the cache maintains the ID of + // a closed wallet. WalletClosedCachePeriod = 7 * 24 * time.Hour ) diff --git a/pkg/tbtc/tbtc.go b/pkg/tbtc/tbtc.go index a341bf24f0..6f6414ba35 100644 --- a/pkg/tbtc/tbtc.go +++ b/pkg/tbtc/tbtc.go @@ -270,8 +270,8 @@ func Initialize( event.WalletID, ); !ok { logger.Warnf( - "Wallet closure for wallet with ID [0x%x] at block [%v] has"+ - "been already processed", + "Wallet closure for wallet with ID [0x%x] at block [%v] "+ + "has been already processed", event.WalletID, event.BlockNumber, ) @@ -279,7 +279,8 @@ func Initialize( } logger.Infof( - "Wallet with ID [0x%x] has been closed at block [%v]", + "Wallet with ID [0x%x] has been closed at block [%v]; "+ + "proceeding with handling wallet closure", event.WalletID, event.BlockNumber, ) @@ -289,7 +290,7 @@ func Initialize( ) if err != nil { logger.Errorf( - "Failure while handling wallet with ID [0x%x]: [%v]", + "Failure while handling wallet closure with ID [0x%x]: [%v]", event.WalletID, err, ) From 740721253cad39bc9d76c872ddfdc55a98304518 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Mon, 10 Jun 2024 11:22:42 +0200 Subject: [PATCH 09/17] Renamed variable --- pkg/tbtc/registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/tbtc/registry.go b/pkg/tbtc/registry.go index 4c81938de9..b6b2381ee4 100644 --- a/pkg/tbtc/registry.go +++ b/pkg/tbtc/registry.go @@ -238,8 +238,8 @@ func (ws *walletStorage) saveSigner(signer *signer) error { // archiveWallet archives the given wallet data in the underlying persistence // layer of the walletStorage. -func (ws *walletStorage) archiveWallet(walletStoragePath string) error { - err := ws.persistence.Archive(walletStoragePath) +func (ws *walletStorage) archiveWallet(walletStorageKey string) error { + err := ws.persistence.Archive(walletStorageKey) if err != nil { return fmt.Errorf( "could not archive wallet storage using the "+ From 810740a5c5fd0bfa03755e2214fac28d5170e022 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Mon, 10 Jun 2024 13:46:05 +0200 Subject: [PATCH 10/17] Exposed function for calculating wallet ID --- pkg/chain/ethereum/tbtc.go | 18 ++++++++++++++ pkg/chain/ethereum/tbtc_test.go | 44 +++++++++++++++++++++++++++++++++ pkg/tbtc/chain.go | 4 +++ pkg/tbtc/chain_test.go | 6 +++++ pkg/tbtcpg/chain_test.go | 7 ++++++ 5 files changed, 79 insertions(+) diff --git a/pkg/chain/ethereum/tbtc.go b/pkg/chain/ethereum/tbtc.go index a970bbff6e..ec5c29d40f 100644 --- a/pkg/chain/ethereum/tbtc.go +++ b/pkg/chain/ethereum/tbtc.go @@ -1415,6 +1415,24 @@ func (tc *TbtcChain) PastNewWalletRegisteredEvents( return convertedEvents, err } +func (tc *TbtcChain) CalculateWalletID( + walletPublicKey *ecdsa.PublicKey, +) ([32]byte, error) { + return calculateWalletID(walletPublicKey) +} + +func calculateWalletID(walletPublicKey *ecdsa.PublicKey) ([32]byte, error) { + walletPublicKeyBytes, err := convertPubKeyToChainFormat(walletPublicKey) + if err != nil { + return [32]byte{}, fmt.Errorf( + "error while converting wallet public key to chain format: [%v]", + err, + ) + } + + return crypto.Keccak256Hash(walletPublicKeyBytes[:]), nil +} + func (tc *TbtcChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { isWalletRegistered, err := tc.walletRegistry.IsWalletRegistered( EcdsaWalletID, diff --git a/pkg/chain/ethereum/tbtc_test.go b/pkg/chain/ethereum/tbtc_test.go index 996e06a199..1c9eef1be0 100644 --- a/pkg/chain/ethereum/tbtc_test.go +++ b/pkg/chain/ethereum/tbtc_test.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/keep-network/keep-core/internal/testutils" + "github.com/keep-network/keep-core/pkg/chain/local_v1" "github.com/keep-network/keep-core/pkg/protocol/group" ) @@ -279,6 +280,49 @@ func TestCalculateInactivityClaimHash(t *testing.T) { ) } +func TestCalculateWalletID(t *testing.T) { + hexToByte32 := func(hexStr string) [32]byte { + if len(hexStr) != 64 { + t.Fatal("hex string length incorrect") + } + + decoded, err := hex.DecodeString(hexStr) + if err != nil { + t.Fatal(err) + } + + var result [32]byte + copy(result[:], decoded) + + return result + } + + xBytes := hexToByte32( + "9a0544440cc47779235ccb76d669590c2cd20c7e431f97e17a1093faf03291c4", + ) + + yBytes := hexToByte32( + "73e661a208a8a565ca1e384059bd2ff7ff6886df081ff1229250099d388c83df", + ) + + walletPublicKey := &ecdsa.PublicKey{ + Curve: local_v1.DefaultCurve, + X: new(big.Int).SetBytes(xBytes[:]), + Y: new(big.Int).SetBytes(yBytes[:]), + } + + actualWalletID, err := calculateWalletID(walletPublicKey) + if err != nil { + t.Fatal(err) + } + + expectedWalletID := hexToByte32( + "a6602e554b8cf7c23538fd040e4ff3520ec680e5e5ce9a075259e613a3e5aa79", + ) + + testutils.AssertBytesEqual(t, expectedWalletID[:], actualWalletID[:]) +} + func TestParseDkgResultValidationOutcome(t *testing.T) { isValid, err := parseDkgResultValidationOutcome( &struct { diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index ad030b5bae..1e9c51ed40 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -245,6 +245,10 @@ type WalletClosedEvent struct { // BridgeChain defines the subset of the TBTC chain interface that pertains // specifically to the tBTC Bridge operations. type BridgeChain interface { + // CalculateWalletID calculates the wallet's ECDSA ID based on the provided + // wallet public key. + CalculateWalletID(walletPublicKey *ecdsa.PublicKey) ([32]byte, error) + // IsWalletRegistered checks whether the given wallet is registered in the // ECDSA wallet registry. IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) diff --git a/pkg/tbtc/chain_test.go b/pkg/tbtc/chain_test.go index 64effd2b3b..a2a6921b8a 100644 --- a/pkg/tbtc/chain_test.go +++ b/pkg/tbtc/chain_test.go @@ -853,6 +853,12 @@ func (lc *localChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { panic("unsupported") } +func (lc *localChain) CalculateWalletID( + walletPublicKey *ecdsa.PublicKey, +) ([32]byte, error) { + panic("unsupported") +} + func (lc *localChain) GetWallet(walletPublicKeyHash [20]byte) ( *WalletChainData, error, diff --git a/pkg/tbtcpg/chain_test.go b/pkg/tbtcpg/chain_test.go index a87ba98f56..52f6ef4137 100644 --- a/pkg/tbtcpg/chain_test.go +++ b/pkg/tbtcpg/chain_test.go @@ -3,6 +3,7 @@ package tbtcpg import ( "bytes" "context" + "crypto/ecdsa" "crypto/sha256" "encoding/binary" "encoding/hex" @@ -1024,6 +1025,12 @@ func (lc *LocalChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { panic("unsupported") } +func (lc *LocalChain) CalculateWalletID( + walletPublicKey *ecdsa.PublicKey, +) ([32]byte, error) { + panic("unsupported") +} + func (lc *LocalChain) GetWallet(walletPublicKeyHash [20]byte) ( *tbtc.WalletChainData, error, From 442905743cf198b3ee3a602ad180951898432acf Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Mon, 10 Jun 2024 17:53:35 +0200 Subject: [PATCH 11/17] Simplified archiving closed wallets --- pkg/tbtc/node.go | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index a4346397e5..f0b34ab64d 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -1128,45 +1128,17 @@ func (n *node) archiveClosedWallets() error { } // Find the wallets that are closed. - initialClosedWallets := getClosedWallets(walletPublicKeyHashes) - if len(initialClosedWallets) == 0 { - logger.Infof("there are no wallets to be archived") + closedWallets := getClosedWallets(walletPublicKeyHashes) + if len(closedWallets) == 0 { + logger.Infof("there are no closed wallets to archive") return nil } - // Wait a significant number of blocks to make sure the transactions that - // caused the wallets to be closed have not been reverted for some reason, - // e.g. due to a chain reorganization. - ctx := context.Background() - - blockCounter, err := n.chain.BlockCounter() - if err != nil { - return fmt.Errorf("error getting block counter [%v]", err) - } - - currentBlock, err := blockCounter.CurrentBlock() - if err != nil { - return fmt.Errorf("error getting current block [%v]", err) - } - - confirmationBlock := currentBlock + walletClosureConfirmationBlocks - - err = n.waitForBlockHeight(ctx, confirmationBlock) - if err != nil { - return fmt.Errorf( - "error while waiting for confirmation block [%v]", - err, - ) - } - - // Filter the closed wallets again. - finalClosedWallets := getClosedWallets(initialClosedWallets) - // Archive the closed wallets. - for _, walletPublicKeyHash := range finalClosedWallets { + for _, walletPublicKeyHash := range closedWallets { err := n.walletRegistry.archiveWallet(walletPublicKeyHash) if err != nil { - logger.Errorf( + return fmt.Errorf( "could not archive wallet with public key hash [0x%x]: [%v]", walletPublicKeyHash, err, From f0eca285537e784525335ffe2472ea3eb0608184 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 11 Jun 2024 10:35:54 +0200 Subject: [PATCH 12/17] Added function for getting wallets by ID --- pkg/tbtc/chain.go | 8 -- pkg/tbtc/chain_test.go | 40 +++++++-- pkg/tbtc/dkg_test.go | 9 +- pkg/tbtc/node.go | 47 +++++----- pkg/tbtc/registry.go | 67 +++++++++++--- pkg/tbtc/registry_test.go | 180 +++++++++++++++++++++++++++++++------- 6 files changed, 270 insertions(+), 81 deletions(-) diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index 1e9c51ed40..55206f86fb 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -268,14 +268,6 @@ type BridgeChain interface { // according to the on-chain Bridge rules. ComputeMainUtxoHash(mainUtxo *bitcoin.UnspentTransactionOutput) [32]byte - // PastNewWalletRegisteredEvents fetches past new wallet registered events - // according to the provided filter or unfiltered if the filter is nil. - // Returned events are sorted by the block number in the ascending order, - // i.e. the latest event is at the end of the slice. - PastNewWalletRegisteredEvents( - filter *NewWalletRegisteredEventFilter, - ) ([]*NewWalletRegisteredEvent, error) - // PastDepositRevealedEvents fetches past deposit reveal events according // to the provided filter or unfiltered if the filter is nil. Returned // events are sorted by the block number in the ascending order, i.e. the diff --git a/pkg/tbtc/chain_test.go b/pkg/tbtc/chain_test.go index a2a6921b8a..d1ce5775c3 100644 --- a/pkg/tbtc/chain_test.go +++ b/pkg/tbtc/chain_test.go @@ -14,17 +14,17 @@ import ( "sync" "time" - "golang.org/x/crypto/sha3" - "github.com/ethereum/go-ethereum/crypto" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/chain" "github.com/keep-network/keep-core/pkg/chain/local_v1" + "github.com/keep-network/keep-core/pkg/internal/byteutils" "github.com/keep-network/keep-core/pkg/operator" "github.com/keep-network/keep-core/pkg/protocol/group" "github.com/keep-network/keep-core/pkg/protocol/inactivity" "github.com/keep-network/keep-core/pkg/subscription" "github.com/keep-network/keep-core/pkg/tecdsa/dkg" + "golang.org/x/crypto/sha3" ) const ( @@ -697,12 +697,6 @@ func (lc *localChain) GetInactivityClaimNonce(walletID [32]byte) (*big.Int, erro return big.NewInt(int64(nonce)), nil } -func (lc *localChain) PastNewWalletRegisteredEvents( - filter *NewWalletRegisteredEventFilter, -) ([]*NewWalletRegisteredEvent, error) { - panic("unsupported") -} - func (lc *localChain) PastDepositRevealedEvents( filter *DepositRevealedEventFilter, ) ([]*DepositRevealedEvent, error) { @@ -856,7 +850,35 @@ func (lc *localChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { func (lc *localChain) CalculateWalletID( walletPublicKey *ecdsa.PublicKey, ) ([32]byte, error) { - panic("unsupported") + walletPublicKeyBytes, err := convertPubKeyToChainFormat(walletPublicKey) + if err != nil { + return [32]byte{}, fmt.Errorf( + "error while converting wallet public key to chain format: [%v]", + err, + ) + } + + return crypto.Keccak256Hash(walletPublicKeyBytes[:]), nil +} + +func convertPubKeyToChainFormat(publicKey *ecdsa.PublicKey) ([64]byte, error) { + var serialized [64]byte + + x, err := byteutils.LeftPadTo32Bytes(publicKey.X.Bytes()) + if err != nil { + return serialized, err + } + + y, err := byteutils.LeftPadTo32Bytes(publicKey.Y.Bytes()) + if err != nil { + return serialized, err + } + + serializedBytes := append(x, y...) + + copy(serialized[:], serializedBytes) + + return serialized, nil } func (lc *localChain) GetWallet(walletPublicKeyHash [20]byte) ( diff --git a/pkg/tbtc/dkg_test.go b/pkg/tbtc/dkg_test.go index 552ad93f9f..547d1b5079 100644 --- a/pkg/tbtc/dkg_test.go +++ b/pkg/tbtc/dkg_test.go @@ -89,7 +89,14 @@ func TestDkgExecutor_RegisterSigner(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} - walletRegistry := newWalletRegistry(persistenceHandle) + chain := Connect() + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } dkgExecutor := &dkgExecutor{ // setting only the fields really needed for this test diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index f0b34ab64d..51df1ccfbe 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -124,7 +124,13 @@ func newNode( proposalGenerator CoordinationProposalGenerator, config Config, ) (*node, error) { - walletRegistry := newWalletRegistry(keyStorePersistance) + walletRegistry, err := newWalletRegistry( + keyStorePersistance, + chain.CalculateWalletID, + ) + if err != nil { + return nil, fmt.Errorf("cannot create wallet registry: [%v]", err) + } latch := generator.NewProtocolLatch() scheduler.RegisterProtocol(latch) @@ -146,7 +152,7 @@ func newNode( // Archive any wallets that might have been closed or terminated while the // client was turned off. - err := node.archiveClosedWallets() + err = node.archiveClosedWallets() if err != nil { return nil, fmt.Errorf("cannot archive closed wallets: [%v]", err) } @@ -1197,33 +1203,32 @@ func (n *node) handleWalletClosure(walletID [32]byte) error { return fmt.Errorf("wallet closure not confirmed") } - events, err := n.chain.PastNewWalletRegisteredEvents( - &NewWalletRegisteredEventFilter{ - EcdsaWalletID: [][32]byte{walletID}, - }, - ) - if err != nil { - return fmt.Errorf("could not get past new wallet registered events") - } - - // There should be only one event returned and the ECDSA wallet ID should - // match the requested wallet ID. These errors should never happen, but - // check just in case. - if len(events) != 1 { - return fmt.Errorf("wrong number of past new wallet registered events") - } - - if events[0].EcdsaWalletID != walletID { - return fmt.Errorf("wrong past new wallet registered event returned") + wallet, ok := n.walletRegistry.getWalletByID(walletID) + if !ok { + // Wallet was not found in the registry. The wallet is not controlled by + // this node. + logger.Infof( + "node does not control wallet with ID [0x%x]; quitting wallet "+ + "archiving", + walletID, + ) + return nil } - walletPublicKeyHash := events[0].WalletPublicKeyHash + walletPublicKeyHash := bitcoin.PublicKeyHash(wallet.publicKey) err = n.walletRegistry.archiveWallet(walletPublicKeyHash) if err != nil { return fmt.Errorf("failed to archive the wallet: [%v]", err) } + logger.Infof( + "Successfully archived wallet with wallet ID [0x%x] and public key "+ + "hash [0x%x]", + walletID, + walletPublicKeyHash, + ) + return nil } diff --git a/pkg/tbtc/registry.go b/pkg/tbtc/registry.go index b6b2381ee4..18472651e3 100644 --- a/pkg/tbtc/registry.go +++ b/pkg/tbtc/registry.go @@ -12,6 +12,10 @@ import ( "github.com/keep-network/keep-common/pkg/persistence" ) +// CalculateWalletIDFunc calculates the ECDSA wallet ID based on the provided +// wallet public key. +type CalculateWalletIdFunc func(walletPublicKey *ecdsa.PublicKey) ([32]byte, error) + // walletRegistry is the component that holds the data of the wallets managed // by the given node. All functions of the registry are safe for concurrent use. type walletRegistry struct { @@ -26,18 +30,28 @@ type walletRegistry struct { // walletStorage is the handle to the wallet storage responsible for // wallet persistence. walletStorage *walletStorage + + // calculateWalletIdFunc calculates the ECDSA wallet ID based on the + // provided wallet public key. + calculateWalletIdFunc CalculateWalletIdFunc } type walletCacheValue struct { // SHA-256+RIPEMD-160 hash computed over the compressed ECDSA public key of // the wallet. walletPublicKeyHash [20]byte + // ECDSA wallet ID calculated as the keccak256 of the 64-byte-long + // concatenation of the X and Y coordinates of the wallet's public key. + walletID [32]byte // Array of wallet signers controlled by this node. signers []*signer } // newWalletRegistry creates a new instance of the walletRegistry. -func newWalletRegistry(persistence persistence.ProtectedHandle) *walletRegistry { +func newWalletRegistry( + persistence persistence.ProtectedHandle, + calculateWalletIdFunc CalculateWalletIdFunc, +) (*walletRegistry, error) { walletStorage := newWalletStorage(persistence) // Pre-populate the wallet cache using the wallet storage. @@ -53,9 +67,19 @@ func newWalletRegistry(persistence persistence.ProtectedHandle) *walletRegistry // them. wallet := signers[0].wallet walletPublicKeyHash := bitcoin.PublicKeyHash(wallet.publicKey) + walletID, err := calculateWalletIdFunc(wallet.publicKey) + if err != nil { + return nil, fmt.Errorf( + "error while calculating wallet ID for wallet with public "+ + "key hash [0x%x]: [%v]", + walletPublicKeyHash, + err, + ) + } walletCache[walletStorageKey] = &walletCacheValue{ walletPublicKeyHash: walletPublicKeyHash, + walletID: walletID, signers: signers, } @@ -72,9 +96,10 @@ func newWalletRegistry(persistence persistence.ProtectedHandle) *walletRegistry } return &walletRegistry{ - walletCache: walletCache, - walletStorage: walletStorage, - } + walletCache: walletCache, + walletStorage: walletStorage, + calculateWalletIdFunc: calculateWalletIdFunc, + }, nil } // getWalletsPublicKeys returns public keys of all registered wallets. @@ -105,12 +130,18 @@ func (wr *walletRegistry) registerSigner(signer *signer) error { walletStorageKey := getWalletStorageKey(signer.wallet.publicKey) // If the wallet cache does not have the given entry yet, initialize - // the value and compute the wallet public key hash. This way, the hash - // is computed only once. No need to initialize signers slice as + // the value and compute the wallet ID and wallet public key hash. This way, + // the hashes are computed only once. No need to initialize signers slice as // appending works with nil values. if _, ok := wr.walletCache[walletStorageKey]; !ok { + walletID, err := wr.calculateWalletIdFunc(signer.wallet.publicKey) + if err != nil { + return fmt.Errorf("cannot calculate wallet ID: [%v]", err) + } + wr.walletCache[walletStorageKey] = &walletCacheValue{ walletPublicKeyHash: bitcoin.PublicKeyHash(signer.wallet.publicKey), + walletID: walletID, } } @@ -156,6 +187,23 @@ func (wr *walletRegistry) getWalletByPublicKeyHash( return wallet{}, false } +// getWalletByID gets the given wallet by its 32-byte wallet ID. Second boolean +// return value denotes whether the wallet was found in the registry or not. +func (wr *walletRegistry) getWalletByID(walletID [32]byte) (wallet, bool) { + wr.mutex.Lock() + defer wr.mutex.Unlock() + + for _, value := range wr.walletCache { + if value.walletID == walletID { + // All signers belong to one wallet. Take that wallet from the + // first signer. + return value.signers[0].wallet, true + } + } + + return wallet{}, false +} + // archiveWallet archives the wallet with the given public key hash. The wallet // data is removed from the wallet cache and the entire wallet storage directory // is moved to the archive directory. @@ -176,12 +224,7 @@ func (wr *walletRegistry) archiveWallet( } if walletPublicKey == nil { - logger.Infof( - "node does not control wallet with public key hash [0x%x]; "+ - "quitting wallet archiving", - walletPublicKeyHash, - ) - return nil + return fmt.Errorf("wallet not found in the wallet cache") } walletStorageKey := getWalletStorageKey(walletPublicKey) diff --git a/pkg/tbtc/registry_test.go b/pkg/tbtc/registry_test.go index ac4682587b..f0d4964ce1 100644 --- a/pkg/tbtc/registry_test.go +++ b/pkg/tbtc/registry_test.go @@ -2,6 +2,7 @@ package tbtc import ( "crypto/ecdsa" + "fmt" "math/big" "reflect" "testing" @@ -15,14 +16,21 @@ import ( func TestWalletRegistry_RegisterSigner(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} + chain := Connect() - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } signer := createMockSigner(t) walletStorageKey := getWalletStorageKey(signer.wallet.publicKey) - err := walletRegistry.registerSigner(signer) + err = walletRegistry.registerSigner(signer) if err != nil { t.Fatal(err) } @@ -62,12 +70,19 @@ func TestWalletRegistry_RegisterSigner(t *testing.T) { func TestWalletRegistry_GetSigners(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} + chain := Connect() - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } signer := createMockSigner(t) - err := walletRegistry.registerSigner(signer) + err = walletRegistry.registerSigner(signer) if err != nil { t.Fatal(err) } @@ -88,12 +103,19 @@ func TestWalletRegistry_GetSigners(t *testing.T) { func TestWalletRegistry_getWalletByPublicKeyHash(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} + chain := Connect() - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } signer := createMockSigner(t) - err := walletRegistry.registerSigner(signer) + err = walletRegistry.registerSigner(signer) if err != nil { t.Fatal(err) } @@ -110,12 +132,19 @@ func TestWalletRegistry_getWalletByPublicKeyHash(t *testing.T) { func TestWalletRegistry_getWalletByPublicKeyHash_NotFound(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} + chain := Connect() - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } signer := createMockSigner(t) - err := walletRegistry.registerSigner(signer) + err = walletRegistry.registerSigner(signer) if err != nil { t.Fatal(err) } @@ -134,6 +163,75 @@ func TestWalletRegistry_getWalletByPublicKeyHash_NotFound(t *testing.T) { } } +func TestWalletRegistry_getWalletByID(t *testing.T) { + persistenceHandle := &mockPersistenceHandle{} + chain := Connect() + + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } + + signer := createMockSigner(t) + + err = walletRegistry.registerSigner(signer) + if err != nil { + t.Fatal(err) + } + + // walletPublicKeyHash := bitcoin.PublicKeyHash(signer.wallet.publicKey) + walletID, err := chain.CalculateWalletID(signer.wallet.publicKey) + if err != nil { + t.Fatal(err) + } + + wallet, ok := walletRegistry.getWalletByID(walletID) + if !ok { + t.Error("should return a wallet") + } + + testutils.AssertStringsEqual(t, "wallet", signer.wallet.String(), wallet.String()) +} + +func TestWalletRegistry_getWalletByID_NotFound(t *testing.T) { + persistenceHandle := &mockPersistenceHandle{} + chain := Connect() + + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } + + signer := createMockSigner(t) + + err = walletRegistry.registerSigner(signer) + if err != nil { + t.Fatal(err) + } + + x, y := tecdsa.Curve.ScalarBaseMult(big.NewInt(100).Bytes()) + + walletID, err := chain.CalculateWalletID(&ecdsa.PublicKey{ + Curve: tecdsa.Curve, + X: x, + Y: y, + }) + if err != nil { + t.Fatal(err) + } + + _, ok := walletRegistry.getWalletByID(walletID) + if ok { + t.Error("should not return a wallet") + } +} + func TestWalletRegistry_PrePopulateWalletCache(t *testing.T) { signer := createMockSigner(t) signerBytes, err := signer.Marshal() @@ -153,8 +251,16 @@ func TestWalletRegistry_PrePopulateWalletCache(t *testing.T) { }, } + chain := Connect() + // Cache pre-population happens within newWalletRegistry. - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } testutils.AssertIntsEqual( t, @@ -184,12 +290,19 @@ func TestWalletRegistry_PrePopulateWalletCache(t *testing.T) { func TestWalletRegistry_GetWalletsPublicKeys(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} + chain := Connect() - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } signer := createMockSigner(t) - err := walletRegistry.registerSigner(signer) + err = walletRegistry.registerSigner(signer) if err != nil { t.Fatal(err) } @@ -207,15 +320,22 @@ func TestWalletRegistry_GetWalletsPublicKeys(t *testing.T) { func TestWalletRegistry_ArchiveWallet(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} + chain := Connect() - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } signer := createMockSigner(t) walletStorageKey := getWalletStorageKey(signer.wallet.publicKey) walletPublicKeyHash := bitcoin.PublicKeyHash(signer.wallet.publicKey) - err := walletRegistry.registerSigner(signer) + err = walletRegistry.registerSigner(signer) if err != nil { t.Fatal(err) } @@ -249,12 +369,19 @@ func TestWalletRegistry_ArchiveWallet(t *testing.T) { func TestWalletRegistry_ArchiveWallet_NotFound(t *testing.T) { persistenceHandle := &mockPersistenceHandle{} + chain := Connect() - walletRegistry := newWalletRegistry(persistenceHandle) + walletRegistry, err := newWalletRegistry( + persistenceHandle, + chain.CalculateWalletID, + ) + if err != nil { + t.Fatal(err) + } signer := createMockSigner(t) - err := walletRegistry.registerSigner(signer) + err = walletRegistry.registerSigner(signer) if err != nil { t.Fatal(err) } @@ -263,23 +390,16 @@ func TestWalletRegistry_ArchiveWallet_NotFound(t *testing.T) { anotherWalletPublicKeyHash := [20]byte{1, 1, 2, 2, 3, 3} err = walletRegistry.archiveWallet(anotherWalletPublicKeyHash) - if err != nil { - t.Fatal(err) - } - testutils.AssertIntsEqual( - t, - "registered wallets count", - 1, - len(walletRegistry.walletCache), - ) + expectedErr := fmt.Errorf("wallet not found in the wallet cache") - testutils.AssertIntsEqual( - t, - "archived wallets count", - 0, - len(persistenceHandle.archived), - ) + if !reflect.DeepEqual(err, expectedErr) { + t.Fatalf( + "unexpected error\nexpected: %v\nactual: %v", + expectedErr, + err, + ) + } } func TestWalletStorage_SaveSigner(t *testing.T) { From ce85a89584bdb6a6a337f65ead9f32c999d220a0 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 11 Jun 2024 10:58:35 +0200 Subject: [PATCH 13/17] Added break statement --- pkg/tbtc/registry.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/tbtc/registry.go b/pkg/tbtc/registry.go index 18472651e3..d39b56849d 100644 --- a/pkg/tbtc/registry.go +++ b/pkg/tbtc/registry.go @@ -220,6 +220,7 @@ func (wr *walletRegistry) archiveWallet( // All signers belong to one wallet. Take the wallet public key from // the first signer. walletPublicKey = value.signers[0].wallet.publicKey + break } } From 88f3bf6b3041e6f24f8b1cbcee6d17de4d6151ec Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 11 Jun 2024 11:56:10 +0200 Subject: [PATCH 14/17] Simplified wallet archiving --- pkg/tbtc/node.go | 59 +++++++++++++----------------------------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index 51df1ccfbe..6ad13f7aa2 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -1097,59 +1097,32 @@ func processCoordinationResult(node *node, result *coordinationResult) { // archiveClosedWallets archives closed or terminated wallets. func (n *node) archiveClosedWallets() error { - getClosedWallets := func(walletPublicKeyHashes [][20]byte) ( - closedWallets [][20]byte, - ) { - for _, walletPublicKeyHash := range walletPublicKeyHashes { - walletChainData, err := n.chain.GetWallet(walletPublicKeyHash) - if err != nil { - // Continue if there was an error getting wallet data. Try to - // get as many closed wallets as possible. - logger.Errorf( - "could not get wallet data for wallet [0x%x]: [%v]", - walletPublicKeyHash, - err, - ) - continue - } - - if walletChainData.State == StateClosed || - walletChainData.State == StateTerminated { - closedWallets = append(closedWallets, walletPublicKeyHash) - } - } - - return - } - // Get all the wallets controlled by the node. walletPublicKeys := n.walletRegistry.getWalletsPublicKeys() - walletPublicKeyHashes := [][20]byte{} for _, walletPublicKey := range walletPublicKeys { - walletPublicKeyHashes = append( - walletPublicKeyHashes, - bitcoin.PublicKeyHash(walletPublicKey), - ) - } - - // Find the wallets that are closed. - closedWallets := getClosedWallets(walletPublicKeyHashes) - if len(closedWallets) == 0 { - logger.Infof("there are no closed wallets to archive") - return nil - } + walletPublicKeyHash := bitcoin.PublicKeyHash(walletPublicKey) - // Archive the closed wallets. - for _, walletPublicKeyHash := range closedWallets { - err := n.walletRegistry.archiveWallet(walletPublicKeyHash) + walletChainData, err := n.chain.GetWallet(walletPublicKeyHash) if err != nil { return fmt.Errorf( - "could not archive wallet with public key hash [0x%x]: [%v]", + "could not get wallet data for wallet [0x%x]: [%v]", walletPublicKeyHash, err, ) - } else { + } + + if walletChainData.State == StateClosed || + walletChainData.State == StateTerminated { + err := n.walletRegistry.archiveWallet(walletPublicKeyHash) + if err != nil { + return fmt.Errorf( + "could not archive wallet with public key hash [0x%x]: [%v]", + walletPublicKeyHash, + err, + ) + } + logger.Infof( "successfully archived wallet with public key hash [0x%x]", walletPublicKeyHash, From ae3417830020165605c6ad72bac8036d44e07d05 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 11 Jun 2024 12:09:57 +0200 Subject: [PATCH 15/17] Used registry when checking the state of wallets --- pkg/tbtc/node.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index 6ad13f7aa2..591c4e7255 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -1103,17 +1103,29 @@ func (n *node) archiveClosedWallets() error { for _, walletPublicKey := range walletPublicKeys { walletPublicKeyHash := bitcoin.PublicKeyHash(walletPublicKey) - walletChainData, err := n.chain.GetWallet(walletPublicKeyHash) + walletID, err := n.chain.CalculateWalletID(walletPublicKey) if err != nil { return fmt.Errorf( - "could not get wallet data for wallet [0x%x]: [%v]", + "could not calculate wallet ID for wallet with public key "+ + "hash [0x%x]: [%v]", walletPublicKeyHash, err, ) } - if walletChainData.State == StateClosed || - walletChainData.State == StateTerminated { + isRegistered, err := n.chain.IsWalletRegistered(walletID) + if err != nil { + return fmt.Errorf( + "could not check if wallet is registered for wallet with ID "+ + "[0x%x]: [%v]", + walletPublicKeyHash, + err, + ) + } + + if !isRegistered { + // If the wallet is no longer registered it means the wallet has + // been closed or terminated. err := n.walletRegistry.archiveWallet(walletPublicKeyHash) if err != nil { return fmt.Errorf( @@ -1124,7 +1136,9 @@ func (n *node) archiveClosedWallets() error { } logger.Infof( - "successfully archived wallet with public key hash [0x%x]", + "successfully archived wallet with ID [0x%x] and public key "+ + "hash [0x%x]", + walletID, walletPublicKeyHash, ) } From c35ddaf26e051911c15125234fca89e7f17cef32 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 11 Jun 2024 13:05:16 +0200 Subject: [PATCH 16/17] Minor logging correction --- pkg/tbtc/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index 591c4e7255..fc9ba55b10 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -1210,7 +1210,7 @@ func (n *node) handleWalletClosure(walletID [32]byte) error { } logger.Infof( - "Successfully archived wallet with wallet ID [0x%x] and public key "+ + "successfully archived wallet with wallet ID [0x%x] and public key "+ "hash [0x%x]", walletID, walletPublicKeyHash, From 2743e6492c920f5bb48945fd3f65464bf3540ac3 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 11 Jun 2024 15:32:27 +0200 Subject: [PATCH 17/17] Set wallet data in unit tests --- pkg/tbtc/chain_test.go | 21 ++++++++++++++---- pkg/tbtc/inactivity_test.go | 10 ++++++--- pkg/tbtc/node_test.go | 43 +++++++++++++++++++++++++++++++++++++ pkg/tbtc/signing_test.go | 15 +++++++++++++ 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/pkg/tbtc/chain_test.go b/pkg/tbtc/chain_test.go index d1ce5775c3..15bb4c94ca 100644 --- a/pkg/tbtc/chain_test.go +++ b/pkg/tbtc/chain_test.go @@ -843,10 +843,6 @@ func buildDepositRequestKey( return sha256.Sum256(append(fundingTxHash[:], buffer...)) } -func (lc *localChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { - panic("unsupported") -} - func (lc *localChain) CalculateWalletID( walletPublicKey *ecdsa.PublicKey, ) ([32]byte, error) { @@ -896,6 +892,23 @@ func (lc *localChain) GetWallet(walletPublicKeyHash [20]byte) ( return walletChainData, nil } +func (lc *localChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) { + lc.walletsMutex.Lock() + defer lc.walletsMutex.Unlock() + + for _, walletData := range lc.wallets { + if EcdsaWalletID == walletData.EcdsaWalletID { + if walletData.State == StateClosed || + walletData.State == StateTerminated { + return false, nil + } + return true, nil + } + } + + return false, fmt.Errorf("wallet not found") +} + func (lc *localChain) setWallet( walletPublicKeyHash [20]byte, walletChainData *WalletChainData, diff --git a/pkg/tbtc/inactivity_test.go b/pkg/tbtc/inactivity_test.go index b1065e4607..3c8370e0d5 100644 --- a/pkg/tbtc/inactivity_test.go +++ b/pkg/tbtc/inactivity_test.go @@ -157,12 +157,16 @@ func setupInactivityClaimExecutorScenario(t *testing.T) ( keyStorePersistence := createMockKeyStorePersistence(t, signers...) walletPublicKeyHash := bitcoin.PublicKeyHash(signers[0].wallet.publicKey) - ecdsaWalletID := [32]byte{1, 2, 3} + walletID, err := localChain.CalculateWalletID(signers[0].wallet.publicKey) + if err != nil { + t.Fatal(err) + } localChain.setWallet( walletPublicKeyHash, &WalletChainData{ - EcdsaWalletID: ecdsaWalletID, + EcdsaWalletID: walletID, + State: StateLive, }, ) @@ -191,7 +195,7 @@ func setupInactivityClaimExecutorScenario(t *testing.T) ( t.Fatal("node is supposed to control wallet signers") } - return executor, ecdsaWalletID, localChain + return executor, walletID, localChain } func TestSignClaim_SigningSuccessful(t *testing.T) { diff --git a/pkg/tbtc/node_test.go b/pkg/tbtc/node_test.go index b9dbb01992..bedfb30995 100644 --- a/pkg/tbtc/node_test.go +++ b/pkg/tbtc/node_test.go @@ -11,6 +11,7 @@ import ( "github.com/keep-network/keep-common/pkg/persistence" "github.com/keep-network/keep-core/internal/testutils" + "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/chain" "github.com/keep-network/keep-core/pkg/generator" "github.com/keep-network/keep-core/pkg/internal/tecdsatest" @@ -31,6 +32,20 @@ func TestNode_GetSigningExecutor(t *testing.T) { signer := createMockSigner(t) + walletPublicKeyHash := bitcoin.PublicKeyHash(signer.wallet.publicKey) + walletID, err := localChain.CalculateWalletID(signer.wallet.publicKey) + if err != nil { + t.Fatal(err) + } + + localChain.setWallet( + walletPublicKeyHash, + &WalletChainData{ + EcdsaWalletID: walletID, + State: StateLive, + }, + ) + // Populate the mock keystore with the mock signer's data. This is // required to make the node controlling the signer's wallet. keyStorePersistence := createMockKeyStorePersistence(t, signer) @@ -149,6 +164,20 @@ func TestNode_GetCoordinationExecutor(t *testing.T) { signer := createMockSigner(t) + walletPublicKeyHash := bitcoin.PublicKeyHash(signer.wallet.publicKey) + walletID, err := localChain.CalculateWalletID(signer.wallet.publicKey) + if err != nil { + t.Fatal(err) + } + + localChain.setWallet( + walletPublicKeyHash, + &WalletChainData{ + EcdsaWalletID: walletID, + State: StateLive, + }, + ) + // Populate the mock keystore with the mock signer's data. This is // required to make the node controlling the signer's wallet. keyStorePersistence := createMockKeyStorePersistence(t, signer) @@ -272,6 +301,20 @@ func TestNode_RunCoordinationLayer(t *testing.T) { signer := createMockSigner(t) + walletPublicKeyHash := bitcoin.PublicKeyHash(signer.wallet.publicKey) + walletID, err := localChain.CalculateWalletID(signer.wallet.publicKey) + if err != nil { + t.Fatal(err) + } + + localChain.setWallet( + walletPublicKeyHash, + &WalletChainData{ + EcdsaWalletID: walletID, + State: StateLive, + }, + ) + // Populate the mock keystore with the mock signer's data. This is // required to make the node controlling the signer's wallet. keyStorePersistence := createMockKeyStorePersistence(t, signer) diff --git a/pkg/tbtc/signing_test.go b/pkg/tbtc/signing_test.go index 9bcd42ef6c..9298ad7d7f 100644 --- a/pkg/tbtc/signing_test.go +++ b/pkg/tbtc/signing_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/keep-network/keep-core/internal/testutils" + "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/chain" "github.com/keep-network/keep-core/pkg/chain/local_v1" "github.com/keep-network/keep-core/pkg/generator" @@ -159,6 +160,20 @@ func setupSigningExecutor(t *testing.T) *signingExecutor { } } + walletPublicKeyHash := bitcoin.PublicKeyHash(signers[0].wallet.publicKey) + walletID, err := localChain.CalculateWalletID(signers[0].wallet.publicKey) + if err != nil { + t.Fatal(err) + } + + localChain.setWallet( + walletPublicKeyHash, + &WalletChainData{ + EcdsaWalletID: walletID, + State: StateLive, + }, + ) + keyStorePersistence := createMockKeyStorePersistence(t, signers...) node, err := newNode(