[NOD-41] Extract methods under connectBlock (#191)

* [DEV-379] Refactored checkBlockContext: Mainly extracted methods and re-organized variable use to minimize clutter

* [DEV-379] Simplify the condition according to which we increment blockCount

* [DEV-379] Move all logic to save new block data to separate method

* [DEV-379] Refactored the checking of finality point

* [DEV-379] Minor styling changes

* [DEV-379] Extracted method in applyUTXOChanges subroutines

* [NOD-41] Moved update of finality point to after block was validated + some minor style fixes

* [NOD-41] call dag.checkFinalityRulesAndGetFinalityPoint(node) even if fastAdd

* [NOD-41] Fix in a comment

* [NOD-41] Some methods of dag that could have been just functions converted to function
This commit is contained in:
Svarog 2019-03-05 14:17:30 +02:00 committed by Ori Newman
parent 45cffb4f69
commit 5e15b8208e
9 changed files with 226 additions and 169 deletions

View File

@ -49,7 +49,7 @@ func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) er
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the main chain or
// such as making blocks that never become part of the DAG or
// blocks that fail to connect available for further analysis.
err = dag.db.Update(func(dbTx database.Tx) error {
return dbStoreBlock(dbTx, block)
@ -58,9 +58,7 @@ func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) er
return err
}
// Create a new block node for the block and add it to the node index. Even
// if the block ultimately gets connected to the main chain, it starts out
// on a side chain.
// Create a new block node for the block and add it to the node index.
blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader, parents, dag.dagParams.K)
newNode.status = statusDataStored

View File

@ -128,6 +128,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents block
timestamp: time.Now().Unix(),
}
// blockHeader is nil only for the virtual block
if blockHeader != nil {
node.hash = blockHeader.BlockHash()
node.workSum = CalcWork(blockHeader.Bits)
@ -225,11 +226,11 @@ func (node *blockNode) RelativeAncestor(distance int32) *blockNode {
return node.Ancestor(node.height - distance)
}
// CalcPastMedianTime calculates the median time of the previous few blocks
// PastMedianTime returns the median time of the previous few blocks
// prior to, and including, the block node.
//
// This function is safe for concurrent access.
func (node *blockNode) CalcPastMedianTime() time.Time {
func (node *blockNode) PastMedianTime() time.Time {
// Create a slice of the previous few block timestamps used to calculate
// the median per the number defined by the constant medianTimeBlocks.
// If there aren't enough blocks yet - pad remaining with genesis block's timestamp.

View File

@ -403,7 +403,7 @@ func (dag *BlockDAG) calcSequenceLock(node *blockNode, utxoSet UTXOSet, tx *util
prevInputHeight = 0
}
blockNode := node.Ancestor(prevInputHeight)
medianTime := blockNode.CalcPastMedianTime()
medianTime := blockNode.PastMedianTime()
// Time based relative time-locks as defined by BIP 68
// have a time granularity of RelativeLockSeconds, so
@ -470,9 +470,7 @@ func (dag *BlockDAG) connectToDAG(node *blockNode, parentNodes blockSet, block *
} else {
return err
}
}
if dag.index.NodeStatus(node).KnownValid() {
} else {
dag.blockCount++
}
@ -497,7 +495,6 @@ func (dag *BlockDAG) connectToDAG(node *blockNode, parentNodes blockSet, block *
//
// This function MUST be called with the chain state lock held (for writes).
func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bool) error {
// No warnings about unknown rules or versions until the DAG is
// current.
if dag.isCurrent() {
@ -514,17 +511,14 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo
}
}
var finalityPointCandidate *blockNode
if !fastAdd {
var err error
finalityPointCandidate, err = dag.checkFinalityRulesAndGetFinalityPointCandidate(node)
if err != nil {
return err
}
var newFinalityPoint *blockNode
var err error
newFinalityPoint, err = dag.checkFinalityRulesAndGetFinalityPoint(node)
if err != nil {
return err
}
err := dag.validateGasLimit(block)
if err != nil {
if err := dag.validateGasLimit(block); err != nil {
return err
}
@ -534,9 +528,7 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo
return err
}
if finalityPointCandidate != nil {
dag.lastFinalityPoint = finalityPointCandidate
}
dag.lastFinalityPoint = newFinalityPoint
// Write any block status changes to DB before updating the DAG state.
err = dag.index.flushToDB()
@ -544,8 +536,26 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo
return err
}
err = dag.saveChangesFromBlock(node, block, utxoDiff, txsAcceptanceData, feeData)
if err != nil {
return err
}
// Notify the caller that the block was connected to the DAG.
// The caller would typically want to react with actions such as
// updating wallets.
dag.dagLock.Unlock()
dag.sendNotification(NTBlockConnected, block)
dag.dagLock.Lock()
return nil
}
func (dag *BlockDAG) saveChangesFromBlock(node *blockNode, block *util.Block, utxoDiff *UTXODiff,
txsAcceptanceData MultiblockTxsAcceptanceData, feeData compactFeeData) error {
// Atomically insert info into the database.
err = dag.db.Update(func(dbTx database.Tx) error {
return dag.db.Update(func(dbTx database.Tx) error {
// Update best block state.
state := &dagState{
TipHashes: dag.TipHashes(),
@ -591,18 +601,6 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo
// Apply the fee data into the database
return dbStoreFeeData(dbTx, block.Hash(), feeData)
})
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, block)
dag.dagLock.Lock()
return nil
}
func (dag *BlockDAG) validateGasLimit(block *util.Block) error {
@ -643,28 +641,37 @@ func (dag *BlockDAG) LastFinalityPointHash() *daghash.Hash {
return &dag.lastFinalityPoint.hash
}
func (dag *BlockDAG) checkFinalityRulesAndGetFinalityPointCandidate(node *blockNode) (*blockNode, error) {
// checkFinalityRulesAndGetFinalityPoint checks the new block does not violate the finality rules
// and if not - returns the (potentially new) finality point
func (dag *BlockDAG) checkFinalityRulesAndGetFinalityPoint(node *blockNode) (*blockNode, error) {
if node.isGenesis() {
return node, nil
}
var finalityPointCandidate *blockNode
finalityPoint := dag.lastFinalityPoint
finalityErr := ruleError(ErrFinality, "The last finality point is not in the selected chain of this block")
if node.blueScore <= dag.lastFinalityPoint.blueScore {
return nil, finalityErr
}
shouldFindFinalityPointCandidate := node.finalityScore() > dag.lastFinalityPoint.finalityScore()
// We are looking for a new finality point only if the new block's finality score is higher
// than the existing finality point's
shouldFindNewFinalityPoint := node.finalityScore() > dag.lastFinalityPoint.finalityScore()
for currentNode := node.selectedParent; currentNode != dag.lastFinalityPoint; currentNode = currentNode.selectedParent {
// If we went past dag's last finality point without encountering it -
// the new block has violated finality.
if currentNode.blueScore <= dag.lastFinalityPoint.blueScore {
return nil, finalityErr
}
if shouldFindFinalityPointCandidate && currentNode.finalityScore() > currentNode.selectedParent.finalityScore() {
finalityPointCandidate = currentNode
// If current node's finality score is higher than it's selectedParent's -
// current node is the new finalityPoint
if shouldFindNewFinalityPoint && currentNode.finalityScore() > currentNode.selectedParent.finalityScore() {
finalityPoint = currentNode
}
}
return finalityPointCandidate, nil
return finalityPoint, nil
}
// NextBlockFeeTransaction prepares the fee transaction for the next mined block
@ -708,8 +715,7 @@ func (dag *BlockDAG) applyUTXOChanges(node *blockNode, block *util.Block, fastAd
// internal structure of block nodes, and it's irrecoverable - therefore
// panic
err = node.updateParents(virtualClone, newBlockUTXO)
if err != nil {
if err = node.updateParents(virtualClone, newBlockUTXO); err != nil {
panic(fmt.Errorf("failed updating parents of %s: %s", node, err))
}
@ -731,7 +737,7 @@ func (dag *BlockDAG) applyUTXOChanges(node *blockNode, block *util.Block, fastAd
// It is now safe to meld the UTXO set to base.
diffSet := newVirtualUTXO.(*DiffUTXOSet)
utxoDiff = diffSet.UTXODiff
dag.updateVirtualUTXO(diffSet)
dag.meldVirtualUTXO(diffSet)
// Set the status to valid for all index nodes to make sure the changes get
// written to the database.
@ -746,7 +752,7 @@ func (dag *BlockDAG) applyUTXOChanges(node *blockNode, block *util.Block, fastAd
return utxoDiff, txsAcceptanceData, feeData, nil
}
func (dag *BlockDAG) updateVirtualUTXO(newVirtualUTXODiffSet *DiffUTXOSet) {
func (dag *BlockDAG) meldVirtualUTXO(newVirtualUTXODiffSet *DiffUTXOSet) {
dag.utxoLock.Lock()
defer dag.utxoLock.Unlock()
newVirtualUTXODiffSet.meldToBase()
@ -1017,7 +1023,7 @@ func (dag *BlockDAG) UTXOSet() *FullUTXOSet {
// CalcPastMedianTime returns the past median time of the DAG.
func (dag *BlockDAG) CalcPastMedianTime() time.Time {
return dag.virtual.tips().bluest().CalcPastMedianTime()
return dag.virtual.tips().bluest().PastMedianTime()
}
// GetUTXOEntry returns the requested unspent transaction output. The returned

View File

@ -263,13 +263,13 @@ func TestCalcSequenceLock(t *testing.T) {
// Obtain the median time past from the PoV of the input created above.
// The MTP for the input is the MTP from the PoV of the block *prior*
// to the one that included it.
medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix()
medianTime := node.RelativeAncestor(5).PastMedianTime().Unix()
// The median time calculated from the PoV of the best block in the
// test chain. For unconfirmed inputs, this value will be used since
// the MTP will be calculated from the PoV of the yet-to-be-mined
// block.
nextMedianTime := node.CalcPastMedianTime().Unix()
nextMedianTime := node.PastMedianTime().Unix()
nextBlockHeight := int32(numBlocksToGenerate) + 1
// Add an additional transaction which will serve as our unconfirmed
@ -561,7 +561,7 @@ func TestCalcPastMedianTime(t *testing.T) {
}
for _, test := range tests {
secondsSinceGenesis := nodes[test.blockNumber].CalcPastMedianTime().Unix() - dag.genesis.Header().Timestamp.Unix()
secondsSinceGenesis := nodes[test.blockNumber].PastMedianTime().Unix() - dag.genesis.Header().Timestamp.Unix()
if secondsSinceGenesis != test.expectedSecondsSinceGenesis {
t.Errorf("TestCalcPastMedianTime: expected past median time of block %v to be %v seconds from genesis but got %v", test.blockNumber, test.expectedSecondsSinceGenesis, secondsSinceGenesis)
}

View File

@ -202,15 +202,15 @@ func (dag *BlockDAG) findPrevTestNetDifficulty(startNode *blockNode) uint32 {
// This function differs from the exported CalcNextRequiredDifficulty in that
// the exported version uses the current best chain as the previous block node
// while this function accepts any block node.
func (dag *BlockDAG) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTime time.Time) (uint32, error) {
func (dag *BlockDAG) calcNextRequiredDifficulty(bluestParent *blockNode, newBlockTime time.Time) (uint32, error) {
// Genesis block.
if lastNode == nil {
if bluestParent == nil {
return dag.dagParams.PowLimitBits, nil
}
// Return the previous block's difficulty requirements if this block
// is not at a difficulty retarget interval.
if (lastNode.height+1)%dag.blocksPerRetarget != 0 {
if (bluestParent.height+1)%dag.blocksPerRetarget != 0 {
// For networks that support it, allow special reduction of the
// required difficulty once too much time has elapsed without
// mining a block.
@ -219,7 +219,7 @@ func (dag *BlockDAG) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// amount of time has elapsed without mining a block.
reductionTime := int64(dag.dagParams.MinDiffReductionTime /
time.Second)
allowMinTime := lastNode.timestamp + reductionTime
allowMinTime := bluestParent.timestamp + reductionTime
if newBlockTime.Unix() > allowMinTime {
return dag.dagParams.PowLimitBits, nil
}
@ -227,24 +227,24 @@ func (dag *BlockDAG) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// The block was mined within the desired timeframe, so
// return the difficulty for the last block which did
// not have the special minimum difficulty rule applied.
return dag.findPrevTestNetDifficulty(lastNode), nil
return dag.findPrevTestNetDifficulty(bluestParent), nil
}
// For the main network (or any unrecognized networks), simply
// return the previous block's difficulty requirements.
return lastNode.bits, nil
return bluestParent.bits, nil
}
// Get the block node at the previous retarget (targetTimespan days
// worth of blocks).
firstNode := lastNode.RelativeAncestor(dag.blocksPerRetarget - 1)
firstNode := bluestParent.RelativeAncestor(dag.blocksPerRetarget - 1)
if firstNode == nil {
return 0, AssertError("unable to obtain previous retarget block")
}
// Limit the amount of adjustment that can occur to the previous
// difficulty.
actualTimespan := lastNode.timestamp - firstNode.timestamp
actualTimespan := bluestParent.timestamp - firstNode.timestamp
adjustedTimespan := actualTimespan
if actualTimespan < dag.minRetargetTimespan {
adjustedTimespan = dag.minRetargetTimespan
@ -257,7 +257,7 @@ func (dag *BlockDAG) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// The result uses integer division which means it will be slightly
// rounded down. Bitcoind also uses integer division to calculate this
// result.
oldTarget := CompactToBig(lastNode.bits)
oldTarget := CompactToBig(bluestParent.bits)
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
targetTimeSpan := int64(dag.dagParams.TargetTimespan / time.Second)
newTarget.Div(newTarget, big.NewInt(targetTimeSpan))
@ -272,8 +272,8 @@ func (dag *BlockDAG) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// newTarget since conversion to the compact representation loses
// precision.
newTargetBits := BigToCompact(newTarget)
log.Debugf("Difficulty retarget at block height %d", lastNode.height+1)
log.Debugf("Old target %08x (%064x)", lastNode.bits, oldTarget)
log.Debugf("Difficulty retarget at block height %d", bluestParent.height+1)
log.Debugf("Old target %08x (%064x)", bluestParent.bits, oldTarget)
log.Debugf("New target %08x (%064x)", newTargetBits, CompactToBig(newTargetBits))
log.Debugf("Actual timespan %s, adjusted timespan %s, target timespan %s",
time.Duration(actualTimespan)*time.Second,

View File

@ -152,7 +152,7 @@ func (dag *BlockDAG) thresholdState(prevNode *blockNode, checker thresholdCondit
// The start and expiration times are based on the median block
// time, so calculate it now.
medianTime := prevNode.CalcPastMedianTime()
medianTime := prevNode.PastMedianTime()
// The state is simply defined if the start time hasn't been
// been reached yet.
@ -192,7 +192,7 @@ func (dag *BlockDAG) thresholdState(prevNode *blockNode, checker thresholdCondit
case ThresholdDefined:
// The deployment of the rule change fails if it expires
// before it is accepted and locked in.
medianTime := prevNode.CalcPastMedianTime()
medianTime := prevNode.PastMedianTime()
medianTimeUnix := uint64(medianTime.Unix())
if medianTimeUnix >= checker.EndTime() {
state = ThresholdFailed
@ -209,7 +209,7 @@ func (dag *BlockDAG) thresholdState(prevNode *blockNode, checker thresholdCondit
case ThresholdStarted:
// The deployment of the rule change fails if it expires
// before it is accepted and locked in.
medianTime := prevNode.CalcPastMedianTime()
medianTime := prevNode.PastMedianTime()
if uint64(medianTime.Unix()) >= checker.EndTime() {
state = ThresholdFailed
break

View File

@ -461,7 +461,6 @@ func checkBlockParentsOrder(header *wire.BlockHeader) error {
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkBlockHeaderSanity.
func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) error {
msgBlock := block.MsgBlock()
header := &msgBlock.Header
err := dag.checkBlockHeaderSanity(header, flags)
@ -646,12 +645,14 @@ func ExtractCoinbaseHeight(coinbaseTx *util.Tx) (int32, error) {
// checkSerializedHeight checks if the signature script in the passed
// transaction starts with the serialized block height of wantHeight.
func checkSerializedHeight(coinbaseTx *util.Tx, wantHeight int32) error {
func checkSerializedHeight(block *util.Block) error {
coinbaseTx := block.CoinbaseTransaction()
serializedHeight, err := ExtractCoinbaseHeight(coinbaseTx)
if err != nil {
return err
}
wantHeight := block.Height()
if serializedHeight != wantHeight {
str := fmt.Sprintf("the coinbase signature script serialized "+
"block height is %d when %d was expected",
@ -669,36 +670,21 @@ func checkSerializedHeight(coinbaseTx *util.Tx, wantHeight int32) error {
// the checkpoints are not performed.
//
// This function MUST be called with the dag state lock held (for writes).
func (dag *BlockDAG) checkBlockHeaderContext(header *wire.BlockHeader, bluestParent *blockNode, blockHeight int32, flags BehaviorFlags) error {
fastAdd := flags&BFFastAdd == BFFastAdd
func (dag *BlockDAG) checkBlockHeaderContext(header *wire.BlockHeader, bluestParent *blockNode, blockHeight int32, fastAdd bool) error {
if !fastAdd {
// Ensure the difficulty specified in the block header matches
// the calculated difficulty based on the previous block and
// difficulty retarget rules.
expectedDifficulty, err := dag.calcNextRequiredDifficulty(bluestParent,
header.Timestamp)
if err != nil {
if err := dag.validateDifficulty(header, bluestParent); err != nil {
return err
}
blockDifficulty := header.Bits
if blockDifficulty != expectedDifficulty {
str := "block difficulty of %d is not the expected value of %d"
str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
return ruleError(ErrUnexpectedDifficulty, str)
}
if !header.IsGenesis() {
// Ensure the timestamp for the block header is not before the
// median time of the last several blocks (medianTimeBlocks).
medianTime := bluestParent.CalcPastMedianTime()
if header.Timestamp.Before(medianTime) {
str := "block timestamp of %s is not after expected %s"
str = fmt.Sprintf(str, header.Timestamp, medianTime)
return ruleError(ErrTimeTooOld, str)
}
if err := validateMedianTime(header, bluestParent); err != nil {
return err
}
}
return dag.validateCheckpoints(header, blockHeight)
}
func (dag *BlockDAG) validateCheckpoints(header *wire.BlockHeader, blockHeight int32) error {
// Ensure dag matches up to predetermined checkpoints.
blockHash := header.BlockHash()
if !dag.verifyCheckpoint(blockHeight, &blockHash) {
@ -725,6 +711,40 @@ func (dag *BlockDAG) checkBlockHeaderContext(header *wire.BlockHeader, bluestPar
return nil
}
func validateMedianTime(header *wire.BlockHeader, bluestParent *blockNode) error {
if !header.IsGenesis() {
// Ensure the timestamp for the block header is not before the
// median time of the last several blocks (medianTimeBlocks).
medianTime := bluestParent.PastMedianTime()
if header.Timestamp.Before(medianTime) {
str := "block timestamp of %s is not after expected %s"
str = fmt.Sprintf(str, header.Timestamp, medianTime)
return ruleError(ErrTimeTooOld, str)
}
}
return nil
}
func (dag *BlockDAG) validateDifficulty(header *wire.BlockHeader, bluestParent *blockNode) error {
// Ensure the difficulty specified in the block header matches
// the calculated difficulty based on the previous block and
// difficulty retarget rules.
expectedDifficulty, err := dag.calcNextRequiredDifficulty(bluestParent,
header.Timestamp)
if err != nil {
return err
}
blockDifficulty := header.Bits
if blockDifficulty != expectedDifficulty {
str := "block difficulty of %d is not the expected value of %d"
str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
return ruleError(ErrUnexpectedDifficulty, str)
}
return nil
}
// validateParents validates that no parent is an ancestor of another parent
func validateParents(blockHeader *wire.BlockHeader, parents blockSet) error {
minHeight := int32(math.MaxInt32)
@ -773,6 +793,8 @@ func validateParents(blockHeader *wire.BlockHeader, parents blockSet) error {
//
// This function MUST be called with the dag state lock held (for writes).
func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, bluestParent *blockNode, flags BehaviorFlags) error {
fastAdd := flags&BFFastAdd == BFFastAdd
err := validateParents(&block.MsgBlock().Header, parents)
if err != nil {
return err
@ -780,33 +802,17 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, blue
// Perform all block header related validation checks.
header := &block.MsgBlock().Header
err = dag.checkBlockHeaderContext(header, bluestParent, block.Height(), flags)
if err != nil {
if err = dag.checkBlockHeaderContext(header, bluestParent, block.Height(), fastAdd); err != nil {
return err
}
fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd {
blockTime := header.Timestamp
if !block.IsGenesis() {
blockTime = bluestParent.CalcPastMedianTime()
}
// Ensure all transactions in the block are finalized.
for _, tx := range block.Transactions() {
if !IsFinalizedTransaction(tx, block.Height(),
blockTime) {
str := fmt.Sprintf("block contains unfinalized "+
"transaction %s", tx.ID())
return ruleError(ErrUnfinalizedTx, str)
}
if err := dag.validateAllTxsFinalized(block, header, bluestParent); err != nil {
return err
}
// Ensure coinbase starts with serialized block heights
coinbaseTx := block.Transactions()[0]
err := checkSerializedHeight(coinbaseTx, block.Height())
err := checkSerializedHeight(block)
if err != nil {
return err
}
@ -816,6 +822,24 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, blue
return nil
}
func (dag *BlockDAG) validateAllTxsFinalized(block *util.Block, header *wire.BlockHeader, bluestParent *blockNode) error {
blockTime := header.Timestamp
if !block.IsGenesis() {
blockTime = bluestParent.PastMedianTime()
}
// Ensure all transactions in the block are finalized.
for _, tx := range block.Transactions() {
if !IsFinalizedTransaction(tx, block.Height(), blockTime) {
str := fmt.Sprintf("block contains unfinalized "+
"transaction %s", tx.ID())
return ruleError(ErrUnfinalizedTx, str)
}
}
return nil
}
// ensureNoDuplicateTx ensures blocks do not contain duplicate transactions which
// 'overwrite' older transactions that are not fully spent. This prevents an
// attack where a coinbase and all of its dependent transactions could be
@ -860,13 +884,14 @@ func ensureNoDuplicateTx(block *blockNode, utxoSet UTXOSet,
// include verifying all inputs exist, ensuring the block reward seasoning
// requirements are met, detecting double spends, validating all values and fees
// are in the legal range and the total output amount doesn't exceed the input
// amount, and verifying the signatures to prove the spender was the owner of
// the bitcoins and therefore allowed to spend them. As it checks the inputs,
// it also calculates the total fees for the transaction and returns that value.
// amount. As it checks the inputs, it also calculates the total fees for the
// transaction and returns that value.
//
// 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, utxoSet UTXOSet, dagParams *dagconfig.Params, fastAdd bool) (uint64, error) {
func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoSet UTXOSet, dagParams *dagconfig.Params, fastAdd bool) (
txFeeInSatoshi uint64, err error) {
// Block reward transactions have no inputs.
if IsBlockReward(tx) {
return 0, nil
@ -886,21 +911,8 @@ func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoSet UTXOSet, dagPar
}
if !fastAdd {
// Ensure the transaction is not spending coins which have not
// yet reached the required block reward maturity.
if entry.IsBlockReward() {
originHeight := entry.BlockHeight()
blocksSincePrev := txHeight - originHeight
blockRewardMaturity := int32(dagParams.BlockRewardMaturity)
if blocksSincePrev < blockRewardMaturity {
str := fmt.Sprintf("tried to spend block reward "+
"transaction output %s from height %d "+
"at height %d before required maturity "+
"of %d blocks", txIn.PreviousOutPoint,
originHeight, txHeight,
blockRewardMaturity)
return 0, ruleError(ErrImmatureSpend, str)
}
if err = validateBlockRewardMaturity(dagParams, entry, txHeight, txIn); err != nil {
return 0, err
}
}
@ -953,10 +965,30 @@ func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoSet UTXOSet, dagPar
// NOTE: bitcoind checks if the transaction fees are < 0 here, but that
// is an impossible condition because of the check above that ensures
// the inputs are >= the outputs.
txFeeInSatoshi := totalSatoshiIn - totalSatoshiOut
txFeeInSatoshi = totalSatoshiIn - totalSatoshiOut
return txFeeInSatoshi, nil
}
func validateBlockRewardMaturity(dagParams *dagconfig.Params, entry *UTXOEntry, txHeight int32, txIn *wire.TxIn) error {
// Ensure the transaction is not spending coins which have not
// yet reached the required block reward maturity.
if entry.IsBlockReward() {
originHeight := entry.BlockHeight()
blocksSincePrev := txHeight - originHeight
blockRewardMaturity := int32(dagParams.BlockRewardMaturity)
if blocksSincePrev < blockRewardMaturity {
str := fmt.Sprintf("tried to spend block reward "+
"transaction output %s from height %d "+
"at height %d before required maturity "+
"of %d blocks", txIn.PreviousOutPoint,
originHeight, txHeight,
blockRewardMaturity)
return ruleError(ErrImmatureSpend, str)
}
}
return nil
}
// checkConnectToPastUTXO performs several checks to confirm connecting the passed
// block to the DAG represented by the passed view does not violate any rules.
//
@ -978,37 +1010,8 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
return nil, err
}
// The number of signature operations must be less than the maximum
// allowed per block. Note that the preliminary sanity checks on a
// block also include a check similar to this one, but this check
// expands the count to include a precise count of pay-to-script-hash
// signature operations in each of the input transaction public key
// scripts.
totalSigOps := 0
for i, tx := range transactions {
numsigOps := CountSigOps(tx)
// Since the first transaction has already been verified to be a
// coinbase transaction, and the second transaction has already
// been verified to be a fee transaction, use i < 2 as an
// optimization for the flag to countP2SHSigOps for whether or
// not the transaction is a block reward transaction rather than
// having to do a full coinbase and fee transaction check again.
numP2SHSigOps, err := CountP2SHSigOps(tx, i < 2, pastUTXO)
if err != nil {
return nil, err
}
numsigOps += numP2SHSigOps
// Check for overflow or going over the limits. We have to do
// this on every loop iteration to avoid overflow.
lastSigops := totalSigOps
totalSigOps += numsigOps
if totalSigOps < lastSigops || totalSigOps > MaxSigOpsPerBlock {
str := fmt.Sprintf("block contains too many "+
"signature operations - got %d, max %d",
totalSigOps, MaxSigOpsPerBlock)
return nil, ruleError(ErrTooManySigOps, str)
}
if err := validateSigopsCount(pastUTXO, transactions); err != nil {
return nil, err
}
}
@ -1087,7 +1090,7 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
// in order to determine if transactions in the current block are final.
medianTime := block.Header().Timestamp
if !block.isGenesis() {
medianTime = block.selectedParent.CalcPastMedianTime()
medianTime = block.selectedParent.PastMedianTime()
}
// We also enforce the relative sequence number based
@ -1125,6 +1128,43 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
return feeData, nil
}
func validateSigopsCount(pastUTXO UTXOSet, transactions []*util.Tx) error {
// The number of signature operations must be less than the maximum
// allowed per block. Note that the preliminary sanity checks on a
// block also include a check similar to this one, but this check
// expands the count to include a precise count of pay-to-script-hash
// signature operations in each of the input transaction public key
// scripts.
totalSigOps := 0
for i, tx := range transactions {
numsigOps := CountSigOps(tx)
// Since the first transaction has already been verified to be a
// coinbase transaction, and the second transaction has already
// been verified to be a fee transaction, use i < 2 as an
// optimization for the flag to countP2SHSigOps for whether or
// not the transaction is a block reward transaction rather than
// having to do a full coinbase and fee transaction check again.
numP2SHSigOps, err := CountP2SHSigOps(tx, i < 2, pastUTXO)
if err != nil {
return err
}
numsigOps += numP2SHSigOps
// Check for overflow or going over the limits. We have to do
// this on every loop iteration to avoid overflow.
lastSigops := totalSigOps
totalSigOps += numsigOps
if totalSigOps < lastSigops || totalSigOps > MaxSigOpsPerBlock {
str := fmt.Sprintf("block contains too many "+
"signature operations - got %d, max %d",
totalSigOps, MaxSigOpsPerBlock)
return ruleError(ErrTooManySigOps, str)
}
}
return nil
}
// countSpentOutputs returns the number of utxos the passed block spends.
func countSpentOutputs(block *util.Block) int {
// Exclude the block reward transactions since they can't spend anything.

View File

@ -510,9 +510,13 @@ func TestCheckSerializedHeight(t *testing.T) {
for i, test := range tests {
msgTx := coinbaseTx.Copy()
msgTx.TxIn[0].SignatureScript = test.sigScript
tx := util.NewTx(msgTx)
err := checkSerializedHeight(tx, test.wantHeight)
msgBlock := wire.NewMsgBlock(wire.NewBlockHeader(1, []daghash.Hash{}, &daghash.Hash{}, &daghash.Hash{}, 0, 0))
msgBlock.AddTransaction(msgTx)
block := util.NewBlock(msgBlock)
block.SetHeight(test.wantHeight)
err := checkSerializedHeight(block)
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
t.Errorf("checkSerializedHeight #%d wrong error type "+
"got: %v <%T>, want: %T", i, err, err, test.err)
@ -552,11 +556,11 @@ func TestPastMedianTime(t *testing.T) {
node := newTestNode(setFromSlice(tip),
blockVersion,
0,
tip.CalcPastMedianTime(),
tip.PastMedianTime(),
dagconfig.MainNetParams.K)
header := node.Header()
err := dag.checkBlockHeaderContext(header, node.parents.bluest(), height, BFNone)
err := dag.checkBlockHeaderContext(header, node.parents.bluest(), height, false)
if err != nil {
t.Errorf("TestPastMedianTime: unexpected error from checkBlockHeaderContext: %v"+
"(a block with timestamp equals to past median time should be valid)", err)
@ -567,11 +571,11 @@ func TestPastMedianTime(t *testing.T) {
node = newTestNode(setFromSlice(tip),
blockVersion,
0,
tip.CalcPastMedianTime().Add(time.Second),
tip.PastMedianTime().Add(time.Second),
dagconfig.MainNetParams.K)
header = node.Header()
err = dag.checkBlockHeaderContext(header, node.parents.bluest(), height, BFNone)
err = dag.checkBlockHeaderContext(header, node.parents.bluest(), height, false)
if err != nil {
t.Errorf("TestPastMedianTime: unexpected error from checkBlockHeaderContext: %v"+
"(a block with timestamp bigger than past median time should be valid)", err)
@ -582,11 +586,11 @@ func TestPastMedianTime(t *testing.T) {
node = newTestNode(setFromSlice(tip),
blockVersion,
0,
tip.CalcPastMedianTime().Add(-time.Second),
tip.PastMedianTime().Add(-time.Second),
dagconfig.MainNetParams.K)
header = node.Header()
err = dag.checkBlockHeaderContext(header, node.parents.bluest(), height, BFNone)
err = dag.checkBlockHeaderContext(header, node.parents.bluest(), height, false)
if err == nil {
t.Errorf("TestPastMedianTime: unexpected success: block should be invalid if its timestamp is before past median time")
}

View File

@ -23,6 +23,9 @@ const (
// yet.
BlockHeightUnknown = int32(-1)
// CoinbaseTransactionIndex is the index of the coinbase transaction in every block
CoinbaseTransactionIndex = 0
// FeeTransactionIndex is the index of the fee transaction in every block (except genesis,
// which doesn't have a fee transaction)
FeeTransactionIndex = 1
@ -205,6 +208,11 @@ func (b *Block) IsGenesis() bool {
return b.MsgBlock().Header.IsGenesis()
}
// CoinbaseTransaction returns this block's coinbase transaction
func (b *Block) CoinbaseTransaction() *Tx {
return b.Transactions()[CoinbaseTransactionIndex]
}
// FeeTransaction returns this block's fee transaction
// If this block is a genesis block, it has no fee transaction, and therefore
// nil is returned.