decred.org/dcrwallet/v3@v3.1.0/wallet/udb/testdata/v11.db.go (about) 1 // Copyright (c) 2018 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 // This file should be compiled from the commit the file was introduced, 6 // otherwise it may not compile due to API changes, or may not create the 7 // database with the correct old version. This file should not be updated for 8 // API changes. 9 10 // V11 test database layout and v12 upgrade test plan 11 // 12 // The v12 database upgrade introduces buckets to track ticket commitments 13 // information in the transaction manager namespace (bucketTicketCommitments and 14 // bucketTicketCommitmentsUsp). These buckets are meant to track outstanding 15 // ticket commitment outputs for the purposes of correct balance calculation: it 16 // allows non-voting wallets (eg: funding wallets in solo-voting setups or 17 // non-voter participants of split tickets) to track their proportional locked 18 // funds. In standard (single-voter) VSP setups, it also allows to correctly 19 // discount the pool fee for correct accounting of total locked funds. 20 // 21 // The v11 database generated by this file is meant to be used on the v12 22 // upgrade verification test (verifyV12Upgrade). This database is setup with a 23 // set of tickets, votes and revocations in order to test that the upgrade was 24 // done successfully and that the balances generated after the upgrade are 25 // correct. 26 // 27 // Each generated ticket uses an address from an odd-numbered account as vote 28 // (ticket submission) address, an address from the next account as commitment 29 // address and a fixed, non-wallet address for pool fee commitment. Votes and 30 // revocations are generated from a corresponding ticket as needed. 31 // 32 // Using different accounts for each combination of 33 // {mined,unmined}{ticket,vote,revocation} allows the upgrade test to easily 34 // verify that the upgrade and balance functions are performing correctly by 35 // checking each account for the expected balance. 36 37 package main 38 39 import ( 40 "bytes" 41 "compress/gzip" 42 "fmt" 43 "io" 44 "math" 45 "os" 46 "time" 47 48 "decred.org/dcrwallet/v3/errors" 49 _ "decred.org/dcrwallet/v3/wallet/internal/bdb" 50 "decred.org/dcrwallet/v3/wallet/udb" 51 "decred.org/dcrwallet/v3/wallet/walletdb" 52 "github.com/decred/dcrd/blockchain/stake" 53 "github.com/decred/dcrd/chaincfg" 54 "github.com/decred/dcrd/chaincfg/chainhash" 55 "github.com/decred/dcrd/dcrutil" 56 "github.com/decred/dcrd/gcs" 57 "github.com/decred/dcrd/txscript" 58 "github.com/decred/dcrd/wire" 59 ) 60 61 const dbname = "v11.db" 62 63 var ( 64 epoch time.Time 65 pubPass = []byte("public") 66 privPass = []byte("private") 67 ) 68 69 var chainParams = &chaincfg.TestNet3Params 70 71 func main() { 72 err := setup() 73 if err != nil { 74 fmt.Fprintf(os.Stderr, "setup: %v\n", err) 75 os.Exit(1) 76 } 77 err = compress() 78 if err != nil { 79 fmt.Fprintf(os.Stderr, "compress: %v\n", err) 80 os.Exit(1) 81 } 82 } 83 84 func pay2ssgen(addr dcrutil.Address) []byte { 85 s, err := txscript.PayToSSGen(addr) 86 if err != nil { 87 panic(err) 88 } 89 return s 90 } 91 92 func pay2ssrtx(addr dcrutil.Address) []byte { 93 s, err := txscript.PayToSSRtx(addr) 94 if err != nil { 95 panic(err) 96 } 97 return s 98 } 99 100 func pay2sstx(addr dcrutil.Address) []byte { 101 s, err := txscript.PayToSStx(addr) 102 if err != nil { 103 panic(err) 104 } 105 return s 106 } 107 108 func pay2sstxChange() []byte { 109 //OP_SSTXCHANGE OP_DUP OP_HASH160 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG 110 return []byte{0xbd, 0x76, 0xa9, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 111 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 112 0x00, 0x00, 0x88, 0xac} 113 } 114 115 func sstxAddrPush(addr dcrutil.Address, amount int64) []byte { 116 s, err := txscript.GenerateSStxAddrPush(addr, dcrutil.Amount(amount), 0x0058) 117 if err != nil { 118 panic(err) 119 } 120 return s 121 } 122 123 func dummyTxIn(idx *uint32) *wire.TxIn { 124 var prevHash chainhash.Hash 125 *idx++ 126 return wire.NewTxIn(wire.NewOutPoint(&prevHash, *idx, 0), 0, nil) 127 } 128 129 func stakeBaseTxIn() *wire.TxIn { 130 var prevHash chainhash.Hash 131 return wire.NewTxIn(wire.NewOutPoint(&prevHash, math.MaxUint32, 0), 0, nil) 132 } 133 134 func ticketSpendTxIn(ticket *wire.MsgTx) *wire.TxIn { 135 th := ticket.TxHash() 136 return wire.NewTxIn(wire.NewOutPoint(&th, 0, 1), 0, nil) 137 } 138 139 func setup() error { 140 db, err := walletdb.Create("bdb", dbname) 141 if err != nil { 142 return err 143 } 144 defer db.Close() 145 var seed [32]byte 146 err = udb.Initialize(db, chainParams, seed[:], pubPass, privPass) 147 if err != nil { 148 return err 149 } 150 151 amgr, txmgr, _, err := udb.Open(db, chainParams, pubPass) 152 if err != nil { 153 return err 154 } 155 156 return walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { 157 amgrns := tx.ReadWriteBucket([]byte("waddrmgr")) 158 txmgrns := tx.ReadWriteBucket([]byte("wtxmgr")) 159 err := amgr.Unlock(amgrns, privPass) 160 if err != nil { 161 return err 162 } 163 164 // The following are constants used throughout the db setup process. 165 166 // Assumes the manager on a new wallet only (and always) creates account 0 167 lastAcctIdx := uint32(0) 168 branchIdx := uint32(0) 169 commitmentAmt := int64(1000) 170 commitmentAmtReward := int64(300) 171 poolFee := int64(100) 172 poolFeeReward := int64(30) 173 ticketPrice := commitmentAmt + poolFee 174 poolFeeAddr, _ := dcrutil.DecodeAddress("TsR28UZRprhgQQhzWns2M6cAwchrNVvbYq2") 175 txinIdx := uint32(0) 176 var blockHash chainhash.Hash 177 var block udb.Block 178 var blockMeta udb.BlockMeta 179 180 // The following are helper functions, defined as closures to generate 181 // test data. 182 183 // Returns the first usable address for the next account of the test. 184 nextAddress := func() (dcrutil.Address, error) { 185 lastAcctIdx++ 186 _, err := amgr.NewAccount(amgrns, fmt.Sprintf("%d", lastAcctIdx)) 187 if err != nil { 188 return nil, err 189 } 190 err = amgr.SyncAccountToAddrIndex(amgrns, lastAcctIdx, 1, branchIdx) 191 if err != nil { 192 return nil, err 193 } 194 xpubBranch, err := amgr.AccountBranchExtendedPubKey(tx, lastAcctIdx, 195 branchIdx) 196 if err != nil { 197 return nil, err 198 } 199 xpubChild, err := xpubBranch.Child(0) 200 if err != nil { 201 return nil, err 202 } 203 addr, err := xpubChild.Address(chainParams) 204 if err != nil { 205 return nil, err 206 } 207 return addr, nil 208 } 209 210 // Generate a ticket. Generates 2 new accounts, uses an address from the 211 // first as vote address and an address from the second as commitment. 212 // Also adds a pool fee commitment to an address not from the wallet. 213 genTicket := func() (*wire.MsgTx, error) { 214 voteAddr, err := nextAddress() 215 if err != nil { 216 return nil, err 217 } 218 219 commitAddr, err := nextAddress() 220 if err != nil { 221 return nil, err 222 } 223 224 tx := wire.NewMsgTx() 225 tx.AddTxIn(dummyTxIn(&txinIdx)) 226 tx.AddTxIn(dummyTxIn(&txinIdx)) 227 tx.AddTxOut(wire.NewTxOut(ticketPrice, pay2sstx(voteAddr))) 228 tx.AddTxOut(wire.NewTxOut(0, sstxAddrPush(poolFeeAddr, poolFee))) 229 tx.AddTxOut(wire.NewTxOut(0, pay2sstxChange())) 230 tx.AddTxOut(wire.NewTxOut(0, sstxAddrPush(commitAddr, commitmentAmt))) 231 tx.AddTxOut(wire.NewTxOut(0, pay2sstxChange())) 232 return tx, nil 233 } 234 235 // Generate a vote for the given ticket. 236 genVote := func(ticket *wire.MsgTx) (*wire.MsgTx, error) { 237 poolFeeAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[1].PkScript, 238 chainParams) 239 if err != nil { 240 return nil, err 241 } 242 243 commitAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[3].PkScript, 244 chainParams) 245 if err != nil { 246 return nil, err 247 } 248 249 blockRef := bytes.Repeat([]byte{0x00}, 36) 250 prevBlockScript := append([]byte{0x6a, 0x24}, blockRef...) 251 252 tx := wire.NewMsgTx() 253 tx.AddTxIn(stakeBaseTxIn()) 254 tx.AddTxIn(ticketSpendTxIn(ticket)) 255 tx.AddTxOut(wire.NewTxOut(0, prevBlockScript)) // prev block 256 tx.AddTxOut(wire.NewTxOut(0, []byte{0x6a, 0x03, 0x00, 0x00, 0x00})) // vote bits 257 tx.AddTxOut(wire.NewTxOut(poolFee+poolFeeReward, pay2ssgen(poolFeeAddr))) 258 tx.AddTxOut(wire.NewTxOut(commitmentAmt+commitmentAmtReward, pay2ssgen(commitAddr))) 259 260 return tx, nil 261 } 262 263 // Generate a revocation for the given ticket. 264 genRevoke := func(ticket *wire.MsgTx) (*wire.MsgTx, error) { 265 poolFeeAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[1].PkScript, 266 chainParams) 267 if err != nil { 268 return nil, err 269 } 270 271 commitAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[3].PkScript, 272 chainParams) 273 if err != nil { 274 return nil, err 275 } 276 277 tx := wire.NewMsgTx() 278 tx.AddTxIn(ticketSpendTxIn(ticket)) 279 tx.AddTxOut(wire.NewTxOut(poolFee-poolFeeReward, pay2ssrtx(poolFeeAddr))) 280 tx.AddTxOut(wire.NewTxOut(commitmentAmt-commitmentAmtReward, pay2ssrtx(commitAddr))) 281 return tx, nil 282 } 283 284 // Insert this transaction and its credits into the tx store. This is a 285 // subset of what happens in chainntfns' ProcessTransaction() as of 286 // version 11 of the database. 287 addTx := func(tx *wire.MsgTx, mined bool) error { 288 rec, err := udb.NewTxRecordFromMsgTx(tx, epoch) 289 if err != nil { 290 return err 291 } 292 var blockMetaToUse *udb.BlockMeta 293 if mined { 294 err = txmgr.InsertMinedTx(txmgrns, amgrns, rec, &blockHash) 295 if err != nil { 296 return err 297 } 298 blockMetaToUse = &blockMeta 299 } else { 300 err = txmgr.InsertMemPoolTx(txmgrns, rec) 301 if err != nil { 302 return err 303 } 304 } 305 for i, txout := range tx.TxOut { 306 if txout.Value == 0 { 307 continue 308 } 309 310 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txout.Version, 311 txout.PkScript, chainParams) 312 if err != nil { 313 return nil 314 } 315 if len(addrs) == 0 { 316 return errors.New("should have an address") 317 } 318 ma, err := amgr.Address(amgrns, addrs[0]) 319 if errors.Is(err, errors.NotExist) { 320 continue 321 } 322 if err != nil { 323 return err 324 } 325 err = txmgr.AddCredit(txmgrns, rec, blockMetaToUse, 326 uint32(i), ma.Internal(), ma.Account()) 327 if err != nil { 328 return err 329 } 330 } 331 332 return nil 333 } 334 335 // Generate and add a ticket as mined. 336 addMinedTicket := func() (*wire.MsgTx, error) { 337 ticket, err := genTicket() 338 if err != nil { 339 return nil, err 340 } 341 err = addTx(ticket, true) 342 if err != nil { 343 return nil, err 344 } 345 return ticket, nil 346 } 347 348 // Add a block to the database. All mined transactions added will be 349 // registered on this block. While this breaks many consensus for 350 // stake transactions, it is fine for simple database testing. 351 prevBlock := chainParams.GenesisHash 352 buf := bytes.Buffer{} 353 blockHeader := &wire.BlockHeader{ 354 Version: 1, 355 PrevBlock: *prevBlock, 356 StakeVersion: 1, 357 VoteBits: 1, 358 Height: uint32(1), 359 } 360 blockHash = blockHeader.BlockHash() 361 headerData := udb.BlockHeaderData{ 362 BlockHash: blockHash, 363 } 364 copy(headerData.SerializedHeader[:], buf.Bytes()) 365 nullgcskey := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 366 nullb := bytes.Repeat([]byte{0}, 16) 367 gcsFilter, err := gcs.NewFilter(1, nullgcskey, [][]byte{nullb}) 368 if err != nil { 369 return err 370 } 371 err = txmgr.ExtendMainChain(txmgrns, blockHeader, gcsFilter) 372 if err != nil { 373 return err 374 } 375 block = udb.Block{ 376 Hash: headerData.BlockHash, 377 Height: int32(1), 378 } 379 blockMeta = udb.BlockMeta{ 380 Block: block, 381 Time: epoch, 382 } 383 384 // Start adding the test data. The initial default account is 0. 385 var ticket, vote, revoke *wire.MsgTx 386 387 // Unmined ticket (accounts 1 & 2) 388 if ticket, err = genTicket(); err != nil { 389 return err 390 } 391 if err = addTx(ticket, false); err != nil { 392 return err 393 } 394 395 // Mined ticket (accounts 3 & 4) 396 if ticket, err = genTicket(); err != nil { 397 return err 398 } 399 if err = addTx(ticket, true); err != nil { 400 return err 401 } 402 403 // Mined ticket with unmined vote (accounts 5 & 6). 404 if ticket, err = addMinedTicket(); err != nil { 405 return err 406 } 407 if vote, err = genVote(ticket); err != nil { 408 return err 409 } 410 if err = addTx(vote, false); err != nil { 411 return err 412 } 413 414 // Mined ticket with mined vote (accounts 7 & 8). 415 if ticket, err = addMinedTicket(); err != nil { 416 return err 417 } 418 if vote, err = genVote(ticket); err != nil { 419 return err 420 } 421 if err = addTx(vote, true); err != nil { 422 return err 423 } 424 425 // Mined ticket with unmined revocation (accounts 9 & 10). 426 if ticket, err = addMinedTicket(); err != nil { 427 return err 428 } 429 if revoke, err = genRevoke(ticket); err != nil { 430 return err 431 } 432 if err = addTx(revoke, false); err != nil { 433 return err 434 } 435 436 // Mined ticket with mined revocation (accounts 11 & 12). 437 if ticket, err = addMinedTicket(); err != nil { 438 return err 439 } 440 if revoke, err = genRevoke(ticket); err != nil { 441 return err 442 } 443 if err = addTx(revoke, true); err != nil { 444 return err 445 } 446 447 return nil 448 }) 449 } 450 451 func compress() error { 452 db, err := os.Open(dbname) 453 if err != nil { 454 return err 455 } 456 defer os.Remove(dbname) 457 defer db.Close() 458 dbgz, err := os.Create(dbname + ".gz") 459 if err != nil { 460 return err 461 } 462 defer dbgz.Close() 463 gz := gzip.NewWriter(dbgz) 464 _, err = io.Copy(gz, db) 465 if err != nil { 466 return err 467 } 468 return gz.Close() 469 }