import (
"fmt"
"math"
-
- "github.com/vapor/config"
- "github.com/vapor/consensus"
- "github.com/vapor/errors"
- "github.com/vapor/math/checked"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/vm"
+ "runtime"
+ "sync"
+
+ "github.com/bytom/vapor/common"
+ "github.com/bytom/vapor/config"
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/math/checked"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/vm"
)
-const ruleAA = 142500
-
// validate transaction error
var (
ErrTxVersion = errors.New("invalid transaction version")
ErrGasCalculate = errors.New("gas usage calculate got a math error")
ErrVotePubKey = errors.New("invalid public key of vote")
ErrVoteOutputAmount = errors.New("invalid vote amount")
+ ErrVoteOutputAseet = errors.New("incorrect asset_id while checking vote asset")
)
// 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
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 {
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.MinVoteOutputAmount {
+
+ if e.Source.Value.Amount < consensus.ActiveNetParams.MinVoteOutputAmount {
return ErrVoteOutputAmount
}
+ if *e.Source.Value.AssetId != *consensus.BTMAssetID {
+ return ErrVoteOutputAseet
+ }
+
case *bc.Retirement:
vs2 := *vs
vs2.sourcePos = 0
}
case *bc.CrossChainInput:
- // check assetID
+ if e.MainchainOutputId == nil {
+ return errors.Wrap(ErrMissingField, "crosschain input without mainchain output ID")
+ }
+
+ mainchainOutput, err := vs.tx.IntraChainOutput(*e.MainchainOutputId)
+ if err != nil {
+ return errors.Wrap(err, "getting mainchain output")
+ }
+
assetID := e.AssetDefinition.ComputeAssetID()
- if *e.Value.AssetId != *consensus.BTMAssetID && *e.Value.AssetId != assetID {
- return errors.New("incorrect asset_id while check CrossChainInput")
+ if *mainchainOutput.Source.Value.AssetId != *consensus.BTMAssetID && *mainchainOutput.Source.Value.AssetId != assetID {
+ return errors.New("incorrect asset_id while checking CrossChainInput")
}
- code := config.FederationProgrom(config.CommonConfig)
+
prog := &bc.Program{
- VmVersion: e.ControlProgram.VmVersion,
- Code: code,
+ VmVersion: e.AssetDefinition.IssuanceProgram.VmVersion,
+ Code: e.AssetDefinition.IssuanceProgram.Code,
}
- _, err := vm.Verify(NewTxVMContext(vs, e, prog, e.WitnessArguments), consensus.DefaultGasCredit)
- if err != nil {
+
+ if !common.IsOpenFederationIssueAsset(e.RawDefinitionByte) {
+ prog.Code = config.FederationWScript(config.CommonConfig, vs.block.Height)
+ }
+
+ 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
+ }
+
+ 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 {
if err != nil {
return errors.Wrap(err, "getting vetoInput prevout")
}
+
if len(voteOutput.Vote) != 64 {
return ErrVotePubKey
}
return ErrWrongCoinbaseAsset
}
- if e.Arbitrary != nil && len(e.Arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
+ if e.Arbitrary != nil && len(e.Arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit {
return ErrCoinbaseArbitraryOversize
}
func checkInputID(tx *bc.Tx, blockHeight uint64) error {
for _, id := range tx.InputIDs {
- if blockHeight >= ruleAA && id.IsZero() {
+ if id.IsZero() {
return ErrEmptyInputIDs
}
}
return nil
}
+func applySoftFork001(vs *validationState, err error) {
+ if err == nil || vs.block.Height < consensus.ActiveNetParams.SoftForkPoint[consensus.SoftFork001] {
+ return
+ }
+
+ if rootErr := errors.Root(err); rootErr == ErrVotePubKey || rootErr == ErrVoteOutputAmount || rootErr == ErrVoteOutputAseet {
+ vs.gasStatus.GasValid = false
+ }
+}
+
// ValidateTx validates a transaction.
func ValidateTx(tx *bc.Tx, block *bc.Block) (*GasState, error) {
gasStatus := &GasState{GasValid: false}
gasStatus: gasStatus,
cache: make(map[bc.Hash]error),
}
- return vs.gasStatus, checkValid(vs, tx.TxHeader)
+
+ err := checkValid(vs, tx.TxHeader)
+ applySoftFork001(vs, err)
+ return vs.gasStatus, err
+}
+
+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)
+ validateWorkerNum := runtime.NumCPU()
+ //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
}