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 }