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) {