kaspad/domain/blockdag/validate.go
Svarog 1e08bfca9c
[NOD-1032] Consensus updates pre-pruning (#917)
* [NOD-1249] Add pruning related constants (#869)

* [NOD-1249] Add pruning related constants

* [NOD-1249] Change status suspect to UTXONotVerified

* [NOD-1249] Add TestPruningDepth

* [NOD-1249] Add comment to pruningDepth

* [NOD-1249] Add pruning helper functions (#875)

* [NOD-1249] Added node.blockAtDepth

* [NOD-1249] Added node.finalityPoint()

* [NOD-1249] Add hasFinalityPointInOthersSelectedChain

* [NOD-1249] Add nonFinalityViolatingBlues

* [NOD-1249] Added isInPastOfAny

* [NOD-1249] Updated all calls to blockNode functions that require dag

* [NOD-1249] Add blockNode.reds field and persist it

* [NOD-1249] Add checkObjectiveFinality

* [NOD-1249] Add isViolatingSubjectiveFinality

* [NOD-1249] Added to TestGHOSTDAG check that reds are as expected

* [NOD-1249] Add checkMergeLimit and checkDAGRelations

* [NOD-1249] Invert condition in blockInDepth

* [NOD-1249] Make isInPastOfAny resemble isInPast

* [NOD-1249] Added comments to isInPast and isInPastOfAny

* [NOD-1252] Remove any references to legacy finality (#876)

* [NOD-1032] validateParents: check number of parents and that no parents were manually rejected (#877)

* [NOD-1254] Block verification changes (#882)

* [NOD-1254] Call checkDAGRelations and move it to correct place

* [NOD-1254] Use blockStatuses properly

* [NOD-1254] Add support for setting node's verification flag and set it to UTXONotVerified once block passes basic verification

* [NOD-1254] Check for subjctiveFinality, and for node not being in the selectedParentChain

* [NOD-1254] Make blockStatus an ordinary value - not bit flags

* [NOD-1254] Isolate all utxo-requiring validation into a single separate if branches

* [NOD-1254] Re-arrange connectBloc so that things that happen in UTXO-validated blocks only are all grouped together

* [NOD-1254] Resolve and check selectedParent's status before validatingUTXO

* [NOD-1254] Separate virtualUTXODiff from utxoVerificationOutput

* [NOD-1254] Stylistic fixes

* [NOD-1254] Use dag.index.(Set)BlockNodeStatus instead of accessing node.status

* [NOD-1288] Sub-routinize checkConnectToPastUTXO

* [NOD-1288] Re-write checkConnectToPastUTXO in a way that allows to filter-out invalid transactions

* [NOD-1288] Make checkTxSequenceLock use already calculated utxo outputs

* [NOD-1288] Make checkTxMass use already calculated utxo outputs

* [NOD-1288] Use dag.sigCache for ValidateTransactionScripts

* [NOD-1288] Use checkConnectTransactionToPastUTXO in applyBlueBlocks

* [NOD-1288] Clean-up old code-path from no longer used functions

* [NOD-1288] Skip any irrelevant parts of txo verification if block is genesis

* [NOD-1288] Set  where it should have been

* [NOD-1288] Fix reachability checks to never use the new node + make isInSelectedParentChainOf return true if node == other

* [NOD-1288] invert the condition for isNewSelectedTip

* [NOD-1288] Separate checkIsAccepted to own function, and properly handle coinbase

* [NOD-1288] Don't update utxo-diff for UTXONotVerified parents/tips + Make PrepareBlockForTest resolve the selectedParent's UTXOSet if needed

* [NOD-1288] Include mass off coinbase transactions

* [NOD-1288] Move comment to correct position

* [NOD-1288] If blockAtDepth should return genesis - do it immidiately

* [NOD-1288] Comment-out instead of removeing scriptval_test.go

* [NOD-1288] Rename: entry -> utxoEntry

* [NOD-1288] Remove special function for calcCoinbaseTxMass

* [NOD-1288] Remove redundant check from checkEntryAmounts

* [NOD-1288] Rename: MaxMassPerBlock -> MaxMassAcceptedByBlock

* [NOD-1255] Implement boundedMergeBreakingParents

* [NOD-1255] Implement selectAllowedTips

* [NOD-1255] Integrate virtual parent selection into block verification process

* [NOD-1255] Add node to tips all the time, remove it from candidates and add it's parents if it's disqualified

* [NOD-1255] remove tips from virtaulBlock

* [NOD-1255] Rename: didVirtualParentsChanged -> didVirtualParentsChange

* [NOD-1255] Remove redundant sanity check

* [NOD-1255] Handle a forgotten error

* [NOD-1255] Prettify selectVirtualParents

* [NOD-1255] UpdateTipsUTXO should run over all UTXO-Verified tips, even if they are not parents of virtual

* [NOD-1311] Make isInPast inclusive

* [NOD-1032] Handle finality conflicts (#904)

* [NOD-1312] AddTip should not include finalityViolating and manuallyRejected blocks

* [NOD-1312] Implement resolveFinalityConflict

* [NOD-1312] Implement dag notifications for finalityChanges + updateing DAG state

* [NOD-1312] Added finality conflict rpc boilerplate

* [NOD-1312] Implement handling of getFinalityConflicts + resolveFinalityConflict RPCs

* [NOD-1312] Implement finality conflict related notifications

* [NOD-1312] Move all time to millisecond time

* [NOD-1312] Add comments + unexport some methods

* [NOD-1312] Add clarification in comments

* [NOD-1312] Move updateFinalityConflictResolution to finality_conflicts.go

* [NOD-1312] Rename: currentSelectedTip -> selectedTip

* [NOD-1312] Add long comment to ResolveFinalityConflict

* [NOD-1312] Convert areAllInSelectedParentChainOf into isInSelectedParentChainOfAll

* [NOD-1312] Rename chainUpdates (type) -> selectedParentChainUpdates, to distinguish from the variable chainUpdates

* [NOD-1032] Make all blockdag tests compile

* [NOD-1278] Fix finality-related tests (#910)

* [NOD-1032] Don't return node.dag.genesis from blockAtDepth because it might still not exist

* [NOD-1032] Actually add a tip in dag.addTip

* [NOD-1278] Add transaction to utxo set if it's coinbase

* [NOD-1278] Use VirtualParentHashes instead of TipHashes where appropriate

* [NOD-1278] If no valid virtual parent candidates - return error, don't wait for panic

* [NOD-1278] Transition TestCalcSequenceLock from newTestDAG to DAGSetup

* [NOD-1278] Fix .bluest() tie-breaker

* [NOD-1278] Remove feeData structure, as it no longer works, store feeData in acceptanceData

* [NOD-1278] Remove dag parameter from blockNode methods

* [NOD-1278] Fix TestBlueBlockWindow

* [NOD-1278] Don't subject selectedParent to MaxMergeSet

* [NOD-1278] se PrepareAndProcessBlockForTest instead of .addTip in TestSelectedPath

* [NOD-1278] Fixed TestDAGStateSerialization

* [NOD-1278] Fix TestAcceptanceIndexRecover

* [NOD-1278] Fix TestCheckConnectBlockTemplate

* [NOD-1278] Fix TestChainUpdates

* [NOD-1278] Fix and rename TestVirtualBlock -> TestTips

* [NOD-1278] Rename checkIsAccepted -> maybeAcceptTx

* [NOD-1278] Re-activate TestDoubleSpends

* Revert "[NOD-1278] Fixed TestDAGStateSerialization"

This reverts commit 845095d6de7207b07cf819d05f3f38ad94da9cf6.

* [NOD-1278] Remove dag parameter from expectedCoinbaseTransaction

* [NOD-1348] Implemented simplified Finality Conflict Resolution scheme (#911)

* [NOD-1348] Rename functions according to Research spec

* [NOD-1348] Added blockSet.areAllIn

* [NOD-1348] Implemented simplified finality conflict resolution scheme

* [NOD-1348] Refactorings and fixes in selectVirtualParents

* [NOD-1278] Fix bugs discovered by unit-tests + Fix unit-tests (#916)

* Updated to version v0.3.1

* [NOD-858] Don't switch sync peer if the syncing process hasn't yet started with the current sync peer (#700)

* [NOD-858] Don't switch sync peer if the syncing process hasn't yet started with the current sync peer

* [NOD-858] SetShouldSendBlockLocator(false) on OnBlockLocator

* [NOD-858] Rename shouldSendBlockLocator->wasBlockLocatorRequested

* [NOD-858] Move panic to shouldReplaceSyncPeer

* [NOD-869] Add a print after os.Exit(1) to see if it is ever called (#701)

* [NOD-1238] Fix acceptance index never being initialized. (#859)

* [NOD-1278] Genesis never violates finality

* [NOD-1348] Refactorings and fixes in selectVirtualParents

* [NOD-1278] Don't call dag.selectVirtualParents for genesis

* [NOD-1278] Properly organize errors in maybeAcceptBlock

* [NOD-1278] updateTipsUTXO should only run on tips whose status is

* [NOD-1278] updateTipsUTXO should only run on tips whose status is `valid`

* [NOD-1278] Fix TestDoubleSpends

* [NOD-1278] Fix TestDAGIndexFailedStatus

* [NOD-1278] IsFinalizedTransaction should use uint64 everywhere

* [NOD-1278] If tx is coinbase and not selectedParent - don't update pastUTXO

* [NOD-1278] Store tips and validTips separately

* [NOD-1278] Store ValidTips and VirtualParents in dagState

* [NOD-1278] Fix TestProcessOrphans

* [NOD-1278] Fix TestProcessOrphans

* [NOD-1278] Fix TestOrderInDiffFromAcceptanceData

* [NOD-1278] Fix TestHelp

* [NOD-1278] Remove mining.PrepareBlockForTest; use blockdag.PrepareBlockForTest instead

* [NOD-1278] Explicitly disallow chained transactions

* [NOD-1278]

* [NOD-1278] Fix some comments

Co-authored-by: Ori Newman <orinewman1@gmail.com>
Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
Co-authored-by: Yuval Shaul <yuval.shaul@gmail.com>

* [NOD-1355] Add unit-test for finality + When resolving finalityConflict - make sure the block that will come out selectedTip is statusValid (#919)

* [NOD-1355] Added test for finality

* [NOD-1355] When resolving finalityConflict - make sure the block that will come out selectedTip is statusValid

* [NOD-1032] Renames: anything about inputsWithReferencedUTXOEntries -> remove 'Referenced'

* [NOD-1032] Don't ignore non-rule errors

* [NOD-1032] Fix comment

* [NOD-1032] Enhanced comments on TestChainUpdates

* [NOD-1032] Remove scriptval_test.go

* [NOD-1032] Extracted isNewSelectedTip to a method

* [NOD-1032] Use dag.Now() instead of mstime.Now()

* [NOD-1032] Print block status when accepting block

* [NOD-1032] Add comment explaining boundedMergeBreakingParents

* [NOD-1032] Enhanced test and imporved comments in TestFinality

* [NOD-1032] Rename: Objective finality -> bounded merge depth

* [NOD-1032] No need to check that validTips are valid

* [NOD-1032] Remove dag from arguments of updateDiffAndDiffChild

* [NOD-1032] Correct variable names in LookupNodes

[NOD-1032] Correct variable names in LookupNodes

* [NOD-1032] Fix some comments

* [NOD-1032] Some style changes

* [NOD-1032] Refactor isAnyInPastOf

* [NOD-1032] Enhance comment in updateVirtualParents

* [NOD-1032] Flip condition in updateVirtualParents

* [NOD-1032] Stylistic and grammatic fixes in dag.go and dag_test.go

* [NOD-1032] Explain why updateSelectedParentSet receives geneses on init

* [NOD-1032] Remove ErrParentManuallyRejected

* [NOD-1032] Added wrapper for resolveNodeStatus that creates a special transaction for it

* [NOD-1032] Rename: statusUTXONotVerified -> statusUTXOPendingVerification

* [NOD-1032] Use virtual parents in CurrentBits()

* [NOD-1032] rename: isViolatingSubjectiveFinality -> isViolatingFinality

* [NOD-1032] Move updateVirtualAndTips to applyDAGChanges

* [NOD-1032] Invert condition for isFinalityPointInPast

* [NOD-1032] Fix antiPastBetween isInPast became inclusive

* [NOD-1032] Remove redundant call for addTip

* [NOD-1032] Use calcCoinbaseTxMass where appropriate

* [NOD-1032] Remove time fields from conflict notifications

* [NOD-1032] Assign the correct thing to i

* [NOD-1032] unify checkOutputsAmounts and checkTxOutputAmounts

* [NOD-1032] Cleanup in CheckTransactionInputsAndCalulateFee

* [NOD-1032] Fixed some style and comments

* [NOD-1032] If selectedParent is disqualifiedFromChain - validateAndApplyUTXOSet should return this as a ruleError

* [NOD-1032] Set the status in resolveNodeStatus

* [NOD-1032] Correct comment on boundedMergeBreakingParents

* [NOD-1032] Fix a typo.

* [NOD-1032] Update a variable name.

* [NOD-1032] Fix a comment.

* [NOD-1032] Fix merge errors.

* [NOD-1032] Add VirtualParentHashes to getBlockDagInfo.

* [NOD-1032] Update handleGetBlockTemplate.

* [NOD-1032] Comment out all the old RPC stuff.

* [NOD-1032] Remove irrelevant type.

* [NOD-1032] Implement ResolveFinalityConflict.

* [NOD-1032] Remove irrelevant comments.

* [NOD-1032] Implement NotifyFinalityConflicts.

* [NOD-1032] Add FinalityConflictNotification and FinalityConflictResolvedNotification.

* [NOD-1032] Finish implementing finality conflict notifications.

* [NOD-1032] Remove old RPC stuff.

* [NOD-1032] Fix grammar in a comment.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
Co-authored-by: Yuval Shaul <yuval.shaul@gmail.com>
Co-authored-by: stasatdaglabs <stas@daglabs.com>
2020-09-13 17:49:59 +03:00

1316 lines
42 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"fmt"
"math"
"sort"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
const (
// MaxCoinbasePayloadLen is the maximum length a coinbase payload can be.
MaxCoinbasePayloadLen = 150
// baseSubsidy is the starting subsidy amount for mined blocks. This
// value is halved every SubsidyHalvingInterval blocks.
baseSubsidy = 50 * util.SompiPerKaspa
// the following are used when calculating a transaction's mass
// MassPerTxByte is the number of grams that any byte
// adds to a transaction.
MassPerTxByte = 1
// MassPerScriptPubKeyByte is the number of grams that any
// scriptPubKey byte adds to a transaction.
MassPerScriptPubKeyByte = 10
// MassPerSigOp is the number of grams that any
// signature operation adds to a transaction.
MassPerSigOp = 10000
// mergeSetSizeLimit is the maximum allowed merge set size for a block.
mergeSetSizeLimit = 1000
)
// isNullOutpoint determines whether or not a previous transaction outpoint
// is set.
func isNullOutpoint(outpoint *appmessage.Outpoint) bool {
if outpoint.Index == math.MaxUint32 && outpoint.TxID == daghash.ZeroTxID {
return true
}
return false
}
// SequenceLockActive determines if a transaction's sequence locks have been
// met, meaning that all the inputs of a given transaction have reached a
// blue score or time sufficient for their relative lock-time maturity.
func SequenceLockActive(sequenceLock *SequenceLock, blockBlueScore uint64,
medianTimePast mstime.Time) bool {
// If either the milliseconds, or blue score relative-lock time has not yet
// reached, then the transaction is not yet mature according to its
// sequence locks.
if sequenceLock.Milliseconds >= medianTimePast.UnixMilliseconds() ||
sequenceLock.BlockBlueScore >= int64(blockBlueScore) {
return false
}
return true
}
// IsFinalizedTransaction determines whether or not a transaction is finalized.
func IsFinalizedTransaction(tx *util.Tx, blockBlueScore uint64, blockTime mstime.Time) bool {
msgTx := tx.MsgTx()
// Lock time of zero means the transaction is finalized.
lockTime := msgTx.LockTime
if lockTime == 0 {
return true
}
// The lock time field of a transaction is either a block blue score at
// which the transaction is finalized or a timestamp depending on if the
// value is before the txscript.LockTimeThreshold. When it is under the
// threshold it is a block blue score.
blockTimeOrBlueScore := uint64(0)
if lockTime < txscript.LockTimeThreshold {
blockTimeOrBlueScore = blockBlueScore
} else {
blockTimeOrBlueScore = uint64(blockTime.UnixMilliseconds())
}
if lockTime < blockTimeOrBlueScore {
return true
}
// At this point, the transaction's lock time hasn't occurred yet, but
// the transaction might still be finalized if the sequence number
// for all transaction inputs is maxed out.
for _, txIn := range msgTx.TxIn {
if txIn.Sequence != math.MaxUint64 {
return false
}
}
return true
}
// CalcBlockSubsidy returns the subsidy amount a block at the provided blue score
// should have. This is mainly used for determining how much the coinbase for
// newly generated blocks awards as well as validating the coinbase for blocks
// has the expected value.
//
// The subsidy is halved every SubsidyReductionInterval blocks. Mathematically
// this is: baseSubsidy / 2^(blueScore/SubsidyReductionInterval)
//
// At the target block generation rate for the main network, this is
// approximately every 4 years.
func CalcBlockSubsidy(blueScore uint64, dagParams *dagconfig.Params) uint64 {
if dagParams.SubsidyReductionInterval == 0 {
return baseSubsidy
}
// Equivalent to: baseSubsidy / 2^(blueScore/subsidyHalvingInterval)
return baseSubsidy >> uint(blueScore/dagParams.SubsidyReductionInterval)
}
// CheckTransactionSanity performs some preliminary checks on a transaction to
// ensure it is sane. These checks are context free.
func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
err := checkTransactionInputCount(tx)
if err != nil {
return err
}
err = checkTransactionAmountRanges(tx)
if err != nil {
return err
}
err = checkDuplicateTransactionInputs(tx)
if err != nil {
return err
}
err = checkCoinbaseLength(tx)
if err != nil {
return err
}
err = checkTransactionPayloadHash(tx)
if err != nil {
return err
}
err = checkGasInBuiltInOrNativeTransactions(tx)
if err != nil {
return err
}
err = checkSubnetworkRegistryTransaction(tx)
if err != nil {
return err
}
err = checkNativeTransactionPayload(tx)
if err != nil {
return err
}
err = checkTransactionSubnetwork(tx, subnetworkID)
if err != nil {
return err
}
return nil
}
func checkTransactionInputCount(tx *util.Tx) error {
// A non-coinbase transaction must have at least one input.
msgTx := tx.MsgTx()
if !tx.IsCoinBase() && len(msgTx.TxIn) == 0 {
return ruleError(ErrNoTxInputs, "transaction has no inputs")
}
return nil
}
func checkTransactionAmountRanges(tx *util.Tx) error {
// Ensure the transaction amounts are in range. Each transaction
// output must not be negative or more than the max allowed per
// transaction. Also, the total of all outputs must abide by the same
// restrictions. All amounts in a transaction are in a unit value known
// as a sompi. One kaspa is a quantity of sompi as defined by the
// SompiPerKaspa constant.
var totalSompi uint64
for _, txOut := range tx.MsgTx().TxOut {
sompi := txOut.Value
if sompi > util.MaxSompi {
str := fmt.Sprintf("transaction output value of %d is "+
"higher than max allowed value of %d", sompi,
util.MaxSompi)
return ruleError(ErrBadTxOutValue, str)
}
// Binary arithmetic guarantees that any overflow is detected and reported.
// This is impossible for Kaspa, but perhaps possible if an alt increases
// the total money supply.
newTotalSompi := totalSompi + sompi
if newTotalSompi < totalSompi {
str := fmt.Sprintf("total value of all transaction "+
"outputs exceeds max allowed value of %d",
util.MaxSompi)
return ruleError(ErrBadTxOutValue, str)
}
totalSompi = newTotalSompi
if totalSompi > util.MaxSompi {
str := fmt.Sprintf("total value of all transaction "+
"outputs is %d which is higher than max "+
"allowed value of %d", totalSompi,
util.MaxSompi)
return ruleError(ErrBadTxOutValue, str)
}
}
return nil
}
func checkDuplicateTransactionInputs(tx *util.Tx) error {
existingTxOut := make(map[appmessage.Outpoint]struct{})
for _, txIn := range tx.MsgTx().TxIn {
if _, exists := existingTxOut[txIn.PreviousOutpoint]; exists {
return ruleError(ErrDuplicateTxInputs, "transaction "+
"contains duplicate inputs")
}
existingTxOut[txIn.PreviousOutpoint] = struct{}{}
}
return nil
}
func checkCoinbaseLength(tx *util.Tx) error {
// Coinbase payload length must not exceed the max length.
if tx.IsCoinBase() {
payloadLen := len(tx.MsgTx().Payload)
if payloadLen > MaxCoinbasePayloadLen {
str := fmt.Sprintf("coinbase transaction payload length "+
"of %d is out of range (max: %d)",
payloadLen, MaxCoinbasePayloadLen)
return ruleError(ErrBadCoinbasePayloadLen, str)
}
} else {
// Previous transaction outputs referenced by the inputs to this
// transaction must not be null.
for _, txIn := range tx.MsgTx().TxIn {
if isNullOutpoint(&txIn.PreviousOutpoint) {
return ruleError(ErrBadTxInput, "transaction "+
"input refers to previous output that "+
"is null")
}
}
}
return nil
}
func checkTransactionPayloadHash(tx *util.Tx) error {
msgTx := tx.MsgTx()
if !msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
payloadHash := daghash.DoubleHashH(msgTx.Payload)
if !msgTx.PayloadHash.IsEqual(&payloadHash) {
return ruleError(ErrInvalidPayloadHash, "invalid payload hash")
}
} else if msgTx.PayloadHash != nil {
return ruleError(ErrInvalidPayloadHash, "unexpected non-empty payload hash in native subnetwork")
}
return nil
}
func checkGasInBuiltInOrNativeTransactions(tx *util.Tx) error {
// Transactions in native, registry and coinbase subnetworks must have Gas = 0
msgTx := tx.MsgTx()
if msgTx.SubnetworkID.IsBuiltInOrNative() && msgTx.Gas > 0 {
return ruleError(ErrInvalidGas, "transaction in the native or "+
"registry subnetworks has gas > 0 ")
}
return nil
}
func checkSubnetworkRegistryTransaction(tx *util.Tx) error {
if tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry) {
err := validateSubnetworkRegistryTransaction(tx.MsgTx())
if err != nil {
return err
}
}
return nil
}
func checkNativeTransactionPayload(tx *util.Tx) error {
msgTx := tx.MsgTx()
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) && len(msgTx.Payload) > 0 {
return ruleError(ErrInvalidPayload, "transaction in the native subnetwork includes a payload")
}
return nil
}
func checkTransactionSubnetwork(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
// If we are a partial node, only transactions on built in subnetworks
// or our own subnetwork may have a payload
msgTx := tx.MsgTx()
isLocalNodeFull := subnetworkID == nil
shouldTxBeFull := msgTx.SubnetworkID.IsBuiltIn() ||
msgTx.SubnetworkID.IsEqual(subnetworkID)
if !isLocalNodeFull && !shouldTxBeFull && len(msgTx.Payload) > 0 {
return ruleError(ErrInvalidPayload,
"transaction that was expected to be partial has a payload "+
"with length > 0")
}
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.
//
// The flags modify the behavior of this function as follows:
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
// difficulty is not performed.
func (dag *BlockDAG) checkProofOfWork(header *appmessage.BlockHeader, flags BehaviorFlags) error {
// The target difficulty must be larger than zero.
target := util.CompactToBig(header.Bits)
if target.Sign() <= 0 {
str := fmt.Sprintf("block target difficulty of %064x is too low",
target)
return ruleError(ErrUnexpectedDifficulty, str)
}
// The target difficulty must be less than the maximum allowed.
if target.Cmp(dag.Params.PowMax) > 0 {
str := fmt.Sprintf("block target difficulty of %064x is "+
"higher than max of %064x", target, dag.Params.PowMax)
return ruleError(ErrUnexpectedDifficulty, str)
}
// The block hash must be less than the claimed target unless the flag
// to avoid proof of work checks is set.
if flags&BFNoPoWCheck != BFNoPoWCheck {
// The block hash must be less than the claimed target.
hash := header.BlockHash()
hashNum := daghash.HashToBig(hash)
if hashNum.Cmp(target) > 0 {
str := fmt.Sprintf("block hash of %064x is higher than "+
"expected max of %064x", hashNum, target)
return ruleError(ErrHighHash, str)
}
}
return nil
}
// ValidateTxMass makes sure that the given transaction's mass does not exceed
// the maximum allowed limit. Currently, it is equivalent to the block mass limit.
// See CalcTxMass for further details.
func ValidateTxMass(tx *util.Tx, utxoSet UTXOSet) error {
txMass, err := CalcTxMassFromUTXOSet(tx, utxoSet)
if err != nil {
return err
}
if txMass > appmessage.MaxMassAcceptedByBlock {
str := fmt.Sprintf("tx %s has mass %d, which is above the "+
"allowed limit of %d", tx.ID(), txMass, appmessage.MaxMassAcceptedByBlock)
return ruleError(ErrTxMassTooHigh, str)
}
return nil
}
func calcTxMassFromInputsWithUTXOEntries(
tx *util.Tx, inputsWithUTXOEntries []*txInputAndUTXOEntry) uint64 {
if tx.IsCoinBase() {
return calcCoinbaseTxMass(tx)
}
previousScriptPubKeys := make([][]byte, 0, len(tx.MsgTx().TxIn))
for _, inputWithUTXOEntry := range inputsWithUTXOEntries {
utxoEntry := inputWithUTXOEntry.utxoEntry
previousScriptPubKeys = append(previousScriptPubKeys, utxoEntry.ScriptPubKey())
}
return CalcTxMass(tx, previousScriptPubKeys)
}
// CalcTxMassFromUTXOSet calculates the transaction mass based on the
// UTXO set in its past.
//
// See CalcTxMass for more details.
func CalcTxMassFromUTXOSet(tx *util.Tx, utxoSet UTXOSet) (uint64, error) {
if tx.IsCoinBase() {
return calcCoinbaseTxMass(tx), nil
}
previousScriptPubKeys := make([][]byte, len(tx.MsgTx().TxIn))
for txInIndex, txIn := range tx.MsgTx().TxIn {
entry, ok := utxoSet.Get(txIn.PreviousOutpoint)
if !ok {
str := fmt.Sprintf("output %s referenced from "+
"transaction %s input %d either does not exist or "+
"has already been spent", txIn.PreviousOutpoint,
tx.ID(), txInIndex)
return 0, ruleError(ErrMissingTxOut, str)
}
previousScriptPubKeys[txInIndex] = entry.ScriptPubKey()
}
return CalcTxMass(tx, previousScriptPubKeys), nil
}
func calcCoinbaseTxMass(tx *util.Tx) uint64 {
return CalcTxMass(tx, nil)
}
// CalcTxMass sums up and returns the "mass" of a transaction. This number
// is an approximation of how many resources (CPU, RAM, etc.) it would take
// to process the transaction.
// The following properties are considered in the calculation:
// * The transaction length in bytes
// * The length of all output scripts in bytes
// * The count of all input sigOps
func CalcTxMass(tx *util.Tx, previousScriptPubKeys [][]byte) uint64 {
txSize := tx.MsgTx().SerializeSize()
if tx.IsCoinBase() {
return uint64(txSize * MassPerTxByte)
}
scriptPubKeySize := 0
for _, txOut := range tx.MsgTx().TxOut {
scriptPubKeySize += len(txOut.ScriptPubKey)
}
sigOpsCount := 0
for txInIndex, txIn := range tx.MsgTx().TxIn {
// Count the precise number of signature operations in the
// referenced public key script.
sigScript := txIn.SignatureScript
isP2SH := txscript.IsPayToScriptHash(previousScriptPubKeys[txInIndex])
sigOpsCount += txscript.GetPreciseSigOpCount(sigScript, previousScriptPubKeys[txInIndex], isP2SH)
}
return uint64(txSize*MassPerTxByte +
scriptPubKeySize*MassPerScriptPubKeyByte +
sigOpsCount*MassPerSigOp)
}
// checkBlockHeaderSanity performs some preliminary checks on a block header to
// ensure it is sane before continuing with processing. These checks are
// context free.
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkProofOfWork.
func (dag *BlockDAG) checkBlockHeaderSanity(block *util.Block, flags BehaviorFlags) error {
// Ensure the proof of work bits in the block header is in min/max range
// and the block hash is less than the target value described by the
// bits.
header := &block.MsgBlock().Header
err := dag.checkProofOfWork(header, flags)
if err != nil {
return err
}
if len(header.ParentHashes) == 0 {
if !header.BlockHash().IsEqual(dag.Params.GenesisHash) {
return ruleError(ErrNoParents, "block has no parents")
}
} else {
err = checkBlockParentsOrder(header)
if err != nil {
return err
}
}
return nil
}
//checkBlockParentsOrder ensures that the block's parents are ordered by hash
func checkBlockParentsOrder(header *appmessage.BlockHeader) error {
sortedHashes := make([]*daghash.Hash, header.NumParentBlocks())
for i, hash := range header.ParentHashes {
sortedHashes[i] = hash
}
sort.Slice(sortedHashes, func(i, j int) bool {
return daghash.Less(sortedHashes[i], sortedHashes[j])
})
if !daghash.AreEqual(header.ParentHashes, sortedHashes) {
return ruleError(ErrWrongParentsOrder, "block parents are not ordered by hash")
}
return nil
}
// checkBlockSanity performs some preliminary checks on a block to ensure it is
// sane before continuing with block processing. These checks are context free.
//
// 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 {
err := dag.checkBlockHeaderSanity(block, flags)
if err != nil {
return err
}
err = dag.checkBlockContainsAtLeastOneTransaction(block)
if err != nil {
return err
}
err = dag.checkBlockContainsLessThanMaxBlockMassTransactions(block)
if err != nil {
return err
}
err = dag.checkFirstBlockTransactionIsCoinbase(block)
if err != nil {
return err
}
err = dag.checkBlockContainsOnlyOneCoinbase(block)
if err != nil {
return err
}
err = dag.checkBlockTransactionOrder(block)
if err != nil {
return err
}
err = dag.checkNoNonNativeTransactions(block)
if err != nil {
return err
}
err = dag.checkBlockTransactionSanity(block)
if err != nil {
return err
}
err = dag.checkBlockHashMerkleRoot(block)
if err != nil {
return err
}
// The following check will be fairly quick since the transaction IDs
// are already cached due to building the merkle tree above.
err = dag.checkBlockDuplicateTransactions(block)
if err != nil {
return err
}
err = dag.checkBlockDoubleSpends(block)
if err != nil {
return err
}
return nil
}
func (dag *BlockDAG) checkBlockContainsAtLeastOneTransaction(block *util.Block) error {
transactions := block.Transactions()
numTx := len(transactions)
if numTx == 0 {
return ruleError(ErrNoTransactions, "block does not contain "+
"any transactions")
}
return nil
}
func (dag *BlockDAG) checkBlockContainsLessThanMaxBlockMassTransactions(block *util.Block) error {
// A block must not have more transactions than the max block mass or
// else it is certainly over the block mass limit.
transactions := block.Transactions()
numTx := len(transactions)
if numTx > appmessage.MaxMassAcceptedByBlock {
str := fmt.Sprintf("block contains too many transactions - "+
"got %d, max %d", numTx, appmessage.MaxMassAcceptedByBlock)
return ruleError(ErrBlockMassTooHigh, str)
}
return nil
}
func (dag *BlockDAG) checkFirstBlockTransactionIsCoinbase(block *util.Block) error {
transactions := block.Transactions()
if !transactions[util.CoinbaseTransactionIndex].IsCoinBase() {
return ruleError(ErrFirstTxNotCoinbase, "first transaction in "+
"block is not a coinbase")
}
return nil
}
func (dag *BlockDAG) checkBlockContainsOnlyOneCoinbase(block *util.Block) error {
transactions := block.Transactions()
for i, tx := range transactions[util.CoinbaseTransactionIndex+1:] {
if tx.IsCoinBase() {
str := fmt.Sprintf("block contains second coinbase at "+
"index %d", i+2)
return ruleError(ErrMultipleCoinbases, str)
}
}
return nil
}
func (dag *BlockDAG) checkBlockTransactionOrder(block *util.Block) error {
transactions := block.Transactions()
for i, tx := range transactions[util.CoinbaseTransactionIndex+1:] {
if i != 0 && subnetworkid.Less(&tx.MsgTx().SubnetworkID, &transactions[i].MsgTx().SubnetworkID) {
return ruleError(ErrTransactionsNotSorted, "transactions must be sorted by subnetwork")
}
}
return nil
}
func (dag *BlockDAG) checkNoNonNativeTransactions(block *util.Block) error {
// Disallow non-native/coinbase subnetworks in networks that don't allow them
if !dag.Params.EnableNonNativeSubnetworks {
transactions := block.Transactions()
for _, tx := range transactions {
if !(tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase)) {
return ruleError(ErrInvalidSubnetwork, "non-native/coinbase subnetworks are not allowed")
}
}
}
return nil
}
func (dag *BlockDAG) checkBlockTransactionSanity(block *util.Block) error {
transactions := block.Transactions()
for _, tx := range transactions {
err := CheckTransactionSanity(tx, dag.subnetworkID)
if err != nil {
return err
}
}
return nil
}
func (dag *BlockDAG) checkBlockHashMerkleRoot(block *util.Block) error {
// Build merkle tree and ensure the calculated merkle root matches the
// entry in the block header. This also has the effect of caching all
// of the transaction hashes in the block to speed up future hash
// checks.
hashMerkleTree := BuildHashMerkleTreeStore(block.Transactions())
calculatedHashMerkleRoot := hashMerkleTree.Root()
if !block.MsgBlock().Header.HashMerkleRoot.IsEqual(calculatedHashMerkleRoot) {
str := fmt.Sprintf("block hash merkle root is invalid - block "+
"header indicates %s, but calculated value is %s",
block.MsgBlock().Header.HashMerkleRoot, calculatedHashMerkleRoot)
return ruleError(ErrBadMerkleRoot, str)
}
return nil
}
func (dag *BlockDAG) checkBlockDuplicateTransactions(block *util.Block) error {
existingTxIDs := make(map[daghash.TxID]struct{})
transactions := block.Transactions()
for _, tx := range transactions {
id := tx.ID()
if _, exists := existingTxIDs[*id]; exists {
str := fmt.Sprintf("block contains duplicate "+
"transaction %s", id)
return ruleError(ErrDuplicateTx, str)
}
existingTxIDs[*id] = struct{}{}
}
return nil
}
func (dag *BlockDAG) checkBlockDoubleSpends(block *util.Block) error {
usedOutpoints := make(map[appmessage.Outpoint]*daghash.TxID)
transactions := block.Transactions()
for _, tx := range transactions {
for _, txIn := range tx.MsgTx().TxIn {
if spendingTxID, exists := usedOutpoints[txIn.PreviousOutpoint]; exists {
str := fmt.Sprintf("transaction %s spends "+
"outpoint %s that was already spent by "+
"transaction %s in this block", tx.ID(), txIn.PreviousOutpoint, spendingTxID)
return ruleError(ErrDoubleSpendInSameBlock, str)
}
usedOutpoints[txIn.PreviousOutpoint] = tx.ID()
}
}
return nil
}
// checkBlockHeaderContext performs several validation checks on the block header
// which depend on its position within the block dag.
//
// The flags modify the behavior of this function as follows:
// - BFFastAdd: No checks are performed.
//
// This function MUST be called with the dag state lock held (for writes).
func (dag *BlockDAG) checkBlockHeaderContext(header *appmessage.BlockHeader, bluestParent *blockNode, fastAdd bool) error {
if !fastAdd {
if err := dag.validateDifficulty(header, bluestParent); err != nil {
return err
}
if err := validateMedianTime(header, bluestParent); err != nil {
return err
}
}
return nil
}
func validateMedianTime(header *appmessage.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 := fmt.Sprintf("block timestamp of %s is not after expected %s", header.Timestamp, medianTime)
return ruleError(ErrTimeTooOld, str)
}
}
return nil
}
func (dag *BlockDAG) validateDifficulty(header *appmessage.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 := dag.requiredDifficulty(bluestParent,
header.Timestamp)
blockDifficulty := header.Bits
if blockDifficulty != expectedDifficulty {
str := fmt.Sprintf("block difficulty of %d is not the expected value of %d", blockDifficulty, expectedDifficulty)
return ruleError(ErrUnexpectedDifficulty, str)
}
return nil
}
// validateParents validates that no parent is an ancestor of another parent, and no parent is finalized
func (dag *BlockDAG) validateParents(blockHeader *appmessage.BlockHeader, parents blockSet) error {
if len(parents) > appmessage.MaxNumParentBlocks {
return ruleError(ErrTooManyParents,
fmt.Sprintf("block %s points to %d parents > MaxNumParentBlocks: %d",
blockHeader.BlockHash(), len(parents), appmessage.MaxNumParentBlocks))
}
for parentA := range parents {
for parentB := range parents {
if parentA == parentB {
continue
}
isAncestorOf, err := dag.isInPast(parentA, parentB)
if err != nil {
return err
}
if isAncestorOf {
return ruleError(ErrInvalidParentsRelation, fmt.Sprintf("block %s is both a parent of %s and an"+
" ancestor of another parent %s",
parentA.hash,
blockHeader.BlockHash(),
parentB.hash,
))
}
}
}
return nil
}
// checkBlockContext peforms several validation checks on the block which depend
// on its position within the block DAG.
//
// The flags modify the behavior of this function as follows:
// - BFFastAdd: The transaction are not checked to see if they are finalized
// and the somewhat expensive BIP0034 validation is not performed.
//
// The flags are also passed to checkBlockHeaderContext. See its documentation
// for how the flags modify its behavior.
//
// This function MUST be called with the dag state lock held (for writes).
func (dag *BlockDAG) checkBlockContext(block *util.Block, flags BehaviorFlags) error {
parents, err := lookupParentNodes(block, dag)
if err != nil {
return dag.handleLookupParentNodesError(block, err)
}
bluestParent := parents.bluest()
fastAdd := flags&BFFastAdd == BFFastAdd
err = dag.validateParents(&block.MsgBlock().Header, parents)
if err != nil {
return err
}
// Perform all block header related validation checks.
header := &block.MsgBlock().Header
if err = dag.checkBlockHeaderContext(header, bluestParent, fastAdd); err != nil {
return err
}
return nil
}
func (node *blockNode) checkDAGRelations() error {
err := node.checkMergeSizeLimit()
if err != nil {
return err
}
err = node.checkBoundedMergeDepth()
if err != nil {
return err
}
return nil
}
func (dag *BlockDAG) handleLookupParentNodesError(block *util.Block, err error) error {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
err := dag.addNodeToIndexWithInvalidAncestor(block)
if err != nil {
return err
}
}
return err
}
func (dag *BlockDAG) checkBlockTransactionsFinalized(block *util.Block, node *blockNode, flags BehaviorFlags) error {
fastAdd := flags&BFFastAdd == BFFastAdd || dag.index.BlockNodeStatus(node).KnownValid()
if fastAdd {
return nil
}
blockTime := block.MsgBlock().Header.Timestamp
if !block.IsGenesis() {
blockTime = node.selectedParent.PastMedianTime()
}
// Ensure all transactions in the block are finalized.
for _, tx := range block.Transactions() {
if !IsFinalizedTransaction(tx, node.blueScore, blockTime) {
str := fmt.Sprintf("block contains unfinalized "+
"transaction %s", tx.ID())
return ruleError(ErrUnfinalizedTx, str)
}
}
return nil
}
func (dag *BlockDAG) checkBlockHasNoChainedTransactions(block *util.Block, node *blockNode, flags BehaviorFlags) error {
fastAdd := flags&BFFastAdd == BFFastAdd || dag.index.BlockNodeStatus(node).KnownValid()
if fastAdd {
return nil
}
transactions := block.Transactions()
transactionsSet := make(map[daghash.TxID]struct{}, len(transactions))
for _, transaction := range transactions {
transactionsSet[*transaction.ID()] = struct{}{}
}
for _, transaction := range transactions {
for i, transactionInput := range transaction.MsgTx().TxIn {
if _, ok := transactionsSet[transactionInput.PreviousOutpoint.TxID]; ok {
str := fmt.Sprintf("block contains chained transactions: Input %d of transaction %s spend"+
"an output of transaction %s", i, transaction.ID(), transactionInput.PreviousOutpoint.TxID)
return ruleError(ErrChainedTransactions, 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
// duplicated to effectively revert the overwritten transactions to a single
// confirmation thereby making them vulnerable to a double spend.
//
// For more details, see http://r6.ca/blog/20120206T005236Z.html.
//
// This function MUST be called with the dag state lock held (for reads).
func ensureNoDuplicateTx(utxoSet UTXOSet, transactions []*util.Tx) 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[appmessage.Outpoint]struct{})
for _, tx := range transactions {
prevOut := appmessage.Outpoint{TxID: *tx.ID()}
for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx)
fetchSet[prevOut] = struct{}{}
}
}
// Duplicate transactions are only allowed if the previous transaction
// is fully spent.
for outpoint := range fetchSet {
if _, ok := utxoSet.Get(outpoint); ok {
str := fmt.Sprintf("tried to overwrite transaction %s "+
"that is not fully spent", outpoint.TxID)
return ruleError(ErrOverwriteTx, str)
}
}
return nil
}
func checkTxIsNotDuplicate(tx *util.Tx, utxoSet UTXOSet) error {
fetchSet := make(map[appmessage.Outpoint]struct{})
// Fetch utxos for all of the ouputs in this transaction.
// Typically, there will not be any utxos for any of the outputs.
prevOut := appmessage.Outpoint{TxID: *tx.ID()}
for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx)
fetchSet[prevOut] = struct{}{}
}
// Duplicate transactions are only allowed if the previous transaction
// is fully spent.
for outpoint := range fetchSet {
if _, ok := utxoSet.Get(outpoint); ok {
str := fmt.Sprintf("tried to overwrite transaction %s "+
"that is not fully spent", outpoint.TxID)
return ruleError(ErrOverwriteTx, str)
}
}
return nil
}
// CheckTransactionInputsAndCalulateFee performs a series of checks on the inputs to a
// transaction to ensure they are valid. An example of some of the checks
// 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. 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 CheckTransactionInputsAndCalulateFee(
tx *util.Tx, txBlueScore uint64, utxoSet UTXOSet, dagParams *dagconfig.Params, fastAdd bool) (
txFeeInSompi uint64, err error) {
// Coinbase transactions have no standard inputs to validate.
if tx.IsCoinBase() {
return 0, nil
}
var totalSompiIn uint64
for txInIndex, txIn := range tx.MsgTx().TxIn {
entry, err := findReferencedOutput(tx, utxoSet, txIn, txInIndex)
if err != nil {
return 0, err
}
if !fastAdd {
if err = validateCoinbaseMaturity(dagParams, entry, txBlueScore, txIn); err != nil {
return 0, err
}
}
totalSompiIn, err = checkEntryAmounts(entry, totalSompiIn)
if err != nil {
return 0, err
}
}
totalSompiOut, err := checkTxOutputAmounts(tx, totalSompiIn)
if err != nil {
return 0, err
}
txFeeInSompi = totalSompiIn - totalSompiOut
return txFeeInSompi, nil
}
func checkEntryAmounts(entry *UTXOEntry, totalSompiInBefore uint64) (totalSompiInAfter uint64, err error) {
// The total of all outputs must not be more than the max
// allowed per transaction. Also, we could potentially overflow
// the accumulator so check for overflow.
lastSompiIn := totalSompiInBefore
originTxSompi := entry.Amount()
totalSompiInAfter = totalSompiInBefore + originTxSompi
if totalSompiInBefore < lastSompiIn ||
totalSompiInBefore > util.MaxSompi {
str := fmt.Sprintf("total value of all transaction "+
"inputs is %d which is higher than max "+
"allowed value of %d", totalSompiInBefore,
util.MaxSompi)
return 0, ruleError(ErrBadTxOutValue, str)
}
return totalSompiInAfter, nil
}
func findReferencedOutput(
tx *util.Tx, utxoSet UTXOSet, txIn *appmessage.TxIn, txInIndex int) (*UTXOEntry, error) {
entry, ok := utxoSet.Get(txIn.PreviousOutpoint)
if !ok {
str := fmt.Sprintf("output %s referenced from "+
"transaction %s input %d either does not exist or "+
"has already been spent", txIn.PreviousOutpoint,
tx.ID(), txInIndex)
return nil, ruleError(ErrMissingTxOut, str)
}
return entry, nil
}
func validateCoinbaseMaturity(dagParams *dagconfig.Params, entry *UTXOEntry, txBlueScore uint64, txIn *appmessage.TxIn) error {
// Ensure the transaction is not spending coins which have not
// yet reached the required coinbase maturity.
if entry.IsCoinbase() {
originBlueScore := entry.BlockBlueScore()
blueScoreSincePrev := txBlueScore - originBlueScore
if blueScoreSincePrev < dagParams.BlockCoinbaseMaturity {
str := fmt.Sprintf("tried to spend coinbase "+
"transaction output %s from blue score %d "+
"to blue score %d before required maturity "+
"of %d", txIn.PreviousOutpoint,
originBlueScore, txBlueScore,
dagParams.BlockCoinbaseMaturity)
return ruleError(ErrImmatureSpend, str)
}
}
return nil
}
func (dag *BlockDAG) checkConnectBlockToPastUTXO(
node *blockNode, pastUTXO UTXOSet, transactions []*util.Tx) (err error) {
selectedParentMedianTime := node.selectedParentMedianTime()
totalFee := uint64(0)
for _, tx := range transactions {
txFee, _, err :=
dag.checkConnectTransactionToPastUTXO(node, tx, pastUTXO, 0, selectedParentMedianTime)
if err != nil {
return err
}
totalFee, err = dag.checkTotalFee(totalFee, txFee)
if err != nil {
return err
}
}
return nil
}
type txInputAndUTXOEntry struct {
txIn *appmessage.TxIn
utxoEntry *UTXOEntry
}
func (dag *BlockDAG) checkConnectTransactionToPastUTXO(
node *blockNode, tx *util.Tx, pastUTXO UTXOSet, accumulatedMassBefore uint64, selectedParentMedianTime mstime.Time) (
txFee uint64, accumulatedMassAfter uint64, err error) {
err = checkTxIsNotDuplicate(tx, pastUTXO)
if err != nil {
return 0, 0, err
}
inputsWithUTXOEntries, err := dag.getReferencedUTXOEntries(tx, pastUTXO)
if err != nil {
return 0, 0, err
}
accumulatedMassAfter, err = dag.checkTxMass(tx, inputsWithUTXOEntries, accumulatedMassBefore)
if err != nil {
return 0, 0, err
}
err = dag.checkTxCoinbaseMaturity(node, inputsWithUTXOEntries)
if err != nil {
return 0, 0, nil
}
totalSompiIn, err := dag.checkTxInputAmounts(inputsWithUTXOEntries)
if err != nil {
return 0, 0, nil
}
totalSompiOut, err := checkTxOutputAmounts(tx, totalSompiIn)
if err != nil {
return 0, 0, nil
}
txFee = totalSompiIn - totalSompiOut
err = dag.checkTxSequenceLock(node, tx, inputsWithUTXOEntries, selectedParentMedianTime)
if err != nil {
return 0, 0, nil
}
err = ValidateTransactionScripts(tx, pastUTXO, txscript.ScriptNoFlags, dag.sigCache)
if err != nil {
return 0, 0, err
}
return txFee, accumulatedMassAfter, nil
}
func (dag *BlockDAG) checkTxSequenceLock(node *blockNode, tx *util.Tx,
inputsWithUTXOEntries []*txInputAndUTXOEntry, medianTime mstime.Time) error {
// A transaction can only be included within a block
// once the sequence locks of *all* its inputs are
// active.
sequenceLock, err := dag.calcTxSequenceLockFromInputsWithUTXOEntries(node, tx, inputsWithUTXOEntries)
if err != nil {
return err
}
if !SequenceLockActive(sequenceLock, node.blueScore, medianTime) {
str := fmt.Sprintf("block contains " +
"transaction whose input sequence " +
"locks are not met")
return ruleError(ErrUnfinalizedTx, str)
}
return nil
}
func checkTxOutputAmounts(tx *util.Tx, totalSompiIn uint64) (uint64, error) {
totalSompiOut := uint64(0)
// Calculate the total output amount for this transaction. It is safe
// to ignore overflow and out of range errors here because those error
// conditions would have already been caught by checkTransactionSanity.
for _, txOut := range tx.MsgTx().TxOut {
totalSompiOut += txOut.Value
}
// Ensure the transaction does not spend more than its inputs.
if totalSompiIn < totalSompiOut {
str := fmt.Sprintf("total value of all transaction inputs for "+
"transaction %s is %d which is less than the amount "+
"spent of %d", tx.ID(), totalSompiIn, totalSompiOut)
return 0, ruleError(ErrSpendTooHigh, str)
}
return totalSompiOut, nil
}
func (dag *BlockDAG) checkTxInputAmounts(
inputsWithUTXOEntries []*txInputAndUTXOEntry) (totalSompiIn uint64, err error) {
totalSompiIn = 0
for _, txInAndReferencedUTXOEntry := range inputsWithUTXOEntries {
utxoEntry := txInAndReferencedUTXOEntry.utxoEntry
// Ensure the transaction amounts are in range. Each of the
// output values of the input transactions must not be negative
// or more than the max allowed per transaction. All amounts in
// a transaction are in a unit value known as a sompi. One
// kaspa is a quantity of sompi as defined by the
// SompiPerKaspa constant.
totalSompiIn, err = checkEntryAmounts(utxoEntry, totalSompiIn)
if err != nil {
return 0, err
}
}
return totalSompiIn, nil
}
func (dag *BlockDAG) checkTxCoinbaseMaturity(
node *blockNode, inputsWithUTXOEntries []*txInputAndUTXOEntry) error {
txBlueScore := node.blueScore
for _, txInAndReferencedUTXOEntry := range inputsWithUTXOEntries {
txIn := txInAndReferencedUTXOEntry.txIn
utxoEntry := txInAndReferencedUTXOEntry.utxoEntry
if utxoEntry.IsCoinbase() {
originBlueScore := utxoEntry.BlockBlueScore()
blueScoreSincePrev := txBlueScore - originBlueScore
if blueScoreSincePrev < dag.Params.BlockCoinbaseMaturity {
str := fmt.Sprintf("tried to spend coinbase "+
"transaction output %s from blue score %d "+
"to blue score %d before required maturity "+
"of %d", txIn.PreviousOutpoint,
originBlueScore, txBlueScore,
dag.Params.BlockCoinbaseMaturity)
return ruleError(ErrImmatureSpend, str)
}
}
}
return nil
}
func (dag *BlockDAG) checkTxMass(tx *util.Tx, inputsWithUTXOEntries []*txInputAndUTXOEntry,
accumulatedMassBefore uint64) (accumulatedMassAfter uint64, err error) {
txMass := calcTxMassFromInputsWithUTXOEntries(tx, inputsWithUTXOEntries)
accumulatedMassAfter = accumulatedMassBefore + txMass
// We could potentially overflow the accumulator so check for
// overflow as well.
if accumulatedMassAfter < txMass || accumulatedMassAfter > appmessage.MaxMassAcceptedByBlock {
str := fmt.Sprintf("block accepts transactions with accumulated mass higher then allowed limit of %d",
appmessage.MaxMassAcceptedByBlock)
return 0, ruleError(ErrBlockMassTooHigh, str)
}
return accumulatedMassAfter, nil
}
func (dag *BlockDAG) getReferencedUTXOEntries(tx *util.Tx, utxoSet UTXOSet) (
[]*txInputAndUTXOEntry, error) {
txIns := tx.MsgTx().TxIn
inputsWithUTXOEntries := make([]*txInputAndUTXOEntry, 0, len(txIns))
for txInIndex, txIn := range txIns {
utxoEntry, ok := utxoSet.Get(txIn.PreviousOutpoint)
if !ok {
str := fmt.Sprintf("output %s referenced from "+
"transaction %s input %d either does not exist or "+
"has already been spent", txIn.PreviousOutpoint,
tx.ID(), txInIndex)
return nil, ruleError(ErrMissingTxOut, str)
}
inputsWithUTXOEntries = append(inputsWithUTXOEntries, &txInputAndUTXOEntry{
txIn: txIn,
utxoEntry: utxoEntry,
})
}
return inputsWithUTXOEntries, nil
}
func (dag *BlockDAG) checkTotalFee(totalFees uint64, txFee uint64) (uint64, error) {
// Sum the total fees and ensure we don't overflow the
// accumulator.
lastTotalFees := totalFees
totalFees += txFee
if totalFees < lastTotalFees || totalFees > util.MaxSompi {
str := fmt.Sprintf("total fees are higher then max allowed value of %d", util.MaxSompi)
return 0, ruleError(ErrBadFees, str)
}
return totalFees, nil
}
func (node *blockNode) validateUTXOCommitment(multiset *secp256k1.MultiSet) error {
calculatedMultisetHash := daghash.Hash(*multiset.Finalize())
if !calculatedMultisetHash.IsEqual(node.utxoCommitment) {
str := fmt.Sprintf("block %s UTXO commitment is invalid - block "+
"header indicates %s, but calculated value is %s", node.hash,
node.utxoCommitment, calculatedMultisetHash)
return ruleError(ErrBadUTXOCommitment, str)
}
return nil
}
// CheckConnectBlockTemplate fully validates that connecting the passed block to
// the DAG does not violate any consensus rules, aside from the proof of
// work requirement.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) CheckConnectBlockTemplate(block *util.Block) error {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
return dag.CheckConnectBlockTemplateNoLock(block)
}
// CheckConnectBlockTemplateNoLock fully validates that connecting the passed block to
// the DAG does not violate any consensus rules, aside from the proof of
// work requirement. The block must connect to the current tip of the main dag.
func (dag *BlockDAG) CheckConnectBlockTemplateNoLock(block *util.Block) error {
// Skip the proof of work check as this is just a block template.
flags := BFNoPoWCheck
header := block.MsgBlock().Header
err := dag.checkBlockSanity(block, flags)
if err != nil {
return err
}
_, isDelayed := dag.shouldBlockBeDelayed(block)
if isDelayed {
return errors.Errorf("Block timestamp is too far in the future")
}
err = dag.checkBlockContext(block, flags)
if err != nil {
return err
}
templateParents, err := dag.index.LookupNodes(header.ParentHashes)
if err != nil {
return err
}
templateNode, _ := dag.newBlockNode(&header, blockSetFromSlice(templateParents...))
return dag.checkConnectBlockToPastUTXO(templateNode, dag.UTXOSet(), block.Transactions())
}
func (dag *BlockDAG) checkDuplicateBlock(blockHash *daghash.Hash, flags BehaviorFlags) error {
wasBlockStored := flags&BFWasStored == BFWasStored
if dag.IsInDAG(blockHash) && !wasBlockStored {
str := fmt.Sprintf("already have block %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
// The block must not already exist as an orphan.
if _, exists := dag.orphans[*blockHash]; exists {
str := fmt.Sprintf("already have block (orphan) %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
if dag.isKnownDelayedBlock(blockHash) {
str := fmt.Sprintf("already have block (delayed) %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
return nil
}