// Copyright (c) 2013 Conformal Systems LLC. // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package btcchain import ( "fmt" "github.com/conformal/btcscript" "github.com/conformal/btcutil" "github.com/conformal/btcwire" "math" "time" ) // txValidate is used to track results of validating scripts for each // transaction input index. type txValidate struct { txIndex int err error } // txProcessList type txProcessList struct { txsha btcwire.ShaHash tx *btcwire.MsgTx } // validateTxIn validates a the script pair for the passed spending transaction // (along with the specific input index) and origin transaction (with the // specific output index). func validateTxIn(txInIdx int, txin *btcwire.TxIn, txSha *btcwire.ShaHash, tx *btcwire.MsgTx, timestamp time.Time, originTx *btcwire.MsgTx) error { // If the input transaction has no previous input, there is nothing // to check. originTxIdx := txin.PreviousOutpoint.Index if originTxIdx == math.MaxUint32 { return nil } if originTxIdx >= uint32(len(originTx.TxOut)) { originTxSha := &txin.PreviousOutpoint.Hash log.Warnf("unable to locate source tx %v spending tx %v", originTxSha, &txSha) return fmt.Errorf("invalid index %x", originTxIdx) } sigScript := txin.SignatureScript pkScript := originTx.TxOut[originTxIdx].PkScript engine, err := btcscript.NewScript(sigScript, pkScript, txInIdx, tx, timestamp.After(btcscript.Bip16Activation)) if err != nil { return err } err = engine.Execute() if err != nil { log.Warnf("validate of input %v failed: %v", txInIdx, err) return err } return nil } // ValidateTransactionScripts validates the scripts for the passed transaction // using multiple goroutines. func ValidateTransactionScripts(tx *btcwire.MsgTx, txHash *btcwire.ShaHash, timestamp time.Time, txStore TxStore) (err error) { c := make(chan txValidate) job := tx.TxIn resultErrors := make([]error, len(job)) var currentItem int var completedItems int processFunc := func(txInIdx int) { log.Tracef("validating tx %v input %v len %v", txHash, currentItem, len(job)) txin := job[txInIdx] originTxSha := &txin.PreviousOutpoint.Hash origintxidx := txin.PreviousOutpoint.Index var originTx *btcwire.MsgTx if origintxidx != math.MaxUint32 { txInfo, ok := txStore[*originTxSha] if !ok { //wtf? fmt.Printf("obj not found in txStore %v", originTxSha) } originTx = txInfo.Tx } err := validateTxIn(txInIdx, job[txInIdx], txHash, tx, timestamp, originTx) r := txValidate{txInIdx, err} c <- r } for currentItem = 0; currentItem < len(job) && currentItem < 16; currentItem++ { go processFunc(currentItem) } for completedItems < len(job) { select { case result := <-c: completedItems++ resultErrors[result.txIndex] = result.err // would be nice to determine if we could stop // on early errors here instead of running more. if err == nil { err = result.err } if currentItem < len(job) { go processFunc(currentItem) currentItem++ } } } for i := 0; i < len(job); i++ { if resultErrors[i] != nil { log.Warnf("tx %v failed input %v, err %v", txHash, i, resultErrors[i]) } } return } // checkBlockScripts executes and validates the scripts for all transactions in // the passed block. func checkBlockScripts(block *btcutil.Block, txStore TxStore) error { timestamp := block.MsgBlock().Header.Timestamp for i, tx := range block.MsgBlock().Transactions { txHash, _ := block.TxSha(i) err := ValidateTransactionScripts(tx, txHash, timestamp, txStore) if err != nil { return err } } return nil }