Improve the RuleError type to include error codes.
This commit changes the RuleError type to a struct which consists of an error code and human-readable description. From a usage perspective, existing code should not break since type asserting an error to a RuleError still works in the same manner. The difference is the caller can now take that type asserted RuleError and access the .ErrorCode field on it to programmatically identify the specific rule that was violated. ok @jrick
This commit is contained in:
parent
87bef61b30
commit
6e0aab52df
6 changed files with 303 additions and 70 deletions
15
accept.go
15
accept.go
|
@ -47,7 +47,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, fastAdd bool) error
|
|||
if blockDifficulty != expectedDifficulty {
|
||||
str := "block difficulty of %d is not the expected value of %d"
|
||||
str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrUnexpectedDifficulty, str)
|
||||
}
|
||||
|
||||
// Ensure the timestamp for the block header is after the
|
||||
|
@ -61,7 +61,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, fastAdd bool) error
|
|||
str := "block timestamp of %v is not after expected %v"
|
||||
str = fmt.Sprintf(str, blockHeader.Timestamp,
|
||||
medianTime)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrTimeTooOld, str)
|
||||
}
|
||||
|
||||
// Ensure all transactions in the block are finalized.
|
||||
|
@ -70,7 +70,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, fastAdd bool) error
|
|||
blockHeader.Timestamp) {
|
||||
str := fmt.Sprintf("block contains "+
|
||||
"unfinalized transaction %v", tx.Sha())
|
||||
return RuleError(str)
|
||||
return ruleError(ErrUnfinalizedTx, str)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, fastAdd bool) error
|
|||
// known good point.
|
||||
str := fmt.Sprintf("block at height %d does not match "+
|
||||
"checkpoint hash", blockHeight)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadCheckpoint, str)
|
||||
}
|
||||
|
||||
// Find the previous checkpoint and prevent blocks which fork the main
|
||||
|
@ -103,7 +103,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, fastAdd bool) error
|
|||
str := fmt.Sprintf("block at height %d forks the main chain "+
|
||||
"before the previous checkpoint at height %d",
|
||||
blockHeight, checkpointBlock.Height())
|
||||
return RuleError(str)
|
||||
return ruleError(ErrForkTooOld, str)
|
||||
}
|
||||
|
||||
if !fastAdd {
|
||||
|
@ -114,9 +114,10 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, fastAdd bool) error
|
|||
b.netParams.BlockV1RejectNumRequired,
|
||||
b.netParams.BlockV1RejectNumToCheck) {
|
||||
|
||||
str := "new blocks with version %d are no longer valid"
|
||||
str := "new blocks with version %d are no " +
|
||||
"longer valid"
|
||||
str = fmt.Sprintf(str, blockHeader.Version)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBlockVersionTooOld, str)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
4
doc.go
4
doc.go
|
@ -124,7 +124,9 @@ Errors
|
|||
Errors returned by this package are either the raw errors provided by underlying
|
||||
calls or of type btcchain.RuleError. This allows the caller to differentiate
|
||||
between unexpected errors, such as database errors, versus errors due to rule
|
||||
violations through type assertions.
|
||||
violations through type assertions. In addition, callers can programmatically
|
||||
determine the specific rule violation by examining the ErrorCode field of the
|
||||
type asserted btcchain.RuleError.
|
||||
|
||||
Bitcoin Improvement Proposals
|
||||
|
||||
|
|
218
error.go
Normal file
218
error.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
// Copyright (c) 2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcchain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrorCode identifies a kind of error.
|
||||
type ErrorCode int
|
||||
|
||||
// These constants are used to identify a specific RuleError.
|
||||
const (
|
||||
// ErrDuplicateBlock indicates a block with the same hash already
|
||||
// exists.
|
||||
ErrDuplicateBlock ErrorCode = iota
|
||||
|
||||
// ErrBlockVersionTooOld indicates the block version is too old and is
|
||||
// no longer accepted since the majority of the network has upgraded
|
||||
// to a newer version.
|
||||
ErrBlockVersionTooOld
|
||||
|
||||
// ErrInvalidTime indicates the time in the passed block has a precision
|
||||
// that is more than one second. The chain consensus rules require
|
||||
// timestamps to have a maximum precision of one second.
|
||||
ErrInvalidTime
|
||||
|
||||
// ErrTimeTooOld indicates the time is either before the median time of
|
||||
// the last several blocks per the chain consensus rules or prior to the
|
||||
// most recent checkpoint.
|
||||
ErrTimeTooOld
|
||||
|
||||
// ErrTimeTooNew indicates the time is too far in the future as compared
|
||||
// the current time.
|
||||
ErrTimeTooNew
|
||||
|
||||
// ErrDifficultyTooLow indicates the difficulty for the block is lower
|
||||
// than the difficulty required by the most recent checkpoint.
|
||||
ErrDifficultyTooLow
|
||||
|
||||
// ErrUnexpectedDifficulty indicates specified bits do not align with
|
||||
// the expected value either because it doesn't match the calculated
|
||||
// valued based on difficulty regarted rules or it is out of the valid
|
||||
// range.
|
||||
ErrUnexpectedDifficulty
|
||||
|
||||
// ErrHighHash indicates the block does not hash to a value which is
|
||||
// lower than the required target difficultly.
|
||||
ErrHighHash
|
||||
|
||||
// ErrBadMerkleRoot indicates the calculated merkle root does not match
|
||||
// the expected value.
|
||||
ErrBadMerkleRoot
|
||||
|
||||
// ErrBadCheckpoint indicates a block that is expected to be at a
|
||||
// checkpoint height does not match the expected one.
|
||||
ErrBadCheckpoint
|
||||
|
||||
// ErrForkTooOld indicates a block is attempted to fork the block chain
|
||||
// before the most recent checkpoint.
|
||||
ErrForkTooOld
|
||||
|
||||
// ErrNoTransactions indicates the block does not have a least one
|
||||
// transaction. A valid block must have at least the coinbase
|
||||
// transaction.
|
||||
ErrNoTransactions
|
||||
|
||||
// ErrNoTxInputs indicates a transaction does not have any inputs. A
|
||||
// valid transaction must have at least one input.
|
||||
ErrNoTxInputs
|
||||
|
||||
// ErrNoTxOutputs indicates a transaction does not have any outputs. A
|
||||
// valid transaction must have at least one output.
|
||||
ErrNoTxOutputs
|
||||
|
||||
// ErrBadTxOutValue indicates an output value for a transaction is
|
||||
// invalid in some way such as being out of range.
|
||||
ErrBadTxOutValue
|
||||
|
||||
// ErrDuplicateTxInputs indicates a transaction references the same
|
||||
// input more than once.
|
||||
ErrDuplicateTxInputs
|
||||
|
||||
// ErrBadTxInput indicates a transaction input is invalid in some way
|
||||
// such as referencing a previous transaction outpoint which is out of
|
||||
// range or not referencing one at all.
|
||||
ErrBadTxInput
|
||||
|
||||
// ErrMissingTx indicates a transaction referenced by an input is
|
||||
// missing.
|
||||
ErrMissingTx
|
||||
|
||||
// ErrUnfinalizedTx indicates a transaction has not been finalized.
|
||||
// A valid block may only contain finalized transactions.
|
||||
ErrUnfinalizedTx
|
||||
|
||||
// ErrDuplicateTx indicates a block contains an identical transaction
|
||||
// (or at least two transactions which hash to the same value). A
|
||||
// valid block may only contain unique transactions.
|
||||
ErrDuplicateTx
|
||||
|
||||
// ErrOverwriteTx indicates a block contains a transaction that has
|
||||
// the same hash as a previous transaction which has not been fully
|
||||
// spent.
|
||||
ErrOverwriteTx
|
||||
|
||||
// ErrImmatureSpend indicates a transaction is attempting to spend a
|
||||
// coinbase that has not yet reached the required maturity.
|
||||
ErrImmatureSpend
|
||||
|
||||
// ErrDoubleSpend indicates a transaction is attempting to spend coins
|
||||
// that have already been spent.
|
||||
ErrDoubleSpend
|
||||
|
||||
// ErrSpendTooHigh indicates a transaction is attempting to spend more
|
||||
// value than the sum of all of its inputs.
|
||||
ErrSpendTooHigh
|
||||
|
||||
// ErrBadFees indicates the total fees for a block are invalid due to
|
||||
// exceeding the maximum possible value.
|
||||
ErrBadFees
|
||||
|
||||
// ErrTooManySigOps indicates the total number of signature operations
|
||||
// for a transaction or block exceed the maximum allowed limits.
|
||||
ErrTooManySigOps
|
||||
|
||||
// ErrFirstTxNotCoinbase indicates the first transaction in a block
|
||||
// is not a coinbase transaction.
|
||||
ErrFirstTxNotCoinbase
|
||||
|
||||
// ErrMultipleCoinbases indicates a block contains more than one
|
||||
// coinbase transaction.
|
||||
ErrMultipleCoinbases
|
||||
|
||||
// ErrBadCoinbaseScriptLen indicates the length of the signature script
|
||||
// for a coinbase transaction is not within the valid range.
|
||||
ErrBadCoinbaseScriptLen
|
||||
|
||||
// ErrBadCoinbaseValue indicates the amount of a coinbase value does
|
||||
// not match the expected value of the subsidy plus the sum of all fees.
|
||||
ErrBadCoinbaseValue
|
||||
|
||||
// ErrMissingCoinbaseHeight indicates the coinbase transaction for a
|
||||
// block does not start with the serialized block block height as
|
||||
// required for version 2 and higher blocks.
|
||||
ErrMissingCoinbaseHeight
|
||||
|
||||
// ErrBadCoinbaseHeight indicates the serialized block height in the
|
||||
// coinbase transaction for version 2 and higher blocks does not match
|
||||
// the expected value.
|
||||
ErrBadCoinbaseHeight
|
||||
)
|
||||
|
||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||
var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrDuplicateBlock: "ErrDuplicateBlock",
|
||||
ErrBlockVersionTooOld: "ErrBlockVersionTooOld",
|
||||
ErrInvalidTime: "ErrInvalidTime",
|
||||
ErrTimeTooOld: "ErrTimeTooOld",
|
||||
ErrTimeTooNew: "ErrTimeTooNew",
|
||||
ErrDifficultyTooLow: "ErrDifficultyTooLow",
|
||||
ErrUnexpectedDifficulty: "ErrUnexpectedDifficulty",
|
||||
ErrHighHash: "ErrHighHash",
|
||||
ErrBadMerkleRoot: "ErrBadMerkleRoot",
|
||||
ErrBadCheckpoint: "ErrBadCheckpoint",
|
||||
ErrForkTooOld: "ErrForkTooOld",
|
||||
ErrNoTransactions: "ErrNoTransactions",
|
||||
ErrNoTxInputs: "ErrNoTxInputs",
|
||||
ErrNoTxOutputs: "ErrNoTxOutputs",
|
||||
ErrBadTxOutValue: "ErrBadTxOutValue",
|
||||
ErrDuplicateTxInputs: "ErrDuplicateTxInputs",
|
||||
ErrBadTxInput: "ErrBadTxInput",
|
||||
ErrMissingTx: "ErrMissingTx",
|
||||
ErrUnfinalizedTx: "ErrUnfinalizedTx",
|
||||
ErrDuplicateTx: "ErrDuplicateTx",
|
||||
ErrOverwriteTx: "ErrOverwriteTx",
|
||||
ErrImmatureSpend: "ErrImmatureSpend",
|
||||
ErrDoubleSpend: "ErrDoubleSpend",
|
||||
ErrSpendTooHigh: "ErrSpendTooHigh",
|
||||
ErrBadFees: "ErrBadFees",
|
||||
ErrTooManySigOps: "ErrTooManySigOps",
|
||||
ErrFirstTxNotCoinbase: "ErrFirstTxNotCoinbase",
|
||||
ErrMultipleCoinbases: "ErrMultipleCoinbases",
|
||||
ErrBadCoinbaseScriptLen: "ErrBadCoinbaseScriptLen",
|
||||
ErrBadCoinbaseValue: "ErrBadCoinbaseValue",
|
||||
ErrMissingCoinbaseHeight: "ErrMissingCoinbaseHeight",
|
||||
ErrBadCoinbaseHeight: "ErrBadCoinbaseHeight",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
func (e ErrorCode) String() string {
|
||||
if s := errorCodeStrings[e]; s != "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
|
||||
}
|
||||
|
||||
// RuleError identifies a rule violation. It is used to indicate that
|
||||
// processing of a block or transaction failed due to one of the many validation
|
||||
// rules. The caller can use type assertions to determine if a failure was
|
||||
// specifically due to a rule violation and access the ErrorCode field to
|
||||
// ascertain the specific reason for the rule violation.
|
||||
type RuleError struct {
|
||||
ErrorCode ErrorCode // Describes the kind of error
|
||||
Description string // Human readable description of the issue
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e RuleError) Error() string {
|
||||
return e.Description
|
||||
}
|
||||
|
||||
// ruleError creates an RuleError given a set of arguments.
|
||||
func ruleError(c ErrorCode, desc string) RuleError {
|
||||
return RuleError{ErrorCode: c, Description: desc}
|
||||
}
|
19
process.go
19
process.go
|
@ -10,17 +10,6 @@ import (
|
|||
"github.com/conformal/btcwire"
|
||||
)
|
||||
|
||||
// RuleError identifies a rule violation. It is used to indicate that
|
||||
// processing of a block or transaction failed due to one of the many validation
|
||||
// rules. The caller can use type assertions to determine if a failure was
|
||||
// specifically due to a rule violation.
|
||||
type RuleError string
|
||||
|
||||
// Error satisfies the error interface to print human-readable errors.
|
||||
func (e RuleError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// blockExists determines whether a block with the given hash exists either in
|
||||
// the main chain or any side chains.
|
||||
func (b *BlockChain) blockExists(hash *btcwire.ShaHash) bool {
|
||||
|
@ -102,13 +91,13 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, fastAdd bool) error {
|
|||
// The block must not already exist in the main chain or side chains.
|
||||
if b.blockExists(blockHash) {
|
||||
str := fmt.Sprintf("already have block %v", blockHash)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
// The block must not already exist as an orphan.
|
||||
if _, exists := b.orphans[*blockHash]; exists {
|
||||
str := fmt.Sprintf("already have block (orphan) %v", blockHash)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
// Perform preliminary sanity checks on the block and its transactions.
|
||||
|
@ -136,7 +125,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, fastAdd bool) error {
|
|||
str := fmt.Sprintf("block %v has timestamp %v before "+
|
||||
"last checkpoint timestamp %v", blockHash,
|
||||
blockHeader.Timestamp, checkpointTime)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrTimeTooOld, str)
|
||||
}
|
||||
if !fastAdd {
|
||||
// Even though the checks prior to now have already ensured the
|
||||
|
@ -153,7 +142,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, fastAdd bool) error {
|
|||
str := fmt.Sprintf("block target difficulty of %064x "+
|
||||
"is too low when compared to the previous "+
|
||||
"checkpoint", currentTarget)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrDifficultyTooLow, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
87
validate.go
87
validate.go
|
@ -181,12 +181,12 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
|||
// A transaction must have at least one input.
|
||||
msgTx := tx.MsgTx()
|
||||
if len(msgTx.TxIn) == 0 {
|
||||
return RuleError("transaction has no inputs")
|
||||
return ruleError(ErrNoTxInputs, "transaction has no inputs")
|
||||
}
|
||||
|
||||
// A transaction must have at least one output.
|
||||
if len(msgTx.TxOut) == 0 {
|
||||
return RuleError("transaction has no outputs")
|
||||
return ruleError(ErrNoTxOutputs, "transaction has no outputs")
|
||||
}
|
||||
|
||||
// NOTE: bitcoind does size limits checking here, but the size limits
|
||||
|
@ -206,13 +206,13 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
|||
if satoshi < 0 {
|
||||
str := fmt.Sprintf("transaction output has negative "+
|
||||
"value of %v", satoshi)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
if satoshi > btcutil.MaxSatoshi {
|
||||
str := fmt.Sprintf("transaction output value of %v is "+
|
||||
"higher than max allowed value of %v", satoshi,
|
||||
btcutil.MaxSatoshi)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
|
||||
// TODO(davec): No need to check < 0 here as satoshi is
|
||||
|
@ -222,14 +222,14 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
|||
if totalSatoshi < 0 {
|
||||
str := fmt.Sprintf("total value of all transaction "+
|
||||
"outputs has negative value of %v", totalSatoshi)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
if totalSatoshi > btcutil.MaxSatoshi {
|
||||
str := fmt.Sprintf("total value of all transaction "+
|
||||
"outputs is %v which is higher than max "+
|
||||
"allowed value of %v", totalSatoshi,
|
||||
btcutil.MaxSatoshi)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,7 +237,8 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
|||
existingTxOut := make(map[btcwire.OutPoint]bool)
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
if _, exists := existingTxOut[txIn.PreviousOutpoint]; exists {
|
||||
return RuleError("transaction contains duplicate outpoint")
|
||||
return ruleError(ErrDuplicateTxInputs, "transaction "+
|
||||
"contains duplicate inputs")
|
||||
}
|
||||
existingTxOut[txIn.PreviousOutpoint] = true
|
||||
}
|
||||
|
@ -249,7 +250,7 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
|||
str := fmt.Sprintf("coinbase transaction script length "+
|
||||
"of %d is out of range (min: %d, max: %d)",
|
||||
slen, MinCoinbaseScriptLen, MaxCoinbaseScriptLen)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadCoinbaseScriptLen, str)
|
||||
}
|
||||
} else {
|
||||
// Previous transaction outputs referenced by the inputs to this
|
||||
|
@ -257,8 +258,9 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
|||
for _, txIn := range msgTx.TxIn {
|
||||
prevOut := &txIn.PreviousOutpoint
|
||||
if isNullOutpoint(prevOut) {
|
||||
return RuleError("transaction input refers to " +
|
||||
"previous output that is null")
|
||||
return ruleError(ErrBadTxInput, "transaction "+
|
||||
"input refers to previous output that "+
|
||||
"is null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,14 +277,14 @@ func CheckProofOfWork(block *btcutil.Block, powLimit *big.Int) error {
|
|||
if target.Sign() <= 0 {
|
||||
str := fmt.Sprintf("block target difficulty of %064x is too low",
|
||||
target)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrUnexpectedDifficulty, str)
|
||||
}
|
||||
|
||||
// The target difficulty must be less than the maximum allowed.
|
||||
if target.Cmp(powLimit) > 0 {
|
||||
str := fmt.Sprintf("block target difficulty of %064x is "+
|
||||
"higher than max of %064x", target, powLimit)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrUnexpectedDifficulty, str)
|
||||
}
|
||||
|
||||
// The block hash must be less than the claimed target.
|
||||
|
@ -294,7 +296,7 @@ func CheckProofOfWork(block *btcutil.Block, powLimit *big.Int) error {
|
|||
if hashNum.Cmp(target) > 0 {
|
||||
str := fmt.Sprintf("block hash of %064x is higher than "+
|
||||
"expected max of %064x", hashNum, target)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrHighHash, str)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -347,7 +349,7 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, txStore TxStore) (int, e
|
|||
str := fmt.Sprintf("unable to find input transaction "+
|
||||
"%v referenced from transaction %v", txInHash,
|
||||
tx.Sha())
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrMissingTx, str)
|
||||
}
|
||||
originMsgTx := originTx.Tx.MsgTx()
|
||||
|
||||
|
@ -358,7 +360,7 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, txStore TxStore) (int, e
|
|||
str := fmt.Sprintf("out of bounds input index %d in "+
|
||||
"transaction %v referenced from transaction %v",
|
||||
originTxIndex, txInHash, tx.Sha())
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrBadTxInput, str)
|
||||
}
|
||||
|
||||
// We're only interested in pay-to-script-hash types, so skip
|
||||
|
@ -383,7 +385,7 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, txStore TxStore) (int, e
|
|||
"output index %d in transaction %v contains "+
|
||||
"too many signature operations - overflow",
|
||||
originTxIndex, txInHash)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrTooManySigOps, str)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,25 +417,27 @@ func CheckBlockSanity(block *btcutil.Block, powLimit *big.Int) error {
|
|||
if !header.Timestamp.Equal(time.Unix(header.Timestamp.Unix(), 0)) {
|
||||
str := fmt.Sprintf("block timestamp of %v has a higher "+
|
||||
"precision than one second", header.Timestamp)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrInvalidTime, str)
|
||||
}
|
||||
|
||||
// Ensure the block time is not more than 2 hours in the future.
|
||||
if header.Timestamp.After(time.Now().Add(time.Hour * 2)) {
|
||||
str := fmt.Sprintf("block timestamp of %v is too far in the "+
|
||||
"future", header.Timestamp)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrTimeTooNew, str)
|
||||
}
|
||||
|
||||
// A block must have at least one transaction.
|
||||
transactions := block.Transactions()
|
||||
if len(transactions) == 0 {
|
||||
return RuleError("block does not contain any transactions")
|
||||
return ruleError(ErrNoTransactions, "block does not contain "+
|
||||
"any transactions")
|
||||
}
|
||||
|
||||
// The first transaction in a block must be a coinbase.
|
||||
if !IsCoinBase(transactions[0]) {
|
||||
return RuleError("first transaction in block is not a coinbase")
|
||||
return ruleError(ErrFirstTxNotCoinbase, "first transaction in "+
|
||||
"block is not a coinbase")
|
||||
}
|
||||
|
||||
// A block must not have more than one coinbase.
|
||||
|
@ -441,7 +445,7 @@ func CheckBlockSanity(block *btcutil.Block, powLimit *big.Int) error {
|
|||
if IsCoinBase(tx) {
|
||||
str := fmt.Sprintf("block contains second coinbase at "+
|
||||
"index %d", i)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrMultipleCoinbases, str)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -466,7 +470,7 @@ func CheckBlockSanity(block *btcutil.Block, powLimit *big.Int) error {
|
|||
str := fmt.Sprintf("block merkle root is invalid - block "+
|
||||
"header indicates %v, but calculated value is %v",
|
||||
header.MerkleRoot, calculatedMerkleRoot)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadMerkleRoot, str)
|
||||
}
|
||||
|
||||
// Check for duplicate transactions. This check will be fairly quick
|
||||
|
@ -478,7 +482,7 @@ func CheckBlockSanity(block *btcutil.Block, powLimit *big.Int) error {
|
|||
if _, exists := existingTxHashes[*hash]; exists {
|
||||
str := fmt.Sprintf("block contains duplicate "+
|
||||
"transaction %v", hash)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrDuplicateTx, str)
|
||||
}
|
||||
existingTxHashes[*hash] = true
|
||||
}
|
||||
|
@ -495,7 +499,7 @@ func CheckBlockSanity(block *btcutil.Block, powLimit *big.Int) error {
|
|||
str := fmt.Sprintf("block contains too many signature "+
|
||||
"operations - got %v, max %v", totalSigOps,
|
||||
MaxSigOpsPerBlock)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrTooManySigOps, str)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,7 +515,7 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
|
|||
"version %d or greater must start with the " +
|
||||
"length of the serialized block height"
|
||||
str = fmt.Sprintf(str, serializedHeightVersion)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrMissingCoinbaseHeight, str)
|
||||
}
|
||||
|
||||
serializedLen := int(sigScript[0])
|
||||
|
@ -520,7 +524,7 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
|
|||
"version %d or greater must start with the " +
|
||||
"serialized block height"
|
||||
str = fmt.Sprintf(str, serializedLen)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrMissingCoinbaseHeight, str)
|
||||
}
|
||||
|
||||
serializedHeightBytes := make([]byte, 8, 8)
|
||||
|
@ -530,7 +534,7 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
|
|||
str := fmt.Sprintf("the coinbase signature script serialized "+
|
||||
"block height is %d when %d was expected",
|
||||
serializedHeight, wantHeight)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadCoinbaseHeight, str)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -584,7 +588,7 @@ func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block) error {
|
|||
"transaction %v at block height %d "+
|
||||
"that is not fully spent", txD.Hash,
|
||||
txD.BlockHeight)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrOverwriteTx, str)
|
||||
}
|
||||
|
||||
// Some other unexpected error occurred. Return it now.
|
||||
|
@ -619,7 +623,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int64, txStore TxStore) (in
|
|||
if !exists || originTx.Err != nil || originTx.Tx == nil {
|
||||
str := fmt.Sprintf("unable to find input transaction "+
|
||||
"%v for transaction %v", txInHash, txHash)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrMissingTx, str)
|
||||
}
|
||||
|
||||
// Ensure the transaction is not spending coins which have not
|
||||
|
@ -633,22 +637,23 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int64, txStore TxStore) (in
|
|||
"height %v before required maturity "+
|
||||
"of %v blocks", txInHash, originHeight,
|
||||
txHeight, coinbaseMaturity)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrImmatureSpend, str)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the transaction is not double spending coins.
|
||||
originTxIndex := txIn.PreviousOutpoint.Index
|
||||
if originTxIndex >= uint32(len(originTx.Spent)) {
|
||||
return 0, fmt.Errorf("out of bounds input index %d in "+
|
||||
str := fmt.Sprintf("out of bounds input index %d in "+
|
||||
"transaction %v referenced from transaction %v",
|
||||
originTxIndex, txInHash, txHash)
|
||||
return 0, ruleError(ErrBadTxInput, str)
|
||||
}
|
||||
if originTx.Spent[originTxIndex] {
|
||||
str := fmt.Sprintf("transaction %v tried to double "+
|
||||
"spend coins from transaction %v", txHash,
|
||||
txInHash)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrDoubleSpend, str)
|
||||
}
|
||||
|
||||
// Ensure the transaction amounts are in range. Each of the
|
||||
|
@ -661,13 +666,13 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int64, txStore TxStore) (in
|
|||
if originTxSatoshi < 0 {
|
||||
str := fmt.Sprintf("transaction output has negative "+
|
||||
"value of %v", originTxSatoshi)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
if originTxSatoshi > btcutil.MaxSatoshi {
|
||||
str := fmt.Sprintf("transaction output value of %v is "+
|
||||
"higher than max allowed value of %v",
|
||||
originTxSatoshi, btcutil.MaxSatoshi)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
|
||||
// The total of all outputs must not be more than the max
|
||||
|
@ -681,7 +686,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int64, txStore TxStore) (in
|
|||
"inputs is %v which is higher than max "+
|
||||
"allowed value of %v", totalSatoshiIn,
|
||||
btcutil.MaxSatoshi)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
|
||||
// Mark the referenced output as spent.
|
||||
|
@ -701,7 +706,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int64, txStore TxStore) (in
|
|||
str := fmt.Sprintf("total value of all transaction inputs for "+
|
||||
"transaction %v is %v which is less than the amount "+
|
||||
"spent of %v", txHash, totalSatoshiIn, totalSatoshiOut)
|
||||
return 0, RuleError(str)
|
||||
return 0, ruleError(ErrSpendTooHigh, str)
|
||||
}
|
||||
|
||||
// NOTE: bitcoind checks if the transaction fees are < 0 here, but that
|
||||
|
@ -725,7 +730,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int64, txStore TxStore) (in
|
|||
// checks performed by this function.
|
||||
func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block) error {
|
||||
// If the side chain blocks end up in the database, a call to
|
||||
// checkBlockSanity should be done here in case a previous version
|
||||
// CheckBlockSanity should be done here in case a previous version
|
||||
// allowed a block that is no longer valid. However, since the
|
||||
// implementation only currently uses memory for the side chain blocks,
|
||||
// it isn't currently necessary.
|
||||
|
@ -803,7 +808,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block) er
|
|||
str := fmt.Sprintf("block contains too many "+
|
||||
"signature operations - got %v, max %v",
|
||||
totalSigOps, MaxSigOpsPerBlock)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrTooManySigOps, str)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -826,8 +831,8 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block) er
|
|||
lastTotalFees := totalFees
|
||||
totalFees += txFee
|
||||
if totalFees < lastTotalFees {
|
||||
return RuleError("total fees for block overflows " +
|
||||
"accumulator")
|
||||
return ruleError(ErrBadFees, "total fees for block "+
|
||||
"overflows accumulator")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -846,7 +851,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block) er
|
|||
str := fmt.Sprintf("coinbase transaction for block pays %v "+
|
||||
"which is more than expected value of %v",
|
||||
totalSatoshiOut, expectedSatoshiOut)
|
||||
return RuleError(str)
|
||||
return ruleError(ErrBadCoinbaseValue, str)
|
||||
}
|
||||
|
||||
// Don't run scripts if this node is before the latest known good
|
||||
|
|
|
@ -68,28 +68,35 @@ func TestCheckSerializedHeight(t *testing.T) {
|
|||
coinbaseTx.Version = 2
|
||||
coinbaseTx.AddTxIn(btcwire.NewTxIn(coinbaseOutpoint, nil))
|
||||
|
||||
//
|
||||
// Expected rule errors.
|
||||
missingHeightError := btcchain.RuleError{
|
||||
ErrorCode: btcchain.ErrMissingCoinbaseHeight,
|
||||
}
|
||||
badHeightError := btcchain.RuleError{
|
||||
ErrorCode: btcchain.ErrBadCoinbaseHeight,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
sigScript []byte // Serialized data
|
||||
wantHeight int64 // Expected height
|
||||
err error // Expected error type
|
||||
}{
|
||||
// No serialized height length.
|
||||
{[]byte{}, 0, btcchain.RuleError("")},
|
||||
{[]byte{}, 0, missingHeightError},
|
||||
// Serialized height length with no height bytes.
|
||||
{[]byte{0x02}, 0, btcchain.RuleError("")},
|
||||
{[]byte{0x02}, 0, missingHeightError},
|
||||
// Serialized height length with too few height bytes.
|
||||
{[]byte{0x02, 0x4a}, 0, btcchain.RuleError("")},
|
||||
{[]byte{0x02, 0x4a}, 0, missingHeightError},
|
||||
// Serialized height that needs 2 bytes to encode.
|
||||
{[]byte{0x02, 0x4a, 0x52}, 21066, nil},
|
||||
// Serialized height that needs 2 bytes to encode, but backwards
|
||||
// endianness.
|
||||
{[]byte{0x02, 0x4a, 0x52}, 19026, btcchain.RuleError("")},
|
||||
{[]byte{0x02, 0x4a, 0x52}, 19026, badHeightError},
|
||||
// Serialized height that needs 3 bytes to encode.
|
||||
{[]byte{0x03, 0x40, 0x0d, 0x03}, 200000, nil},
|
||||
// Serialized height that needs 3 bytes to encode, but backwards
|
||||
// endianness.
|
||||
{[]byte{0x03, 0x40, 0x0d, 0x03}, 1074594560, btcchain.RuleError("")},
|
||||
{[]byte{0x03, 0x40, 0x0d, 0x03}, 1074594560, badHeightError},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
|
@ -104,6 +111,17 @@ func TestCheckSerializedHeight(t *testing.T) {
|
|||
"got: %v <%T>, want: %T", i, err, err, test.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if rerr, ok := err.(btcchain.RuleError); ok {
|
||||
trerr := test.err.(btcchain.RuleError)
|
||||
if rerr.ErrorCode != trerr.ErrorCode {
|
||||
t.Errorf("checkSerializedHeight #%d wrong "+
|
||||
"error code got: %v, want: %v", i,
|
||||
rerr.ErrorCode, trerr.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue