mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 19:06:51 +00:00
This commit is the first stage of several that are planned to convert
the blockchain package into a concurrent safe package that will
ultimately allow support for multi-peer download and concurrent chain
processing. The goal is to update btcd proper after each step so it can
take advantage of the enhancements as they are developed.
In addition to the aforementioned benefit, this staged approach has been
chosen since it is absolutely critical to maintain consensus.
Separating the changes into several stages makes it easier for reviewers
to logically follow what is happening and therefore helps prevent
consensus bugs. Naturally there are significant automated tests to help
prevent consensus issues as well.
The main focus of this stage is to convert the blockchain package to use
the new database interface and implement the chain-related functionality
which it no longer handles. It also aims to improve efficiency in
various areas by making use of the new database and chain capabilities.
The following is an overview of the chain changes:
- Update to use the new database interface
- Add chain-related functionality that the old database used to handle
- Main chain structure and state
- Transaction spend tracking
- Implement a new pruned unspent transaction output (utxo) set
- Provides efficient direct access to the unspent transaction outputs
- Uses a domain specific compression algorithm that understands the
standard transaction scripts in order to significantly compress them
- Removes reliance on the transaction index and paves the way toward
eventually enabling block pruning
- Modify the New function to accept a Config struct instead of
inidividual parameters
- Replace the old TxStore type with a new UtxoViewpoint type that makes
use of the new pruned utxo set
- Convert code to treat the new UtxoViewpoint as a rolling view that is
used between connects and disconnects to improve efficiency
- Make best chain state always set when the chain instance is created
- Remove now unnecessary logic for dealing with unset best state
- Make all exported functions concurrent safe
- Currently using a single chain state lock as it provides a straight
forward and easy to review path forward however this can be improved
with more fine grained locking
- Optimize various cases where full blocks were being loaded when only
the header is needed to help reduce the I/O load
- Add the ability for callers to get a snapshot of the current best
chain stats in a concurrent safe fashion
- Does not block callers while new blocks are being processed
- Make error messages that reference transaction outputs consistently
use <transaction hash>:<output index>
- Introduce a new AssertError type an convert internal consistency
checks to use it
- Update tests and examples to reflect the changes
- Add a full suite of tests to ensure correct functionality of the new
code
The following is an overview of the btcd changes:
- Update to use the new database and chain interfaces
- Temporarily remove all code related to the transaction index
- Temporarily remove all code related to the address index
- Convert all code that uses transaction stores to use the new utxo
view
- Rework several calls that required the block manager for safe
concurrency to use the chain package directly now that it is
concurrent safe
- Change all calls to obtain the best hash to use the new best state
snapshot capability from the chain package
- Remove workaround for limits on fetching height ranges since the new
database interface no longer imposes them
- Correct the gettxout RPC handler to return the best chain hash as
opposed the hash the txout was found in
- Optimize various RPC handlers:
- Change several of the RPC handlers to use the new chain snapshot
capability to avoid needlessly loading data
- Update several handlers to use new functionality to avoid accessing
the block manager so they are able to return the data without
blocking when the server is busy processing blocks
- Update non-verbose getblock to avoid deserialization and
serialization overhead
- Update getblockheader to request the block height directly from
chain and only load the header
- Update getdifficulty to use the new cached data from chain
- Update getmininginfo to use the new cached data from chain
- Update non-verbose getrawtransaction to avoid deserialization and
serialization overhead
- Update gettxout to use the new utxo store versus loading
full transactions using the transaction index
The following is an overview of the utility changes:
- Update addblock to use the new database and chain interfaces
- Update findcheckpoint to use the new database and chain interfaces
- Remove the dropafter utility which is no longer supported
NOTE: The transaction index and address index will be reimplemented in
another commit.
540 lines
16 KiB
Go
540 lines
16 KiB
Go
// Copyright (c) 2013-2015 The btcsuite developers
|
|
// Copyright (c) 2015-2016 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package txscript
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/decred/dcrd/chaincfg"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
// SigHashType represents hash type bits at the end of a signature.
|
|
type SigHashType byte
|
|
|
|
// Hash type bits from the end of a signature.
|
|
const (
|
|
SigHashOld SigHashType = 0x0
|
|
SigHashAll SigHashType = 0x1
|
|
SigHashNone SigHashType = 0x2
|
|
SigHashSingle SigHashType = 0x3
|
|
SigHashAllValue SigHashType = 0x4
|
|
SigHashAnyOneCanPay SigHashType = 0x80
|
|
|
|
// sigHashMask defines the number of bits of the hash type which is used
|
|
// to identify which outputs are signed.
|
|
sigHashMask = 0x1f
|
|
)
|
|
|
|
// These are the constants specified for maximums in individual scripts.
|
|
const (
|
|
MaxOpsPerScript = 255 // Max number of non-push operations.
|
|
MaxPubKeysPerMultiSig = 20 // Multisig can't have more sigs than this.
|
|
MaxScriptElementSize = 2048 // Max bytes pushable to the stack.
|
|
)
|
|
|
|
// isSmallInt returns whether or not the opcode is considered a small integer,
|
|
// which is an OP_0, or OP_1 through OP_16.
|
|
func isSmallInt(op *opcode) bool {
|
|
if op.value == OP_0 || (op.value >= OP_1 && op.value <= OP_16) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsPayToScriptHash returns true if the script is in the standard
|
|
// pay-to-script-hash (P2SH) format, false otherwise.
|
|
func IsPayToScriptHash(script []byte) bool {
|
|
pops, err := parseScript(script)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return isScriptHash(pops)
|
|
}
|
|
|
|
// isPushOnly returns true if the script only pushes data, false otherwise.
|
|
func isPushOnly(pops []parsedOpcode) bool {
|
|
// NOTE: This function does NOT verify opcodes directly since it is
|
|
// internal and is only called with parsed opcodes for scripts that did
|
|
// not have any parse errors. Thus, consensus is properly maintained.
|
|
|
|
for _, pop := range pops {
|
|
// All opcodes up to OP_16 are data push instructions.
|
|
// NOTE: This does consider OP_RESERVED to be a data push
|
|
// instruction, but execution of OP_RESERVED will fail anyways
|
|
// and matches the behavior required by consensus.
|
|
if pop.opcode.value > OP_16 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IsPushOnlyScript returns whether or not the passed script only pushes data.
|
|
//
|
|
// False will be returned when the script does not parse.
|
|
func IsPushOnlyScript(script []byte) bool {
|
|
pops, err := parseScript(script)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return isPushOnly(pops)
|
|
}
|
|
|
|
// HasP2SHScriptSigStakeOpCodes returns an error is the p2sh script has either
|
|
// stake opcodes or if the pkscript cannot be retrieved.
|
|
func HasP2SHScriptSigStakeOpCodes(version uint16, scriptSig,
|
|
scriptPubKey []byte) error {
|
|
class := GetScriptClass(version, scriptPubKey)
|
|
if IsStakeOutput(scriptPubKey) {
|
|
class, _ = GetStakeOutSubclass(scriptPubKey)
|
|
}
|
|
if class == ScriptHashTy {
|
|
// Obtain the embedded pkScript from the scriptSig of the
|
|
// current transaction. Then, ensure that it does not use
|
|
// any stake tagging OP codes.
|
|
shScript, err := GetPkScriptFromP2SHSigScript(scriptSig)
|
|
if err != nil {
|
|
return fmt.Errorf("unexpected error retrieving pkscript "+
|
|
"from p2sh transaction: %v", err.Error())
|
|
}
|
|
|
|
hasStakeOpCodes, err := ContainsStakeOpCodes(shScript)
|
|
if err != nil {
|
|
return fmt.Errorf("unexpected error checking pkscript "+
|
|
"from p2sh transaction: %v", err.Error())
|
|
}
|
|
if hasStakeOpCodes {
|
|
return ErrP2SHStakeOpCodes
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseScriptTemplate is the same as parseScript but allows the passing of the
|
|
// template list for testing purposes. When there are parse errors, it returns
|
|
// the list of parsed opcodes up to the point of failure along with the error.
|
|
func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode,
|
|
error) {
|
|
retScript := make([]parsedOpcode, 0, len(script))
|
|
for i := 0; i < len(script); {
|
|
instr := script[i]
|
|
op := opcodes[instr]
|
|
pop := parsedOpcode{opcode: &op}
|
|
|
|
// Parse data out of instruction.
|
|
switch {
|
|
// No additional data. Note that some of the opcodes, notably
|
|
// OP_1NEGATE, OP_0, and OP_[1-16] represent the data
|
|
// themselves.
|
|
case op.length == 1:
|
|
i++
|
|
|
|
// Data pushes of specific lengths -- OP_DATA_[1-75].
|
|
case op.length > 1:
|
|
if len(script[i:]) < op.length {
|
|
return retScript, ErrStackShortScript
|
|
}
|
|
|
|
// Slice out the data.
|
|
pop.data = script[i+1 : i+op.length]
|
|
i += op.length
|
|
|
|
// Data pushes with parsed lengths -- OP_PUSHDATAP{1,2,4}.
|
|
case op.length < 0:
|
|
var l uint
|
|
off := i + 1
|
|
|
|
if len(script[off:]) < -op.length {
|
|
return retScript, ErrStackShortScript
|
|
}
|
|
|
|
// Next -length bytes are little endian length of data.
|
|
switch op.length {
|
|
case -1:
|
|
l = uint(script[off])
|
|
case -2:
|
|
l = ((uint(script[off+1]) << 8) |
|
|
uint(script[off]))
|
|
case -4:
|
|
l = ((uint(script[off+3]) << 24) |
|
|
(uint(script[off+2]) << 16) |
|
|
(uint(script[off+1]) << 8) |
|
|
uint(script[off]))
|
|
default:
|
|
return retScript,
|
|
fmt.Errorf("invalid opcode length %d",
|
|
op.length)
|
|
}
|
|
|
|
// Move offset to beginning of the data.
|
|
off += -op.length
|
|
|
|
// Disallow entries that do not fit script or were
|
|
// sign extended.
|
|
if int(l) > len(script[off:]) || int(l) < 0 {
|
|
return retScript, ErrStackShortScript
|
|
}
|
|
|
|
pop.data = script[off : off+int(l)]
|
|
i += 1 - op.length + int(l)
|
|
}
|
|
|
|
retScript = append(retScript, pop)
|
|
}
|
|
|
|
return retScript, nil
|
|
}
|
|
|
|
// parseScript preparses the script in bytes into a list of parsedOpcodes while
|
|
// applying a number of sanity checks.
|
|
func parseScript(script []byte) ([]parsedOpcode, error) {
|
|
return parseScriptTemplate(script, &opcodeArray)
|
|
}
|
|
|
|
// unparseScript reversed the action of parseScript and returns the
|
|
// parsedOpcodes as a list of bytes
|
|
func unparseScript(pops []parsedOpcode) ([]byte, error) {
|
|
script := make([]byte, 0, len(pops))
|
|
for _, pop := range pops {
|
|
b, err := pop.bytes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
script = append(script, b...)
|
|
}
|
|
return script, nil
|
|
}
|
|
|
|
// DisasmString formats a disassembled script for one line printing. When the
|
|
// script fails to parse, the returned string will contain the disassembled
|
|
// script up to the point the failure occurred along with the string '[error]'
|
|
// appended. In addition, the reason the script failed to parse is returned
|
|
// if the caller wants more information about the failure.
|
|
func DisasmString(buf []byte) (string, error) {
|
|
var disbuf bytes.Buffer
|
|
opcodes, err := parseScript(buf)
|
|
for _, pop := range opcodes {
|
|
disbuf.WriteString(pop.print(true))
|
|
disbuf.WriteByte(' ')
|
|
}
|
|
if disbuf.Len() > 0 {
|
|
disbuf.Truncate(disbuf.Len() - 1)
|
|
}
|
|
if err != nil {
|
|
disbuf.WriteString("[error]")
|
|
}
|
|
return disbuf.String(), err
|
|
}
|
|
|
|
// removeOpcode will remove any opcode matching ``opcode'' from the opcode
|
|
// stream in pkscript
|
|
func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode {
|
|
retScript := make([]parsedOpcode, 0, len(pkscript))
|
|
for _, pop := range pkscript {
|
|
if pop.opcode.value != opcode {
|
|
retScript = append(retScript, pop)
|
|
}
|
|
}
|
|
return retScript
|
|
}
|
|
|
|
// canonicalPush returns true if the object is either not a push instruction
|
|
// or the push instruction contained wherein is matches the canonical form
|
|
// or using the smallest instruction to do the job. False otherwise.
|
|
func canonicalPush(pop parsedOpcode) bool {
|
|
opcode := pop.opcode.value
|
|
data := pop.data
|
|
dataLen := len(pop.data)
|
|
if opcode > OP_16 {
|
|
return true
|
|
}
|
|
|
|
if opcode < OP_PUSHDATA1 && opcode > OP_0 && (dataLen == 1 && data[0] <= 16) {
|
|
return false
|
|
}
|
|
if opcode == OP_PUSHDATA1 && dataLen < OP_PUSHDATA1 {
|
|
return false
|
|
}
|
|
if opcode == OP_PUSHDATA2 && dataLen <= 0xff {
|
|
return false
|
|
}
|
|
if opcode == OP_PUSHDATA4 && dataLen <= 0xffff {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// removeOpcodeByData will return the script minus any opcodes that would push
|
|
// the passed data to the stack.
|
|
func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode {
|
|
retScript := make([]parsedOpcode, 0, len(pkscript))
|
|
for _, pop := range pkscript {
|
|
if !canonicalPush(pop) || !bytes.Contains(pop.data, data) {
|
|
retScript = append(retScript, pop)
|
|
}
|
|
}
|
|
return retScript
|
|
|
|
}
|
|
|
|
// CalcSignatureHash is an exported version for testing.
|
|
func CalcSignatureHash(script []parsedOpcode, hashType SigHashType,
|
|
tx *wire.MsgTx, idx int, cachedPrefix *chainhash.Hash) ([]byte, error) {
|
|
return calcSignatureHash(script, hashType, tx, idx, cachedPrefix)
|
|
}
|
|
|
|
// calcSignatureHash will, given a script and hash type for the current script
|
|
// engine instance, calculate the signature hash to be used for signing and
|
|
// verification.
|
|
func calcSignatureHash(script []parsedOpcode, hashType SigHashType,
|
|
tx *wire.MsgTx, idx int, cachedPrefix *chainhash.Hash) ([]byte, error) {
|
|
// The SigHashSingle signature type signs only the corresponding input
|
|
// and output (the output with the same index number as the input).
|
|
//
|
|
// Since transactions can have more inputs than outputs, this means it
|
|
// is improper to use SigHashSingle on input indices that don't have a
|
|
// corresponding output.
|
|
//
|
|
// A bug in the original Satoshi client implementation means specifying
|
|
// an index that is out of range results in a signature hash of 1 (as a
|
|
// uint256 little endian). The original intent appeared to be to
|
|
// indicate failure, but unfortunately, it was never checked and thus is
|
|
// treated as the actual signature hash. This buggy behavior is now
|
|
// part of the consensus and a hard fork would be required to fix it.
|
|
//
|
|
// Due to this, care must be taken by software that creates transactions
|
|
// which make use of SigHashSingle because it can lead to an extremely
|
|
// dangerous situation where the invalid inputs will end up signing a
|
|
// hash of 1. This in turn presents an opportunity for attackers to
|
|
// cleverly construct transactions which can steal those coins provided
|
|
// they can reuse signatures.
|
|
//
|
|
// Decred mitigates this by actually returning an error instead.
|
|
if hashType&sigHashMask == SigHashSingle && idx >= len(tx.TxOut) {
|
|
return nil, ErrSighashSingleIdx
|
|
}
|
|
|
|
// Remove all instances of OP_CODESEPARATOR from the script.
|
|
script = removeOpcode(script, OP_CODESEPARATOR)
|
|
|
|
// Make a deep copy of the transaction, zeroing out the script for all
|
|
// inputs that are not currently being processed.
|
|
txCopy := tx.Copy()
|
|
for i := range txCopy.TxIn {
|
|
if i == idx {
|
|
// UnparseScript cannot fail here because removeOpcode
|
|
// above only returns a valid script.
|
|
sigScript, _ := unparseScript(script)
|
|
txCopy.TxIn[idx].SignatureScript = sigScript
|
|
} else {
|
|
txCopy.TxIn[i].SignatureScript = nil
|
|
}
|
|
}
|
|
|
|
switch hashType & sigHashMask {
|
|
case SigHashNone:
|
|
txCopy.TxOut = txCopy.TxOut[0:0] // Empty slice.
|
|
for i := range txCopy.TxIn {
|
|
if i != idx {
|
|
txCopy.TxIn[i].Sequence = 0
|
|
}
|
|
}
|
|
|
|
case SigHashSingle:
|
|
// Resize output array to up to and including requested index.
|
|
txCopy.TxOut = txCopy.TxOut[:idx+1]
|
|
|
|
// All but current output get zeroed out.
|
|
for i := 0; i < idx; i++ {
|
|
txCopy.TxOut[i].Value = -1
|
|
txCopy.TxOut[i].PkScript = nil
|
|
}
|
|
|
|
// Sequence on all other inputs is 0, too.
|
|
for i := range txCopy.TxIn {
|
|
if i != idx {
|
|
txCopy.TxIn[i].Sequence = 0
|
|
}
|
|
}
|
|
|
|
default:
|
|
// Consensus treats undefined hashtypes like normal SigHashAll
|
|
// for purposes of hash generation.
|
|
fallthrough
|
|
case SigHashOld:
|
|
fallthrough
|
|
case SigHashAllValue:
|
|
fallthrough
|
|
case SigHashAll:
|
|
// Nothing special here.
|
|
}
|
|
if hashType&SigHashAnyOneCanPay != 0 {
|
|
txCopy.TxIn = txCopy.TxIn[idx : idx+1]
|
|
idx = 0
|
|
}
|
|
|
|
// The final hash (message to sign) is the hash of:
|
|
// 1) hash of the prefix ||
|
|
// 2) hash of the witness for signing ||
|
|
// 3) the hash type (encoded as a 4-byte little-endian value)
|
|
var wbuf bytes.Buffer
|
|
binary.Write(&wbuf, binary.LittleEndian, uint32(hashType))
|
|
|
|
// Optimization for SIGHASH_ALL. In this case, the prefix hash is
|
|
// the same as the transaction hash because only the inputs have
|
|
// been modified, so don't bother to do the wasteful O(N^2) extra
|
|
// hash here.
|
|
// The caching only works if the "anyone can pay flag" is also
|
|
// disabled.
|
|
var prefixHash chainhash.Hash
|
|
if cachedPrefix != nil &&
|
|
(hashType&sigHashMask == SigHashAll) &&
|
|
(hashType&SigHashAnyOneCanPay == 0) &&
|
|
chaincfg.SigHashOptimization {
|
|
prefixHash = *cachedPrefix
|
|
} else {
|
|
prefixHash = txCopy.TxSha()
|
|
}
|
|
|
|
// If the ValueIn is to be included in what we're signing, sign
|
|
// the witness hash that includes it. Otherwise, just sign the
|
|
// prefix and signature scripts.
|
|
var witnessHash chainhash.Hash
|
|
if hashType&sigHashMask != SigHashAllValue {
|
|
witnessHash = txCopy.TxShaWitnessSigning()
|
|
} else {
|
|
witnessHash = txCopy.TxShaWitnessValueSigning()
|
|
}
|
|
wbuf.Write(prefixHash.Bytes())
|
|
wbuf.Write(witnessHash.Bytes())
|
|
return chainhash.HashFuncB(wbuf.Bytes()), nil
|
|
}
|
|
|
|
// asSmallInt returns the passed opcode, which must be true according to
|
|
// isSmallInt(), as an integer.
|
|
func asSmallInt(op *opcode) int {
|
|
if op.value == OP_0 {
|
|
return 0
|
|
}
|
|
|
|
return int(op.value - (OP_1 - 1))
|
|
}
|
|
|
|
// getSigOpCount is the implementation function for counting the number of
|
|
// signature operations in the script provided by pops. If precise mode is
|
|
// requested then we attempt to count the number of operations for a multisig
|
|
// op. Otherwise we use the maximum.
|
|
func getSigOpCount(pops []parsedOpcode, precise bool) int {
|
|
nSigs := 0
|
|
for i, pop := range pops {
|
|
switch pop.opcode.value {
|
|
case OP_CHECKSIG:
|
|
fallthrough
|
|
case OP_CHECKSIGVERIFY:
|
|
fallthrough
|
|
case OP_CHECKSIGALT:
|
|
fallthrough
|
|
case OP_CHECKSIGALTVERIFY:
|
|
nSigs++
|
|
case OP_CHECKMULTISIG:
|
|
fallthrough
|
|
case OP_CHECKMULTISIGVERIFY:
|
|
// If we are being precise then look for familiar
|
|
// patterns for multisig, for now all we recognize is
|
|
// OP_1 - OP_16 to signify the number of pubkeys.
|
|
// Otherwise, we use the max of 20.
|
|
if precise && i > 0 &&
|
|
pops[i-1].opcode.value >= OP_1 &&
|
|
pops[i-1].opcode.value <= OP_16 {
|
|
nSigs += asSmallInt(pops[i-1].opcode)
|
|
} else {
|
|
nSigs += MaxPubKeysPerMultiSig
|
|
}
|
|
default:
|
|
// Not a sigop.
|
|
}
|
|
}
|
|
|
|
return nSigs
|
|
}
|
|
|
|
// GetSigOpCount provides a quick count of the number of signature operations
|
|
// in a script. a CHECKSIG operations counts for 1, and a CHECK_MULTISIG for 20.
|
|
// If the script fails to parse, then the count up to the point of failure is
|
|
// returned.
|
|
func GetSigOpCount(script []byte) int {
|
|
// Don't check error since parseScript returns the parsed-up-to-error
|
|
// list of pops.
|
|
pops, _ := parseScript(script)
|
|
return getSigOpCount(pops, false)
|
|
}
|
|
|
|
// GetPreciseSigOpCount returns the number of signature operations in
|
|
// scriptPubKey. If bip16 is true then scriptSig may be searched for the
|
|
// Pay-To-Script-Hash script in order to find the precise number of signature
|
|
// operations in the transaction. If the script fails to parse, then the count
|
|
// up to the point of failure is returned.
|
|
func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int {
|
|
// Don't check error since parseScript returns the parsed-up-to-error
|
|
// list of pops.
|
|
pops, _ := parseScript(scriptPubKey)
|
|
|
|
// Treat non P2SH transactions as normal.
|
|
if !(bip16 && isScriptHash(pops)) {
|
|
return getSigOpCount(pops, true)
|
|
}
|
|
|
|
// The public key script is a pay-to-script-hash, so parse the signature
|
|
// script to get the final item. Scripts that fail to fully parse count
|
|
// as 0 signature operations.
|
|
sigPops, err := parseScript(scriptSig)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
// The signature script must only push data to the stack for P2SH to be
|
|
// a valid pair, so the signature operation count is 0 when that is not
|
|
// the case.
|
|
if !isPushOnly(sigPops) || len(sigPops) == 0 {
|
|
return 0
|
|
}
|
|
|
|
// The P2SH script is the last item the signature script pushes to the
|
|
// stack. When the script is empty, there are no signature operations.
|
|
shScript := sigPops[len(sigPops)-1].data
|
|
if len(shScript) == 0 {
|
|
return 0
|
|
}
|
|
|
|
// Parse the P2SH script and don't check the error since parseScript
|
|
// returns the parsed-up-to-error list of pops and the consensus rules
|
|
// dictate signature operations are counted up to the first parse
|
|
// failure.
|
|
shPops, _ := parseScript(shScript)
|
|
return getSigOpCount(shPops, true)
|
|
}
|
|
|
|
// IsUnspendable returns whether the passed public key script is unspendable, or
|
|
// guaranteed to fail at execution. This allows inputs to be pruned instantly
|
|
// when entering the UTXO set. In Decred, all zero value outputs are unspendable.
|
|
func IsUnspendable(amount int64, pkScript []byte) bool {
|
|
if amount == 0 {
|
|
return true
|
|
}
|
|
|
|
pops, err := parseScript(pkScript)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
|
|
return len(pops) > 0 && pops[0].opcode.value == OP_RETURN
|
|
}
|