decred.org/dcrwallet/v3@v3.1.0/wallet/udb/stakedb.go (about) 1 // Copyright (c) 2015-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 package udb 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "io" 11 "time" 12 13 "decred.org/dcrwallet/v3/errors" 14 "decred.org/dcrwallet/v3/wallet/walletdb" 15 "github.com/decred/dcrd/blockchain/stake/v5" 16 "github.com/decred/dcrd/chaincfg/chainhash" 17 "github.com/decred/dcrd/dcrutil/v4" 18 "github.com/decred/dcrd/wire" 19 ) 20 21 const ( 22 // Size of various types in bytes. 23 int8Size = 1 24 int16Size = 2 25 int32Size = 4 26 int64Size = 8 27 hashSize = 32 28 29 // stakePoolUserTicketSize is the size 30 // of a serialized stake pool user 31 // ticket. 32 // hash + uint32 + uint8 + uint32 + hash 33 stakePoolUserTicketSize = 32 + 4 + 1 + 4 + 32 34 35 // stakePoolTicketsPrefixSize is the length of 36 // stakePoolTicketsPrefix. 37 stakePoolTicketsPrefixSize = 5 38 39 // stakePoolInvalidPrefixSize is the length of 40 // stakePoolInvalidPrefix. 41 stakePoolInvalidPrefixSize = 5 42 43 // scriptHashLen is the length of a HASH160 44 // hash. 45 scriptHashSize = 20 46 ) 47 48 var ( 49 // sstxTicket2PKHPrefix is the PkScript byte prefix for an SStx 50 // P2PKH ticket output. The entire prefix is 0xba76a914, but we 51 // only use the first 3 bytes. 52 sstxTicket2PKHPrefix = []byte{0xba, 0x76, 0xa9} 53 54 // sstxTicket2SHPrefix is the PkScript byte prefix for an SStx 55 // P2SH ticket output. 56 sstxTicket2SHPrefix = []byte{0xba, 0xa9, 0x14} 57 58 // stakePoolTicketsPrefix is the byte slice prefix for valid 59 // tickets in the stake pool for a given user. 60 stakePoolTicketsPrefix = []byte("tickt") 61 62 // stakePoolInvalidPrefix is the byte slice prefix for invalid 63 // tickets in the stake pool for a given user. 64 stakePoolInvalidPrefix = []byte("invld") 65 ) 66 67 // Key names for various database fields. 68 // sstxRecords 69 // 70 // key: sstx tx hash 71 // val: sstxRecord 72 // 73 // ssgenRecords 74 // 75 // key: sstx tx hash 76 // val: serialized slice of ssgenRecords 77 var ( 78 // Bucket names. 79 sstxRecordsBucketName = []byte("sstxrecords") 80 ssgenRecordsBucketName = []byte("ssgenrecords") 81 ssrtxRecordsBucketName = []byte("ssrtxrecords") 82 83 // Db related key names (main bucket). 84 stakeStoreCreateDateName = []byte("stakestorecreated") 85 ) 86 87 // deserializeSStxRecord deserializes the passed serialized tx record information. 88 func deserializeSStxRecord(serializedSStxRecord []byte, dbVersion uint32) (*sstxRecord, error) { 89 switch { 90 case dbVersion < 3: 91 record := new(sstxRecord) 92 93 curPos := 0 94 95 // Read MsgTx size (as a uint64). 96 msgTxLen := int(binary.LittleEndian.Uint64( 97 serializedSStxRecord[curPos : curPos+int64Size])) 98 curPos += int64Size 99 100 // Pretend to read the pkScrLoc for the 0th output pkScript. 101 curPos += int32Size 102 103 // Read the intended voteBits and extended voteBits length (uint8). 104 record.voteBitsSet = false 105 voteBitsLen := int(serializedSStxRecord[curPos]) 106 if voteBitsLen != 0 { 107 record.voteBitsSet = true 108 } 109 curPos += int8Size 110 111 // Read the assumed 2 byte VoteBits as well as the extended 112 // votebits (75 bytes max). 113 record.voteBits = binary.LittleEndian.Uint16( 114 serializedSStxRecord[curPos : curPos+int16Size]) 115 curPos += int16Size 116 if voteBitsLen != 0 { 117 record.voteBitsExt = make([]byte, voteBitsLen-int16Size) 118 copy(record.voteBitsExt, serializedSStxRecord[curPos:curPos+voteBitsLen-int16Size]) 119 } 120 curPos += stake.MaxSingleBytePushLength - int16Size 121 122 // Prepare a buffer for the msgTx. 123 buf := bytes.NewBuffer(serializedSStxRecord[curPos : curPos+msgTxLen]) 124 curPos += msgTxLen 125 126 // Deserialize transaction. 127 msgTx := new(wire.MsgTx) 128 err := msgTx.Deserialize(buf) 129 if err != nil { 130 if errors.Is(err, io.EOF) { 131 err = io.ErrUnexpectedEOF 132 } 133 return nil, err 134 } 135 136 // Create and save the dcrutil.Tx of the read MsgTx and set its index. 137 tx := dcrutil.NewTx(msgTx) 138 tx.SetIndex(dcrutil.TxIndexUnknown) 139 tx.SetTree(wire.TxTreeStake) 140 record.tx = tx 141 142 // Read received unix time (int64). 143 received := int64(binary.LittleEndian.Uint64( 144 serializedSStxRecord[curPos : curPos+int64Size])) 145 record.ts = time.Unix(received, 0) 146 147 return record, nil 148 149 case dbVersion >= 3: 150 // Don't need to read the pkscript location, so first four bytes are 151 // skipped. 152 serializedSStxRecord = serializedSStxRecord[4:] 153 154 var tx wire.MsgTx 155 err := tx.Deserialize(bytes.NewReader(serializedSStxRecord)) 156 if err != nil { 157 return nil, err 158 } 159 unixTime := int64(binary.LittleEndian.Uint64(serializedSStxRecord[tx.SerializeSize():])) 160 return &sstxRecord{tx: dcrutil.NewTx(&tx), ts: time.Unix(unixTime, 0)}, nil 161 162 default: 163 panic("unreachable") 164 } 165 } 166 167 // deserializeSStxTicketHash160 deserializes and returns a 20 byte script 168 // hash for a ticket's 0th output. 169 func deserializeSStxTicketHash160(serializedSStxRecord []byte, dbVersion uint32) (hash160 []byte, p2sh bool, err error) { 170 var pkscriptLocOffset int 171 var txOffset int 172 switch { 173 case dbVersion < 3: 174 pkscriptLocOffset = 8 // After transaction size 175 txOffset = 8 + 4 + 1 + stake.MaxSingleBytePushLength 176 case dbVersion >= 3: 177 pkscriptLocOffset = 0 178 txOffset = 4 179 } 180 181 pkscriptLoc := int(binary.LittleEndian.Uint32(serializedSStxRecord[pkscriptLocOffset:])) + txOffset 182 183 // Pop off the script prefix, then pop off the 20 bytes 184 // HASH160 pubkey or script hash. 185 prefixBytes := serializedSStxRecord[pkscriptLoc : pkscriptLoc+3] 186 scriptHash := make([]byte, 20) 187 p2sh = false 188 switch { 189 case bytes.Equal(prefixBytes, sstxTicket2PKHPrefix): 190 scrHashLoc := pkscriptLoc + 4 191 if scrHashLoc+20 >= len(serializedSStxRecord) { 192 return nil, false, errors.E(errors.IO, "bad sstx record size") 193 } 194 copy(scriptHash, serializedSStxRecord[scrHashLoc:scrHashLoc+20]) 195 case bytes.Equal(prefixBytes, sstxTicket2SHPrefix): 196 scrHashLoc := pkscriptLoc + 3 197 if scrHashLoc+20 >= len(serializedSStxRecord) { 198 return nil, false, errors.E(errors.IO, "bad sstx record size") 199 } 200 copy(scriptHash, serializedSStxRecord[scrHashLoc:scrHashLoc+20]) 201 p2sh = true 202 } 203 204 return scriptHash, p2sh, nil 205 } 206 207 // serializeSSTxRecord returns the serialization of the passed txrecord row. 208 func serializeSStxRecord(record *sstxRecord, dbVersion uint32) ([]byte, error) { 209 switch { 210 case dbVersion < 3: 211 msgTx := record.tx.MsgTx() 212 msgTxSize := int64(msgTx.SerializeSize()) 213 214 size := 0 215 216 // tx tree is implicit (stake) 217 218 // size of msgTx (recast to int64) 219 size += int64Size 220 221 // byte index of the ticket pk script 222 size += int32Size 223 224 // intended votebits length (uint8) 225 size += int8Size 226 227 // intended votebits (75 bytes) 228 size += stake.MaxSingleBytePushLength 229 230 // msgTx size is variable. 231 size += int(msgTxSize) 232 233 // timestamp (int64) 234 size += int64Size 235 236 buf := make([]byte, size) 237 238 curPos := 0 239 240 // Write msgTx size (as a uint64). 241 binary.LittleEndian.PutUint64(buf[curPos:curPos+int64Size], uint64(msgTxSize)) 242 curPos += int64Size 243 244 // Write the pkScript loc for the ticket output as a uint32. 245 pkScrLoc := msgTx.PkScriptLocs() 246 binary.LittleEndian.PutUint32(buf[curPos:curPos+int32Size], uint32(pkScrLoc[0])) 247 curPos += int32Size 248 249 // Write the intended votebits length (uint8). Hardcode the uint16 250 // size for now. 251 buf[curPos] = byte(int16Size + len(record.voteBitsExt)) 252 curPos += int8Size 253 254 // Write the first two bytes for the intended votebits (75 bytes max), 255 // then write the extended vote bits. 256 binary.LittleEndian.PutUint16(buf[curPos:curPos+int16Size], record.voteBits) 257 curPos += int16Size 258 copy(buf[curPos:], record.voteBitsExt) 259 curPos += stake.MaxSingleBytePushLength - 2 260 261 // Serialize and write transaction. 262 var b bytes.Buffer 263 b.Grow(msgTx.SerializeSize()) 264 err := msgTx.Serialize(&b) 265 if err != nil { 266 return buf, err 267 } 268 copy(buf[curPos:curPos+int(msgTxSize)], b.Bytes()) 269 curPos += int(msgTxSize) 270 271 // Write received unix time (int64). 272 binary.LittleEndian.PutUint64(buf[curPos:curPos+int64Size], uint64(record.ts.Unix())) 273 274 return buf, nil 275 276 case dbVersion >= 3: 277 tx := record.tx.MsgTx() 278 txSize := tx.SerializeSize() 279 280 buf := make([]byte, 4+txSize+8) // pkscript location + tx + unix timestamp 281 pkScrLoc := tx.PkScriptLocs() 282 binary.LittleEndian.PutUint32(buf, uint32(pkScrLoc[0])) 283 err := tx.Serialize(bytes.NewBuffer(buf[4:4])) 284 if err != nil { 285 return nil, err 286 } 287 binary.LittleEndian.PutUint64(buf[4+txSize:], uint64(record.ts.Unix())) 288 return buf, nil 289 290 default: 291 panic("unreachable") 292 } 293 } 294 295 // stakeStoreExists returns whether or not the stake store has already 296 // been created in the given database namespace. 297 func stakeStoreExists(ns walletdb.ReadBucket) bool { 298 mainBucket := ns.NestedReadBucket(mainBucketName) 299 return mainBucket != nil 300 } 301 302 // fetchSStxRecord retrieves a tx record from the sstx records bucket 303 // with the given hash. 304 func fetchSStxRecord(ns walletdb.ReadBucket, hash *chainhash.Hash, dbVersion uint32) (*sstxRecord, error) { 305 bucket := ns.NestedReadBucket(sstxRecordsBucketName) 306 307 key := hash[:] 308 val := bucket.Get(key) 309 if val == nil { 310 return nil, errors.E(errors.NotExist, errors.Errorf("no ticket purchase %v", hash)) 311 } 312 313 return deserializeSStxRecord(val, dbVersion) 314 } 315 316 // fetchSStxRecordSStxTicketHash160 retrieves a ticket 0th output script or 317 // pubkeyhash from the sstx records bucket with the given hash. 318 func fetchSStxRecordSStxTicketHash160(ns walletdb.ReadBucket, hash *chainhash.Hash, dbVersion uint32) (hash160 []byte, p2sh bool, err error) { 319 bucket := ns.NestedReadBucket(sstxRecordsBucketName) 320 321 key := hash[:] 322 val := bucket.Get(key) 323 if val == nil { 324 return nil, false, errors.E(errors.NotExist, errors.Errorf("no ticket purchase %v", hash)) 325 } 326 327 return deserializeSStxTicketHash160(val, dbVersion) 328 } 329 330 // putSStxRecord inserts a given SStx record to the SStxrecords bucket. 331 func putSStxRecord(ns walletdb.ReadWriteBucket, record *sstxRecord, dbVersion uint32) error { 332 bucket := ns.NestedReadWriteBucket(sstxRecordsBucketName) 333 334 // Write the serialized txrecord keyed by the tx hash. 335 serializedSStxRecord, err := serializeSStxRecord(record, dbVersion) 336 if err != nil { 337 return errors.E(errors.IO, err) 338 } 339 err = bucket.Put(record.tx.Hash()[:], serializedSStxRecord) 340 if err != nil { 341 return errors.E(errors.IO, err) 342 } 343 return nil 344 } 345 346 // deserializeUserTicket deserializes the passed serialized user 347 // ticket information. 348 func deserializeUserTicket(serializedTicket []byte) (*PoolTicket, error) { 349 // Cursory check to make sure that the size of the 350 // ticket makes sense. 351 if len(serializedTicket)%stakePoolUserTicketSize != 0 { 352 return nil, errors.E(errors.IO, "invalid pool ticket record size") 353 } 354 355 record := new(PoolTicket) 356 357 curPos := 0 358 359 // Insert the ticket hash into the record. 360 copy(record.Ticket[:], serializedTicket[curPos:curPos+hashSize]) 361 curPos += hashSize 362 363 // Insert the ticket height into the record. 364 record.HeightTicket = binary.LittleEndian.Uint32( 365 serializedTicket[curPos : curPos+int32Size]) 366 curPos += int32Size 367 368 // Insert the status into the record. 369 record.Status = TicketStatus(serializedTicket[curPos]) 370 curPos += int8Size 371 372 // Insert the spent by height into the record. 373 record.HeightSpent = binary.LittleEndian.Uint32( 374 serializedTicket[curPos : curPos+int32Size]) 375 curPos += int32Size 376 377 // Insert the spending hash into the record. 378 copy(record.SpentBy[:], serializedTicket[curPos:curPos+hashSize]) 379 380 return record, nil 381 } 382 383 // deserializeUserTickets deserializes the passed serialized pool 384 // users tickets information. 385 func deserializeUserTickets(serializedTickets []byte) ([]*PoolTicket, error) { 386 // Cursory check to make sure that the number of records 387 // makes sense. 388 if len(serializedTickets)%stakePoolUserTicketSize != 0 { 389 err := io.ErrUnexpectedEOF 390 return nil, err 391 } 392 393 numRecords := len(serializedTickets) / stakePoolUserTicketSize 394 395 records := make([]*PoolTicket, numRecords) 396 397 // Loop through all the records, deserialize them, and 398 // store them. 399 for i := 0; i < numRecords; i++ { 400 record, err := deserializeUserTicket( 401 serializedTickets[i*stakePoolUserTicketSize : (i+ 402 1)*stakePoolUserTicketSize]) 403 if err != nil { 404 return nil, err 405 } 406 407 records[i] = record 408 } 409 410 return records, nil 411 } 412 413 // serializeUserTicket returns the serialization of a single stake pool 414 // user ticket. 415 func serializeUserTicket(record *PoolTicket) []byte { 416 buf := make([]byte, stakePoolUserTicketSize) 417 418 curPos := 0 419 420 // Write the ticket hash. 421 copy(buf[curPos:curPos+hashSize], record.Ticket[:]) 422 curPos += hashSize 423 424 // Write the ticket block height. 425 binary.LittleEndian.PutUint32(buf[curPos:curPos+int32Size], record.HeightTicket) 426 curPos += int32Size 427 428 // Write the ticket status. 429 buf[curPos] = byte(record.Status) 430 curPos += int8Size 431 432 // Write the spending height. 433 binary.LittleEndian.PutUint32(buf[curPos:curPos+int32Size], record.HeightSpent) 434 curPos += int32Size 435 436 // Write the spending tx hash. 437 copy(buf[curPos:curPos+hashSize], record.SpentBy[:]) 438 439 return buf 440 } 441 442 // serializeUserTickets returns the serialization of the passed stake pool 443 // user tickets slice. 444 func serializeUserTickets(records []*PoolTicket) []byte { 445 numRecords := len(records) 446 447 buf := make([]byte, numRecords*stakePoolUserTicketSize) 448 449 // Serialize and write each record into the slice sequentially. 450 for i := 0; i < numRecords; i++ { 451 recordBytes := serializeUserTicket(records[i]) 452 453 copy(buf[i*stakePoolUserTicketSize:(i+1)*stakePoolUserTicketSize], 454 recordBytes) 455 } 456 457 return buf 458 } 459 460 // fetchStakePoolUserTickets retrieves pool user tickets from the meta bucket with 461 // the given hash. 462 func fetchStakePoolUserTickets(ns walletdb.ReadBucket, scriptHash [20]byte) ([]*PoolTicket, error) { 463 bucket := ns.NestedReadBucket(metaBucketName) 464 465 key := make([]byte, stakePoolTicketsPrefixSize+scriptHashSize) 466 copy(key[0:stakePoolTicketsPrefixSize], stakePoolTicketsPrefix) 467 copy(key[stakePoolTicketsPrefixSize:stakePoolTicketsPrefixSize+scriptHashSize], 468 scriptHash[:]) 469 val := bucket.Get(key) 470 if val == nil { 471 return nil, errors.E(errors.NotExist, errors.Errorf("no ticket purchase for hash160 %x", &scriptHash)) 472 } 473 474 return deserializeUserTickets(val) 475 } 476 477 // duplicateExistsInUserTickets checks to see if an exact duplicated of a 478 // record already exists in a slice of user ticket records. 479 func duplicateExistsInUserTickets(record *PoolTicket, records []*PoolTicket) bool { 480 for _, r := range records { 481 if *r == *record { 482 return true 483 } 484 } 485 return false 486 } 487 488 // recordExistsInUserTickets checks to see if a record already exists 489 // in a slice of user ticket records. If it does exist, it returns 490 // the location where it exists in the slice. 491 func recordExistsInUserTickets(record *PoolTicket, records []*PoolTicket) (bool, int) { 492 for i, r := range records { 493 if r.Ticket == record.Ticket { 494 return true, i 495 } 496 } 497 return false, 0 498 } 499 500 // updateStakePoolUserTickets updates a database entry for a pool user's tickets. 501 // The function pulls the current entry in the database, checks to see if the 502 // ticket is already there, updates it accordingly, or adds it to the list of 503 // tickets. 504 func updateStakePoolUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *PoolTicket) error { 505 // Fetch the current content of the key. 506 // Possible buggy behaviour: If deserialization fails, 507 // we won't detect it here. We assume we're throwing 508 // ErrPoolUserTicketsNotFound. 509 oldRecords, _ := fetchStakePoolUserTickets(ns, scriptHash) 510 511 // Don't reinsert duplicate records we already have. 512 if duplicateExistsInUserTickets(record, oldRecords) { 513 return nil 514 } 515 516 // Does this modify an old record? If so, modify the record 517 // itself and push. Otherwise, we need to insert a new 518 // record. 519 var records []*PoolTicket 520 preExists, loc := recordExistsInUserTickets(record, oldRecords) 521 if preExists { 522 records = oldRecords 523 records[loc] = record 524 } else { 525 // Either create a slice if currently nothing exists for this 526 // key in the db, or append the entry to the slice. 527 if oldRecords == nil { 528 records = make([]*PoolTicket, 1) 529 records[0] = record 530 } else { 531 records = append(oldRecords, record) 532 } 533 } 534 535 bucket := ns.NestedReadWriteBucket(metaBucketName) 536 key := make([]byte, stakePoolTicketsPrefixSize+scriptHashSize) 537 copy(key[0:stakePoolTicketsPrefixSize], stakePoolTicketsPrefix) 538 copy(key[stakePoolTicketsPrefixSize:stakePoolTicketsPrefixSize+scriptHashSize], 539 scriptHash[:]) 540 541 // Write the serialized ticket data keyed by the script. 542 serializedRecords := serializeUserTickets(records) 543 544 err := bucket.Put(key, serializedRecords) 545 if err != nil { 546 return errors.E(errors.IO, err) 547 } 548 return nil 549 } 550 551 // deserializeUserInvalTickets deserializes the passed serialized pool 552 // users invalid tickets information. 553 func deserializeUserInvalTickets(serializedTickets []byte) ([]*chainhash.Hash, error) { 554 // Cursory check to make sure that the number of records 555 // makes sense. 556 if len(serializedTickets)%chainhash.HashSize != 0 { 557 err := io.ErrUnexpectedEOF 558 return nil, err 559 } 560 561 numRecords := len(serializedTickets) / chainhash.HashSize 562 563 records := make([]*chainhash.Hash, numRecords) 564 565 // Loop through all the ssgen records, deserialize them, and 566 // store them. 567 for i := 0; i < numRecords; i++ { 568 start := i * chainhash.HashSize 569 end := (i + 1) * chainhash.HashSize 570 h, err := chainhash.NewHash(serializedTickets[start:end]) 571 if err != nil { 572 return nil, err 573 } 574 575 records[i] = h 576 } 577 578 return records, nil 579 } 580 581 // serializeUserInvalTickets returns the serialization of the passed stake pool 582 // invalid user tickets slice. 583 func serializeUserInvalTickets(records []*chainhash.Hash) []byte { 584 numRecords := len(records) 585 586 buf := make([]byte, numRecords*chainhash.HashSize) 587 588 // Serialize and write each record into the slice sequentially. 589 for i := 0; i < numRecords; i++ { 590 start := i * chainhash.HashSize 591 end := (i + 1) * chainhash.HashSize 592 copy(buf[start:end], records[i][:]) 593 } 594 595 return buf 596 } 597 598 // fetchStakePoolUserInvalTickets retrieves the list of invalid pool user tickets 599 // from the meta bucket with the given hash. 600 func fetchStakePoolUserInvalTickets(ns walletdb.ReadBucket, scriptHash [20]byte) ([]*chainhash.Hash, error) { 601 bucket := ns.NestedReadBucket(metaBucketName) 602 603 key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize) 604 copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix) 605 copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize], 606 scriptHash[:]) 607 val := bucket.Get(key) 608 if val == nil { 609 return nil, errors.E(errors.NotExist, errors.Errorf("no pool ticket for hash160 %x", &scriptHash)) 610 } 611 612 return deserializeUserInvalTickets(val) 613 } 614 615 // duplicateExistsInInvalTickets checks to see if an exact duplicated of a 616 // record already exists in a slice of invalid user ticket records. 617 func duplicateExistsInInvalTickets(record *chainhash.Hash, records []*chainhash.Hash) bool { 618 for _, r := range records { 619 if *r == *record { 620 return true 621 } 622 } 623 return false 624 } 625 626 // removeStakePoolInvalUserTickets removes the ticket hash from the inval 627 // ticket bucket. 628 func removeStakePoolInvalUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *chainhash.Hash) error { 629 // Fetch the current content of the key. 630 // Possible buggy behaviour: If deserialization fails, 631 // we won't detect it here. We assume we're throwing 632 // ErrPoolUserInvalTcktsNotFound. 633 oldRecords, _ := fetchStakePoolUserInvalTickets(ns, scriptHash) 634 635 // Don't need to remove records that don't exist. 636 if !duplicateExistsInInvalTickets(record, oldRecords) { 637 return nil 638 } 639 640 var newRecords []*chainhash.Hash 641 for i := range oldRecords { 642 if record.IsEqual(oldRecords[i]) { 643 newRecords = append(oldRecords[:i:i], oldRecords[i+1:]...) 644 } 645 } 646 647 if newRecords == nil { 648 return nil 649 } 650 651 bucket := ns.NestedReadWriteBucket(metaBucketName) 652 key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize) 653 copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix) 654 copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize], 655 scriptHash[:]) 656 657 // Write the serialized invalid user ticket hashes. 658 serializedRecords := serializeUserInvalTickets(newRecords) 659 660 err := bucket.Put(key, serializedRecords) 661 if err != nil { 662 return errors.E(errors.IO, err) 663 } 664 665 return nil 666 } 667 668 // updateStakePoolInvalUserTickets updates a database entry for a pool user's 669 // invalid tickets. The function pulls the current entry in the database, 670 // checks to see if the ticket is already there. If it is it returns, otherwise 671 // it adds it to the list of tickets. 672 func updateStakePoolInvalUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *chainhash.Hash) error { 673 // Fetch the current content of the key. 674 // Possible buggy behaviour: If deserialization fails, 675 // we won't detect it here. We assume we're throwing 676 // ErrPoolUserInvalTcktsNotFound. 677 oldRecords, _ := fetchStakePoolUserInvalTickets(ns, scriptHash) 678 679 // Don't reinsert duplicate records we already have. 680 if duplicateExistsInInvalTickets(record, oldRecords) { 681 return nil 682 } 683 684 // Either create a slice if currently nothing exists for this 685 // key in the db, or append the entry to the slice. 686 var records []*chainhash.Hash 687 if oldRecords == nil { 688 records = make([]*chainhash.Hash, 1) 689 records[0] = record 690 } else { 691 records = append(oldRecords, record) 692 } 693 694 bucket := ns.NestedReadWriteBucket(metaBucketName) 695 key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize) 696 copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix) 697 copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize], 698 scriptHash[:]) 699 700 // Write the serialized invalid user ticket hashes. 701 serializedRecords := serializeUserInvalTickets(records) 702 703 err := bucket.Put(key, serializedRecords) 704 if err != nil { 705 return errors.E(errors.IO, err) 706 } 707 return nil 708 } 709 710 // initialize creates the DB if it doesn't exist, and otherwise 711 // loads the database. 712 func initializeEmpty(ns walletdb.ReadWriteBucket) error { 713 // Initialize the buckets and main db fields as needed. 714 mainBucket, err := ns.CreateBucketIfNotExists(mainBucketName) 715 if err != nil { 716 return errors.E(errors.IO, err) 717 } 718 719 _, err = ns.CreateBucketIfNotExists(sstxRecordsBucketName) 720 if err != nil { 721 return errors.E(errors.IO, err) 722 } 723 724 _, err = ns.CreateBucketIfNotExists(ssgenRecordsBucketName) 725 if err != nil { 726 return errors.E(errors.IO, err) 727 } 728 729 _, err = ns.CreateBucketIfNotExists(ssrtxRecordsBucketName) 730 if err != nil { 731 return errors.E(errors.IO, err) 732 } 733 734 _, err = ns.CreateBucketIfNotExists(metaBucketName) 735 if err != nil { 736 return errors.E(errors.IO, err) 737 } 738 739 createBytes := mainBucket.Get(stakeStoreCreateDateName) 740 if createBytes == nil { 741 createDate := uint64(time.Now().Unix()) 742 var buf [8]byte 743 binary.LittleEndian.PutUint64(buf[:], createDate) 744 err := mainBucket.Put(stakeStoreCreateDateName, buf[:]) 745 if err != nil { 746 return errors.E(errors.IO, err) 747 } 748 } 749 750 return nil 751 }