From 3105d6a2ecaf28453c2baf95fdad986be75692b8 Mon Sep 17 00:00:00 2001 From: priyanshiiit Date: Sat, 23 Jul 2022 11:07:34 +0530 Subject: [PATCH] lntest: adds test for list addresses --- lntest/itest/lnd_misc_test.go | 149 ++++++++++++++++++++++++++ lntest/itest/lnd_test_list_on_test.go | 4 + lntest/itest/utils.go | 38 +++++++ 3 files changed, 191 insertions(+) diff --git a/lntest/itest/lnd_misc_test.go b/lntest/itest/lnd_misc_test.go index 9b5c365f1..8b244d9b5 100644 --- a/lntest/itest/lnd_misc_test.go +++ b/lntest/itest/lnd_misc_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/rand" + "encoding/hex" "fmt" "io/ioutil" "strings" @@ -1673,3 +1674,151 @@ func testSweepAllCoins(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("sweep attempt should fail") } } + +// testListAddresses tests that we get all the addresses and their +// corresponding balance correctly. +func testListAddresses(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // First, we'll make a new node - Alice, which will be generating + // new addresses. + alice := net.NewNode(t.t, "Alice", nil) + defer shutdownAndAssert(net, t, alice) + + // Next, we'll give Alice exactly 1 utxo of 1 BTC. + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) + + type addressDetails struct { + Balance int64 + Type walletrpc.AddressType + } + + // A map of generated address and its balance. + generatedAddr := make(map[string]addressDetails) + + // Create an address generated from internal keys. + keyLoc := &walletrpc.KeyReq{KeyFamily: 123} + keyDesc, err := alice.WalletKitClient.DeriveNextKey(ctxb, keyLoc) + require.NoError(t.t, err) + + // Hex Encode the public key. + pubkeyString := hex.EncodeToString(keyDesc.RawKeyBytes) + + // Create a p2tr address. + resp, err := alice.NewAddress(ctxb, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_TAPROOT_PUBKEY, + }) + require.NoError(t.t, err) + generatedAddr[resp.Address] = addressDetails{ + Balance: 200_000, + Type: walletrpc.AddressType_TAPROOT_PUBKEY, + } + + // Create a p2wkh address. + resp, err = alice.NewAddress(ctxb, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, + }) + require.NoError(t.t, err) + generatedAddr[resp.Address] = addressDetails{ + Balance: 300_000, + Type: walletrpc.AddressType_WITNESS_PUBKEY_HASH, + } + + // Create a np2wkh address. + resp, err = alice.NewAddress(ctxb, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_NESTED_PUBKEY_HASH, + }) + require.NoError(t.t, err) + generatedAddr[resp.Address] = addressDetails{ + Balance: 400_000, + Type: walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH, + } + + for addr, addressDetail := range generatedAddr { + _, err := alice.SendCoins(ctxb, &lnrpc.SendCoinsRequest{ + Addr: addr, + Amount: addressDetail.Balance, + SpendUnconfirmed: true, + }) + require.NoError(t.t, err) + } + + mineBlocks(t, net, 1, 3) + + // Get all the accounts except LND's custom accounts. + addressLists, err := alice.WalletKitClient.ListAddresses( + ctxb, &walletrpc.ListAddressesRequest{}, + ) + require.NoError(t.t, err) + + foundAddresses := 0 + for _, addressList := range addressLists.AccountWithAddresses { + addresses := addressList.Addresses + derivationPath, err := parseDerivationPath( + addressList.DerivationPath, + ) + require.NoError(t.t, err) + + // Should not get an account with KeyFamily - 123. + require.NotEqual( + t.t, uint32(keyLoc.KeyFamily), derivationPath[2], + ) + + for _, address := range addresses { + if _, ok := generatedAddr[address.Address]; ok { + addrDetails := generatedAddr[address.Address] + require.Equal( + t.t, addrDetails.Balance, + address.Balance, + ) + require.Equal( + t.t, addrDetails.Type, + addressList.AddressType, + ) + foundAddresses++ + } + } + } + + require.Equal(t.t, len(generatedAddr), foundAddresses) + foundAddresses = 0 + + // Get all the accounts (including LND's custom accounts). + addressLists, err = alice.WalletKitClient.ListAddresses( + ctxb, &walletrpc.ListAddressesRequest{ + ShowCustomAccounts: true, + }, + ) + require.NoError(t.t, err) + + for _, addressList := range addressLists.AccountWithAddresses { + addresses := addressList.Addresses + derivationPath, err := parseDerivationPath( + addressList.DerivationPath, + ) + require.NoError(t.t, err) + + for _, address := range addresses { + // Check if the KeyFamily in derivation path is 123. + if uint32(keyLoc.KeyFamily) == derivationPath[2] { + // For LND's custom accounts, the address + // represents the public key. + pubkey := address.Address + require.Equal(t.t, pubkeyString, pubkey) + } else if _, ok := generatedAddr[address.Address]; ok { + addrDetails := generatedAddr[address.Address] + require.Equal( + t.t, addrDetails.Balance, + address.Balance, + ) + require.Equal( + t.t, addrDetails.Type, + addressList.AddressType, + ) + foundAddresses++ + } + } + } + + require.Equal(t.t, len(generatedAddr), foundAddresses) +} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 39f6b24cd..0cf1c106e 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -12,6 +12,10 @@ var allTestCases = []*testCase{ name: "sweep coins", test: testSweepAllCoins, }, + { + name: "list addresses", + test: testListAddresses, + }, { name: "recovery info", test: testGetRecoveryInfo, diff --git a/lntest/itest/utils.go b/lntest/itest/utils.go index 9ad895c5a..d42393ee8 100644 --- a/lntest/itest/utils.go +++ b/lntest/itest/utils.go @@ -5,6 +5,8 @@ import ( "crypto/rand" "fmt" "io" + "strconv" + "strings" "time" "github.com/btcsuite/btcd/btcutil" @@ -494,6 +496,42 @@ func getOutputIndex(t *harnessTest, miner *lntest.HarnessMiner, return p2trOutputIndex } +// parseDerivationPath parses a path in the form of m/x'/y'/z'/a/b into a slice +// of [x, y, z, a, b], meaning that the apostrophe is ignored and 2^31 is _not_ +// added to the numbers. +func parseDerivationPath(path string) ([]uint32, error) { + path = strings.TrimSpace(path) + if len(path) == 0 { + return nil, fmt.Errorf("path cannot be empty") + } + if !strings.HasPrefix(path, "m/") { + return nil, fmt.Errorf("path must start with m/") + } + + // Just the root key, no path was provided. This is valid but not useful + // in most cases. + rest := strings.ReplaceAll(path, "m/", "") + if rest == "" { + return []uint32{}, nil + } + + parts := strings.Split(rest, "/") + indices := make([]uint32, len(parts)) + for i := 0; i < len(parts); i++ { + part := parts[i] + if strings.Contains(parts[i], "'") { + part = strings.TrimRight(parts[i], "'") + } + parsed, err := strconv.ParseInt(part, 10, 32) + if err != nil { + return nil, fmt.Errorf("could not parse part \"%s\": "+ + "%v", part, err) + } + indices[i] = uint32(parsed) + } + return indices, nil +} + // acceptChannel is used to accept a single channel that comes across. This // should be run in a goroutine and is used to test nodes with the zero-conf // feature bit.