2013-07-18 16:49:28 +02:00
|
|
|
// 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).
|
2013-08-05 22:20:35 +02:00
|
|
|
func validateTxIn(txInIdx int, txin *btcwire.TxIn, txSha *btcwire.ShaHash, tx *btcwire.MsgTx, timestamp time.Time, originTx *btcwire.MsgTx) error {
|
2013-07-18 16:49:28 +02:00
|
|
|
// 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,
|
2013-08-05 22:20:35 +02:00
|
|
|
timestamp.After(btcscript.Bip16Activation))
|
2013-07-18 16:49:28 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-09-30 23:47:37 +02:00
|
|
|
// 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) {
|
2013-07-18 16:49:28 +02:00
|
|
|
c := make(chan txValidate)
|
2013-09-30 23:21:35 +02:00
|
|
|
job := tx.TxIn
|
2013-07-18 16:49:28 +02:00
|
|
|
resultErrors := make([]error, len(job))
|
|
|
|
|
|
|
|
var currentItem int
|
|
|
|
var completedItems int
|
|
|
|
|
|
|
|
processFunc := func(txInIdx int) {
|
|
|
|
log.Tracef("validating tx %v input %v len %v",
|
2013-09-30 23:21:35 +02:00
|
|
|
txHash, currentItem, len(job))
|
2013-07-18 16:49:28 +02:00
|
|
|
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)
|
|
|
|
}
|
2013-09-30 23:21:35 +02:00
|
|
|
originTx = txInfo.Tx
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
2013-09-30 23:21:35 +02:00
|
|
|
err := validateTxIn(txInIdx, job[txInIdx], txHash, tx, timestamp,
|
|
|
|
originTx)
|
2013-07-18 16:49:28 +02:00
|
|
|
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 {
|
2013-09-30 23:21:35 +02:00
|
|
|
log.Warnf("tx %v failed input %v, err %v", txHash, i, resultErrors[i])
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkBlockScripts executes and validates the scripts for all transactions in
|
|
|
|
// the passed block.
|
2013-09-30 23:21:35 +02:00
|
|
|
func checkBlockScripts(block *btcutil.Block, txStore TxStore) error {
|
2013-07-18 16:49:28 +02:00
|
|
|
timestamp := block.MsgBlock().Header.Timestamp
|
2013-10-12 00:14:58 +02:00
|
|
|
|
|
|
|
txList := block.MsgBlock().Transactions
|
|
|
|
c := make(chan txValidate)
|
|
|
|
resultErrors := make([]error, len(txList))
|
|
|
|
|
|
|
|
var currentItem int
|
|
|
|
var completedItems int
|
|
|
|
processFunc := func(txIdx int) {
|
|
|
|
tx := txList[txIdx]
|
|
|
|
txHash, _ := block.TxSha(txIdx)
|
|
|
|
|
2013-09-30 23:47:37 +02:00
|
|
|
err := ValidateTransactionScripts(tx, txHash, timestamp, txStore)
|
2013-10-12 00:14:58 +02:00
|
|
|
r := txValidate{txIdx, err}
|
|
|
|
c <- r
|
|
|
|
}
|
|
|
|
for currentItem = 0; currentItem < len(txList) && currentItem < 8; currentItem++ {
|
|
|
|
go processFunc(currentItem)
|
|
|
|
}
|
|
|
|
for completedItems < len(txList) {
|
|
|
|
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 currentItem < len(txList) {
|
|
|
|
go processFunc(currentItem)
|
|
|
|
currentItem++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := 0; i < len(txList); i++ {
|
|
|
|
if resultErrors[i] != nil {
|
|
|
|
return resultErrors[i]
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|