mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

* [NOD-510] Change coinbase flags to kaspad. * [NOD-510] Removed superfluous spaces after periods in comments. * [NOD-510] Rename btcd -> kaspad in the root folder. * [NOD-510] Rename BtcEncode -> KaspaEncode and BtcDecode -> KaspaDecode. * [NOD-510] Rename BtcEncode -> KaspaEncode and BtcDecode -> KaspaDecode. * [NOD-510] Continue renaming btcd -> kaspad. * [NOD-510] Rename btcjson -> kaspajson. * [NOD-510] Rename file names inside kaspajson. * [NOD-510] Rename kaspajson -> jsonrpc. * [NOD-510] Finish renaming in addrmgr. * [NOD-510] Rename package btcec to ecc. * [NOD-510] Finish renaming stuff in blockdag. * [NOD-510] Rename stuff in cmd. * [NOD-510] Rename stuff in config. * [NOD-510] Rename stuff in connmgr. * [NOD-510] Rename stuff in dagconfig. * [NOD-510] Rename stuff in database. * [NOD-510] Rename stuff in docker. * [NOD-510] Rename stuff in integration. * [NOD-510] Rename jsonrpc to rpcmodel. * [NOD-510] Rename stuff in limits. * [NOD-510] Rename stuff in logger. * [NOD-510] Rename stuff in mempool. * [NOD-510] Rename stuff in mining. * [NOD-510] Rename stuff in netsync. * [NOD-510] Rename stuff in peer. * [NOD-510] Rename stuff in release. * [NOD-510] Rename stuff in rpcclient. * [NOD-510] Rename stuff in server. * [NOD-510] Rename stuff in signal. * [NOD-510] Rename stuff in txscript. * [NOD-510] Rename stuff in util. * [NOD-510] Rename stuff in wire. * [NOD-510] Fix failing tests. * [NOD-510] Fix merge errors. * [NOD-510] Fix go vet errors. * [NOD-510] Remove merged file that's no longer relevant. * [NOD-510] Add a comment above Op0. * [NOD-510] Fix some comments referencing Bitcoin Core. * [NOD-510] Fix some more comments referencing Bitcoin Core. * [NOD-510] Fix bitcoin -> kaspa. * [NOD-510] Fix more bitcoin -> kaspa. * [NOD-510] Fix comments, remove DisconnectBlock in addrindex. * [NOD-510] Rename KSPD to KASD. * [NOD-510] Fix comments and user agent.
242 lines
6.8 KiB
Go
242 lines
6.8 KiB
Go
// Copyright (c) 2013-2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockdag
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/kaspanet/kaspad/txscript"
|
|
"github.com/kaspanet/kaspad/util"
|
|
"github.com/kaspanet/kaspad/wire"
|
|
)
|
|
|
|
// txValidateItem holds a transaction along with which input to validate.
|
|
type txValidateItem struct {
|
|
txInIndex int
|
|
txIn *wire.TxIn
|
|
tx *util.Tx
|
|
}
|
|
|
|
// txValidator provides a type which asynchronously validates transaction
|
|
// inputs. It provides several channels for communication and a processing
|
|
// function that is intended to be in run multiple goroutines.
|
|
type txValidator struct {
|
|
validateChan chan *txValidateItem
|
|
quitChan chan struct{}
|
|
resultChan chan error
|
|
utxoSet UTXOSet
|
|
flags txscript.ScriptFlags
|
|
sigCache *txscript.SigCache
|
|
}
|
|
|
|
// sendResult sends the result of a script pair validation on the internal
|
|
// result channel while respecting the quit channel. This allows orderly
|
|
// shutdown when the validation process is aborted early due to a validation
|
|
// error in one of the other goroutines.
|
|
func (v *txValidator) sendResult(result error) {
|
|
select {
|
|
case v.resultChan <- result:
|
|
case <-v.quitChan:
|
|
}
|
|
}
|
|
|
|
// validateHandler consumes items to validate from the internal validate channel
|
|
// and returns the result of the validation on the internal result channel. It
|
|
// must be run as a goroutine.
|
|
func (v *txValidator) validateHandler() {
|
|
out:
|
|
for {
|
|
select {
|
|
case txVI := <-v.validateChan:
|
|
// Ensure the referenced input utxo is available.
|
|
txIn := txVI.txIn
|
|
entry, ok := v.utxoSet.Get(txIn.PreviousOutpoint)
|
|
if !ok {
|
|
str := fmt.Sprintf("unable to find unspent "+
|
|
"output %s referenced from "+
|
|
"transaction %s input %d",
|
|
txIn.PreviousOutpoint, txVI.tx.ID(),
|
|
txVI.txInIndex)
|
|
err := ruleError(ErrMissingTxOut, str)
|
|
v.sendResult(err)
|
|
break out
|
|
}
|
|
|
|
// Create a new script engine for the script pair.
|
|
sigScript := txIn.SignatureScript
|
|
scriptPubKey := entry.ScriptPubKey()
|
|
vm, err := txscript.NewEngine(scriptPubKey, txVI.tx.MsgTx(),
|
|
txVI.txInIndex, v.flags, v.sigCache)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to parse input "+
|
|
"%s:%d which references output %s - "+
|
|
"%s (input script bytes %x, prev "+
|
|
"output script bytes %x)",
|
|
txVI.tx.ID(), txVI.txInIndex,
|
|
txIn.PreviousOutpoint, err, sigScript, scriptPubKey)
|
|
err := ruleError(ErrScriptMalformed, str)
|
|
v.sendResult(err)
|
|
break out
|
|
}
|
|
|
|
// Execute the script pair.
|
|
if err := vm.Execute(); err != nil {
|
|
str := fmt.Sprintf("failed to validate input "+
|
|
"%s:%d which references output %s - "+
|
|
"%s (input script bytes %x, prev output "+
|
|
"script bytes %x)",
|
|
txVI.tx.ID(), txVI.txInIndex,
|
|
txIn.PreviousOutpoint, err, sigScript, scriptPubKey)
|
|
err := ruleError(ErrScriptValidation, str)
|
|
v.sendResult(err)
|
|
break out
|
|
}
|
|
|
|
// Validation succeeded.
|
|
v.sendResult(nil)
|
|
|
|
case <-v.quitChan:
|
|
break out
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate validates the scripts for all of the passed transaction inputs using
|
|
// multiple goroutines.
|
|
func (v *txValidator) Validate(items []*txValidateItem) error {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Limit the number of goroutines to do script validation based on the
|
|
// number of processor cores. This helps ensure the system stays
|
|
// reasonably responsive under heavy load.
|
|
maxGoRoutines := runtime.NumCPU() * 3
|
|
if maxGoRoutines <= 0 {
|
|
maxGoRoutines = 1
|
|
}
|
|
if maxGoRoutines > len(items) {
|
|
maxGoRoutines = len(items)
|
|
}
|
|
|
|
// Start up validation handlers that are used to asynchronously
|
|
// validate each transaction input.
|
|
for i := 0; i < maxGoRoutines; i++ {
|
|
spawn(v.validateHandler)
|
|
}
|
|
|
|
// Validate each of the inputs. The quit channel is closed when any
|
|
// errors occur so all processing goroutines exit regardless of which
|
|
// input had the validation error.
|
|
numInputs := len(items)
|
|
currentItem := 0
|
|
processedItems := 0
|
|
for processedItems < numInputs {
|
|
// Only send items while there are still items that need to
|
|
// be processed. The select statement will never select a nil
|
|
// channel.
|
|
var validateChan chan *txValidateItem
|
|
var item *txValidateItem
|
|
if currentItem < numInputs {
|
|
validateChan = v.validateChan
|
|
item = items[currentItem]
|
|
}
|
|
|
|
select {
|
|
case validateChan <- item:
|
|
currentItem++
|
|
|
|
case err := <-v.resultChan:
|
|
processedItems++
|
|
if err != nil {
|
|
close(v.quitChan)
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
close(v.quitChan)
|
|
return nil
|
|
}
|
|
|
|
// newTxValidator returns a new instance of txValidator to be used for
|
|
// validating transaction scripts asynchronously.
|
|
func newTxValidator(utxoSet UTXOSet, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator {
|
|
return &txValidator{
|
|
validateChan: make(chan *txValidateItem),
|
|
quitChan: make(chan struct{}),
|
|
resultChan: make(chan error),
|
|
utxoSet: utxoSet,
|
|
sigCache: sigCache,
|
|
flags: flags,
|
|
}
|
|
}
|
|
|
|
// ValidateTransactionScripts validates the scripts for the passed transaction
|
|
// using multiple goroutines.
|
|
func ValidateTransactionScripts(tx *util.Tx, utxoSet UTXOSet, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
|
|
// Don't validate coinbase transaction scripts.
|
|
if tx.IsCoinBase() {
|
|
return nil
|
|
}
|
|
|
|
// Collect all of the transaction inputs and required information for
|
|
// validation.
|
|
txIns := tx.MsgTx().TxIn
|
|
txValItems := make([]*txValidateItem, 0, len(txIns))
|
|
for txInIdx, txIn := range txIns {
|
|
txVI := &txValidateItem{
|
|
txInIndex: txInIdx,
|
|
txIn: txIn,
|
|
tx: tx,
|
|
}
|
|
txValItems = append(txValItems, txVI)
|
|
}
|
|
|
|
// Validate all of the inputs.
|
|
validator := newTxValidator(utxoSet, flags, sigCache)
|
|
return validator.Validate(txValItems)
|
|
}
|
|
|
|
// checkBlockScripts executes and validates the scripts for all transactions in
|
|
// the passed block using multiple goroutines.
|
|
func checkBlockScripts(block *blockNode, utxoSet UTXOSet, transactions []*util.Tx, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
|
|
// Collect all of the transaction inputs and required information for
|
|
// validation for all transactions in the block into a single slice.
|
|
numInputs := 0
|
|
for _, tx := range transactions {
|
|
numInputs += len(tx.MsgTx().TxIn)
|
|
}
|
|
txValItems := make([]*txValidateItem, 0, numInputs)
|
|
for _, tx := range transactions {
|
|
// Skip coinbase transactions.
|
|
if tx.IsCoinBase() {
|
|
continue
|
|
}
|
|
for txInIdx, txIn := range tx.MsgTx().TxIn {
|
|
txVI := &txValidateItem{
|
|
txInIndex: txInIdx,
|
|
txIn: txIn,
|
|
tx: tx,
|
|
}
|
|
txValItems = append(txValItems, txVI)
|
|
}
|
|
}
|
|
|
|
// Validate all of the inputs.
|
|
validator := newTxValidator(utxoSet, scriptFlags, sigCache)
|
|
start := time.Now()
|
|
if err := validator.Validate(txValItems); err != nil {
|
|
return err
|
|
}
|
|
elapsed := time.Since(start)
|
|
|
|
log.Tracef("block %s took %s to verify", block.hash, elapsed)
|
|
|
|
return nil
|
|
}
|