decred.org/dcrwallet/v3@v3.1.0/wallet/addresses_test.go (about)

     1  // Copyright (c) 2018-2019 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package wallet
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/hex"
    11  	"os"
    12  	"testing"
    13  
    14  	"decred.org/dcrwallet/v3/wallet/walletdb"
    15  	"github.com/decred/dcrd/chaincfg/v3"
    16  	"github.com/decred/dcrd/dcrutil/v4"
    17  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    18  )
    19  
    20  // expectedAddr is used to house the expected return values from a managed
    21  // address.  Not all fields for used for all managed address types.
    22  type expectedAddr struct {
    23  	address     string
    24  	addressHash []byte
    25  	branch      uint32
    26  	pubKey      []byte
    27  }
    28  
    29  // testContext is used to store context information about a running test which
    30  // is passed into helper functions.
    31  type testContext struct {
    32  	t            *testing.T
    33  	account      uint32
    34  	watchingOnly bool
    35  }
    36  
    37  // hexToBytes is a wrapper around hex.DecodeString that panics if there is an
    38  // error.  It MUST only be used with hard coded values in the tests.
    39  func hexToBytes(origHex string) []byte {
    40  	buf, err := hex.DecodeString(origHex)
    41  	if err != nil {
    42  		panic(err)
    43  	}
    44  	return buf
    45  }
    46  
    47  var (
    48  	// seed is the master seed used throughout the tests.
    49  	seed = []byte{
    50  		0xb4, 0x6b, 0xc6, 0x50, 0x2a, 0x30, 0xbe, 0xb9, 0x2f,
    51  		0x0a, 0xeb, 0xc7, 0x76, 0x40, 0x3c, 0x3d, 0xbf, 0x11,
    52  		0xbf, 0xb6, 0x83, 0x05, 0x96, 0x7c, 0x36, 0xda, 0xc9,
    53  		0xef, 0x8d, 0x64, 0x15, 0x67,
    54  	}
    55  
    56  	pubPassphrase  = []byte("_DJr{fL4H0O}*-0\n:V1izc)(6BomK")
    57  	privPassphrase = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj")
    58  
    59  	walletConfig = Config{
    60  		PubPassphrase: pubPassphrase,
    61  		GapLimit:      20,
    62  		RelayFee:      dcrutil.Amount(1e5),
    63  		Params:        chaincfg.SimNetParams(),
    64  	}
    65  
    66  	defaultAccount     = uint32(0)
    67  	defaultAccountName = "default"
    68  
    69  	waddrmgrBucketKey = []byte("waddrmgr")
    70  
    71  	expectedInternalAddrs = []expectedAddr{
    72  		{
    73  			address:     "SsrFKd8aX4KHabWSQfbmEaDv3BJCpSH2ySj",
    74  			addressHash: hexToBytes("f032b89ec075ab2847e2ec186ad000be16cf354b"),
    75  			branch:      1,
    76  			pubKey:      hexToBytes("03d1ad44eeac8eb59e9598f7e530a1cbe2c1684c0aa5f45ab24d33d38a2102dd1a"),
    77  		},
    78  		{
    79  			address:     "SsW4roiFKWkbbhiAeEV5byet1pLKAP4xRks",
    80  			addressHash: hexToBytes("12d5a8e19b9a070d6d5e6e425b593c2c137285e3"),
    81  			branch:      1,
    82  			pubKey:      hexToBytes("02cbcf5c1aa84bf8e6d04412d867eccbaa6cc12ebb792f3f1eaf4d2887f8e884f3"),
    83  		},
    84  		{
    85  			address:     "SscaK4A6V94dawc6ZBRCGUxPjdf7um1GJgD",
    86  			addressHash: hexToBytes("5a38638f09937214b07481c656d0c9c73020f8bf"),
    87  			branch:      1,
    88  			pubKey:      hexToBytes("0392735a0eee9026425556ef5c5ae23ad3e54598132a1ca0d74dbcac7bfe31bfa4"),
    89  		},
    90  		{
    91  			address:     "Ssm4BeTKgwKGTqNR63WiGtP1FJaKCRJsN1S",
    92  			addressHash: hexToBytes("b73edb8f32957800e2e3b9424c3b659acac51b7f"),
    93  			branch:      1,
    94  			pubKey:      hexToBytes("037c1e500884c6c3cb044390b52525d324fd67c031fdd9a47d742d0323fe5de73f"),
    95  		},
    96  		{
    97  			address:     "SssBoVxTkCUb6xs7vph3BHdPmki3weVvRsF",
    98  			addressHash: hexToBytes("fa8073fcb670ba7312a1ef0d908cfb05c59b70b9"),
    99  			branch:      1,
   100  			pubKey:      hexToBytes("0327540e546f9cfac45f51699e2656732727507971060641ead554d78eeea88aa6"),
   101  		},
   102  	}
   103  
   104  	expectedExternalAddrs = []expectedAddr{
   105  		{
   106  			address:     "SsfPTmZmaXGkXfcNGjftRPmoGGCqtNPCHKx",
   107  			addressHash: hexToBytes("791376f67fb3becf392b071d25d7c99c82139ee3"),
   108  			pubKey:      hexToBytes("031634efb3e83c834a82cdc898000f85215a09dc742d5b3b82ace7221ca1bb0938"),
   109  		},
   110  		{
   111  			address:     "SsXhSHBiaEan7Ls36bvhLspZ3LC1NKzuwQz",
   112  			addressHash: hexToBytes("24b8b3d89f987bf3cd80a8c16d9368d683217fa4"),
   113  			pubKey:      hexToBytes("0280beb72c6ef42ce3133fd6d340fd5bedcfccaded5a6eabb6d2430e3958bf7c85"),
   114  		},
   115  		{
   116  			address:     "SspSfaWDNwc9TA31Q9iR2jot2eV1hk2ix6U",
   117  			addressHash: hexToBytes("dc67b3d95adb1789efe4aa73607d8a8c57eee2bb"),
   118  			pubKey:      hexToBytes("03b120e0e073a12a1957680251a1562c5c6e30e547797fb5411107eac19699f601"),
   119  		},
   120  		{
   121  			address:     "SsrSYTB9MQQ1czAfPmWE66ZFqv7NrwzqfQT",
   122  			addressHash: hexToBytes("f252015c8e0059c21cae623704d8588d12ca5c74"),
   123  			pubKey:      hexToBytes("0350822c9bd61f524f4d68fa605e850c34c5e8ccc9b5cf278782131c1e21dd261b"),
   124  		},
   125  		{
   126  			address:     "SsqBcGBre8SZrG61cd5M5e2GaMJbK2CMdEa",
   127  			addressHash: hexToBytes("e486d22d1244becac5a30b38dda1c8c4c1b3bdeb"),
   128  			pubKey:      hexToBytes("031c494068c9c454bef7de35fa4717f21c07dec4471bd8500650b133d57e49a81d"),
   129  		},
   130  	}
   131  )
   132  
   133  func setupWallet(ctx context.Context, t *testing.T, cfg *Config) (*Wallet, walletdb.DB, func()) {
   134  	f, err := os.CreateTemp("", "testwallet.db")
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	f.Close()
   139  
   140  	db, err := walletdb.Create("bdb", f.Name())
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	err = Create(ctx, opaqueDB{db}, pubPassphrase, privPassphrase, seed, cfg.Params)
   145  	if err != nil {
   146  		db.Close()
   147  		os.Remove(f.Name())
   148  		t.Fatal(err)
   149  	}
   150  	cfg.DB = opaqueDB{db}
   151  
   152  	w, err := Open(ctx, cfg)
   153  	if err != nil {
   154  		db.Close()
   155  		os.Remove(f.Name())
   156  		t.Fatal(err)
   157  	}
   158  
   159  	teardown := func() {
   160  		db.Close()
   161  		os.Remove(f.Name())
   162  	}
   163  
   164  	return w, db, teardown
   165  }
   166  
   167  type newAddressFunc func(*Wallet, context.Context, uint32, ...NextAddressCallOption) (stdaddr.Address, error)
   168  
   169  func testKnownAddresses(ctx context.Context, tc *testContext, prefix string, unlock bool, newAddr newAddressFunc, tests []expectedAddr) {
   170  	w, db, teardown := setupWallet(ctx, tc.t, &walletConfig)
   171  	defer teardown()
   172  
   173  	if unlock {
   174  		err := w.Unlock(ctx, privPassphrase, nil)
   175  		if err != nil {
   176  			tc.t.Fatal(err)
   177  		}
   178  	}
   179  
   180  	if tc.watchingOnly {
   181  		err := walletdb.Update(ctx, db, func(tx walletdb.ReadWriteTx) error {
   182  			ns := tx.ReadWriteBucket(waddrmgrBucketKey)
   183  			return w.manager.ConvertToWatchingOnly(ns)
   184  		})
   185  		if err != nil {
   186  			tc.t.Fatalf("%s: failed to convert wallet to watching only: %v",
   187  				prefix, err)
   188  		}
   189  	}
   190  
   191  	for i := 0; i < len(tests); i++ {
   192  		addr, err := newAddr(w, ctx, defaultAccount)
   193  		if err != nil {
   194  			tc.t.Fatalf("%s: failed to generate external address: %v",
   195  				prefix, err)
   196  		}
   197  
   198  		ka, err := w.KnownAddress(ctx, addr)
   199  		if err != nil {
   200  			tc.t.Errorf("Unexpected error: %v", err)
   201  			continue
   202  		}
   203  
   204  		if ka.AccountName() != defaultAccountName {
   205  			tc.t.Errorf("%s: expected account %v got %v", prefix,
   206  				defaultAccount, ka.AccountName())
   207  		}
   208  
   209  		if ka.String() != tests[i].address {
   210  			tc.t.Errorf("%s: expected address %v got %v", prefix,
   211  				tests[i].address, ka)
   212  		}
   213  		a := ka.(BIP0044Address)
   214  		if !bytes.Equal(a.PubKeyHash(), tests[i].addressHash) {
   215  			tc.t.Errorf("%s: expected address hash %v got %v", prefix,
   216  				hex.EncodeToString(tests[i].addressHash),
   217  				hex.EncodeToString(a.PubKeyHash()))
   218  		}
   219  
   220  		if _, branch, _ := a.Path(); branch != tests[i].branch {
   221  			tc.t.Errorf("%s: expected branch of %v got %v", prefix,
   222  				tests[i].branch, branch)
   223  		}
   224  
   225  		pubKey := a.PubKey()
   226  		if !bytes.Equal(pubKey, tests[i].pubKey) {
   227  			tc.t.Errorf("%s: expected pubkey %v got %v",
   228  				prefix, hex.EncodeToString(tests[i].pubKey),
   229  				hex.EncodeToString(pubKey))
   230  		}
   231  	}
   232  }
   233  
   234  func TestAddresses(t *testing.T) {
   235  	ctx := context.Background()
   236  
   237  	testAddresses(ctx, t, false)
   238  	testAddresses(ctx, t, true)
   239  }
   240  
   241  func testAddresses(ctx context.Context, t *testing.T, unlock bool) {
   242  	testKnownAddresses(ctx, &testContext{
   243  		t:            t,
   244  		account:      defaultAccount,
   245  		watchingOnly: false,
   246  	}, "testInternalAddresses", unlock, (*Wallet).NewInternalAddress, expectedInternalAddrs)
   247  
   248  	testKnownAddresses(ctx, &testContext{
   249  		t:            t,
   250  		account:      defaultAccount,
   251  		watchingOnly: true,
   252  	}, "testInternalAddresses", unlock, (*Wallet).NewInternalAddress, expectedInternalAddrs)
   253  
   254  	testKnownAddresses(ctx, &testContext{
   255  		t:            t,
   256  		account:      defaultAccount,
   257  		watchingOnly: false,
   258  	}, "testExternalAddresses", unlock, (*Wallet).NewExternalAddress, expectedExternalAddrs)
   259  
   260  	testKnownAddresses(ctx, &testContext{
   261  		t:            t,
   262  		account:      defaultAccount,
   263  		watchingOnly: true,
   264  	}, "testExternalAddresses", unlock, (*Wallet).NewExternalAddress, expectedExternalAddrs)
   265  }
   266  
   267  func TestAccountIndexes(t *testing.T) {
   268  	ctx := context.Background()
   269  
   270  	cfg := basicWalletConfig
   271  	w, teardown := testWallet(ctx, t, &cfg)
   272  	defer teardown()
   273  
   274  	w.SetNetworkBackend(mockNetwork{})
   275  
   276  	tests := []struct {
   277  		f       func(ctx context.Context, t *testing.T, w *Wallet)
   278  		indexes accountIndexes
   279  	}{
   280  		{nil, accountIndexes{{^uint32(0), 0}, {^uint32(0), 0}}},
   281  		{nextAddresses(1), accountIndexes{{^uint32(0), 1}, {^uint32(0), 0}}},
   282  		{nextAddresses(19), accountIndexes{{^uint32(0), 20}, {^uint32(0), 0}}},
   283  		{watchFutureAddresses, accountIndexes{{^uint32(0), 20}, {^uint32(0), 0}}},
   284  		{useAddress(10), accountIndexes{{10, 9}, {^uint32(0), 0}}},
   285  		{nextAddresses(1), accountIndexes{{10, 10}, {^uint32(0), 0}}},
   286  		{nextAddresses(10), accountIndexes{{10, 20}, {^uint32(0), 0}}},
   287  		{useAddress(30), accountIndexes{{30, 0}, {^uint32(0), 0}}},
   288  		{useAddress(31), accountIndexes{{31, 0}, {^uint32(0), 0}}},
   289  	}
   290  	for i, test := range tests {
   291  		if test.f != nil {
   292  			test.f(ctx, t, w)
   293  		}
   294  		w.addressBuffersMu.Lock()
   295  		b := w.addressBuffers[0]
   296  		t.Logf("ext last=%d, ext cursor=%d, int last=%d, int cursor=%d",
   297  			b.albExternal.lastUsed, b.albExternal.cursor, b.albInternal.lastUsed, b.albInternal.cursor)
   298  		check := func(what string, a, b uint32) {
   299  			if a != b {
   300  				t.Fatalf("%d: %s do not match: %d != %d", i, what, a, b)
   301  			}
   302  		}
   303  		check("external last indexes", b.albExternal.lastUsed, test.indexes[0].last)
   304  		check("external cursors", b.albExternal.cursor, test.indexes[0].cursor)
   305  		check("internal last indexes", b.albInternal.lastUsed, test.indexes[1].last)
   306  		check("internal cursors", b.albInternal.cursor, test.indexes[1].cursor)
   307  		w.addressBuffersMu.Unlock()
   308  	}
   309  }
   310  
   311  type accountIndexes [2]struct {
   312  	last, cursor uint32
   313  }
   314  
   315  func nextAddresses(n int) func(ctx context.Context, t *testing.T, w *Wallet) {
   316  	return func(ctx context.Context, t *testing.T, w *Wallet) {
   317  		for i := 0; i < n; i++ {
   318  			_, err := w.NewExternalAddress(ctx, 0)
   319  			if err != nil {
   320  				t.Fatal(err)
   321  			}
   322  		}
   323  	}
   324  }
   325  
   326  func watchFutureAddresses(ctx context.Context, t *testing.T, w *Wallet) {
   327  	n, _ := w.NetworkBackend()
   328  	_, err := w.watchHDAddrs(ctx, false, n)
   329  	if err != nil {
   330  		t.Fatal(err)
   331  	}
   332  }
   333  
   334  func useAddress(child uint32) func(ctx context.Context, t *testing.T, w *Wallet) {
   335  	return func(ctx context.Context, t *testing.T, w *Wallet) {
   336  		w.addressBuffersMu.Lock()
   337  		xbranch := w.addressBuffers[0].albExternal.branchXpub
   338  		w.addressBuffersMu.Unlock()
   339  		addr, err := deriveChildAddress(xbranch, child, basicWalletConfig.Params)
   340  		if err != nil {
   341  			t.Fatal(err)
   342  		}
   343  		err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   344  			ns := dbtx.ReadWriteBucket(waddrmgrBucketKey)
   345  			ma, err := w.manager.Address(ns, addr)
   346  			if err != nil {
   347  				return err
   348  			}
   349  			return w.markUsedAddress("", dbtx, ma)
   350  		})
   351  		if err != nil {
   352  			t.Fatal(err)
   353  		}
   354  		watchFutureAddresses(ctx, t, w)
   355  	}
   356  }