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:
Dave Collins 2019-06-17 13:43:24 -05:00
parent a0b84e4005
commit ce852eb852
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
7 changed files with 76 additions and 37 deletions

View File

@ -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

View File

@ -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=

View File

@ -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
View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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=