forked from cerc-io/ipld-eth-server
Update dependencies
- uses newer version of go-ethereum required for go1.11
This commit is contained in:
+2
-2
@@ -63,7 +63,7 @@ is by no means exhaustive:
|
||||
|
||||
* [ProcessBlock Example](http://godoc.org/github.com/btcsuite/btcd/blockchain#example-BlockChain-ProcessBlock)
|
||||
Demonstrates how to create a new chain instance and use ProcessBlock to
|
||||
attempt to attempt add a block to the chain. This example intentionally
|
||||
attempt to add a block to the chain. This example intentionally
|
||||
attempts to insert a duplicate genesis block to illustrate how an invalid
|
||||
block is handled.
|
||||
|
||||
@@ -73,7 +73,7 @@ is by no means exhaustive:
|
||||
typical hex notation.
|
||||
|
||||
* [BigToCompact Example](http://godoc.org/github.com/btcsuite/btcd/blockchain#example-BigToCompact)
|
||||
Demonstrates how to convert how to convert a target difficulty into the
|
||||
Demonstrates how to convert a target difficulty into the
|
||||
compact "bits" in a block header which represent that target difficulty.
|
||||
|
||||
## GPG Verification Key
|
||||
|
||||
+10
-9
@@ -54,23 +54,24 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
|
||||
// such as making blocks that never become part of the main chain or
|
||||
// blocks that fail to connect available for further analysis.
|
||||
err = b.db.Update(func(dbTx database.Tx) error {
|
||||
return dbMaybeStoreBlock(dbTx, block)
|
||||
return dbStoreBlock(dbTx, block)
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Create a new block node for the block and add it to the in-memory
|
||||
// block chain (could be either a side chain or the main chain).
|
||||
// Create a new block node for the block and add it to the node index. Even
|
||||
// if the block ultimately gets connected to the main chain, it starts out
|
||||
// on a side chain.
|
||||
blockHeader := &block.MsgBlock().Header
|
||||
newNode := newBlockNode(blockHeader, blockHeight)
|
||||
newNode := newBlockNode(blockHeader, prevNode)
|
||||
newNode.status = statusDataStored
|
||||
if prevNode != nil {
|
||||
newNode.parent = prevNode
|
||||
newNode.height = blockHeight
|
||||
newNode.workSum.Add(prevNode.workSum, newNode.workSum)
|
||||
}
|
||||
|
||||
b.index.AddNode(newNode)
|
||||
err = b.index.flushToDB()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Connect the passed block to the chain while respecting proper chain
|
||||
// selection according to the chain with the most proof of work. This
|
||||
|
||||
+58
-17
@@ -101,33 +101,33 @@ type blockNode struct {
|
||||
status blockStatus
|
||||
}
|
||||
|
||||
// initBlockNode initializes a block node from the given header and height. The
|
||||
// node is completely disconnected from the chain and the workSum value is just
|
||||
// the work for the passed block. The work sum must be updated accordingly when
|
||||
// the node is inserted into a chain.
|
||||
//
|
||||
// initBlockNode initializes a block node from the given header and parent node,
|
||||
// calculating the height and workSum from the respective fields on the parent.
|
||||
// This function is NOT safe for concurrent access. It must only be called when
|
||||
// initially creating a node.
|
||||
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, height int32) {
|
||||
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *blockNode) {
|
||||
*node = blockNode{
|
||||
hash: blockHeader.BlockHash(),
|
||||
workSum: CalcWork(blockHeader.Bits),
|
||||
height: height,
|
||||
version: blockHeader.Version,
|
||||
bits: blockHeader.Bits,
|
||||
nonce: blockHeader.Nonce,
|
||||
timestamp: blockHeader.Timestamp.Unix(),
|
||||
merkleRoot: blockHeader.MerkleRoot,
|
||||
}
|
||||
if parent != nil {
|
||||
node.parent = parent
|
||||
node.height = parent.height + 1
|
||||
node.workSum = node.workSum.Add(parent.workSum, node.workSum)
|
||||
}
|
||||
}
|
||||
|
||||
// newBlockNode returns a new block node for the given block header. It is
|
||||
// completely disconnected from the chain and the workSum value is just the work
|
||||
// for the passed block. The work sum must be updated accordingly when the node
|
||||
// is inserted into a chain.
|
||||
func newBlockNode(blockHeader *wire.BlockHeader, height int32) *blockNode {
|
||||
// newBlockNode returns a new block node for the given block header and parent
|
||||
// node, calculating the height and workSum from the respective fields on the
|
||||
// parent. This function is NOT safe for concurrent access.
|
||||
func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode {
|
||||
var node blockNode
|
||||
initBlockNode(&node, blockHeader, height)
|
||||
initBlockNode(&node, blockHeader, parent)
|
||||
return &node
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ func newBlockNode(blockHeader *wire.BlockHeader, height int32) *blockNode {
|
||||
// This function is safe for concurrent access.
|
||||
func (node *blockNode) Header() wire.BlockHeader {
|
||||
// No lock is needed because all accessed fields are immutable.
|
||||
prevHash := zeroHash
|
||||
prevHash := &zeroHash
|
||||
if node.parent != nil {
|
||||
prevHash = &node.parent.hash
|
||||
}
|
||||
@@ -231,6 +231,7 @@ type blockIndex struct {
|
||||
|
||||
sync.RWMutex
|
||||
index map[chainhash.Hash]*blockNode
|
||||
dirty map[*blockNode]struct{}
|
||||
}
|
||||
|
||||
// newBlockIndex returns a new empty instance of a block index. The index will
|
||||
@@ -241,6 +242,7 @@ func newBlockIndex(db database.DB, chainParams *chaincfg.Params) *blockIndex {
|
||||
db: db,
|
||||
chainParams: chainParams,
|
||||
index: make(map[chainhash.Hash]*blockNode),
|
||||
dirty: make(map[*blockNode]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,16 +267,25 @@ func (bi *blockIndex) LookupNode(hash *chainhash.Hash) *blockNode {
|
||||
return node
|
||||
}
|
||||
|
||||
// AddNode adds the provided node to the block index. Duplicate entries are not
|
||||
// checked so it is up to caller to avoid adding them.
|
||||
// AddNode adds the provided node to the block index and marks it as dirty.
|
||||
// Duplicate entries are not checked so it is up to caller to avoid adding them.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) AddNode(node *blockNode) {
|
||||
bi.Lock()
|
||||
bi.index[node.hash] = node
|
||||
bi.addNode(node)
|
||||
bi.dirty[node] = struct{}{}
|
||||
bi.Unlock()
|
||||
}
|
||||
|
||||
// addNode adds the provided node to the block index, but does not mark it as
|
||||
// dirty. This can be used while initializing the block index.
|
||||
//
|
||||
// This function is NOT safe for concurrent access.
|
||||
func (bi *blockIndex) addNode(node *blockNode) {
|
||||
bi.index[node.hash] = node
|
||||
}
|
||||
|
||||
// NodeStatus provides concurrent-safe access to the status field of a node.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
@@ -293,6 +304,7 @@ func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
|
||||
func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
|
||||
bi.Lock()
|
||||
node.status |= flags
|
||||
bi.dirty[node] = struct{}{}
|
||||
bi.Unlock()
|
||||
}
|
||||
|
||||
@@ -303,5 +315,34 @@ func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
|
||||
func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) {
|
||||
bi.Lock()
|
||||
node.status &^= flags
|
||||
bi.dirty[node] = struct{}{}
|
||||
bi.Unlock()
|
||||
}
|
||||
|
||||
// flushToDB writes all dirty block nodes to the database. If all writes
|
||||
// succeed, this clears the dirty set.
|
||||
func (bi *blockIndex) flushToDB() error {
|
||||
bi.Lock()
|
||||
if len(bi.dirty) == 0 {
|
||||
bi.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := bi.db.Update(func(dbTx database.Tx) error {
|
||||
for node := range bi.dirty {
|
||||
err := dbStoreBlockNode(dbTx, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// If write was successful, clear the dirty set.
|
||||
if err == nil {
|
||||
bi.dirty = make(map[*blockNode]struct{})
|
||||
}
|
||||
|
||||
bi.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
+258
-79
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2013-2018 The btcsuite developers
|
||||
// Copyright (c) 2015-2018 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -399,7 +400,7 @@ func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx, utxoView
|
||||
nextHeight := node.height + 1
|
||||
|
||||
for txInIndex, txIn := range mTx.TxIn {
|
||||
utxo := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash)
|
||||
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
|
||||
if utxo == nil {
|
||||
str := fmt.Sprintf("output %v referenced from "+
|
||||
"transaction %s:%d either does not exist or "+
|
||||
@@ -495,6 +496,8 @@ func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
|
||||
// passed node is the new end of the main chain. The lists will be empty if the
|
||||
// passed node is not on a side chain.
|
||||
//
|
||||
// This function may modify node statuses in the block index without flushing.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for reads).
|
||||
func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List) {
|
||||
attachNodes := list.New()
|
||||
@@ -544,20 +547,6 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List
|
||||
return detachNodes, attachNodes
|
||||
}
|
||||
|
||||
// dbMaybeStoreBlock stores the provided block in the database if it's not
|
||||
// already there.
|
||||
func dbMaybeStoreBlock(dbTx database.Tx, block *btcutil.Block) error {
|
||||
hasBlock, err := dbTx.HasBlock(block.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasBlock {
|
||||
return nil
|
||||
}
|
||||
|
||||
return dbTx.StoreBlock(block)
|
||||
}
|
||||
|
||||
// connectBlock handles connecting the passed node/block to the end of the main
|
||||
// (best) chain.
|
||||
//
|
||||
@@ -569,7 +558,9 @@ func dbMaybeStoreBlock(dbTx database.Tx, block *btcutil.Block) error {
|
||||
// it would be inefficient to repeat it.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos []spentTxOut) error {
|
||||
func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block,
|
||||
view *UtxoViewpoint, stxos []SpentTxOut) error {
|
||||
|
||||
// Make sure it's extending the end of the best chain.
|
||||
prevHash := &block.MsgBlock().Header.PrevBlock
|
||||
if !prevHash.IsEqual(&b.bestChain.Tip().hash) {
|
||||
@@ -599,6 +590,12 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U
|
||||
}
|
||||
}
|
||||
|
||||
// Write any block status changes to DB before updating best state.
|
||||
err := b.index.flushToDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate a new best state snapshot that will be used to update the
|
||||
// database and later memory if all database updates are successful.
|
||||
b.stateLock.RLock()
|
||||
@@ -611,7 +608,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U
|
||||
curTotalTxns+numTxns, node.CalcPastMedianTime())
|
||||
|
||||
// Atomically insert info into the database.
|
||||
err := b.db.Update(func(dbTx database.Tx) error {
|
||||
err = b.db.Update(func(dbTx database.Tx) error {
|
||||
// Update best block state.
|
||||
err := dbPutBestState(dbTx, state, node.workSum)
|
||||
if err != nil {
|
||||
@@ -644,7 +641,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U
|
||||
// optional indexes with the block being connected so they can
|
||||
// update themselves accordingly.
|
||||
if b.indexManager != nil {
|
||||
err := b.indexManager.ConnectBlock(dbTx, block, view)
|
||||
err := b.indexManager.ConnectBlock(dbTx, block, stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -705,6 +702,12 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
|
||||
return err
|
||||
}
|
||||
|
||||
// Write any block status changes to DB before updating best state.
|
||||
err = b.index.flushToDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate a new best state snapshot that will be used to update the
|
||||
// database and later memory if all database updates are successful.
|
||||
b.stateLock.RLock()
|
||||
@@ -739,8 +742,15 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
|
||||
return err
|
||||
}
|
||||
|
||||
// Before we delete the spend journal entry for this back,
|
||||
// we'll fetch it as is so the indexers can utilize if needed.
|
||||
stxos, err := dbFetchSpendJournalEntry(dbTx, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the transaction spend journal by removing the record
|
||||
// that contains all txos spent by the block .
|
||||
// that contains all txos spent by the block.
|
||||
err = dbRemoveSpendJournalEntry(dbTx, block.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -750,7 +760,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
|
||||
// optional indexes with the block being disconnected so they
|
||||
// can update themselves accordingly.
|
||||
if b.indexManager != nil {
|
||||
err := b.indexManager.DisconnectBlock(dbTx, block, view)
|
||||
err := b.indexManager.DisconnectBlock(dbTx, block, stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -806,15 +816,49 @@ func countSpentOutputs(block *btcutil.Block) int {
|
||||
// the chain) and nodes the are being attached must be in forwards order
|
||||
// (think pushing them onto the end of the chain).
|
||||
//
|
||||
// This function may modify node statuses in the block index without flushing.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error {
|
||||
// Nothing to do if no reorganize nodes were provided.
|
||||
if detachNodes.Len() == 0 && attachNodes.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure the provided nodes match the current best chain.
|
||||
tip := b.bestChain.Tip()
|
||||
if detachNodes.Len() != 0 {
|
||||
firstDetachNode := detachNodes.Front().Value.(*blockNode)
|
||||
if firstDetachNode.hash != tip.hash {
|
||||
return AssertError(fmt.Sprintf("reorganize nodes to detach are "+
|
||||
"not for the current best chain -- first detach node %v, "+
|
||||
"current chain %v", &firstDetachNode.hash, &tip.hash))
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the provided nodes are for the same fork point.
|
||||
if attachNodes.Len() != 0 && detachNodes.Len() != 0 {
|
||||
firstAttachNode := attachNodes.Front().Value.(*blockNode)
|
||||
lastDetachNode := detachNodes.Back().Value.(*blockNode)
|
||||
if firstAttachNode.parent.hash != lastDetachNode.parent.hash {
|
||||
return AssertError(fmt.Sprintf("reorganize nodes do not have the "+
|
||||
"same fork point -- first attach parent %v, last detach "+
|
||||
"parent %v", &firstAttachNode.parent.hash,
|
||||
&lastDetachNode.parent.hash))
|
||||
}
|
||||
}
|
||||
|
||||
// Track the old and new best chains heads.
|
||||
oldBest := tip
|
||||
newBest := tip
|
||||
|
||||
// All of the blocks to detach and related spend journal entries needed
|
||||
// to unspend transaction outputs in the blocks being disconnected must
|
||||
// be loaded from the database during the reorg check phase below and
|
||||
// then they are needed again when doing the actual database updates.
|
||||
// Rather than doing two loads, cache the loaded data into these slices.
|
||||
detachBlocks := make([]*btcutil.Block, 0, detachNodes.Len())
|
||||
detachSpentTxOuts := make([][]spentTxOut, 0, detachNodes.Len())
|
||||
detachSpentTxOuts := make([][]SpentTxOut, 0, detachNodes.Len())
|
||||
attachBlocks := make([]*btcutil.Block, 0, attachNodes.Len())
|
||||
|
||||
// Disconnect all of the blocks back to the point of the fork. This
|
||||
@@ -822,7 +866,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
// database and using that information to unspend all of the spent txos
|
||||
// and remove the utxos created by the blocks.
|
||||
view := NewUtxoViewpoint()
|
||||
view.SetBestHash(&b.bestChain.Tip().hash)
|
||||
view.SetBestHash(&oldBest.hash)
|
||||
for e := detachNodes.Front(); e != nil; e = e.Next() {
|
||||
n := e.Value.(*blockNode)
|
||||
var block *btcutil.Block
|
||||
@@ -834,6 +878,11 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n.hash != *block.Hash() {
|
||||
return AssertError(fmt.Sprintf("detach block node hash %v (height "+
|
||||
"%v) does not match previous parent block hash %v", &n.hash,
|
||||
n.height, block.Hash()))
|
||||
}
|
||||
|
||||
// Load all of the utxos referenced by the block that aren't
|
||||
// already in the view.
|
||||
@@ -844,9 +893,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
|
||||
// Load all of the spent txos for the block from the spend
|
||||
// journal.
|
||||
var stxos []spentTxOut
|
||||
var stxos []SpentTxOut
|
||||
err = b.db.View(func(dbTx database.Tx) error {
|
||||
stxos, err = dbFetchSpendJournalEntry(dbTx, block, view)
|
||||
stxos, err = dbFetchSpendJournalEntry(dbTx, block)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
@@ -857,10 +906,19 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
detachBlocks = append(detachBlocks, block)
|
||||
detachSpentTxOuts = append(detachSpentTxOuts, stxos)
|
||||
|
||||
err = view.disconnectTransactions(block, stxos)
|
||||
err = view.disconnectTransactions(b.db, block, stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newBest = n.parent
|
||||
}
|
||||
|
||||
// Set the fork point only if there are nodes to attach since otherwise
|
||||
// blocks are only being disconnected and thus there is no fork point.
|
||||
var forkNode *blockNode
|
||||
if attachNodes.Len() > 0 {
|
||||
forkNode = newBest
|
||||
}
|
||||
|
||||
// Perform several checks to verify each block that needs to be attached
|
||||
@@ -875,17 +933,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
// at least a couple of ways accomplish that rollback, but both involve
|
||||
// tweaking the chain and/or database. This approach catches these
|
||||
// issues before ever modifying the chain.
|
||||
var validationError error
|
||||
for e := attachNodes.Front(); e != nil; e = e.Next() {
|
||||
n := e.Value.(*blockNode)
|
||||
|
||||
// If any previous nodes in attachNodes failed validation,
|
||||
// mark this one as having an invalid ancestor.
|
||||
if validationError != nil {
|
||||
b.index.SetStatusFlags(n, statusInvalidAncestor)
|
||||
continue
|
||||
}
|
||||
|
||||
var block *btcutil.Block
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
@@ -911,6 +961,8 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newBest = n
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -918,23 +970,24 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
// thus will not be generated. This is done because the state
|
||||
// is not being immediately written to the database, so it is
|
||||
// not needed.
|
||||
//
|
||||
// In the case the block is determined to be invalid due to a
|
||||
// rule violation, mark it as invalid and mark all of its
|
||||
// descendants as having an invalid ancestor.
|
||||
err = b.checkConnectBlock(n, block, view, nil)
|
||||
if err != nil {
|
||||
// If the block failed validation mark it as invalid, then
|
||||
// continue to loop through remaining nodes, marking them as
|
||||
// having an invalid ancestor.
|
||||
if _, ok := err.(RuleError); ok {
|
||||
b.index.SetStatusFlags(n, statusValidateFailed)
|
||||
validationError = err
|
||||
continue
|
||||
for de := e.Next(); de != nil; de = de.Next() {
|
||||
dn := de.Value.(*blockNode)
|
||||
b.index.SetStatusFlags(dn, statusInvalidAncestor)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
b.index.SetStatusFlags(n, statusValid)
|
||||
}
|
||||
|
||||
if validationError != nil {
|
||||
return validationError
|
||||
newBest = n
|
||||
}
|
||||
|
||||
// Reset the view for the actual connection code below. This is
|
||||
@@ -959,7 +1012,8 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
|
||||
// Update the view to unspend all of the spent txos and remove
|
||||
// the utxos created by the block.
|
||||
err = view.disconnectTransactions(block, detachSpentTxOuts[i])
|
||||
err = view.disconnectTransactions(b.db, block,
|
||||
detachSpentTxOuts[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -987,7 +1041,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
// as spent and add all transactions being created by this block
|
||||
// to it. Also, provide an stxo slice so the spent txout
|
||||
// details are generated.
|
||||
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
|
||||
stxos := make([]SpentTxOut, 0, countSpentOutputs(block))
|
||||
err = view.connectTransactions(block, &stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1002,12 +1056,14 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
|
||||
// Log the point where the chain forked and old and new best chain
|
||||
// heads.
|
||||
firstAttachNode := attachNodes.Front().Value.(*blockNode)
|
||||
firstDetachNode := detachNodes.Front().Value.(*blockNode)
|
||||
lastAttachNode := attachNodes.Back().Value.(*blockNode)
|
||||
log.Infof("REORGANIZE: Chain forks at %v", firstAttachNode.parent.hash)
|
||||
log.Infof("REORGANIZE: Old best chain head was %v", firstDetachNode.hash)
|
||||
log.Infof("REORGANIZE: New best chain head is %v", lastAttachNode.hash)
|
||||
if forkNode != nil {
|
||||
log.Infof("REORGANIZE: Chain forks at %v (height %v)", forkNode.hash,
|
||||
forkNode.height)
|
||||
}
|
||||
log.Infof("REORGANIZE: Old best chain head was %v (height %v)",
|
||||
&oldBest.hash, oldBest.height)
|
||||
log.Infof("REORGANIZE: New best chain head is %v (height %v)",
|
||||
newBest.hash, newBest.height)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1029,6 +1085,17 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||
func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
|
||||
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||
|
||||
flushIndexState := func() {
|
||||
// Intentionally ignore errors writing updated node status to DB. If
|
||||
// it fails to write, it's not the end of the world. If the block is
|
||||
// valid, we flush in connectBlock and if the block is invalid, the
|
||||
// worst that can happen is we revalidate the block after a restart.
|
||||
if writeErr := b.index.flushToDB(); writeErr != nil {
|
||||
log.Warnf("Error flushing block index changes to disk: %v",
|
||||
writeErr)
|
||||
}
|
||||
}
|
||||
|
||||
// We are extending the main (best) chain with a new block. This is the
|
||||
// most common case.
|
||||
parentHash := &block.MsgBlock().Header.PrevBlock
|
||||
@@ -1041,16 +1108,22 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
||||
// actually connecting the block.
|
||||
view := NewUtxoViewpoint()
|
||||
view.SetBestHash(parentHash)
|
||||
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
|
||||
stxos := make([]SpentTxOut, 0, countSpentOutputs(block))
|
||||
if !fastAdd {
|
||||
err := b.checkConnectBlock(node, block, view, &stxos)
|
||||
if err == nil {
|
||||
b.index.SetStatusFlags(node, statusValid)
|
||||
} else if _, ok := err.(RuleError); ok {
|
||||
b.index.SetStatusFlags(node, statusValidateFailed)
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
|
||||
flushIndexState()
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(RuleError); ok {
|
||||
b.index.SetStatusFlags(node, statusValidateFailed)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
b.index.SetStatusFlags(node, statusValid)
|
||||
}
|
||||
|
||||
// In the fast add case the code to check the block connection
|
||||
@@ -1071,9 +1144,28 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
||||
// Connect the block to the main chain.
|
||||
err := b.connectBlock(node, block, view, stxos)
|
||||
if err != nil {
|
||||
// If we got hit with a rule error, then we'll mark
|
||||
// that status of the block as invalid and flush the
|
||||
// index state to disk before returning with the error.
|
||||
if _, ok := err.(RuleError); ok {
|
||||
b.index.SetStatusFlags(
|
||||
node, statusValidateFailed,
|
||||
)
|
||||
}
|
||||
|
||||
flushIndexState()
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If this is fast add, or this block node isn't yet marked as
|
||||
// valid, then we'll update its status and flush the state to
|
||||
// disk again.
|
||||
if fastAdd || !b.index.NodeStatus(node).KnownValid() {
|
||||
b.index.SetStatusFlags(node, statusValid)
|
||||
flushIndexState()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
if fastAdd {
|
||||
@@ -1111,11 +1203,16 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
||||
// Reorganize the chain.
|
||||
log.Infof("REORGANIZE: Block %v is causing a reorganize.", node.hash)
|
||||
err := b.reorganizeChain(detachNodes, attachNodes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
// Either getReorganizeNodes or reorganizeChain could have made unsaved
|
||||
// changes to the block index, so flush regardless of whether there was an
|
||||
// error. The index would only be dirty if the block failed to connect, so
|
||||
// we can ignore any errors writing.
|
||||
if writeErr := b.index.flushToDB(); writeErr != nil {
|
||||
log.Warnf("Error flushing block index changes to disk: %v", writeErr)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
// isCurrent returns whether or not the chain believes it is current. Several
|
||||
@@ -1168,25 +1265,17 @@ func (b *BlockChain) BestSnapshot() *BestState {
|
||||
return snapshot
|
||||
}
|
||||
|
||||
// FetchHeader returns the block header identified by the given hash or an error
|
||||
// if it doesn't exist.
|
||||
func (b *BlockChain) FetchHeader(hash *chainhash.Hash) (wire.BlockHeader, error) {
|
||||
// Reconstruct the header from the block index if possible.
|
||||
if node := b.index.LookupNode(hash); node != nil {
|
||||
return node.Header(), nil
|
||||
}
|
||||
|
||||
// Fall back to loading it from the database.
|
||||
var header *wire.BlockHeader
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
header, err = dbFetchHeaderByHash(dbTx, hash)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
// HeaderByHash returns the block header identified by the given hash or an
|
||||
// error if it doesn't exist. Note that this will return headers from both the
|
||||
// main and side chains.
|
||||
func (b *BlockChain) HeaderByHash(hash *chainhash.Hash) (wire.BlockHeader, error) {
|
||||
node := b.index.LookupNode(hash)
|
||||
if node == nil {
|
||||
err := fmt.Errorf("block %s is not known", hash)
|
||||
return wire.BlockHeader{}, err
|
||||
}
|
||||
return *header, nil
|
||||
|
||||
return node.Header(), nil
|
||||
}
|
||||
|
||||
// MainChainHasBlock returns whether or not the block with the given hash is in
|
||||
@@ -1302,6 +1391,87 @@ func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// HeightToHashRange returns a range of block hashes for the given start height
|
||||
// and end hash, inclusive on both ends. The hashes are for all blocks that are
|
||||
// ancestors of endHash with height greater than or equal to startHeight. The
|
||||
// end hash must belong to a block that is known to be valid.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) HeightToHashRange(startHeight int32,
|
||||
endHash *chainhash.Hash, maxResults int) ([]chainhash.Hash, error) {
|
||||
|
||||
endNode := b.index.LookupNode(endHash)
|
||||
if endNode == nil {
|
||||
return nil, fmt.Errorf("no known block header with hash %v", endHash)
|
||||
}
|
||||
if !b.index.NodeStatus(endNode).KnownValid() {
|
||||
return nil, fmt.Errorf("block %v is not yet validated", endHash)
|
||||
}
|
||||
endHeight := endNode.height
|
||||
|
||||
if startHeight < 0 {
|
||||
return nil, fmt.Errorf("start height (%d) is below 0", startHeight)
|
||||
}
|
||||
if startHeight > endHeight {
|
||||
return nil, fmt.Errorf("start height (%d) is past end height (%d)",
|
||||
startHeight, endHeight)
|
||||
}
|
||||
|
||||
resultsLength := int(endHeight - startHeight + 1)
|
||||
if resultsLength > maxResults {
|
||||
return nil, fmt.Errorf("number of results (%d) would exceed max (%d)",
|
||||
resultsLength, maxResults)
|
||||
}
|
||||
|
||||
// Walk backwards from endHeight to startHeight, collecting block hashes.
|
||||
node := endNode
|
||||
hashes := make([]chainhash.Hash, resultsLength)
|
||||
for i := resultsLength - 1; i >= 0; i-- {
|
||||
hashes[i] = node.hash
|
||||
node = node.parent
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// IntervalBlockHashes returns hashes for all blocks that are ancestors of
|
||||
// endHash where the block height is a positive multiple of interval.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) IntervalBlockHashes(endHash *chainhash.Hash, interval int,
|
||||
) ([]chainhash.Hash, error) {
|
||||
|
||||
endNode := b.index.LookupNode(endHash)
|
||||
if endNode == nil {
|
||||
return nil, fmt.Errorf("no known block header with hash %v", endHash)
|
||||
}
|
||||
if !b.index.NodeStatus(endNode).KnownValid() {
|
||||
return nil, fmt.Errorf("block %v is not yet validated", endHash)
|
||||
}
|
||||
endHeight := endNode.height
|
||||
|
||||
resultsLength := int(endHeight) / interval
|
||||
hashes := make([]chainhash.Hash, resultsLength)
|
||||
|
||||
b.bestChain.mtx.Lock()
|
||||
defer b.bestChain.mtx.Unlock()
|
||||
|
||||
blockNode := endNode
|
||||
for index := int(endHeight) / interval; index > 0; index-- {
|
||||
// Use the bestChain chainView for faster lookups once lookup intersects
|
||||
// the best chain.
|
||||
blockHeight := int32(index * interval)
|
||||
if b.bestChain.contains(blockNode) {
|
||||
blockNode = b.bestChain.nodeByHeight(blockHeight)
|
||||
} else {
|
||||
blockNode = blockNode.Ancestor(blockHeight)
|
||||
}
|
||||
|
||||
hashes[index-1] = blockNode.hash
|
||||
}
|
||||
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// locateInventory returns the node of the block after the first known block in
|
||||
// the locator along with the number of subsequent nodes needed to either reach
|
||||
// the provided stop hash or the provided max number of entries.
|
||||
@@ -1467,12 +1637,16 @@ type IndexManager interface {
|
||||
Init(*BlockChain, <-chan struct{}) error
|
||||
|
||||
// ConnectBlock is invoked when a new block has been connected to the
|
||||
// main chain.
|
||||
ConnectBlock(database.Tx, *btcutil.Block, *UtxoViewpoint) error
|
||||
// main chain. The set of output spent within a block is also passed in
|
||||
// so indexers can access the previous output scripts input spent if
|
||||
// required.
|
||||
ConnectBlock(database.Tx, *btcutil.Block, []SpentTxOut) error
|
||||
|
||||
// DisconnectBlock is invoked when a block has been disconnected from
|
||||
// the main chain.
|
||||
DisconnectBlock(database.Tx, *btcutil.Block, *UtxoViewpoint) error
|
||||
// the main chain. The set of outputs scripts that were spent within
|
||||
// this block is also returned so indexers can clean up the prior index
|
||||
// state for this block.
|
||||
DisconnectBlock(database.Tx, *btcutil.Block, []SpentTxOut) error
|
||||
}
|
||||
|
||||
// Config is a descriptor which specifies the blockchain instance configuration.
|
||||
@@ -1601,6 +1775,11 @@ func New(config *Config) (*BlockChain, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Perform any upgrades to the various chain-specific buckets as needed.
|
||||
if err := b.maybeUpgradeDbBuckets(config.Interrupt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize and catch up all of the currently active optional indexes
|
||||
// as needed.
|
||||
if config.IndexManager != nil {
|
||||
|
||||
+164
@@ -800,3 +800,167 @@ func TestLocateInventory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHeightToHashRange ensures that fetching a range of block hashes by start
|
||||
// height and end hash works as expected.
|
||||
func TestHeightToHashRange(t *testing.T) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
// \-> 16a -> 17a -> 18a (unvalidated)
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
|
||||
branch1Nodes := chainedNodes(branch0Nodes[14], 3)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
for _, node := range branch1Nodes {
|
||||
if node.height < 18 {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
}
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
startHeight int32 // locator for requested inventory
|
||||
endHash chainhash.Hash // stop hash for locator
|
||||
maxResults int // max to locate, 0 = wire const
|
||||
hashes []chainhash.Hash // expected located hashes
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "blocks below tip",
|
||||
startHeight: 11,
|
||||
endHash: branch0Nodes[14].hash,
|
||||
maxResults: 10,
|
||||
hashes: nodeHashes(branch0Nodes, 10, 11, 12, 13, 14),
|
||||
},
|
||||
{
|
||||
name: "blocks on main chain",
|
||||
startHeight: 15,
|
||||
endHash: branch0Nodes[17].hash,
|
||||
maxResults: 10,
|
||||
hashes: nodeHashes(branch0Nodes, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
name: "blocks on stale chain",
|
||||
startHeight: 15,
|
||||
endHash: branch1Nodes[1].hash,
|
||||
maxResults: 10,
|
||||
hashes: append(nodeHashes(branch0Nodes, 14),
|
||||
nodeHashes(branch1Nodes, 0, 1)...),
|
||||
},
|
||||
{
|
||||
name: "invalid start height",
|
||||
startHeight: 19,
|
||||
endHash: branch0Nodes[17].hash,
|
||||
maxResults: 10,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "too many results",
|
||||
startHeight: 1,
|
||||
endHash: branch0Nodes[17].hash,
|
||||
maxResults: 10,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "unvalidated block",
|
||||
startHeight: 15,
|
||||
endHash: branch1Nodes[2].hash,
|
||||
maxResults: 10,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
hashes, err := chain.HeightToHashRange(test.startHeight, &test.endHash,
|
||||
test.maxResults)
|
||||
if err != nil {
|
||||
if !test.expectError {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(hashes, test.hashes) {
|
||||
t.Errorf("%s: unxpected hashes -- got %v, want %v",
|
||||
test.name, hashes, test.hashes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntervalBlockHashes ensures that fetching block hashes at specified
|
||||
// intervals by end hash works as expected.
|
||||
func TestIntervalBlockHashes(t *testing.T) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
// \-> 16a -> 17a -> 18a (unvalidated)
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
|
||||
branch1Nodes := chainedNodes(branch0Nodes[14], 3)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
for _, node := range branch1Nodes {
|
||||
if node.height < 18 {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
}
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
endHash chainhash.Hash
|
||||
interval int
|
||||
hashes []chainhash.Hash
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "blocks on main chain",
|
||||
endHash: branch0Nodes[17].hash,
|
||||
interval: 8,
|
||||
hashes: nodeHashes(branch0Nodes, 7, 15),
|
||||
},
|
||||
{
|
||||
name: "blocks on stale chain",
|
||||
endHash: branch1Nodes[1].hash,
|
||||
interval: 8,
|
||||
hashes: append(nodeHashes(branch0Nodes, 7),
|
||||
nodeHashes(branch1Nodes, 0)...),
|
||||
},
|
||||
{
|
||||
name: "no results",
|
||||
endHash: branch0Nodes[17].hash,
|
||||
interval: 20,
|
||||
hashes: []chainhash.Hash{},
|
||||
},
|
||||
{
|
||||
name: "unvalidated block",
|
||||
endHash: branch1Nodes[2].hash,
|
||||
interval: 8,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
hashes, err := chain.IntervalBlockHashes(&test.endHash, test.interval)
|
||||
if err != nil {
|
||||
if !test.expectError {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(hashes, test.hashes) {
|
||||
t.Errorf("%s: unxpected hashes -- got %v, want %v",
|
||||
test.name, hashes, test.hashes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+553
-455
File diff suppressed because it is too large
Load Diff
+121
-432
@@ -11,7 +11,6 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
@@ -38,19 +37,6 @@ func TestErrNotInMainChain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// maybeDecompress decompresses the amount and public key script fields of the
|
||||
// stxo and marks it decompressed if needed.
|
||||
func (o *spentTxOut) maybeDecompress(version int32) {
|
||||
// Nothing to do if it's not compressed.
|
||||
if !o.compressed {
|
||||
return
|
||||
}
|
||||
|
||||
o.amount = int64(decompressTxOutAmount(uint64(o.amount)))
|
||||
o.pkScript = decompressScript(o.pkScript, version)
|
||||
o.compressed = false
|
||||
}
|
||||
|
||||
// TestStxoSerialization ensures serializing and deserializing spent transaction
|
||||
// output entries works as expected.
|
||||
func TestStxoSerialization(t *testing.T) {
|
||||
@@ -58,43 +44,38 @@ func TestStxoSerialization(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
stxo spentTxOut
|
||||
txVersion int32 // When the txout is not fully spent.
|
||||
stxo SpentTxOut
|
||||
serialized []byte
|
||||
}{
|
||||
// From block 170 in main blockchain.
|
||||
{
|
||||
name: "Spends last output of coinbase",
|
||||
stxo: spentTxOut{
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
isCoinBase: true,
|
||||
height: 9,
|
||||
version: 1,
|
||||
stxo: SpentTxOut{
|
||||
Amount: 5000000000,
|
||||
PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
IsCoinBase: true,
|
||||
Height: 9,
|
||||
},
|
||||
serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
serialized: hexToBytes("1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
},
|
||||
// Adapted from block 100025 in main blockchain.
|
||||
{
|
||||
name: "Spends last output of non coinbase",
|
||||
stxo: spentTxOut{
|
||||
amount: 13761000000,
|
||||
pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
|
||||
isCoinBase: false,
|
||||
height: 100024,
|
||||
version: 1,
|
||||
stxo: SpentTxOut{
|
||||
Amount: 13761000000,
|
||||
PkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
|
||||
IsCoinBase: false,
|
||||
Height: 100024,
|
||||
},
|
||||
serialized: hexToBytes("8b99700186c64700b2fb57eadf61e106a100a7445a8c3f67898841ec"),
|
||||
serialized: hexToBytes("8b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec"),
|
||||
},
|
||||
// Adapted from block 100025 in main blockchain.
|
||||
{
|
||||
name: "Does not spend last output",
|
||||
stxo: spentTxOut{
|
||||
amount: 34405000000,
|
||||
pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
|
||||
version: 1,
|
||||
name: "Does not spend last output, legacy format",
|
||||
stxo: SpentTxOut{
|
||||
Amount: 34405000000,
|
||||
PkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
|
||||
},
|
||||
txVersion: 1,
|
||||
serialized: hexToBytes("0091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
|
||||
},
|
||||
}
|
||||
@@ -104,7 +85,7 @@ func TestStxoSerialization(t *testing.T) {
|
||||
// actually serializing it is calculated properly.
|
||||
gotSize := spentTxOutSerializeSize(&test.stxo)
|
||||
if gotSize != len(test.serialized) {
|
||||
t.Errorf("spentTxOutSerializeSize (%s): did not get "+
|
||||
t.Errorf("SpentTxOutSerializeSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
gotSize, len(test.serialized))
|
||||
continue
|
||||
@@ -129,15 +110,13 @@ func TestStxoSerialization(t *testing.T) {
|
||||
|
||||
// Ensure the serialized bytes are decoded back to the expected
|
||||
// stxo.
|
||||
var gotStxo spentTxOut
|
||||
gotBytesRead, err := decodeSpentTxOut(test.serialized, &gotStxo,
|
||||
test.txVersion)
|
||||
var gotStxo SpentTxOut
|
||||
gotBytesRead, err := decodeSpentTxOut(test.serialized, &gotStxo)
|
||||
if err != nil {
|
||||
t.Errorf("decodeSpentTxOut (%s): unexpected error: %v",
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
gotStxo.maybeDecompress(test.stxo.version)
|
||||
if !reflect.DeepEqual(gotStxo, test.stxo) {
|
||||
t.Errorf("decodeSpentTxOut (%s) mismatched entries - "+
|
||||
"got %v, want %v", test.name, gotStxo, test.stxo)
|
||||
@@ -159,53 +138,43 @@ func TestStxoDecodeErrors(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
stxo spentTxOut
|
||||
txVersion int32 // When the txout is not fully spent.
|
||||
stxo SpentTxOut
|
||||
serialized []byte
|
||||
bytesRead int // Expected number of bytes read.
|
||||
errType error
|
||||
}{
|
||||
{
|
||||
name: "nothing serialized",
|
||||
stxo: spentTxOut{},
|
||||
stxo: SpentTxOut{},
|
||||
serialized: hexToBytes(""),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 0,
|
||||
},
|
||||
{
|
||||
name: "no data after header code w/o version",
|
||||
stxo: spentTxOut{},
|
||||
name: "no data after header code w/o reserved",
|
||||
stxo: SpentTxOut{},
|
||||
serialized: hexToBytes("00"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 1,
|
||||
},
|
||||
{
|
||||
name: "no data after header code with version",
|
||||
stxo: spentTxOut{},
|
||||
name: "no data after header code with reserved",
|
||||
stxo: SpentTxOut{},
|
||||
serialized: hexToBytes("13"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 1,
|
||||
},
|
||||
{
|
||||
name: "no data after version",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1301"),
|
||||
name: "no data after reserved",
|
||||
stxo: SpentTxOut{},
|
||||
serialized: hexToBytes("1300"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 2,
|
||||
},
|
||||
{
|
||||
name: "no serialized tx version and passed -1",
|
||||
stxo: spentTxOut{},
|
||||
txVersion: -1,
|
||||
serialized: hexToBytes("003205"),
|
||||
errType: AssertError(""),
|
||||
bytesRead: 1,
|
||||
},
|
||||
{
|
||||
name: "incomplete compressed txout",
|
||||
stxo: spentTxOut{},
|
||||
txVersion: 1,
|
||||
serialized: hexToBytes("0032"),
|
||||
stxo: SpentTxOut{},
|
||||
serialized: hexToBytes("1332"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 2,
|
||||
},
|
||||
@@ -214,7 +183,7 @@ func TestStxoDecodeErrors(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
// Ensure the expected error type is returned.
|
||||
gotBytesRead, err := decodeSpentTxOut(test.serialized,
|
||||
&test.stxo, test.txVersion)
|
||||
&test.stxo)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("decodeSpentTxOut (%s): expected error type "+
|
||||
"does not match - got %T, want %T", test.name,
|
||||
@@ -239,9 +208,8 @@ func TestSpendJournalSerialization(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entry []spentTxOut
|
||||
entry []SpentTxOut
|
||||
blockTxns []*wire.MsgTx
|
||||
utxoView *UtxoViewpoint
|
||||
serialized []byte
|
||||
}{
|
||||
// From block 2 in main blockchain.
|
||||
@@ -249,18 +217,16 @@ func TestSpendJournalSerialization(t *testing.T) {
|
||||
name: "No spends",
|
||||
entry: nil,
|
||||
blockTxns: nil,
|
||||
utxoView: NewUtxoViewpoint(),
|
||||
serialized: nil,
|
||||
},
|
||||
// From block 170 in main blockchain.
|
||||
{
|
||||
name: "One tx with one input spends last output of coinbase",
|
||||
entry: []spentTxOut{{
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
isCoinBase: true,
|
||||
height: 9,
|
||||
version: 1,
|
||||
entry: []SpentTxOut{{
|
||||
Amount: 5000000000,
|
||||
PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
IsCoinBase: true,
|
||||
Height: 9,
|
||||
}},
|
||||
blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
|
||||
Version: 1,
|
||||
@@ -281,22 +247,21 @@ func TestSpendJournalSerialization(t *testing.T) {
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
utxoView: NewUtxoViewpoint(),
|
||||
serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
serialized: hexToBytes("1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
},
|
||||
// Adapted from block 100025 in main blockchain.
|
||||
{
|
||||
name: "Two txns when one spends last output, one doesn't",
|
||||
entry: []spentTxOut{{
|
||||
amount: 34405000000,
|
||||
pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
|
||||
version: 1,
|
||||
entry: []SpentTxOut{{
|
||||
Amount: 34405000000,
|
||||
PkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
|
||||
IsCoinBase: false,
|
||||
Height: 100024,
|
||||
}, {
|
||||
amount: 13761000000,
|
||||
pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
|
||||
isCoinBase: false,
|
||||
height: 100024,
|
||||
version: 1,
|
||||
Amount: 13761000000,
|
||||
PkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
|
||||
IsCoinBase: false,
|
||||
Height: 100024,
|
||||
}},
|
||||
blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
|
||||
Version: 1,
|
||||
@@ -335,73 +300,7 @@ func TestSpendJournalSerialization(t *testing.T) {
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
utxoView: &UtxoViewpoint{entries: map[chainhash.Hash]*UtxoEntry{
|
||||
*newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 100024,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
1: {
|
||||
amount: 34405000000,
|
||||
pkScript: hexToBytes("76a9142084541c3931677527a7eafe56fd90207c344eb088ac"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
serialized: hexToBytes("8b99700186c64700b2fb57eadf61e106a100a7445a8c3f67898841ec0091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
|
||||
},
|
||||
// Hand crafted.
|
||||
{
|
||||
name: "One tx, two inputs from same tx, neither spend last output",
|
||||
entry: []spentTxOut{{
|
||||
amount: 165125632,
|
||||
pkScript: hexToBytes("51"),
|
||||
version: 1,
|
||||
}, {
|
||||
amount: 154370000,
|
||||
pkScript: hexToBytes("51"),
|
||||
version: 1,
|
||||
}},
|
||||
blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
|
||||
Index: 1,
|
||||
},
|
||||
SignatureScript: hexToBytes(""),
|
||||
Sequence: 0xffffffff,
|
||||
}, {
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
|
||||
Index: 2,
|
||||
},
|
||||
SignatureScript: hexToBytes(""),
|
||||
Sequence: 0xffffffff,
|
||||
}},
|
||||
TxOut: []*wire.TxOut{{
|
||||
Value: 165125632,
|
||||
PkScript: hexToBytes("51"),
|
||||
}, {
|
||||
Value: 154370000,
|
||||
PkScript: hexToBytes("51"),
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
utxoView: &UtxoViewpoint{entries: map[chainhash.Hash]*UtxoEntry{
|
||||
*newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 100000,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
0: {
|
||||
amount: 165712179,
|
||||
pkScript: hexToBytes("51"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
serialized: hexToBytes("0087bc3707510084c3d19a790751"),
|
||||
serialized: hexToBytes("8b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec8b99700091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -417,16 +316,12 @@ func TestSpendJournalSerialization(t *testing.T) {
|
||||
|
||||
// Deserialize to a spend journal entry.
|
||||
gotEntry, err := deserializeSpendJournalEntry(test.serialized,
|
||||
test.blockTxns, test.utxoView)
|
||||
test.blockTxns)
|
||||
if err != nil {
|
||||
t.Errorf("deserializeSpendJournalEntry #%d (%s) "+
|
||||
"unexpected error: %v", i, test.name, err)
|
||||
continue
|
||||
}
|
||||
for stxoIdx := range gotEntry {
|
||||
stxo := &gotEntry[stxoIdx]
|
||||
stxo.maybeDecompress(test.entry[stxoIdx].version)
|
||||
}
|
||||
|
||||
// Ensure that the deserialized spend journal entry has the
|
||||
// correct properties.
|
||||
@@ -447,7 +342,6 @@ func TestSpendJournalErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
blockTxns []*wire.MsgTx
|
||||
utxoView *UtxoViewpoint
|
||||
serialized []byte
|
||||
errType error
|
||||
}{
|
||||
@@ -466,7 +360,6 @@ func TestSpendJournalErrors(t *testing.T) {
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
utxoView: NewUtxoViewpoint(),
|
||||
serialized: hexToBytes(""),
|
||||
errType: AssertError(""),
|
||||
},
|
||||
@@ -484,7 +377,6 @@ func TestSpendJournalErrors(t *testing.T) {
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
utxoView: NewUtxoViewpoint(),
|
||||
serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
@@ -494,7 +386,7 @@ func TestSpendJournalErrors(t *testing.T) {
|
||||
// Ensure the expected error type is returned and the returned
|
||||
// slice is nil.
|
||||
stxos, err := deserializeSpendJournalEntry(test.serialized,
|
||||
test.blockTxns, test.utxoView)
|
||||
test.blockTxns)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("deserializeSpendJournalEntry (%s): expected "+
|
||||
"error type does not match - got %T, want %T",
|
||||
@@ -521,186 +413,52 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
serialized []byte
|
||||
}{
|
||||
// From tx in main blockchain:
|
||||
// 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
||||
// 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098:0
|
||||
{
|
||||
name: "Only output 0, coinbase",
|
||||
name: "height 1, coinbase",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: true,
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
blockHeight: 1,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
0: {
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
},
|
||||
},
|
||||
packedFlags: tfCoinBase,
|
||||
},
|
||||
serialized: hexToBytes("010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"),
|
||||
serialized: hexToBytes("03320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"),
|
||||
},
|
||||
// From tx in main blockchain:
|
||||
// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb
|
||||
// 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098:0
|
||||
{
|
||||
name: "Only output 1, not coinbase",
|
||||
name: "height 1, coinbase, spent",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 100001,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
1: {
|
||||
amount: 1000000,
|
||||
pkScript: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
||||
},
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("01858c21040700ee8bd501094a7d5ca318da2506de35e1cb025ddc"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// df3f3f442d9699857f7f49de4ff0b5d0f3448bec31cdc7b5bf6d25f2abd637d5
|
||||
{
|
||||
name: "Only output 2, coinbase",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: true,
|
||||
blockHeight: 99004,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
2: {
|
||||
amount: 100937281,
|
||||
pkScript: hexToBytes("76a914da33f77cee27c2a975ed5124d7e4f7f97513510188ac"),
|
||||
},
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0185843c010182b095bf4100da33f77cee27c2a975ed5124d7e4f7f975135101"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||
{
|
||||
name: "outputs 0 and 2 not coinbase",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 113931,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
0: {
|
||||
amount: 20000000,
|
||||
pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
|
||||
},
|
||||
2: {
|
||||
amount: 15000000,
|
||||
pkScript: hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"),
|
||||
},
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||
{
|
||||
name: "outputs 0 and 2, not coinbase, 1 marked spent",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 113931,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
0: {
|
||||
amount: 20000000,
|
||||
pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
|
||||
},
|
||||
1: { // This won't be serialized.
|
||||
spent: true,
|
||||
amount: 1000000,
|
||||
pkScript: hexToBytes("76a914e43031c3e46f20bf1ccee9553ce815de5a48467588ac"),
|
||||
},
|
||||
2: {
|
||||
amount: 15000000,
|
||||
pkScript: hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"),
|
||||
},
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||
{
|
||||
name: "outputs 0 and 2, not coinbase, output 2 compressed",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 113931,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
0: {
|
||||
amount: 20000000,
|
||||
pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
|
||||
},
|
||||
2: {
|
||||
// Uncompressed Amount: 15000000
|
||||
// Uncompressed PkScript: 76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac
|
||||
compressed: true,
|
||||
amount: 137,
|
||||
pkScript: hexToBytes("00b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
|
||||
},
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||
{
|
||||
name: "outputs 0 and 2, not coinbase, output 2 compressed, packed indexes reversed",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 113931,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
0: {
|
||||
amount: 20000000,
|
||||
pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
|
||||
},
|
||||
2: {
|
||||
// Uncompressed Amount: 15000000
|
||||
// Uncompressed PkScript: 76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac
|
||||
compressed: true,
|
||||
amount: 137,
|
||||
pkScript: hexToBytes("00b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
|
||||
},
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
|
||||
},
|
||||
// From tx in main blockchain:
|
||||
// 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
||||
{
|
||||
name: "Only output 0, coinbase, fully spent",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: true,
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
blockHeight: 1,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
0: {
|
||||
spent: true,
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
},
|
||||
},
|
||||
packedFlags: tfCoinBase | tfSpent,
|
||||
},
|
||||
serialized: nil,
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620
|
||||
// From tx in main blockchain:
|
||||
// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb:1
|
||||
{
|
||||
name: "Only output 22, not coinbase",
|
||||
name: "height 100001, not coinbase",
|
||||
entry: &UtxoEntry{
|
||||
version: 1,
|
||||
isCoinBase: false,
|
||||
blockHeight: 338156,
|
||||
sparseOutputs: map[uint32]*utxoOutput{
|
||||
22: {
|
||||
spent: false,
|
||||
amount: 366875659,
|
||||
pkScript: hexToBytes("a9141dd46a006572d820e448e12d2bbb38640bc718e687"),
|
||||
},
|
||||
},
|
||||
amount: 1000000,
|
||||
pkScript: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
||||
blockHeight: 100001,
|
||||
packedFlags: 0,
|
||||
},
|
||||
serialized: hexToBytes("0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6"),
|
||||
serialized: hexToBytes("8b99420700ee8bd501094a7d5ca318da2506de35e1cb025ddc"),
|
||||
},
|
||||
// From tx in main blockchain:
|
||||
// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb:1
|
||||
{
|
||||
name: "height 100001, not coinbase, spent",
|
||||
entry: &UtxoEntry{
|
||||
amount: 1000000,
|
||||
pkScript: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
||||
blockHeight: 100001,
|
||||
packedFlags: tfSpent,
|
||||
},
|
||||
serialized: nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -719,9 +477,9 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't try to deserialize if the test entry was fully spent
|
||||
// since it will have a nil serialization.
|
||||
if test.entry.IsFullySpent() {
|
||||
// Don't try to deserialize if the test entry was spent since it
|
||||
// will have a nil serialization.
|
||||
if test.entry.IsSpent() {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -733,12 +491,33 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure that the deserialized utxo entry has the same
|
||||
// properties for the containing transaction and block height.
|
||||
if utxoEntry.Version() != test.entry.Version() {
|
||||
// The deserialized entry must not be marked spent since unspent
|
||||
// entries are not serialized.
|
||||
if utxoEntry.IsSpent() {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) output should "+
|
||||
"not be marked spent", i, test.name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the deserialized entry has the same properties as the
|
||||
// ones in the test entry.
|
||||
if utxoEntry.Amount() != test.entry.Amount() {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
|
||||
"version: got %d, want %d", i, test.name,
|
||||
utxoEntry.Version(), test.entry.Version())
|
||||
"amounts: got %d, want %d", i, test.name,
|
||||
utxoEntry.Amount(), test.entry.Amount())
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(utxoEntry.PkScript(), test.entry.PkScript()) {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
|
||||
"scripts: got %x, want %x", i, test.name,
|
||||
utxoEntry.PkScript(), test.entry.PkScript())
|
||||
continue
|
||||
}
|
||||
if utxoEntry.BlockHeight() != test.entry.BlockHeight() {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
|
||||
"block height: got %d, want %d", i, test.name,
|
||||
utxoEntry.BlockHeight(), test.entry.BlockHeight())
|
||||
continue
|
||||
}
|
||||
if utxoEntry.IsCoinBase() != test.entry.IsCoinBase() {
|
||||
@@ -747,71 +526,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
utxoEntry.IsCoinBase(), test.entry.IsCoinBase())
|
||||
continue
|
||||
}
|
||||
if utxoEntry.BlockHeight() != test.entry.BlockHeight() {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
|
||||
"block height: got %d, want %d", i, test.name,
|
||||
utxoEntry.BlockHeight(),
|
||||
test.entry.BlockHeight())
|
||||
continue
|
||||
}
|
||||
if utxoEntry.IsFullySpent() != test.entry.IsFullySpent() {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
|
||||
"fully spent: got %v, want %v", i, test.name,
|
||||
utxoEntry.IsFullySpent(),
|
||||
test.entry.IsFullySpent())
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure all of the outputs in the test entry match the
|
||||
// spentness of the output in the deserialized entry and the
|
||||
// deserialized entry does not contain any additional utxos.
|
||||
var numUnspent int
|
||||
for outputIndex := range test.entry.sparseOutputs {
|
||||
gotSpent := utxoEntry.IsOutputSpent(outputIndex)
|
||||
wantSpent := test.entry.IsOutputSpent(outputIndex)
|
||||
if !wantSpent {
|
||||
numUnspent++
|
||||
}
|
||||
if gotSpent != wantSpent {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) output "+
|
||||
"#%d: mismatched spent: got %v, want "+
|
||||
"%v", i, test.name, outputIndex,
|
||||
gotSpent, wantSpent)
|
||||
continue
|
||||
|
||||
}
|
||||
}
|
||||
if len(utxoEntry.sparseOutputs) != numUnspent {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s): mismatched "+
|
||||
"number of unspent outputs: got %d, want %d", i,
|
||||
test.name, len(utxoEntry.sparseOutputs),
|
||||
numUnspent)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure all of the amounts and scripts of the utxos in the
|
||||
// deserialized entry match the ones in the test entry.
|
||||
for outputIndex := range utxoEntry.sparseOutputs {
|
||||
gotAmount := utxoEntry.AmountByIndex(outputIndex)
|
||||
wantAmount := test.entry.AmountByIndex(outputIndex)
|
||||
if gotAmount != wantAmount {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) "+
|
||||
"output #%d: mismatched amounts: got "+
|
||||
"%d, want %d", i, test.name,
|
||||
outputIndex, gotAmount, wantAmount)
|
||||
continue
|
||||
}
|
||||
|
||||
gotPkScript := utxoEntry.PkScriptByIndex(outputIndex)
|
||||
wantPkScript := test.entry.PkScriptByIndex(outputIndex)
|
||||
if !bytes.Equal(gotPkScript, wantPkScript) {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) "+
|
||||
"output #%d mismatched scripts: got "+
|
||||
"%x, want %x", i, test.name,
|
||||
outputIndex, gotPkScript, wantPkScript)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,23 +535,21 @@ func TestUtxoEntryHeaderCodeErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entry *UtxoEntry
|
||||
code uint64
|
||||
bytesRead int // Expected number of bytes read.
|
||||
errType error
|
||||
name string
|
||||
entry *UtxoEntry
|
||||
code uint64
|
||||
errType error
|
||||
}{
|
||||
{
|
||||
name: "Force assertion due to fully spent tx",
|
||||
entry: &UtxoEntry{},
|
||||
errType: AssertError(""),
|
||||
bytesRead: 0,
|
||||
name: "Force assertion due to spent output",
|
||||
entry: &UtxoEntry{packedFlags: tfSpent},
|
||||
errType: AssertError(""),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the expected error type is returned and the code is 0.
|
||||
code, gotBytesRead, err := utxoEntryHeaderCode(test.entry, 0)
|
||||
code, err := utxoEntryHeaderCode(test.entry)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("utxoEntryHeaderCode (%s): expected error "+
|
||||
"type does not match - got %T, want %T",
|
||||
@@ -849,14 +561,6 @@ func TestUtxoEntryHeaderCodeErrors(t *testing.T) {
|
||||
"on error - got %d, want 0", test.name, code)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the expected number of bytes read is returned.
|
||||
if gotBytesRead != test.bytesRead {
|
||||
t.Errorf("utxoEntryHeaderCode (%s): unexpected number "+
|
||||
"of bytes read - got %d, want %d", test.name,
|
||||
gotBytesRead, test.bytesRead)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,29 +574,14 @@ func TestUtxoEntryDeserializeErrors(t *testing.T) {
|
||||
serialized []byte
|
||||
errType error
|
||||
}{
|
||||
{
|
||||
name: "no data after version",
|
||||
serialized: hexToBytes("01"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
{
|
||||
name: "no data after block height",
|
||||
serialized: hexToBytes("0101"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
{
|
||||
name: "no data after header code",
|
||||
serialized: hexToBytes("010102"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
{
|
||||
name: "not enough bytes for unspentness bitmap",
|
||||
serialized: hexToBytes("01017800"),
|
||||
serialized: hexToBytes("02"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
{
|
||||
name: "incomplete compressed txout",
|
||||
serialized: hexToBytes("01010232"),
|
||||
serialized: hexToBytes("0232"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
}
|
||||
|
||||
+3
-8
@@ -27,16 +27,11 @@ func chainedNodes(parent *blockNode, numNodes int) []*blockNode {
|
||||
// This is invalid, but all that is needed is enough to get the
|
||||
// synthetic tests to work.
|
||||
header := wire.BlockHeader{Nonce: testNoncePrng.Uint32()}
|
||||
height := int32(0)
|
||||
if tip != nil {
|
||||
header.PrevBlock = tip.hash
|
||||
height = tip.height + 1
|
||||
}
|
||||
node := newBlockNode(&header, height)
|
||||
node.parent = tip
|
||||
tip = node
|
||||
|
||||
nodes[i] = node
|
||||
nodes[i] = newBlockNode(&header, tip)
|
||||
tip = nodes[i]
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
@@ -74,7 +69,7 @@ func zipLocators(locators ...BlockLocator) BlockLocator {
|
||||
}
|
||||
|
||||
// TestChainView ensures all of the exported functionality of chain views works
|
||||
// as intended with the expection of some special cases which are handled in
|
||||
// as intended with the exception of some special cases which are handled in
|
||||
// other tests.
|
||||
func TestChainView(t *testing.T) {
|
||||
// Construct a synthetic block index consisting of the following
|
||||
|
||||
+97
-11
@@ -190,10 +190,10 @@ func chainSetup(dbName string, params *chaincfg.Params) (*BlockChain, func(), er
|
||||
// loadUtxoView returns a utxo view loaded from a file.
|
||||
func loadUtxoView(filename string) (*UtxoViewpoint, error) {
|
||||
// The utxostore file format is:
|
||||
// <tx hash><serialized utxo len><serialized utxo>
|
||||
// <tx hash><output index><serialized utxo len><serialized utxo>
|
||||
//
|
||||
// The serialized utxo len is a little endian uint32 and the serialized
|
||||
// utxo uses the format described in chainio.go.
|
||||
// The output index and serialized utxo len are little endian uint32s
|
||||
// and the serialized utxo uses the format described in chainio.go.
|
||||
|
||||
filename = filepath.Join("testdata", filename)
|
||||
fi, err := os.Open(filename)
|
||||
@@ -223,7 +223,14 @@ func loadUtxoView(filename string) (*UtxoViewpoint, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Num of serialize utxo entry bytes.
|
||||
// Output index of the utxo entry.
|
||||
var index uint32
|
||||
err = binary.Read(r, binary.LittleEndian, &index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Num of serialized utxo entry bytes.
|
||||
var numBytes uint32
|
||||
err = binary.Read(r, binary.LittleEndian, &numBytes)
|
||||
if err != nil {
|
||||
@@ -238,16 +245,98 @@ func loadUtxoView(filename string) (*UtxoViewpoint, error) {
|
||||
}
|
||||
|
||||
// Deserialize it and add it to the view.
|
||||
utxoEntry, err := deserializeUtxoEntry(serialized)
|
||||
entry, err := deserializeUtxoEntry(serialized)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view.Entries()[hash] = utxoEntry
|
||||
view.Entries()[wire.OutPoint{Hash: hash, Index: index}] = entry
|
||||
}
|
||||
|
||||
return view, nil
|
||||
}
|
||||
|
||||
// convertUtxoStore reads a utxostore from the legacy format and writes it back
|
||||
// out using the latest format. It is only useful for converting utxostore data
|
||||
// used in the tests, which has already been done. However, the code is left
|
||||
// available for future reference.
|
||||
func convertUtxoStore(r io.Reader, w io.Writer) error {
|
||||
// The old utxostore file format was:
|
||||
// <tx hash><serialized utxo len><serialized utxo>
|
||||
//
|
||||
// The serialized utxo len was a little endian uint32 and the serialized
|
||||
// utxo uses the format described in upgrade.go.
|
||||
|
||||
littleEndian := binary.LittleEndian
|
||||
for {
|
||||
// Hash of the utxo entry.
|
||||
var hash chainhash.Hash
|
||||
_, err := io.ReadAtLeast(r, hash[:], len(hash[:]))
|
||||
if err != nil {
|
||||
// Expected EOF at the right offset.
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Num of serialized utxo entry bytes.
|
||||
var numBytes uint32
|
||||
err = binary.Read(r, littleEndian, &numBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialized utxo entry.
|
||||
serialized := make([]byte, numBytes)
|
||||
_, err = io.ReadAtLeast(r, serialized, int(numBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deserialize the entry.
|
||||
entries, err := deserializeUtxoEntryV0(serialized)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Loop through all of the utxos and write them out in the new
|
||||
// format.
|
||||
for outputIdx, entry := range entries {
|
||||
// Reserialize the entries using the new format.
|
||||
serialized, err := serializeUtxoEntry(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the hash of the utxo entry.
|
||||
_, err = w.Write(hash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the output index of the utxo entry.
|
||||
err = binary.Write(w, littleEndian, outputIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write num of serialized utxo entry bytes.
|
||||
err = binary.Write(w, littleEndian, uint32(len(serialized)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the serialized utxo.
|
||||
_, err = w.Write(serialized)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TstSetCoinbaseMaturity makes the ability to set the coinbase maturity
|
||||
// available when running tests.
|
||||
func (b *BlockChain) TstSetCoinbaseMaturity(maturity uint16) {
|
||||
@@ -261,7 +350,7 @@ func (b *BlockChain) TstSetCoinbaseMaturity(maturity uint16) {
|
||||
func newFakeChain(params *chaincfg.Params) *BlockChain {
|
||||
// Create a genesis block node and block index index populated with it
|
||||
// for use when creating the fake chain below.
|
||||
node := newBlockNode(¶ms.GenesisBlock.Header, 0)
|
||||
node := newBlockNode(¶ms.GenesisBlock.Header, nil)
|
||||
index := newBlockIndex(nil, params)
|
||||
index.AddNode(node)
|
||||
|
||||
@@ -291,8 +380,5 @@ func newFakeNode(parent *blockNode, blockVersion int32, bits uint32, timestamp t
|
||||
Bits: bits,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
node := newBlockNode(header, parent.height+1)
|
||||
node.parent = parent
|
||||
node.workSum.Add(parent.workSum, node.workSum)
|
||||
return node
|
||||
return newBlockNode(header, parent)
|
||||
}
|
||||
|
||||
+22
-39
@@ -241,7 +241,7 @@ func isPubKey(script []byte) (bool, []byte) {
|
||||
|
||||
// compressedScriptSize returns the number of bytes the passed script would take
|
||||
// when encoded with the domain specific compression algorithm described above.
|
||||
func compressedScriptSize(pkScript []byte, version int32) int {
|
||||
func compressedScriptSize(pkScript []byte) int {
|
||||
// Pay-to-pubkey-hash script.
|
||||
if valid, _ := isPubKeyHash(pkScript); valid {
|
||||
return 21
|
||||
@@ -268,7 +268,7 @@ func compressedScriptSize(pkScript []byte, version int32) int {
|
||||
// script, possibly followed by other data, and returns the number of bytes it
|
||||
// occupies taking into account the special encoding of the script size by the
|
||||
// domain specific compression algorithm described above.
|
||||
func decodeCompressedScriptSize(serialized []byte, version int32) int {
|
||||
func decodeCompressedScriptSize(serialized []byte) int {
|
||||
scriptSize, bytesRead := deserializeVLQ(serialized)
|
||||
if bytesRead == 0 {
|
||||
return 0
|
||||
@@ -296,7 +296,7 @@ func decodeCompressedScriptSize(serialized []byte, version int32) int {
|
||||
// target byte slice. The target byte slice must be at least large enough to
|
||||
// handle the number of bytes returned by the compressedScriptSize function or
|
||||
// it will panic.
|
||||
func putCompressedScript(target, pkScript []byte, version int32) int {
|
||||
func putCompressedScript(target, pkScript []byte) int {
|
||||
// Pay-to-pubkey-hash script.
|
||||
if valid, hash := isPubKeyHash(pkScript); valid {
|
||||
target[0] = cstPayToPubKeyHash
|
||||
@@ -344,7 +344,7 @@ func putCompressedScript(target, pkScript []byte, version int32) int {
|
||||
// NOTE: The script parameter must already have been proven to be long enough
|
||||
// to contain the number of bytes returned by decodeCompressedScriptSize or it
|
||||
// will panic. This is acceptable since it is only an internal function.
|
||||
func decompressScript(compressedPkScript []byte, version int32) []byte {
|
||||
func decompressScript(compressedPkScript []byte) []byte {
|
||||
// In practice this function will not be called with a zero-length or
|
||||
// nil script since the nil script encoding includes the length, however
|
||||
// the code below assumes the length exists, so just return nil now if
|
||||
@@ -542,43 +542,27 @@ func decompressTxOutAmount(amount uint64) uint64 {
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// compressedTxOutSize returns the number of bytes the passed transaction output
|
||||
// fields would take when encoded with the format described above. The
|
||||
// preCompressed flag indicates the provided amount and script are already
|
||||
// compressed. This is useful since loaded utxo entries are not decompressed
|
||||
// until the output is accessed.
|
||||
func compressedTxOutSize(amount uint64, pkScript []byte, version int32, preCompressed bool) int {
|
||||
if preCompressed {
|
||||
return serializeSizeVLQ(amount) + len(pkScript)
|
||||
}
|
||||
|
||||
// fields would take when encoded with the format described above.
|
||||
func compressedTxOutSize(amount uint64, pkScript []byte) int {
|
||||
return serializeSizeVLQ(compressTxOutAmount(amount)) +
|
||||
compressedScriptSize(pkScript, version)
|
||||
compressedScriptSize(pkScript)
|
||||
}
|
||||
|
||||
// putCompressedTxOut potentially compresses the passed amount and script
|
||||
// according to their domain specific compression algorithms and encodes them
|
||||
// directly into the passed target byte slice with the format described above.
|
||||
// The preCompressed flag indicates the provided amount and script are already
|
||||
// compressed in which case the values are not modified. This is useful since
|
||||
// loaded utxo entries are not decompressed until the output is accessed. The
|
||||
// target byte slice must be at least large enough to handle the number of bytes
|
||||
// returned by the compressedTxOutSize function or it will panic.
|
||||
func putCompressedTxOut(target []byte, amount uint64, pkScript []byte, version int32, preCompressed bool) int {
|
||||
if preCompressed {
|
||||
offset := putVLQ(target, amount)
|
||||
copy(target[offset:], pkScript)
|
||||
return offset + len(pkScript)
|
||||
}
|
||||
|
||||
// putCompressedTxOut compresses the passed amount and script according to their
|
||||
// domain specific compression algorithms and encodes them directly into the
|
||||
// passed target byte slice with the format described above. The target byte
|
||||
// slice must be at least large enough to handle the number of bytes returned by
|
||||
// the compressedTxOutSize function or it will panic.
|
||||
func putCompressedTxOut(target []byte, amount uint64, pkScript []byte) int {
|
||||
offset := putVLQ(target, compressTxOutAmount(amount))
|
||||
offset += putCompressedScript(target[offset:], pkScript, version)
|
||||
offset += putCompressedScript(target[offset:], pkScript)
|
||||
return offset
|
||||
}
|
||||
|
||||
// decodeCompressedTxOut decodes the passed compressed txout, possibly followed
|
||||
// by other data, into its compressed amount and compressed script and returns
|
||||
// them along with the number of bytes they occupied.
|
||||
func decodeCompressedTxOut(serialized []byte, version int32) (uint64, []byte, int, error) {
|
||||
// by other data, into its uncompressed amount and script and returns them along
|
||||
// with the number of bytes they occupied prior to decompression.
|
||||
func decodeCompressedTxOut(serialized []byte) (uint64, []byte, int, error) {
|
||||
// Deserialize the compressed amount and ensure there are bytes
|
||||
// remaining for the compressed script.
|
||||
compressedAmount, bytesRead := deserializeVLQ(serialized)
|
||||
@@ -589,15 +573,14 @@ func decodeCompressedTxOut(serialized []byte, version int32) (uint64, []byte, in
|
||||
|
||||
// Decode the compressed script size and ensure there are enough bytes
|
||||
// left in the slice for it.
|
||||
scriptSize := decodeCompressedScriptSize(serialized[bytesRead:], version)
|
||||
scriptSize := decodeCompressedScriptSize(serialized[bytesRead:])
|
||||
if len(serialized[bytesRead:]) < scriptSize {
|
||||
return 0, nil, bytesRead, errDeserialize("unexpected end of " +
|
||||
"data after script size")
|
||||
}
|
||||
|
||||
// Make a copy of the compressed script so the original serialized data
|
||||
// can be released as soon as possible.
|
||||
compressedScript := make([]byte, scriptSize)
|
||||
copy(compressedScript, serialized[bytesRead:bytesRead+scriptSize])
|
||||
return compressedAmount, compressedScript, bytesRead + scriptSize, nil
|
||||
// Decompress and return the amount and script.
|
||||
amount := decompressTxOutAmount(compressedAmount)
|
||||
script := decompressScript(serialized[bytesRead : bytesRead+scriptSize])
|
||||
return amount, script, bytesRead + scriptSize, nil
|
||||
}
|
||||
|
||||
+33
-77
@@ -109,79 +109,66 @@ func TestScriptCompression(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version int32
|
||||
uncompressed []byte
|
||||
compressed []byte
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
version: 1,
|
||||
uncompressed: nil,
|
||||
compressed: hexToBytes("06"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey-hash 1",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac"),
|
||||
compressed: hexToBytes("001018853670f9f3b0582c5b9ee8ce93764ac32b93"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey-hash 2",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("76a914e34cce70c86373273efcc54ce7d2a491bb4a0e8488ac"),
|
||||
compressed: hexToBytes("00e34cce70c86373273efcc54ce7d2a491bb4a0e84"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-script-hash 1",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("a914da1745e9b549bd0bfa1a569971c77eba30cd5a4b87"),
|
||||
compressed: hexToBytes("01da1745e9b549bd0bfa1a569971c77eba30cd5a4b"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-script-hash 2",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("a914f815b036d9bbbce5e9f2a00abd1bf3dc91e9551087"),
|
||||
compressed: hexToBytes("01f815b036d9bbbce5e9f2a00abd1bf3dc91e95510"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey compressed 0x02",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("2102192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4ac"),
|
||||
compressed: hexToBytes("02192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey compressed 0x03",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("2103b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65ac"),
|
||||
compressed: hexToBytes("03b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey uncompressed 0x04 even",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("4104192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453eac"),
|
||||
compressed: hexToBytes("04192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey uncompressed 0x04 odd",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
compressed: hexToBytes("0511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey invalid pubkey",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("3302aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"),
|
||||
compressed: hexToBytes("293302aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"),
|
||||
},
|
||||
{
|
||||
name: "null data",
|
||||
version: 1,
|
||||
uncompressed: hexToBytes("6a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
|
||||
compressed: hexToBytes("286a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
|
||||
},
|
||||
{
|
||||
name: "requires 2 size bytes - data push 200 bytes",
|
||||
version: 1,
|
||||
uncompressed: append(hexToBytes("4cc8"), bytes.Repeat([]byte{0x00}, 200)...),
|
||||
// [0x80, 0x50] = 208 as a variable length quantity
|
||||
// [0x4c, 0xc8] = OP_PUSHDATA1 200
|
||||
@@ -192,7 +179,7 @@ func TestScriptCompression(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
// Ensure the function to calculate the serialized size without
|
||||
// actually serializing the value is calculated properly.
|
||||
gotSize := compressedScriptSize(test.uncompressed, test.version)
|
||||
gotSize := compressedScriptSize(test.uncompressed)
|
||||
if gotSize != len(test.compressed) {
|
||||
t.Errorf("compressedScriptSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
@@ -203,7 +190,7 @@ func TestScriptCompression(t *testing.T) {
|
||||
// Ensure the script compresses to the expected bytes.
|
||||
gotCompressed := make([]byte, gotSize)
|
||||
gotBytesWritten := putCompressedScript(gotCompressed,
|
||||
test.uncompressed, test.version)
|
||||
test.uncompressed)
|
||||
if !bytes.Equal(gotCompressed, test.compressed) {
|
||||
t.Errorf("putCompressedScript (%s): did not get "+
|
||||
"expected bytes - got %x, want %x", test.name,
|
||||
@@ -220,8 +207,7 @@ func TestScriptCompression(t *testing.T) {
|
||||
|
||||
// Ensure the compressed script size is properly decoded from
|
||||
// the compressed script.
|
||||
gotDecodedSize := decodeCompressedScriptSize(test.compressed,
|
||||
test.version)
|
||||
gotDecodedSize := decodeCompressedScriptSize(test.compressed)
|
||||
if gotDecodedSize != len(test.compressed) {
|
||||
t.Errorf("decodeCompressedScriptSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
@@ -230,7 +216,7 @@ func TestScriptCompression(t *testing.T) {
|
||||
}
|
||||
|
||||
// Ensure the script decompresses to the expected bytes.
|
||||
gotDecompressed := decompressScript(test.compressed, test.version)
|
||||
gotDecompressed := decompressScript(test.compressed)
|
||||
if !bytes.Equal(gotDecompressed, test.uncompressed) {
|
||||
t.Errorf("decompressScript (%s): did not get expected "+
|
||||
"bytes - got %x, want %x", test.name,
|
||||
@@ -246,13 +232,13 @@ func TestScriptCompressionErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// A nil script must result in a decoded size of 0.
|
||||
if gotSize := decodeCompressedScriptSize(nil, 1); gotSize != 0 {
|
||||
if gotSize := decodeCompressedScriptSize(nil); gotSize != 0 {
|
||||
t.Fatalf("decodeCompressedScriptSize with nil script did not "+
|
||||
"return 0 - got %d", gotSize)
|
||||
}
|
||||
|
||||
// A nil script must result in a nil decompressed script.
|
||||
if gotScript := decompressScript(nil, 1); gotScript != nil {
|
||||
if gotScript := decompressScript(nil); gotScript != nil {
|
||||
t.Fatalf("decompressScript with nil script did not return nil "+
|
||||
"decompressed script - got %x", gotScript)
|
||||
}
|
||||
@@ -261,7 +247,7 @@ func TestScriptCompressionErrors(t *testing.T) {
|
||||
// in an invalid pubkey must result in a nil decompressed script.
|
||||
compressedScript := hexToBytes("04012d74d0cb94344c9569c2e77901573d8d" +
|
||||
"7903c3ebec3a957724895dca52c6b4")
|
||||
if gotScript := decompressScript(compressedScript, 1); gotScript != nil {
|
||||
if gotScript := decompressScript(compressedScript); gotScript != nil {
|
||||
t.Fatalf("decompressScript with compressed pay-to-"+
|
||||
"uncompressed-pubkey that is invalid did not return "+
|
||||
"nil decompressed script - got %x", gotScript)
|
||||
@@ -352,48 +338,35 @@ func TestCompressedTxOut(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
amount uint64
|
||||
compAmount uint64
|
||||
pkScript []byte
|
||||
compPkScript []byte
|
||||
version int32
|
||||
compressed []byte
|
||||
name string
|
||||
amount uint64
|
||||
pkScript []byte
|
||||
compressed []byte
|
||||
}{
|
||||
{
|
||||
name: "nulldata with 0 BTC",
|
||||
amount: 0,
|
||||
compAmount: 0,
|
||||
pkScript: hexToBytes("6a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
|
||||
compPkScript: hexToBytes("286a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
|
||||
version: 1,
|
||||
compressed: hexToBytes("00286a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
|
||||
name: "nulldata with 0 BTC",
|
||||
amount: 0,
|
||||
pkScript: hexToBytes("6a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
|
||||
compressed: hexToBytes("00286a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey-hash dust",
|
||||
amount: 546,
|
||||
compAmount: 4911,
|
||||
pkScript: hexToBytes("76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac"),
|
||||
compPkScript: hexToBytes("001018853670f9f3b0582c5b9ee8ce93764ac32b93"),
|
||||
version: 1,
|
||||
compressed: hexToBytes("a52f001018853670f9f3b0582c5b9ee8ce93764ac32b93"),
|
||||
name: "pay-to-pubkey-hash dust",
|
||||
amount: 546,
|
||||
pkScript: hexToBytes("76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac"),
|
||||
compressed: hexToBytes("a52f001018853670f9f3b0582c5b9ee8ce93764ac32b93"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey uncompressed 1 BTC",
|
||||
amount: 100000000,
|
||||
compAmount: 9,
|
||||
pkScript: hexToBytes("4104192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453eac"),
|
||||
compPkScript: hexToBytes("04192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
version: 1,
|
||||
compressed: hexToBytes("0904192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
name: "pay-to-pubkey uncompressed 1 BTC",
|
||||
amount: 100000000,
|
||||
pkScript: hexToBytes("4104192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453eac"),
|
||||
compressed: hexToBytes("0904192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the function to calculate the serialized size without
|
||||
// actually serializing the txout is calculated properly.
|
||||
gotSize := compressedTxOutSize(test.amount, test.pkScript,
|
||||
test.version, false)
|
||||
gotSize := compressedTxOutSize(test.amount, test.pkScript)
|
||||
if gotSize != len(test.compressed) {
|
||||
t.Errorf("compressedTxOutSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
@@ -404,7 +377,7 @@ func TestCompressedTxOut(t *testing.T) {
|
||||
// Ensure the txout compresses to the expected value.
|
||||
gotCompressed := make([]byte, gotSize)
|
||||
gotBytesWritten := putCompressedTxOut(gotCompressed,
|
||||
test.amount, test.pkScript, test.version, false)
|
||||
test.amount, test.pkScript)
|
||||
if !bytes.Equal(gotCompressed, test.compressed) {
|
||||
t.Errorf("compressTxOut (%s): did not get expected "+
|
||||
"bytes - got %x, want %x", test.name,
|
||||
@@ -420,24 +393,24 @@ func TestCompressedTxOut(t *testing.T) {
|
||||
}
|
||||
|
||||
// Ensure the serialized bytes are decoded back to the expected
|
||||
// compressed values.
|
||||
// uncompressed values.
|
||||
gotAmount, gotScript, gotBytesRead, err := decodeCompressedTxOut(
|
||||
test.compressed, test.version)
|
||||
test.compressed)
|
||||
if err != nil {
|
||||
t.Errorf("decodeCompressedTxOut (%s): unexpected "+
|
||||
"error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
if gotAmount != test.compAmount {
|
||||
if gotAmount != test.amount {
|
||||
t.Errorf("decodeCompressedTxOut (%s): did not get "+
|
||||
"expected amount - got %d, want %d",
|
||||
test.name, gotAmount, test.compAmount)
|
||||
test.name, gotAmount, test.amount)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(gotScript, test.compPkScript) {
|
||||
if !bytes.Equal(gotScript, test.pkScript) {
|
||||
t.Errorf("decodeCompressedTxOut (%s): did not get "+
|
||||
"expected script - got %x, want %x",
|
||||
test.name, gotScript, test.compPkScript)
|
||||
test.name, gotScript, test.pkScript)
|
||||
continue
|
||||
}
|
||||
if gotBytesRead != len(test.compressed) {
|
||||
@@ -446,23 +419,6 @@ func TestCompressedTxOut(t *testing.T) {
|
||||
test.name, gotBytesRead, len(test.compressed))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the compressed values decompress to the expected
|
||||
// txout.
|
||||
gotAmount = decompressTxOutAmount(gotAmount)
|
||||
if gotAmount != test.amount {
|
||||
t.Errorf("decompressTxOut (%s): did not get expected "+
|
||||
"value - got %d, want %d", test.name, gotAmount,
|
||||
test.amount)
|
||||
continue
|
||||
}
|
||||
gotScript = decompressScript(gotScript, test.version)
|
||||
if !bytes.Equal(gotScript, test.pkScript) {
|
||||
t.Errorf("decompressTxOut (%s): did not get expected "+
|
||||
"script - got %x, want %x", test.name,
|
||||
gotScript, test.pkScript)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +429,7 @@ func TestTxOutCompressionErrors(t *testing.T) {
|
||||
|
||||
// A compressed txout with missing compressed script must error.
|
||||
compressedTxOut := hexToBytes("00")
|
||||
_, _, _, err := decodeCompressedTxOut(compressedTxOut, 1)
|
||||
_, _, _, err := decodeCompressedTxOut(compressedTxOut)
|
||||
if !isDeserializeErr(err) {
|
||||
t.Fatalf("decodeCompressedTxOut with missing compressed script "+
|
||||
"did not return expected error type - got %T, want "+
|
||||
@@ -482,7 +438,7 @@ func TestTxOutCompressionErrors(t *testing.T) {
|
||||
|
||||
// A compressed txout with short compressed script must error.
|
||||
compressedTxOut = hexToBytes("0010")
|
||||
_, _, _, err = decodeCompressedTxOut(compressedTxOut, 1)
|
||||
_, _, _, err = decodeCompressedTxOut(compressedTxOut)
|
||||
if !isDeserializeErr(err) {
|
||||
t.Fatalf("decodeCompressedTxOut with short compressed script "+
|
||||
"did not return expected error type - got %T, want "+
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// This example demonstrates how to create a new chain instance and use
|
||||
// ProcessBlock to attempt to attempt add a block to the chain. As the package
|
||||
// ProcessBlock to attempt to add a block to the chain. As the package
|
||||
// overview documentation describes, this includes all of the Bitcoin consensus
|
||||
// rules. This example intentionally attempts to insert a duplicate genesis
|
||||
// block to illustrate how an invalid block is handled.
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ independent versions over the peer-to-peer network.
|
||||
|
||||
This package has intentionally been designed so it can be used as a standalone
|
||||
package for any projects needing to test their implementation against a full set
|
||||
of blocks that excerise the consensus validation rules.
|
||||
of blocks that exercise the consensus validation rules.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
|
||||
+1
-1
@@ -15,6 +15,6 @@ independent versions over the peer-to-peer network.
|
||||
|
||||
This package has intentionally been designed so it can be used as a standalone
|
||||
package for any projects needing to test their implementation against a full set
|
||||
of blocks that excerise the consensus validation rules.
|
||||
of blocks that exercise the consensus validation rules.
|
||||
*/
|
||||
package fullblocktests
|
||||
|
||||
+25
-21
@@ -651,7 +651,7 @@ func (idx *AddrIndex) Create(dbTx database.Tx) error {
|
||||
}
|
||||
|
||||
// writeIndexData represents the address index data to be written for one block.
|
||||
// It consistens of the address mapped to an ordered list of the transactions
|
||||
// It consists of the address mapped to an ordered list of the transactions
|
||||
// that involve the address in block. It is ordered so the transactions can be
|
||||
// stored in the order they appear in the block.
|
||||
type writeIndexData map[[addrKeySize]byte][]int
|
||||
@@ -690,27 +690,28 @@ func (idx *AddrIndex) indexPkScript(data writeIndexData, pkScript []byte, txIdx
|
||||
}
|
||||
|
||||
// indexBlock extract all of the standard addresses from all of the transactions
|
||||
// in the passed block and maps each of them to the assocaited transaction using
|
||||
// in the passed block and maps each of them to the associated transaction using
|
||||
// the passed map.
|
||||
func (idx *AddrIndex) indexBlock(data writeIndexData, block *btcutil.Block, view *blockchain.UtxoViewpoint) {
|
||||
func (idx *AddrIndex) indexBlock(data writeIndexData, block *btcutil.Block,
|
||||
stxos []blockchain.SpentTxOut) {
|
||||
|
||||
stxoIndex := 0
|
||||
for txIdx, tx := range block.Transactions() {
|
||||
// Coinbases do not reference any inputs. Since the block is
|
||||
// required to have already gone through full validation, it has
|
||||
// already been proven on the first transaction in the block is
|
||||
// a coinbase.
|
||||
if txIdx != 0 {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
// The view should always have the input since
|
||||
// the index contract requires it, however, be
|
||||
// safe and simply ignore any missing entries.
|
||||
origin := &txIn.PreviousOutPoint
|
||||
entry := view.LookupEntry(&origin.Hash)
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pkScript := entry.PkScriptByIndex(origin.Index)
|
||||
for range tx.MsgTx().TxIn {
|
||||
// We'll access the slice of all the
|
||||
// transactions spent in this block properly
|
||||
// ordered to fetch the previous input script.
|
||||
pkScript := stxos[stxoIndex].PkScript
|
||||
idx.indexPkScript(data, pkScript, txIdx)
|
||||
|
||||
// With an input indexed, we'll advance the
|
||||
// stxo coutner.
|
||||
stxoIndex++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,7 +726,9 @@ func (idx *AddrIndex) indexBlock(data writeIndexData, block *btcutil.Block, view
|
||||
// the transactions in the block involve.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
stxos []blockchain.SpentTxOut) error {
|
||||
|
||||
// The offset and length of the transactions within the serialized
|
||||
// block.
|
||||
txLocs, err := block.TxLoc()
|
||||
@@ -741,7 +744,7 @@ func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view
|
||||
|
||||
// Build all of the address to transaction mappings in a local map.
|
||||
addrsToTxns := make(writeIndexData)
|
||||
idx.indexBlock(addrsToTxns, block, view)
|
||||
idx.indexBlock(addrsToTxns, block, stxos)
|
||||
|
||||
// Add all of the index entries for each address.
|
||||
addrIdxBucket := dbTx.Metadata().Bucket(addrIndexKey)
|
||||
@@ -763,10 +766,12 @@ func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view
|
||||
// each transaction in the block involve.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func (idx *AddrIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
stxos []blockchain.SpentTxOut) error {
|
||||
|
||||
// Build all of the address to transaction mappings in a local map.
|
||||
addrsToTxns := make(writeIndexData)
|
||||
idx.indexBlock(addrsToTxns, block, view)
|
||||
idx.indexBlock(addrsToTxns, block, stxos)
|
||||
|
||||
// Remove all of the index entries for each address.
|
||||
bucket := dbTx.Metadata().Bucket(addrIndexKey)
|
||||
@@ -872,15 +877,14 @@ func (idx *AddrIndex) AddUnconfirmedTx(tx *btcutil.Tx, utxoView *blockchain.Utxo
|
||||
// transaction has already been validated and thus all inputs are
|
||||
// already known to exist.
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
entry := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash)
|
||||
entry := utxoView.LookupEntry(txIn.PreviousOutPoint)
|
||||
if entry == nil {
|
||||
// Ignore missing entries. This should never happen
|
||||
// in practice since the function comments specifically
|
||||
// call out all inputs must be available.
|
||||
continue
|
||||
}
|
||||
pkScript := entry.PkScriptByIndex(txIn.PreviousOutPoint.Index)
|
||||
idx.indexUnconfirmedAddresses(pkScript, tx)
|
||||
idx.indexUnconfirmedAddresses(entry.PkScript(), tx)
|
||||
}
|
||||
|
||||
// Index addresses of all created outputs.
|
||||
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/gcs"
|
||||
"github.com/btcsuite/btcutil/gcs/builder"
|
||||
)
|
||||
|
||||
const (
|
||||
// cfIndexName is the human-readable name for the index.
|
||||
cfIndexName = "committed filter index"
|
||||
)
|
||||
|
||||
// Committed filters come in one flavor currently: basic. They are generated
|
||||
// and dropped in pairs, and both are indexed by a block's hash. Besides
|
||||
// holding different content, they also live in different buckets.
|
||||
var (
|
||||
// cfIndexParentBucketKey is the name of the parent bucket used to
|
||||
// house the index. The rest of the buckets live below this bucket.
|
||||
cfIndexParentBucketKey = []byte("cfindexparentbucket")
|
||||
|
||||
// cfIndexKeys is an array of db bucket names used to house indexes of
|
||||
// block hashes to cfilters.
|
||||
cfIndexKeys = [][]byte{
|
||||
[]byte("cf0byhashidx"),
|
||||
}
|
||||
|
||||
// cfHeaderKeys is an array of db bucket names used to house indexes of
|
||||
// block hashes to cf headers.
|
||||
cfHeaderKeys = [][]byte{
|
||||
[]byte("cf0headerbyhashidx"),
|
||||
}
|
||||
|
||||
// cfHashKeys is an array of db bucket names used to house indexes of
|
||||
// block hashes to cf hashes.
|
||||
cfHashKeys = [][]byte{
|
||||
[]byte("cf0hashbyhashidx"),
|
||||
}
|
||||
|
||||
maxFilterType = uint8(len(cfHeaderKeys) - 1)
|
||||
|
||||
// zeroHash is the chainhash.Hash value of all zero bytes, defined here
|
||||
// for convenience.
|
||||
zeroHash chainhash.Hash
|
||||
)
|
||||
|
||||
// dbFetchFilterIdxEntry retrieves a data blob from the filter index database.
|
||||
// An entry's absence is not considered an error.
|
||||
func dbFetchFilterIdxEntry(dbTx database.Tx, key []byte, h *chainhash.Hash) ([]byte, error) {
|
||||
idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
|
||||
return idx.Get(h[:]), nil
|
||||
}
|
||||
|
||||
// dbStoreFilterIdxEntry stores a data blob in the filter index database.
|
||||
func dbStoreFilterIdxEntry(dbTx database.Tx, key []byte, h *chainhash.Hash, f []byte) error {
|
||||
idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
|
||||
return idx.Put(h[:], f)
|
||||
}
|
||||
|
||||
// dbDeleteFilterIdxEntry deletes a data blob from the filter index database.
|
||||
func dbDeleteFilterIdxEntry(dbTx database.Tx, key []byte, h *chainhash.Hash) error {
|
||||
idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
|
||||
return idx.Delete(h[:])
|
||||
}
|
||||
|
||||
// CfIndex implements a committed filter (cf) by hash index.
|
||||
type CfIndex struct {
|
||||
db database.DB
|
||||
chainParams *chaincfg.Params
|
||||
}
|
||||
|
||||
// Ensure the CfIndex type implements the Indexer interface.
|
||||
var _ Indexer = (*CfIndex)(nil)
|
||||
|
||||
// Ensure the CfIndex type implements the NeedsInputser interface.
|
||||
var _ NeedsInputser = (*CfIndex)(nil)
|
||||
|
||||
// NeedsInputs signals that the index requires the referenced inputs in order
|
||||
// to properly create the index.
|
||||
//
|
||||
// This implements the NeedsInputser interface.
|
||||
func (idx *CfIndex) NeedsInputs() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Init initializes the hash-based cf index. This is part of the Indexer
|
||||
// interface.
|
||||
func (idx *CfIndex) Init() error {
|
||||
return nil // Nothing to do.
|
||||
}
|
||||
|
||||
// Key returns the database key to use for the index as a byte slice. This is
|
||||
// part of the Indexer interface.
|
||||
func (idx *CfIndex) Key() []byte {
|
||||
return cfIndexParentBucketKey
|
||||
}
|
||||
|
||||
// Name returns the human-readable name of the index. This is part of the
|
||||
// Indexer interface.
|
||||
func (idx *CfIndex) Name() string {
|
||||
return cfIndexName
|
||||
}
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs to
|
||||
// be created for the first time. It creates buckets for the two hash-based cf
|
||||
// indexes (regular only currently).
|
||||
func (idx *CfIndex) Create(dbTx database.Tx) error {
|
||||
meta := dbTx.Metadata()
|
||||
|
||||
cfIndexParentBucket, err := meta.CreateBucket(cfIndexParentBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, bucketName := range cfIndexKeys {
|
||||
_, err = cfIndexParentBucket.CreateBucket(bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, bucketName := range cfHeaderKeys {
|
||||
_, err = cfIndexParentBucket.CreateBucket(bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, bucketName := range cfHashKeys {
|
||||
_, err = cfIndexParentBucket.CreateBucket(bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// storeFilter stores a given filter, and performs the steps needed to
|
||||
// generate the filter's header.
|
||||
func storeFilter(dbTx database.Tx, block *btcutil.Block, f *gcs.Filter,
|
||||
filterType wire.FilterType) error {
|
||||
if uint8(filterType) > maxFilterType {
|
||||
return errors.New("unsupported filter type")
|
||||
}
|
||||
|
||||
// Figure out which buckets to use.
|
||||
fkey := cfIndexKeys[filterType]
|
||||
hkey := cfHeaderKeys[filterType]
|
||||
hashkey := cfHashKeys[filterType]
|
||||
|
||||
// Start by storing the filter.
|
||||
h := block.Hash()
|
||||
filterBytes, err := f.NBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbStoreFilterIdxEntry(dbTx, fkey, h, filterBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next store the filter hash.
|
||||
filterHash, err := builder.GetFilterHash(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbStoreFilterIdxEntry(dbTx, hashkey, h, filterHash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Then fetch the previous block's filter header.
|
||||
var prevHeader *chainhash.Hash
|
||||
ph := &block.MsgBlock().Header.PrevBlock
|
||||
if ph.IsEqual(&zeroHash) {
|
||||
prevHeader = &zeroHash
|
||||
} else {
|
||||
pfh, err := dbFetchFilterIdxEntry(dbTx, hkey, ph)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Construct the new block's filter header, and store it.
|
||||
prevHeader, err = chainhash.NewHash(pfh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fh, err := builder.MakeHeaderForFilter(f, *prevHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dbStoreFilterIdxEntry(dbTx, hkey, h, fh[:])
|
||||
}
|
||||
|
||||
// ConnectBlock is invoked by the index manager when a new block has been
|
||||
// connected to the main chain. This indexer adds a hash-to-cf mapping for
|
||||
// every passed block. This is part of the Indexer interface.
|
||||
func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
stxos []blockchain.SpentTxOut) error {
|
||||
|
||||
prevScripts := make([][]byte, len(stxos))
|
||||
for i, stxo := range stxos {
|
||||
prevScripts[i] = stxo.PkScript
|
||||
}
|
||||
|
||||
f, err := builder.BuildBasicFilter(block.MsgBlock(), prevScripts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return storeFilter(dbTx, block, f, wire.GCSFilterRegular)
|
||||
}
|
||||
|
||||
// DisconnectBlock is invoked by the index manager when a block has been
|
||||
// disconnected from the main chain. This indexer removes the hash-to-cf
|
||||
// mapping for every passed block. This is part of the Indexer interface.
|
||||
func (idx *CfIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
_ []blockchain.SpentTxOut) error {
|
||||
|
||||
for _, key := range cfIndexKeys {
|
||||
err := dbDeleteFilterIdxEntry(dbTx, key, block.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range cfHeaderKeys {
|
||||
err := dbDeleteFilterIdxEntry(dbTx, key, block.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range cfHashKeys {
|
||||
err := dbDeleteFilterIdxEntry(dbTx, key, block.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// entryByBlockHash fetches a filter index entry of a particular type
|
||||
// (eg. filter, filter header, etc) for a filter type and block hash.
|
||||
func (idx *CfIndex) entryByBlockHash(filterTypeKeys [][]byte,
|
||||
filterType wire.FilterType, h *chainhash.Hash) ([]byte, error) {
|
||||
|
||||
if uint8(filterType) > maxFilterType {
|
||||
return nil, errors.New("unsupported filter type")
|
||||
}
|
||||
key := filterTypeKeys[filterType]
|
||||
|
||||
var entry []byte
|
||||
err := idx.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
entry, err = dbFetchFilterIdxEntry(dbTx, key, h)
|
||||
return err
|
||||
})
|
||||
return entry, err
|
||||
}
|
||||
|
||||
// entriesByBlockHashes batch fetches a filter index entry of a particular type
|
||||
// (eg. filter, filter header, etc) for a filter type and slice of block hashes.
|
||||
func (idx *CfIndex) entriesByBlockHashes(filterTypeKeys [][]byte,
|
||||
filterType wire.FilterType, blockHashes []*chainhash.Hash) ([][]byte, error) {
|
||||
|
||||
if uint8(filterType) > maxFilterType {
|
||||
return nil, errors.New("unsupported filter type")
|
||||
}
|
||||
key := filterTypeKeys[filterType]
|
||||
|
||||
entries := make([][]byte, 0, len(blockHashes))
|
||||
err := idx.db.View(func(dbTx database.Tx) error {
|
||||
for _, blockHash := range blockHashes {
|
||||
entry, err := dbFetchFilterIdxEntry(dbTx, key, blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return entries, err
|
||||
}
|
||||
|
||||
// FilterByBlockHash returns the serialized contents of a block's basic or
|
||||
// committed filter.
|
||||
func (idx *CfIndex) FilterByBlockHash(h *chainhash.Hash,
|
||||
filterType wire.FilterType) ([]byte, error) {
|
||||
return idx.entryByBlockHash(cfIndexKeys, filterType, h)
|
||||
}
|
||||
|
||||
// FiltersByBlockHashes returns the serialized contents of a block's basic or
|
||||
// committed filter for a set of blocks by hash.
|
||||
func (idx *CfIndex) FiltersByBlockHashes(blockHashes []*chainhash.Hash,
|
||||
filterType wire.FilterType) ([][]byte, error) {
|
||||
return idx.entriesByBlockHashes(cfIndexKeys, filterType, blockHashes)
|
||||
}
|
||||
|
||||
// FilterHeaderByBlockHash returns the serialized contents of a block's basic
|
||||
// committed filter header.
|
||||
func (idx *CfIndex) FilterHeaderByBlockHash(h *chainhash.Hash,
|
||||
filterType wire.FilterType) ([]byte, error) {
|
||||
return idx.entryByBlockHash(cfHeaderKeys, filterType, h)
|
||||
}
|
||||
|
||||
// FilterHeadersByBlockHashes returns the serialized contents of a block's
|
||||
// basic committed filter header for a set of blocks by hash.
|
||||
func (idx *CfIndex) FilterHeadersByBlockHashes(blockHashes []*chainhash.Hash,
|
||||
filterType wire.FilterType) ([][]byte, error) {
|
||||
return idx.entriesByBlockHashes(cfHeaderKeys, filterType, blockHashes)
|
||||
}
|
||||
|
||||
// FilterHashByBlockHash returns the serialized contents of a block's basic
|
||||
// committed filter hash.
|
||||
func (idx *CfIndex) FilterHashByBlockHash(h *chainhash.Hash,
|
||||
filterType wire.FilterType) ([]byte, error) {
|
||||
return idx.entryByBlockHash(cfHashKeys, filterType, h)
|
||||
}
|
||||
|
||||
// FilterHashesByBlockHashes returns the serialized contents of a block's basic
|
||||
// committed filter hash for a set of blocks by hash.
|
||||
func (idx *CfIndex) FilterHashesByBlockHashes(blockHashes []*chainhash.Hash,
|
||||
filterType wire.FilterType) ([][]byte, error) {
|
||||
return idx.entriesByBlockHashes(cfHashKeys, filterType, blockHashes)
|
||||
}
|
||||
|
||||
// NewCfIndex returns a new instance of an indexer that is used to create a
|
||||
// mapping of the hashes of all blocks in the blockchain to their respective
|
||||
// committed filters.
|
||||
//
|
||||
// It implements the Indexer interface which plugs into the IndexManager that
|
||||
// in turn is used by the blockchain package. This allows the index to be
|
||||
// seamlessly maintained along with the chain.
|
||||
func NewCfIndex(db database.DB, chainParams *chaincfg.Params) *CfIndex {
|
||||
return &CfIndex{db: db, chainParams: chainParams}
|
||||
}
|
||||
|
||||
// DropCfIndex drops the CF index from the provided database if exists.
|
||||
func DropCfIndex(db database.DB, interrupt <-chan struct{}) error {
|
||||
return dropIndex(db, cfIndexParentBucketKey, cfIndexName, interrupt)
|
||||
}
|
||||
+10
-6
@@ -50,13 +50,17 @@ type Indexer interface {
|
||||
// every load, including the case the index was just created.
|
||||
Init() error
|
||||
|
||||
// ConnectBlock is invoked when the index manager is notified that a new
|
||||
// block has been connected to the main chain.
|
||||
ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error
|
||||
// ConnectBlock is invoked when a new block has been connected to the
|
||||
// main chain. The set of output spent within a block is also passed in
|
||||
// so indexers can access the pevious output scripts input spent if
|
||||
// required.
|
||||
ConnectBlock(database.Tx, *btcutil.Block, []blockchain.SpentTxOut) error
|
||||
|
||||
// DisconnectBlock is invoked when the index manager is notified that a
|
||||
// block has been disconnected from the main chain.
|
||||
DisconnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error
|
||||
// DisconnectBlock is invoked when a block has been disconnected from
|
||||
// the main chain. The set of outputs scripts that were spent within
|
||||
// this block is also returned so indexers can clean up the prior index
|
||||
// state for this block
|
||||
DisconnectBlock(database.Tx, *btcutil.Block, []blockchain.SpentTxOut) error
|
||||
}
|
||||
|
||||
// AssertError identifies an error that indicates an internal code consistency
|
||||
|
||||
+124
-100
@@ -68,7 +68,9 @@ func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*chainhash.Hash, int32,
|
||||
// given block using the provided indexer and updates the tip of the indexer
|
||||
// accordingly. An error will be returned if the current tip for the indexer is
|
||||
// not the previous block for the passed block.
|
||||
func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block,
|
||||
stxo []blockchain.SpentTxOut) error {
|
||||
|
||||
// Assert that the block being connected properly connects to the
|
||||
// current tip of the index.
|
||||
idxKey := indexer.Key()
|
||||
@@ -84,7 +86,7 @@ func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block
|
||||
}
|
||||
|
||||
// Notify the indexer with the connected block so it can index it.
|
||||
if err := indexer.ConnectBlock(dbTx, block, view); err != nil {
|
||||
if err := indexer.ConnectBlock(dbTx, block, stxo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -96,7 +98,9 @@ func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block
|
||||
// given block using the provided indexer and updates the tip of the indexer
|
||||
// accordingly. An error will be returned if the current tip for the indexer is
|
||||
// not the passed block.
|
||||
func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block,
|
||||
stxo []blockchain.SpentTxOut) error {
|
||||
|
||||
// Assert that the block being disconnected is the current tip of the
|
||||
// index.
|
||||
idxKey := indexer.Key()
|
||||
@@ -113,7 +117,7 @@ func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Bl
|
||||
|
||||
// Notify the indexer with the disconnected block so it can remove all
|
||||
// of the appropriate entries.
|
||||
if err := indexer.DisconnectBlock(dbTx, block, view); err != nil {
|
||||
if err := indexer.DisconnectBlock(dbTx, block, stxo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -299,34 +303,38 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
|
||||
// loaded directly since it is no longer in the main
|
||||
// chain and thus the chain.BlockByHash function would
|
||||
// error.
|
||||
err = m.db.Update(func(dbTx database.Tx) error {
|
||||
var block *btcutil.Block
|
||||
err := m.db.View(func(dbTx database.Tx) error {
|
||||
blockBytes, err := dbTx.FetchBlock(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, err := btcutil.NewBlockFromBytes(blockBytes)
|
||||
block, err = btcutil.NewBlockFromBytes(blockBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block.SetHeight(height)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// When the index requires all of the referenced
|
||||
// txouts they need to be retrieved from the
|
||||
// transaction index.
|
||||
var view *blockchain.UtxoViewpoint
|
||||
if indexNeedsInputs(indexer) {
|
||||
var err error
|
||||
view, err = makeUtxoView(dbTx, block,
|
||||
interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// We'll also grab the set of outputs spent by this
|
||||
// block so we can remove them from the index.
|
||||
spentTxos, err := chain.FetchSpendJournal(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With the block and stxo set for that block retrieved,
|
||||
// we can now update the index itself.
|
||||
err = m.db.Update(func(dbTx database.Tx) error {
|
||||
// Remove all of the index entries associated
|
||||
// with the block and update the indexer tip.
|
||||
err = dbIndexDisconnectBlock(dbTx, indexer,
|
||||
block, view)
|
||||
err = dbIndexDisconnectBlock(
|
||||
dbTx, indexer, block, spentTxos,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -407,7 +415,7 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
|
||||
}
|
||||
|
||||
// Connect the block for all indexes that need it.
|
||||
var view *blockchain.UtxoViewpoint
|
||||
var spentTxos []blockchain.SpentTxOut
|
||||
for i, indexer := range m.enabledIndexes {
|
||||
// Skip indexes that don't need to be updated with this
|
||||
// block.
|
||||
@@ -415,21 +423,20 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
|
||||
continue
|
||||
}
|
||||
|
||||
err := m.db.Update(func(dbTx database.Tx) error {
|
||||
// When the index requires all of the referenced
|
||||
// txouts and they haven't been loaded yet, they
|
||||
// need to be retrieved from the transaction
|
||||
// index.
|
||||
if view == nil && indexNeedsInputs(indexer) {
|
||||
var err error
|
||||
view, err = makeUtxoView(dbTx, block,
|
||||
interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// When the index requires all of the referenced txouts
|
||||
// and they haven't been loaded yet, they need to be
|
||||
// retrieved from the spend journal.
|
||||
if spentTxos == nil && indexNeedsInputs(indexer) {
|
||||
spentTxos, err = chain.FetchSpendJournal(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dbIndexConnectBlock(dbTx, indexer, block,
|
||||
view)
|
||||
}
|
||||
|
||||
err := m.db.Update(func(dbTx database.Tx) error {
|
||||
return dbIndexConnectBlock(
|
||||
dbTx, indexer, block, spentTxos,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -487,52 +494,18 @@ func dbFetchTx(dbTx database.Tx, hash *chainhash.Hash) (*wire.MsgTx, error) {
|
||||
return &msgTx, nil
|
||||
}
|
||||
|
||||
// makeUtxoView creates a mock unspent transaction output view by using the
|
||||
// transaction index in order to look up all inputs referenced by the
|
||||
// transactions in the block. This is sometimes needed when catching indexes up
|
||||
// because many of the txouts could actually already be spent however the
|
||||
// associated scripts are still required to index them.
|
||||
func makeUtxoView(dbTx database.Tx, block *btcutil.Block, interrupt <-chan struct{}) (*blockchain.UtxoViewpoint, error) {
|
||||
view := blockchain.NewUtxoViewpoint()
|
||||
for txIdx, tx := range block.Transactions() {
|
||||
// Coinbases do not reference any inputs. Since the block is
|
||||
// required to have already gone through full validation, it has
|
||||
// already been proven on the first transaction in the block is
|
||||
// a coinbase.
|
||||
if txIdx == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the transaction index to load all of the referenced
|
||||
// inputs and add their outputs to the view.
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
originOut := &txIn.PreviousOutPoint
|
||||
originTx, err := dbFetchTx(dbTx, &originOut.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
view.AddTxOuts(btcutil.NewTx(originTx), 0)
|
||||
}
|
||||
|
||||
if interruptRequested(interrupt) {
|
||||
return nil, errInterruptRequested
|
||||
}
|
||||
}
|
||||
|
||||
return view, nil
|
||||
}
|
||||
|
||||
// ConnectBlock must be invoked when a block is extending the main chain. It
|
||||
// keeps track of the state of each index it is managing, performs some sanity
|
||||
// checks, and invokes each indexer.
|
||||
//
|
||||
// This is part of the blockchain.IndexManager interface.
|
||||
func (m *Manager) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func (m *Manager) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
stxos []blockchain.SpentTxOut) error {
|
||||
|
||||
// Call each of the currently active optional indexes with the block
|
||||
// being connected so they can update accordingly.
|
||||
for _, index := range m.enabledIndexes {
|
||||
err := dbIndexConnectBlock(dbTx, index, block, view)
|
||||
err := dbIndexConnectBlock(dbTx, index, block, stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -546,11 +519,13 @@ func (m *Manager) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *blo
|
||||
// the index entries associated with the block.
|
||||
//
|
||||
// This is part of the blockchain.IndexManager interface.
|
||||
func (m *Manager) DisconnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func (m *Manager) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
stxo []blockchain.SpentTxOut) error {
|
||||
|
||||
// Call each of the currently active optional indexes with the block
|
||||
// being disconnected so they can update accordingly.
|
||||
for _, index := range m.enabledIndexes {
|
||||
err := dbIndexDisconnectBlock(dbTx, index, block, view)
|
||||
err := dbIndexDisconnectBlock(dbTx, index, block, stxo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -609,37 +584,90 @@ func dropIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan s
|
||||
// the bucket in a single database transaction would result in massive
|
||||
// memory usage and likely crash many systems due to ulimits. In order
|
||||
// to avoid this, use a cursor to delete a maximum number of entries out
|
||||
// of the bucket at a time.
|
||||
// of the bucket at a time. Recurse buckets depth-first to delete any
|
||||
// sub-buckets.
|
||||
const maxDeletions = 2000000
|
||||
var totalDeleted uint64
|
||||
for numDeleted := maxDeletions; numDeleted == maxDeletions; {
|
||||
numDeleted = 0
|
||||
err := db.Update(func(dbTx database.Tx) error {
|
||||
bucket := dbTx.Metadata().Bucket(idxKey)
|
||||
cursor := bucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() &&
|
||||
numDeleted < maxDeletions {
|
||||
|
||||
if err := cursor.Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
numDeleted++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
// Recurse through all buckets in the index, cataloging each for
|
||||
// later deletion.
|
||||
var subBuckets [][][]byte
|
||||
var subBucketClosure func(database.Tx, []byte, [][]byte) error
|
||||
subBucketClosure = func(dbTx database.Tx,
|
||||
subBucket []byte, tlBucket [][]byte) error {
|
||||
// Get full bucket name and append to subBuckets for later
|
||||
// deletion.
|
||||
var bucketName [][]byte
|
||||
if (tlBucket == nil) || (len(tlBucket) == 0) {
|
||||
bucketName = append(bucketName, subBucket)
|
||||
} else {
|
||||
bucketName = append(tlBucket, subBucket)
|
||||
}
|
||||
subBuckets = append(subBuckets, bucketName)
|
||||
// Recurse sub-buckets to append to subBuckets slice.
|
||||
bucket := dbTx.Metadata()
|
||||
for _, subBucketName := range bucketName {
|
||||
bucket = bucket.Bucket(subBucketName)
|
||||
}
|
||||
return bucket.ForEachBucket(func(k []byte) error {
|
||||
return subBucketClosure(dbTx, k, bucketName)
|
||||
})
|
||||
}
|
||||
|
||||
if numDeleted > 0 {
|
||||
totalDeleted += uint64(numDeleted)
|
||||
log.Infof("Deleted %d keys (%d total) from %s",
|
||||
numDeleted, totalDeleted, idxName)
|
||||
// Call subBucketClosure with top-level bucket.
|
||||
err = db.View(func(dbTx database.Tx) error {
|
||||
return subBucketClosure(dbTx, idxKey, nil)
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate through each sub-bucket in reverse, deepest-first, deleting
|
||||
// all keys inside them and then dropping the buckets themselves.
|
||||
for i := range subBuckets {
|
||||
bucketName := subBuckets[len(subBuckets)-1-i]
|
||||
// Delete maxDeletions key/value pairs at a time.
|
||||
for numDeleted := maxDeletions; numDeleted == maxDeletions; {
|
||||
numDeleted = 0
|
||||
err := db.Update(func(dbTx database.Tx) error {
|
||||
subBucket := dbTx.Metadata()
|
||||
for _, subBucketName := range bucketName {
|
||||
subBucket = subBucket.Bucket(subBucketName)
|
||||
}
|
||||
cursor := subBucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() &&
|
||||
numDeleted < maxDeletions {
|
||||
|
||||
if err := cursor.Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
numDeleted++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if numDeleted > 0 {
|
||||
totalDeleted += uint64(numDeleted)
|
||||
log.Infof("Deleted %d keys (%d total) from %s",
|
||||
numDeleted, totalDeleted, idxName)
|
||||
}
|
||||
}
|
||||
|
||||
if interruptRequested(interrupt) {
|
||||
return errInterruptRequested
|
||||
}
|
||||
|
||||
// Drop the bucket itself.
|
||||
err = db.Update(func(dbTx database.Tx) error {
|
||||
bucket := dbTx.Metadata()
|
||||
for j := 0; j < len(bucketName)-1; j++ {
|
||||
bucket = bucket.Bucket(bucketName[j])
|
||||
}
|
||||
return bucket.DeleteBucket(bucketName[len(bucketName)-1])
|
||||
})
|
||||
}
|
||||
|
||||
// Call extra index specific deinitialization for the transaction index.
|
||||
@@ -658,10 +686,6 @@ func dropIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan s
|
||||
return err
|
||||
}
|
||||
|
||||
if err := meta.DeleteBucket(idxKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return indexesBucket.Delete(indexDropKey(idxKey))
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+7
-3
@@ -40,7 +40,7 @@ var (
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The transaction index consists of an entry for every transaction in the main
|
||||
// chain. In order to significanly optimize the space requirements a separate
|
||||
// chain. In order to significantly optimize the space requirements a separate
|
||||
// index which provides an internal mapping between each block that has been
|
||||
// indexed and a unique ID for use within the hash to location mappings. The ID
|
||||
// is simply a sequentially incremented uint32. This is useful because it is
|
||||
@@ -388,7 +388,9 @@ func (idx *TxIndex) Create(dbTx database.Tx) error {
|
||||
// for every transaction in the passed block.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
stxos []blockchain.SpentTxOut) error {
|
||||
|
||||
// Increment the internal block ID to use for the block being connected
|
||||
// and add all of the transactions in the block to the index.
|
||||
newBlockID := idx.curBlockID + 1
|
||||
@@ -411,7 +413,9 @@ func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *b
|
||||
// hash-to-transaction mapping for every transaction in the block.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
|
||||
func (idx *TxIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
|
||||
stxos []blockchain.SpentTxOut) error {
|
||||
|
||||
// Remove all of the transactions in the block from the index.
|
||||
if err := dbRemoveTxIndexEntries(dbTx, block); err != nil {
|
||||
return err
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ func TestMedianTime(t *testing.T) {
|
||||
// be ignored.
|
||||
{in: []int64{-4201, 4202, -4203, 4204, -4205}, wantOffset: 0},
|
||||
|
||||
// Excerise the condition where the median offset is greater
|
||||
// Exercise the condition where the median offset is greater
|
||||
// than the max allowed adjustment, but there is at least one
|
||||
// sample that is close enough to the current time to avoid
|
||||
// triggering a warning about an invalid local clock.
|
||||
|
||||
+14
-28
@@ -55,31 +55,16 @@ out:
|
||||
for {
|
||||
select {
|
||||
case txVI := <-v.validateChan:
|
||||
// Ensure the referenced input transaction is available.
|
||||
// Ensure the referenced input utxo is available.
|
||||
txIn := txVI.txIn
|
||||
originTxHash := &txIn.PreviousOutPoint.Hash
|
||||
originTxIndex := txIn.PreviousOutPoint.Index
|
||||
txEntry := v.utxoView.LookupEntry(originTxHash)
|
||||
if txEntry == nil {
|
||||
str := fmt.Sprintf("unable to find input "+
|
||||
"transaction %v referenced from "+
|
||||
"transaction %v", originTxHash,
|
||||
txVI.tx.Hash())
|
||||
err := ruleError(ErrMissingTxOut, str)
|
||||
v.sendResult(err)
|
||||
break out
|
||||
}
|
||||
|
||||
// Ensure the referenced input transaction public key
|
||||
// script is available.
|
||||
pkScript := txEntry.PkScriptByIndex(originTxIndex)
|
||||
if pkScript == nil {
|
||||
utxo := v.utxoView.LookupEntry(txIn.PreviousOutPoint)
|
||||
if utxo == nil {
|
||||
str := fmt.Sprintf("unable to find unspent "+
|
||||
"output %v script referenced from "+
|
||||
"output %v referenced from "+
|
||||
"transaction %s:%d",
|
||||
txIn.PreviousOutPoint, txVI.tx.Hash(),
|
||||
txVI.txInIndex)
|
||||
err := ruleError(ErrBadTxInput, str)
|
||||
err := ruleError(ErrMissingTxOut, str)
|
||||
v.sendResult(err)
|
||||
break out
|
||||
}
|
||||
@@ -87,18 +72,19 @@ out:
|
||||
// Create a new script engine for the script pair.
|
||||
sigScript := txIn.SignatureScript
|
||||
witness := txIn.Witness
|
||||
inputAmount := txEntry.AmountByIndex(originTxIndex)
|
||||
pkScript := utxo.PkScript()
|
||||
inputAmount := utxo.Amount()
|
||||
vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
|
||||
txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes,
|
||||
inputAmount)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to parse input "+
|
||||
"%s:%d which references output %s:%d - "+
|
||||
"%s:%d which references output %v - "+
|
||||
"%v (input witness %x, input script "+
|
||||
"bytes %x, prev output script bytes %x)",
|
||||
txVI.tx.Hash(), txVI.txInIndex, originTxHash,
|
||||
originTxIndex, err, witness, sigScript,
|
||||
pkScript)
|
||||
txVI.tx.Hash(), txVI.txInIndex,
|
||||
txIn.PreviousOutPoint, err, witness,
|
||||
sigScript, pkScript)
|
||||
err := ruleError(ErrScriptMalformed, str)
|
||||
v.sendResult(err)
|
||||
break out
|
||||
@@ -107,12 +93,12 @@ out:
|
||||
// Execute the script pair.
|
||||
if err := vm.Execute(); err != nil {
|
||||
str := fmt.Sprintf("failed to validate input "+
|
||||
"%s:%d which references output %s:%d - "+
|
||||
"%s:%d which references output %v - "+
|
||||
"%v (input witness %x, input script "+
|
||||
"bytes %x, prev output script bytes %x)",
|
||||
txVI.tx.Hash(), txVI.txInIndex,
|
||||
originTxHash, originTxIndex, err,
|
||||
witness, sigScript, pkScript)
|
||||
txIn.PreviousOutPoint, err, witness,
|
||||
sigScript, pkScript)
|
||||
err := ruleError(ErrScriptValidation, str)
|
||||
v.sendResult(err)
|
||||
break out
|
||||
|
||||
BIN
Binary file not shown.
+1
-1
@@ -85,7 +85,7 @@ type thresholdConditionChecker interface {
|
||||
|
||||
// Condition returns whether or not the rule change activation condition
|
||||
// has been met. This typically involves checking whether or not the
|
||||
// bit assocaited with the condition is set, but can be more complex as
|
||||
// bit associated with the condition is set, but can be more complex as
|
||||
// needed.
|
||||
Condition(*blockNode) (bool, error)
|
||||
}
|
||||
|
||||
+604
@@ -0,0 +1,604 @@
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
// blockHdrOffset defines the offsets into a v1 block index row for the
|
||||
// block header.
|
||||
//
|
||||
// The serialized block index row format is:
|
||||
// <blocklocation><blockheader>
|
||||
blockHdrOffset = 12
|
||||
)
|
||||
|
||||
// errInterruptRequested indicates that an operation was cancelled due
|
||||
// to a user-requested interrupt.
|
||||
var errInterruptRequested = errors.New("interrupt requested")
|
||||
|
||||
// interruptRequested returns true when the provided channel has been closed.
|
||||
// This simplifies early shutdown slightly since the caller can just use an if
|
||||
// statement instead of a select.
|
||||
func interruptRequested(interrupted <-chan struct{}) bool {
|
||||
select {
|
||||
case <-interrupted:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// blockChainContext represents a particular block's placement in the block
|
||||
// chain. This is used by the block index migration to track block metadata that
|
||||
// will be written to disk.
|
||||
type blockChainContext struct {
|
||||
parent *chainhash.Hash
|
||||
children []*chainhash.Hash
|
||||
height int32
|
||||
mainChain bool
|
||||
}
|
||||
|
||||
// migrateBlockIndex migrates all block entries from the v1 block index bucket
|
||||
// to the v2 bucket. The v1 bucket stores all block entries keyed by block hash,
|
||||
// whereas the v2 bucket stores the exact same values, but keyed instead by
|
||||
// block height + hash.
|
||||
func migrateBlockIndex(db database.DB) error {
|
||||
// Hardcoded bucket names so updates to the global values do not affect
|
||||
// old upgrades.
|
||||
v1BucketName := []byte("ffldb-blockidx")
|
||||
v2BucketName := []byte("blockheaderidx")
|
||||
|
||||
err := db.Update(func(dbTx database.Tx) error {
|
||||
v1BlockIdxBucket := dbTx.Metadata().Bucket(v1BucketName)
|
||||
if v1BlockIdxBucket == nil {
|
||||
return fmt.Errorf("Bucket %s does not exist", v1BucketName)
|
||||
}
|
||||
|
||||
log.Info("Re-indexing block information in the database. This might take a while...")
|
||||
|
||||
v2BlockIdxBucket, err :=
|
||||
dbTx.Metadata().CreateBucketIfNotExists(v2BucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get tip of the main chain.
|
||||
serializedData := dbTx.Metadata().Get(chainStateKeyName)
|
||||
state, err := deserializeBestChainState(serializedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tip := &state.hash
|
||||
|
||||
// Scan the old block index bucket and construct a mapping of each block
|
||||
// to parent block and all child blocks.
|
||||
blocksMap, err := readBlockTree(v1BlockIdxBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use the block graph to calculate the height of each block.
|
||||
err = determineBlockHeights(blocksMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find blocks on the main chain with the block graph and current tip.
|
||||
determineMainChainBlocks(blocksMap, tip)
|
||||
|
||||
// Now that we have heights for all blocks, scan the old block index
|
||||
// bucket and insert all rows into the new one.
|
||||
return v1BlockIdxBucket.ForEach(func(hashBytes, blockRow []byte) error {
|
||||
endOffset := blockHdrOffset + blockHdrSize
|
||||
headerBytes := blockRow[blockHdrOffset:endOffset:endOffset]
|
||||
|
||||
var hash chainhash.Hash
|
||||
copy(hash[:], hashBytes[0:chainhash.HashSize])
|
||||
chainContext := blocksMap[hash]
|
||||
|
||||
if chainContext.height == -1 {
|
||||
return fmt.Errorf("Unable to calculate chain height for "+
|
||||
"stored block %s", hash)
|
||||
}
|
||||
|
||||
// Mark blocks as valid if they are part of the main chain.
|
||||
status := statusDataStored
|
||||
if chainContext.mainChain {
|
||||
status |= statusValid
|
||||
}
|
||||
|
||||
// Write header to v2 bucket
|
||||
value := make([]byte, blockHdrSize+1)
|
||||
copy(value[0:blockHdrSize], headerBytes)
|
||||
value[blockHdrSize] = byte(status)
|
||||
|
||||
key := blockIndexKey(&hash, uint32(chainContext.height))
|
||||
err := v2BlockIdxBucket.Put(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete header from v1 bucket
|
||||
truncatedRow := blockRow[0:blockHdrOffset:blockHdrOffset]
|
||||
return v1BlockIdxBucket.Put(hashBytes, truncatedRow)
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Block database migration complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
// readBlockTree reads the old block index bucket and constructs a mapping of
|
||||
// each block to its parent block and all child blocks. This mapping represents
|
||||
// the full tree of blocks. This function does not populate the height or
|
||||
// mainChain fields of the returned blockChainContext values.
|
||||
func readBlockTree(v1BlockIdxBucket database.Bucket) (map[chainhash.Hash]*blockChainContext, error) {
|
||||
blocksMap := make(map[chainhash.Hash]*blockChainContext)
|
||||
err := v1BlockIdxBucket.ForEach(func(_, blockRow []byte) error {
|
||||
var header wire.BlockHeader
|
||||
endOffset := blockHdrOffset + blockHdrSize
|
||||
headerBytes := blockRow[blockHdrOffset:endOffset:endOffset]
|
||||
err := header.Deserialize(bytes.NewReader(headerBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blockHash := header.BlockHash()
|
||||
prevHash := header.PrevBlock
|
||||
|
||||
if blocksMap[blockHash] == nil {
|
||||
blocksMap[blockHash] = &blockChainContext{height: -1}
|
||||
}
|
||||
if blocksMap[prevHash] == nil {
|
||||
blocksMap[prevHash] = &blockChainContext{height: -1}
|
||||
}
|
||||
|
||||
blocksMap[blockHash].parent = &prevHash
|
||||
blocksMap[prevHash].children =
|
||||
append(blocksMap[prevHash].children, &blockHash)
|
||||
return nil
|
||||
})
|
||||
return blocksMap, err
|
||||
}
|
||||
|
||||
// determineBlockHeights takes a map of block hashes to a slice of child hashes
|
||||
// and uses it to compute the height for each block. The function assigns a
|
||||
// height of 0 to the genesis hash and explores the tree of blocks
|
||||
// breadth-first, assigning a height to every block with a path back to the
|
||||
// genesis block. This function modifies the height field on the blocksMap
|
||||
// entries.
|
||||
func determineBlockHeights(blocksMap map[chainhash.Hash]*blockChainContext) error {
|
||||
queue := list.New()
|
||||
|
||||
// The genesis block is included in blocksMap as a child of the zero hash
|
||||
// because that is the value of the PrevBlock field in the genesis header.
|
||||
preGenesisContext, exists := blocksMap[zeroHash]
|
||||
if !exists || len(preGenesisContext.children) == 0 {
|
||||
return fmt.Errorf("Unable to find genesis block")
|
||||
}
|
||||
|
||||
for _, genesisHash := range preGenesisContext.children {
|
||||
blocksMap[*genesisHash].height = 0
|
||||
queue.PushBack(genesisHash)
|
||||
}
|
||||
|
||||
for e := queue.Front(); e != nil; e = queue.Front() {
|
||||
queue.Remove(e)
|
||||
hash := e.Value.(*chainhash.Hash)
|
||||
height := blocksMap[*hash].height
|
||||
|
||||
// For each block with this one as a parent, assign it a height and
|
||||
// push to queue for future processing.
|
||||
for _, childHash := range blocksMap[*hash].children {
|
||||
blocksMap[*childHash].height = height + 1
|
||||
queue.PushBack(childHash)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// determineMainChainBlocks traverses the block graph down from the tip to
|
||||
// determine which block hashes that are part of the main chain. This function
|
||||
// modifies the mainChain field on the blocksMap entries.
|
||||
func determineMainChainBlocks(blocksMap map[chainhash.Hash]*blockChainContext, tip *chainhash.Hash) {
|
||||
for nextHash := tip; *nextHash != zeroHash; nextHash = blocksMap[*nextHash].parent {
|
||||
blocksMap[*nextHash].mainChain = true
|
||||
}
|
||||
}
|
||||
|
||||
// deserializeUtxoEntryV0 decodes a utxo entry from the passed serialized byte
|
||||
// slice according to the legacy version 0 format into a map of utxos keyed by
|
||||
// the output index within the transaction. The map is necessary because the
|
||||
// previous format encoded all unspent outputs for a transaction using a single
|
||||
// entry, whereas the new format encodes each unspent output individually.
|
||||
//
|
||||
// The legacy format is as follows:
|
||||
//
|
||||
// <version><height><header code><unspentness bitmap>[<compressed txouts>,...]
|
||||
//
|
||||
// Field Type Size
|
||||
// version VLQ variable
|
||||
// block height VLQ variable
|
||||
// header code VLQ variable
|
||||
// unspentness bitmap []byte variable
|
||||
// compressed txouts
|
||||
// compressed amount VLQ variable
|
||||
// compressed script []byte variable
|
||||
//
|
||||
// The serialized header code format is:
|
||||
// bit 0 - containing transaction is a coinbase
|
||||
// bit 1 - output zero is unspent
|
||||
// bit 2 - output one is unspent
|
||||
// bits 3-x - number of bytes in unspentness bitmap. When both bits 1 and 2
|
||||
// are unset, it encodes N-1 since there must be at least one unspent
|
||||
// output.
|
||||
//
|
||||
// The rationale for the header code scheme is as follows:
|
||||
// - Transactions which only pay to a single output and a change output are
|
||||
// extremely common, thus an extra byte for the unspentness bitmap can be
|
||||
// avoided for them by encoding those two outputs in the low order bits.
|
||||
// - Given it is encoded as a VLQ which can encode values up to 127 with a
|
||||
// single byte, that leaves 4 bits to represent the number of bytes in the
|
||||
// unspentness bitmap while still only consuming a single byte for the
|
||||
// header code. In other words, an unspentness bitmap with up to 120
|
||||
// transaction outputs can be encoded with a single-byte header code.
|
||||
// This covers the vast majority of transactions.
|
||||
// - Encoding N-1 bytes when both bits 1 and 2 are unset allows an additional
|
||||
// 8 outpoints to be encoded before causing the header code to require an
|
||||
// additional byte.
|
||||
//
|
||||
// Example 1:
|
||||
// From tx in main blockchain:
|
||||
// Blk 1, 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
||||
//
|
||||
// 010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52
|
||||
// <><><><------------------------------------------------------------------>
|
||||
// | | \--------\ |
|
||||
// | height | compressed txout 0
|
||||
// version header code
|
||||
//
|
||||
// - version: 1
|
||||
// - height: 1
|
||||
// - header code: 0x03 (coinbase, output zero unspent, 0 bytes of unspentness)
|
||||
// - unspentness: Nothing since it is zero bytes
|
||||
// - compressed txout 0:
|
||||
// - 0x32: VLQ-encoded compressed amount for 5000000000 (50 BTC)
|
||||
// - 0x04: special script type pay-to-pubkey
|
||||
// - 0x96...52: x-coordinate of the pubkey
|
||||
//
|
||||
// Example 2:
|
||||
// From tx in main blockchain:
|
||||
// Blk 113931, 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||
//
|
||||
// 0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58
|
||||
// <><----><><><------------------------------------------><-------------------------------------------->
|
||||
// | | | \-------------------\ | |
|
||||
// version | \--------\ unspentness | compressed txout 2
|
||||
// height header code compressed txout 0
|
||||
//
|
||||
// - version: 1
|
||||
// - height: 113931
|
||||
// - header code: 0x0a (output zero unspent, 1 byte in unspentness bitmap)
|
||||
// - unspentness: [0x01] (bit 0 is set, so output 0+2 = 2 is unspent)
|
||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||
// - compressed txout 0:
|
||||
// - 0x12: VLQ-encoded compressed amount for 20000000 (0.2 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xe2...8a: pubkey hash
|
||||
// - compressed txout 2:
|
||||
// - 0x8009: VLQ-encoded compressed amount for 15000000 (0.15 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xb8...58: pubkey hash
|
||||
//
|
||||
// Example 3:
|
||||
// From tx in main blockchain:
|
||||
// Blk 338156, 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620
|
||||
//
|
||||
// 0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6
|
||||
// <><----><><----><-------------------------------------------------->
|
||||
// | | | \-----------------\ |
|
||||
// version | \--------\ unspentness |
|
||||
// height header code compressed txout 22
|
||||
//
|
||||
// - version: 1
|
||||
// - height: 338156
|
||||
// - header code: 0x10 (2+1 = 3 bytes in unspentness bitmap)
|
||||
// NOTE: It's +1 since neither bit 1 nor 2 are set, so N-1 is encoded.
|
||||
// - unspentness: [0x00 0x00 0x10] (bit 20 is set, so output 20+2 = 22 is unspent)
|
||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||
// - compressed txout 22:
|
||||
// - 0x8ba5b9e763: VLQ-encoded compressed amount for 366875659 (3.66875659 BTC)
|
||||
// - 0x01: special script type pay-to-script-hash
|
||||
// - 0x1d...e6: script hash
|
||||
func deserializeUtxoEntryV0(serialized []byte) (map[uint32]*UtxoEntry, error) {
|
||||
// Deserialize the version.
|
||||
//
|
||||
// NOTE: Ignore version since it is no longer used in the new format.
|
||||
_, bytesRead := deserializeVLQ(serialized)
|
||||
offset := bytesRead
|
||||
if offset >= len(serialized) {
|
||||
return nil, errDeserialize("unexpected end of data after version")
|
||||
}
|
||||
|
||||
// Deserialize the block height.
|
||||
blockHeight, bytesRead := deserializeVLQ(serialized[offset:])
|
||||
offset += bytesRead
|
||||
if offset >= len(serialized) {
|
||||
return nil, errDeserialize("unexpected end of data after height")
|
||||
}
|
||||
|
||||
// Deserialize the header code.
|
||||
code, bytesRead := deserializeVLQ(serialized[offset:])
|
||||
offset += bytesRead
|
||||
if offset >= len(serialized) {
|
||||
return nil, errDeserialize("unexpected end of data after header")
|
||||
}
|
||||
|
||||
// Decode the header code.
|
||||
//
|
||||
// Bit 0 indicates whether the containing transaction is a coinbase.
|
||||
// Bit 1 indicates output 0 is unspent.
|
||||
// Bit 2 indicates output 1 is unspent.
|
||||
// Bits 3-x encodes the number of non-zero unspentness bitmap bytes that
|
||||
// follow. When both output 0 and 1 are spent, it encodes N-1.
|
||||
isCoinBase := code&0x01 != 0
|
||||
output0Unspent := code&0x02 != 0
|
||||
output1Unspent := code&0x04 != 0
|
||||
numBitmapBytes := code >> 3
|
||||
if !output0Unspent && !output1Unspent {
|
||||
numBitmapBytes++
|
||||
}
|
||||
|
||||
// Ensure there are enough bytes left to deserialize the unspentness
|
||||
// bitmap.
|
||||
if uint64(len(serialized[offset:])) < numBitmapBytes {
|
||||
return nil, errDeserialize("unexpected end of data for " +
|
||||
"unspentness bitmap")
|
||||
}
|
||||
|
||||
// Add sparse output for unspent outputs 0 and 1 as needed based on the
|
||||
// details provided by the header code.
|
||||
var outputIndexes []uint32
|
||||
if output0Unspent {
|
||||
outputIndexes = append(outputIndexes, 0)
|
||||
}
|
||||
if output1Unspent {
|
||||
outputIndexes = append(outputIndexes, 1)
|
||||
}
|
||||
|
||||
// Decode the unspentness bitmap adding a sparse output for each unspent
|
||||
// output.
|
||||
for i := uint32(0); i < uint32(numBitmapBytes); i++ {
|
||||
unspentBits := serialized[offset]
|
||||
for j := uint32(0); j < 8; j++ {
|
||||
if unspentBits&0x01 != 0 {
|
||||
// The first 2 outputs are encoded via the
|
||||
// header code, so adjust the output number
|
||||
// accordingly.
|
||||
outputNum := 2 + i*8 + j
|
||||
outputIndexes = append(outputIndexes, outputNum)
|
||||
}
|
||||
unspentBits >>= 1
|
||||
}
|
||||
offset++
|
||||
}
|
||||
|
||||
// Map to hold all of the converted outputs.
|
||||
entries := make(map[uint32]*UtxoEntry)
|
||||
|
||||
// All entries will need to potentially be marked as a coinbase.
|
||||
var packedFlags txoFlags
|
||||
if isCoinBase {
|
||||
packedFlags |= tfCoinBase
|
||||
}
|
||||
|
||||
// Decode and add all of the utxos.
|
||||
for i, outputIndex := range outputIndexes {
|
||||
// Decode the next utxo.
|
||||
amount, pkScript, bytesRead, err := decodeCompressedTxOut(
|
||||
serialized[offset:])
|
||||
if err != nil {
|
||||
return nil, errDeserialize(fmt.Sprintf("unable to "+
|
||||
"decode utxo at index %d: %v", i, err))
|
||||
}
|
||||
offset += bytesRead
|
||||
|
||||
// Create a new utxo entry with the details deserialized above.
|
||||
entries[outputIndex] = &UtxoEntry{
|
||||
amount: int64(amount),
|
||||
pkScript: pkScript,
|
||||
blockHeight: int32(blockHeight),
|
||||
packedFlags: packedFlags,
|
||||
}
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// upgradeUtxoSetToV2 migrates the utxo set entries from version 1 to 2 in
|
||||
// batches. It is guaranteed to updated if this returns without failure.
|
||||
func upgradeUtxoSetToV2(db database.DB, interrupt <-chan struct{}) error {
|
||||
// Hardcoded bucket names so updates to the global values do not affect
|
||||
// old upgrades.
|
||||
var (
|
||||
v1BucketName = []byte("utxoset")
|
||||
v2BucketName = []byte("utxosetv2")
|
||||
)
|
||||
|
||||
log.Infof("Upgrading utxo set to v2. This will take a while...")
|
||||
start := time.Now()
|
||||
|
||||
// Create the new utxo set bucket as needed.
|
||||
err := db.Update(func(dbTx database.Tx) error {
|
||||
_, err := dbTx.Metadata().CreateBucketIfNotExists(v2BucketName)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// doBatch contains the primary logic for upgrading the utxo set from
|
||||
// version 1 to 2 in batches. This is done because the utxo set can be
|
||||
// huge and thus attempting to migrate in a single database transaction
|
||||
// would result in massive memory usage and could potentially crash on
|
||||
// many systems due to ulimits.
|
||||
//
|
||||
// It returns the number of utxos processed.
|
||||
const maxUtxos = 200000
|
||||
doBatch := func(dbTx database.Tx) (uint32, error) {
|
||||
v1Bucket := dbTx.Metadata().Bucket(v1BucketName)
|
||||
v2Bucket := dbTx.Metadata().Bucket(v2BucketName)
|
||||
v1Cursor := v1Bucket.Cursor()
|
||||
|
||||
// Migrate utxos so long as the max number of utxos for this
|
||||
// batch has not been exceeded.
|
||||
var numUtxos uint32
|
||||
for ok := v1Cursor.First(); ok && numUtxos < maxUtxos; ok =
|
||||
v1Cursor.Next() {
|
||||
|
||||
// Old key was the transaction hash.
|
||||
oldKey := v1Cursor.Key()
|
||||
var txHash chainhash.Hash
|
||||
copy(txHash[:], oldKey)
|
||||
|
||||
// Deserialize the old entry which included all utxos
|
||||
// for the given transaction.
|
||||
utxos, err := deserializeUtxoEntryV0(v1Cursor.Value())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Add an entry for each utxo into the new bucket using
|
||||
// the new format.
|
||||
for txOutIdx, utxo := range utxos {
|
||||
reserialized, err := serializeUtxoEntry(utxo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
key := outpointKey(wire.OutPoint{
|
||||
Hash: txHash,
|
||||
Index: txOutIdx,
|
||||
})
|
||||
err = v2Bucket.Put(*key, reserialized)
|
||||
// NOTE: The key is intentionally not recycled
|
||||
// here since the database interface contract
|
||||
// prohibits modifications. It will be garbage
|
||||
// collected normally when the database is done
|
||||
// with it.
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove old entry.
|
||||
err = v1Bucket.Delete(oldKey)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
numUtxos += uint32(len(utxos))
|
||||
|
||||
if interruptRequested(interrupt) {
|
||||
// No error here so the database transaction
|
||||
// is not cancelled and therefore outstanding
|
||||
// work is written to disk.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return numUtxos, nil
|
||||
}
|
||||
|
||||
// Migrate all entries in batches for the reasons mentioned above.
|
||||
var totalUtxos uint64
|
||||
for {
|
||||
var numUtxos uint32
|
||||
err := db.Update(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
numUtxos, err = doBatch(dbTx)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if interruptRequested(interrupt) {
|
||||
return errInterruptRequested
|
||||
}
|
||||
|
||||
if numUtxos == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
totalUtxos += uint64(numUtxos)
|
||||
log.Infof("Migrated %d utxos (%d total)", numUtxos, totalUtxos)
|
||||
}
|
||||
|
||||
// Remove the old bucket and update the utxo set version once it has
|
||||
// been fully migrated.
|
||||
err = db.Update(func(dbTx database.Tx) error {
|
||||
err := dbTx.Metadata().DeleteBucket(v1BucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbPutVersion(dbTx, utxoSetVersionKeyName, 2)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
seconds := int64(time.Since(start) / time.Second)
|
||||
log.Infof("Done upgrading utxo set. Total utxos: %d in %d seconds",
|
||||
totalUtxos, seconds)
|
||||
return nil
|
||||
}
|
||||
|
||||
// maybeUpgradeDbBuckets checks the database version of the buckets used by this
|
||||
// package and performs any needed upgrades to bring them to the latest version.
|
||||
//
|
||||
// All buckets used by this package are guaranteed to be the latest version if
|
||||
// this function returns without error.
|
||||
func (b *BlockChain) maybeUpgradeDbBuckets(interrupt <-chan struct{}) error {
|
||||
// Load or create bucket versions as needed.
|
||||
var utxoSetVersion uint32
|
||||
err := b.db.Update(func(dbTx database.Tx) error {
|
||||
// Load the utxo set version from the database or create it and
|
||||
// initialize it to version 1 if it doesn't exist.
|
||||
var err error
|
||||
utxoSetVersion, err = dbFetchOrCreateVersion(dbTx,
|
||||
utxoSetVersionKeyName, 1)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the utxo set to v2 if needed.
|
||||
if utxoSetVersion < 2 {
|
||||
if err := upgradeUtxoSetToV2(b.db, interrupt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDeserializeUtxoEntryV0 ensures deserializing unspent trasaction output
|
||||
// entries from the legacy version 0 format works as expected.
|
||||
func TestDeserializeUtxoEntryV0(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
entries map[uint32]*UtxoEntry
|
||||
serialized []byte
|
||||
}{
|
||||
// From tx in main blockchain:
|
||||
// 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
||||
{
|
||||
name: "Only output 0, coinbase",
|
||||
entries: map[uint32]*UtxoEntry{
|
||||
0: {
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
blockHeight: 1,
|
||||
packedFlags: tfCoinBase,
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"),
|
||||
},
|
||||
// From tx in main blockchain:
|
||||
// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb
|
||||
{
|
||||
name: "Only output 1, not coinbase",
|
||||
entries: map[uint32]*UtxoEntry{
|
||||
1: {
|
||||
amount: 1000000,
|
||||
pkScript: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
||||
blockHeight: 100001,
|
||||
packedFlags: 0,
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("01858c21040700ee8bd501094a7d5ca318da2506de35e1cb025ddc"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// df3f3f442d9699857f7f49de4ff0b5d0f3448bec31cdc7b5bf6d25f2abd637d5
|
||||
{
|
||||
name: "Only output 2, coinbase",
|
||||
entries: map[uint32]*UtxoEntry{
|
||||
2: {
|
||||
amount: 100937281,
|
||||
pkScript: hexToBytes("76a914da33f77cee27c2a975ed5124d7e4f7f97513510188ac"),
|
||||
blockHeight: 99004,
|
||||
packedFlags: tfCoinBase,
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0185843c010182b095bf4100da33f77cee27c2a975ed5124d7e4f7f975135101"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||
{
|
||||
name: "outputs 0 and 2 not coinbase",
|
||||
entries: map[uint32]*UtxoEntry{
|
||||
0: {
|
||||
amount: 20000000,
|
||||
pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
|
||||
blockHeight: 113931,
|
||||
packedFlags: 0,
|
||||
},
|
||||
2: {
|
||||
amount: 15000000,
|
||||
pkScript: hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"),
|
||||
blockHeight: 113931,
|
||||
packedFlags: 0,
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
|
||||
},
|
||||
// Adapted from tx in main blockchain:
|
||||
// 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620
|
||||
{
|
||||
name: "Only output 22, not coinbase",
|
||||
entries: map[uint32]*UtxoEntry{
|
||||
22: {
|
||||
amount: 366875659,
|
||||
pkScript: hexToBytes("a9141dd46a006572d820e448e12d2bbb38640bc718e687"),
|
||||
blockHeight: 338156,
|
||||
packedFlags: 0,
|
||||
},
|
||||
},
|
||||
serialized: hexToBytes("0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
// Deserialize to map of utxos keyed by the output index.
|
||||
entries, err := deserializeUtxoEntryV0(test.serialized)
|
||||
if err != nil {
|
||||
t.Errorf("deserializeUtxoEntryV0 #%d (%s) unexpected "+
|
||||
"error: %v", i, test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the deserialized entry has the same properties as the
|
||||
// ones in the test entry.
|
||||
if !reflect.DeepEqual(entries, test.entries) {
|
||||
t.Errorf("deserializeUtxoEntryV0 #%d (%s) unexpected "+
|
||||
"entries: got %v, want %v", i, test.name,
|
||||
entries, test.entries)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+318
-308
@@ -10,179 +10,104 @@ import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// utxoOutput houses details about an individual unspent transaction output such
|
||||
// as whether or not it is spent, its public key script, and how much it pays.
|
||||
//
|
||||
// Standard public key scripts are stored in the database using a compressed
|
||||
// format. Since the vast majority of scripts are of the standard form, a fairly
|
||||
// significant savings is achieved by discarding the portions of the standard
|
||||
// scripts that can be reconstructed.
|
||||
//
|
||||
// Also, since it is common for only a specific output in a given utxo entry to
|
||||
// be referenced from a redeeming transaction, the script and amount for a given
|
||||
// output is not uncompressed until the first time it is accessed. This
|
||||
// provides a mechanism to avoid the overhead of needlessly uncompressing all
|
||||
// outputs for a given utxo entry at the time of load.
|
||||
type utxoOutput struct {
|
||||
spent bool // Output is spent.
|
||||
compressed bool // The amount and public key script are compressed.
|
||||
amount int64 // The amount of the output.
|
||||
pkScript []byte // The public key script for the output.
|
||||
}
|
||||
// txoFlags is a bitmask defining additional information and state for a
|
||||
// transaction output in a utxo view.
|
||||
type txoFlags uint8
|
||||
|
||||
// maybeDecompress decompresses the amount and public key script fields of the
|
||||
// utxo and marks it decompressed if needed.
|
||||
func (o *utxoOutput) maybeDecompress(version int32) {
|
||||
// Nothing to do if it's not compressed.
|
||||
if !o.compressed {
|
||||
return
|
||||
}
|
||||
const (
|
||||
// tfCoinBase indicates that a txout was contained in a coinbase tx.
|
||||
tfCoinBase txoFlags = 1 << iota
|
||||
|
||||
o.amount = int64(decompressTxOutAmount(uint64(o.amount)))
|
||||
o.pkScript = decompressScript(o.pkScript, version)
|
||||
o.compressed = false
|
||||
}
|
||||
// tfSpent indicates that a txout is spent.
|
||||
tfSpent
|
||||
|
||||
// UtxoEntry contains contextual information about an unspent transaction such
|
||||
// as whether or not it is a coinbase transaction, which block it was found in,
|
||||
// and the spent status of its outputs.
|
||||
// tfModified indicates that a txout has been modified since it was
|
||||
// loaded.
|
||||
tfModified
|
||||
)
|
||||
|
||||
// UtxoEntry houses details about an individual transaction output in a utxo
|
||||
// view such as whether or not it was contained in a coinbase tx, the height of
|
||||
// the block that contains the tx, whether or not it is spent, its public key
|
||||
// script, and how much it pays.
|
||||
type UtxoEntry struct {
|
||||
modified bool // Entry changed since load.
|
||||
version int32 // The version of this tx.
|
||||
isCoinBase bool // Whether entry is a coinbase tx.
|
||||
blockHeight int32 // Height of block containing tx.
|
||||
sparseOutputs map[uint32]*utxoOutput // Sparse map of unspent outputs.
|
||||
// NOTE: Additions, deletions, or modifications to the order of the
|
||||
// definitions in this struct should not be changed without considering
|
||||
// how it affects alignment on 64-bit platforms. The current order is
|
||||
// specifically crafted to result in minimal padding. There will be a
|
||||
// lot of these in memory, so a few extra bytes of padding adds up.
|
||||
|
||||
amount int64
|
||||
pkScript []byte // The public key script for the output.
|
||||
blockHeight int32 // Height of block containing tx.
|
||||
|
||||
// packedFlags contains additional info about output such as whether it
|
||||
// is a coinbase, whether it is spent, and whether it has been modified
|
||||
// since it was loaded. This approach is used in order to reduce memory
|
||||
// usage since there will be a lot of these in memory.
|
||||
packedFlags txoFlags
|
||||
}
|
||||
|
||||
// Version returns the version of the transaction the utxo represents.
|
||||
func (entry *UtxoEntry) Version() int32 {
|
||||
return entry.version
|
||||
// isModified returns whether or not the output has been modified since it was
|
||||
// loaded.
|
||||
func (entry *UtxoEntry) isModified() bool {
|
||||
return entry.packedFlags&tfModified == tfModified
|
||||
}
|
||||
|
||||
// IsCoinBase returns whether or not the transaction the utxo entry represents
|
||||
// is a coinbase.
|
||||
// IsCoinBase returns whether or not the output was contained in a coinbase
|
||||
// transaction.
|
||||
func (entry *UtxoEntry) IsCoinBase() bool {
|
||||
return entry.isCoinBase
|
||||
return entry.packedFlags&tfCoinBase == tfCoinBase
|
||||
}
|
||||
|
||||
// BlockHeight returns the height of the block containing the transaction the
|
||||
// utxo entry represents.
|
||||
// BlockHeight returns the height of the block containing the output.
|
||||
func (entry *UtxoEntry) BlockHeight() int32 {
|
||||
return entry.blockHeight
|
||||
}
|
||||
|
||||
// IsOutputSpent returns whether or not the provided output index has been
|
||||
// spent based upon the current state of the unspent transaction output view
|
||||
// the entry was obtained from.
|
||||
//
|
||||
// Returns true if the output index references an output that does not exist
|
||||
// either due to it being invalid or because the output is not part of the view
|
||||
// due to previously being spent/pruned.
|
||||
func (entry *UtxoEntry) IsOutputSpent(outputIndex uint32) bool {
|
||||
output, ok := entry.sparseOutputs[outputIndex]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return output.spent
|
||||
// IsSpent returns whether or not the output has been spent based upon the
|
||||
// current state of the unspent transaction output view it was obtained from.
|
||||
func (entry *UtxoEntry) IsSpent() bool {
|
||||
return entry.packedFlags&tfSpent == tfSpent
|
||||
}
|
||||
|
||||
// SpendOutput marks the output at the provided index as spent. Specifying an
|
||||
// output index that does not exist will not have any effect.
|
||||
func (entry *UtxoEntry) SpendOutput(outputIndex uint32) {
|
||||
output, ok := entry.sparseOutputs[outputIndex]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Spend marks the output as spent. Spending an output that is already spent
|
||||
// has no effect.
|
||||
func (entry *UtxoEntry) Spend() {
|
||||
// Nothing to do if the output is already spent.
|
||||
if output.spent {
|
||||
if entry.IsSpent() {
|
||||
return
|
||||
}
|
||||
|
||||
entry.modified = true
|
||||
output.spent = true
|
||||
// Mark the output as spent and modified.
|
||||
entry.packedFlags |= tfSpent | tfModified
|
||||
}
|
||||
|
||||
// IsFullySpent returns whether or not the transaction the utxo entry represents
|
||||
// is fully spent.
|
||||
func (entry *UtxoEntry) IsFullySpent() bool {
|
||||
// The entry is not fully spent if any of the outputs are unspent.
|
||||
for _, output := range entry.sparseOutputs {
|
||||
if !output.spent {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
// Amount returns the amount of the output.
|
||||
func (entry *UtxoEntry) Amount() int64 {
|
||||
return entry.amount
|
||||
}
|
||||
|
||||
// AmountByIndex returns the amount of the provided output index.
|
||||
//
|
||||
// Returns 0 if the output index references an output that does not exist
|
||||
// either due to it being invalid or because the output is not part of the view
|
||||
// due to previously being spent/pruned.
|
||||
func (entry *UtxoEntry) AmountByIndex(outputIndex uint32) int64 {
|
||||
output, ok := entry.sparseOutputs[outputIndex]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Ensure the output is decompressed before returning the amount.
|
||||
output.maybeDecompress(entry.version)
|
||||
return output.amount
|
||||
// PkScript returns the public key script for the output.
|
||||
func (entry *UtxoEntry) PkScript() []byte {
|
||||
return entry.pkScript
|
||||
}
|
||||
|
||||
// PkScriptByIndex returns the public key script for the provided output index.
|
||||
//
|
||||
// Returns nil if the output index references an output that does not exist
|
||||
// either due to it being invalid or because the output is not part of the view
|
||||
// due to previously being spent/pruned.
|
||||
func (entry *UtxoEntry) PkScriptByIndex(outputIndex uint32) []byte {
|
||||
output, ok := entry.sparseOutputs[outputIndex]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure the output is decompressed before returning the script.
|
||||
output.maybeDecompress(entry.version)
|
||||
return output.pkScript
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the utxo entry.
|
||||
// Clone returns a shallow copy of the utxo entry.
|
||||
func (entry *UtxoEntry) Clone() *UtxoEntry {
|
||||
if entry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
newEntry := &UtxoEntry{
|
||||
version: entry.version,
|
||||
isCoinBase: entry.isCoinBase,
|
||||
blockHeight: entry.blockHeight,
|
||||
sparseOutputs: make(map[uint32]*utxoOutput),
|
||||
}
|
||||
for outputIndex, output := range entry.sparseOutputs {
|
||||
newEntry.sparseOutputs[outputIndex] = &utxoOutput{
|
||||
spent: output.spent,
|
||||
compressed: output.compressed,
|
||||
amount: output.amount,
|
||||
pkScript: output.pkScript,
|
||||
}
|
||||
}
|
||||
return newEntry
|
||||
}
|
||||
|
||||
// newUtxoEntry returns a new unspent transaction output entry with the provided
|
||||
// coinbase flag and block height ready to have unspent outputs added.
|
||||
func newUtxoEntry(version int32, isCoinBase bool, blockHeight int32) *UtxoEntry {
|
||||
return &UtxoEntry{
|
||||
version: version,
|
||||
isCoinBase: isCoinBase,
|
||||
blockHeight: blockHeight,
|
||||
sparseOutputs: make(map[uint32]*utxoOutput),
|
||||
amount: entry.amount,
|
||||
pkScript: entry.pkScript,
|
||||
blockHeight: entry.blockHeight,
|
||||
packedFlags: entry.packedFlags,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +119,7 @@ func newUtxoEntry(version int32, isCoinBase bool, blockHeight int32) *UtxoEntry
|
||||
// The unspent outputs are needed by other transactions for things such as
|
||||
// script validation and double spend prevention.
|
||||
type UtxoViewpoint struct {
|
||||
entries map[chainhash.Hash]*UtxoEntry
|
||||
entries map[wire.OutPoint]*UtxoEntry
|
||||
bestHash chainhash.Hash
|
||||
}
|
||||
|
||||
@@ -210,17 +135,60 @@ func (view *UtxoViewpoint) SetBestHash(hash *chainhash.Hash) {
|
||||
view.bestHash = *hash
|
||||
}
|
||||
|
||||
// LookupEntry returns information about a given transaction according to the
|
||||
// current state of the view. It will return nil if the passed transaction
|
||||
// hash does not exist in the view or is otherwise not available such as when
|
||||
// it has been disconnected during a reorg.
|
||||
func (view *UtxoViewpoint) LookupEntry(txHash *chainhash.Hash) *UtxoEntry {
|
||||
entry, ok := view.entries[*txHash]
|
||||
if !ok {
|
||||
return nil
|
||||
// LookupEntry returns information about a given transaction output according to
|
||||
// the current state of the view. It will return nil if the passed output does
|
||||
// not exist in the view or is otherwise not available such as when it has been
|
||||
// disconnected during a reorg.
|
||||
func (view *UtxoViewpoint) LookupEntry(outpoint wire.OutPoint) *UtxoEntry {
|
||||
return view.entries[outpoint]
|
||||
}
|
||||
|
||||
// addTxOut adds the specified output to the view if it is not provably
|
||||
// unspendable. When the view already has an entry for the output, it will be
|
||||
// marked unspent. All fields will be updated for existing entries since it's
|
||||
// possible it has changed during a reorg.
|
||||
func (view *UtxoViewpoint) addTxOut(outpoint wire.OutPoint, txOut *wire.TxOut, isCoinBase bool, blockHeight int32) {
|
||||
// Don't add provably unspendable outputs.
|
||||
if txscript.IsUnspendable(txOut.PkScript) {
|
||||
return
|
||||
}
|
||||
|
||||
return entry
|
||||
// Update existing entries. All fields are updated because it's
|
||||
// possible (although extremely unlikely) that the existing entry is
|
||||
// being replaced by a different transaction with the same hash. This
|
||||
// is allowed so long as the previous transaction is fully spent.
|
||||
entry := view.LookupEntry(outpoint)
|
||||
if entry == nil {
|
||||
entry = new(UtxoEntry)
|
||||
view.entries[outpoint] = entry
|
||||
}
|
||||
|
||||
entry.amount = txOut.Value
|
||||
entry.pkScript = txOut.PkScript
|
||||
entry.blockHeight = blockHeight
|
||||
entry.packedFlags = tfModified
|
||||
if isCoinBase {
|
||||
entry.packedFlags |= tfCoinBase
|
||||
}
|
||||
}
|
||||
|
||||
// AddTxOut adds the specified output of the passed transaction to the view if
|
||||
// it exists and is not provably unspendable. When the view already has an
|
||||
// entry for the output, it will be marked unspent. All fields will be updated
|
||||
// for existing entries since it's possible it has changed during a reorg.
|
||||
func (view *UtxoViewpoint) AddTxOut(tx *btcutil.Tx, txOutIdx uint32, blockHeight int32) {
|
||||
// Can't add an output for an out of bounds index.
|
||||
if txOutIdx >= uint32(len(tx.MsgTx().TxOut)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update existing entries. All fields are updated because it's
|
||||
// possible (although extremely unlikely) that the existing entry is
|
||||
// being replaced by a different transaction with the same hash. This
|
||||
// is allowed so long as the previous transaction is fully spent.
|
||||
prevOut := wire.OutPoint{Hash: *tx.Hash(), Index: txOutIdx}
|
||||
txOut := tx.MsgTx().TxOut[txOutIdx]
|
||||
view.addTxOut(prevOut, txOut, IsCoinBase(tx), blockHeight)
|
||||
}
|
||||
|
||||
// AddTxOuts adds all outputs in the passed transaction which are not provably
|
||||
@@ -228,45 +196,18 @@ func (view *UtxoViewpoint) LookupEntry(txHash *chainhash.Hash) *UtxoEntry {
|
||||
// outputs, they are simply marked unspent. All fields will be updated for
|
||||
// existing entries since it's possible it has changed during a reorg.
|
||||
func (view *UtxoViewpoint) AddTxOuts(tx *btcutil.Tx, blockHeight int32) {
|
||||
// When there are not already any utxos associated with the transaction,
|
||||
// add a new entry for it to the view.
|
||||
entry := view.LookupEntry(tx.Hash())
|
||||
if entry == nil {
|
||||
entry = newUtxoEntry(tx.MsgTx().Version, IsCoinBase(tx),
|
||||
blockHeight)
|
||||
view.entries[*tx.Hash()] = entry
|
||||
} else {
|
||||
entry.blockHeight = blockHeight
|
||||
}
|
||||
entry.modified = true
|
||||
|
||||
// Loop all of the transaction outputs and add those which are not
|
||||
// provably unspendable.
|
||||
isCoinBase := IsCoinBase(tx)
|
||||
prevOut := wire.OutPoint{Hash: *tx.Hash()}
|
||||
for txOutIdx, txOut := range tx.MsgTx().TxOut {
|
||||
if txscript.IsUnspendable(txOut.PkScript) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update existing entries. All fields are updated because it's
|
||||
// possible (although extremely unlikely) that the existing
|
||||
// entry is being replaced by a different transaction with the
|
||||
// same hash. This is allowed so long as the previous
|
||||
// transaction is fully spent.
|
||||
if output, ok := entry.sparseOutputs[uint32(txOutIdx)]; ok {
|
||||
output.spent = false
|
||||
output.compressed = false
|
||||
output.amount = txOut.Value
|
||||
output.pkScript = txOut.PkScript
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the unspent transaction output.
|
||||
entry.sparseOutputs[uint32(txOutIdx)] = &utxoOutput{
|
||||
spent: false,
|
||||
compressed: false,
|
||||
amount: txOut.Value,
|
||||
pkScript: txOut.PkScript,
|
||||
}
|
||||
prevOut.Index = uint32(txOutIdx)
|
||||
view.addTxOut(prevOut, txOut, isCoinBase, blockHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +216,7 @@ func (view *UtxoViewpoint) AddTxOuts(tx *btcutil.Tx, blockHeight int32) {
|
||||
// spent. In addition, when the 'stxos' argument is not nil, it will be updated
|
||||
// to append an entry for each spent txout. An error will be returned if the
|
||||
// view does not contain the required utxos.
|
||||
func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, stxos *[]spentTxOut) error {
|
||||
func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, stxos *[]SpentTxOut) error {
|
||||
// Coinbase transactions don't have any inputs to spend.
|
||||
if IsCoinBase(tx) {
|
||||
// Add the transaction's outputs as available utxos.
|
||||
@@ -287,39 +228,30 @@ func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32,
|
||||
// if a slice was provided for the spent txout details, append an entry
|
||||
// to it.
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
originIndex := txIn.PreviousOutPoint.Index
|
||||
entry := view.entries[txIn.PreviousOutPoint.Hash]
|
||||
|
||||
// Ensure the referenced utxo exists in the view. This should
|
||||
// never happen unless there is a bug is introduced in the code.
|
||||
entry := view.entries[txIn.PreviousOutPoint]
|
||||
if entry == nil {
|
||||
return AssertError(fmt.Sprintf("view missing input %v",
|
||||
txIn.PreviousOutPoint))
|
||||
}
|
||||
entry.SpendOutput(originIndex)
|
||||
|
||||
// Don't create the stxo details if not requested.
|
||||
if stxos == nil {
|
||||
continue
|
||||
// Only create the stxo details if requested.
|
||||
if stxos != nil {
|
||||
// Populate the stxo details using the utxo entry.
|
||||
var stxo = SpentTxOut{
|
||||
Amount: entry.Amount(),
|
||||
PkScript: entry.PkScript(),
|
||||
Height: entry.BlockHeight(),
|
||||
IsCoinBase: entry.IsCoinBase(),
|
||||
}
|
||||
*stxos = append(*stxos, stxo)
|
||||
}
|
||||
|
||||
// Populate the stxo details using the utxo entry. When the
|
||||
// transaction is fully spent, set the additional stxo fields
|
||||
// accordingly since those details will no longer be available
|
||||
// in the utxo set.
|
||||
var stxo = spentTxOut{
|
||||
compressed: false,
|
||||
version: entry.Version(),
|
||||
amount: entry.AmountByIndex(originIndex),
|
||||
pkScript: entry.PkScriptByIndex(originIndex),
|
||||
}
|
||||
if entry.IsFullySpent() {
|
||||
stxo.height = entry.BlockHeight()
|
||||
stxo.isCoinBase = entry.IsCoinBase()
|
||||
}
|
||||
|
||||
// Append the entry to the provided spent txouts slice.
|
||||
*stxos = append(*stxos, stxo)
|
||||
// Mark the entry as spent. This is not done until after the
|
||||
// relevant details have been accessed since spending it might
|
||||
// clear the fields from memory in the future.
|
||||
entry.Spend()
|
||||
}
|
||||
|
||||
// Add the transaction's outputs as available utxos.
|
||||
@@ -332,7 +264,7 @@ func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32,
|
||||
// spend as spent, and setting the best hash for the view to the passed block.
|
||||
// In addition, when the 'stxos' argument is not nil, it will be updated to
|
||||
// append an entry for each spent txout.
|
||||
func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]spentTxOut) error {
|
||||
func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]SpentTxOut) error {
|
||||
for _, tx := range block.Transactions() {
|
||||
err := view.connectTransaction(tx, block.Height(), stxos)
|
||||
if err != nil {
|
||||
@@ -346,11 +278,37 @@ func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]sp
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchEntryByHash attempts to find any available utxo for the given hash by
|
||||
// searching the entire set of possible outputs for the given hash. It checks
|
||||
// the view first and then falls back to the database if needed.
|
||||
func (view *UtxoViewpoint) fetchEntryByHash(db database.DB, hash *chainhash.Hash) (*UtxoEntry, error) {
|
||||
// First attempt to find a utxo with the provided hash in the view.
|
||||
prevOut := wire.OutPoint{Hash: *hash}
|
||||
for idx := uint32(0); idx < MaxOutputsPerBlock; idx++ {
|
||||
prevOut.Index = idx
|
||||
entry := view.LookupEntry(prevOut)
|
||||
if entry != nil {
|
||||
return entry, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check the database since it doesn't exist in the view. This will
|
||||
// often by the case since only specifically referenced utxos are loaded
|
||||
// into the view.
|
||||
var entry *UtxoEntry
|
||||
err := db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
entry, err = dbFetchUtxoEntryByHash(dbTx, hash)
|
||||
return err
|
||||
})
|
||||
return entry, err
|
||||
}
|
||||
|
||||
// disconnectTransactions updates the view by removing all of the transactions
|
||||
// created by the passed block, restoring all utxos the transactions spent by
|
||||
// using the provided spent txo information, and setting the best hash for the
|
||||
// view to the block before the passed block.
|
||||
func (view *UtxoViewpoint) disconnectTransactions(block *btcutil.Block, stxos []spentTxOut) error {
|
||||
func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil.Block, stxos []SpentTxOut) error {
|
||||
// Sanity check the correct number of stxos are provided.
|
||||
if len(stxos) != countSpentOutputs(block) {
|
||||
return AssertError("disconnectTransactions called with bad " +
|
||||
@@ -365,25 +323,52 @@ func (view *UtxoViewpoint) disconnectTransactions(block *btcutil.Block, stxos []
|
||||
for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- {
|
||||
tx := transactions[txIdx]
|
||||
|
||||
// Clear this transaction from the view if it already exists or
|
||||
// create a new empty entry for when it does not. This is done
|
||||
// because the code relies on its existence in the view in order
|
||||
// to signal modifications have happened.
|
||||
isCoinbase := txIdx == 0
|
||||
entry := view.entries[*tx.Hash()]
|
||||
if entry == nil {
|
||||
entry = newUtxoEntry(tx.MsgTx().Version, isCoinbase,
|
||||
block.Height())
|
||||
view.entries[*tx.Hash()] = entry
|
||||
// All entries will need to potentially be marked as a coinbase.
|
||||
var packedFlags txoFlags
|
||||
isCoinBase := txIdx == 0
|
||||
if isCoinBase {
|
||||
packedFlags |= tfCoinBase
|
||||
}
|
||||
|
||||
// Mark all of the spendable outputs originally created by the
|
||||
// transaction as spent. It is instructive to note that while
|
||||
// the outputs aren't actually being spent here, rather they no
|
||||
// longer exist, since a pruned utxo set is used, there is no
|
||||
// practical difference between a utxo that does not exist and
|
||||
// one that has been spent.
|
||||
//
|
||||
// When the utxo does not already exist in the view, add an
|
||||
// entry for it and then mark it spent. This is done because
|
||||
// the code relies on its existence in the view in order to
|
||||
// signal modifications have happened.
|
||||
txHash := tx.Hash()
|
||||
prevOut := wire.OutPoint{Hash: *txHash}
|
||||
for txOutIdx, txOut := range tx.MsgTx().TxOut {
|
||||
if txscript.IsUnspendable(txOut.PkScript) {
|
||||
continue
|
||||
}
|
||||
|
||||
prevOut.Index = uint32(txOutIdx)
|
||||
entry := view.entries[prevOut]
|
||||
if entry == nil {
|
||||
entry = &UtxoEntry{
|
||||
amount: txOut.Value,
|
||||
pkScript: txOut.PkScript,
|
||||
blockHeight: block.Height(),
|
||||
packedFlags: packedFlags,
|
||||
}
|
||||
|
||||
view.entries[prevOut] = entry
|
||||
}
|
||||
|
||||
entry.Spend()
|
||||
}
|
||||
entry.modified = true
|
||||
entry.sparseOutputs = make(map[uint32]*utxoOutput)
|
||||
|
||||
// Loop backwards through all of the transaction inputs (except
|
||||
// for the coinbase which has no inputs) and unspend the
|
||||
// referenced txos. This is necessary to match the order of the
|
||||
// spent txout entries.
|
||||
if isCoinbase {
|
||||
if isCoinBase {
|
||||
continue
|
||||
}
|
||||
for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- {
|
||||
@@ -393,40 +378,57 @@ func (view *UtxoViewpoint) disconnectTransactions(block *btcutil.Block, stxos []
|
||||
stxoIdx--
|
||||
|
||||
// When there is not already an entry for the referenced
|
||||
// transaction in the view, it means it was fully spent,
|
||||
// output in the view, it means it was previously spent,
|
||||
// so create a new utxo entry in order to resurrect it.
|
||||
txIn := tx.MsgTx().TxIn[txInIdx]
|
||||
originHash := &txIn.PreviousOutPoint.Hash
|
||||
originIndex := txIn.PreviousOutPoint.Index
|
||||
entry := view.entries[*originHash]
|
||||
originOut := &tx.MsgTx().TxIn[txInIdx].PreviousOutPoint
|
||||
entry := view.entries[*originOut]
|
||||
if entry == nil {
|
||||
entry = newUtxoEntry(stxo.version,
|
||||
stxo.isCoinBase, stxo.height)
|
||||
view.entries[*originHash] = entry
|
||||
entry = new(UtxoEntry)
|
||||
view.entries[*originOut] = entry
|
||||
}
|
||||
|
||||
// Mark the entry as modified since it is either new
|
||||
// or will be changed below.
|
||||
entry.modified = true
|
||||
|
||||
// Restore the specific utxo using the stxo data from
|
||||
// the spend journal if it doesn't already exist in the
|
||||
// view.
|
||||
output, ok := entry.sparseOutputs[originIndex]
|
||||
if !ok {
|
||||
// Add the unspent transaction output.
|
||||
entry.sparseOutputs[originIndex] = &utxoOutput{
|
||||
spent: false,
|
||||
compressed: stxo.compressed,
|
||||
amount: stxo.amount,
|
||||
pkScript: stxo.pkScript,
|
||||
// The legacy v1 spend journal format only stored the
|
||||
// coinbase flag and height when the output was the last
|
||||
// unspent output of the transaction. As a result, when
|
||||
// the information is missing, search for it by scanning
|
||||
// all possible outputs of the transaction since it must
|
||||
// be in one of them.
|
||||
//
|
||||
// It should be noted that this is quite inefficient,
|
||||
// but it realistically will almost never run since all
|
||||
// new entries include the information for all outputs
|
||||
// and thus the only way this will be hit is if a long
|
||||
// enough reorg happens such that a block with the old
|
||||
// spend data is being disconnected. The probability of
|
||||
// that in practice is extremely low to begin with and
|
||||
// becomes vanishingly small the more new blocks are
|
||||
// connected. In the case of a fresh database that has
|
||||
// only ever run with the new v2 format, this code path
|
||||
// will never run.
|
||||
if stxo.Height == 0 {
|
||||
utxo, err := view.fetchEntryByHash(db, txHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
if utxo == nil {
|
||||
return AssertError(fmt.Sprintf("unable "+
|
||||
"to resurrect legacy stxo %v",
|
||||
*originOut))
|
||||
}
|
||||
|
||||
stxo.Height = utxo.BlockHeight()
|
||||
stxo.IsCoinBase = utxo.IsCoinBase()
|
||||
}
|
||||
|
||||
// Mark the existing referenced transaction output as
|
||||
// unspent.
|
||||
output.spent = false
|
||||
// Restore the utxo using the stxo data from the spend
|
||||
// journal and mark it as modified.
|
||||
entry.amount = stxo.Amount
|
||||
entry.pkScript = stxo.PkScript
|
||||
entry.blockHeight = stxo.Height
|
||||
entry.packedFlags = tfModified
|
||||
if stxo.IsCoinBase {
|
||||
entry.packedFlags |= tfCoinBase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,88 +438,94 @@ func (view *UtxoViewpoint) disconnectTransactions(block *btcutil.Block, stxos []
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveEntry removes the given transaction output from the current state of
|
||||
// the view. It will have no effect if the passed output does not exist in the
|
||||
// view.
|
||||
func (view *UtxoViewpoint) RemoveEntry(outpoint wire.OutPoint) {
|
||||
delete(view.entries, outpoint)
|
||||
}
|
||||
|
||||
// Entries returns the underlying map that stores of all the utxo entries.
|
||||
func (view *UtxoViewpoint) Entries() map[chainhash.Hash]*UtxoEntry {
|
||||
func (view *UtxoViewpoint) Entries() map[wire.OutPoint]*UtxoEntry {
|
||||
return view.entries
|
||||
}
|
||||
|
||||
// commit prunes all entries marked modified that are now fully spent and marks
|
||||
// all entries as unmodified.
|
||||
func (view *UtxoViewpoint) commit() {
|
||||
for txHash, entry := range view.entries {
|
||||
if entry == nil || (entry.modified && entry.IsFullySpent()) {
|
||||
delete(view.entries, txHash)
|
||||
for outpoint, entry := range view.entries {
|
||||
if entry == nil || (entry.isModified() && entry.IsSpent()) {
|
||||
delete(view.entries, outpoint)
|
||||
continue
|
||||
}
|
||||
|
||||
entry.modified = false
|
||||
entry.packedFlags ^= tfModified
|
||||
}
|
||||
}
|
||||
|
||||
// fetchUtxosMain fetches unspent transaction output data about the provided
|
||||
// set of transactions from the point of view of the end of the main chain at
|
||||
// the time of the call.
|
||||
// set of outpoints from the point of view of the end of the main chain at the
|
||||
// time of the call.
|
||||
//
|
||||
// Upon completion of this function, the view will contain an entry for each
|
||||
// requested transaction. Fully spent transactions, or those which otherwise
|
||||
// don't exist, will result in a nil entry in the view.
|
||||
func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, txSet map[chainhash.Hash]struct{}) error {
|
||||
// Nothing to do if there are no requested hashes.
|
||||
if len(txSet) == 0 {
|
||||
// requested outpoint. Spent outputs, or those which otherwise don't exist,
|
||||
// will result in a nil entry in the view.
|
||||
func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.OutPoint]struct{}) error {
|
||||
// Nothing to do if there are no requested outputs.
|
||||
if len(outpoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load the unspent transaction output information for the requested set
|
||||
// of transactions from the point of view of the end of the main chain.
|
||||
// Load the requested set of unspent transaction outputs from the point
|
||||
// of view of the end of the main chain.
|
||||
//
|
||||
// NOTE: Missing entries are not considered an error here and instead
|
||||
// will result in nil entries in the view. This is intentionally done
|
||||
// since other code uses the presence of an entry in the store as a way
|
||||
// to optimize spend and unspend updates to apply only to the specific
|
||||
// utxos that the caller needs access to.
|
||||
// so other code can use the presence of an entry in the store as a way
|
||||
// to unnecessarily avoid attempting to reload it from the database.
|
||||
return db.View(func(dbTx database.Tx) error {
|
||||
for hash := range txSet {
|
||||
hashCopy := hash
|
||||
entry, err := dbFetchUtxoEntry(dbTx, &hashCopy)
|
||||
for outpoint := range outpoints {
|
||||
entry, err := dbFetchUtxoEntry(dbTx, outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
view.entries[hash] = entry
|
||||
view.entries[outpoint] = entry
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// fetchUtxos loads utxo details about provided set of transaction hashes into
|
||||
// the view from the database as needed unless they already exist in the view in
|
||||
// which case they are ignored.
|
||||
func (view *UtxoViewpoint) fetchUtxos(db database.DB, txSet map[chainhash.Hash]struct{}) error {
|
||||
// Nothing to do if there are no requested hashes.
|
||||
if len(txSet) == 0 {
|
||||
// fetchUtxos loads the unspent transaction outputs for the provided set of
|
||||
// outputs into the view from the database as needed unless they already exist
|
||||
// in the view in which case they are ignored.
|
||||
func (view *UtxoViewpoint) fetchUtxos(db database.DB, outpoints map[wire.OutPoint]struct{}) error {
|
||||
// Nothing to do if there are no requested outputs.
|
||||
if len(outpoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter entries that are already in the view.
|
||||
txNeededSet := make(map[chainhash.Hash]struct{})
|
||||
for hash := range txSet {
|
||||
neededSet := make(map[wire.OutPoint]struct{})
|
||||
for outpoint := range outpoints {
|
||||
// Already loaded into the current view.
|
||||
if _, ok := view.entries[hash]; ok {
|
||||
if _, ok := view.entries[outpoint]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
txNeededSet[hash] = struct{}{}
|
||||
neededSet[outpoint] = struct{}{}
|
||||
}
|
||||
|
||||
// Request the input utxos from the database.
|
||||
return view.fetchUtxosMain(db, txNeededSet)
|
||||
return view.fetchUtxosMain(db, neededSet)
|
||||
}
|
||||
|
||||
// fetchInputUtxos loads utxo details about the input transactions referenced
|
||||
// by the transactions in the given block into the view from the database as
|
||||
// needed. In particular, referenced entries that are earlier in the block are
|
||||
// added to the view and entries that are already in the view are not modified.
|
||||
// fetchInputUtxos loads the unspent transaction outputs for the inputs
|
||||
// referenced by the transactions in the given block into the view from the
|
||||
// database as needed. In particular, referenced entries that are earlier in
|
||||
// the block are added to the view and entries that are already in the view are
|
||||
// not modified.
|
||||
func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block) error {
|
||||
// Build a map of in-flight transactions because some of the inputs in
|
||||
// this block could be referencing other transactions earlier in this
|
||||
@@ -531,7 +539,7 @@ func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block)
|
||||
// Loop through all of the transaction inputs (except for the coinbase
|
||||
// which has no inputs) collecting them into sets of what is needed and
|
||||
// what is already known (in-flight).
|
||||
txNeededSet := make(map[chainhash.Hash]struct{})
|
||||
neededSet := make(map[wire.OutPoint]struct{})
|
||||
for i, tx := range transactions[1:] {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
// It is acceptable for a transaction input to reference
|
||||
@@ -556,72 +564,74 @@ func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block)
|
||||
|
||||
// Don't request entries that are already in the view
|
||||
// from the database.
|
||||
if _, ok := view.entries[*originHash]; ok {
|
||||
if _, ok := view.entries[txIn.PreviousOutPoint]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
txNeededSet[*originHash] = struct{}{}
|
||||
neededSet[txIn.PreviousOutPoint] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Request the input utxos from the database.
|
||||
return view.fetchUtxosMain(db, txNeededSet)
|
||||
return view.fetchUtxosMain(db, neededSet)
|
||||
}
|
||||
|
||||
// NewUtxoViewpoint returns a new empty unspent transaction output view.
|
||||
func NewUtxoViewpoint() *UtxoViewpoint {
|
||||
return &UtxoViewpoint{
|
||||
entries: make(map[chainhash.Hash]*UtxoEntry),
|
||||
entries: make(map[wire.OutPoint]*UtxoEntry),
|
||||
}
|
||||
}
|
||||
|
||||
// FetchUtxoView loads utxo details about the input transactions referenced by
|
||||
// FetchUtxoView loads unspent transaction outputs for the inputs referenced by
|
||||
// the passed transaction from the point of view of the end of the main chain.
|
||||
// It also attempts to fetch the utxo details for the transaction itself so the
|
||||
// returned view can be examined for duplicate unspent transaction outputs.
|
||||
// It also attempts to fetch the utxos for the outputs of the transaction itself
|
||||
// so the returned view can be examined for duplicate transactions.
|
||||
//
|
||||
// This function is safe for concurrent access however the returned view is NOT.
|
||||
func (b *BlockChain) FetchUtxoView(tx *btcutil.Tx) (*UtxoViewpoint, error) {
|
||||
b.chainLock.RLock()
|
||||
defer b.chainLock.RUnlock()
|
||||
|
||||
// Create a set of needed transactions based on those referenced by the
|
||||
// inputs of the passed transaction. Also, add the passed transaction
|
||||
// itself as a way for the caller to detect duplicates that are not
|
||||
// fully spent.
|
||||
txNeededSet := make(map[chainhash.Hash]struct{})
|
||||
txNeededSet[*tx.Hash()] = struct{}{}
|
||||
// Create a set of needed outputs based on those referenced by the
|
||||
// inputs of the passed transaction and the outputs of the transaction
|
||||
// itself.
|
||||
neededSet := make(map[wire.OutPoint]struct{})
|
||||
prevOut := wire.OutPoint{Hash: *tx.Hash()}
|
||||
for txOutIdx := range tx.MsgTx().TxOut {
|
||||
prevOut.Index = uint32(txOutIdx)
|
||||
neededSet[prevOut] = struct{}{}
|
||||
}
|
||||
if !IsCoinBase(tx) {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{}
|
||||
neededSet[txIn.PreviousOutPoint] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Request the utxos from the point of view of the end of the main
|
||||
// chain.
|
||||
view := NewUtxoViewpoint()
|
||||
err := view.fetchUtxosMain(b.db, txNeededSet)
|
||||
b.chainLock.RLock()
|
||||
err := view.fetchUtxosMain(b.db, neededSet)
|
||||
b.chainLock.RUnlock()
|
||||
return view, err
|
||||
}
|
||||
|
||||
// FetchUtxoEntry loads and returns the unspent transaction output entry for the
|
||||
// passed hash from the point of view of the end of the main chain.
|
||||
// FetchUtxoEntry loads and returns the requested unspent transaction output
|
||||
// from the point of view of the end of the main chain.
|
||||
//
|
||||
// NOTE: Requesting a hash for which there is no data will NOT return an error.
|
||||
// Instead both the entry and the error will be nil. This is done to allow
|
||||
// pruning of fully spent transactions. In practice this means the caller must
|
||||
// check if the returned entry is nil before invoking methods on it.
|
||||
// NOTE: Requesting an output for which there is no data will NOT return an
|
||||
// error. Instead both the entry and the error will be nil. This is done to
|
||||
// allow pruning of spent transaction outputs. In practice this means the
|
||||
// caller must check if the returned entry is nil before invoking methods on it.
|
||||
//
|
||||
// This function is safe for concurrent access however the returned entry (if
|
||||
// any) is NOT.
|
||||
func (b *BlockChain) FetchUtxoEntry(txHash *chainhash.Hash) (*UtxoEntry, error) {
|
||||
func (b *BlockChain) FetchUtxoEntry(outpoint wire.OutPoint) (*UtxoEntry, error) {
|
||||
b.chainLock.RLock()
|
||||
defer b.chainLock.RUnlock()
|
||||
|
||||
var entry *UtxoEntry
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
entry, err = dbFetchUtxoEntry(dbTx, txHash)
|
||||
entry, err = dbFetchUtxoEntry(dbTx, outpoint)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+32
-34
@@ -47,7 +47,7 @@ var (
|
||||
// zeroHash is the zero value for a chainhash.Hash and is defined as
|
||||
// a package level variable to avoid the need to create a new instance
|
||||
// every time a check is needed.
|
||||
zeroHash = &chainhash.Hash{}
|
||||
zeroHash chainhash.Hash
|
||||
|
||||
// block91842Hash is one of the two nodes which violate the rules
|
||||
// set forth in BIP0030. It is defined as a package level variable to
|
||||
@@ -63,7 +63,7 @@ var (
|
||||
// isNullOutpoint determines whether or not a previous transaction output point
|
||||
// is set.
|
||||
func isNullOutpoint(outpoint *wire.OutPoint) bool {
|
||||
if outpoint.Index == math.MaxUint32 && outpoint.Hash.IsEqual(zeroHash) {
|
||||
if outpoint.Index == math.MaxUint32 && outpoint.Hash == zeroHash {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -95,7 +95,7 @@ func IsCoinBaseTx(msgTx *wire.MsgTx) bool {
|
||||
// The previous output of a coin base must have a max value index and
|
||||
// a zero hash.
|
||||
prevOut := &msgTx.TxIn[0].PreviousOutPoint
|
||||
if prevOut.Index != math.MaxUint32 || !prevOut.Hash.IsEqual(zeroHash) {
|
||||
if prevOut.Index != math.MaxUint32 || prevOut.Hash != zeroHash {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -286,8 +286,7 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
||||
// Previous transaction outputs referenced by the inputs to this
|
||||
// transaction must not be null.
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
prevOut := &txIn.PreviousOutPoint
|
||||
if isNullOutpoint(prevOut) {
|
||||
if isNullOutpoint(&txIn.PreviousOutPoint) {
|
||||
return ruleError(ErrBadTxInput, "transaction "+
|
||||
"input refers to previous output that "+
|
||||
"is null")
|
||||
@@ -385,10 +384,8 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint)
|
||||
totalSigOps := 0
|
||||
for txInIndex, txIn := range msgTx.TxIn {
|
||||
// Ensure the referenced input transaction is available.
|
||||
originTxHash := &txIn.PreviousOutPoint.Hash
|
||||
originTxIndex := txIn.PreviousOutPoint.Index
|
||||
txEntry := utxoView.LookupEntry(originTxHash)
|
||||
if txEntry == nil || txEntry.IsOutputSpent(originTxIndex) {
|
||||
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
|
||||
if utxo == nil || utxo.IsSpent() {
|
||||
str := fmt.Sprintf("output %v referenced from "+
|
||||
"transaction %s:%d either does not exist or "+
|
||||
"has already been spent", txIn.PreviousOutPoint,
|
||||
@@ -398,7 +395,7 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint)
|
||||
|
||||
// We're only interested in pay-to-script-hash types, so skip
|
||||
// this input if it's not one.
|
||||
pkScript := txEntry.PkScriptByIndex(originTxIndex)
|
||||
pkScript := utxo.PkScript()
|
||||
if !txscript.IsPayToScriptHash(pkScript) {
|
||||
continue
|
||||
}
|
||||
@@ -791,7 +788,7 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
|
||||
}
|
||||
|
||||
// If segwit is active, then we'll need to fully validate the
|
||||
// new witness commitment for adherance to the rules.
|
||||
// new witness commitment for adherence to the rules.
|
||||
if segwitState == ThresholdActive {
|
||||
// Validate the witness commitment (if any) within the
|
||||
// block. This involves asserting that if the coinbase
|
||||
@@ -827,16 +824,21 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
|
||||
// duplicated to effectively revert the overwritten transactions to a single
|
||||
// confirmation thereby making them vulnerable to a double spend.
|
||||
//
|
||||
// For more details, see https://en.bitcoin.it/wiki/BIP_0030 and
|
||||
// For more details, see
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki and
|
||||
// http://r6.ca/blog/20120206T005236Z.html.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for reads).
|
||||
func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block, view *UtxoViewpoint) error {
|
||||
// Fetch utxo details for all of the transactions in this block.
|
||||
// Typically, there will not be any utxos for any of the transactions.
|
||||
fetchSet := make(map[chainhash.Hash]struct{})
|
||||
// Fetch utxos for all of the transaction ouputs in this block.
|
||||
// Typically, there will not be any utxos for any of the outputs.
|
||||
fetchSet := make(map[wire.OutPoint]struct{})
|
||||
for _, tx := range block.Transactions() {
|
||||
fetchSet[*tx.Hash()] = struct{}{}
|
||||
prevOut := wire.OutPoint{Hash: *tx.Hash()}
|
||||
for txOutIdx := range tx.MsgTx().TxOut {
|
||||
prevOut.Index = uint32(txOutIdx)
|
||||
fetchSet[prevOut] = struct{}{}
|
||||
}
|
||||
}
|
||||
err := view.fetchUtxos(b.db, fetchSet)
|
||||
if err != nil {
|
||||
@@ -845,12 +847,12 @@ func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block, view *U
|
||||
|
||||
// Duplicate transactions are only allowed if the previous transaction
|
||||
// is fully spent.
|
||||
for _, tx := range block.Transactions() {
|
||||
txEntry := view.LookupEntry(tx.Hash())
|
||||
if txEntry != nil && !txEntry.IsFullySpent() {
|
||||
for outpoint := range fetchSet {
|
||||
utxo := view.LookupEntry(outpoint)
|
||||
if utxo != nil && !utxo.IsSpent() {
|
||||
str := fmt.Sprintf("tried to overwrite transaction %v "+
|
||||
"at block height %d that is not fully spent",
|
||||
tx.Hash(), txEntry.blockHeight)
|
||||
outpoint.Hash, utxo.BlockHeight())
|
||||
return ruleError(ErrOverwriteTx, str)
|
||||
}
|
||||
}
|
||||
@@ -879,10 +881,8 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo
|
||||
var totalSatoshiIn int64
|
||||
for txInIndex, txIn := range tx.MsgTx().TxIn {
|
||||
// Ensure the referenced input transaction is available.
|
||||
originTxHash := &txIn.PreviousOutPoint.Hash
|
||||
originTxIndex := txIn.PreviousOutPoint.Index
|
||||
utxoEntry := utxoView.LookupEntry(originTxHash)
|
||||
if utxoEntry == nil || utxoEntry.IsOutputSpent(originTxIndex) {
|
||||
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
|
||||
if utxo == nil || utxo.IsSpent() {
|
||||
str := fmt.Sprintf("output %v referenced from "+
|
||||
"transaction %s:%d either does not exist or "+
|
||||
"has already been spent", txIn.PreviousOutPoint,
|
||||
@@ -892,15 +892,15 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo
|
||||
|
||||
// Ensure the transaction is not spending coins which have not
|
||||
// yet reached the required coinbase maturity.
|
||||
if utxoEntry.IsCoinBase() {
|
||||
originHeight := utxoEntry.BlockHeight()
|
||||
if utxo.IsCoinBase() {
|
||||
originHeight := utxo.BlockHeight()
|
||||
blocksSincePrev := txHeight - originHeight
|
||||
coinbaseMaturity := int32(chainParams.CoinbaseMaturity)
|
||||
if blocksSincePrev < coinbaseMaturity {
|
||||
str := fmt.Sprintf("tried to spend coinbase "+
|
||||
"transaction %v from height %v at "+
|
||||
"height %v before required maturity "+
|
||||
"of %v blocks", originTxHash,
|
||||
"transaction output %v from height %v "+
|
||||
"at height %v before required maturity "+
|
||||
"of %v blocks", txIn.PreviousOutPoint,
|
||||
originHeight, txHeight,
|
||||
coinbaseMaturity)
|
||||
return 0, ruleError(ErrImmatureSpend, str)
|
||||
@@ -913,7 +913,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo
|
||||
// a transaction are in a unit value known as a satoshi. One
|
||||
// bitcoin is a quantity of satoshi as defined by the
|
||||
// SatoshiPerBitcoin constant.
|
||||
originTxSatoshi := utxoEntry.AmountByIndex(originTxIndex)
|
||||
originTxSatoshi := utxo.Amount()
|
||||
if originTxSatoshi < 0 {
|
||||
str := fmt.Sprintf("transaction output has negative "+
|
||||
"value of %v", btcutil.Amount(originTxSatoshi))
|
||||
@@ -986,7 +986,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo
|
||||
// with that node.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos *[]spentTxOut) error {
|
||||
func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos *[]SpentTxOut) error {
|
||||
// If the side chain blocks end up in the database, a call to
|
||||
// CheckBlockSanity should be done here in case a previous version
|
||||
// allowed a block that is no longer valid. However, since the
|
||||
@@ -1274,8 +1274,6 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *btcutil.Block) error {
|
||||
// is not needed and thus extra work can be avoided.
|
||||
view := NewUtxoViewpoint()
|
||||
view.SetBestHash(&tip.hash)
|
||||
newNode := newBlockNode(&header, tip.height+1)
|
||||
newNode.parent = tip
|
||||
newNode.workSum = newNode.workSum.Add(tip.workSum, newNode.workSum)
|
||||
newNode := newBlockNode(&header, tip)
|
||||
return b.checkConnectBlock(newNode, block, view, nil)
|
||||
}
|
||||
|
||||
+17
-11
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
@@ -27,13 +28,21 @@ const (
|
||||
|
||||
// MaxBlockSigOpsCost is the maximum number of signature operations
|
||||
// allowed for a block. It is calculated via a weighted algorithm which
|
||||
// weights segragated witness sig ops lower than regular sig ops.
|
||||
// weights segregated witness sig ops lower than regular sig ops.
|
||||
MaxBlockSigOpsCost = 80000
|
||||
|
||||
// WitnessScaleFactor determines the level of "discount" witness data
|
||||
// receives compared to "base" data. A scale factor of 4, denotes that
|
||||
// witness data is 1/4 as cheap as regular non-witness data.
|
||||
WitnessScaleFactor = 4
|
||||
|
||||
// MinTxOutputWeight is the minimum possible weight for a transaction
|
||||
// output.
|
||||
MinTxOutputWeight = WitnessScaleFactor * wire.MinTxOutPayload
|
||||
|
||||
// MaxOutputsPerBlock is the maximum number of transaction outputs there
|
||||
// can be in a block of max weight size.
|
||||
MaxOutputsPerBlock = MaxBlockWeight / MinTxOutputWeight
|
||||
)
|
||||
|
||||
// GetBlockWeight computes the value of the weight metric for a given block.
|
||||
@@ -71,9 +80,7 @@ func GetTransactionWeight(tx *btcutil.Tx) int64 {
|
||||
// legacy sig op count scaled according to the WitnessScaleFactor, the sig op
|
||||
// count for all p2sh inputs scaled by the WitnessScaleFactor, and finally the
|
||||
// unscaled sig op count for any inputs spending witness programs.
|
||||
func GetSigOpCost(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint,
|
||||
bip16, segWit bool) (int, error) {
|
||||
|
||||
func GetSigOpCost(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint, bip16, segWit bool) (int, error) {
|
||||
numSigOps := CountSigOps(tx) * WitnessScaleFactor
|
||||
if bip16 {
|
||||
numP2SHSigOps, err := CountP2SHSigOps(tx, isCoinBaseTx, utxoView)
|
||||
@@ -86,11 +93,10 @@ func GetSigOpCost(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint,
|
||||
if segWit && !isCoinBaseTx {
|
||||
msgTx := tx.MsgTx()
|
||||
for txInIndex, txIn := range msgTx.TxIn {
|
||||
// Ensure the referenced input transaction is available.
|
||||
originTxHash := &txIn.PreviousOutPoint.Hash
|
||||
originTxIndex := txIn.PreviousOutPoint.Index
|
||||
txEntry := utxoView.LookupEntry(originTxHash)
|
||||
if txEntry == nil || txEntry.IsOutputSpent(originTxIndex) {
|
||||
// Ensure the referenced output is available and hasn't
|
||||
// already been spent.
|
||||
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
|
||||
if utxo == nil || utxo.IsSpent() {
|
||||
str := fmt.Sprintf("output %v referenced from "+
|
||||
"transaction %s:%d either does not "+
|
||||
"exist or has already been spent",
|
||||
@@ -101,7 +107,7 @@ func GetSigOpCost(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint,
|
||||
|
||||
witness := txIn.Witness
|
||||
sigScript := txIn.SignatureScript
|
||||
pkScript := txEntry.PkScriptByIndex(originTxIndex)
|
||||
pkScript := utxo.PkScript()
|
||||
numSigOps += txscript.GetWitnessSigOpCount(sigScript, pkScript, witness)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user