func (w *Wallet) extendMainChain(dbtx walletdb.ReadWriteTx, block *wtxmgr.BlockHeaderData, transactions [][]byte) error { txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) log.Infof("Connecting block %v, height %v", block.BlockHash, block.SerializedHeader.Height()) err := w.TxStore.ExtendMainChain(txmgrNs, block) if err != nil { return err } // Notify interested clients of the connected block. var header wire.BlockHeader header.Deserialize(bytes.NewReader(block.SerializedHeader[:])) w.NtfnServer.notifyAttachedBlock(dbtx, &header, &block.BlockHash) blockMeta, err := w.TxStore.GetBlockMetaForHash(txmgrNs, &block.BlockHash) if err != nil { return err } for _, serializedTx := range transactions { err = w.processTransaction(dbtx, serializedTx, &block.SerializedHeader, &blockMeta) if err != nil { return err } } return nil }
func makeBlockMeta(h *wire.BlockHeader) *BlockMeta { return &BlockMeta{ Block: Block{ Hash: h.BlockSha(), Height: int32(h.Height), }, Time: time.Time{}, } }
func makeHeaderData(h *wire.BlockHeader) BlockHeaderData { var b bytes.Buffer err := h.Serialize(&b) if err != nil { panic(err) } d := BlockHeaderData{BlockHash: h.BlockSha()} copy(d.SerializedHeader[:], b.Bytes()) return d }
// FetchBlockHeaderBySha - return a ShaHash func (db *LevelDb) FetchBlockHeaderBySha(sha *chainhash.Hash) (bh *wire.BlockHeader, err error) { db.dbLock.Lock() defer db.dbLock.Unlock() // Read the raw block from the database. buf, _, err := db.fetchSha(sha) if err != nil { return nil, err } // Only deserialize the header portion and ensure the transaction count // is zero since this is a standalone header. var blockHeader wire.BlockHeader err = blockHeader.Deserialize(bytes.NewReader(buf)) if err != nil { return nil, err } bh = &blockHeader return bh, err }
func TestBlockHeaderHashing(t *testing.T) { dummyHeader := "0000000049e0b48ade043f729d60095ed92642d96096fe6aba42f2eda" + "632d461591a152267dc840ff27602ce1968a81eb30a43423517207617a0150b56c4f72" + "b803e497f00000000000000000000000000000000000000000000000000000000000000" + "00010000000000000000000000b7000000ffff7f20204e0000000000005800000060010" + "0008b990956000000000000000000000000000000000000000000000000000000000000" + "0000000000000000ABCD" // This hash has reversed endianness compared to what chainhash spits out. hashStr := "0d40d58703482d81d711be0ffc1b313788d3c3937e1617e4876661d33a8c4c41" hashB, _ := hex.DecodeString(hashStr) hash, _ := chainhash.NewHash(hashB) vecH, _ := hex.DecodeString(dummyHeader) r := bytes.NewReader(vecH) var bh wire.BlockHeader bh.Deserialize(r) hash2 := bh.BlockSha() if !hash2.IsEqual(hash) { t.Errorf("wrong block sha returned (want %v, got %v)", hash, hash2) } }
// TestBlockHeaderSerialize tests BlockHeader serialize and deserialize. func TestBlockHeaderSerialize(t *testing.T) { nonce := uint32(123123) // 0x1e0f3 // baseBlockHdr is used in the various tests as a baseline BlockHeader. bits := uint32(0x1d00ffff) baseBlockHdr := &wire.BlockHeader{ Version: 1, PrevBlock: mainNetGenesisHash, MerkleRoot: mainNetGenesisMerkleRoot, StakeRoot: mainNetGenesisMerkleRoot, VoteBits: uint16(0x0000), FinalState: [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Voters: uint16(0x0000), FreshStake: uint8(0x00), Revocations: uint8(0x00), Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST Bits: bits, SBits: int64(0x0000000000000000), Nonce: nonce, Height: uint32(0), Size: uint32(0), } // baseBlockHdrEncoded is the wire encoded bytes of baseBlockHdr. baseBlockHdrEncoded := []byte{ 0x01, 0x00, 0x00, 0x00, // Version 1 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f, 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // StakeRoot 0x00, 0x00, // VoteBits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FinalState 0x00, 0x00, // Voters 0x00, // FreshStake 0x00, // Revocations 0x00, 0x00, 0x00, 0x00, //Poolsize 0xff, 0xff, 0x00, 0x1d, // Bits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SBits 0x00, 0x00, 0x00, 0x00, // Height 0x00, 0x00, 0x00, 0x00, // Size 0x29, 0xab, 0x5f, 0x49, // Timestamp 0xf3, 0xe0, 0x01, 0x00, // Nonce 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ExtraData 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } tests := []struct { in *wire.BlockHeader // Data to encode out *wire.BlockHeader // Expected decoded data buf []byte // Serialized data }{ { baseBlockHdr, baseBlockHdr, baseBlockHdrEncoded, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the block header. var buf bytes.Buffer err := test.in.Serialize(&buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue } if !bytes.Equal(buf.Bytes(), test.buf) { t.Errorf("Serialize #%d\n got: %s want: %s", i, spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) continue } // Deserialize the block header. var bh wire.BlockHeader rbuf := bytes.NewReader(test.buf) err = bh.Deserialize(rbuf) if err != nil { t.Errorf("Deserialize #%d error %v", i, err) continue } if !reflect.DeepEqual(&bh, test.out) { t.Errorf("Deserialize #%d\n got: %s want: %s", i, spew.Sdump(&bh), spew.Sdump(test.out)) continue } } }
// onBlockConnected is the entry point for processing chain server // blockconnected notifications. func (w *Wallet) onBlockConnected(dbtx walletdb.ReadWriteTx, serializedBlockHeader []byte, transactions [][]byte) error { var blockHeader wire.BlockHeader err := blockHeader.Deserialize(bytes.NewReader(serializedBlockHeader)) if err != nil { return err } block := wtxmgr.BlockHeaderData{BlockHash: blockHeader.BlockSha()} err = copyHeaderSliceToArray(&block.SerializedHeader, serializedBlockHeader) if err != nil { return err } w.reorganizingLock.Lock() reorg, reorgToHash := w.reorganizing, w.reorganizeToHash w.reorganizingLock.Unlock() if reorg { // add to side chain scBlock := sideChainBlock{ transactions: transactions, headerData: block, } w.sideChain = append(w.sideChain, scBlock) log.Infof("Adding block %v (height %v) to sidechain", block.BlockHash, block.SerializedHeader.Height()) if block.BlockHash != reorgToHash { // Nothing left to do until the later blocks are // received. return nil } err = w.switchToSideChain(dbtx) if err != nil { return err } w.sideChain = nil w.reorganizingLock.Lock() w.reorganizing = false w.reorganizingLock.Unlock() log.Infof("Wallet reorganization to block %v complete", reorgToHash) } else { err = w.extendMainChain(dbtx, &block, transactions) if err != nil { return err } } height := int32(blockHeader.Height) // Handle automatic ticket purchasing if enabled. This function should // not error due to an error purchasing tickets (several tickets may be // have been purhcased and successfully published, as well as addresses // created and used), so just log it instead. err = w.handleTicketPurchases(dbtx, height) switch err.(type) { case nil: case txauthor.InsufficientFundsError: log.Debugf("Insufficient funds to auto-purchase maximum number " + "of tickets") default: log.Errorf("Failed to perform automatic picket purchasing: %v", err) } // Prune all expired transactions and all stake tickets that no longer // meet the minimum stake difficulty. txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) err = w.TxStore.PruneUnconfirmed(txmgrNs, height, blockHeader.SBits) if err != nil { log.Errorf("Failed to prune unconfirmed transactions when "+ "connecting block height %v: %s", height, err.Error()) } return nil }
func Test_dupTx(t *testing.T) { // Ignore db remove errors since it means we didn't have an old one. dbname := fmt.Sprintf("tstdbdup0") dbnamever := dbname + ".ver" _ = os.RemoveAll(dbname) _ = os.RemoveAll(dbnamever) db, err := database.CreateDB("leveldb", dbname) if err != nil { t.Errorf("Failed to open test database %v", err) return } defer os.RemoveAll(dbname) defer os.RemoveAll(dbnamever) defer func() { if err := db.Close(); err != nil { t.Errorf("Close: unexpected error: %v", err) } }() testdatafile := filepath.Join("../", "../blockchain/testdata", "blocks0to168.bz2") blocks, err := loadBlocks(t, testdatafile) if err != nil { t.Errorf("Unable to load blocks from test data for: %v", err) return } var lastSha *chainhash.Hash // Populate with the fisrt 256 blocks, so we have blocks to 'mess with' err = nil out: for height := int64(0); height < int64(len(blocks)); height++ { block := blocks[height] if height != 0 { // except for NoVerify which does not allow lookups check inputs mblock := block.MsgBlock() //t.Errorf("%v", blockchain.DebugBlockString(block)) parentBlock := blocks[height-1] mParentBlock := parentBlock.MsgBlock() var txneededList []*chainhash.Hash opSpentInBlock := make(map[wire.OutPoint]struct{}) if dcrutil.IsFlagSet16(dcrutil.BlockValid, mParentBlock.Header.VoteBits) { for _, tx := range mParentBlock.Transactions { for _, txin := range tx.TxIn { if txin.PreviousOutPoint.Index == uint32(4294967295) { continue } if existsInOwnBlockRegTree(mParentBlock, txin.PreviousOutPoint.Hash) { _, used := opSpentInBlock[txin.PreviousOutPoint] if !used { // Origin tx is in the block and so hasn't been // added yet, continue opSpentInBlock[txin.PreviousOutPoint] = struct{}{} continue } else { t.Errorf("output ref %v attempted double spend of previously spend output", txin.PreviousOutPoint) } } origintxsha := &txin.PreviousOutPoint.Hash txneededList = append(txneededList, origintxsha) exists, err := db.ExistsTxSha(origintxsha) if err != nil { t.Errorf("ExistsTxSha: unexpected error %v ", err) } if !exists { t.Errorf("referenced tx not found %v (height %v)", origintxsha, height) } _, err = db.FetchTxBySha(origintxsha) if err != nil { t.Errorf("referenced tx not found %v err %v ", origintxsha, err) } } } } for _, stx := range mblock.STransactions { for _, txin := range stx.TxIn { if txin.PreviousOutPoint.Index == uint32(4294967295) { continue } if existsInOwnBlockRegTree(mParentBlock, txin.PreviousOutPoint.Hash) { _, used := opSpentInBlock[txin.PreviousOutPoint] if !used { // Origin tx is in the block and so hasn't been // added yet, continue opSpentInBlock[txin.PreviousOutPoint] = struct{}{} continue } else { t.Errorf("output ref %v attempted double spend of previously spend output", txin.PreviousOutPoint) } } origintxsha := &txin.PreviousOutPoint.Hash txneededList = append(txneededList, origintxsha) exists, err := db.ExistsTxSha(origintxsha) if err != nil { t.Errorf("ExistsTxSha: unexpected error %v ", err) } if !exists { t.Errorf("referenced tx not found %v", origintxsha) } _, err = db.FetchTxBySha(origintxsha) if err != nil { t.Errorf("referenced tx not found %v err %v ", origintxsha, err) } } } txlist := db.FetchUnSpentTxByShaList(txneededList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v ", txe.Sha, txe.Err) break out } } } newheight, err := db.InsertBlock(block) if err != nil { t.Errorf("failed to insert block %v err %v", height, err) break out } if newheight != height { t.Errorf("height mismatch expect %v returned %v", height, newheight) break out } newSha, blkid, err := db.NewestSha() if err != nil { t.Errorf("failed to obtain latest sha %v %v", height, err) } if blkid != height { t.Errorf("height doe not match latest block height %v %v %v", blkid, height, err) } blkSha := block.Sha() if *newSha != *blkSha { t.Errorf("Newest block sha does not match freshly inserted one %v %v %v ", newSha, blkSha, err) } lastSha = blkSha } // generate a new block based on the last sha // these block are not verified, so there are a bunch of garbage fields // in the 'generated' block. var bh wire.BlockHeader bh.Version = 0 bh.PrevBlock = *lastSha // Bits, Nonce are not filled in mblk := wire.NewMsgBlock(&bh) hash, _ := chainhash.NewHashFromStr("c23953c56cb2ef8e4698e3ed3b0fc4c837754d3cd16485192d893e35f32626b4") po := wire.NewOutPoint(hash, 0, dcrutil.TxTreeRegular) txI := wire.NewTxIn(po, []byte("garbage")) txO := wire.NewTxOut(50000000, []byte("garbageout")) var tx wire.MsgTx tx.AddTxIn(txI) tx.AddTxOut(txO) mblk.AddTransaction(&tx) blk := dcrutil.NewBlock(mblk) fetchList := []*chainhash.Hash{hash} listReply := db.FetchUnSpentTxByShaList(fetchList) for _, lr := range listReply { if lr.Err != nil { t.Errorf("sha %v spent %v err %v\n", lr.Sha, lr.TxSpent, lr.Err) } } _, err = db.InsertBlock(blk) if err != nil { t.Errorf("failed to insert phony block %v", err) } // ok, did it 'spend' the tx ? listReply = db.FetchUnSpentTxByShaList(fetchList) for _, lr := range listReply { if lr.Err != nil && lr.Err != database.ErrTxShaMissing { t.Errorf("sha %v spent %v err %v\n", lr.Sha, lr.TxSpent, lr.Err) } } txlist := blk.Transactions() for _, tx := range txlist { txsha := tx.Sha() txReply, err := db.FetchTxBySha(txsha) if err != nil { t.Errorf("fully spent lookup %v err %v\n", hash, err) } else { for _, lr := range txReply { if lr.Err != nil { t.Errorf("stx %v spent %v err %v\n", lr.Sha, lr.TxSpent, lr.Err) } } } } err = db.DropAfterBlockBySha(lastSha) if err != nil { t.Errorf("failed to drop spending block %v", err) } }
// TestBlockHeaderWire tests the BlockHeader wire encode and decode for various // protocol versions. func TestBlockHeaderWire(t *testing.T) { nonce := uint32(123123) // 0x1e0f3 pver := uint32(70001) /*bh := dcrwire.NewBlockHeader( &hash, &merkleHash, &merkleHash, // stakeRoot votebits, winner, overflow, voters, freshstake, revocations, bits, sbits, nonce, height, size)*/ // baseBlockHdr is used in the various tests as a baseline BlockHeader. bits := uint32(0x1d00ffff) baseBlockHdr := &wire.BlockHeader{ Version: 1, PrevBlock: mainNetGenesisHash, MerkleRoot: mainNetGenesisMerkleRoot, StakeRoot: mainNetGenesisMerkleRoot, VoteBits: uint16(0x0000), FinalState: [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Voters: uint16(0x0000), FreshStake: uint8(0x00), Revocations: uint8(0x00), PoolSize: uint32(0x00000000), Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST Bits: bits, SBits: int64(0x0000000000000000), Nonce: nonce, Height: uint32(0), Size: uint32(0), } // baseBlockHdrEncoded is the wire encoded bytes of baseBlockHdr. baseBlockHdrEncoded := []byte{ 0x01, 0x00, 0x00, 0x00, // Version 1 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f, 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // StakeRoot 0x00, 0x00, // VoteBits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FinalState 0x00, 0x00, // Voters 0x00, // FreshStake 0x00, // Revocations 0x00, 0x00, 0x00, 0x00, //Poolsize 0xff, 0xff, 0x00, 0x1d, // Bits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SBits 0x00, 0x00, 0x00, 0x00, // Height 0x00, 0x00, 0x00, 0x00, // Size 0x29, 0xab, 0x5f, 0x49, // Timestamp 0xf3, 0xe0, 0x01, 0x00, // Nonce 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ExtraData 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } tests := []struct { in *wire.BlockHeader // Data to encode out *wire.BlockHeader // Expected decoded data buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding }{ // Latest protocol version. { baseBlockHdr, baseBlockHdr, baseBlockHdrEncoded, wire.ProtocolVersion, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Encode to wire format. // Former test (doesn't work because of capacity error) var buf bytes.Buffer err := wire.TstWriteBlockHeader(&buf, test.pver, test.in) if err != nil { t.Errorf("writeBlockHeader #%d error %v", i, err) continue } if !bytes.Equal(buf.Bytes(), test.buf) { t.Errorf("writeBlockHeader #%d\n got: %s want: %s", i, spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) continue } buf.Reset() err = test.in.BtcEncode(&buf, pver) if err != nil { t.Errorf("BtcEncode #%d error %v", i, err) continue } if !bytes.Equal(buf.Bytes(), test.buf) { t.Errorf("BtcEncode #%d\n got: %s want: %s", i, spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) continue } // Decode the block header from wire format. var bh wire.BlockHeader rbuf := bytes.NewReader(test.buf) err = wire.TstReadBlockHeader(rbuf, test.pver, &bh) if err != nil { t.Errorf("readBlockHeader #%d error %v", i, err) continue } if !reflect.DeepEqual(&bh, test.out) { t.Errorf("readBlockHeader #%d\n got: %s want: %s", i, spew.Sdump(&bh), spew.Sdump(test.out)) continue } rbuf = bytes.NewReader(test.buf) err = bh.BtcDecode(rbuf, pver) if err != nil { t.Errorf("BtcDecode #%d error %v", i, err) continue } if !reflect.DeepEqual(&bh, test.out) { t.Errorf("BtcDecode #%d\n got: %s want: %s", i, spew.Sdump(&bh), spew.Sdump(test.out)) continue } } }
// disconnectNode disconnects a stake node from itself and returns the state of // the parent node. The database transaction should be included if the // UndoTicketDataSlice or tickets are nil in order to look up the undo data or // tickets from the database. func disconnectNode(node *Node, parentHeader wire.BlockHeader, parentUtds UndoTicketDataSlice, parentTickets []chainhash.Hash, dbTx database.Tx) (*Node, error) { // Edge case for the parent being the genesis block. if node.height == 1 { return genesisNode(node.params), nil } if node == nil { return nil, fmt.Errorf("missing stake node pointer input when " + "disconnecting") } // The undo ticket slice is normally stored in memory for the most // recent blocks and the sidechain, but it may be the case that it // is missing because it's in the mainchain and very old (thus // outside the node cache). In this case, restore this data from // disk. if parentUtds == nil || parentTickets == nil { if dbTx == nil { return nil, stakeRuleError(ErrMissingDatabaseTx, "needed to "+ "look up undo data in the database, but no dbtx passed") } var err error parentUtds, err = ticketdb.DbFetchBlockUndoData(dbTx, node.height-1) if err != nil { return nil, err } parentTickets, err = ticketdb.DbFetchNewTickets(dbTx, node.height-1) if err != nil { return nil, err } } restoredNode := &Node{ height: node.height - 1, liveTickets: node.liveTickets, missedTickets: node.missedTickets, revokedTickets: node.revokedTickets, databaseUndoUpdate: parentUtds, databaseBlockTickets: parentTickets, nextWinners: make([]chainhash.Hash, 0), params: node.params, } // Iterate through the block undo data and write all database // changes to the respective treap, reversing all the changes // added when the child block was added to the chain. stateBuffer := make([]byte, 0, (node.params.TicketsPerBlock+1)*chainhash.HashSize) for _, undo := range node.databaseUndoUpdate { var err error k := tickettreap.Key(undo.TicketHash) v := &tickettreap.Value{ Height: undo.TicketHeight, Missed: undo.Missed, Revoked: undo.Revoked, Spent: undo.Spent, Expired: undo.Expired, } switch { // All flags are unset; this is a newly added ticket. // Remove it from the list of live tickets. case !undo.Missed && !undo.Revoked && !undo.Spent: restoredNode.liveTickets, err = safeDelete(restoredNode.liveTickets, k) if err != nil { return nil, err } // The ticket was missed and revoked. It needs to // be moved from the revoked ticket treap to the // missed ticket treap. case undo.Missed && undo.Revoked: v.Revoked = false restoredNode.revokedTickets, err = safeDelete(restoredNode.revokedTickets, k) if err != nil { return nil, err } restoredNode.missedTickets, err = safePut(restoredNode.missedTickets, k, v) if err != nil { return nil, err } // The ticket was missed and was previously live. // Remove it from the missed tickets bucket and // move it to the live tickets bucket. case undo.Missed && !undo.Revoked: // Expired tickets could never have been // winners. if !undo.Expired { restoredNode.nextWinners = append(restoredNode.nextWinners, undo.TicketHash) stateBuffer = append(stateBuffer, undo.TicketHash[:]...) } else { v.Expired = false } v.Missed = false restoredNode.missedTickets, err = safeDelete(restoredNode.missedTickets, k) if err != nil { return nil, err } restoredNode.liveTickets, err = safePut(restoredNode.liveTickets, k, v) if err != nil { return nil, err } // The ticket was spent. Reinsert it into the live // tickets treap and add it to the list of next // winners. case undo.Spent: v.Spent = false restoredNode.nextWinners = append(restoredNode.nextWinners, undo.TicketHash) stateBuffer = append(stateBuffer, undo.TicketHash[:]...) restoredNode.liveTickets, err = safePut(restoredNode.liveTickets, k, v) if err != nil { return nil, err } default: return nil, stakeRuleError(ErrMemoryCorruption, "unknown ticket state in undo data") } } if node.height >= uint32(node.params.StakeValidationHeight) { phB, err := parentHeader.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(phB) _, err = findTicketIdxs(int64(restoredNode.liveTickets.Len()), int(node.params.TicketsPerBlock), prng) if err != nil { return nil, err } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(restoredNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } return restoredNode, nil }
// connectNode connects a child to a parent stake node, returning the // modified stake node for the child. It is important to keep in mind that // the argument node is the parent node, and that the child stake node is // returned after subsequent modification of the parent node's immutable // data. func connectNode(node *Node, header wire.BlockHeader, ticketsSpentInBlock, revokedTickets, newTickets []chainhash.Hash) (*Node, error) { if node == nil { return nil, fmt.Errorf("missing stake node pointer input when connecting") } connectedNode := &Node{ height: node.height + 1, liveTickets: node.liveTickets, missedTickets: node.missedTickets, revokedTickets: node.revokedTickets, databaseUndoUpdate: make(UndoTicketDataSlice, 0), databaseBlockTickets: newTickets, nextWinners: make([]chainhash.Hash, 0), params: node.params, } // We only have to deal with voted related issues and expiry after // StakeEnabledHeight. var err error if connectedNode.height >= uint32(connectedNode.params.StakeEnabledHeight) { // Basic sanity check. for i := range ticketsSpentInBlock { if !hashInSlice(ticketsSpentInBlock[i], node.nextWinners) { return nil, stakeRuleError(ErrUnknownTicketSpent, fmt.Sprintf("unknown ticket %v spent in block", ticketsSpentInBlock[i])) } } // Iterate through all possible winners and construct the undo data, // updating the live and missed ticket treaps as necessary. We need // to copy the value here so we don't modify it in the previous treap. for _, ticket := range node.nextWinners { k := tickettreap.Key(ticket) v, err := safeGet(connectedNode.liveTickets, k) if err != nil { return nil, err } // If it's spent in this block, mark it as being spent. Otherwise, // it was missed. Spent tickets are dropped from the live ticket // bucket, while missed tickets are pushed to the missed ticket // bucket. Because we already know from the above check that the // ticket should still be in the live tickets treap, we probably // do not have to use the safe delete functions, but do so anyway // just to be safe. if hashInSlice(ticket, ticketsSpentInBlock) { v.Spent = true v.Missed = false connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, k) if err != nil { return nil, err } } else { v.Spent = false v.Missed = true connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, k) if err != nil { return nil, err } connectedNode.missedTickets, err = safePut(connectedNode.missedTickets, k, v) if err != nil { return nil, err } } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: ticket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // Find the expiring tickets and drop them as well. We already know what // the winners are from the cached information in the previous block, so // no drop the results of that here. toExpireHeight := uint32(0) if connectedNode.height > uint32(connectedNode.params.TicketExpiry) { toExpireHeight = connectedNode.height - uint32(connectedNode.params.TicketExpiry) } expired := fetchExpired(toExpireHeight, connectedNode.liveTickets) for _, treapKey := range expired { v, err := safeGet(connectedNode.liveTickets, *treapKey) if err != nil { return nil, err } v.Missed = true v.Expired = true connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, *treapKey) if err != nil { return nil, err } connectedNode.missedTickets, err = safePut(connectedNode.missedTickets, *treapKey, v) if err != nil { return nil, err } ticketHash := chainhash.Hash(*treapKey) connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: ticketHash, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // Process all the revocations, moving them from the missed to the // revoked treap and recording them in the undo data. for _, revokedTicket := range revokedTickets { v, err := safeGet(connectedNode.missedTickets, tickettreap.Key(revokedTicket)) if err != nil { return nil, err } v.Revoked = true connectedNode.missedTickets, err = safeDelete(connectedNode.missedTickets, tickettreap.Key(revokedTicket)) if err != nil { return nil, err } connectedNode.revokedTickets, err = safePut(connectedNode.revokedTickets, tickettreap.Key(revokedTicket), v) if err != nil { return nil, err } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: revokedTicket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } } // Add all the new tickets. for _, newTicket := range newTickets { k := tickettreap.Key(newTicket) v := &tickettreap.Value{ Height: connectedNode.height, Missed: false, Revoked: false, Spent: false, Expired: false, } connectedNode.liveTickets, err = safePut(connectedNode.liveTickets, k, v) if err != nil { return nil, err } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: newTicket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // The first block voted on is at StakeEnabledHeight, so begin calculating // winners at the block before StakeEnabledHeight. if connectedNode.height >= uint32(connectedNode.params.StakeValidationHeight-1) { // Find the next set of winners. hB, err := header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(hB) idxs, err := findTicketIdxs(int64(connectedNode.liveTickets.Len()), int(connectedNode.params.TicketsPerBlock), prng) if err != nil { return nil, err } stateBuffer := make([]byte, 0, (connectedNode.params.TicketsPerBlock+1)*chainhash.HashSize) nextWinnersKeys, err := fetchWinners(idxs, connectedNode.liveTickets) if err != nil { return nil, err } for _, treapKey := range nextWinnersKeys { ticketHash := chainhash.Hash(*treapKey) connectedNode.nextWinners = append(connectedNode.nextWinners, ticketHash) stateBuffer = append(stateBuffer, ticketHash[:]...) } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(connectedNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } return connectedNode, nil }
// LoadBestNode is used when the blockchain is initialized, to get the initial // stake node from the database bucket. The blockchain must pass the height // and the blockHash to confirm that the ticket database is on the same // location in the blockchain as the blockchain itself. This function also // checks to ensure that the database has not failed the upgrade process and // reports the current version. Upgrades are also handled by this function, // when they become applicable. func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, header wire.BlockHeader, params *chaincfg.Params) (*Node, error) { info, err := ticketdb.DbFetchDatabaseInfo(dbTx) if err != nil { return nil, err } // Compare the tip and make sure it matches. state, err := ticketdb.DbFetchBestState(dbTx) if err != nil { return nil, err } if state.Hash != blockHash || state.Height != height { return nil, stakeRuleError(ErrDatabaseCorrupt, "best state corruption") } // Restore the best node treaps form the database. node := new(Node) node.height = height node.params = params node.liveTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.LiveTicketsBucketName) if err != nil { return nil, err } if node.liveTickets.Len() != int(state.Live) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("live tickets corruption (got "+ "%v in state but loaded %v)", int(state.Live), node.liveTickets.Len())) } node.missedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.MissedTicketsBucketName) if err != nil { return nil, err } if node.missedTickets.Len() != int(state.Missed) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("missed tickets corruption (got "+ "%v in state but loaded %v)", int(state.Missed), node.missedTickets.Len())) } node.revokedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.RevokedTicketsBucketName) if err != nil { return nil, err } if node.revokedTickets.Len() != int(state.Revoked) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("revoked tickets corruption (got "+ "%v in state but loaded %v)", int(state.Revoked), node.revokedTickets.Len())) } // Restore the node undo and new tickets data. node.databaseUndoUpdate, err = ticketdb.DbFetchBlockUndoData(dbTx, height) if err != nil { return nil, err } node.databaseBlockTickets, err = ticketdb.DbFetchNewTickets(dbTx, height) if err != nil { return nil, err } // Restore the next winners for the node. node.nextWinners = make([]chainhash.Hash, 0) if node.height >= uint32(node.params.StakeValidationHeight-1) { node.nextWinners = make([]chainhash.Hash, len(state.NextWinners)) for i := range state.NextWinners { node.nextWinners[i] = state.NextWinners[i] } // Calculate the final state from the block header. stateBuffer := make([]byte, 0, (node.params.TicketsPerBlock+1)*chainhash.HashSize) for _, ticketHash := range node.nextWinners { stateBuffer = append(stateBuffer, ticketHash[:]...) } hB, err := header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(hB) _, err = findTicketIdxs(int64(node.liveTickets.Len()), int(node.params.TicketsPerBlock), prng) if err != nil { return nil, err } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(node.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } log.Infof("Stake database version %v loaded", info.Version) return node, nil }
// estimateNextStakeDifficulty returns a user-specified estimate for the next // stake difficulty, with the passed ticketsInWindow indicating the number of // fresh stake to pretend exists within this window. Optionally the user can // also override this variable with useMaxTickets, which simply plugs in the // maximum number of tickets the user can try. func (b *BlockChain) estimateNextStakeDifficulty(curNode *blockNode, ticketsInWindow int64, useMaxTickets bool) (int64, error) { alpha := b.chainParams.StakeDiffAlpha stakeDiffStartHeight := int64(b.chainParams.CoinbaseMaturity) + 1 maxRetarget := int64(b.chainParams.RetargetAdjustmentFactor) TicketPoolWeight := int64(b.chainParams.TicketPoolSizeWeight) // Number of nodes to traverse while calculating difficulty. nodesToTraverse := (b.chainParams.StakeDiffWindowSize * b.chainParams.StakeDiffWindows) // Genesis block. Block at height 1 has these parameters. if curNode == nil || curNode.height < stakeDiffStartHeight { return b.chainParams.MinimumStakeDiff, nil } // Create a fake blockchain on top of the current best node with // the number of freshly purchased tickets as indicated by the // user. oldDiff := curNode.header.SBits topNode := curNode if (curNode.height+1)%b.chainParams.StakeDiffWindowSize != 0 { nextAdjHeight := ((curNode.height / b.chainParams.StakeDiffWindowSize) + 1) * b.chainParams.StakeDiffWindowSize maxTickets := (nextAdjHeight - curNode.height) * int64(b.chainParams.MaxFreshStakePerBlock) // If the user has indicated that the automatically // calculated maximum amount of tickets should be // used, plug that in here. if useMaxTickets { ticketsInWindow = maxTickets } // Double check to make sure there isn't too much. if ticketsInWindow > maxTickets { return 0, fmt.Errorf("too much fresh stake to be used "+ "in evaluation requested; max %v, got %v", maxTickets, ticketsInWindow) } // Insert all the tickets into bogus nodes that will be // used to calculate the next difficulty below. ticketsToInsert := ticketsInWindow for i := curNode.height + 1; i < nextAdjHeight; i++ { emptyHeader := new(wire.BlockHeader) emptyHeader.Height = uint32(i) // User a constant pool size for estimate, since // this has much less fluctuation than freshStake. // TODO Use a better pool size estimate? emptyHeader.PoolSize = curNode.header.PoolSize // Insert the fake fresh stake into each block, // decrementing the amount we need to use each // time until we hit 0. freshStake := b.chainParams.MaxFreshStakePerBlock if int64(freshStake) > ticketsToInsert { freshStake = uint8(ticketsToInsert) ticketsToInsert -= ticketsToInsert } else { ticketsToInsert -= int64(b.chainParams.MaxFreshStakePerBlock) } emptyHeader.FreshStake = freshStake // Connect the header. emptyHeader.PrevBlock = topNode.hash // Make up a node hash. hB, err := emptyHeader.Bytes() if err != nil { return 0, err } emptyHeaderHash := chainhash.HashFuncH(hB) thisNode := new(blockNode) thisNode.header = *emptyHeader thisNode.hash = emptyHeaderHash thisNode.height = i thisNode.parent = topNode topNode = thisNode } } // The target size of the ticketPool in live tickets. Recast these as int64 // to avoid possible overflows for large sizes of either variable in // params. targetForTicketPool := int64(b.chainParams.TicketsPerBlock) * int64(b.chainParams.TicketPoolSize) // Initialize bigInt slice for the percentage changes for each window period // above or below the target. windowChanges := make([]*big.Int, b.chainParams.StakeDiffWindows) // Regress through all of the previous blocks and store the percent changes // per window period; use bigInts to emulate 64.32 bit fixed point. oldNode := topNode windowPeriod := int64(0) weights := uint64(0) for i := int64(0); ; i++ { // Store and reset after reaching the end of every window period. if (i+1)%b.chainParams.StakeDiffWindowSize == 0 { // First adjust based on ticketPoolSize. Skew the difference // in ticketPoolSize by max adjustment factor to help // weight ticket pool size versus tickets per block. poolSizeSkew := (int64(oldNode.header.PoolSize)- targetForTicketPool)*TicketPoolWeight + targetForTicketPool // Don't let this be negative or zero. if poolSizeSkew <= 0 { poolSizeSkew = 1 } curPoolSizeTemp := big.NewInt(poolSizeSkew) curPoolSizeTemp.Lsh(curPoolSizeTemp, 32) // Add padding targetTemp := big.NewInt(targetForTicketPool) windowAdjusted := curPoolSizeTemp.Div(curPoolSizeTemp, targetTemp) // Weight it exponentially. Be aware that this could at some point // overflow if alpha or the number of blocks used is really large. windowAdjusted = windowAdjusted.Lsh(windowAdjusted, uint((b.chainParams.StakeDiffWindows-windowPeriod)*alpha)) // Sum up all the different weights incrementally. weights += 1 << uint64((b.chainParams.StakeDiffWindows-windowPeriod)* alpha) // Store it in the slice. windowChanges[windowPeriod] = windowAdjusted // windowFreshStake = 0 windowPeriod++ } if (i + 1) == nodesToTraverse { break // Exit for loop when we hit the end. } // Get the previous block node. var err error tempNode := oldNode oldNode, err = b.getPrevNodeFromNode(oldNode) if err != nil { return 0, err } // If we're at the genesis block, reset the oldNode // so that it stays at the genesis block. if oldNode == nil { oldNode = tempNode } } // Sum up the weighted window periods. weightedSum := big.NewInt(0) for i := int64(0); i < b.chainParams.StakeDiffWindows; i++ { weightedSum.Add(weightedSum, windowChanges[i]) } // Divide by the sum of all weights. weightsBig := big.NewInt(int64(weights)) weightedSumDiv := weightedSum.Div(weightedSum, weightsBig) // Multiply by the old stake diff. oldDiffBig := big.NewInt(oldDiff) nextDiffBig := weightedSumDiv.Mul(weightedSumDiv, oldDiffBig) // Right shift to restore the original padding (restore non-fixed point). nextDiffBig = nextDiffBig.Rsh(nextDiffBig, 32) nextDiffTicketPool := nextDiffBig.Int64() // Check to see if we're over the limits for the maximum allowable retarget; // if we are, return the maximum or minimum except in the case that oldDiff // is zero. if oldDiff == 0 { // This should never really happen, but in case it does... return nextDiffTicketPool, nil } else if nextDiffTicketPool == 0 { nextDiffTicketPool = oldDiff / maxRetarget } else if (nextDiffTicketPool / oldDiff) > (maxRetarget - 1) { nextDiffTicketPool = oldDiff * maxRetarget } else if (oldDiff / nextDiffTicketPool) > (maxRetarget - 1) { nextDiffTicketPool = oldDiff / maxRetarget } // The target number of new SStx per block for any given window period. targetForWindow := b.chainParams.StakeDiffWindowSize * int64(b.chainParams.TicketsPerBlock) // Regress through all of the previous blocks and store the percent changes // per window period; use bigInts to emulate 64.32 bit fixed point. oldNode = topNode windowFreshStake := int64(0) windowPeriod = int64(0) weights = uint64(0) for i := int64(0); ; i++ { // Add the fresh stake into the store for this window period. windowFreshStake += int64(oldNode.header.FreshStake) // Store and reset after reaching the end of every window period. if (i+1)%b.chainParams.StakeDiffWindowSize == 0 { // Don't let fresh stake be zero. if windowFreshStake <= 0 { windowFreshStake = 1 } freshTemp := big.NewInt(windowFreshStake) freshTemp.Lsh(freshTemp, 32) // Add padding targetTemp := big.NewInt(targetForWindow) // Get the percentage change. windowAdjusted := freshTemp.Div(freshTemp, targetTemp) // Weight it exponentially. Be aware that this could at some point // overflow if alpha or the number of blocks used is really large. windowAdjusted = windowAdjusted.Lsh(windowAdjusted, uint((b.chainParams.StakeDiffWindows-windowPeriod)*alpha)) // Sum up all the different weights incrementally. weights += 1 << uint64((b.chainParams.StakeDiffWindows-windowPeriod)*alpha) // Store it in the slice. windowChanges[windowPeriod] = windowAdjusted windowFreshStake = 0 windowPeriod++ } if (i + 1) == nodesToTraverse { break // Exit for loop when we hit the end. } // Get the previous block node. var err error tempNode := oldNode oldNode, err = b.getPrevNodeFromNode(oldNode) if err != nil { return 0, err } // If we're at the genesis block, reset the oldNode // so that it stays at the genesis block. if oldNode == nil { oldNode = tempNode } } // Sum up the weighted window periods. weightedSum = big.NewInt(0) for i := int64(0); i < b.chainParams.StakeDiffWindows; i++ { weightedSum.Add(weightedSum, windowChanges[i]) } // Divide by the sum of all weights. weightsBig = big.NewInt(int64(weights)) weightedSumDiv = weightedSum.Div(weightedSum, weightsBig) // Multiply by the old stake diff. oldDiffBig = big.NewInt(oldDiff) nextDiffBig = weightedSumDiv.Mul(weightedSumDiv, oldDiffBig) // Right shift to restore the original padding (restore non-fixed point). nextDiffBig = nextDiffBig.Rsh(nextDiffBig, 32) nextDiffFreshStake := nextDiffBig.Int64() // Check to see if we're over the limits for the maximum allowable retarget; // if we are, return the maximum or minimum except in the case that oldDiff // is zero. if oldDiff == 0 { // This should never really happen, but in case it does... return nextDiffFreshStake, nil } else if nextDiffFreshStake == 0 { nextDiffFreshStake = oldDiff / maxRetarget } else if (nextDiffFreshStake / oldDiff) > (maxRetarget - 1) { nextDiffFreshStake = oldDiff * maxRetarget } else if (oldDiff / nextDiffFreshStake) > (maxRetarget - 1) { nextDiffFreshStake = oldDiff / maxRetarget } // Average the two differences using scaled multiplication. nextDiff := mergeDifficulty(oldDiff, nextDiffTicketPool, nextDiffFreshStake) // Check to see if we're over the limits for the maximum allowable retarget; // if we are, return the maximum or minimum except in the case that oldDiff // is zero. if oldDiff == 0 { // This should never really happen, but in case it does... return oldDiff, nil } else if nextDiff == 0 { nextDiff = oldDiff / maxRetarget } else if (nextDiff / oldDiff) > (maxRetarget - 1) { nextDiff = oldDiff * maxRetarget } else if (oldDiff / nextDiff) > (maxRetarget - 1) { nextDiff = oldDiff / maxRetarget } // If the next diff is below the network minimum, set the required stake // difficulty to the minimum. if nextDiff < b.chainParams.MinimumStakeDiff { return b.chainParams.MinimumStakeDiff, nil } return nextDiff, nil }