decred.org/dcrwallet/v3@v3.1.0/wallet/udb/testdata/v5.db.go (about) 1 // Copyright (c) 2017 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 compiled from the commit the file was introduced, otherwise 6 // it may not compile due to API changes, or may not create the database with 7 // the correct old version. This file should not be updated for API changes. 8 9 package main 10 11 import ( 12 "bytes" 13 "compress/gzip" 14 "encoding/binary" 15 "errors" 16 "fmt" 17 "io" 18 "log" 19 "os" 20 "time" 21 22 "github.com/decred/dcrd/blockchain" 23 "github.com/decred/dcrd/blockchain/stake" 24 "github.com/decred/dcrd/chaincfg" 25 "github.com/decred/dcrd/chaincfg/chainec" 26 "github.com/decred/dcrd/chaincfg/chainhash" 27 "github.com/decred/dcrd/dcrec/secp256k1" 28 "github.com/decred/dcrd/txscript" 29 "github.com/decred/dcrd/wire" 30 "github.com/decred/dcrutil" 31 "github.com/decred/dcrutil/hdkeychain" 32 _ "github.com/decred/dcrwallet/wallet/internal/bdb" 33 "github.com/decred/dcrwallet/wallet/internal/txsizes" 34 "github.com/decred/dcrwallet/wallet/txrules" 35 "github.com/decred/dcrwallet/wallet/udb" 36 "github.com/decred/dcrwallet/wallet/walletdb" 37 "github.com/decred/dcrwallet/walletseed" 38 ) 39 40 const dbname = "v5.db" 41 42 var ( 43 pubPass = []byte("public") 44 privPass = []byte("private") 45 privKey = []byte{31: 1} 46 addr dcrutil.Address 47 ) 48 49 var chainParams = &chaincfg.TestNet2Params 50 51 var ( 52 epoch time.Time 53 votebits = stake.VoteBits{Bits: 1} 54 subsidies = blockchain.NewSubsidyCache(0, chainParams) 55 ) 56 57 const ticketFeeLimits = 0x5800 // This is dark magic 58 59 func main() { 60 err := setup() 61 if err != nil { 62 fmt.Fprintf(os.Stderr, "setup: %v\n", err) 63 os.Exit(1) 64 } 65 err = compress() 66 if err != nil { 67 fmt.Fprintf(os.Stderr, "compress: %v\n", err) 68 os.Exit(1) 69 } 70 } 71 72 func setup() error { 73 os.Remove(dbname) 74 db, err := walletdb.Create("bdb", dbname) 75 if err != nil { 76 return err 77 } 78 defer db.Close() 79 seed, err := walletseed.GenerateRandomSeed(hdkeychain.RecommendedSeedLen) 80 if err != nil { 81 return err 82 } 83 err = udb.Initialize(db, chainParams, seed, pubPass, privPass) 84 if err != nil { 85 return err 86 } 87 88 amgr, txmgr, _, err := udb.Open(db, chainParams, pubPass) 89 if err != nil { 90 return err 91 } 92 93 return walletdb.Update(db, func(dbtx walletdb.ReadWriteTx) error { 94 amgrns := dbtx.ReadWriteBucket([]byte("waddrmgr")) 95 txmgrns := dbtx.ReadWriteBucket([]byte("wtxmgr")) 96 97 err := amgr.Unlock(amgrns, privPass) 98 if err != nil { 99 return err 100 } 101 102 privKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey) 103 wif, err := dcrutil.NewWIF(privKey, chainParams, chainec.ECTypeSecp256k1) 104 if err != nil { 105 return err 106 } 107 maddr, err := amgr.ImportPrivateKey(amgrns, wif) 108 if err != nil { 109 return err 110 } 111 addr = maddr.Address() 112 113 // Add some fake blocks 114 var prevBlock = chainParams.GenesisHash 115 for height := 1; height < 8; height++ { 116 var buf bytes.Buffer 117 err := (&wire.BlockHeader{ 118 Version: 1, 119 PrevBlock: *prevBlock, 120 StakeVersion: 1, 121 VoteBits: 1, 122 Height: uint32(height), 123 }).Serialize(&buf) 124 if err != nil { 125 return err 126 } 127 headerData := udb.BlockHeaderData{ 128 BlockHash: chainhash.Hash{31: byte(height)}, 129 } 130 copy(headerData.SerializedHeader[:], buf.Bytes()) 131 err = txmgr.ExtendMainChain(txmgrns, &headerData) 132 if err != nil { 133 return err 134 } 135 prevBlock = &headerData.BlockHash 136 } 137 138 // Create 6 tickets: 139 // 140 // 0: Unmined, unspent 141 // 1: Mined at height 2, unspent 142 // 2: Mined at height 3, spent by unmined vote 143 // 3: Mined at height 4, spent by mined vote in block 5 144 // 4: Mined at height 5, spent by unmined revocation 145 // 5: Mined at height 6, spent by mined revocation in block 7 146 // 147 // These tickets have the following hashes: 148 // 149 // 0: 7bc19eb0bf3a57be73d6879b6c411404b14b0156353dd47c5e0456768704bfd1 150 // 1: a6abeb0127c347b5f38ebc2401134b324612d5b1ad9a9b8bdf6a91521842b7b1 151 // 2: 1107757fa4f238803192c617c7b60bf35bdc57bc0fc94b408c71239ff9eaeb98 152 // 3: 3fd00cda28c4d148e0cd38e1d646ba1365116b3ddd9a49aca4483bef80513ff9 153 // 4: f4bdebefaa174470182960046fa53f554108b8ea09a86de5306a14c3a0124566 154 // 5: bca8c2649860585f10b27d774b354ea7b80007e9ad79c090ea05596d63995cf5 155 for i := uint32(0); i < 6; i++ { 156 height := int32(i + 1) 157 ticket, err := createUnsignedTicketPurchase(&wire.OutPoint{Index: i}, 50e8, 50e8-3e5) 158 if err != nil { 159 return err 160 } 161 ticketRec, err := udb.NewTxRecordFromMsgTx(ticket, epoch) 162 if err != nil { 163 return err 164 } 165 var block *udb.BlockMeta 166 switch i { 167 case 0: 168 err := txmgr.InsertMemPoolTx(txmgrns, ticketRec) 169 if err != nil { 170 return err 171 } 172 default: 173 block = &udb.BlockMeta{ 174 Block: udb.Block{ 175 Hash: chainhash.Hash{31: byte(height)}, 176 Height: height, 177 }, 178 VoteBits: 1, 179 } 180 err := txmgr.InsertMinedTx(txmgrns, amgrns, ticketRec, &block.Hash) 181 if err != nil { 182 return err 183 } 184 } 185 err = txmgr.AddCredit(txmgrns, ticketRec, block, 0, false, udb.ImportedAddrAccount) 186 if err != nil { 187 return err 188 } 189 log.Printf("Added ticket %v", &ticketRec.Hash) 190 191 switch i { 192 case 0, 1: 193 // Skip adding a spender 194 continue 195 } 196 197 var spender *wire.MsgTx 198 switch i { 199 case 2, 3: 200 spender, err = createUnsignedVote(&ticketRec.Hash, ticket, 201 height+1, &chainhash.Hash{31: byte(height + 1)}, votebits, 202 subsidies, chainParams) 203 case 4, 5: 204 spender, err = createUnsignedRevocation(&ticketRec.Hash, ticket, 1e5) 205 } 206 if err != nil { 207 return err 208 } 209 spenderRec, err := udb.NewTxRecordFromMsgTx(spender, epoch) 210 if err != nil { 211 return err 212 } 213 block = nil 214 switch i { 215 case 2, 4: 216 err := txmgr.InsertMemPoolTx(txmgrns, spenderRec) 217 if err != nil { 218 return err 219 } 220 case 3, 5: 221 block = &udb.BlockMeta{ 222 Block: udb.Block{ 223 Hash: chainhash.Hash{31: byte(height + 1)}, 224 Height: height + 1, 225 }, 226 VoteBits: 1, 227 } 228 err := txmgr.InsertMinedTx(txmgrns, amgrns, spenderRec, &block.Hash) 229 if err != nil { 230 return err 231 } 232 } 233 err = txmgr.AddCredit(txmgrns, spenderRec, block, 0, false, udb.ImportedAddrAccount) 234 if err != nil { 235 return err 236 } 237 } 238 239 return nil 240 }) 241 } 242 243 func compress() error { 244 db, err := os.Open(dbname) 245 if err != nil { 246 return err 247 } 248 defer os.Remove(dbname) 249 defer db.Close() 250 dbgz, err := os.Create(dbname + ".gz") 251 if err != nil { 252 return err 253 } 254 defer dbgz.Close() 255 gz := gzip.NewWriter(dbgz) 256 _, err = io.Copy(gz, db) 257 if err != nil { 258 return err 259 } 260 return gz.Close() 261 } 262 263 func createUnsignedTicketPurchase(prevOut *wire.OutPoint, 264 inputAmount, ticketPrice dcrutil.Amount) (*wire.MsgTx, error) { 265 266 tx := wire.NewMsgTx() 267 txIn := wire.NewTxIn(prevOut, nil) 268 txIn.ValueIn = inputAmount 269 tx.AddTxIn(txIn) 270 271 pkScript, err := txscript.PayToSStx(addr) 272 if err != nil { 273 return nil, err 274 } 275 276 tx.AddTxOut(wire.NewTxOut(int64(ticketPrice), pkScript)) 277 278 _, amountsCommitted, err := stake.SStxNullOutputAmounts( 279 []int64{int64(inputAmount)}, []int64{0}, int64(ticketPrice)) 280 if err != nil { 281 return nil, err 282 } 283 284 pkScript, err = txscript.GenerateSStxAddrPush(addr, 285 dcrutil.Amount(amountsCommitted[0]), ticketFeeLimits) 286 if err != nil { 287 return nil, err 288 } 289 tx.AddTxOut(wire.NewTxOut(0, pkScript)) 290 291 pkScript, err = txscript.PayToSStxChange(addr) 292 if err != nil { 293 return nil, err 294 } 295 tx.AddTxOut(wire.NewTxOut(0, pkScript)) 296 297 _, err = stake.IsSStx(tx) 298 return tx, err 299 } 300 301 // newVoteScript generates a voting script from the passed VoteBits, for 302 // use in a vote. 303 func newVoteScript(voteBits stake.VoteBits) ([]byte, error) { 304 b := make([]byte, 2+len(voteBits.ExtendedBits)) 305 binary.LittleEndian.PutUint16(b[0:2], voteBits.Bits) 306 copy(b[2:], voteBits.ExtendedBits[:]) 307 return txscript.GenerateProvablyPruneableOut(b) 308 } 309 310 // createUnsignedVote creates an unsigned vote transaction that votes using the 311 // ticket specified by a ticket purchase hash and transaction with the provided 312 // vote bits. The block height and hash must be of the previous block the vote 313 // is voting on. 314 func createUnsignedVote(ticketHash *chainhash.Hash, ticketPurchase *wire.MsgTx, 315 blockHeight int32, blockHash *chainhash.Hash, voteBits stake.VoteBits, 316 subsidyCache *blockchain.SubsidyCache, params *chaincfg.Params) (*wire.MsgTx, error) { 317 318 // Parse the ticket purchase transaction to determine the required output 319 // destinations for vote rewards or revocations. 320 ticketPayKinds, ticketHash160s, ticketValues, _, _, _ := 321 stake.TxSStxStakeOutputInfo(ticketPurchase) 322 323 // Calculate the subsidy for votes at this height. 324 subsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, int64(blockHeight), 325 params) 326 327 // Calculate the output values from this vote using the subsidy. 328 voteRewardValues := stake.CalculateRewards(ticketValues, 329 ticketPurchase.TxOut[0].Value, subsidy) 330 331 // Begin constructing the vote transaction. 332 vote := wire.NewMsgTx() 333 334 // Add stakebase input to the vote. 335 stakebaseOutPoint := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0), 336 wire.TxTreeRegular) 337 stakebaseInput := wire.NewTxIn(stakebaseOutPoint, nil) 338 stakebaseInput.ValueIn = subsidy 339 vote.AddTxIn(stakebaseInput) 340 341 // Votes reference the ticket purchase with the second input. 342 ticketOutPoint := wire.NewOutPoint(ticketHash, 0, wire.TxTreeStake) 343 ticketInput := wire.NewTxIn(ticketOutPoint, nil) 344 ticketInput.ValueIn = ticketPurchase.TxOut[ticketOutPoint.Index].Value 345 vote.AddTxIn(ticketInput) 346 347 // The first output references the previous block the vote is voting on. 348 // This function never errors. 349 blockRefScript, _ := txscript.GenerateSSGenBlockRef(*blockHash, 350 uint32(blockHeight)) 351 vote.AddTxOut(wire.NewTxOut(0, blockRefScript)) 352 353 // The second output contains the votebits encode as a null data script. 354 voteScript, err := newVoteScript(voteBits) 355 if err != nil { 356 return nil, err 357 } 358 vote.AddTxOut(wire.NewTxOut(0, voteScript)) 359 360 // All remaining outputs pay to the output destinations and amounts tagged 361 // by the ticket purchase. 362 for i, hash160 := range ticketHash160s { 363 scriptFn := txscript.PayToSSGenPKHDirect 364 if ticketPayKinds[i] { // P2SH 365 scriptFn = txscript.PayToSSGenSHDirect 366 } 367 // Error is checking for a nil hash160, just ignore it. 368 script, _ := scriptFn(hash160) 369 vote.AddTxOut(wire.NewTxOut(voteRewardValues[i], script)) 370 } 371 372 return vote, nil 373 } 374 375 // createUnsignedRevocation creates an unsigned revocation transaction that 376 // revokes a missed or expired ticket. Revocations must carry a relay fee and 377 // this function can error if the revocation contains no suitable output to 378 // decrease the estimated relay fee from. 379 func createUnsignedRevocation(ticketHash *chainhash.Hash, ticketPurchase *wire.MsgTx, feePerKB dcrutil.Amount) (*wire.MsgTx, error) { 380 // Parse the ticket purchase transaction to determine the required output 381 // destinations for vote rewards or revocations. 382 ticketPayKinds, ticketHash160s, ticketValues, _, _, _ := 383 stake.TxSStxStakeOutputInfo(ticketPurchase) 384 385 // Calculate the output values for the revocation. Revocations do not 386 // contain any subsidy. 387 revocationValues := stake.CalculateRewards(ticketValues, 388 ticketPurchase.TxOut[0].Value, 0) 389 390 // Begin constructing the revocation transaction. 391 revocation := wire.NewMsgTx() 392 393 // Revocations reference the ticket purchase with the first (and only) 394 // input. 395 ticketOutPoint := wire.NewOutPoint(ticketHash, 0, wire.TxTreeStake) 396 ticketInput := wire.NewTxIn(ticketOutPoint, nil) 397 ticketInput.ValueIn = ticketPurchase.TxOut[ticketOutPoint.Index].Value 398 revocation.AddTxIn(ticketInput) 399 400 // All remaining outputs pay to the output destinations and amounts tagged 401 // by the ticket purchase. 402 for i, hash160 := range ticketHash160s { 403 scriptFn := txscript.PayToSSRtxPKHDirect 404 if ticketPayKinds[i] { // P2SH 405 scriptFn = txscript.PayToSSRtxSHDirect 406 } 407 // Error is checking for a nil hash160, just ignore it. 408 script, _ := scriptFn(hash160) 409 revocation.AddTxOut(wire.NewTxOut(revocationValues[i], script)) 410 } 411 412 // Revocations must pay a fee but do so by decreasing one of the output 413 // values instead of increasing the input value and using a change output. 414 // Calculate the estimated signed serialize size. 415 sizeEstimate := txsizes.EstimateSerializeSize(1, revocation.TxOut, false) 416 feeEstimate := txrules.FeeForSerializeSize(feePerKB, sizeEstimate) 417 418 // Reduce the output value of one of the outputs to accommodate for the relay 419 // fee. To avoid creating dust outputs, a suitable output value is reduced 420 // by the fee estimate only if it is large enough to not create dust. This 421 // code does not currently handle reducing the output values of multiple 422 // commitment outputs to accommodate for the fee. 423 for _, output := range revocation.TxOut { 424 if dcrutil.Amount(output.Value) > feeEstimate { 425 amount := dcrutil.Amount(output.Value) - feeEstimate 426 if !txrules.IsDustAmount(amount, len(output.PkScript), feePerKB) { 427 output.Value = int64(amount) 428 return revocation, nil 429 } 430 } 431 } 432 return nil, errors.New("no suitable revocation outputs to pay relay fee") 433 }