mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[DEV-75] Update UTXO model to work with Diffs (#53)
* [DEV-75] Removed fetchEntryByHash, which was no longer used. * [DEV-75] Removed a few functions in manager.go that weren't used by anything. * [DEV-75] checkConnectBlock will soon not accept a utxoViewpoint, so removed the call to view.Tip() so that it could be deleted. * [DEV-75] Got rid of tips in UtxoViewpoint. * [DEV-75] Added the full UTXO set to the virtual block. * [DEV-75] Implemented UTXO-wrangling when adding a new block. * [DEV-75] Added isCoinbase to utxoEntry creation. * [DEV-75] Added blockHeight to utxoEntry creation. * Implemented fetching the fullUTXOSet from the database. * [DEV-75] Simplified DAGState because almost all of the state in it was unnecessary. Also got rid of dbDAGState. * [DEV-75] Made the process around adding a new block, UTXO-wise, much safer. * [DEV-75] Implemented melding the virtual UTXO diff to the database. * [DEV-75] Fixed utxoSet loading from the wrong bucket. * [DEV-75] Began pruning utxoviewpoint.go. Replaced FetchUtxoEntry with a fullUTXOSet-based GetUTXOEntry. * [DEV-75] Removed fetchUtxos. * [DEV-75] Moved GetUTXOEntry into the virtual block. * [DEV-75] Updated IndexManager to not use utxoViewpoints. * [DEV-75] Fixed some bad login in restoreUTXO involving nodeDiffs. * [DEV-75] Got rid of the UTXO spend journal, which wasn't used anywhere. * [DEV-75] Moved some STXO-related validation logic out of connectToDAG and into checkConnectBlock. * [DEV-75] Renamed UtxoXxx to UTXOXxx. Removed a bunch of methods that were no longer used by anything. * [DEV-75] Another Utxo -> UTXO rename. * [DEV-75] Removed IsModified from UTXOView, which was not used anywhere. * [DEV-75] Renamed nodeDiff to provisionalNode. Added a bunch of comments. * [DEV-75] Removed the test for genesis in pastUTXO, since it can never happen. * [DEV-75] Wrote tests for errors in pastUTXO. * [DEV-75] Wrote tests for errors in restoreUTXO. * [DEV-75] Improved testErrorThroughPatching. * [DEV-75] Wrote tests for errors in verifyAndBuildUTXO. * [DEV-75] Used TipHashes instead of tips().hashes(), fixed comments in a few places, and brought back an error return that I erroneously removed. * [DEV-75] Removed UTXO set logs. * [DEV-75] Recreated add/remove/contains functions for utxoCollection. * [DEV-75] Changed newUTXOEntry to use an object initializer. * [DEV-75] Changed the UTXO bucket version to 1. * Added a comment that clarifies that the index is not initialized before initDAGState is called. * [DEV-75] Renamed GetVirtualBlock to VirtualBlock. * [DEV-75] Removed superfluous variables. * [DEV-75] Combined connectBlockToParents with updateParentsDiffs. * [DEV-75] Removed another superfluous variable. * [DEV-75] In pastUTXO, first fetch transactions from the database and only then add them. * [DEV-75] Reworded the comment for commit(). * [DEV-75] Made all the connectUTXO subfunctions not BlockDAG methods. * [DEV-75] Updated the comment for connectUTXO. * [DEV-75] Updated the comment explaining why we're creating provisionalNodes. * [DEV-75] Removed a couple of unnecessary calls to toProvisionalNode. * [DEV-75] Renamed connectUTXO to applyUTXOChanges. * [DEV-75] Replaced allProvisionalNodes with provisionalNodeSet, an object that holds provisionalNodes for this particular call to applyUTXOChanges. * [DEV-75] Changed createProvisionalNode to accept a boolean "withParents" instead of relying on the caller to supply the node's parents or an empty set. * [DEV-75] Made most applyUTXOChanges subfunctions be methods on provisionalNode. * [DEV-75] Fixed a couple of bad comments. * [DEV-75] Added descriptive error messages to callers of applyUTXOChanges. * [DEV-75] Fixed weird English. * [DEV-75] Replaced DAGState with a slice of DAG tip hashes. * [DEV-75] In createProvisionalNode, changed withParents to withRelatives to avoid certain kinds of attacks. * [DEV-75] Renamed createdProvisionalNode to newProvisionalNode. * [DEV-75] Added precalculating the amount of transactions inside a new block's blue set. * [DEV-75] Pruned unnecessary variable.
This commit is contained in:
parent
9bb40e1085
commit
37cd482db3
8
Gopkg.lock
generated
8
Gopkg.lock
generated
@ -1,6 +1,12 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "bou.ke/monkey"
|
||||
packages = ["."]
|
||||
revision = "21d0bd9ad94e052d4a8117b0910ba026bec94c99"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/aead/siphash"
|
||||
packages = ["."]
|
||||
@ -95,6 +101,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "9910d3cbbf65bc6204c141bb24b416f79962d69115cba9a0e97a28246fb2a0ea"
|
||||
inputs-digest = "5976f1edbea819ab26c66eb5e5604c255a72f174b7a47bb218f3b8a6733d0c3a"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -59,6 +59,10 @@
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "bou.ke/monkey"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
@ -90,7 +90,7 @@ type blockNode struct {
|
||||
// diff is the UTXO representation of the block
|
||||
// A block's UTXO is reconstituted by applying diffWith on every block in the chain of diffChildren
|
||||
// from the virtual block down to the block. See diffChild
|
||||
diff utxoDiff
|
||||
diff *utxoDiff
|
||||
|
||||
// diffChild is the child that diff will be built from. See diff
|
||||
diffChild *blockNode
|
||||
@ -155,7 +155,6 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents block
|
||||
func addNodeAsChildToParents(node *blockNode) {
|
||||
for _, parent := range node.parents {
|
||||
parent.children.add(node)
|
||||
parent.diffChild = node
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,10 +115,10 @@ func loadBlocks(filename string) (blocks []*util.Block, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// chainSetup is used to create a new db and chain instance with the genesis
|
||||
// dagSetup is used to create a new db and chain instance with the genesis
|
||||
// block already inserted. In addition to the new chain instance, it returns
|
||||
// a teardown function the caller should invoke when done testing to clean up.
|
||||
func chainSetup(dbName string, params *dagconfig.Params) (*BlockDAG, func(), error) {
|
||||
func dagSetup(dbName string, params *dagconfig.Params) (*BlockDAG, func(), error) {
|
||||
if !isSupportedDbType(testDbType) {
|
||||
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
|
||||
}
|
||||
@ -187,8 +187,8 @@ func chainSetup(dbName string, params *dagconfig.Params) (*BlockDAG, func(), err
|
||||
return chain, teardown, nil
|
||||
}
|
||||
|
||||
// loadUtxoView returns a utxo view loaded from a file.
|
||||
func loadUtxoView(filename string) (*UtxoViewpoint, error) {
|
||||
// loadUTXOView returns a utxo view loaded from a file.
|
||||
func loadUTXOView(filename string) (*UTXOView, error) {
|
||||
// The utxostore file format is:
|
||||
// <tx hash><output index><serialized utxo len><serialized utxo>
|
||||
//
|
||||
@ -210,7 +210,7 @@ func loadUtxoView(filename string) (*UtxoViewpoint, error) {
|
||||
}
|
||||
defer fi.Close()
|
||||
|
||||
view := NewUtxoViewpoint()
|
||||
view := NewUTXOView()
|
||||
for {
|
||||
// Hash of the utxo entry.
|
||||
var hash daghash.Hash
|
||||
@ -245,7 +245,7 @@ func loadUtxoView(filename string) (*UtxoViewpoint, error) {
|
||||
}
|
||||
|
||||
// Deserialize it and add it to the view.
|
||||
entry, err := deserializeUtxoEntry(serialized)
|
||||
entry, err := deserializeUTXOEntry(serialized)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
645
blockdag/dag.go
645
blockdag/dag.go
@ -47,47 +47,6 @@ type orphanBlock struct {
|
||||
expiration time.Time
|
||||
}
|
||||
|
||||
// DAGState houses information about the current tips and other info
|
||||
// related to the state of the DAG.
|
||||
//
|
||||
// The GetDAGState method can be used to obtain access to this information
|
||||
// in a concurrent safe manner and the data will not be changed out from under
|
||||
// the caller when chain state changes occur as the function name implies.
|
||||
// However, the returned snapshot must be treated as immutable since it is
|
||||
// shared by all callers.
|
||||
type DAGState struct {
|
||||
SelectedTip SelectedTipState // State of the selected tip
|
||||
TipHashes []daghash.Hash // The hashes of the tips
|
||||
TotalTxs uint64 // The total number of transactions in the DAG.
|
||||
}
|
||||
|
||||
type SelectedTipState struct {
|
||||
Hash daghash.Hash // The hash of the tip.
|
||||
Height int32 // The height of the tip.
|
||||
Bits uint32 // The difficulty bits of the tip.
|
||||
BlockSize uint64 // The size of the tip.
|
||||
NumTxs uint64 // The number of transactions in the tip.
|
||||
MedianTime time.Time // Median time as per CalcPastMedianTime.
|
||||
}
|
||||
|
||||
// newDAGState returns a new state instance for the given parameters.
|
||||
func newDAGState(tipHashes []daghash.Hash, node *blockNode, blockSize, numTxs,
|
||||
totalTxs uint64, medianTime time.Time) *DAGState {
|
||||
|
||||
return &DAGState{
|
||||
SelectedTip: SelectedTipState{
|
||||
Hash: node.hash,
|
||||
Height: node.height,
|
||||
Bits: node.bits,
|
||||
BlockSize: blockSize,
|
||||
NumTxs: numTxs,
|
||||
MedianTime: medianTime,
|
||||
},
|
||||
TipHashes: tipHashes,
|
||||
TotalTxs: totalTxs,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockDAG provides functions for working with the bitcoin block chain.
|
||||
// It includes functionality such as rejecting duplicate blocks, ensuring blocks
|
||||
// follow all rules, orphan handling, checkpoint handling, and best chain
|
||||
@ -126,7 +85,7 @@ type BlockDAG struct {
|
||||
index *blockIndex
|
||||
|
||||
// virtual tracks the current tips.
|
||||
virtual *virtualBlock
|
||||
virtual *VirtualBlock
|
||||
|
||||
// These fields are related to handling of orphan blocks. They are
|
||||
// protected by a combination of the chain lock and the orphan lock.
|
||||
@ -140,20 +99,6 @@ type BlockDAG struct {
|
||||
nextCheckpoint *dagconfig.Checkpoint
|
||||
checkpointNode *blockNode
|
||||
|
||||
// The state is used as a fairly efficient way to cache information
|
||||
// about the current DAG state that is returned to callers when
|
||||
// requested. It operates on the principle of MVCC such that any time a
|
||||
// new block becomes the best block, the state pointer is replaced with
|
||||
// a new struct and the old state is left untouched. In this way,
|
||||
// multiple callers can be pointing to different DAG states.
|
||||
// This is acceptable for most callers because the state is only being
|
||||
// queried at a specific point in time.
|
||||
//
|
||||
// In addition, some of the fields are stored in the database so the
|
||||
// DAG state can be quickly reconstructed on load.
|
||||
stateLock sync.RWMutex
|
||||
dagState *DAGState
|
||||
|
||||
// The following caches are used to efficiently keep track of the
|
||||
// current deployment threshold state of each rule change deployment.
|
||||
//
|
||||
@ -361,7 +306,7 @@ type SequenceLock struct {
|
||||
}
|
||||
|
||||
// CalcSequenceLock computes a relative lock-time SequenceLock for the passed
|
||||
// transaction using the passed UtxoViewpoint to obtain the past median time
|
||||
// transaction using the passed UTXOView to obtain the past median time
|
||||
// for blocks in which the referenced inputs of the transactions were included
|
||||
// within. The generated SequenceLock lock can be used in conjunction with a
|
||||
// block height, and adjusted median block time to determine if all the inputs
|
||||
@ -369,7 +314,7 @@ type SequenceLock struct {
|
||||
// the candidate transaction to be included in a block.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) CalcSequenceLock(tx *util.Tx, utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) {
|
||||
func (dag *BlockDAG) CalcSequenceLock(tx *util.Tx, utxoView *UTXOView, mempool bool) (*SequenceLock, error) {
|
||||
dag.dagLock.Lock()
|
||||
defer dag.dagLock.Unlock()
|
||||
|
||||
@ -380,7 +325,7 @@ func (dag *BlockDAG) CalcSequenceLock(tx *util.Tx, utxoView *UtxoViewpoint, memp
|
||||
// transaction. See the exported version, CalcSequenceLock for further details.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (dag *BlockDAG) calcSequenceLock(node *blockNode, tx *util.Tx, utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) {
|
||||
func (dag *BlockDAG) calcSequenceLock(node *blockNode, tx *util.Tx, utxoView *UTXOView, mempool bool) (*SequenceLock, error) {
|
||||
// A value of -1 for each relative lock type represents a relative time
|
||||
// lock value that will allow a transaction to be included in a block
|
||||
// at any given height or time.
|
||||
@ -487,135 +432,6 @@ func LockTimeToSequence(isSeconds bool, locktime uint64) uint64 {
|
||||
locktime>>wire.SequenceLockTimeGranularity
|
||||
}
|
||||
|
||||
// connectBlock handles connecting the passed node/block to the DAG.
|
||||
//
|
||||
// This passed utxo view must have all referenced txos the block spends marked
|
||||
// as spent and all of the new txos the block creates added to it. In addition,
|
||||
// the passed stxos slice must be populated with all of the information for the
|
||||
// spent txos. This approach is used because the connection validation that
|
||||
// must happen prior to calling this function requires the same details, so
|
||||
// it would be inefficient to repeat it.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, view *UtxoViewpoint, stxos []spentTxOut) error {
|
||||
// Sanity check the correct number of stxos are provided.
|
||||
if len(stxos) != countSpentOutputs(block) {
|
||||
return AssertError("connectBlock called with inconsistent " +
|
||||
"spent transaction out information")
|
||||
}
|
||||
|
||||
// No warnings about unknown rules or versions until the chain is
|
||||
// current.
|
||||
if dag.isCurrent() {
|
||||
// Warn if any unknown new rules are either about to activate or
|
||||
// have already been activated.
|
||||
if err := dag.warnUnknownRuleActivations(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Warn if a high enough percentage of the last blocks have
|
||||
// unexpected versions.
|
||||
if err := dag.warnUnknownVersions(node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Write any block status changes to DB before updating best state.
|
||||
err := dag.index.flushToDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate a new state snapshot that will be used to update the
|
||||
// database and later memory if all database updates are successful.
|
||||
dag.stateLock.RLock()
|
||||
currentTotalTxs := dag.dagState.TotalTxs
|
||||
dag.stateLock.RUnlock()
|
||||
numTxs := uint64(len(block.MsgBlock().Transactions))
|
||||
blockSize := uint64(block.MsgBlock().SerializeSize())
|
||||
state := newDAGState(view.tips.hashes(), node, blockSize, numTxs,
|
||||
currentTotalTxs+numTxs, node.CalcPastMedianTime())
|
||||
|
||||
// Atomically insert info into the database.
|
||||
err = dag.db.Update(func(dbTx database.Tx) error {
|
||||
// Update best block state.
|
||||
err := dbPutDAGState(dbTx, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the block hash and height to the block index which tracks
|
||||
// the main chain.
|
||||
err = dbPutBlockIndex(dbTx, block.Hash(), node.height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the utxo set using the state of the utxo view. This
|
||||
// entails removing all of the utxos spent and adding the new
|
||||
// ones created by the block.
|
||||
err = dbPutUtxoView(dbTx, view)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the transaction spend journal by adding a record for
|
||||
// the block that contains all txos spent by it.
|
||||
err = dbPutSpendJournalEntry(dbTx, block.Hash(), stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Allow the index manager to call each of the currently active
|
||||
// optional indexes with the block being connected so they can
|
||||
// update themselves accordingly.
|
||||
if dag.indexManager != nil {
|
||||
err := dag.indexManager.ConnectBlock(dbTx, block, view)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune fully spent entries and mark all entries in the view unmodified
|
||||
// now that the modifications have been committed to the database.
|
||||
view.commit()
|
||||
|
||||
// This node is now at the end of the DAG.
|
||||
dag.virtual.AddTip(node)
|
||||
|
||||
// Update the state for the best block. Notice how this replaces the
|
||||
// entire struct instead of updating the existing one. This effectively
|
||||
// allows the old version to act as a snapshot which callers can use
|
||||
// freely without needing to hold a lock for the duration. See the
|
||||
// comments on the state variable for more details.
|
||||
dag.setDAGState(state)
|
||||
|
||||
// Notify the caller that the block was connected to the main chain.
|
||||
// The caller would typically want to react with actions such as
|
||||
// updating wallets.
|
||||
dag.dagLock.Unlock()
|
||||
dag.sendNotification(NTBlockConnected, dag.virtual.Tips().hashes())
|
||||
dag.dagLock.Lock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// countSpentOutputs returns the number of utxos the passed block spends.
|
||||
func countSpentOutputs(block *util.Block) int {
|
||||
// Exclude the coinbase transaction since it can't spend anything.
|
||||
var numSpent int
|
||||
for _, tx := range block.Transactions()[1:] {
|
||||
numSpent += len(tx.MsgTx().TxIn)
|
||||
}
|
||||
return numSpent
|
||||
}
|
||||
|
||||
// connectToDAG handles connecting the passed block to the DAG.
|
||||
//
|
||||
// The flags modify the behavior of this function as follows:
|
||||
@ -630,11 +446,8 @@ func (dag *BlockDAG) connectToDAG(node *blockNode, parentNodes blockSet, block *
|
||||
// Perform several checks to verify the block can be connected
|
||||
// to the DAG without violating any rules and without actually
|
||||
// connecting the block.
|
||||
view := NewUtxoViewpoint()
|
||||
view.SetTips(parentNodes)
|
||||
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
|
||||
if !fastAdd {
|
||||
err := dag.checkConnectBlock(node, block, view, &stxos)
|
||||
err := dag.checkConnectBlock(node, block)
|
||||
if err == nil {
|
||||
dag.index.SetStatusFlags(node, statusValid)
|
||||
} else if _, ok := err.(RuleError); ok {
|
||||
@ -657,23 +470,8 @@ func (dag *BlockDAG) connectToDAG(node *blockNode, parentNodes blockSet, block *
|
||||
}
|
||||
}
|
||||
|
||||
// In the fast add case the code to check the block connection
|
||||
// was skipped, so the utxo view needs to load the referenced
|
||||
// utxos, spend them, and add the new utxos being created by
|
||||
// this block.
|
||||
if fastAdd {
|
||||
err := view.fetchInputUtxos(dag.db, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = view.connectTransactions(node, block.Transactions(), &stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Connect the block to the DAG.
|
||||
err := dag.connectBlock(node, block, view, stxos)
|
||||
err := dag.connectBlock(node, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -681,6 +479,382 @@ func (dag *BlockDAG) connectToDAG(node *blockNode, parentNodes blockSet, block *
|
||||
return nil
|
||||
}
|
||||
|
||||
// connectBlock handles connecting the passed node/block to the DAG.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block) error {
|
||||
// No warnings about unknown rules or versions until the DAG is
|
||||
// current.
|
||||
if dag.isCurrent() {
|
||||
// Warn if any unknown new rules are either about to activate or
|
||||
// have already been activated.
|
||||
if err := dag.warnUnknownRuleActivations(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Warn if a high enough percentage of the last blocks have
|
||||
// unexpected versions.
|
||||
if err := dag.warnUnknownVersions(node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the node to the virtual and update the UTXO set of the DAG.
|
||||
utxoDiff, err := dag.applyUTXOChanges(node, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write any block status changes to DB before updating the DAG state.
|
||||
err = dag.index.flushToDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Atomically insert info into the database.
|
||||
err = dag.db.Update(func(dbTx database.Tx) error {
|
||||
// Update best block state.
|
||||
err := dbPutDAGTipHashes(dbTx, dag.virtual.TipHashes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the block hash and height to the block index which tracks
|
||||
// the main chain.
|
||||
err = dbPutBlockIndex(dbTx, block.Hash(), node.height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the UTXO set using the diffSet that was melded into the
|
||||
// full UTXO set.
|
||||
err = dbPutUTXODiff(dbTx, utxoDiff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Allow the index manager to call each of the currently active
|
||||
// optional indexes with the block being connected so they can
|
||||
// update themselves accordingly.
|
||||
if dag.indexManager != nil {
|
||||
err := dag.indexManager.ConnectBlock(dbTx, block, dag.virtual)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Notify the caller that the block was connected to the main chain.
|
||||
// The caller would typically want to react with actions such as
|
||||
// updating wallets.
|
||||
dag.dagLock.Unlock()
|
||||
dag.sendNotification(NTBlockConnected, dag.virtual.TipHashes())
|
||||
dag.dagLock.Lock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyUTXOChanges does the following:
|
||||
// 1. Verifies that each transaction within the new block could spend an existing UTXO.
|
||||
// 2. Connects each of the new block's parents to the block.
|
||||
// 3. Adds the new block to the DAG's tips.
|
||||
// 4. Updates the DAG's full UTXO set.
|
||||
// 5. Updates each of the tips' utxoDiff.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (dag *BlockDAG) applyUTXOChanges(node *blockNode, block *util.Block) (*utxoDiff, error) {
|
||||
// Prepare provisionalNodes for all the relevant nodes to avoid modifying the original nodes.
|
||||
// We avoid modifying the original nodes in this function because it could potentially
|
||||
// fail if the block is not valid, thus bringing all the affected nodes (and the virtual)
|
||||
// into an undefined state.
|
||||
provisionalSet := newProvisionalNodeSet()
|
||||
newNodeProvisional := provisionalSet.newProvisionalNode(node, true, block.Transactions())
|
||||
|
||||
// Clone the virtual block so that we don't modify the existing one.
|
||||
virtualClone := dag.virtual.clone()
|
||||
|
||||
newBlockUTXO, err := newNodeProvisional.verifyAndBuildUTXO(virtualClone, dag.db)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error verifying UTXO for %v: %s", node, err)
|
||||
}
|
||||
|
||||
err = newNodeProvisional.updateParents(virtualClone, newBlockUTXO)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed updating parents of %v: %s", node, err)
|
||||
}
|
||||
|
||||
// Update the virtual block's children (the DAG tips) to include the new block.
|
||||
virtualClone.AddTip(node)
|
||||
|
||||
// Build a UTXO set for the new virtual block and update the DAG tips' diffs.
|
||||
virtualNodeProvisional := provisionalSet.newProvisionalNode(&virtualClone.blockNode, true, nil)
|
||||
newVirtualUTXO, err := virtualNodeProvisional.pastUTXO(virtualClone, dag.db)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not restore past UTXO for virtual %v: %s", virtualClone, err)
|
||||
}
|
||||
|
||||
// Apply new utxoDiffs to all the tips
|
||||
err = updateTipsUTXO(virtualNodeProvisional.parents, virtualClone, newVirtualUTXO)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed updating the tips' UTXO: %s", err)
|
||||
}
|
||||
|
||||
// It is now safe to meld the UTXO set to base.
|
||||
diffSet := newVirtualUTXO.(*diffUTXOSet)
|
||||
utxoDiff := diffSet.utxoDiff
|
||||
diffSet.meldToBase()
|
||||
|
||||
// It is now safe to commit all the provisionalNodes
|
||||
for _, provisional := range provisionalSet {
|
||||
provisional.commit()
|
||||
|
||||
// Set the status to valid for all index nodes to make sure the changes get
|
||||
// written to the database.
|
||||
if provisional != virtualNodeProvisional {
|
||||
dag.index.SetStatusFlags(provisional.original, statusValid)
|
||||
}
|
||||
}
|
||||
|
||||
// It is now safe to apply the new virtual block
|
||||
dag.virtual = virtualClone
|
||||
|
||||
return utxoDiff, nil
|
||||
}
|
||||
|
||||
// provisionalNodeSet is a temporary collection of provisionalNodes. It is used exclusively
|
||||
// inside applyUTXOChanges. The purpose of this set is twofold:
|
||||
// 1. Provide easy access to all provisionalNodes created inside this particular call to applyUTXOChanges
|
||||
// 2. Avoid needless recreation of provisionalNodes
|
||||
type provisionalNodeSet map[daghash.Hash]*provisionalNode
|
||||
|
||||
// newProvisionalNodeSet creates an empty provisionalNodeSet
|
||||
func newProvisionalNodeSet() provisionalNodeSet {
|
||||
return provisionalNodeSet{}
|
||||
}
|
||||
|
||||
// provisionalNode is a temporary and partial copy of a blockNode. It is used exclusively
|
||||
// inside applyUTXOChanges. We use this struct instead of the original blockNode because
|
||||
// applyUTXOChanges has a few points of failure which, were we to modify it, would leave the
|
||||
// blockNode in an undefined state.
|
||||
//
|
||||
// Important: provisionalNode.original must be treated as immutable.
|
||||
type provisionalNode struct {
|
||||
original *blockNode
|
||||
selectedParent *provisionalNode
|
||||
parents []*provisionalNode
|
||||
children []*provisionalNode
|
||||
diff *utxoDiff
|
||||
diffChild *provisionalNode
|
||||
transactions []*util.Tx
|
||||
}
|
||||
|
||||
// newProvisionalNode takes a node and builds a provisionalNode from it.
|
||||
// To avoid building the entire DAG in provisionalNode format we pass withRelatives = true
|
||||
// only when the node's relatives (parents and children) are required.
|
||||
func (pns provisionalNodeSet) newProvisionalNode(node *blockNode, withRelatives bool,
|
||||
transactions []*util.Tx) *provisionalNode {
|
||||
if existingProvisional, ok := pns[node.hash]; ok {
|
||||
return existingProvisional
|
||||
}
|
||||
|
||||
provisional := &provisionalNode{
|
||||
original: node,
|
||||
parents: []*provisionalNode{},
|
||||
children: []*provisionalNode{},
|
||||
transactions: transactions,
|
||||
}
|
||||
if node.hash != zeroHash {
|
||||
pns[node.hash] = provisional
|
||||
}
|
||||
|
||||
if withRelatives {
|
||||
for _, parent := range node.parents {
|
||||
provisional.parents = append(provisional.parents, pns.newProvisionalNode(parent, false, nil))
|
||||
}
|
||||
if node.selectedParent != nil {
|
||||
provisional.selectedParent = pns[node.selectedParent.hash]
|
||||
}
|
||||
|
||||
for _, child := range node.children {
|
||||
provisional.children = append(provisional.children, pns.newProvisionalNode(child, false, nil))
|
||||
}
|
||||
if node.diffChild != nil {
|
||||
provisional.diffChild = pns[node.diffChild.hash]
|
||||
}
|
||||
}
|
||||
if node.diff != nil {
|
||||
provisional.diff = node.diff.clone()
|
||||
}
|
||||
|
||||
return provisional
|
||||
}
|
||||
|
||||
// verifyAndBuildUTXO verifies all transactions in the given block (in provisionalNode format) and builds its UTXO
|
||||
func (p *provisionalNode) verifyAndBuildUTXO(virtual *VirtualBlock, db database.DB) (utxoSet, error) {
|
||||
utxo, err := p.pastUTXO(virtual, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tx := range p.transactions {
|
||||
ok := utxo.addTx(tx.MsgTx(), p.original.height)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("transaction %v is not compatible with UTXO", tx)
|
||||
}
|
||||
}
|
||||
|
||||
return utxo, nil
|
||||
}
|
||||
|
||||
// pastUTXO returns the UTXO of a given block's (in provisionalNode format) past
|
||||
func (p *provisionalNode) pastUTXO(virtual *VirtualBlock, db database.DB) (utxoSet, error) {
|
||||
pastUTXO, err := p.selectedParent.restoreUTXO(virtual)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch from the database all the transactions for this block's blue set (besides the selected parent)
|
||||
var blueBlockTransactions []*util.Tx
|
||||
err = db.View(func(tx database.Tx) error {
|
||||
// Precalculate the amount of transactions in this block's blue set, besides the selected parent.
|
||||
// This is to avoid an attack in which an attacker fabricates a block that will deliberately cause
|
||||
// a lot of copying, causing a high cost to the whole network.
|
||||
transactionCount := 0
|
||||
blueBlocks := make([]*util.Block, 0, len(p.original.blues)-1)
|
||||
for i := len(p.original.blues) - 1; i >= 0; i-- {
|
||||
blueBlockNode := p.original.blues[i]
|
||||
if blueBlockNode == p.original.selectedParent {
|
||||
continue
|
||||
}
|
||||
|
||||
blueBlock, err := dbFetchBlockByNode(tx, blueBlockNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transactionCount += len(blueBlock.Transactions())
|
||||
blueBlocks = append(blueBlocks, blueBlock)
|
||||
}
|
||||
|
||||
blueBlockTransactions = make([]*util.Tx, 0, transactionCount)
|
||||
for _, blueBlock := range blueBlocks {
|
||||
blueBlockTransactions = append(blueBlockTransactions, blueBlock.Transactions()...)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add all transactions to the pastUTXO
|
||||
// Purposefully ignore failures - these are just unaccepted transactions
|
||||
for _, tx := range blueBlockTransactions {
|
||||
_ = pastUTXO.addTx(tx.MsgTx(), p.original.height)
|
||||
}
|
||||
|
||||
return pastUTXO, nil
|
||||
}
|
||||
|
||||
// restoreUTXO restores the UTXO of a given block (in provisionalNode format) from its diff
|
||||
func (p *provisionalNode) restoreUTXO(virtual *VirtualBlock) (utxoSet, error) {
|
||||
stack := []*provisionalNode{p}
|
||||
current := p
|
||||
|
||||
for current.diffChild != nil {
|
||||
current = current.diffChild
|
||||
stack = append(stack, current)
|
||||
}
|
||||
|
||||
utxo := utxoSet(virtual.utxoSet)
|
||||
|
||||
var err error
|
||||
for i := len(stack) - 1; i >= 0; i-- {
|
||||
utxo, err = utxo.withDiff(stack[i].diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return utxo, nil
|
||||
}
|
||||
|
||||
// updateParents adds this block (in provisionalNode format) to the children sets of its parents
|
||||
// and updates the diff of any parent whose DiffChild is this block
|
||||
func (p *provisionalNode) updateParents(virtual *VirtualBlock, newBlockUTXO utxoSet) error {
|
||||
virtualDiffFromNewBlock, err := virtual.utxoSet.diffFrom(newBlockUTXO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.diff = virtualDiffFromNewBlock
|
||||
|
||||
for _, parent := range p.parents {
|
||||
if parent.diffChild == nil {
|
||||
parentUTXO, err := parent.restoreUTXO(virtual)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parent.diffChild = p
|
||||
parent.diff, err = newBlockUTXO.diffFrom(parentUTXO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
parent.children = append(parent.children, p)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateTipsUTXO builds and applies new diff UTXOs for all the DAG's tips (in provisionalNode format)
|
||||
func updateTipsUTXO(tipProvisionals []*provisionalNode, virtual *VirtualBlock, virtualUTXO utxoSet) error {
|
||||
for _, tipProvisional := range tipProvisionals {
|
||||
tipUTXO, err := tipProvisional.restoreUTXO(virtual)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tipProvisional.diff, err = virtualUTXO.diffFrom(tipUTXO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// commit updates the original blockNode this provisionalNode was created from with all the changes made to it
|
||||
func (p *provisionalNode) commit() {
|
||||
if p.selectedParent != nil {
|
||||
p.original.selectedParent = p.selectedParent.original
|
||||
}
|
||||
|
||||
parents := newSet()
|
||||
for _, parent := range p.parents {
|
||||
parents.add(parent.original)
|
||||
}
|
||||
p.original.parents = parents
|
||||
|
||||
children := newSet()
|
||||
for _, child := range p.children {
|
||||
children.add(child.original)
|
||||
}
|
||||
p.original.children = children
|
||||
|
||||
if p.diff != nil {
|
||||
p.original.diff = p.diff
|
||||
}
|
||||
if p.diffChild != nil {
|
||||
p.original.diffChild = p.diffChild.original
|
||||
}
|
||||
}
|
||||
|
||||
// isCurrent returns whether or not the chain believes it is current. Several
|
||||
// factors are used to guess, but the key factors that allow the chain to
|
||||
// believe it is current are:
|
||||
@ -719,31 +893,13 @@ func (dag *BlockDAG) IsCurrent() bool {
|
||||
return dag.isCurrent()
|
||||
}
|
||||
|
||||
// GetDAGState returns information about the DAG and related state as of the
|
||||
// current point in time. The returned instance must be treated as immutable
|
||||
// since it is shared by all callers.
|
||||
// VirtualBlock returns the DAG's virtual block in the current point in time.
|
||||
// The returned instance must be treated as immutable since it is shared by all
|
||||
// callers.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) GetDAGState() *DAGState {
|
||||
dag.stateLock.RLock()
|
||||
defer func() {
|
||||
dag.stateLock.RUnlock()
|
||||
}()
|
||||
|
||||
return dag.dagState
|
||||
}
|
||||
|
||||
// setDAGState sets information about the DAG and related state as of the
|
||||
// current point in time.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) setDAGState(dagState *DAGState) {
|
||||
dag.stateLock.Lock()
|
||||
defer func() {
|
||||
dag.stateLock.Unlock()
|
||||
}()
|
||||
|
||||
dag.dagState = dagState
|
||||
func (dag *BlockDAG) VirtualBlock() *VirtualBlock {
|
||||
return dag.virtual
|
||||
}
|
||||
|
||||
// HeaderByHash returns the block header identified by the given hash or an
|
||||
@ -1112,11 +1268,11 @@ type IndexManager interface {
|
||||
|
||||
// ConnectBlock is invoked when a new block has been connected to the
|
||||
// main chain.
|
||||
ConnectBlock(database.Tx, *util.Block, *UtxoViewpoint) error
|
||||
ConnectBlock(database.Tx, *util.Block, *VirtualBlock) error
|
||||
|
||||
// DisconnectBlock is invoked when a block has been disconnected from
|
||||
// the main chain.
|
||||
DisconnectBlock(database.Tx, *util.Block, *UtxoViewpoint) error
|
||||
DisconnectBlock(database.Tx, *util.Block, *VirtualBlock) error
|
||||
}
|
||||
|
||||
// Config is a descriptor which specifies the blockchain instance configuration.
|
||||
@ -1209,7 +1365,7 @@ func New(config *Config) (*BlockDAG, error) {
|
||||
targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second)
|
||||
adjustmentFactor := params.RetargetAdjustmentFactor
|
||||
index := newBlockIndex(config.DB, params)
|
||||
b := BlockDAG{
|
||||
dag := BlockDAG{
|
||||
checkpoints: config.Checkpoints,
|
||||
checkpointsByHeight: checkpointsByHeight,
|
||||
db: config.DB,
|
||||
@ -1222,7 +1378,6 @@ func New(config *Config) (*BlockDAG, error) {
|
||||
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
|
||||
index: index,
|
||||
virtual: newVirtualBlock(nil, params.K),
|
||||
genesis: index.LookupNode(params.GenesisHash),
|
||||
orphans: make(map[daghash.Hash]*orphanBlock),
|
||||
prevOrphans: make(map[daghash.Hash][]*orphanBlock),
|
||||
warningCaches: newThresholdCaches(vbNumBits),
|
||||
@ -1232,28 +1387,32 @@ func New(config *Config) (*BlockDAG, error) {
|
||||
// Initialize the chain state from the passed database. When the db
|
||||
// does not yet contain any chain state, both it and the chain state
|
||||
// will be initialized to contain only the genesis block.
|
||||
if err := b.initDAGState(); err != nil {
|
||||
if err := dag.initDAGState(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save a reference to the genesis block. Note that we may only get
|
||||
// an index reference to it here because the index is uninitialized
|
||||
// before initDAGState.
|
||||
dag.genesis = index.LookupNode(params.GenesisHash)
|
||||
|
||||
// Initialize and catch up all of the currently active optional indexes
|
||||
// as needed.
|
||||
if config.IndexManager != nil {
|
||||
err := config.IndexManager.Init(&b, config.Interrupt)
|
||||
err := config.IndexManager.Init(&dag, config.Interrupt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize rule change threshold state caches.
|
||||
if err := b.initThresholdCaches(); err != nil {
|
||||
if err := dag.initThresholdCaches(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectedTip := b.virtual.SelectedTip()
|
||||
log.Infof("DAG state (height %d, hash %v, totaltx %d, work %v)",
|
||||
selectedTip.height, selectedTip.hash, b.dagState.TotalTxs,
|
||||
selectedTip.workSum)
|
||||
selectedTip := dag.virtual.SelectedTip()
|
||||
log.Infof("DAG state (height %d, hash %v, work %v)",
|
||||
selectedTip.height, selectedTip.hash, selectedTip.workSum)
|
||||
|
||||
return &b, nil
|
||||
return &dag, nil
|
||||
}
|
||||
|
@ -5,7 +5,11 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"bou.ke/monkey"
|
||||
"errors"
|
||||
"github.com/daglabs/btcd/database"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -38,7 +42,7 @@ func TestHaveBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a new database and chain instance to run tests against.
|
||||
chain, teardownFunc, err := chainSetup("haveblock",
|
||||
chain, teardownFunc, err := dagSetup("haveblock",
|
||||
&dagconfig.MainNetParams)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup chain instance: %v", err)
|
||||
@ -168,9 +172,8 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
Value: 10,
|
||||
}},
|
||||
})
|
||||
utxoView := NewUtxoViewpoint()
|
||||
utxoView := NewUTXOView()
|
||||
utxoView.AddTxOuts(targetTx, int32(numBlocksToGenerate)-4)
|
||||
utxoView.SetTips(setFromSlice(node))
|
||||
|
||||
// Create a utxo that spends the fake utxo created above for use in the
|
||||
// transactions created in the tests. It has an age of 4 blocks. Note
|
||||
@ -214,7 +217,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
tx *wire.MsgTx
|
||||
view *UtxoViewpoint
|
||||
view *UTXOView
|
||||
mempool bool
|
||||
want *SequenceLock
|
||||
}{
|
||||
@ -646,3 +649,95 @@ func TestIntervalBlockHashes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPastUTXOErrors tests all error-cases in restoreUTXO.
|
||||
// The non-error-cases are tested in the more general tests.
|
||||
func TestVerifyAndBuildUTXOErrors(t *testing.T) {
|
||||
targetErrorMessage := "not compatible with UTXO"
|
||||
testErrorThroughPatching(
|
||||
t,
|
||||
targetErrorMessage,
|
||||
(*diffUTXOSet).addTx,
|
||||
func(fus *diffUTXOSet, tx *wire.MsgTx, blockHeight int32) bool {
|
||||
return false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestPastUTXOErrors tests all error-cases in restoreUTXO.
|
||||
// The non-error-cases are tested in the more general tests.
|
||||
func TestPastUTXOErrors(t *testing.T) {
|
||||
targetErrorMessage := "dbFetchBlockByNode error"
|
||||
testErrorThroughPatching(
|
||||
t,
|
||||
targetErrorMessage,
|
||||
dbFetchBlockByNode,
|
||||
func(dbTx database.Tx, node *blockNode) (*util.Block, error) {
|
||||
return nil, errors.New(targetErrorMessage)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestRestoreUTXOErrors tests all error-cases in restoreUTXO.
|
||||
// The non-error-cases are tested in the more general tests.
|
||||
func TestRestoreUTXOErrors(t *testing.T) {
|
||||
targetErrorMessage := "withDiff error"
|
||||
testErrorThroughPatching(
|
||||
t,
|
||||
targetErrorMessage,
|
||||
(*fullUTXOSet).withDiff,
|
||||
func(fus *fullUTXOSet, other *utxoDiff) (utxoSet, error) {
|
||||
return nil, errors.New(targetErrorMessage)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func testErrorThroughPatching(t *testing.T, expectedErrorMessage string, targetFunction interface{}, replacementFunction interface{}) {
|
||||
// Load up blocks such that there is a fork in the DAG.
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4
|
||||
// \-> 3b
|
||||
testFiles := []string{
|
||||
"blk_0_to_4.dat",
|
||||
"blk_3B.dat",
|
||||
}
|
||||
|
||||
var blocks []*util.Block
|
||||
for _, file := range testFiles {
|
||||
blockTmp, err := loadBlocks(file)
|
||||
if err != nil {
|
||||
t.Fatalf("Error loading file: %v\n", err)
|
||||
}
|
||||
blocks = append(blocks, blockTmp...)
|
||||
}
|
||||
|
||||
// Create a new database and dag instance to run tests against.
|
||||
dag, teardownFunc, err := dagSetup("testErrorThroughPatching", &dagconfig.MainNetParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup dag instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Since we're not dealing with the real block chain, set the coinbase
|
||||
// maturity to 1.
|
||||
dag.TstSetCoinbaseMaturity(1)
|
||||
|
||||
monkey.Patch(targetFunction, replacementFunction)
|
||||
|
||||
err = nil
|
||||
for i := 1; i < len(blocks); i++ {
|
||||
_, err = dag.ProcessBlock(blocks[i], BFNone)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("ProcessBlock unexpectedly succeeded. "+
|
||||
"Expected: %s", expectedErrorMessage)
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedErrorMessage) {
|
||||
t.Errorf("ProcessBlock returned wrong error. "+
|
||||
"Want: %s, got: %s", expectedErrorMessage, err)
|
||||
}
|
||||
|
||||
monkey.Unpatch(targetFunction)
|
||||
}
|
||||
|
@ -7,16 +7,14 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/database"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -25,14 +23,9 @@ const (
|
||||
// wire.MaxBlockHeaderPayload is quite long.
|
||||
blockHdrSize = wire.MaxBlockHeaderPayload
|
||||
|
||||
// latestUtxoSetBucketVersion is the current version of the utxo set
|
||||
// latestUTXOSetBucketVersion is the current version of the UTXO set
|
||||
// bucket that is used to track all unspent outputs.
|
||||
latestUtxoSetBucketVersion = 2
|
||||
|
||||
// latestSpendJournalBucketVersion is the current version of the spend
|
||||
// journal bucket that is used to track all spent transactions for use
|
||||
// in reorgs.
|
||||
latestSpendJournalBucketVersion = 1
|
||||
latestUTXOSetBucketVersion = 1
|
||||
)
|
||||
|
||||
var (
|
||||
@ -48,17 +41,9 @@ var (
|
||||
// the block height -> block hash index.
|
||||
heightIndexBucketName = []byte("heightidx")
|
||||
|
||||
// dagStateKeyName is the name of the db key used to store the DAG
|
||||
// state.
|
||||
dagStateKeyName = []byte("dagstate")
|
||||
|
||||
// spendJournalVersionKeyName is the name of the db key used to store
|
||||
// the version of the spend journal currently in the database.
|
||||
spendJournalVersionKeyName = []byte("spendjournalversion")
|
||||
|
||||
// spendJournalBucketName is the name of the db bucket used to house
|
||||
// transactions outputs that are spent in each block.
|
||||
spendJournalBucketName = []byte("spendjournal")
|
||||
// dagTipHashesKeyName is the name of the db key used to store the DAG
|
||||
// tip hashes.
|
||||
dagTipHashesKeyName = []byte("dagtiphashes")
|
||||
|
||||
// utxoSetVersionKeyName is the name of the db key used to store the
|
||||
// version of the utxo set currently in the database.
|
||||
@ -66,7 +51,7 @@ var (
|
||||
|
||||
// utxoSetBucketName is the name of the db bucket used to house the
|
||||
// unspent transaction output set.
|
||||
utxoSetBucketName = []byte("utxosetv2")
|
||||
utxoSetBucketName = []byte("utxoset")
|
||||
|
||||
// byteOrder is the preferred byte order used for serializing numeric
|
||||
// fields for storage in the database.
|
||||
@ -105,18 +90,6 @@ func isDeserializeErr(err error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// dbFetchVersion fetches an individual version with the given key from the
|
||||
// metadata bucket. It is primarily used to track versions on entities such as
|
||||
// buckets. It returns zero if the provided key does not exist.
|
||||
func dbFetchVersion(dbTx database.Tx, key []byte) uint32 {
|
||||
serialized := dbTx.Metadata().Get(key)
|
||||
if serialized == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return byteOrder.Uint32(serialized[:])
|
||||
}
|
||||
|
||||
// dbPutVersion uses an existing database transaction to update the provided
|
||||
// key in the metadata bucket to the given version. It is primarily used to
|
||||
// track versions on entities such as buckets.
|
||||
@ -126,24 +99,6 @@ func dbPutVersion(dbTx database.Tx, key []byte, version uint32) error {
|
||||
return dbTx.Metadata().Put(key, serialized[:])
|
||||
}
|
||||
|
||||
// dbFetchOrCreateVersion uses an existing database transaction to attempt to
|
||||
// fetch the provided key from the metadata bucket as a version and in the case
|
||||
// it doesn't exist, it adds the entry with the provided default version and
|
||||
// returns that. This is useful during upgrades to automatically handle loading
|
||||
// and adding version keys as necessary.
|
||||
func dbFetchOrCreateVersion(dbTx database.Tx, key []byte, defaultVersion uint32) (uint32, error) {
|
||||
version := dbFetchVersion(dbTx, key)
|
||||
if version == 0 {
|
||||
version = defaultVersion
|
||||
err := dbPutVersion(dbTx, key, version)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The transaction spend journal consists of an entry for each block connected
|
||||
// to the main chain which contains the transaction outputs the block spends
|
||||
@ -152,7 +107,7 @@ func dbFetchOrCreateVersion(dbTx database.Tx, key []byte, defaultVersion uint32)
|
||||
// This is required because reorganizing the chain necessarily entails
|
||||
// disconnecting blocks to get back to the point of the fork which implies
|
||||
// unspending all of the transaction outputs that each block previously spent.
|
||||
// Since the utxo set, by definition, only contains unspent transaction outputs,
|
||||
// Since the UTXO set, by definition, only contains unspent transaction outputs,
|
||||
// the spent transaction outputs must be resurrected from somewhere. There is
|
||||
// more than one way this could be done, however this is the most straight
|
||||
// forward method that does not require having a transaction index and unpruned
|
||||
@ -160,7 +115,7 @@ func dbFetchOrCreateVersion(dbTx database.Tx, key []byte, defaultVersion uint32)
|
||||
//
|
||||
// NOTE: This format is NOT self describing. The additional details such as
|
||||
// the number of entries (transaction inputs) are expected to come from the
|
||||
// block itself and the utxo set (for legacy entries). The rationale in doing
|
||||
// block itself and the UTXO set (for legacy entries). The rationale in doing
|
||||
// this is to save space. This is also the reason the spent outputs are
|
||||
// serialized in the reverse order they are spent because later transactions are
|
||||
// allowed to spend outputs from earlier ones in the same block.
|
||||
@ -410,25 +365,15 @@ func serializeSpendJournalEntry(stxos []spentTxOut) []byte {
|
||||
return serialized
|
||||
}
|
||||
|
||||
// dbPutSpendJournalEntry uses an existing database transaction to update the
|
||||
// spend journal entry for the given block hash using the provided slice of
|
||||
// spent txouts. The spent txouts slice must contain an entry for every txout
|
||||
// the transactions in the block spend in the order they are spent.
|
||||
func dbPutSpendJournalEntry(dbTx database.Tx, blockHash *daghash.Hash, stxos []spentTxOut) error {
|
||||
spendBucket := dbTx.Metadata().Bucket(spendJournalBucketName)
|
||||
serialized := serializeSpendJournalEntry(stxos)
|
||||
return spendBucket.Put(blockHash[:], serialized)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The unspent transaction output (utxo) set consists of an entry for each
|
||||
// The unspent transaction output (UTXO) set consists of an entry for each
|
||||
// unspent output using a format that is optimized to reduce space using domain
|
||||
// specific compression algorithms. This format is a slightly modified version
|
||||
// of the format used in Bitcoin Core.
|
||||
//
|
||||
// Each entry is keyed by an outpoint as specified below. It is important to
|
||||
// note that the key encoding uses a VLQ, which employs an MSB encoding so
|
||||
// iteration of utxos when doing byte-wise comparisons will produce them in
|
||||
// iteration of UTXOs when doing byte-wise comparisons will produce them in
|
||||
// order.
|
||||
//
|
||||
// The serialized key format is:
|
||||
@ -511,7 +456,7 @@ var outpointKeyPool = sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
// outpointKey returns a key suitable for use as a database key in the utxo set
|
||||
// outpointKey returns a key suitable for use as a database key in the UTXO set
|
||||
// while making use of a free list. A new buffer is allocated if there are not
|
||||
// already any available on the free list. The returned byte slice should be
|
||||
// returned to the free list by using the recycleOutpointKey function when the
|
||||
@ -519,7 +464,7 @@ var outpointKeyPool = sync.Pool{
|
||||
// the caller can calculate such as when used to write to the database.
|
||||
func outpointKey(outpoint wire.OutPoint) *[]byte {
|
||||
// A VLQ employs an MSB encoding, so they are useful not only to reduce
|
||||
// the amount of storage space, but also so iteration of utxos when
|
||||
// the amount of storage space, but also so iteration of UTXOs when
|
||||
// doing byte-wise comparisons will produce them in order.
|
||||
key := outpointKeyPool.Get().(*[]byte)
|
||||
idx := uint64(outpoint.Index)
|
||||
@ -537,9 +482,9 @@ func recycleOutpointKey(key *[]byte) {
|
||||
|
||||
// utxoEntryHeaderCode returns the calculated header code to be used when
|
||||
// serializing the provided utxo entry.
|
||||
func utxoEntryHeaderCode(entry *UtxoEntry) (uint64, error) {
|
||||
func utxoEntryHeaderCode(entry *UTXOEntry) (uint64, error) {
|
||||
if entry.IsSpent() {
|
||||
return 0, AssertError("attempt to serialize spent utxo header")
|
||||
return 0, AssertError("attempt to serialize spent UTXO header")
|
||||
}
|
||||
|
||||
// As described in the serialization format comments, the header code
|
||||
@ -553,9 +498,9 @@ func utxoEntryHeaderCode(entry *UtxoEntry) (uint64, error) {
|
||||
return headerCode, nil
|
||||
}
|
||||
|
||||
// serializeUtxoEntry returns the entry serialized to a format that is suitable
|
||||
// serializeUTXOEntry returns the entry serialized to a format that is suitable
|
||||
// for long-term storage. The format is described in detail above.
|
||||
func serializeUtxoEntry(entry *UtxoEntry) ([]byte, error) {
|
||||
func serializeUTXOEntry(entry *UTXOEntry) ([]byte, error) {
|
||||
// Spent outputs have no serialization.
|
||||
if entry.IsSpent() {
|
||||
return nil, nil
|
||||
@ -581,10 +526,24 @@ func serializeUtxoEntry(entry *UtxoEntry) ([]byte, error) {
|
||||
return serialized, nil
|
||||
}
|
||||
|
||||
// deserializeUtxoEntry decodes a utxo entry from the passed serialized byte
|
||||
// slice into a new UtxoEntry using a format that is suitable for long-term
|
||||
// deserializeOutPoint decodes an outPoint from the passed serialized byte
|
||||
// slice into a new wire.OutPoint using a format that is suitable for long-
|
||||
// term storage. this format is described in detail above.
|
||||
func deserializeOutPoint(serialized []byte) (*wire.OutPoint, error) {
|
||||
if len(serialized) <= daghash.HashSize {
|
||||
return nil, errDeserialize("unexpected end of data")
|
||||
}
|
||||
|
||||
hash := daghash.Hash{}
|
||||
hash.SetBytes(serialized[:daghash.HashSize])
|
||||
index, _ := deserializeVLQ(serialized[daghash.HashSize:])
|
||||
return wire.NewOutPoint(&hash, uint32(index)), nil
|
||||
}
|
||||
|
||||
// deserializeUTXOEntry decodes a UTXO entry from the passed serialized byte
|
||||
// slice into a new UTXOEntry using a format that is suitable for long-term
|
||||
// storage. The format is described in detail above.
|
||||
func deserializeUtxoEntry(serialized []byte) (*UtxoEntry, error) {
|
||||
func deserializeUTXOEntry(serialized []byte) (*UTXOEntry, error) {
|
||||
// Deserialize the header code.
|
||||
code, offset := deserializeVLQ(serialized)
|
||||
if offset >= len(serialized) {
|
||||
@ -602,10 +561,10 @@ func deserializeUtxoEntry(serialized []byte) (*UtxoEntry, error) {
|
||||
amount, pkScript, _, err := decodeCompressedTxOut(serialized[offset:])
|
||||
if err != nil {
|
||||
return nil, errDeserialize(fmt.Sprintf("unable to decode "+
|
||||
"utxo: %v", err))
|
||||
"UTXO: %v", err))
|
||||
}
|
||||
|
||||
entry := &UtxoEntry{
|
||||
entry := &UTXOEntry{
|
||||
amount: int64(amount),
|
||||
pkScript: pkScript,
|
||||
blockHeight: blockHeight,
|
||||
@ -618,70 +577,38 @@ func deserializeUtxoEntry(serialized []byte) (*UtxoEntry, error) {
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// dbFetchUtxoEntryByHash attempts to find and fetch a utxo for the given hash.
|
||||
// It uses a cursor and seek to try and do this as efficiently as possible.
|
||||
//
|
||||
// When there are no entries for the provided hash, nil will be returned for the
|
||||
// both the entry and the error.
|
||||
func dbFetchUtxoEntryByHash(dbTx database.Tx, hash *daghash.Hash) (*UtxoEntry, error) {
|
||||
// Attempt to find an entry by seeking for the hash along with a zero
|
||||
// index. Due to the fact the keys are serialized as <hash><index>,
|
||||
// where the index uses an MSB encoding, if there are any entries for
|
||||
// the hash at all, one will be found.
|
||||
cursor := dbTx.Metadata().Bucket(utxoSetBucketName).Cursor()
|
||||
key := outpointKey(wire.OutPoint{Hash: *hash, Index: 0})
|
||||
ok := cursor.Seek(*key)
|
||||
recycleOutpointKey(key)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// An entry was found, but it could just be an entry with the next
|
||||
// highest hash after the requested one, so make sure the hashes
|
||||
// actually match.
|
||||
cursorKey := cursor.Key()
|
||||
if len(cursorKey) < daghash.HashSize {
|
||||
return nil, nil
|
||||
}
|
||||
if !bytes.Equal(hash[:], cursorKey[:daghash.HashSize]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return deserializeUtxoEntry(cursor.Value())
|
||||
}
|
||||
|
||||
// dbFetchUtxoEntry uses an existing database transaction to fetch the specified
|
||||
// transaction output from the utxo set.
|
||||
// dbFetchUTXOEntry uses an existing database transaction to fetch the specified
|
||||
// transaction output from the UTXO set.
|
||||
//
|
||||
// When there is no entry for the provided output, nil will be returned for both
|
||||
// the entry and the error.
|
||||
func dbFetchUtxoEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoEntry, error) {
|
||||
func dbFetchUTXOEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UTXOEntry, error) {
|
||||
// Fetch the unspent transaction output information for the passed
|
||||
// transaction output. Return now when there is no entry.
|
||||
key := outpointKey(outpoint)
|
||||
utxoBucket := dbTx.Metadata().Bucket(utxoSetBucketName)
|
||||
serializedUtxo := utxoBucket.Get(*key)
|
||||
serializedUTXO := utxoBucket.Get(*key)
|
||||
recycleOutpointKey(key)
|
||||
if serializedUtxo == nil {
|
||||
if serializedUTXO == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// A non-nil zero-length entry means there is an entry in the database
|
||||
// for a spent transaction output which should never be the case.
|
||||
if len(serializedUtxo) == 0 {
|
||||
if len(serializedUTXO) == 0 {
|
||||
return nil, AssertError(fmt.Sprintf("database contains entry "+
|
||||
"for spent tx output %v", outpoint))
|
||||
}
|
||||
|
||||
// Deserialize the utxo entry and return it.
|
||||
entry, err := deserializeUtxoEntry(serializedUtxo)
|
||||
entry, err := deserializeUTXOEntry(serializedUTXO)
|
||||
if err != nil {
|
||||
// Ensure any deserialization errors are returned as database
|
||||
// corruption errors.
|
||||
if isDeserializeErr(err) {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("corrupt utxo entry "+
|
||||
Description: fmt.Sprintf("corrupt UTXO entry "+
|
||||
"for %v: %v", outpoint, err),
|
||||
}
|
||||
}
|
||||
@ -692,36 +619,29 @@ func dbFetchUtxoEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoEntry, err
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// dbPutUtxoView uses an existing database transaction to update the utxo set
|
||||
// in the database based on the provided utxo view contents and state. In
|
||||
// dbPutUTXODiff uses an existing database transaction to update the UTXO set
|
||||
// in the database based on the provided UTXO view contents and state. In
|
||||
// particular, only the entries that have been marked as modified are written
|
||||
// to the database.
|
||||
func dbPutUtxoView(dbTx database.Tx, view *UtxoViewpoint) error {
|
||||
func dbPutUTXODiff(dbTx database.Tx, diff *utxoDiff) error {
|
||||
utxoBucket := dbTx.Metadata().Bucket(utxoSetBucketName)
|
||||
for outpoint, entry := range view.entries {
|
||||
// No need to update the database if the entry was not modified.
|
||||
if entry == nil || !entry.isModified() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove the utxo entry if it is spent.
|
||||
if entry.IsSpent() {
|
||||
key := outpointKey(outpoint)
|
||||
err := utxoBucket.Delete(*key)
|
||||
recycleOutpointKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Serialize and store the utxo entry.
|
||||
serialized, err := serializeUtxoEntry(entry)
|
||||
for outPoint := range diff.toRemove {
|
||||
key := outpointKey(outPoint)
|
||||
err := utxoBucket.Delete(*key)
|
||||
recycleOutpointKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := outpointKey(outpoint)
|
||||
}
|
||||
|
||||
for outPoint, entry := range diff.toAdd {
|
||||
// Serialize and store the UTXO entry.
|
||||
serialized, err := serializeUTXOEntry(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := outpointKey(outPoint)
|
||||
err = utxoBucket.Put(*key, serialized)
|
||||
// NOTE: The key is intentionally not recycled here since the
|
||||
// database interface contract prohibits modifications. It will
|
||||
@ -787,48 +707,38 @@ func dbFetchHeightByHash(dbTx database.Tx, hash *daghash.Hash) (int32, error) {
|
||||
return int32(byteOrder.Uint32(serializedHeight)), nil
|
||||
}
|
||||
|
||||
// dbDAGState represents the data to be stored in the database for the current
|
||||
// DAG state.
|
||||
type dbDAGState struct {
|
||||
Tips []daghash.Hash
|
||||
TotalTxs uint64
|
||||
// serializeDAGTipHashes returns the serialization of the DAG tip hashes.
|
||||
// This is data to be stored in the DAG tip hashes bucket.
|
||||
func serializeDAGTipHashes(tipHashes []daghash.Hash) ([]byte, error) {
|
||||
return json.Marshal(tipHashes)
|
||||
}
|
||||
|
||||
// serializeDAGState returns the serialization of the DAG state.
|
||||
// This is data to be stored in the DAG state bucket.
|
||||
func serializeDAGState(state dbDAGState) ([]byte, error) {
|
||||
return json.Marshal(state)
|
||||
}
|
||||
|
||||
// deserializeDAGState deserializes the passed serialized DAG
|
||||
// state. This is data stored in the DAG state bucket and is updated after
|
||||
// every block is connected or disconnected form the DAG.
|
||||
func deserializeDAGState(serializedData []byte) (*dbDAGState, error) {
|
||||
var dbState dbDAGState
|
||||
err := json.Unmarshal(serializedData, &dbState)
|
||||
// deserializeDAGTipHashes deserializes the passed serialized DAG tip hashes.
|
||||
// This is data stored in the DAG tip hashes bucket and is updated after
|
||||
// every block is connected to the DAG.
|
||||
func deserializeDAGTipHashes(serializedData []byte) ([]daghash.Hash, error) {
|
||||
var tipHashes []daghash.Hash
|
||||
err := json.Unmarshal(serializedData, &tipHashes)
|
||||
if err != nil {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: "corrupt DAG state",
|
||||
Description: "corrupt DAG tip hashes",
|
||||
}
|
||||
}
|
||||
|
||||
return &dbState, nil
|
||||
return tipHashes, nil
|
||||
}
|
||||
|
||||
// dbPutDAGState uses an existing database transaction to update the DAG
|
||||
// state with the given parameters.
|
||||
func dbPutDAGState(dbTx database.Tx, state *DAGState) error {
|
||||
serializedData, err := serializeDAGState(dbDAGState{
|
||||
Tips: state.TipHashes,
|
||||
TotalTxs: state.TotalTxs,
|
||||
})
|
||||
// dbPutDAGTipHashes uses an existing database transaction to store the latest
|
||||
// tip hashes of the DAG.
|
||||
func dbPutDAGTipHashes(dbTx database.Tx, tipHashes []daghash.Hash) error {
|
||||
serializedData, err := serializeDAGTipHashes(tipHashes)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbTx.Metadata().Put(dagStateKeyName, serializedData)
|
||||
return dbTx.Metadata().Put(dagTipHashesKeyName, serializedData)
|
||||
}
|
||||
|
||||
// createDAGState initializes both the database and the DAG state to the
|
||||
@ -841,19 +751,23 @@ func (dag *BlockDAG) createDAGState() error {
|
||||
header := &genesisBlock.MsgBlock().Header
|
||||
node := newBlockNode(header, newSet(), dag.dagParams.K)
|
||||
node.status = statusDataStored | statusValid
|
||||
|
||||
genesisCoinbase := genesisBlock.Transactions()[0].MsgTx()
|
||||
genesisCoinbaseTxIn := genesisCoinbase.TxIn[0]
|
||||
genesisCoinbaseTxOut := genesisCoinbase.TxOut[0]
|
||||
genesisCoinbaseOutpoint := *wire.NewOutPoint(&genesisCoinbaseTxIn.PreviousOutPoint.Hash, genesisCoinbaseTxIn.PreviousOutPoint.Index)
|
||||
genesisCoinbaseUTXOEntry := newUTXOEntry(genesisCoinbaseTxOut, true, 0)
|
||||
node.diff = &utxoDiff{
|
||||
toAdd: utxoCollection{genesisCoinbaseOutpoint: genesisCoinbaseUTXOEntry},
|
||||
toRemove: utxoCollection{},
|
||||
}
|
||||
|
||||
dag.virtual.utxoSet.addTx(genesisCoinbase, 0)
|
||||
dag.virtual.SetTips(setFromSlice(node))
|
||||
|
||||
// Add the new node to the index which is used for faster lookups.
|
||||
dag.index.addNode(node)
|
||||
|
||||
// Initialize the DAG state. Since it is the genesis block, use
|
||||
// its timestamp for the median time.
|
||||
numTxs := uint64(len(genesisBlock.MsgBlock().Transactions))
|
||||
blockSize := uint64(genesisBlock.MsgBlock().SerializeSize())
|
||||
dagState := newDAGState(dag.virtual.Tips().hashes(), node, blockSize, numTxs,
|
||||
numTxs, time.Unix(node.timestamp, 0))
|
||||
dag.setDAGState(dagState)
|
||||
|
||||
// Create the initial the database chain state including creating the
|
||||
// necessary index buckets and inserting the genesis block.
|
||||
err := dag.db.Update(func(dbTx database.Tx) error {
|
||||
@ -879,18 +793,6 @@ func (dag *BlockDAG) createDAGState() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the bucket that houses the spend journal data and
|
||||
// store its version.
|
||||
_, err = meta.CreateBucket(spendJournalBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbPutVersion(dbTx, utxoSetVersionKeyName,
|
||||
latestUtxoSetBucketVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the bucket that houses the utxo set and store its
|
||||
// version. Note that the genesis block coinbase transaction is
|
||||
// intentionally not inserted here since it is not spendable by
|
||||
@ -899,8 +801,8 @@ func (dag *BlockDAG) createDAGState() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbPutVersion(dbTx, spendJournalVersionKeyName,
|
||||
latestSpendJournalBucketVersion)
|
||||
err = dbPutVersion(dbTx, utxoSetVersionKeyName,
|
||||
latestUTXOSetBucketVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -918,8 +820,8 @@ func (dag *BlockDAG) createDAGState() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the current DAG state into the database.
|
||||
err = dbPutDAGState(dbTx, dag.dagState)
|
||||
// Store the current DAG tip hashes into the database.
|
||||
err = dbPutDAGTipHashes(dbTx, dag.virtual.TipHashes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -938,7 +840,7 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
// everything from scratch or upgrade certain buckets.
|
||||
var initialized bool
|
||||
err := dag.db.View(func(dbTx database.Tx) error {
|
||||
initialized = dbTx.Metadata().Get(dagStateKeyName) != nil
|
||||
initialized = dbTx.Metadata().Get(dagTipHashesKeyName) != nil
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -953,13 +855,13 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
|
||||
// Attempt to load the DAG state from the database.
|
||||
return dag.db.View(func(dbTx database.Tx) error {
|
||||
// Fetch the stored DAG state from the database metadata.
|
||||
// Fetch the stored DAG tipHashes from the database metadata.
|
||||
// When it doesn't exist, it means the database hasn't been
|
||||
// initialized for use with the DAG yet, so break out now to allow
|
||||
// that to happen under a writable database transaction.
|
||||
serializedData := dbTx.Metadata().Get(dagStateKeyName)
|
||||
log.Tracef("Serialized DAG state: %x", serializedData)
|
||||
state, err := deserializeDAGState(serializedData)
|
||||
serializedData := dbTx.Metadata().Get(dagTipHashesKeyName)
|
||||
log.Tracef("Serialized DAG tip hashes: %x", serializedData)
|
||||
tipHashes, err := deserializeDAGTipHashes(serializedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1025,36 +927,72 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
i++
|
||||
}
|
||||
|
||||
// Set the DAG view to the stored state.
|
||||
// Load all of the known UTXO entries and construct the full
|
||||
// UTXO set accordingly. Since the number of entries is already
|
||||
// known, perform a single alloc for them versus a whole bunch
|
||||
// of little ones to reduce pressure on the GC.
|
||||
log.Infof("Loading UTXO set...")
|
||||
|
||||
utxoEntryBucket := dbTx.Metadata().Bucket(utxoSetBucketName)
|
||||
|
||||
// Determine how many UTXO entries will be loaded into the index so we can
|
||||
// allocate the right amount.
|
||||
var utxoEntryCount int32
|
||||
cursor = utxoEntryBucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
utxoEntryCount++
|
||||
}
|
||||
|
||||
fullUTXOCollection := make(utxoCollection, utxoEntryCount)
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
// Deserialize the outPoint
|
||||
outPoint, err := deserializeOutPoint(cursor.Key())
|
||||
if err != nil {
|
||||
// Ensure any deserialization errors are returned as database
|
||||
// corruption errors.
|
||||
if isDeserializeErr(err) {
|
||||
return database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("corrupt outPoint: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Deserialize the utxo entry
|
||||
entry, err := deserializeUTXOEntry(cursor.Value())
|
||||
if err != nil {
|
||||
// Ensure any deserialization errors are returned as database
|
||||
// corruption errors.
|
||||
if isDeserializeErr(err) {
|
||||
return database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("corrupt utxo entry: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
fullUTXOCollection[*outPoint] = entry
|
||||
}
|
||||
|
||||
// Apply the loaded utxoCollection to the virtual block.
|
||||
dag.virtual.utxoSet.utxoCollection = fullUTXOCollection
|
||||
|
||||
// Apply the stored tips to the virtual block.
|
||||
tips := newSet()
|
||||
for _, tipHash := range state.Tips {
|
||||
for _, tipHash := range tipHashes {
|
||||
tip := dag.index.LookupNode(&tipHash)
|
||||
if tip == nil {
|
||||
return AssertError(fmt.Sprintf("initDAGState: cannot find "+
|
||||
"DAG tip %s in block index", state.Tips))
|
||||
"DAG tip %s in block index", tipHashes))
|
||||
}
|
||||
tips.add(tip)
|
||||
}
|
||||
dag.virtual.SetTips(tips)
|
||||
|
||||
// Load the raw block bytes for the selected tip.
|
||||
selectedTip := dag.virtual.selectedParent
|
||||
blockBytes, err := dbTx.FetchBlock(&selectedTip.hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var block wire.MsgBlock
|
||||
err = block.Deserialize(bytes.NewReader(blockBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the DAG state.
|
||||
blockSize := uint64(len(blockBytes))
|
||||
numTxns := uint64(len(block.Transactions))
|
||||
dagState := newDAGState(dag.virtual.Tips().hashes(), selectedTip, blockSize, numTxns, state.TotalTxs, selectedTip.CalcPastMedianTime())
|
||||
dag.setDAGState(dagState)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@ -1078,23 +1016,6 @@ func deserializeBlockRow(blockRow []byte) (*wire.BlockHeader, blockStatus, error
|
||||
return &header, blockStatus(statusByte), nil
|
||||
}
|
||||
|
||||
// dbFetchHeaderByHash uses an existing database transaction to retrieve the
|
||||
// block header for the provided hash.
|
||||
func dbFetchHeaderByHash(dbTx database.Tx, hash *daghash.Hash) (*wire.BlockHeader, error) {
|
||||
headerBytes, err := dbTx.FetchBlockHeader(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var header wire.BlockHeader
|
||||
err = header.Deserialize(bytes.NewReader(headerBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &header, nil
|
||||
}
|
||||
|
||||
// dbFetchBlockByNode uses an existing database transaction to retrieve the
|
||||
// raw block for the provided node, deserialize it, and return a util.Block
|
||||
// with the height set.
|
||||
|
@ -410,14 +410,14 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entry *UtxoEntry
|
||||
entry *UTXOEntry
|
||||
serialized []byte
|
||||
}{
|
||||
// From tx in main blockchain:
|
||||
// b7c3332bc138e2c9429818f5fed500bcc1746544218772389054dc8047d7cd3f:0
|
||||
{
|
||||
name: "height 1, coinbase",
|
||||
entry: &UtxoEntry{
|
||||
entry: &UTXOEntry{
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
blockHeight: 1,
|
||||
@ -429,7 +429,7 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
// b7c3332bc138e2c9429818f5fed500bcc1746544218772389054dc8047d7cd3f:0
|
||||
{
|
||||
name: "height 1, coinbase, spent",
|
||||
entry: &UtxoEntry{
|
||||
entry: &UTXOEntry{
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
blockHeight: 1,
|
||||
@ -441,7 +441,7 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb:1
|
||||
{
|
||||
name: "height 100001, not coinbase",
|
||||
entry: &UtxoEntry{
|
||||
entry: &UTXOEntry{
|
||||
amount: 1000000,
|
||||
pkScript: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
||||
blockHeight: 100001,
|
||||
@ -453,7 +453,7 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb:1
|
||||
{
|
||||
name: "height 100001, not coinbase, spent",
|
||||
entry: &UtxoEntry{
|
||||
entry: &UTXOEntry{
|
||||
amount: 1000000,
|
||||
pkScript: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
||||
blockHeight: 100001,
|
||||
@ -465,14 +465,14 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
|
||||
for i, test := range tests {
|
||||
// Ensure the utxo entry serializes to the expected value.
|
||||
gotBytes, err := serializeUtxoEntry(test.entry)
|
||||
gotBytes, err := serializeUTXOEntry(test.entry)
|
||||
if err != nil {
|
||||
t.Errorf("serializeUtxoEntry #%d (%s) unexpected "+
|
||||
t.Errorf("serializeUTXOEntry #%d (%s) unexpected "+
|
||||
"error: %v", i, test.name, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(gotBytes, test.serialized) {
|
||||
t.Errorf("serializeUtxoEntry #%d (%s): mismatched "+
|
||||
t.Errorf("serializeUTXOEntry #%d (%s): mismatched "+
|
||||
"bytes - got %x, want %x", i, test.name,
|
||||
gotBytes, test.serialized)
|
||||
continue
|
||||
@ -485,9 +485,9 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
}
|
||||
|
||||
// Deserialize to a utxo entry.
|
||||
utxoEntry, err := deserializeUtxoEntry(test.serialized)
|
||||
utxoEntry, err := deserializeUTXOEntry(test.serialized)
|
||||
if err != nil {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) unexpected "+
|
||||
t.Errorf("deserializeUTXOEntry #%d (%s) unexpected "+
|
||||
"error: %v", i, test.name, err)
|
||||
continue
|
||||
}
|
||||
@ -495,7 +495,7 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
// The deserialized entry must not be marked spent since unspent
|
||||
// entries are not serialized.
|
||||
if utxoEntry.IsSpent() {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) output should "+
|
||||
t.Errorf("deserializeUTXOEntry #%d (%s) output should "+
|
||||
"not be marked spent", i, test.name)
|
||||
continue
|
||||
}
|
||||
@ -503,26 +503,26 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
// 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 "+
|
||||
t.Errorf("deserializeUTXOEntry #%d (%s) mismatched "+
|
||||
"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 "+
|
||||
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 "+
|
||||
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() {
|
||||
t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
|
||||
t.Errorf("deserializeUTXOEntry #%d (%s) mismatched "+
|
||||
"coinbase flag: got %v, want %v", i, test.name,
|
||||
utxoEntry.IsCoinBase(), test.entry.IsCoinBase())
|
||||
continue
|
||||
@ -537,13 +537,13 @@ func TestUtxoEntryHeaderCodeErrors(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entry *UtxoEntry
|
||||
entry *UTXOEntry
|
||||
code uint64
|
||||
errType error
|
||||
}{
|
||||
{
|
||||
name: "Force assertion due to spent output",
|
||||
entry: &UtxoEntry{packedFlags: tfSpent},
|
||||
entry: &UTXOEntry{packedFlags: tfSpent},
|
||||
errType: AssertError(""),
|
||||
},
|
||||
}
|
||||
@ -590,85 +590,79 @@ func TestUtxoEntryDeserializeErrors(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
// Ensure the expected error type is returned and the returned
|
||||
// entry is nil.
|
||||
entry, err := deserializeUtxoEntry(test.serialized)
|
||||
entry, err := deserializeUTXOEntry(test.serialized)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("deserializeUtxoEntry (%s): expected error "+
|
||||
t.Errorf("deserializeUTXOEntry (%s): expected error "+
|
||||
"type does not match - got %T, want %T",
|
||||
test.name, err, test.errType)
|
||||
continue
|
||||
}
|
||||
if entry != nil {
|
||||
t.Errorf("deserializeUtxoEntry (%s): returned entry "+
|
||||
t.Errorf("deserializeUTXOEntry (%s): returned entry "+
|
||||
"is not nil", test.name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDAGStateSerialization ensures serializing and deserializing the
|
||||
// DAG state works as expected.
|
||||
func TestDAGStateSerialization(t *testing.T) {
|
||||
// TestDAGTipHashesSerialization ensures serializing and deserializing the
|
||||
// DAG tip hashes works as expected.
|
||||
func TestDAGTipHashesSerialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
state dbDAGState
|
||||
tipHashes []daghash.Hash
|
||||
serialized []byte
|
||||
}{
|
||||
{
|
||||
name: "genesis",
|
||||
state: dbDAGState{
|
||||
Tips: []daghash.Hash{*newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
|
||||
TotalTxs: 1,
|
||||
},
|
||||
serialized: []byte("{\"Tips\":[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]],\"TotalTxs\":1}"),
|
||||
name: "genesis",
|
||||
tipHashes: []daghash.Hash{*newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
|
||||
serialized: []byte("[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]]"),
|
||||
},
|
||||
{
|
||||
name: "block 1",
|
||||
state: dbDAGState{
|
||||
Tips: []daghash.Hash{*newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
|
||||
TotalTxs: 2,
|
||||
},
|
||||
serialized: []byte("{\"Tips\":[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]],\"TotalTxs\":2}"),
|
||||
name: "block 1",
|
||||
tipHashes: []daghash.Hash{*newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
|
||||
serialized: []byte("[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]]"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
gotBytes, err := serializeDAGState(test.state)
|
||||
gotBytes, err := serializeDAGTipHashes(test.tipHashes)
|
||||
if err != nil {
|
||||
t.Errorf("serializeDAGState #%d (%s) "+
|
||||
t.Errorf("serializeDAGTipHashes #%d (%s) "+
|
||||
"unexpected error: %v", i, test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the state serializes to the expected value.
|
||||
// Ensure the tipHashes serializes to the expected value.
|
||||
if !bytes.Equal(gotBytes, test.serialized) {
|
||||
t.Errorf("serializeDAGState #%d (%s): mismatched "+
|
||||
t.Errorf("serializeDAGTipHashes #%d (%s): mismatched "+
|
||||
"bytes - got %s, want %s", i, test.name,
|
||||
string(gotBytes), string(test.serialized))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the serialized bytes are decoded back to the expected
|
||||
// state.
|
||||
state, err := deserializeDAGState(test.serialized)
|
||||
// tipHashes.
|
||||
tipHashes, err := deserializeDAGTipHashes(test.serialized)
|
||||
if err != nil {
|
||||
t.Errorf("deserializeDAGState #%d (%s) "+
|
||||
t.Errorf("deserializeDAGTipHashes #%d (%s) "+
|
||||
"unexpected error: %v", i, test.name, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(*state, test.state) {
|
||||
t.Errorf("deserializeDAGState #%d (%s) "+
|
||||
"mismatched state - got %v, want %v", i,
|
||||
test.name, *state, test.state)
|
||||
if !reflect.DeepEqual(tipHashes, test.tipHashes) {
|
||||
t.Errorf("deserializeDAGTipHashes #%d (%s) "+
|
||||
"mismatched tipHashes - got %v, want %v", i,
|
||||
test.name, tipHashes, test.tipHashes)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDAGStateDeserializeErrors performs negative tests against
|
||||
// deserializing the DAG state to ensure error paths work as expected.
|
||||
func TestDAGStateDeserializeErrors(t *testing.T) {
|
||||
// TestDAGTipHashesDeserializeErrors performs negative tests against
|
||||
// deserializing the DAG tip hashes to ensure error paths work as expected.
|
||||
func TestDAGTipHashesDeserializeErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
@ -683,16 +677,16 @@ func TestDAGStateDeserializeErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "corrupted data",
|
||||
serialized: []byte("{\"Tips\":[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,7"),
|
||||
serialized: []byte("[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,7"),
|
||||
errType: database.Error{ErrorCode: database.ErrCorruption},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the expected error type and code is returned.
|
||||
_, err := deserializeDAGState(test.serialized)
|
||||
_, err := deserializeDAGTipHashes(test.serialized)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("deserializeDAGState (%s): expected "+
|
||||
t.Errorf("deserializeDAGTipHashes (%s): expected "+
|
||||
"error type does not match - got %T, want %T",
|
||||
test.name, err, test.errType)
|
||||
continue
|
||||
@ -700,8 +694,8 @@ func TestDAGStateDeserializeErrors(t *testing.T) {
|
||||
if derr, ok := err.(database.Error); ok {
|
||||
tderr := test.errType.(database.Error)
|
||||
if derr.ErrorCode != tderr.ErrorCode {
|
||||
t.Errorf("deserializeDAGState (%s): "+
|
||||
"wrong error code got: %v, want: %v",
|
||||
t.Errorf("deserializeDAGTipHashes (%s): "+
|
||||
"wrong error code got: %v, want: %v",
|
||||
test.name, derr.ErrorCode,
|
||||
tderr.ErrorCode)
|
||||
continue
|
||||
|
@ -57,10 +57,10 @@ func isSupportedDbType(dbType string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// chainSetup is used to create a new db and chain instance with the genesis
|
||||
// dagSetup is used to create a new db and chain instance with the genesis
|
||||
// block already inserted. In addition to the new chain instance, it returns
|
||||
// a teardown function the caller should invoke when done testing to clean up.
|
||||
func chainSetup(dbName string, params *dagconfig.Params) (*blockdag.BlockDAG, func(), error) {
|
||||
func dagSetup(dbName string, params *dagconfig.Params) (*blockdag.BlockDAG, func(), error) {
|
||||
if !isSupportedDbType(testDbType) {
|
||||
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
|
||||
}
|
||||
@ -142,7 +142,7 @@ func TestFullBlocks(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a new database and chain instance to run tests against.
|
||||
chain, teardownFunc, err := chainSetup("fullblocktest",
|
||||
dag, teardownFunc, err := dagSetup("fullblocktest",
|
||||
&dagconfig.RegressionNetParams)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup chain instance: %v", err)
|
||||
@ -160,7 +160,7 @@ func TestFullBlocks(t *testing.T) {
|
||||
t.Logf("Testing block %s (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
isOrphan, err := chain.ProcessBlock(block,
|
||||
isOrphan, err := dag.ProcessBlock(block,
|
||||
blockdag.BFNone)
|
||||
if err != nil {
|
||||
t.Fatalf("block %q (hash %s, height %d) should "+
|
||||
@ -186,7 +186,7 @@ func TestFullBlocks(t *testing.T) {
|
||||
t.Logf("Testing block %s (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
_, err := chain.ProcessBlock(block, blockdag.BFNone)
|
||||
_, err := dag.ProcessBlock(block, blockdag.BFNone)
|
||||
if err == nil {
|
||||
t.Fatalf("block %q (hash %s, height %d) should not "+
|
||||
"have been accepted", item.Name, block.Hash(),
|
||||
@ -243,7 +243,7 @@ func TestFullBlocks(t *testing.T) {
|
||||
t.Logf("Testing block %s (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
isOrphan, err := chain.ProcessBlock(block, blockdag.BFNone)
|
||||
isOrphan, err := dag.ProcessBlock(block, blockdag.BFNone)
|
||||
if err != nil {
|
||||
// Ensure the error code is of the expected type.
|
||||
if _, ok := err.(blockdag.RuleError); !ok {
|
||||
@ -272,14 +272,14 @@ func TestFullBlocks(t *testing.T) {
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
// Ensure hash and height match.
|
||||
dagState := chain.GetDAGState()
|
||||
if dagState.SelectedTip.Hash != item.Block.BlockHash() ||
|
||||
dagState.SelectedTip.Height != blockHeight {
|
||||
virtualBlock := dag.VirtualBlock()
|
||||
if virtualBlock.SelectedTipHash() != item.Block.BlockHash() ||
|
||||
virtualBlock.SelectedTipHeight() != blockHeight {
|
||||
|
||||
t.Fatalf("block %q (hash %s, height %d) should be "+
|
||||
"the current tip -- got (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight, dagState.SelectedTip.Hash,
|
||||
dagState.SelectedTip.Height)
|
||||
item.Name, block.Hash(), blockHeight, virtualBlock.SelectedTipHash(),
|
||||
virtualBlock.SelectedTipHeight())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -662,7 +662,7 @@ 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 associated transaction using
|
||||
// the passed map.
|
||||
func (idx *AddrIndex) indexBlock(data writeIndexData, block *util.Block, view *blockdag.UtxoViewpoint) {
|
||||
func (idx *AddrIndex) indexBlock(data writeIndexData, block *util.Block, virtual *blockdag.VirtualBlock) {
|
||||
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
|
||||
@ -670,11 +670,11 @@ func (idx *AddrIndex) indexBlock(data writeIndexData, block *util.Block, view *b
|
||||
// a coinbase.
|
||||
if txIdx != 0 {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
// The view should always have the input since
|
||||
// The UTXO should always have the input since
|
||||
// the index contract requires it, however, be
|
||||
// safe and simply ignore any missing entries.
|
||||
entry := view.LookupEntry(txIn.PreviousOutPoint)
|
||||
if entry == nil {
|
||||
entry, ok := virtual.GetUTXOEntry(txIn.PreviousOutPoint)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -693,7 +693,7 @@ func (idx *AddrIndex) indexBlock(data writeIndexData, block *util.Block, view *b
|
||||
// the transactions in the block involve.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) error {
|
||||
// The offset and length of the transactions within the serialized
|
||||
// block.
|
||||
txLocs, err := block.TxLoc()
|
||||
@ -709,7 +709,7 @@ func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *util.Block, view *bl
|
||||
|
||||
// Build all of the address to transaction mappings in a local map.
|
||||
addrsToTxns := make(writeIndexData)
|
||||
idx.indexBlock(addrsToTxns, block, view)
|
||||
idx.indexBlock(addrsToTxns, block, virtual)
|
||||
|
||||
// Add all of the index entries for each address.
|
||||
addrIdxBucket := dbTx.Metadata().Bucket(addrIndexKey)
|
||||
@ -731,10 +731,10 @@ func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *util.Block, view *bl
|
||||
// each transaction in the block involve.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) DisconnectBlock(dbTx database.Tx, block *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func (idx *AddrIndex) DisconnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) 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, virtual)
|
||||
|
||||
// Remove all of the index entries for each address.
|
||||
bucket := dbTx.Metadata().Bucket(addrIndexKey)
|
||||
@ -833,7 +833,7 @@ func (idx *AddrIndex) indexUnconfirmedAddresses(pkScript []byte, tx *util.Tx) {
|
||||
// addresses not being indexed.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (idx *AddrIndex) AddUnconfirmedTx(tx *util.Tx, utxoView *blockdag.UtxoViewpoint) {
|
||||
func (idx *AddrIndex) AddUnconfirmedTx(tx *util.Tx, utxoView *blockdag.UTXOView) {
|
||||
// Index addresses of all referenced previous transaction outputs.
|
||||
//
|
||||
// The existence checks are elided since this is only called after the
|
||||
|
@ -203,7 +203,7 @@ func storeFilter(dbTx database.Tx, block *util.Block, f *gcs.Filter,
|
||||
// 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 *util.Block,
|
||||
view *blockdag.UtxoViewpoint) error {
|
||||
virtual *blockdag.VirtualBlock) error {
|
||||
|
||||
f, err := builder.BuildBasicFilter(block.MsgBlock())
|
||||
if err != nil {
|
||||
@ -227,7 +227,7 @@ func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *util.Block,
|
||||
// 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 *util.Block,
|
||||
view *blockdag.UtxoViewpoint) error {
|
||||
virtual *blockdag.VirtualBlock) error {
|
||||
|
||||
for _, key := range cfIndexKeys {
|
||||
err := dbDeleteFilterIdxEntry(dbTx, key, block.Hash())
|
||||
|
@ -52,11 +52,11 @@ type Indexer interface {
|
||||
|
||||
// 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 *util.Block, view *blockdag.UtxoViewpoint) error
|
||||
ConnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) 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 *util.Block, view *blockdag.UtxoViewpoint) error
|
||||
DisconnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) error
|
||||
}
|
||||
|
||||
// AssertError identifies an error that indicates an internal code consistency
|
||||
|
@ -5,7 +5,6 @@
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/daglabs/btcd/blockdag"
|
||||
@ -13,6 +12,7 @@ import (
|
||||
"github.com/daglabs/btcd/database"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -68,7 +68,7 @@ func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*daghash.Hash, int32, e
|
||||
// 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 *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *util.Block, virtual *blockdag.VirtualBlock) error {
|
||||
// Assert that the block being connected properly connects to the
|
||||
// current tip of the index.
|
||||
idxKey := indexer.Key()
|
||||
@ -86,7 +86,7 @@ func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *util.Block, v
|
||||
}
|
||||
|
||||
// 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, virtual); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *util.Block, v
|
||||
// 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 *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *util.Block, virtual *blockdag.VirtualBlock) error {
|
||||
// Assert that the block being disconnected is the current tip of the
|
||||
// index.
|
||||
idxKey := indexer.Key()
|
||||
@ -115,7 +115,7 @@ func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *util.Block
|
||||
|
||||
// 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, virtual); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -314,8 +314,8 @@ func dbFetchTx(dbTx database.Tx, hash *daghash.Hash) (*wire.MsgTx, error) {
|
||||
// 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 *util.Block, interrupt <-chan struct{}) (*blockdag.UtxoViewpoint, error) {
|
||||
view := blockdag.NewUtxoViewpoint()
|
||||
func makeUtxoView(dbTx database.Tx, block *util.Block, interrupt <-chan struct{}) (*blockdag.UTXOView, error) {
|
||||
view := blockdag.NewUTXOView()
|
||||
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
|
||||
@ -350,11 +350,11 @@ func makeUtxoView(dbTx database.Tx, block *util.Block, interrupt <-chan struct{}
|
||||
// checks, and invokes each indexer.
|
||||
//
|
||||
// This is part of the blockchain.IndexManager interface.
|
||||
func (m *Manager) ConnectBlock(dbTx database.Tx, block *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func (m *Manager) ConnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) 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, virtual)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -368,11 +368,11 @@ func (m *Manager) ConnectBlock(dbTx database.Tx, block *util.Block, view *blockd
|
||||
// the index entries associated with the block.
|
||||
//
|
||||
// This is part of the blockchain.IndexManager interface.
|
||||
func (m *Manager) DisconnectBlock(dbTx database.Tx, block *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func (m *Manager) DisconnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) 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, virtual)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ 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 *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) 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 +411,7 @@ func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block *util.Block, view *bloc
|
||||
// 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 *util.Block, view *blockdag.UtxoViewpoint) error {
|
||||
func (idx *TxIndex) DisconnectBlock(dbTx database.Tx, block *util.Block, virtual *blockdag.VirtualBlock) error {
|
||||
// Remove all of the transactions in the block from the index.
|
||||
if err := dbRemoveTxIndexEntries(dbTx, block); err != nil {
|
||||
return err
|
||||
|
@ -18,7 +18,7 @@ func TestNotifications(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a new database and chain instance to run tests against.
|
||||
chain, teardownFunc, err := chainSetup("notifications",
|
||||
chain, teardownFunc, err := dagSetup("notifications",
|
||||
&dagconfig.MainNetParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup chain instance: %v", err)
|
||||
|
@ -29,7 +29,7 @@ type txValidator struct {
|
||||
validateChan chan *txValidateItem
|
||||
quitChan chan struct{}
|
||||
resultChan chan error
|
||||
utxoView *UtxoViewpoint
|
||||
utxoView *UTXOView
|
||||
flags txscript.ScriptFlags
|
||||
sigCache *txscript.SigCache
|
||||
}
|
||||
@ -166,7 +166,7 @@ func (v *txValidator) Validate(items []*txValidateItem) error {
|
||||
|
||||
// newTxValidator returns a new instance of txValidator to be used for
|
||||
// validating transaction scripts asynchronously.
|
||||
func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator {
|
||||
func newTxValidator(utxoView *UTXOView, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator {
|
||||
return &txValidator{
|
||||
validateChan: make(chan *txValidateItem),
|
||||
quitChan: make(chan struct{}),
|
||||
@ -179,7 +179,7 @@ func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCach
|
||||
|
||||
// ValidateTransactionScripts validates the scripts for the passed transaction
|
||||
// using multiple goroutines.
|
||||
func ValidateTransactionScripts(tx *util.Tx, utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
|
||||
func ValidateTransactionScripts(tx *util.Tx, utxoView *UTXOView, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
|
||||
// Collect all of the transaction inputs and required information for
|
||||
// validation.
|
||||
txIns := tx.MsgTx().TxIn
|
||||
@ -205,7 +205,7 @@ func ValidateTransactionScripts(tx *util.Tx, utxoView *UtxoViewpoint, flags txsc
|
||||
|
||||
// checkBlockScripts executes and validates the scripts for all transactions in
|
||||
// the passed block using multiple goroutines.
|
||||
func checkBlockScripts(block *util.Block, utxoView *UtxoViewpoint, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
|
||||
func checkBlockScripts(block *util.Block, utxoView *UTXOView, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
|
||||
// Collect all of the transaction inputs and required information for
|
||||
// validation for all transactions in the block into a single slice.
|
||||
numInputs := 0
|
||||
|
@ -34,7 +34,7 @@ func TestCheckBlockScripts(t *testing.T) {
|
||||
}
|
||||
|
||||
storeDataFile := fmt.Sprintf("%d.utxostore", testBlockNum)
|
||||
view, err := loadUtxoView(storeDataFile)
|
||||
view, err := loadUTXOView(storeDataFile)
|
||||
if err != nil {
|
||||
t.Errorf("Error loading txstore: %v\n", err)
|
||||
return
|
||||
|
@ -1,15 +1,15 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// utxoCollection represents a set of UTXOs indexed by their outPoints
|
||||
type utxoCollection map[wire.OutPoint]*UtxoEntry
|
||||
type utxoCollection map[wire.OutPoint]*UTXOEntry
|
||||
|
||||
func (uc utxoCollection) String() string {
|
||||
utxoStrings := make([]string, len(uc))
|
||||
@ -26,11 +26,27 @@ func (uc utxoCollection) String() string {
|
||||
return fmt.Sprintf("[ %s ]", strings.Join(utxoStrings, ", "))
|
||||
}
|
||||
|
||||
// add adds a new UTXO entry to this collection
|
||||
func (uc utxoCollection) add(outPoint wire.OutPoint, entry *UTXOEntry) {
|
||||
uc[outPoint] = entry
|
||||
}
|
||||
|
||||
// remove removes a UTXO entry from this collection if it exists
|
||||
func (uc utxoCollection) remove(outPoint wire.OutPoint) {
|
||||
delete(uc, outPoint)
|
||||
}
|
||||
|
||||
// contains returns a boolean value indicating whether a UTXO entry is in the set
|
||||
func (uc utxoCollection) contains(outPoint wire.OutPoint) bool {
|
||||
_, ok := uc[outPoint]
|
||||
return ok
|
||||
}
|
||||
|
||||
// clone returns a clone of this collection
|
||||
func (uc utxoCollection) clone() utxoCollection {
|
||||
clone := utxoCollection{}
|
||||
for outPoint, entry := range uc {
|
||||
clone[outPoint] = entry
|
||||
clone.add(outPoint, entry)
|
||||
}
|
||||
|
||||
return clone
|
||||
@ -90,10 +106,10 @@ func (d *utxoDiff) diffFrom(other *utxoDiff) (*utxoDiff, error) {
|
||||
// If they are not in other.toAdd - should be added in result.toRemove
|
||||
// If they are in other.toRemove - base utxoSet is not the same
|
||||
for outPoint, utxoEntry := range d.toAdd {
|
||||
if _, ok := other.toAdd[outPoint]; !ok {
|
||||
result.toRemove[outPoint] = utxoEntry
|
||||
if !other.toAdd.contains(outPoint) {
|
||||
result.toRemove.add(outPoint, utxoEntry)
|
||||
}
|
||||
if _, ok := other.toRemove[outPoint]; ok {
|
||||
if other.toRemove.contains(outPoint) {
|
||||
return nil, errors.New("diffFrom: transaction both in d.toAdd and in other.toRemove")
|
||||
}
|
||||
}
|
||||
@ -102,10 +118,10 @@ func (d *utxoDiff) diffFrom(other *utxoDiff) (*utxoDiff, error) {
|
||||
// If they are not in other.toRemove - should be added in result.toAdd
|
||||
// If they are in other.toAdd - base utxoSet is not the same
|
||||
for outPoint, utxoEntry := range d.toRemove {
|
||||
if _, ok := other.toRemove[outPoint]; !ok {
|
||||
result.toAdd[outPoint] = utxoEntry
|
||||
if !other.toRemove.contains(outPoint) {
|
||||
result.toAdd.add(outPoint, utxoEntry)
|
||||
}
|
||||
if _, ok := other.toAdd[outPoint]; ok {
|
||||
if other.toAdd.contains(outPoint) {
|
||||
return nil, errors.New("diffFrom: transaction both in d.toRemove and in other.toAdd")
|
||||
}
|
||||
}
|
||||
@ -113,16 +129,16 @@ func (d *utxoDiff) diffFrom(other *utxoDiff) (*utxoDiff, error) {
|
||||
// All transactions in other.toAdd:
|
||||
// If they are not in d.toAdd - should be added in result.toAdd
|
||||
for outPoint, utxoEntry := range other.toAdd {
|
||||
if _, ok := d.toAdd[outPoint]; !ok {
|
||||
result.toAdd[outPoint] = utxoEntry
|
||||
if !d.toAdd.contains(outPoint) {
|
||||
result.toAdd.add(outPoint, utxoEntry)
|
||||
}
|
||||
}
|
||||
|
||||
// All transactions in other.toRemove:
|
||||
// If they are not in d.toRemove - should be added in result.toRemove
|
||||
for outPoint, utxoEntry := range other.toRemove {
|
||||
if _, ok := d.toRemove[outPoint]; !ok {
|
||||
result.toRemove[outPoint] = utxoEntry
|
||||
if !d.toRemove.contains(outPoint) {
|
||||
result.toRemove.add(outPoint, utxoEntry)
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,10 +179,10 @@ func (d *utxoDiff) withDiff(diff *utxoDiff) (*utxoDiff, error) {
|
||||
// If they are in diff.toAdd - should throw an error
|
||||
// Otherwise - should be ignored
|
||||
for outPoint, utxoEntry := range d.toAdd {
|
||||
if _, ok := diff.toRemove[outPoint]; !ok {
|
||||
result.toAdd[outPoint] = utxoEntry
|
||||
if !diff.toRemove.contains(outPoint) {
|
||||
result.toAdd.add(outPoint, utxoEntry)
|
||||
}
|
||||
if _, ok := diff.toAdd[outPoint]; ok {
|
||||
if diff.toAdd.contains(outPoint) {
|
||||
return nil, errors.New("withDiff: transaction both in d.toAdd and in other.toAdd")
|
||||
}
|
||||
}
|
||||
@ -176,10 +192,10 @@ func (d *utxoDiff) withDiff(diff *utxoDiff) (*utxoDiff, error) {
|
||||
// If they are in diff.toRemove - should throw an error
|
||||
// Otherwise - should be ignored
|
||||
for outPoint, utxoEntry := range d.toRemove {
|
||||
if _, ok := diff.toAdd[outPoint]; !ok {
|
||||
result.toRemove[outPoint] = utxoEntry
|
||||
if !diff.toAdd.contains(outPoint) {
|
||||
result.toRemove.add(outPoint, utxoEntry)
|
||||
}
|
||||
if _, ok := diff.toRemove[outPoint]; ok {
|
||||
if diff.toRemove.contains(outPoint) {
|
||||
return nil, errors.New("withDiff: transaction both in d.toRemove and in other.toRemove")
|
||||
}
|
||||
}
|
||||
@ -187,16 +203,16 @@ func (d *utxoDiff) withDiff(diff *utxoDiff) (*utxoDiff, error) {
|
||||
// All transactions in diff.toAdd:
|
||||
// If they are not in d.toRemove - should be added in result.toAdd
|
||||
for outPoint, utxoEntry := range diff.toAdd {
|
||||
if _, ok := d.toRemove[outPoint]; !ok {
|
||||
result.toAdd[outPoint] = utxoEntry
|
||||
if !d.toRemove.contains(outPoint) {
|
||||
result.toAdd.add(outPoint, utxoEntry)
|
||||
}
|
||||
}
|
||||
|
||||
// All transactions in diff.toRemove:
|
||||
// If they are not in d.toAdd - should be added in result.toRemove
|
||||
for outPoint, utxoEntry := range diff.toRemove {
|
||||
if _, ok := d.toAdd[outPoint]; !ok {
|
||||
result.toRemove[outPoint] = utxoEntry
|
||||
if !d.toAdd.contains(outPoint) {
|
||||
result.toRemove.add(outPoint, utxoEntry)
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,10 +232,17 @@ func (d utxoDiff) String() string {
|
||||
}
|
||||
|
||||
// newUTXOEntry creates a new utxoEntry representing the given txOut
|
||||
func newUTXOEntry(txOut *wire.TxOut) *UtxoEntry {
|
||||
entry := new(UtxoEntry)
|
||||
entry.amount = txOut.Value
|
||||
entry.pkScript = txOut.PkScript
|
||||
func newUTXOEntry(txOut *wire.TxOut, isCoinbase bool, blockHeight int32) *UTXOEntry {
|
||||
entry := &UTXOEntry{
|
||||
amount: txOut.Value,
|
||||
pkScript: txOut.PkScript,
|
||||
blockHeight: blockHeight,
|
||||
packedFlags: tfModified,
|
||||
}
|
||||
|
||||
if isCoinbase {
|
||||
entry.packedFlags |= tfCoinBase
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
@ -240,7 +263,7 @@ type utxoSet interface {
|
||||
fmt.Stringer
|
||||
diffFrom(other utxoSet) (*utxoDiff, error)
|
||||
withDiff(utxoDiff *utxoDiff) (utxoSet, error)
|
||||
addTx(tx *wire.MsgTx) (ok bool)
|
||||
addTx(tx *wire.MsgTx, blockHeight int32) (ok bool)
|
||||
clone() utxoSet
|
||||
}
|
||||
|
||||
@ -277,22 +300,25 @@ func (fus *fullUTXOSet) withDiff(other *utxoDiff) (utxoSet, error) {
|
||||
}
|
||||
|
||||
// addTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context
|
||||
func (fus *fullUTXOSet) addTx(tx *wire.MsgTx) bool {
|
||||
if !fus.containsInputs(tx) {
|
||||
return false
|
||||
}
|
||||
func (fus *fullUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
|
||||
isCoinbase := IsCoinBaseTx(tx)
|
||||
if !isCoinbase {
|
||||
if !fus.containsInputs(tx) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
delete(fus.utxoCollection, outPoint)
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
fus.remove(outPoint)
|
||||
}
|
||||
}
|
||||
|
||||
for i, txOut := range tx.TxOut {
|
||||
hash := tx.TxHash()
|
||||
outPoint := *wire.NewOutPoint(&hash, uint32(i))
|
||||
entry := newUTXOEntry(txOut)
|
||||
entry := newUTXOEntry(txOut, isCoinbase, blockHeight)
|
||||
|
||||
fus.utxoCollection[outPoint] = entry
|
||||
fus.add(outPoint, entry)
|
||||
}
|
||||
|
||||
return true
|
||||
@ -301,7 +327,7 @@ func (fus *fullUTXOSet) addTx(tx *wire.MsgTx) bool {
|
||||
func (fus *fullUTXOSet) containsInputs(tx *wire.MsgTx) bool {
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
if _, ok := fus.utxoCollection[outPoint]; !ok {
|
||||
if !fus.contains(outPoint) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -319,6 +345,11 @@ func (fus *fullUTXOSet) clone() utxoSet {
|
||||
return &fullUTXOSet{utxoCollection: fus.utxoCollection.clone()}
|
||||
}
|
||||
|
||||
func (fus *fullUTXOSet) getUTXOEntry(outPoint wire.OutPoint) (*UTXOEntry, bool) {
|
||||
utxoEntry, ok := fus.utxoCollection[outPoint]
|
||||
return utxoEntry, ok
|
||||
}
|
||||
|
||||
// diffUTXOSet represents a utxoSet with a base fullUTXOSet and a UTXODiff
|
||||
type diffUTXOSet struct {
|
||||
base *fullUTXOSet
|
||||
@ -359,30 +390,33 @@ func (dus *diffUTXOSet) withDiff(other *utxoDiff) (utxoSet, error) {
|
||||
}
|
||||
|
||||
// addTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context
|
||||
func (dus *diffUTXOSet) addTx(tx *wire.MsgTx) bool {
|
||||
if !dus.containsInputs(tx) {
|
||||
return false
|
||||
}
|
||||
func (dus *diffUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
|
||||
isCoinbase := IsCoinBaseTx(tx)
|
||||
if !isCoinbase {
|
||||
if !dus.containsInputs(tx) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
if _, ok := dus.utxoDiff.toAdd[outPoint]; ok {
|
||||
delete(dus.utxoDiff.toAdd, outPoint)
|
||||
} else {
|
||||
prevUTXOEntry := dus.base.utxoCollection[outPoint]
|
||||
dus.utxoDiff.toRemove[outPoint] = prevUTXOEntry
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
if dus.utxoDiff.toAdd.contains(outPoint) {
|
||||
dus.utxoDiff.toAdd.remove(outPoint)
|
||||
} else {
|
||||
prevUTXOEntry := dus.base.utxoCollection[outPoint]
|
||||
dus.utxoDiff.toRemove.add(outPoint, prevUTXOEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, txOut := range tx.TxOut {
|
||||
hash := tx.TxHash()
|
||||
outPoint := *wire.NewOutPoint(&hash, uint32(i))
|
||||
entry := newUTXOEntry(txOut)
|
||||
entry := newUTXOEntry(txOut, isCoinbase, blockHeight)
|
||||
|
||||
if _, ok := dus.utxoDiff.toRemove[outPoint]; ok {
|
||||
delete(dus.utxoDiff.toRemove, outPoint)
|
||||
if dus.utxoDiff.toRemove.contains(outPoint) {
|
||||
dus.utxoDiff.toRemove.remove(outPoint)
|
||||
} else {
|
||||
dus.utxoDiff.toAdd[outPoint] = entry
|
||||
dus.utxoDiff.toAdd.add(outPoint, entry)
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,9 +426,9 @@ func (dus *diffUTXOSet) addTx(tx *wire.MsgTx) bool {
|
||||
func (dus *diffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
_, isInBase := dus.base.utxoCollection[outPoint]
|
||||
_, isInDiffToAdd := dus.utxoDiff.toAdd[outPoint]
|
||||
_, isInDiffToRemove := dus.utxoDiff.toRemove[outPoint]
|
||||
isInBase := dus.base.contains(outPoint)
|
||||
isInDiffToAdd := dus.utxoDiff.toAdd.contains(outPoint)
|
||||
isInDiffToRemove := dus.utxoDiff.toRemove.contains(outPoint)
|
||||
if (!isInBase && !isInDiffToAdd) || isInDiffToRemove {
|
||||
return false
|
||||
}
|
||||
@ -406,11 +440,11 @@ func (dus *diffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
|
||||
// meldToBase updates the base fullUTXOSet with all changes in diff
|
||||
func (dus *diffUTXOSet) meldToBase() {
|
||||
for outPoint := range dus.utxoDiff.toRemove {
|
||||
delete(dus.base.utxoCollection, outPoint)
|
||||
dus.base.remove(outPoint)
|
||||
}
|
||||
|
||||
for outPoint, utxoEntry := range dus.utxoDiff.toAdd {
|
||||
dus.base.utxoCollection[outPoint] = utxoEntry
|
||||
dus.base.add(outPoint, utxoEntry)
|
||||
}
|
||||
|
||||
dus.utxoDiff = newUTXODiff()
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"reflect"
|
||||
"testing"
|
||||
"math"
|
||||
)
|
||||
|
||||
// TestUTXOCollection makes sure that utxoCollection cloning and string representations work as expected.
|
||||
@ -13,8 +14,8 @@ func TestUTXOCollection(t *testing.T) {
|
||||
hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")
|
||||
outPoint0 := *wire.NewOutPoint(hash0, 0)
|
||||
outPoint1 := *wire.NewOutPoint(hash1, 0)
|
||||
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10})
|
||||
utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20})
|
||||
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
|
||||
utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
|
||||
|
||||
// For each of the following test cases, we will:
|
||||
// .String() the given collection and compare it to expectedString
|
||||
@ -72,8 +73,8 @@ func TestUTXODiff(t *testing.T) {
|
||||
hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")
|
||||
outPoint0 := *wire.NewOutPoint(hash0, 0)
|
||||
outPoint1 := *wire.NewOutPoint(hash1, 0)
|
||||
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10})
|
||||
utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20})
|
||||
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
|
||||
utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
|
||||
diff := utxoDiff{
|
||||
toAdd: utxoCollection{outPoint0: utxoEntry0},
|
||||
toRemove: utxoCollection{outPoint1: utxoEntry1},
|
||||
@ -109,7 +110,7 @@ func TestUTXODiff(t *testing.T) {
|
||||
func TestUTXODiffRules(t *testing.T) {
|
||||
hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
outPoint0 := *wire.NewOutPoint(hash0, 0)
|
||||
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10})
|
||||
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
|
||||
|
||||
// For each of the following test cases, we will:
|
||||
// this.diffFrom(other) and compare it to expectedDiffFromResult
|
||||
@ -329,8 +330,8 @@ func TestFullUTXOSet(t *testing.T) {
|
||||
outPoint1 := *wire.NewOutPoint(hash1, 0)
|
||||
txOut0 := &wire.TxOut{PkScript: []byte{}, Value: 10}
|
||||
txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20}
|
||||
utxoEntry0 := newUTXOEntry(txOut0)
|
||||
utxoEntry1 := newUTXOEntry(txOut1)
|
||||
utxoEntry0 := newUTXOEntry(txOut0, true, 0)
|
||||
utxoEntry1 := newUTXOEntry(txOut1, false, 1)
|
||||
diff := &utxoDiff{
|
||||
toAdd: utxoCollection{outPoint0: utxoEntry0},
|
||||
toRemove: utxoCollection{outPoint1: utxoEntry1},
|
||||
@ -360,11 +361,11 @@ func TestFullUTXOSet(t *testing.T) {
|
||||
transaction0 := wire.NewMsgTx(1)
|
||||
transaction0.TxIn = []*wire.TxIn{txIn0}
|
||||
transaction0.TxOut = []*wire.TxOut{txOut0}
|
||||
if ok = emptySet.addTx(transaction0); ok {
|
||||
if ok = emptySet.addTx(transaction0, 0); ok {
|
||||
t.Errorf("addTx unexpectedly succeeded")
|
||||
}
|
||||
emptySet = &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}}
|
||||
if ok = emptySet.addTx(transaction0); !ok {
|
||||
if ok = emptySet.addTx(transaction0, 0); !ok {
|
||||
t.Errorf("addTx unexpectedly failed")
|
||||
}
|
||||
|
||||
@ -391,8 +392,8 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
outPoint1 := *wire.NewOutPoint(hash1, 0)
|
||||
txOut0 := &wire.TxOut{PkScript: []byte{}, Value: 10}
|
||||
txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20}
|
||||
utxoEntry0 := newUTXOEntry(txOut0)
|
||||
utxoEntry1 := newUTXOEntry(txOut1)
|
||||
utxoEntry0 := newUTXOEntry(txOut0, true, 0)
|
||||
utxoEntry1 := newUTXOEntry(txOut1, false, 1)
|
||||
diff := &utxoDiff{
|
||||
toAdd: utxoCollection{outPoint0: utxoEntry0},
|
||||
toRemove: utxoCollection{outPoint1: utxoEntry1},
|
||||
@ -629,11 +630,13 @@ func TestUTXOSetDiffRules(t *testing.T) {
|
||||
|
||||
// TestDiffUTXOSet_addTx makes sure that diffUTXOSet addTx works as expected
|
||||
func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
// transaction0 is coinbase. As such, it does not have any inputs
|
||||
// transaction0 is coinbase. As such, it has exactly one input with hash zero and MaxUInt32 index
|
||||
hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: *hash0, Index: math.MaxUint32}, Sequence: 0}
|
||||
txOut0 := &wire.TxOut{PkScript: []byte{0}, Value: 10}
|
||||
utxoEntry0 := newUTXOEntry(txOut0)
|
||||
utxoEntry0 := newUTXOEntry(txOut0, true, 0)
|
||||
transaction0 := wire.NewMsgTx(1)
|
||||
transaction0.TxIn = []*wire.TxIn{}
|
||||
transaction0.TxIn = []*wire.TxIn{txIn0}
|
||||
transaction0.TxOut = []*wire.TxOut{txOut0}
|
||||
|
||||
// transaction1 spends transaction0
|
||||
@ -641,7 +644,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
outPoint1 := *wire.NewOutPoint(&hash1, 0)
|
||||
txIn1 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: hash1, Index: 0}, Sequence: 0}
|
||||
txOut1 := &wire.TxOut{PkScript: []byte{1}, Value: 20}
|
||||
utxoEntry1 := newUTXOEntry(txOut1)
|
||||
utxoEntry1 := newUTXOEntry(txOut1, false, 1)
|
||||
transaction1 := wire.NewMsgTx(1)
|
||||
transaction1.TxIn = []*wire.TxIn{txIn1}
|
||||
transaction1.TxOut = []*wire.TxOut{txOut1}
|
||||
@ -651,7 +654,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
outPoint2 := *wire.NewOutPoint(&hash2, 0)
|
||||
txIn2 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: hash2, Index: 0}, Sequence: 0}
|
||||
txOut2 := &wire.TxOut{PkScript: []byte{2}, Value: 30}
|
||||
utxoEntry2 := newUTXOEntry(txOut2)
|
||||
utxoEntry2 := newUTXOEntry(txOut2, false, 2)
|
||||
transaction2 := wire.NewMsgTx(1)
|
||||
transaction2.TxIn = []*wire.TxIn{txIn2}
|
||||
transaction2.TxOut = []*wire.TxOut{txOut2}
|
||||
@ -661,18 +664,20 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
outPoint3 := *wire.NewOutPoint(&hash3, 0)
|
||||
|
||||
// For each of the following test cases, we will:
|
||||
// 1. startSet.addTx() all the transactions in toAdd, in order
|
||||
// 1. startSet.addTx() all the transactions in toAdd, in order, with the initial block height startHeight
|
||||
// 2. Compare the result set with expectedSet
|
||||
tests := []struct {
|
||||
name string
|
||||
startSet *diffUTXOSet
|
||||
startHeight int32
|
||||
toAdd []*wire.MsgTx
|
||||
expectedSet *diffUTXOSet
|
||||
}{
|
||||
{
|
||||
name: "add coinbase transaction to empty set",
|
||||
startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()),
|
||||
toAdd: []*wire.MsgTx{transaction0},
|
||||
name: "add coinbase transaction to empty set",
|
||||
startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()),
|
||||
startHeight: 0,
|
||||
toAdd: []*wire.MsgTx{transaction0},
|
||||
expectedSet: &diffUTXOSet{
|
||||
base: &fullUTXOSet{utxoCollection: utxoCollection{}},
|
||||
utxoDiff: &utxoDiff{
|
||||
@ -682,9 +687,10 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add regular transaction to empty set",
|
||||
startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()),
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
name: "add regular transaction to empty set",
|
||||
startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()),
|
||||
startHeight: 0,
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
expectedSet: &diffUTXOSet{
|
||||
base: &fullUTXOSet{utxoCollection: utxoCollection{}},
|
||||
utxoDiff: &utxoDiff{
|
||||
@ -702,7 +708,8 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
startHeight: 1,
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
expectedSet: &diffUTXOSet{
|
||||
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}},
|
||||
utxoDiff: &utxoDiff{
|
||||
@ -720,7 +727,8 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
startHeight: 1,
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
expectedSet: &diffUTXOSet{
|
||||
base: newFullUTXOSet(),
|
||||
utxoDiff: &utxoDiff{
|
||||
@ -738,7 +746,8 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
toRemove: utxoCollection{outPoint2: utxoEntry1},
|
||||
},
|
||||
},
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
startHeight: 1,
|
||||
toAdd: []*wire.MsgTx{transaction1},
|
||||
expectedSet: &diffUTXOSet{
|
||||
base: newFullUTXOSet(),
|
||||
utxoDiff: &utxoDiff{
|
||||
@ -756,7 +765,8 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
toAdd: []*wire.MsgTx{transaction1, transaction2},
|
||||
startHeight: 1,
|
||||
toAdd: []*wire.MsgTx{transaction1, transaction2},
|
||||
expectedSet: &diffUTXOSet{
|
||||
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}},
|
||||
utxoDiff: &utxoDiff{
|
||||
@ -770,9 +780,9 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
diffSet := test.startSet.clone()
|
||||
|
||||
// Apply all transactions, in order, to diffSet
|
||||
for _, transaction := range test.toAdd {
|
||||
diffSet.addTx(transaction)
|
||||
// Apply all transactions to diffSet, in order, with the initial block height startHeight
|
||||
for i, transaction := range test.toAdd {
|
||||
diffSet.addTx(transaction, test.startHeight + int32(i))
|
||||
}
|
||||
|
||||
// Make sure that the result diffSet equals to the expectedSet
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/database"
|
||||
"github.com/daglabs/btcd/txscript"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// txoFlags is a bitmask defining additional information and state for a
|
||||
@ -30,11 +30,11 @@ const (
|
||||
tfModified
|
||||
)
|
||||
|
||||
// UtxoEntry houses details about an individual transaction output in a utxo
|
||||
// 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 {
|
||||
type UTXOEntry struct {
|
||||
// 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
|
||||
@ -52,32 +52,26 @@ type UtxoEntry struct {
|
||||
packedFlags txoFlags
|
||||
}
|
||||
|
||||
// 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 output was contained in a coinbase
|
||||
// transaction.
|
||||
func (entry *UtxoEntry) IsCoinBase() bool {
|
||||
func (entry *UTXOEntry) IsCoinBase() bool {
|
||||
return entry.packedFlags&tfCoinBase == tfCoinBase
|
||||
}
|
||||
|
||||
// BlockHeight returns the height of the block containing the output.
|
||||
func (entry *UtxoEntry) BlockHeight() int32 {
|
||||
func (entry *UTXOEntry) BlockHeight() int32 {
|
||||
return entry.blockHeight
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (entry *UTXOEntry) IsSpent() bool {
|
||||
return entry.packedFlags&tfSpent == tfSpent
|
||||
}
|
||||
|
||||
// Spend marks the output as spent. Spending an output that is already spent
|
||||
// has no effect.
|
||||
func (entry *UtxoEntry) Spend() {
|
||||
func (entry *UTXOEntry) Spend() {
|
||||
// Nothing to do if the output is already spent.
|
||||
if entry.IsSpent() {
|
||||
return
|
||||
@ -88,22 +82,22 @@ func (entry *UtxoEntry) Spend() {
|
||||
}
|
||||
|
||||
// Amount returns the amount of the output.
|
||||
func (entry *UtxoEntry) Amount() int64 {
|
||||
func (entry *UTXOEntry) Amount() int64 {
|
||||
return entry.amount
|
||||
}
|
||||
|
||||
// PkScript returns the public key script for the output.
|
||||
func (entry *UtxoEntry) PkScript() []byte {
|
||||
func (entry *UTXOEntry) PkScript() []byte {
|
||||
return entry.pkScript
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the utxo entry.
|
||||
func (entry *UtxoEntry) Clone() *UtxoEntry {
|
||||
func (entry *UTXOEntry) Clone() *UTXOEntry {
|
||||
if entry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UtxoEntry{
|
||||
return &UTXOEntry{
|
||||
amount: entry.amount,
|
||||
pkScript: entry.pkScript,
|
||||
blockHeight: entry.blockHeight,
|
||||
@ -111,47 +105,29 @@ func (entry *UtxoEntry) Clone() *UtxoEntry {
|
||||
}
|
||||
}
|
||||
|
||||
// UtxoViewpoint represents a view into the set of unspent transaction outputs
|
||||
// UTXOView represents a view into the set of unspent transaction outputs
|
||||
// from a specific point of view in the chain. For example, it could be for
|
||||
// the end of the main chain, some point in the history of the main chain, or
|
||||
// down a side chain.
|
||||
//
|
||||
// The unspent outputs are needed by other transactions for things such as
|
||||
// script validation and double spend prevention.
|
||||
type UtxoViewpoint struct {
|
||||
entries map[wire.OutPoint]*UtxoEntry
|
||||
tips blockSet
|
||||
type UTXOView struct {
|
||||
entries map[wire.OutPoint]*UTXOEntry
|
||||
}
|
||||
|
||||
// Tips returns the hashes of the tips in the DAG the view currently
|
||||
// represents.
|
||||
func (view *UtxoViewpoint) Tips() blockSet {
|
||||
return view.tips
|
||||
}
|
||||
|
||||
// SetTips sets the hashes of the tips in the DAG the view currently
|
||||
// represents.
|
||||
func (view *UtxoViewpoint) SetTips(tips blockSet) {
|
||||
view.tips = tips
|
||||
}
|
||||
|
||||
// AddBlock removes all the parents of block from the tips and adds
|
||||
// the given block to the tips.
|
||||
func (view *UtxoViewpoint) AddBlock(block *blockNode) {
|
||||
updatedTips := view.tips.clone()
|
||||
for _, parent := range block.parents {
|
||||
updatedTips.remove(parent)
|
||||
// NewUTXOView returns a new empty unspent transaction output view.
|
||||
func NewUTXOView() *UTXOView {
|
||||
return &UTXOView{
|
||||
entries: make(map[wire.OutPoint]*UTXOEntry),
|
||||
}
|
||||
|
||||
updatedTips.add(block)
|
||||
view.tips = updatedTips
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (view *UTXOView) LookupEntry(outpoint wire.OutPoint) *UTXOEntry {
|
||||
return view.entries[outpoint]
|
||||
}
|
||||
|
||||
@ -159,7 +135,7 @@ func (view *UtxoViewpoint) LookupEntry(outpoint wire.OutPoint) *UtxoEntry {
|
||||
// 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) {
|
||||
func (view *UTXOView) addTxOut(outpoint wire.OutPoint, txOut *wire.TxOut, isCoinBase bool, blockHeight int32) {
|
||||
// Don't add provably unspendable outputs.
|
||||
if txscript.IsUnspendable(txOut.PkScript) {
|
||||
return
|
||||
@ -171,7 +147,7 @@ func (view *UtxoViewpoint) addTxOut(outpoint wire.OutPoint, txOut *wire.TxOut, i
|
||||
// is allowed so long as the previous transaction is fully spent.
|
||||
entry := view.LookupEntry(outpoint)
|
||||
if entry == nil {
|
||||
entry = new(UtxoEntry)
|
||||
entry = new(UTXOEntry)
|
||||
view.entries[outpoint] = entry
|
||||
}
|
||||
|
||||
@ -188,7 +164,7 @@ func (view *UtxoViewpoint) addTxOut(outpoint wire.OutPoint, txOut *wire.TxOut, i
|
||||
// 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 *util.Tx, txOutIdx uint32, blockHeight int32) {
|
||||
func (view *UTXOView) AddTxOut(tx *util.Tx, txOutIdx uint32, blockHeight int32) {
|
||||
// Can't add an output for an out of bounds index.
|
||||
if txOutIdx >= uint32(len(tx.MsgTx().TxOut)) {
|
||||
return
|
||||
@ -207,7 +183,7 @@ func (view *UtxoViewpoint) AddTxOut(tx *util.Tx, txOutIdx uint32, blockHeight in
|
||||
// unspendable to the view. When the view already has entries for any of the
|
||||
// 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 *util.Tx, blockHeight int32) {
|
||||
func (view *UTXOView) AddTxOuts(tx *util.Tx, blockHeight int32) {
|
||||
// Loop all of the transaction outputs and add those which are not
|
||||
// provably unspendable.
|
||||
isCoinBase := IsCoinBase(tx)
|
||||
@ -228,7 +204,7 @@ func (view *UtxoViewpoint) AddTxOuts(tx *util.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 *util.Tx, blockHeight int32, stxos *[]spentTxOut) error {
|
||||
func (view *UTXOView) connectTransaction(tx *util.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.
|
||||
@ -271,84 +247,26 @@ func (view *UtxoViewpoint) connectTransaction(tx *util.Tx, blockHeight int32, st
|
||||
return nil
|
||||
}
|
||||
|
||||
// connectTransactions updates the view by adding all new utxos created by all
|
||||
// of the transactions in the passed block, marking all utxos the transactions
|
||||
// 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 *blockNode, transactions []*util.Tx, stxos *[]spentTxOut) error {
|
||||
for _, tx := range transactions {
|
||||
err := view.connectTransaction(tx, block.height, stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update the tips for view to include this block since all of its
|
||||
// transactions have been connected.
|
||||
view.AddBlock(block)
|
||||
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 *daghash.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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (view *UTXOView) 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[wire.OutPoint]*UtxoEntry {
|
||||
func (view *UTXOView) 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 outpoint, entry := range view.entries {
|
||||
if entry == nil || (entry.isModified() && entry.IsSpent()) {
|
||||
delete(view.entries, outpoint)
|
||||
continue
|
||||
}
|
||||
|
||||
entry.packedFlags ^= tfModified
|
||||
}
|
||||
}
|
||||
|
||||
// fetchUtxosMain fetches unspent transaction output data about the provided
|
||||
// fetchUTXOs fetches unspent transaction output data about the provided
|
||||
// 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 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 {
|
||||
func (view *UTXOView) 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
|
||||
@ -363,7 +281,7 @@ func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.Out
|
||||
// to unnecessarily avoid attempting to reload it from the database.
|
||||
return db.View(func(dbTx database.Tx) error {
|
||||
for outpoint := range outpoints {
|
||||
entry, err := dbFetchUtxoEntry(dbTx, outpoint)
|
||||
entry, err := dbFetchUTXOEntry(dbTx, outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -375,36 +293,12 @@ func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.Out
|
||||
})
|
||||
}
|
||||
|
||||
// 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.
|
||||
neededSet := make(map[wire.OutPoint]struct{})
|
||||
for outpoint := range outpoints {
|
||||
// Already loaded into the current view.
|
||||
if _, ok := view.entries[outpoint]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
neededSet[outpoint] = struct{}{}
|
||||
}
|
||||
|
||||
// Request the input utxos from the database.
|
||||
return view.fetchUtxosMain(db, neededSet)
|
||||
}
|
||||
|
||||
// fetchInputUtxos loads the unspent transaction outputs for the inputs
|
||||
// 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 *util.Block) error {
|
||||
func (view *UTXOView) fetchInputUTXOs(db database.DB, block *util.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
|
||||
// block which are not yet in the chain.
|
||||
@ -451,23 +345,16 @@ func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *util.Block) er
|
||||
}
|
||||
|
||||
// Request the input utxos from the database.
|
||||
return view.fetchUtxosMain(db, neededSet)
|
||||
return view.fetchUTXOs(db, neededSet)
|
||||
}
|
||||
|
||||
// NewUtxoViewpoint returns a new empty unspent transaction output view.
|
||||
func NewUtxoViewpoint() *UtxoViewpoint {
|
||||
return &UtxoViewpoint{
|
||||
entries: make(map[wire.OutPoint]*UtxoEntry),
|
||||
}
|
||||
}
|
||||
|
||||
// FetchUtxoView loads unspent transaction outputs for the inputs 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 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 (dag *BlockDAG) FetchUtxoView(tx *util.Tx) (*UtxoViewpoint, error) {
|
||||
func (dag *BlockDAG) FetchUTXOView(tx *util.Tx) (*UTXOView, error) {
|
||||
// Create a set of needed outputs based on those referenced by the
|
||||
// inputs of the passed transaction and the outputs of the transaction
|
||||
// itself.
|
||||
@ -485,36 +372,9 @@ func (dag *BlockDAG) FetchUtxoView(tx *util.Tx) (*UtxoViewpoint, error) {
|
||||
|
||||
// Request the utxos from the point of view of the end of the main
|
||||
// chain.
|
||||
view := NewUtxoViewpoint()
|
||||
view := NewUTXOView()
|
||||
dag.dagLock.RLock()
|
||||
err := view.fetchUtxosMain(dag.db, neededSet)
|
||||
err := view.fetchUTXOs(dag.db, neededSet)
|
||||
dag.dagLock.RUnlock()
|
||||
return view, err
|
||||
}
|
||||
|
||||
// FetchUtxoEntry loads and returns the requested unspent transaction output
|
||||
// from the point of view of the end of the main chain.
|
||||
//
|
||||
// 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 (dag *BlockDAG) FetchUtxoEntry(outpoint wire.OutPoint) (*UtxoEntry, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
var entry *UtxoEntry
|
||||
err := dag.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
entry, err = dbFetchUtxoEntry(dbTx, outpoint)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
@ -53,16 +53,6 @@ var (
|
||||
// a package level variable to avoid the need to create a new instance
|
||||
// every time a check is needed.
|
||||
zeroHash daghash.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
|
||||
// avoid the need to create a new instance every time a check is needed.
|
||||
block91842Hash = newHashFromStr("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")
|
||||
|
||||
// block91880Hash is one of the two nodes which violate the rules
|
||||
// set forth in BIP0030. It is defined as a package level variable to
|
||||
// avoid the need to create a new instance every time a check is needed.
|
||||
block91880Hash = newHashFromStr("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")
|
||||
)
|
||||
|
||||
// isNullOutpoint determines whether or not a previous transaction output point
|
||||
@ -317,13 +307,6 @@ func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags Behavio
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckProofOfWork ensures the block header bits which indicate the target
|
||||
// difficulty is in min/max range and that the block hash is less than the
|
||||
// target difficulty as claimed.
|
||||
func CheckProofOfWork(block *util.Block, powLimit *big.Int) error {
|
||||
return checkProofOfWork(&block.MsgBlock().Header, powLimit, BFNone)
|
||||
}
|
||||
|
||||
// CountSigOps returns the number of signature operations for all transaction
|
||||
// input and output scripts in the provided transaction. This uses the
|
||||
// quicker, but imprecise, signature operation counting mechanism from
|
||||
@ -353,7 +336,7 @@ func CountSigOps(tx *util.Tx) int {
|
||||
// transactions which are of the pay-to-script-hash type. This uses the
|
||||
// precise, signature operation counting mechanism from the script engine which
|
||||
// requires access to the input transaction scripts.
|
||||
func CountP2SHSigOps(tx *util.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint) (int, error) {
|
||||
func CountP2SHSigOps(tx *util.Tx, isCoinBaseTx bool, utxoView *UTXOView) (int, error) {
|
||||
// Coinbase transactions have no interesting inputs.
|
||||
if isCoinBaseTx {
|
||||
return 0, nil
|
||||
@ -801,7 +784,7 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, sele
|
||||
// http://r6.ca/blog/20120206T005236Z.html.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for reads).
|
||||
func (dag *BlockDAG) ensureNoDuplicateTx(node *blockNode, block *util.Block, view *UtxoViewpoint) error {
|
||||
func (dag *BlockDAG) ensureNoDuplicateTx(node *blockNode, block *util.Block) error {
|
||||
// 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{})
|
||||
@ -812,16 +795,12 @@ func (dag *BlockDAG) ensureNoDuplicateTx(node *blockNode, block *util.Block, vie
|
||||
fetchSet[prevOut] = struct{}{}
|
||||
}
|
||||
}
|
||||
err := view.fetchUtxos(dag.db, fetchSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate transactions are only allowed if the previous transaction
|
||||
// is fully spent.
|
||||
for outpoint := range fetchSet {
|
||||
utxo := view.LookupEntry(outpoint)
|
||||
if utxo != nil && !utxo.IsSpent() {
|
||||
utxo, ok := dag.virtual.GetUTXOEntry(outpoint)
|
||||
if ok && !utxo.IsSpent() {
|
||||
str := fmt.Sprintf("tried to overwrite transaction %v "+
|
||||
"at block height %d that is not fully spent",
|
||||
outpoint.Hash, utxo.BlockHeight())
|
||||
@ -843,7 +822,7 @@ func (dag *BlockDAG) ensureNoDuplicateTx(node *blockNode, block *util.Block, vie
|
||||
//
|
||||
// NOTE: The transaction MUST have already been sanity checked with the
|
||||
// CheckTransactionSanity function prior to calling this function.
|
||||
func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoView *UtxoViewpoint, dagParams *dagconfig.Params) (int64, error) {
|
||||
func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoView *UTXOView, dagParams *dagconfig.Params) (int64, error) {
|
||||
// Coinbase transactions have no inputs.
|
||||
if IsCoinBase(tx) {
|
||||
return 0, nil
|
||||
@ -958,7 +937,7 @@ func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoView *UtxoViewpoint
|
||||
// with that node.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block, view *UtxoViewpoint, stxos *[]spentTxOut) error {
|
||||
func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block) 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
|
||||
@ -972,15 +951,7 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block, view
|
||||
return ruleError(ErrMissingTxOut, str)
|
||||
}
|
||||
|
||||
// Ensure the view is for the node being checked.
|
||||
parentHashes := block.MsgBlock().Header.PrevBlocks
|
||||
if !view.Tips().hashesEqual(parentHashes) {
|
||||
return AssertError(fmt.Sprintf("inconsistent view when "+
|
||||
"checking block connection: tips are %v instead "+
|
||||
"of expected %v", view.Tips(), parentHashes))
|
||||
}
|
||||
|
||||
err := dag.ensureNoDuplicateTx(node, block, view)
|
||||
err := dag.ensureNoDuplicateTx(node, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -990,7 +961,8 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block, view
|
||||
//
|
||||
// These utxo entries are needed for verification of things such as
|
||||
// transaction inputs, counting pay-to-script-hashes, and scripts.
|
||||
err = view.fetchInputUtxos(dag.db, block)
|
||||
view := NewUTXOView()
|
||||
err = view.fetchInputUTXOs(dag.db, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1036,6 +1008,8 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block, view
|
||||
// still relatively cheap as compared to running the scripts) checks
|
||||
// against all the inputs when the signature operations are out of
|
||||
// bounds.
|
||||
targetSpentOutputCount := countSpentOutputs(block)
|
||||
stxos := make([]spentTxOut, 0, targetSpentOutputCount)
|
||||
var totalFees int64
|
||||
for _, tx := range transactions {
|
||||
txFee, err := CheckTransactionInputs(tx, node.height, view,
|
||||
@ -1057,15 +1031,17 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block, view
|
||||
// provably unspendable as available utxos. Also, the passed
|
||||
// spent txos slice is updated to contain an entry for each
|
||||
// spent txout in the order each transaction spends them.
|
||||
err = view.connectTransaction(tx, node.height, stxos)
|
||||
err = view.connectTransaction(tx, node.height, &stxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update the tips for view to include this block since all of its
|
||||
// transactions have been connected.
|
||||
view.AddBlock(node)
|
||||
// Sanity check the correct number of stxos are provided.
|
||||
if len(stxos) != targetSpentOutputCount {
|
||||
return AssertError("connectBlock called with inconsistent " +
|
||||
"spent transaction out information")
|
||||
}
|
||||
|
||||
// The total output values of the coinbase transaction must not exceed
|
||||
// the expected subsidy value plus total transaction fees gained from
|
||||
@ -1135,13 +1111,19 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block, view
|
||||
}
|
||||
}
|
||||
|
||||
// Update the view tips to include this block since all of its
|
||||
// transactions have been connected.
|
||||
view.AddBlock(node)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// countSpentOutputs returns the number of utxos the passed block spends.
|
||||
func countSpentOutputs(block *util.Block) int {
|
||||
// Exclude the coinbase transaction since it can't spend anything.
|
||||
var numSpent int
|
||||
for _, tx := range block.Transactions()[1:] {
|
||||
numSpent += len(tx.MsgTx().TxIn)
|
||||
}
|
||||
return numSpent
|
||||
}
|
||||
|
||||
// CheckConnectBlockTemplate fully validates that connecting the passed block to
|
||||
// the main chain does not violate any consensus rules, aside from the proof of
|
||||
// work requirement. The block must connect to the current tip of the main chain.
|
||||
@ -1156,7 +1138,7 @@ func (dag *BlockDAG) CheckConnectBlockTemplate(block *util.Block) error {
|
||||
|
||||
// This only checks whether the block can be connected to the tip of the
|
||||
// current chain.
|
||||
tips := dag.virtual.Tips()
|
||||
tips := dag.virtual.tips()
|
||||
header := block.MsgBlock().Header
|
||||
prevHashes := header.PrevBlocks
|
||||
if !tips.hashesEqual(prevHashes) {
|
||||
@ -1180,10 +1162,6 @@ func (dag *BlockDAG) CheckConnectBlockTemplate(block *util.Block) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Leave the spent txouts entry nil in the state since the information
|
||||
// is not needed and thus extra work can be avoided.
|
||||
view := NewUtxoViewpoint()
|
||||
view.SetTips(tips)
|
||||
newNode := newBlockNode(&header, dag.virtual.Tips(), dag.dagParams.K)
|
||||
return dag.checkConnectBlock(newNode, block, view, nil)
|
||||
newNode := newBlockNode(&header, dag.virtual.tips(), dag.dagParams.K)
|
||||
return dag.checkConnectBlock(newNode, block)
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ func TestSequenceLocksActive(t *testing.T) {
|
||||
// ensure it fails.
|
||||
func TestCheckConnectBlockTemplate(t *testing.T) {
|
||||
// Create a new database and chain instance to run tests against.
|
||||
chain, teardownFunc, err := chainSetup("checkconnectblocktemplate",
|
||||
chain, teardownFunc, err := dagSetup("checkconnectblocktemplate",
|
||||
&dagconfig.MainNetParams)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup chain instance: %v", err)
|
||||
|
@ -6,31 +6,44 @@ package blockdag
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// virtualBlock is a virtual block whose parents are the tips of the DAG.
|
||||
type virtualBlock struct {
|
||||
// VirtualBlock is a virtual block whose parents are the tips of the DAG.
|
||||
type VirtualBlock struct {
|
||||
mtx sync.Mutex
|
||||
phantomK uint32
|
||||
utxoSet *fullUTXOSet
|
||||
blockNode
|
||||
}
|
||||
|
||||
// newVirtualBlock creates and returns a new virtualBlock.
|
||||
func newVirtualBlock(tips blockSet, phantomK uint32) *virtualBlock {
|
||||
// newVirtualBlock creates and returns a new VirtualBlock.
|
||||
func newVirtualBlock(tips blockSet, phantomK uint32) *VirtualBlock {
|
||||
// The mutex is intentionally not held since this is a constructor.
|
||||
var virtual virtualBlock
|
||||
var virtual VirtualBlock
|
||||
virtual.phantomK = phantomK
|
||||
virtual.utxoSet = newFullUTXOSet()
|
||||
virtual.setTips(tips)
|
||||
|
||||
return &virtual
|
||||
}
|
||||
|
||||
// clone creates and returns a clone of the virtual block.
|
||||
func (v *VirtualBlock) clone() *VirtualBlock {
|
||||
return &VirtualBlock{
|
||||
phantomK: v.phantomK,
|
||||
utxoSet: v.utxoSet.clone().(*fullUTXOSet),
|
||||
blockNode: v.blockNode,
|
||||
}
|
||||
}
|
||||
|
||||
// setTips replaces the tips of the virtual block with the blocks in the
|
||||
// given blockSet. This only differs from the exported version in that it
|
||||
// is up to the caller to ensure the lock is held.
|
||||
//
|
||||
// This function MUST be called with the view mutex locked (for writes).
|
||||
func (v *virtualBlock) setTips(tips blockSet) {
|
||||
func (v *VirtualBlock) setTips(tips blockSet) {
|
||||
v.blockNode = *newBlockNode(nil, tips, v.phantomK)
|
||||
}
|
||||
|
||||
@ -38,7 +51,7 @@ func (v *virtualBlock) setTips(tips blockSet) {
|
||||
// given blockSet.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (v *virtualBlock) SetTips(tips blockSet) {
|
||||
func (v *VirtualBlock) SetTips(tips blockSet) {
|
||||
v.mtx.Lock()
|
||||
v.setTips(tips)
|
||||
v.mtx.Unlock()
|
||||
@ -50,8 +63,8 @@ func (v *virtualBlock) SetTips(tips blockSet) {
|
||||
// is up to the caller to ensure the lock is held.
|
||||
//
|
||||
// This function MUST be called with the view mutex locked (for writes).
|
||||
func (v *virtualBlock) addTip(newTip *blockNode) {
|
||||
updatedTips := v.Tips().clone()
|
||||
func (v *VirtualBlock) addTip(newTip *blockNode) {
|
||||
updatedTips := v.tips().clone()
|
||||
for _, parent := range newTip.parents {
|
||||
updatedTips.remove(parent)
|
||||
}
|
||||
@ -65,17 +78,17 @@ func (v *virtualBlock) addTip(newTip *blockNode) {
|
||||
// from the set.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (v *virtualBlock) AddTip(newTip *blockNode) {
|
||||
func (v *VirtualBlock) AddTip(newTip *blockNode) {
|
||||
v.mtx.Lock()
|
||||
v.addTip(newTip)
|
||||
v.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Tips returns the current tip block nodes for the DAG. It will return
|
||||
// tips returns the current tip block nodes for the DAG. It will return
|
||||
// an empty blockSet if there is no tip.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (v *virtualBlock) Tips() blockSet {
|
||||
func (v *VirtualBlock) tips() blockSet {
|
||||
return v.parents
|
||||
}
|
||||
|
||||
@ -83,6 +96,30 @@ func (v *virtualBlock) Tips() blockSet {
|
||||
// It will return nil if there is no tip.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (v *virtualBlock) SelectedTip() *blockNode {
|
||||
func (v *VirtualBlock) SelectedTip() *blockNode {
|
||||
return v.selectedParent
|
||||
}
|
||||
|
||||
// SelectedTipHeight returns the height of the selected tip of the virtual block.
|
||||
func (v *VirtualBlock) SelectedTipHeight() int32 {
|
||||
return v.SelectedTip().height
|
||||
}
|
||||
|
||||
// TipHashes returns the hashes of the tips of the virtual block.
|
||||
func (v *VirtualBlock) TipHashes() []daghash.Hash {
|
||||
return v.tips().hashes()
|
||||
}
|
||||
|
||||
// SelectedTipHash returns the hash of the selected tip of the virtual block.
|
||||
func (v *VirtualBlock) SelectedTipHash() daghash.Hash {
|
||||
return v.SelectedTip().hash;
|
||||
}
|
||||
|
||||
// GetUTXOEntry returns the requested unspent transaction output. The returned
|
||||
// instance must be treated as immutable since it is shared by all callers.
|
||||
//
|
||||
// This function is safe for concurrent access. However, the returned entry (if
|
||||
// any) is NOT.
|
||||
func (v *VirtualBlock) GetUTXOEntry(outPoint wire.OutPoint) (*UTXOEntry, bool) {
|
||||
return v.utxoSet.getUTXOEntry(outPoint)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
)
|
||||
|
||||
// TestVirtualBlock ensures that virtualBlock works as expected.
|
||||
// TestVirtualBlock ensures that VirtualBlock works as expected.
|
||||
func TestVirtualBlock(t *testing.T) {
|
||||
// For the purposes of this test, we'll create blockNodes whose hashes are a
|
||||
// series of numbers from 0 to n.
|
||||
@ -38,7 +38,7 @@ func TestVirtualBlock(t *testing.T) {
|
||||
node5 := buildNode(setFromSlice(node3, node4))
|
||||
node6 := buildNode(setFromSlice(node3, node4))
|
||||
|
||||
// Given an empty virtualBlock, each of the following test cases will:
|
||||
// Given an empty VirtualBlock, each of the following test cases will:
|
||||
// Set its tips to tipsToSet
|
||||
// Add to it all the tips in tipsToAdd, one after the other
|
||||
// Call .Tips() on it and compare the result to expectedTips
|
||||
@ -81,7 +81,7 @@ func TestVirtualBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create an empty virtualBlock
|
||||
// Create an empty VirtualBlock
|
||||
virtual := newVirtualBlock(nil, phantomK)
|
||||
|
||||
// Set the tips. This will be the initial state
|
||||
@ -93,7 +93,7 @@ func TestVirtualBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
// Ensure that the virtual block's tips are now equal to expectedTips
|
||||
resultTips := virtual.Tips()
|
||||
resultTips := virtual.tips()
|
||||
if !reflect.DeepEqual(resultTips, test.expectedTips) {
|
||||
t.Errorf("unexpected tips in test \"%s\". "+
|
||||
"Expected: %v, got: %v.", test.name, test.expectedTips, resultTips)
|
||||
|
@ -162,11 +162,12 @@ func main() {
|
||||
|
||||
// Get the latest block hash and height from the database and report
|
||||
// status.
|
||||
dagState := dag.GetDAGState()
|
||||
fmt.Printf("Block database loaded with block height %d\n", dagState.SelectedTip.Height)
|
||||
virtualBlock := dag.VirtualBlock()
|
||||
fmt.Printf("Block database loaded with block height %d\n", virtualBlock.SelectedTipHeight())
|
||||
|
||||
// Find checkpoint candidates.
|
||||
candidates, err := findCandidates(dag, &dagState.SelectedTip.Hash)
|
||||
selectedTipHash := virtualBlock.SelectedTipHash()
|
||||
candidates, err := findCandidates(dag, &selectedTipHash)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Unable to identify candidates:", err)
|
||||
return
|
||||
|
@ -55,9 +55,9 @@ type Config struct {
|
||||
// associated with.
|
||||
ChainParams *dagconfig.Params
|
||||
|
||||
// FetchUtxoView defines the function to use to fetch unspent
|
||||
// FetchUTXOView defines the function to use to fetch unspent
|
||||
// transaction output information.
|
||||
FetchUtxoView func(*util.Tx) (*blockdag.UtxoViewpoint, error)
|
||||
FetchUtxoView func(*util.Tx) (*blockdag.UTXOView, error)
|
||||
|
||||
// BestHeight defines the function to use to access the block height of
|
||||
// the current best chain.
|
||||
@ -71,7 +71,7 @@ type Config struct {
|
||||
// CalcSequenceLock defines the function to use in order to generate
|
||||
// the current sequence lock for the given transaction using the passed
|
||||
// utxo view.
|
||||
CalcSequenceLock func(*util.Tx, *blockdag.UtxoViewpoint) (*blockdag.SequenceLock, error)
|
||||
CalcSequenceLock func(*util.Tx, *blockdag.UTXOView) (*blockdag.SequenceLock, error)
|
||||
|
||||
// IsDeploymentActive returns true if the target deploymentID is
|
||||
// active, and false otherwise. The mempool uses this function to gauge
|
||||
@ -515,7 +515,7 @@ func (mp *TxPool) RemoveDoubleSpends(tx *util.Tx) {
|
||||
// helper for maybeAcceptTransaction.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *TxPool) addTransaction(utxoView *blockdag.UtxoViewpoint, tx *util.Tx, height int32, fee int64) *TxDesc {
|
||||
func (mp *TxPool) addTransaction(utxoView *blockdag.UTXOView, tx *util.Tx, height int32, fee int64) *TxDesc {
|
||||
// Add the transaction to the pool and mark the referenced outpoints
|
||||
// as spent by the pool.
|
||||
txD := &TxDesc{
|
||||
@ -585,7 +585,7 @@ func (mp *TxPool) CheckSpend(op wire.OutPoint) *util.Tx {
|
||||
// transaction pool.
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for reads).
|
||||
func (mp *TxPool) fetchInputUtxos(tx *util.Tx) (*blockdag.UtxoViewpoint, error) {
|
||||
func (mp *TxPool) fetchInputUtxos(tx *util.Tx) (*blockdag.UTXOView, error) {
|
||||
utxoView, err := mp.cfg.FetchUtxoView(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -26,18 +26,18 @@ import (
|
||||
// transactions to appear as though they are spending completely valid utxos.
|
||||
type fakeChain struct {
|
||||
sync.RWMutex
|
||||
utxos *blockdag.UtxoViewpoint
|
||||
utxos *blockdag.UTXOView
|
||||
currentHeight int32
|
||||
medianTimePast time.Time
|
||||
}
|
||||
|
||||
// FetchUtxoView loads utxo details about the inputs referenced by the passed
|
||||
// FetchUTXOView loads utxo details about the inputs referenced by the passed
|
||||
// transaction from the point of view of the fake chain. 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 (s *fakeChain) FetchUtxoView(tx *util.Tx) (*blockdag.UtxoViewpoint, error) {
|
||||
func (s *fakeChain) FetchUtxoView(tx *util.Tx) (*blockdag.UTXOView, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
@ -45,7 +45,7 @@ func (s *fakeChain) FetchUtxoView(tx *util.Tx) (*blockdag.UtxoViewpoint, error)
|
||||
// do not affect the fake chain's view.
|
||||
|
||||
// Add an entry for the tx itself to the new view.
|
||||
viewpoint := blockdag.NewUtxoViewpoint()
|
||||
viewpoint := blockdag.NewUTXOView()
|
||||
prevOut := wire.OutPoint{Hash: *tx.Hash()}
|
||||
for txOutIdx := range tx.MsgTx().TxOut {
|
||||
prevOut.Index = uint32(txOutIdx)
|
||||
@ -98,7 +98,7 @@ func (s *fakeChain) SetMedianTimePast(mtp time.Time) {
|
||||
// CalcSequenceLock returns the current sequence lock for the passed
|
||||
// transaction associated with the fake chain instance.
|
||||
func (s *fakeChain) CalcSequenceLock(tx *util.Tx,
|
||||
view *blockdag.UtxoViewpoint) (*blockdag.SequenceLock, error) {
|
||||
view *blockdag.UTXOView) (*blockdag.SequenceLock, error) {
|
||||
|
||||
return &blockdag.SequenceLock{
|
||||
Seconds: -1,
|
||||
@ -299,7 +299,7 @@ func newPoolHarness(chainParams *dagconfig.Params) (*poolHarness, []spendableOut
|
||||
}
|
||||
|
||||
// Create a new fake chain and harness bound to it.
|
||||
chain := &fakeChain{utxos: blockdag.NewUtxoViewpoint()}
|
||||
chain := &fakeChain{utxos: blockdag.NewUTXOView()}
|
||||
harness := poolHarness{
|
||||
signKey: signKey,
|
||||
payAddr: payAddr,
|
||||
|
@ -85,7 +85,7 @@ func calcMinRequiredTxRelayFee(serializedSize int64, minRelayTxFee util.Amount)
|
||||
// context of this function is one whose referenced public key script is of a
|
||||
// standard form and, for pay-to-script-hash, does not have more than
|
||||
// maxStandardP2SHSigOps signature operations.
|
||||
func checkInputsStandard(tx *util.Tx, utxoView *blockdag.UtxoViewpoint) error {
|
||||
func checkInputsStandard(tx *util.Tx, utxoView *blockdag.UTXOView) error {
|
||||
// NOTE: The reference implementation also does a coinbase check here,
|
||||
// but coinbases have already been rejected prior to calling this
|
||||
// function so no need to recheck.
|
||||
|
@ -162,7 +162,7 @@ func (m *CPUMiner) submitBlock(block *util.Block) bool {
|
||||
// a new block, but the check only happens periodically, so it is
|
||||
// possible a block was found and submitted in between.
|
||||
msgBlock := block.MsgBlock()
|
||||
if !daghash.AreEqual(msgBlock.Header.PrevBlocks, m.g.GetDAGState().TipHashes) {
|
||||
if !daghash.AreEqual(msgBlock.Header.PrevBlocks, m.g.VirtualBlock().TipHashes()) {
|
||||
log.Debugf("Block submitted via CPU miner with previous "+
|
||||
"blocks %s is stale", msgBlock.Header.PrevBlocks)
|
||||
return false
|
||||
@ -247,8 +247,8 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
|
||||
hashesCompleted = 0
|
||||
|
||||
// The current block is stale if the DAG has changed.
|
||||
dagState := m.g.GetDAGState()
|
||||
if !daghash.AreEqual(header.PrevBlocks, dagState.TipHashes) {
|
||||
virtualBlock := m.g.VirtualBlock()
|
||||
if !daghash.AreEqual(header.PrevBlocks, virtualBlock.TipHashes()) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -326,7 +326,7 @@ out:
|
||||
// this would otherwise end up building a new block template on
|
||||
// a block that is in the process of becoming stale.
|
||||
m.submitBlockLock.Lock()
|
||||
curHeight := m.g.GetDAGState().SelectedTip.Height
|
||||
curHeight := m.g.VirtualBlock().SelectedTipHeight()
|
||||
if curHeight != 0 && !m.cfg.IsCurrent() {
|
||||
m.submitBlockLock.Unlock()
|
||||
time.Sleep(time.Second)
|
||||
@ -585,7 +585,7 @@ func (m *CPUMiner) GenerateNBlocks(n uint32) ([]*daghash.Hash, error) {
|
||||
// be changing and this would otherwise end up building a new block
|
||||
// template on a block that is in the process of becoming stale.
|
||||
m.submitBlockLock.Lock()
|
||||
curHeight := m.g.GetDAGState().SelectedTip.Height
|
||||
curHeight := m.g.VirtualBlock().SelectedTipHeight()
|
||||
|
||||
// Choose a payment address at random.
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
@ -216,7 +216,7 @@ type BlockTemplate struct {
|
||||
// viewA will contain all of its original entries plus all of the entries
|
||||
// in viewB. It will replace any entries in viewB which also exist in viewA
|
||||
// if the entry in viewA is spent.
|
||||
func mergeUtxoView(viewA *blockdag.UtxoViewpoint, viewB *blockdag.UtxoViewpoint) {
|
||||
func mergeUtxoView(viewA *blockdag.UTXOView, viewB *blockdag.UTXOView) {
|
||||
viewAEntries := viewA.Entries()
|
||||
for outpoint, entryB := range viewB.Entries() {
|
||||
if entryA, exists := viewAEntries[outpoint]; !exists ||
|
||||
@ -282,7 +282,7 @@ func createCoinbaseTx(params *dagconfig.Params, coinbaseScript []byte, nextBlock
|
||||
// spendTransaction updates the passed view by marking the inputs to the passed
|
||||
// transaction as spent. It also adds all outputs in the passed transaction
|
||||
// which are not provably unspendable as available unspent transaction outputs.
|
||||
func spendTransaction(utxoView *blockdag.UtxoViewpoint, tx *util.Tx, height int32) error {
|
||||
func spendTransaction(utxoView *blockdag.UTXOView, tx *util.Tx, height int32) error {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
entry := utxoView.LookupEntry(txIn.PreviousOutPoint)
|
||||
if entry != nil {
|
||||
@ -311,14 +311,14 @@ func logSkippedDeps(tx *util.Tx, deps map[daghash.Hash]*txPrioItem) {
|
||||
// on the end of the provided best chain. In particular, it is one second after
|
||||
// the median timestamp of the last several blocks per the chain consensus
|
||||
// rules.
|
||||
func MinimumMedianTime(dagState *blockdag.DAGState) time.Time {
|
||||
return dagState.SelectedTip.MedianTime.Add(time.Second)
|
||||
func MinimumMedianTime(dagMedianTime time.Time) time.Time {
|
||||
return dagMedianTime.Add(time.Second)
|
||||
}
|
||||
|
||||
// medianAdjustedTime returns the current time adjusted to ensure it is at least
|
||||
// one second after the median timestamp of the last several blocks per the
|
||||
// chain consensus rules.
|
||||
func medianAdjustedTime(chainState *blockdag.DAGState, timeSource blockdag.MedianTimeSource) time.Time {
|
||||
func medianAdjustedTime(dagMedianTime time.Time, timeSource blockdag.MedianTimeSource) time.Time {
|
||||
// The timestamp for the block must not be before the median timestamp
|
||||
// of the last several blocks. Thus, choose the maximum between the
|
||||
// current time and one second after the past median time. The current
|
||||
@ -326,7 +326,7 @@ func medianAdjustedTime(chainState *blockdag.DAGState, timeSource blockdag.Media
|
||||
// block timestamp does not supported a precision greater than one
|
||||
// second.
|
||||
newTimestamp := timeSource.AdjustedTime()
|
||||
minTimestamp := MinimumMedianTime(chainState)
|
||||
minTimestamp := MinimumMedianTime(dagMedianTime)
|
||||
if newTimestamp.Before(minTimestamp) {
|
||||
newTimestamp = minTimestamp
|
||||
}
|
||||
@ -432,8 +432,8 @@ func NewBlkTmplGenerator(policy *Policy, params *dagconfig.Params,
|
||||
// ----------------------------------- --
|
||||
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTemplate, error) {
|
||||
// Extend the most recently known best block.
|
||||
dagState := g.dag.GetDAGState()
|
||||
nextBlockHeight := dagState.SelectedTip.Height + 1
|
||||
virtualBlock := g.dag.VirtualBlock()
|
||||
nextBlockHeight := virtualBlock.SelectedTipHeight() + 1
|
||||
|
||||
// Create a standard coinbase transaction paying to the provided
|
||||
// address. NOTE: The coinbase value will be updated to include the
|
||||
@ -471,7 +471,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
||||
// avoided.
|
||||
blockTxns := make([]*util.Tx, 0, len(sourceTxns))
|
||||
blockTxns = append(blockTxns, coinbaseTx)
|
||||
blockUtxos := blockdag.NewUtxoViewpoint()
|
||||
blockUtxos := blockdag.NewUTXOView()
|
||||
|
||||
// dependers is used to track transactions which depend on another
|
||||
// transaction in the source pool. This, in conjunction with the
|
||||
@ -515,7 +515,7 @@ mempoolLoop:
|
||||
// mempool since a transaction which depends on other
|
||||
// transactions in the mempool must come after those
|
||||
// dependencies in the final generated block.
|
||||
utxos, err := g.dag.FetchUtxoView(tx)
|
||||
utxos, err := g.dag.FetchUTXOView(tx)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to fetch utxo view for tx %s: %v",
|
||||
tx.Hash(), err)
|
||||
@ -746,7 +746,7 @@ mempoolLoop:
|
||||
// Calculate the required difficulty for the block. The timestamp
|
||||
// is potentially adjusted to ensure it comes after the median time of
|
||||
// the last several blocks per the chain consensus rules.
|
||||
ts := medianAdjustedTime(dagState, g.timeSource)
|
||||
ts := medianAdjustedTime(virtualBlock.SelectedTip().CalcPastMedianTime(), g.timeSource)
|
||||
reqDifficulty, err := g.dag.CalcNextRequiredDifficulty(ts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -764,7 +764,7 @@ mempoolLoop:
|
||||
var msgBlock wire.MsgBlock
|
||||
msgBlock.Header = wire.BlockHeader{
|
||||
Version: nextBlockVersion,
|
||||
PrevBlocks: dagState.TipHashes,
|
||||
PrevBlocks: virtualBlock.TipHashes(),
|
||||
MerkleRoot: *merkles[len(merkles)-1],
|
||||
Timestamp: ts,
|
||||
Bits: reqDifficulty,
|
||||
@ -808,7 +808,8 @@ func (g *BlkTmplGenerator) UpdateBlockTime(msgBlock *wire.MsgBlock) error {
|
||||
// The new timestamp is potentially adjusted to ensure it comes after
|
||||
// the median time of the last several blocks per the chain consensus
|
||||
// rules.
|
||||
newTime := medianAdjustedTime(g.dag.GetDAGState(), g.timeSource)
|
||||
dagMedianTime := g.dag.VirtualBlock().CalcPastMedianTime()
|
||||
newTime := medianAdjustedTime(dagMedianTime, g.timeSource)
|
||||
msgBlock.Header.Timestamp = newTime
|
||||
|
||||
// Recalculate the difficulty if running on a network that requires it.
|
||||
@ -851,14 +852,13 @@ func (g *BlkTmplGenerator) UpdateExtraNonce(msgBlock *wire.MsgBlock, blockHeight
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDAGState returns information about the current state
|
||||
// as of the current point in time using the DAG instance
|
||||
// associated with the block template generator. The returned state must be
|
||||
// treated as immutable since it is shared by all callers.
|
||||
// VirtualBlock returns the DAG's virtual block in the current point in time.
|
||||
// The returned instance must be treated as immutable since it is shared by all
|
||||
// callers.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (g *BlkTmplGenerator) GetDAGState() *blockdag.DAGState {
|
||||
return g.dag.GetDAGState()
|
||||
func (g *BlkTmplGenerator) VirtualBlock() *blockdag.VirtualBlock {
|
||||
return g.dag.VirtualBlock()
|
||||
}
|
||||
|
||||
// TxSource returns the associated transaction source.
|
||||
|
@ -54,7 +54,7 @@ func minInt(a, b int) int {
|
||||
// age is the sum of this value for each txin. Any inputs to the transaction
|
||||
// which are currently in the mempool and hence not mined into a block yet,
|
||||
// contribute no additional input age to the transaction.
|
||||
func calcInputValueAge(tx *wire.MsgTx, utxoView *blockdag.UtxoViewpoint, nextBlockHeight int32) float64 {
|
||||
func calcInputValueAge(tx *wire.MsgTx, utxoView *blockdag.UTXOView, nextBlockHeight int32) float64 {
|
||||
var totalInputAge float64
|
||||
for _, txIn := range tx.TxIn {
|
||||
// Don't attempt to accumulate the total input age if the
|
||||
@ -86,7 +86,7 @@ func calcInputValueAge(tx *wire.MsgTx, utxoView *blockdag.UtxoViewpoint, nextBlo
|
||||
// of each of its input values multiplied by their age (# of confirmations).
|
||||
// Thus, the final formula for the priority is:
|
||||
// sum(inputValue * inputAge) / adjustedTxSize
|
||||
func CalcPriority(tx *wire.MsgTx, utxoView *blockdag.UtxoViewpoint, nextBlockHeight int32) float64 {
|
||||
func CalcPriority(tx *wire.MsgTx, utxoView *blockdag.UTXOView, nextBlockHeight int32) float64 {
|
||||
// In order to encourage spending multiple old unspent transaction
|
||||
// outputs thereby reducing the total set, don't count the constant
|
||||
// overhead for each input as well as enough bytes of the signature
|
||||
|
@ -46,12 +46,12 @@ func hexToBytes(s string) []byte {
|
||||
// provided source transactions as if there were available at the respective
|
||||
// block height specified in the heights slice. The length of the source txns
|
||||
// and source tx heights must match or it will panic.
|
||||
func newUtxoViewpoint(sourceTxns []*wire.MsgTx, sourceTxHeights []int32) *blockdag.UtxoViewpoint {
|
||||
func newUtxoViewpoint(sourceTxns []*wire.MsgTx, sourceTxHeights []int32) *blockdag.UTXOView {
|
||||
if len(sourceTxns) != len(sourceTxHeights) {
|
||||
panic("each transaction must have its block height specified")
|
||||
}
|
||||
|
||||
view := blockdag.NewUtxoViewpoint()
|
||||
view := blockdag.NewUTXOView()
|
||||
for i, tx := range sourceTxns {
|
||||
view.AddTxOuts(util.NewTx(tx), sourceTxHeights[i])
|
||||
}
|
||||
@ -128,11 +128,11 @@ func TestCalcPriority(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string // test description
|
||||
tx *wire.MsgTx // tx to calc priority for
|
||||
utxoView *blockdag.UtxoViewpoint // inputs to tx
|
||||
nextHeight int32 // height for priority calc
|
||||
want float64 // expected priority
|
||||
name string // test description
|
||||
tx *wire.MsgTx // tx to calc priority for
|
||||
utxoView *blockdag.UTXOView // inputs to tx
|
||||
nextHeight int32 // height for priority calc
|
||||
want float64 // expected priority
|
||||
}{
|
||||
{
|
||||
name: "one height 7 input, prio tx height 169",
|
||||
|
@ -17,8 +17,8 @@ import (
|
||||
"github.com/daglabs/btcd/database"
|
||||
"github.com/daglabs/btcd/mempool"
|
||||
peerpkg "github.com/daglabs/btcd/peer"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -226,7 +226,7 @@ func (sm *SyncManager) startSync() {
|
||||
return
|
||||
}
|
||||
|
||||
dagState := sm.dag.GetDAGState()
|
||||
virtualBlock := sm.dag.VirtualBlock()
|
||||
var bestPeer *peerpkg.Peer
|
||||
for peer, state := range sm.peerStates {
|
||||
if !state.syncCandidate {
|
||||
@ -239,7 +239,7 @@ func (sm *SyncManager) startSync() {
|
||||
// doesn't have a later block when it's equal, it will likely
|
||||
// have one soon so it is a reasonable choice. It also allows
|
||||
// the case where both are at 0 such as during regression test.
|
||||
if peer.LastBlock() < dagState.SelectedTip.Height {
|
||||
if peer.LastBlock() < virtualBlock.SelectedTipHeight() {
|
||||
state.syncCandidate = false
|
||||
continue
|
||||
}
|
||||
@ -284,13 +284,13 @@ func (sm *SyncManager) startSync() {
|
||||
// not support the headers-first approach so do normal block
|
||||
// downloads when in regression test mode.
|
||||
if sm.nextCheckpoint != nil &&
|
||||
dagState.SelectedTip.Height < sm.nextCheckpoint.Height &&
|
||||
virtualBlock.SelectedTipHeight() < sm.nextCheckpoint.Height &&
|
||||
sm.chainParams != &dagconfig.RegressionNetParams {
|
||||
|
||||
bestPeer.PushGetHeadersMsg(locator, sm.nextCheckpoint.Hash)
|
||||
sm.headersFirstMode = true
|
||||
log.Infof("Downloading headers for blocks %d to "+
|
||||
"%d from peer %s", dagState.SelectedTip.Height+1,
|
||||
"%d from peer %s", virtualBlock.SelectedTipHeight()+1,
|
||||
sm.nextCheckpoint.Height, bestPeer.Addr())
|
||||
} else {
|
||||
bestPeer.PushGetBlocksMsg(locator, &zeroHash)
|
||||
@ -392,8 +392,9 @@ func (sm *SyncManager) handleDonePeerMsg(peer *peerpkg.Peer) {
|
||||
if sm.syncPeer == peer {
|
||||
sm.syncPeer = nil
|
||||
if sm.headersFirstMode {
|
||||
dagState := sm.dag.GetDAGState()
|
||||
sm.resetHeaderState(&dagState.SelectedTip.Hash, dagState.SelectedTip.Height)
|
||||
virtualBlock := sm.dag.VirtualBlock()
|
||||
selectedTipHash := virtualBlock.SelectedTipHash()
|
||||
sm.resetHeaderState(&selectedTipHash, virtualBlock.SelectedTipHeight())
|
||||
}
|
||||
sm.startSync()
|
||||
}
|
||||
@ -482,7 +483,7 @@ func (sm *SyncManager) current() bool {
|
||||
|
||||
// No matter what chain thinks, if we are below the block we are syncing
|
||||
// to we are not current.
|
||||
if sm.dag.GetDAGState().SelectedTip.Height < sm.syncPeer.LastBlock() {
|
||||
if sm.dag.VirtualBlock().SelectedTipHeight() < sm.syncPeer.LastBlock() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -615,9 +616,10 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) {
|
||||
|
||||
// Update this peer's latest block height, for future
|
||||
// potential sync node candidacy.
|
||||
dagState := sm.dag.GetDAGState()
|
||||
heightUpdate = dagState.SelectedTip.Height
|
||||
blkHashUpdate = &dagState.SelectedTip.Hash
|
||||
virtualBlock := sm.dag.VirtualBlock()
|
||||
selectedTipHash := virtualBlock.SelectedTipHash()
|
||||
heightUpdate = virtualBlock.SelectedTipHeight()
|
||||
blkHashUpdate = &selectedTipHash
|
||||
|
||||
// Clear the rejected transactions.
|
||||
sm.rejectedTxns = make(map[daghash.Hash]struct{})
|
||||
@ -872,9 +874,9 @@ func (sm *SyncManager) haveInventory(invVect *wire.InvVect) (bool, error) {
|
||||
prevOut := wire.OutPoint{Hash: invVect.Hash}
|
||||
for i := uint32(0); i < 2; i++ {
|
||||
prevOut.Index = i
|
||||
entry, err := sm.dag.FetchUtxoEntry(prevOut)
|
||||
if err != nil {
|
||||
return false, err
|
||||
entry, ok := sm.dag.VirtualBlock().GetUTXOEntry(prevOut)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
if entry != nil && !entry.IsSpent() {
|
||||
return true, nil
|
||||
@ -1182,7 +1184,7 @@ func (sm *SyncManager) handleBlockchainNotification(notification *blockdag.Notif
|
||||
iv := wire.NewInvVect(wire.InvTypeBlock, block.Hash())
|
||||
sm.peerNotifier.RelayInventory(iv, block.MsgBlock().Header)
|
||||
|
||||
// A block has been connected to the main block chain.
|
||||
// A block has been connected to the block DAG.
|
||||
case blockdag.NTBlockConnected:
|
||||
block, ok := notification.Data.(*util.Block)
|
||||
if !ok {
|
||||
@ -1220,7 +1222,7 @@ func (sm *SyncManager) handleBlockchainNotification(notification *blockdag.Notif
|
||||
}
|
||||
}
|
||||
|
||||
// A block has been disconnected from the main block chain.
|
||||
// A block has been disconnected from the block DAG.
|
||||
case blockdag.NTBlockDisconnected:
|
||||
block, ok := notification.Data.(*util.Block)
|
||||
if !ok {
|
||||
@ -1396,12 +1398,13 @@ func New(config *Config) (*SyncManager, error) {
|
||||
feeEstimator: config.FeeEstimator,
|
||||
}
|
||||
|
||||
dagState := sm.dag.GetDAGState()
|
||||
virtualBlock := sm.dag.VirtualBlock()
|
||||
selectedTipHash := virtualBlock.SelectedTipHash()
|
||||
if !config.DisableCheckpoints {
|
||||
// Initialize the next checkpoint based on the current height.
|
||||
sm.nextCheckpoint = sm.findNextHeaderCheckpoint(dagState.SelectedTip.Height)
|
||||
sm.nextCheckpoint = sm.findNextHeaderCheckpoint(virtualBlock.SelectedTipHeight())
|
||||
if sm.nextCheckpoint != nil {
|
||||
sm.resetHeaderState(&dagState.SelectedTip.Hash, dagState.SelectedTip.Height)
|
||||
sm.resetHeaderState(&selectedTipHash, virtualBlock.SelectedTipHeight())
|
||||
}
|
||||
} else {
|
||||
log.Info("Checkpoints are disabled")
|
||||
|
@ -290,8 +290,9 @@ func newServerPeer(s *Server, isPersistent bool) *Peer {
|
||||
// newestBlock returns the current best block hash and height using the format
|
||||
// required by the configuration for the peer package.
|
||||
func (sp *Peer) newestBlock() (*daghash.Hash, int32, error) {
|
||||
dagState := sp.server.DAG.GetDAGState()
|
||||
return &dagState.SelectedTip.Hash, dagState.SelectedTip.Height, nil
|
||||
virtualBlock := sp.server.DAG.VirtualBlock()
|
||||
selectedTipHash := virtualBlock.SelectedTipHash()
|
||||
return &selectedTipHash, virtualBlock.SelectedTipHeight(), nil
|
||||
}
|
||||
|
||||
// addKnownAddresses adds the given addresses to the set of known addresses to
|
||||
@ -1307,9 +1308,9 @@ func (s *Server) pushBlockMsg(sp *Peer, hash *daghash.Hash, doneChan chan<- stru
|
||||
// to trigger it to issue another getblocks message for the next
|
||||
// batch of inventory.
|
||||
if sendInv {
|
||||
dagState := sp.server.DAG.GetDAGState()
|
||||
selectedTipHash := sp.server.DAG.VirtualBlock().SelectedTipHash()
|
||||
invMsg := wire.NewMsgInvSizeHint(1)
|
||||
iv := wire.NewInvVect(wire.InvTypeBlock, &dagState.SelectedTip.Hash)
|
||||
iv := wire.NewInvVect(wire.InvTypeBlock, &selectedTipHash)
|
||||
invMsg.AddInvVect(iv)
|
||||
sp.QueueMessage(invMsg, doneChan)
|
||||
sp.continueHash = nil
|
||||
@ -2418,7 +2419,7 @@ func NewServer(listenAddrs []string, db database.DB, dagParams *dagconfig.Params
|
||||
|
||||
// If no feeEstimator has been found, or if the one that has been found
|
||||
// is behind somehow, create a new one and start over.
|
||||
if s.FeeEstimator == nil || s.FeeEstimator.LastKnownHeight() != s.DAG.GetDAGState().SelectedTip.Height {
|
||||
if s.FeeEstimator == nil || s.FeeEstimator.LastKnownHeight() != s.DAG.VirtualBlock().SelectedTipHeight() {
|
||||
s.FeeEstimator = mempool.NewFeeEstimator(
|
||||
mempool.DefaultEstimateFeeMaxRollback,
|
||||
mempool.DefaultEstimateFeeMinRegisteredBlocks)
|
||||
@ -2436,10 +2437,10 @@ func NewServer(listenAddrs []string, db database.DB, dagParams *dagconfig.Params
|
||||
MaxTxVersion: 1,
|
||||
},
|
||||
ChainParams: dagParams,
|
||||
FetchUtxoView: s.DAG.FetchUtxoView,
|
||||
BestHeight: func() int32 { return s.DAG.GetDAGState().SelectedTip.Height },
|
||||
MedianTimePast: func() time.Time { return s.DAG.GetDAGState().SelectedTip.MedianTime },
|
||||
CalcSequenceLock: func(tx *util.Tx, view *blockdag.UtxoViewpoint) (*blockdag.SequenceLock, error) {
|
||||
FetchUtxoView: s.DAG.FetchUTXOView,
|
||||
BestHeight: func() int32 { return s.DAG.VirtualBlock().SelectedTipHeight() },
|
||||
MedianTimePast: func() time.Time { return s.DAG.VirtualBlock().SelectedTip().CalcPastMedianTime() },
|
||||
CalcSequenceLock: func(tx *util.Tx, view *blockdag.UTXOView) (*blockdag.SequenceLock, error) {
|
||||
return s.DAG.CalcSequenceLock(tx, view, true)
|
||||
},
|
||||
IsDeploymentActive: s.DAG.IsDeploymentActive,
|
||||
|
@ -1009,18 +1009,18 @@ func handleGetBestBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (
|
||||
// All other "get block" commands give either the height, the
|
||||
// hash, or both but require the block SHA. This gets both for
|
||||
// the best block.
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
result := &btcjson.GetBestBlockResult{
|
||||
Hash: dagState.SelectedTip.Hash.String(),
|
||||
Height: dagState.SelectedTip.Height,
|
||||
Hash: virtualBlock.SelectedTipHash().String(),
|
||||
Height: virtualBlock.SelectedTipHeight(),
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// handleGetBestBlockHash implements the getbestblockhash command.
|
||||
func handleGetBestBlockHash(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
return dagState.SelectedTip.Hash.String(), nil
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
return virtualBlock.SelectedTipHash().String(), nil
|
||||
}
|
||||
|
||||
// getDifficultyRatio returns the proof-of-work difficulty as a multiple of the
|
||||
@ -1087,11 +1087,11 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
|
||||
return nil, internalRPCError(err.Error(), context)
|
||||
}
|
||||
blk.SetHeight(blockHeight)
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
|
||||
// Get the hashes for the next blocks unless there are none.
|
||||
var nextHashStrings []string
|
||||
if blockHeight < dagState.SelectedTip.Height {
|
||||
if blockHeight < virtualBlock.SelectedTipHeight() {
|
||||
childHashes, err := s.cfg.DAG.ChildHashesByHash(hash)
|
||||
if err != nil {
|
||||
context := "No next block"
|
||||
@ -1110,7 +1110,7 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
|
||||
PreviousHashes: daghash.Strings(blockHeader.PrevBlocks),
|
||||
Nonce: blockHeader.Nonce,
|
||||
Time: blockHeader.Timestamp.Unix(),
|
||||
Confirmations: uint64(1 + dagState.SelectedTip.Height - blockHeight),
|
||||
Confirmations: uint64(1 + virtualBlock.SelectedTipHeight() - blockHeight),
|
||||
Height: int64(blockHeight),
|
||||
Size: int32(len(blkBytes)),
|
||||
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
|
||||
@ -1132,7 +1132,7 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
|
||||
for i, tx := range txns {
|
||||
rawTxn, err := createTxRawResult(params, tx.MsgTx(),
|
||||
tx.Hash().String(), blockHeader, hash.String(),
|
||||
blockHeight, dagState.SelectedTip.Height)
|
||||
blockHeight, virtualBlock.SelectedTipHeight())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1169,15 +1169,15 @@ func handleGetBlockDAGInfo(s *Server, cmd interface{}, closeChan <-chan struct{}
|
||||
// populate the response to this call primarily from this snapshot.
|
||||
params := s.cfg.ChainParams
|
||||
dag := s.cfg.DAG
|
||||
dagState := dag.GetDAGState()
|
||||
virtualBlock := dag.VirtualBlock()
|
||||
|
||||
chainInfo := &btcjson.GetBlockDAGInfoResult{
|
||||
dagInfo := &btcjson.GetBlockDAGInfoResult{
|
||||
DAG: params.Name,
|
||||
Blocks: dagState.SelectedTip.Height,
|
||||
Headers: dagState.SelectedTip.Height,
|
||||
TipHashes: daghash.Strings(dagState.TipHashes),
|
||||
Difficulty: getDifficultyRatio(dagState.SelectedTip.Bits, params),
|
||||
MedianTime: dagState.SelectedTip.MedianTime.Unix(),
|
||||
Blocks: virtualBlock.SelectedTipHeight(),
|
||||
Headers: virtualBlock.SelectedTipHeight(),
|
||||
TipHashes: daghash.Strings(virtualBlock.TipHashes()),
|
||||
Difficulty: getDifficultyRatio(virtualBlock.SelectedTip().Header().Bits, params),
|
||||
MedianTime: virtualBlock.SelectedTip().CalcPastMedianTime().Unix(),
|
||||
Pruned: false,
|
||||
Bip9SoftForks: make(map[string]*btcjson.Bip9SoftForkDescription),
|
||||
}
|
||||
@ -1222,7 +1222,7 @@ func handleGetBlockDAGInfo(s *Server, cmd interface{}, closeChan <-chan struct{}
|
||||
|
||||
// Finally, populate the soft-fork description with all the
|
||||
// information gathered above.
|
||||
chainInfo.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{
|
||||
dagInfo.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{
|
||||
Status: strings.ToLower(statusString),
|
||||
Bit: deploymentDetails.BitNumber,
|
||||
StartTime: int64(deploymentDetails.StartTime),
|
||||
@ -1230,13 +1230,13 @@ func handleGetBlockDAGInfo(s *Server, cmd interface{}, closeChan <-chan struct{}
|
||||
}
|
||||
}
|
||||
|
||||
return chainInfo, nil
|
||||
return dagInfo, nil
|
||||
}
|
||||
|
||||
// handleGetBlockCount implements the getblockcount command.
|
||||
func handleGetBlockCount(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
return int64(dagState.SelectedTip.Height), nil
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
return int64(virtualBlock.SelectedTipHeight()), nil
|
||||
}
|
||||
|
||||
// handleGetBlockHash implements the getblockhash command.
|
||||
@ -1283,11 +1283,11 @@ func handleGetBlockHeader(s *Server, cmd interface{}, closeChan <-chan struct{})
|
||||
context := "Failed to obtain block height"
|
||||
return nil, internalRPCError(err.Error(), context)
|
||||
}
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
|
||||
// Get the hashes for the next blocks unless there are none.
|
||||
var nextHashStrings []string
|
||||
if blockHeight < dagState.SelectedTip.Height {
|
||||
if blockHeight < virtualBlock.SelectedTipHeight() {
|
||||
childHashes, err := s.cfg.DAG.ChildHashesByHash(hash)
|
||||
if err != nil {
|
||||
context := "No next block"
|
||||
@ -1299,7 +1299,7 @@ func handleGetBlockHeader(s *Server, cmd interface{}, closeChan <-chan struct{})
|
||||
params := s.cfg.ChainParams
|
||||
blockHeaderReply := btcjson.GetBlockHeaderVerboseResult{
|
||||
Hash: c.Hash,
|
||||
Confirmations: uint64(1 + dagState.SelectedTip.Height - blockHeight),
|
||||
Confirmations: uint64(1 + virtualBlock.SelectedTipHeight() - blockHeight),
|
||||
Height: blockHeight,
|
||||
Version: blockHeader.Version,
|
||||
VersionHex: fmt.Sprintf("%08x", blockHeader.Version),
|
||||
@ -1493,7 +1493,8 @@ func (state *gbtWorkState) updateBlockTemplate(s *Server, useCoinbaseValue bool)
|
||||
// generated.
|
||||
var msgBlock *wire.MsgBlock
|
||||
var targetDifficulty string
|
||||
tipHashes := s.cfg.DAG.GetDAGState().TipHashes
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
tipHashes := virtualBlock.TipHashes()
|
||||
template := state.template
|
||||
if template == nil || state.tipHashes == nil ||
|
||||
!daghash.AreEqual(state.tipHashes, tipHashes) ||
|
||||
@ -1532,8 +1533,7 @@ func (state *gbtWorkState) updateBlockTemplate(s *Server, useCoinbaseValue bool)
|
||||
// Get the minimum allowed timestamp for the block based on the
|
||||
// median timestamp of the last several blocks per the chain
|
||||
// consensus rules.
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
minTimestamp := mining.MinimumMedianTime(dagState)
|
||||
minTimestamp := mining.MinimumMedianTime(virtualBlock.SelectedTip().CalcPastMedianTime())
|
||||
|
||||
// Update work state to ensure another block template isn't
|
||||
// generated until needed.
|
||||
@ -1889,7 +1889,7 @@ func handleGetBlockTemplateRequest(s *Server, request *btcjson.TemplateRequest,
|
||||
}
|
||||
|
||||
// No point in generating or accepting work before the chain is synced.
|
||||
currentHeight := s.cfg.DAG.GetDAGState().SelectedTip.Height
|
||||
currentHeight := s.cfg.DAG.VirtualBlock().SelectedTipHeight()
|
||||
if currentHeight != 0 && !s.cfg.SyncMgr.IsCurrent() {
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCClientInInitialDownload,
|
||||
@ -2054,7 +2054,7 @@ func handleGetBlockTemplateProposal(s *Server, request *btcjson.TemplateRequest)
|
||||
block := util.NewBlock(&msgBlock)
|
||||
|
||||
// Ensure the block is building from the expected previous blocks.
|
||||
expectedPrevHashes := s.cfg.DAG.GetDAGState().TipHashes
|
||||
expectedPrevHashes := s.cfg.DAG.VirtualBlock().TipHashes()
|
||||
prevHashes := block.MsgBlock().Header.PrevBlocks
|
||||
if !daghash.AreEqual(expectedPrevHashes, prevHashes) {
|
||||
return "bad-prevblk", nil
|
||||
@ -2176,8 +2176,8 @@ func handleGetCurrentNet(s *Server, cmd interface{}, closeChan <-chan struct{})
|
||||
|
||||
// handleGetDifficulty implements the getdifficulty command.
|
||||
func handleGetDifficulty(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
return getDifficultyRatio(dagState.SelectedTip.Bits, s.cfg.ChainParams), nil
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
return getDifficultyRatio(virtualBlock.SelectedTip().Header().Bits, s.cfg.ChainParams), nil
|
||||
}
|
||||
|
||||
// handleGetGenerate implements the getgenerate command.
|
||||
@ -2234,15 +2234,15 @@ func handleGetHeaders(s *Server, cmd interface{}, closeChan <-chan struct{}) (in
|
||||
// handleGetInfo implements the getinfo command. We only return the fields
|
||||
// that are not related to wallet functionality.
|
||||
func handleGetInfo(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
ret := &btcjson.InfoDAGResult{
|
||||
Version: int32(1000000*version.AppMajor + 10000*version.AppMinor + 100*version.AppPatch),
|
||||
ProtocolVersion: int32(maxProtocolVersion),
|
||||
Blocks: dagState.SelectedTip.Height,
|
||||
Blocks: virtualBlock.SelectedTipHeight(),
|
||||
TimeOffset: int64(s.cfg.TimeSource.Offset().Seconds()),
|
||||
Connections: s.cfg.ConnMgr.ConnectedCount(),
|
||||
Proxy: config.MainConfig().Proxy,
|
||||
Difficulty: getDifficultyRatio(dagState.SelectedTip.Bits, s.cfg.ChainParams),
|
||||
Difficulty: getDifficultyRatio(virtualBlock.SelectedTip().Header().Bits, s.cfg.ChainParams),
|
||||
TestNet: config.MainConfig().TestNet3,
|
||||
RelayFee: config.MainConfig().MinRelayTxFee.ToBTC(),
|
||||
}
|
||||
@ -2286,12 +2286,21 @@ func handleGetMiningInfo(s *Server, cmd interface{}, closeChan <-chan struct{})
|
||||
}
|
||||
}
|
||||
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
selectedTipHash := virtualBlock.SelectedTipHash()
|
||||
selectedBlock, err := s.cfg.DAG.BlockByHash(&selectedTipHash)
|
||||
if err != nil {
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInternal.Code,
|
||||
Message: "could not find block for selected tip",
|
||||
}
|
||||
}
|
||||
|
||||
result := btcjson.GetMiningInfoResult{
|
||||
Blocks: int64(dagState.SelectedTip.Height),
|
||||
CurrentBlockSize: dagState.SelectedTip.BlockSize,
|
||||
CurrentBlockTx: dagState.SelectedTip.NumTxs,
|
||||
Difficulty: getDifficultyRatio(dagState.SelectedTip.Bits, s.cfg.ChainParams),
|
||||
Blocks: int64(virtualBlock.SelectedTipHeight()),
|
||||
CurrentBlockSize: uint64(selectedBlock.MsgBlock().SerializeSize()),
|
||||
CurrentBlockTx: uint64(len(selectedBlock.MsgBlock().Transactions)),
|
||||
Difficulty: getDifficultyRatio(virtualBlock.SelectedTip().Header().Bits, s.cfg.ChainParams),
|
||||
Generate: s.cfg.CPUMiner.IsMining(),
|
||||
GenProcLimit: s.cfg.CPUMiner.NumWorkers(),
|
||||
HashesPerSec: int64(s.cfg.CPUMiner.HashesPerSecond()),
|
||||
@ -2476,7 +2485,7 @@ func handleGetRawTransaction(s *Server, cmd interface{}, closeChan <-chan struct
|
||||
// The verbose flag is set, so generate the JSON object and return it.
|
||||
var blkHeader *wire.BlockHeader
|
||||
var blkHashStr string
|
||||
var chainHeight int32
|
||||
var dagHeight int32
|
||||
if blkHash != nil {
|
||||
// Fetch the header from chain.
|
||||
header, err := s.cfg.DAG.HeaderByHash(blkHash)
|
||||
@ -2487,11 +2496,11 @@ func handleGetRawTransaction(s *Server, cmd interface{}, closeChan <-chan struct
|
||||
|
||||
blkHeader = &header
|
||||
blkHashStr = blkHash.String()
|
||||
chainHeight = s.cfg.DAG.GetDAGState().SelectedTip.Height
|
||||
dagHeight = s.cfg.DAG.VirtualBlock().SelectedTipHeight()
|
||||
}
|
||||
|
||||
rawTxn, err := createTxRawResult(s.cfg.ChainParams, mtx, txHash.String(),
|
||||
blkHeader, blkHashStr, blkHeight, chainHeight)
|
||||
blkHeader, blkHashStr, blkHeight, dagHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -2543,16 +2552,16 @@ func handleGetTxOut(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
|
||||
return nil, internalRPCError(errStr, "")
|
||||
}
|
||||
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
bestBlockHash = dagState.SelectedTip.Hash.String()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
bestBlockHash = virtualBlock.SelectedTipHash().String()
|
||||
confirmations = 0
|
||||
value = txOut.Value
|
||||
pkScript = txOut.PkScript
|
||||
isCoinbase = blockdag.IsCoinBaseTx(mtx)
|
||||
} else {
|
||||
out := wire.OutPoint{Hash: *txHash, Index: c.Vout}
|
||||
entry, err := s.cfg.DAG.FetchUtxoEntry(out)
|
||||
if err != nil {
|
||||
entry, ok := s.cfg.DAG.VirtualBlock().GetUTXOEntry(out)
|
||||
if !ok {
|
||||
return nil, rpcNoTxInfoError(txHash)
|
||||
}
|
||||
|
||||
@ -2565,9 +2574,9 @@ func handleGetTxOut(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
bestBlockHash = dagState.SelectedTip.Hash.String()
|
||||
confirmations = 1 + dagState.SelectedTip.Height - entry.BlockHeight()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
bestBlockHash = virtualBlock.SelectedTipHash().String()
|
||||
confirmations = 1 + virtualBlock.SelectedTipHeight() - entry.BlockHeight()
|
||||
value = entry.Amount()
|
||||
pkScript = entry.PkScript()
|
||||
isCoinbase = entry.IsCoinBase()
|
||||
@ -3060,7 +3069,7 @@ func handleSearchRawTransactions(s *Server, cmd interface{}, closeChan <-chan st
|
||||
}
|
||||
|
||||
// The verbose flag is set, so generate the JSON object and return it.
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
srtList := make([]btcjson.SearchRawTransactionsResult, len(addressTxns))
|
||||
for i := range addressTxns {
|
||||
// The deserialized transaction is needed, so deserialize the
|
||||
@ -3130,7 +3139,7 @@ func handleSearchRawTransactions(s *Server, cmd interface{}, closeChan <-chan st
|
||||
result.Time = uint64(blkHeader.Timestamp.Unix())
|
||||
result.Blocktime = uint64(blkHeader.Timestamp.Unix())
|
||||
result.BlockHash = blkHashStr
|
||||
result.Confirmations = uint64(1 + dagState.SelectedTip.Height - blkHeight)
|
||||
result.Confirmations = uint64(1 + virtualBlock.SelectedTipHeight() - blkHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3315,18 +3324,18 @@ func handleValidateAddress(s *Server, cmd interface{}, closeChan <-chan struct{}
|
||||
}
|
||||
|
||||
func verifyDAG(s *Server, level, depth int32) error {
|
||||
dagState := s.cfg.DAG.GetDAGState()
|
||||
finishHeight := dagState.SelectedTip.Height - depth
|
||||
virtualBlock := s.cfg.DAG.VirtualBlock()
|
||||
finishHeight := virtualBlock.SelectedTipHeight() - depth
|
||||
if finishHeight < 0 {
|
||||
finishHeight = 0
|
||||
}
|
||||
log.Infof("Verifying chain for %d blocks at level %d",
|
||||
dagState.SelectedTip.Height-finishHeight, level)
|
||||
virtualBlock.SelectedTipHeight()-finishHeight, level)
|
||||
|
||||
currentHash := &dagState.SelectedTip.Hash
|
||||
for height := dagState.SelectedTip.Height; height > finishHeight; {
|
||||
currentHash := virtualBlock.SelectedTipHash()
|
||||
for height := virtualBlock.SelectedTipHeight(); height > finishHeight; {
|
||||
// Level 0 just looks up the block.
|
||||
block, err := s.cfg.DAG.BlockByHash(currentHash)
|
||||
block, err := s.cfg.DAG.BlockByHash(¤tHash)
|
||||
if err != nil {
|
||||
log.Errorf("Verify is unable to fetch block at "+
|
||||
"height %d: %v", height, err)
|
||||
@ -3345,7 +3354,7 @@ func verifyDAG(s *Server, level, depth int32) error {
|
||||
}
|
||||
}
|
||||
|
||||
currentHash = block.MsgBlock().Header.SelectedPrevBlock()
|
||||
currentHash = *block.MsgBlock().Header.SelectedPrevBlock()
|
||||
}
|
||||
log.Infof("Chain verify completed successfully")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user