mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[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:
parent
45cffb4f69
commit
5e15b8208e
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user