import (
"fmt"
"math"
+ "sync"
- "github.com/vapor/claim"
+ "github.com/vapor/config"
"github.com/vapor/consensus"
- "github.com/vapor/consensus/segwit"
"github.com/vapor/errors"
"github.com/vapor/math/checked"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/vm"
)
+const (
+ validateWorkerNum = 32
+)
+
// validate transaction error
var (
ErrTxVersion = errors.New("invalid transaction version")
ErrWrongTransactionSize = errors.New("invalid transaction size")
ErrBadTimeRange = errors.New("invalid transaction time range")
+ ErrEmptyInputIDs = errors.New("got the empty InputIDs")
ErrNotStandardTx = errors.New("not standard transaction")
ErrWrongCoinbaseTransaction = errors.New("wrong coinbase transaction")
ErrWrongCoinbaseAsset = errors.New("wrong coinbase assetID")
ErrUnbalanced = errors.New("unbalanced asset amount between input and output")
ErrOverGasCredit = errors.New("all gas credit has been spend")
ErrGasCalculate = errors.New("gas usage calculate got a math error")
+ ErrVotePubKey = errors.New("invalid public key of vote")
+ ErrVoteOutputAmount = errors.New("invalid vote amount")
)
// GasState record the gas usage status
}
g.BTMValue = uint64(BTMValue)
-
var ok bool
- if g.GasLeft, ok = checked.DivInt64(BTMValue, consensus.VMGasRate); !ok {
+ if g.GasLeft, ok = checked.DivInt64(BTMValue, consensus.ActiveNetParams.VMGasRate); !ok {
return errors.Wrap(ErrGasCalculate, "setGas calc gas amount")
}
- if g.GasLeft > consensus.MaxGasAmount {
- g.GasLeft = consensus.MaxGasAmount
+ if g.GasLeft, ok = checked.AddInt64(g.GasLeft, consensus.ActiveNetParams.DefaultGasCredit); !ok {
+ return errors.Wrap(ErrGasCalculate, "setGas calc free gas")
+ }
+
+ if g.GasLeft > consensus.ActiveNetParams.MaxGasAmount {
+ g.GasLeft = consensus.ActiveNetParams.MaxGasAmount
}
- if g.StorageGas, ok = checked.MulInt64(txSize, consensus.StorageGasRate); !ok {
+ if g.StorageGas, ok = checked.MulInt64(txSize, consensus.ActiveNetParams.StorageGasRate); !ok {
return errors.Wrap(ErrGasCalculate, "setGas calc tx storage gas")
}
return nil
return errors.Wrap(ErrGasCalculate, "updateUsage calc gas diff")
}
- if !g.GasValid && (g.GasUsed > consensus.DefaultGasCredit || g.StorageGas > g.GasLeft) {
+ if !g.GasValid && (g.GasUsed > consensus.ActiveNetParams.DefaultGasCredit || g.StorageGas > g.GasLeft) {
return ErrOverGasCredit
}
return nil
case *bc.Mux:
parity := make(map[bc.AssetID]int64)
for i, src := range e.Sources {
- _, ok := vs.tx.Entries[*src.Ref]
- if !ok {
- return errors.Wrapf(bc.ErrMissingEntry, "entry for bytom input %x not found", *src.Ref)
- }
-
if src.Value.Amount > math.MaxInt64 {
return errors.WithDetailf(ErrOverflow, "amount %d exceeds maximum value 2^63", src.Value.Amount)
}
parity[*dest.Value.AssetId] = diff
}
+ btmAmount := int64(0)
for assetID, amount := range parity {
if assetID == *consensus.BTMAssetID {
- if err = vs.gasStatus.setGas(amount, int64(vs.tx.SerializedSize)); err != nil {
- return err
- }
+ btmAmount = amount
} else if amount != 0 {
return errors.WithDetailf(ErrUnbalanced, "asset %x sources - destinations = %d (should be 0)", assetID.Bytes(), amount)
}
}
+ if err = vs.gasStatus.setGas(btmAmount, int64(vs.tx.SerializedSize)); err != nil {
+ return err
+ }
+
for _, BTMInputID := range vs.tx.GasInputIDs {
e, ok := vs.tx.Entries[BTMInputID]
if !ok {
return errors.Wrapf(bc.ErrMissingEntry, "entry for bytom input %x not found", BTMInputID)
}
+
vs2 := *vs
vs2.entryID = BTMInputID
if err := checkValid(&vs2, e); err != nil {
}
}
- if len(vs.tx.GasInputIDs) > 0 {
- if err := vs.gasStatus.setGasValid(); err != nil {
- return err
- }
+ if err := vs.gasStatus.setGasValid(); err != nil {
+ return err
}
for i, src := range e.Sources {
}
}
- case *bc.Output:
+ case *bc.IntraChainOutput:
+ vs2 := *vs
+ vs2.sourcePos = 0
+ if err = checkValidSrc(&vs2, e.Source); err != nil {
+ return errors.Wrap(err, "checking output source")
+ }
+
+ case *bc.CrossChainOutput:
vs2 := *vs
vs2.sourcePos = 0
if err = checkValidSrc(&vs2, e.Source); err != nil {
return errors.Wrap(err, "checking output source")
}
+ case *bc.VoteOutput:
+ if len(e.Vote) != 64 {
+ return ErrVotePubKey
+ }
+ vs2 := *vs
+ vs2.sourcePos = 0
+ if err = checkValidSrc(&vs2, e.Source); err != nil {
+ return errors.Wrap(err, "checking vote output source")
+ }
+
+ if e.Source.Value.Amount < consensus.ActiveNetParams.MinVoteOutputAmount {
+ return ErrVoteOutputAmount
+ }
+
case *bc.Retirement:
vs2 := *vs
vs2.sourcePos = 0
return errors.Wrap(err, "checking retirement source")
}
- case *bc.Issuance:
- computedAssetID := e.WitnessAssetDefinition.ComputeAssetID()
- if computedAssetID != *e.Value.AssetId {
- return errors.WithDetailf(ErrMismatchedAssetID, "asset ID is %x, issuance wants %x", computedAssetID.Bytes(), e.Value.AssetId.Bytes())
+ case *bc.CrossChainInput:
+ if e.MainchainOutputId == nil {
+ return errors.Wrap(ErrMissingField, "crosschain input without mainchain output ID")
}
- gasLeft, err := vm.Verify(NewTxVMContext(vs, e, e.WitnessAssetDefinition.IssuanceProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
+ mainchainOutput, err := vs.tx.IntraChainOutput(*e.MainchainOutputId)
if err != nil {
- return errors.Wrap(err, "checking issuance program")
+ return errors.Wrap(err, "getting mainchain output")
}
- if err = vs.gasStatus.updateUsage(gasLeft); err != nil {
+
+ assetID := e.AssetDefinition.ComputeAssetID()
+ if *mainchainOutput.Source.Value.AssetId != *consensus.BTMAssetID && *mainchainOutput.Source.Value.AssetId != assetID {
+ return errors.New("incorrect asset_id while checking CrossChainInput")
+ }
+
+ prog := &bc.Program{
+ VmVersion: e.ControlProgram.VmVersion,
+ Code: config.FederationWScript(config.CommonConfig),
+ }
+
+ if _, err := vm.Verify(NewTxVMContext(vs, e, prog, e.WitnessArguments), consensus.ActiveNetParams.DefaultGasCredit); err != nil {
+ return errors.Wrap(err, "checking cross-chain input control program")
+ }
+
+ eq, err := mainchainOutput.Source.Value.Equal(e.WitnessDestination.Value)
+ if err != nil {
return err
}
- destVS := *vs
- destVS.destPos = 0
- if err = checkValidDest(&destVS, e.WitnessDestination); err != nil {
- return errors.Wrap(err, "checking issuance destination")
+ if !eq {
+ return errors.WithDetailf(
+ ErrMismatchedValue,
+ "previous output is for %d unit(s) of %x, spend wants %d unit(s) of %x",
+ mainchainOutput.Source.Value.Amount,
+ mainchainOutput.Source.Value.AssetId.Bytes(),
+ e.WitnessDestination.Value.Amount,
+ e.WitnessDestination.Value.AssetId.Bytes(),
+ )
+ }
+
+ vs2 := *vs
+ vs2.destPos = 0
+ if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
+ return errors.Wrap(err, "checking cross-chain input destination")
}
+ vs.gasStatus.StorageGas = 0
case *bc.Spend:
if e.SpentOutputId == nil {
return errors.Wrap(ErrMissingField, "spend without spent output ID")
}
- spentOutput, err := vs.tx.Output(*e.SpentOutputId)
+
+ spentOutput, err := vs.tx.IntraChainOutput(*e.SpentOutputId)
if err != nil {
return errors.Wrap(err, "getting spend prevout")
}
+
gasLeft, err := vm.Verify(NewTxVMContext(vs, e, spentOutput.ControlProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
if err != nil {
return errors.Wrap(err, "checking control program")
e.WitnessDestination.Value.AssetId.Bytes(),
)
}
-
vs2 := *vs
vs2.destPos = 0
if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
return errors.Wrap(err, "checking spend destination")
}
- case *bc.Coinbase:
- if vs.block == nil || len(vs.block.Transactions) == 0 || vs.block.Transactions[0] != vs.tx {
- return ErrWrongCoinbaseTransaction
- }
-
- if *e.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
- return ErrWrongCoinbaseAsset
+ case *bc.VetoInput:
+ if e.SpentOutputId == nil {
+ return errors.Wrap(ErrMissingField, "vetoInput without vetoInput output ID")
}
- if e.Arbitrary != nil && len(e.Arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
- return ErrCoinbaseArbitraryOversize
+ voteOutput, err := vs.tx.VoteOutput(*e.SpentOutputId)
+ if err != nil {
+ return errors.Wrap(err, "getting vetoInput prevout")
}
- vs2 := *vs
- vs2.destPos = 0
- if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
- return errors.Wrap(err, "checking coinbase destination")
+ if len(voteOutput.Vote) != 64 {
+ return ErrVotePubKey
}
- // special case for coinbase transaction, it's valid unit all the verify has been passed
- vs.gasStatus.GasValid = true
- case *bc.Claim:
- // 对交易的合法性进行验证
- if e.SpentOutputId == nil {
- return errors.Wrap(ErrMissingField, "spend without spent output ID")
- }
- spentOutput, err := vs.tx.Output(*e.SpentOutputId)
+ gasLeft, err := vm.Verify(NewTxVMContext(vs, e, voteOutput.ControlProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
if err != nil {
- return errors.Wrap(err, "getting spend prevout")
- }
- stack := e.GetPeginwitness()
- if len(stack) < 5 || stack[1] == nil || spentOutput.Source == nil {
-
- return errors.New("pegin-no-witness")
+ return errors.Wrap(err, "checking control program")
}
-
- // 根据claim链类型选择验证类型
- validation := &claim.BytomClaimValidation{}
- if err := validation.IsValidPeginWitness(stack, *spentOutput); err != nil {
+ if err = vs.gasStatus.updateUsage(gasLeft); err != nil {
return err
}
- eq, err := spentOutput.Source.Value.Equal(e.WitnessDestination.Value)
+ eq, err := voteOutput.Source.Value.Equal(e.WitnessDestination.Value)
if err != nil {
return err
}
if !eq {
return errors.WithDetailf(
ErrMismatchedValue,
- "previous output is for %d unit(s) of %x, spend wants %d unit(s) of %x",
- spentOutput.Source.Value.Amount,
- spentOutput.Source.Value.AssetId.Bytes(),
+ "previous output is for %d unit(s) of %x, vetoInput wants %d unit(s) of %x",
+ voteOutput.Source.Value.Amount,
+ voteOutput.Source.Value.AssetId.Bytes(),
e.WitnessDestination.Value.Amount,
e.WitnessDestination.Value.AssetId.Bytes(),
)
}
+ vs2 := *vs
+ vs2.destPos = 0
+ if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
+ return errors.Wrap(err, "checking vetoInput destination")
+ }
+
+ case *bc.Coinbase:
+ if vs.block == nil || len(vs.block.Transactions) == 0 || vs.block.Transactions[0] != vs.tx {
+ return ErrWrongCoinbaseTransaction
+ }
+
+ if *e.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
+ return ErrWrongCoinbaseAsset
+ }
+
+ if e.Arbitrary != nil && len(e.Arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit {
+ return ErrCoinbaseArbitraryOversize
+ }
vs2 := *vs
vs2.destPos = 0
if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
- return errors.Wrap(err, "checking spend destination")
+ return errors.Wrap(err, "checking coinbase destination")
}
- vs.gasStatus.GasValid = true
+ vs.gasStatus.StorageGas = 0
+
default:
return fmt.Errorf("entry has unexpected type %T", e)
}
var dest *bc.ValueDestination
switch ref := e.(type) {
+ case *bc.VetoInput:
+ if vs.Position != 0 {
+ return errors.Wrapf(ErrPosition, "invalid position %d for veto-input source", vs.Position)
+ }
+ dest = ref.WitnessDestination
+
case *bc.Coinbase:
if vs.Position != 0 {
return errors.Wrapf(ErrPosition, "invalid position %d for coinbase source", vs.Position)
}
dest = ref.WitnessDestination
- case *bc.Issuance:
+ case *bc.CrossChainInput:
if vs.Position != 0 {
- return errors.Wrapf(ErrPosition, "invalid position %d for issuance source", vs.Position)
+ return errors.Wrapf(ErrPosition, "invalid position %d for cross-chain input source", vs.Position)
}
dest = ref.WitnessDestination
return errors.Wrapf(ErrPosition, "invalid position %d for %d-destination mux source", vs.Position, len(ref.WitnessDestinations))
}
dest = ref.WitnessDestinations[vs.Position]
- case *bc.Claim:
- if vs.Position != 0 {
- return errors.Wrapf(ErrPosition, "invalid position %d for coinbase source", vs.Position)
- }
- dest = ref.WitnessDestination
+
default:
- return errors.Wrapf(bc.ErrEntryType, "value source is %T, should be coinbase, issuance, spend, or mux", e)
+ return errors.Wrapf(bc.ErrEntryType, "value source is %T, should be coinbase, cross-chain input, spend, or mux", e)
}
if dest.Ref == nil || *dest.Ref != vstate.entryID {
return errors.Wrap(ErrMissingField, "missing ref on value destination")
}
if vd.Value == nil || vd.Value.AssetId == nil {
- return errors.Wrap(ErrMissingField, "missing value on value source")
+ return errors.Wrap(ErrMissingField, "missing value on value destination")
}
e, ok := vs.tx.Entries[*vd.Ref]
var src *bc.ValueSource
switch ref := e.(type) {
- case *bc.Output:
+ case *bc.IntraChainOutput:
+ if vd.Position != 0 {
+ return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
+ }
+ src = ref.Source
+
+ case *bc.CrossChainOutput:
+ if vd.Position != 0 {
+ return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
+ }
+ src = ref.Source
+
+ case *bc.VoteOutput:
if vd.Position != 0 {
return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
}
src = ref.Sources[vd.Position]
default:
- return errors.Wrapf(bc.ErrEntryType, "value destination is %T, should be output, retirement, or mux", e)
+ return errors.Wrapf(bc.ErrEntryType, "value destination is %T, should be intra-chain/cross-chain output, retirement, or mux", e)
}
if src.Ref == nil || *src.Ref != vs.entryID {
return nil
}
-func checkStandardTx(tx *bc.Tx) error {
- for _, id := range tx.GasInputIDs {
- spend, err := tx.Spend(id)
- if err != nil {
- return err
- }
- spentOutput, err := tx.Output(*spend.SpentOutputId)
- if err != nil {
- return err
- }
-
- if !segwit.IsP2WScript(spentOutput.ControlProgram.Code) {
- return ErrNotStandardTx
- }
- }
-
- for _, id := range tx.ResultIds {
- e, ok := tx.Entries[*id]
- if !ok {
- return errors.Wrapf(bc.ErrMissingEntry, "id %x", id.Bytes())
- }
-
- output, ok := e.(*bc.Output)
- if !ok || *output.Source.Value.AssetId != *consensus.BTMAssetID {
- continue
- }
-
- if !segwit.IsP2WScript(output.ControlProgram.Code) {
- return ErrNotStandardTx
+func checkInputID(tx *bc.Tx, blockHeight uint64) error {
+ for _, id := range tx.InputIDs {
+ if id.IsZero() {
+ return ErrEmptyInputIDs
}
}
return nil
if tx.TimeRange < block.Height {
return ErrBadTimeRange
}
+
return nil
}
if err := checkTimeRange(tx, block); err != nil {
return gasStatus, err
}
- if err := checkStandardTx(tx); err != nil {
+ if err := checkInputID(tx, block.Height); err != nil {
return gasStatus, err
}
+
vs := &validationState{
block: block,
tx: tx,
}
return vs.gasStatus, checkValid(vs, tx.TxHeader)
}
+
+type validateTxWork struct {
+ i int
+ tx *bc.Tx
+ block *bc.Block
+}
+
+// ValidateTxResult is the result of async tx validate
+type ValidateTxResult struct {
+ i int
+ gasStatus *GasState
+ err error
+}
+
+// GetGasState return the gasStatus
+func (r *ValidateTxResult) GetGasState() *GasState {
+ return r.gasStatus
+}
+
+// GetError return the err
+func (r *ValidateTxResult) GetError() error {
+ return r.err
+}
+
+func validateTxWorker(workCh chan *validateTxWork, resultCh chan *ValidateTxResult, closeCh chan struct{}, wg *sync.WaitGroup) {
+ for {
+ select {
+ case work := <-workCh:
+ gasStatus, err := ValidateTx(work.tx, work.block)
+ resultCh <- &ValidateTxResult{i: work.i, gasStatus: gasStatus, err: err}
+ case <-closeCh:
+ wg.Done()
+ return
+ }
+ }
+}
+
+// ValidateTxs validates txs in async mode
+func ValidateTxs(txs []*bc.Tx, block *bc.Block) []*ValidateTxResult {
+ txSize := len(txs)
+ //init the goroutine validate worker
+ var wg sync.WaitGroup
+ workCh := make(chan *validateTxWork, txSize)
+ resultCh := make(chan *ValidateTxResult, txSize)
+ closeCh := make(chan struct{})
+ for i := 0; i <= validateWorkerNum && i < txSize; i++ {
+ wg.Add(1)
+ go validateTxWorker(workCh, resultCh, closeCh, &wg)
+ }
+
+ //sent the works
+ for i, tx := range txs {
+ workCh <- &validateTxWork{i: i, tx: tx, block: block}
+ }
+
+ //collect validate results
+ results := make([]*ValidateTxResult, txSize)
+ for i := 0; i < txSize; i++ {
+ result := <-resultCh
+ results[result.i] = result
+ }
+
+ close(closeCh)
+ wg.Wait()
+ close(workCh)
+ close(resultCh)
+ return results
+}