diff --git a/blockchain/go.mod b/blockchain/go.mod index c32c7a84..2c5fa278 100644 --- a/blockchain/go.mod +++ b/blockchain/go.mod @@ -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 diff --git a/blockchain/go.sum b/blockchain/go.sum index e5fc2d91..2255a153 100644 --- a/blockchain/go.sum +++ b/blockchain/go.sum @@ -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= diff --git a/blockchain/validate.go b/blockchain/validate.go index a1649fa7..449dffea 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -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) } diff --git a/go.mod b/go.mod index 7292428b..c0adbfc4 100644 --- a/go.mod +++ b/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 diff --git a/txscript/consensus.go b/txscript/consensus.go index cfeeb6f7..36aeaf83 100644 --- a/txscript/consensus.go +++ b/txscript/consensus.go @@ -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. diff --git a/txscript/error.go b/txscript/error.go index 1aeb19eb..7cc210c7 100644 --- a/txscript/error.go +++ b/txscript/error.go @@ -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 diff --git a/txscript/go.sum b/txscript/go.sum index 4fc7b421..0cdb97b6 100644 --- a/txscript/go.sum +++ b/txscript/go.sum @@ -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=