mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
blockchain: Do coinbase nulldata check locally.
This refactors the consensus code which extracts the null data from the coinbase from txscript.ExtractCoinbaseNullData so that it is performed directly in the validation code where it more properly belongs. The only reason the extraction was previously done in txscript is because it was not possible to parse scripts outside of it, but that is no longer the case now that txscript offers an exported tokenizer for that purpose. The extraction code is ever so slightly more efficient now that it no longer needs to be as generic since it now has direct knowledge of the conditions that need to be handled. Great care was taken to ensure the semantics are not changed while refactoring the code and no additional tests are added in this commit because all of the conditions and code paths are covered by the tests recently added to the full block tests. While here, also perform some related code cleanup in the function and improve the error messages . Since the txscript.ExtractCoinbaseNullData is no longer necessary, this deprecates the function and releated error code and constant so they can be removed in the next major version of txscript. Finally, since this relies on the script tokenizer which is not yet in a released version of the txscript module, bump the requirement to include an as yet unreleased version of txscript to ensure the next time the blockchain module is released, it will require a newer version of txscript to be released first.
This commit is contained in:
parent
a0b84e4005
commit
ce852eb852
@ -12,7 +12,7 @@ require (
|
||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.1
|
||||
github.com/decred/dcrd/dcrutil v1.2.0
|
||||
github.com/decred/dcrd/gcs v1.0.2
|
||||
github.com/decred/dcrd/txscript v1.0.2
|
||||
github.com/decred/dcrd/txscript v1.1.0
|
||||
github.com/decred/dcrd/wire v1.2.0
|
||||
github.com/decred/slog v1.0.0
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
|
||||
@ -84,6 +84,8 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
golang.org/x/crypto v0.0.0-20180718160520-a2144134853f h1:lRy+hhwk7YT7MsKejxuz0C5Q1gk6p/QoPQYEmKmGFb8=
|
||||
golang.org/x/crypto v0.0.0-20180718160520-a2144134853f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24 h1:mEsFm194MmS9vCwxFy+zwu0EU7ZkxxMD1iH++vmGdUY=
|
||||
golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
||||
@ -43,6 +43,11 @@ const (
|
||||
// MaxCoinbaseScriptLen is the maximum length a coinbase script can be.
|
||||
MaxCoinbaseScriptLen = 100
|
||||
|
||||
// maxUniqueCoinbaseNullDataSize is the maximum number of bytes allowed
|
||||
// in the pushed data output of the coinbase output that is used to
|
||||
// ensure the coinbase has a unique hash.
|
||||
maxUniqueCoinbaseNullDataSize = 256
|
||||
|
||||
// medianTimeBlocks is the number of previous blocks which should be
|
||||
// used to calculate the median time used to validate block timestamps.
|
||||
medianTimeBlocks = 11
|
||||
@ -1197,46 +1202,72 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
|
||||
// coinbase contains the height encoding to make coinbase hash collisions
|
||||
// impossible.
|
||||
func checkCoinbaseUniqueHeight(blockHeight int64, block *dcrutil.Block) error {
|
||||
// Coinbase TxOut[0] is always tax, TxOut[1] is always
|
||||
// height + extranonce, so at least two outputs must
|
||||
// exist.
|
||||
if len(block.MsgBlock().Transactions[0].TxOut) < 2 {
|
||||
str := fmt.Sprintf("block %v is missing necessary coinbase "+
|
||||
"outputs", block.Hash())
|
||||
// Coinbase output 0 is the project subsidy, and output 1 is height +
|
||||
// extranonce, so at least two outputs must exist.
|
||||
const minReqOutputs = 2
|
||||
coinbaseTx := block.MsgBlock().Transactions[0]
|
||||
if len(coinbaseTx.TxOut) < minReqOutputs {
|
||||
str := fmt.Sprintf("block %s is missing required coinbase outputs ("+
|
||||
"num outputs: %d, min required: %d)", block.Hash(),
|
||||
len(coinbaseTx.TxOut), minReqOutputs)
|
||||
return ruleError(ErrFirstTxNotCoinbase, str)
|
||||
}
|
||||
|
||||
// Only version 0 scripts are currently valid.
|
||||
nullDataOut := block.MsgBlock().Transactions[0].TxOut[1]
|
||||
if nullDataOut.Version != 0 {
|
||||
str := fmt.Sprintf("block %v output 1 has wrong script version",
|
||||
block.Hash())
|
||||
const scriptVersion = 0
|
||||
nullDataOut := coinbaseTx.TxOut[1]
|
||||
if nullDataOut.Version != scriptVersion {
|
||||
str := fmt.Sprintf("block %s coinbase output 1 script version %d is "+
|
||||
"not the required version %d", block.Hash(), nullDataOut.Version,
|
||||
scriptVersion)
|
||||
return ruleError(ErrFirstTxNotCoinbase, str)
|
||||
}
|
||||
|
||||
// The first 4 bytes of the null data output must be the encoded height
|
||||
// of the block, so that every coinbase created has a unique transaction
|
||||
// hash.
|
||||
nullData, err := txscript.ExtractCoinbaseNullData(nullDataOut.PkScript)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("block %v output 1 has wrong script type",
|
||||
block.Hash())
|
||||
// The nulldata in the coinbase must be a single OP_RETURN followed by a
|
||||
// data push up to maxUniqueCoinbaseNullDataSize bytes and the first 4 bytes
|
||||
// of that data must be the encoded height of the block so that every
|
||||
// coinbase created has a unique transaction hash.
|
||||
//
|
||||
// NOTE: This is intentionally not using GetScriptClass and the related
|
||||
// functions because those are specifically for standardness checks which
|
||||
// can change over time and this function is enforces consensus rules.
|
||||
//
|
||||
// Also of note is that technically normal nulldata scripts support encoding
|
||||
// numbers via small opcodes, however, for legacy reasons, the consensus
|
||||
// rules require the block height to be encoded as a 4-byte little-endian
|
||||
// uint32 pushed via a normal data push, as opposed to using the normal
|
||||
// number handling semantics of scripts, so this is specialized to
|
||||
// accommodate that.
|
||||
var nullData []byte
|
||||
pkScript := nullDataOut.PkScript
|
||||
if len(pkScript) > 1 && pkScript[0] == txscript.OP_RETURN {
|
||||
tokenizer := txscript.MakeScriptTokenizer(scriptVersion, pkScript[1:])
|
||||
if tokenizer.Next() && tokenizer.Done() && tokenizer.Opcode() <=
|
||||
txscript.OP_PUSHDATA4 {
|
||||
|
||||
nullData = tokenizer.Data()
|
||||
}
|
||||
}
|
||||
if len(nullData) > maxUniqueCoinbaseNullDataSize {
|
||||
str := fmt.Sprintf("block %s coinbase output 1 pushes %d bytes which "+
|
||||
"is more than allowed value of %d", block.Hash(), len(nullData),
|
||||
maxUniqueCoinbaseNullDataSize)
|
||||
return ruleError(ErrFirstTxNotCoinbase, str)
|
||||
}
|
||||
if len(nullData) < 4 {
|
||||
str := fmt.Sprintf("block %v output 1 data push too short to "+
|
||||
"contain height", block.Hash())
|
||||
str := fmt.Sprintf("block %s coinbase output 1 pushes %d bytes which "+
|
||||
"is too short to encode height", block.Hash(), len(nullData))
|
||||
return ruleError(ErrFirstTxNotCoinbase, str)
|
||||
}
|
||||
|
||||
// Check the height and ensure it is correct.
|
||||
cbHeight := binary.LittleEndian.Uint32(nullData[0:4])
|
||||
if cbHeight != uint32(blockHeight) {
|
||||
prevBlock := block.MsgBlock().Header.PrevBlock
|
||||
str := fmt.Sprintf("block %v output 1 has wrong height in "+
|
||||
"coinbase; want %v, got %v; prevBlock %v, header height %v",
|
||||
block.Hash(), blockHeight, cbHeight, prevBlock,
|
||||
block.MsgBlock().Header.Height)
|
||||
header := &block.MsgBlock().Header
|
||||
str := fmt.Sprintf("block %s coinbase output 1 encodes height %d "+
|
||||
"instead of expected height %d (prev block: %s, header height %d)",
|
||||
block.Hash(), cbHeight, uint32(blockHeight), header.PrevBlock,
|
||||
header.Height)
|
||||
return ruleError(ErrCoinbaseHeight, str)
|
||||
}
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@ -26,7 +26,7 @@ require (
|
||||
github.com/decred/dcrd/mining v1.1.0
|
||||
github.com/decred/dcrd/peer v1.1.0
|
||||
github.com/decred/dcrd/rpcclient/v2 v2.0.0
|
||||
github.com/decred/dcrd/txscript v1.0.2
|
||||
github.com/decred/dcrd/txscript v1.1.0
|
||||
github.com/decred/dcrd/wire v1.2.0
|
||||
github.com/decred/dcrwallet/rpc/jsonrpc/types v1.0.0
|
||||
github.com/decred/slog v1.0.0
|
||||
|
||||
@ -19,6 +19,8 @@ const (
|
||||
// maxUniqueCoinbaseNullDataSize is the maximum number of bytes allowed
|
||||
// in the pushed data output of the coinbase output that is used to
|
||||
// ensure the coinbase has a unique hash.
|
||||
//
|
||||
// Deprecated: This will be removed in the next major version bump.
|
||||
maxUniqueCoinbaseNullDataSize = 256
|
||||
)
|
||||
|
||||
@ -29,6 +31,8 @@ const (
|
||||
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||
// does not accept a script version, the results are undefined for other script
|
||||
// versions.
|
||||
//
|
||||
// Deprecated: This will be removed in the next major version bump.
|
||||
func ExtractCoinbaseNullData(pkScript []byte) ([]byte, error) {
|
||||
// The nulldata in the coinbase must be a single OP_RETURN followed by a
|
||||
// data push up to maxUniqueCoinbaseNullDataSize bytes.
|
||||
|
||||
@ -48,6 +48,8 @@ const (
|
||||
// ErrMalformedCoinbaseNullData is returned when the nulldata output
|
||||
// of a coinbase transaction that is used to ensure the coinbase has a
|
||||
// unique hash is not properly formed.
|
||||
//
|
||||
// Deprecated: This will be removed in the next major version bump.
|
||||
ErrMalformedCoinbaseNullData
|
||||
|
||||
// ErrTooMuchNullData is returned from NullDataScript when the length of
|
||||
|
||||
@ -8,28 +8,28 @@ github.com/dchest/blake256 v1.0.0 h1:6gUgI5MHdz9g0TdrgKqXsoDX+Zjxmm1Sc6OsoGru50I
|
||||
github.com/dchest/blake256 v1.0.0/go.mod h1:xXNWCE1jsAP8DAjP+rKw2MbeqLczjI3TRx2VK+9OEYY=
|
||||
github.com/decred/base58 v1.0.0 h1:BVi1FQCThIjZ0ehG+I99NJ51o0xcc9A/fDKhmJxY6+w=
|
||||
github.com/decred/base58 v1.0.0/go.mod h1:LLY1p5e3g91byL/UO1eiZaYd+uRoVRarybgcoymu9Ks=
|
||||
github.com/decred/dcrd/chaincfg v1.2.0 h1:Vj0xr85wmqOdQDxKLkpP9TqwK1RykqY2eC0fWcCsl0k=
|
||||
github.com/decred/dcrd/chaincfg v1.2.0/go.mod h1:kpoGTMIriKn5hHRSu5b65+Q9LlGUdbQcMzGujac1BVs=
|
||||
github.com/decred/dcrd/chaincfg v1.5.1 h1:u1Xbq0VTnAXIHW5ECqrWe0VYSgf5vWHqpSiwoLBzxAQ=
|
||||
github.com/decred/dcrd/chaincfg v1.5.1/go.mod h1:FukMzTjkwzjPU+hK7CqDMQe3NMbSZAYU5PAcsx1wlv0=
|
||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.1 h1:0vG7U9+dSjSCaHQKdoSKURK2pOb47+b+8FK5q4+Je7M=
|
||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.1/go.mod h1:OVfvaOsNLS/A1y4Eod0Ip/Lf8qga7VXCQjUQLbkY0Go=
|
||||
github.com/decred/dcrd/dcrec v0.0.0-20180721005212-59fe2b293f69/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA=
|
||||
github.com/decred/dcrd/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o=
|
||||
github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8=
|
||||
github.com/decred/dcrd/dcrec/edwards v0.0.0-20181208004914-a0816cf4301f h1:NF7vp3nZ4MsAiXswGmE//m83jCN0lDsQrLI7IwLCTlo=
|
||||
github.com/decred/dcrd/dcrec/edwards v0.0.0-20181208004914-a0816cf4301f/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU=
|
||||
github.com/decred/dcrd/dcrec/edwards v1.0.0 h1:UDcPNzclKiJlWqV3x1Fl8xMCJrolo4PB4X9t8LwKDWU=
|
||||
github.com/decred/dcrd/dcrec/edwards v1.0.0/go.mod h1:HblVh1OfMt7xSxUL1ufjToaEvpbjpWvvTAUx4yem8BI=
|
||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.1 h1:EFWVd1p0t0Y5tnsm/dJujgV0ORogRJ6vo7CMAjLseAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.1/go.mod h1:lhu4eZFSfTJWUnR3CFRcpD+Vta0KUAqnhTsTksHXgy0=
|
||||
github.com/decred/dcrd/dcrutil v1.2.0 h1:Pd5Wf650g6Xu6luYDfGkh1yiUoPUAgqzRu6K+BGyJGg=
|
||||
github.com/decred/dcrd/dcrutil v1.2.0/go.mod h1:tUNHS2gj7ApeEVS8gb6O+4wJW7w3O2MSRyRdcjW1JxU=
|
||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.2 h1:awk7sYJ4pGWmtkiGHFfctztJjHMKGLV8jctGQhAbKe0=
|
||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.2/go.mod h1:CHTUIVfmDDd0KFVFpNX1pFVCBUegxW387nN0IGwNKR0=
|
||||
github.com/decred/dcrd/dcrutil v1.3.0 h1:LtKIiDnq925yJT/4OpIKKiU9/WaxfD9LfhxrpLSi0Qs=
|
||||
github.com/decred/dcrd/dcrutil v1.3.0/go.mod h1:7fUT70QAarhDwQK62g92uDbbYpjXlXngpy5RBiecufo=
|
||||
github.com/decred/dcrd/wire v1.2.0 h1:HqJVB7vcklIguzFWgRXw/WYCQ9cD3bUC5TKj53i1Hng=
|
||||
github.com/decred/dcrd/wire v1.2.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM=
|
||||
github.com/decred/slog v1.0.0 h1:Dl+W8O6/JH6n2xIFN2p3DNjCmjYwvrXsjlSJTQQ4MhE=
|
||||
github.com/decred/slog v1.0.0/go.mod h1:zR98rEZHSnbZ4WHZtO0iqmSZjDLKhkXfrPTZQKtAonQ=
|
||||
golang.org/x/crypto v0.0.0-20180718160520-a2144134853f h1:lRy+hhwk7YT7MsKejxuz0C5Q1gk6p/QoPQYEmKmGFb8=
|
||||
golang.org/x/crypto v0.0.0-20180718160520-a2144134853f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
Loading…
Reference in New Issue
Block a user