X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=blobdiff_plain;f=protocol%2Fvalidation%2Ftx.go;h=d360854e120ca09a01a1cdfcbe3349cd68472bec;hp=421cc8a4377ff9df17cc69368411cdc03014ad21;hb=4635e5a0ad60e7bd8d87243ea5702b4272212980;hpb=c60c74a35412081003ba52e00e8b30247c6d74e5 diff --git a/protocol/validation/tx.go b/protocol/validation/tx.go index 421cc8a4..d360854e 100644 --- a/protocol/validation/tx.go +++ b/protocol/validation/tx.go @@ -1,24 +1,20 @@ package validation import ( - "bytes" - "encoding/json" "fmt" "math" - "strconv" + "sync" + "github.com/vapor/config" "github.com/vapor/consensus" - "github.com/vapor/consensus/segwit" - "github.com/vapor/crypto" - "github.com/vapor/equity/pegin_contract" "github.com/vapor/errors" "github.com/vapor/math/checked" "github.com/vapor/protocol/bc" - "github.com/vapor/protocol/bc/types" - bytomtypes "github.com/vapor/protocol/bc/types/bytom/types" "github.com/vapor/protocol/vm" - "github.com/vapor/protocol/vm/vmutil" - "github.com/vapor/util" +) + +const ( + validateWorkerNum = 32 ) // validate transaction error @@ -26,6 +22,7 @@ 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") @@ -42,6 +39,8 @@ var ( 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 @@ -59,17 +58,20 @@ func (g *GasState) setGas(BTMValue int64, txSize int64) error { } 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.StorageGas, ok = checked.MulInt64(txSize, consensus.StorageGasRate); !ok { + if g.GasLeft > consensus.ActiveNetParams.MaxGasAmount { + g.GasLeft = consensus.ActiveNetParams.MaxGasAmount + } + + if g.StorageGas, ok = checked.MulInt64(txSize, consensus.ActiveNetParams.StorageGasRate); !ok { return errors.Wrap(ErrGasCalculate, "setGas calc tx storage gas") } return nil @@ -101,7 +103,7 @@ func (g *GasState) updateUsage(gasLeft int64) error { 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 @@ -173,16 +175,19 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { 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 { @@ -204,10 +209,8 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { } } - 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 { @@ -218,13 +221,30 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { } } - 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") + } + case *bc.Retirement: vs2 := *vs vs2.sourcePos = 0 @@ -232,31 +252,59 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { 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") } @@ -283,204 +331,78 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { 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") } - - if err := IsValidPeginWitness(stack, *spentOutput); err != nil { + if err = vs.gasStatus.updateUsage(gasLeft); err != nil { return err } - // 判断cliam tx的输入是否已经被用 - - 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 spend destination") + return errors.Wrap(err, "checking vetoInput destination") } - vs.gasStatus.GasValid = true - default: - return fmt.Errorf("entry has unexpected type %T", e) - } - - return nil -} - -type MerkleBlock struct { - BlockHeader []byte `json:"block_header"` - TxHashes []*bc.Hash `json:"tx_hashes"` - StatusHashes []*bc.Hash `json:"status_hashes"` - Flags []uint32 `json:"flags"` - MatchedTxIDs []*bc.Hash `json:"matched_tx_ids"` -} - -func IsValidPeginWitness(peginWitness [][]byte, prevout bc.Output) (err error) { - /* - assetID := bc.AssetID{} - assetID.V0 = prevout.Source.Value.AssetId.GetV0() - assetID.V1 = prevout.Source.Value.AssetId.GetV1() - assetID.V2 = prevout.Source.Value.AssetId.GetV2() - assetID.V3 = prevout.Source.Value.AssetId.GetV3() - //bytomPrevout.Source.Value.AssetId = &assetId - - sourceID := bc.Hash{} - sourceID.V0 = prevout.Source.Ref.GetV0() - sourceID.V1 = prevout.Source.Ref.GetV1() - sourceID.V2 = prevout.Source.Ref.GetV2() - sourceID.V3 = prevout.Source.Ref.GetV3() - */ - - assetAmount := &bc.AssetAmount{ - AssetId: prevout.Source.Value.AssetId, - Amount: prevout.Source.Value.Amount, - } - src := &bc.ValueSource{ - Ref: prevout.Source.Ref, - Value: assetAmount, - Position: prevout.Source.Position, - } - prog := &bc.Program{prevout.ControlProgram.VmVersion, prevout.ControlProgram.Code} - bytomPrevout := bc.NewOutput(src, prog, prevout.Source.Position) - - if len(peginWitness) != 5 { - return errors.New("peginWitness is error") - } - amount, err := strconv.ParseUint(string(peginWitness[0]), 10, 64) - if err != nil { - return err - } - if !consensus.MoneyRange(amount) { - return errors.New("Amount out of range") - } - - if len(peginWitness[1]) != 64 { - return errors.New("The length of gennesisBlockHash is not correct") - } - - claimScript := peginWitness[2] - - rawTx := &bytomtypes.Tx{} - err = rawTx.UnmarshalText(peginWitness[3]) - if err != nil { - return err - } - - merkleBlock := &MerkleBlock{} - err = json.Unmarshal(peginWitness[4], merkleBlock) - if err != nil { - return err - } - // proof验证 - var flags []uint8 - for flag := range merkleBlock.Flags { - flags = append(flags, uint8(flag)) - } - blockHeader := &bytomtypes.BlockHeader{} - if err = blockHeader.UnmarshalText(merkleBlock.BlockHeader); err != nil { - return err - } - - if !types.ValidateTxMerkleTreeProof(merkleBlock.TxHashes, flags, merkleBlock.MatchedTxIDs, blockHeader.BlockCommitment.TransactionsMerkleRoot) { - return errors.New("Merkleblock validation failed") - } - - // 交易进行验证 - if err = checkPeginTx(rawTx, bytomPrevout, amount, claimScript); err != nil { - return err - } + case *bc.Coinbase: + if vs.block == nil || len(vs.block.Transactions) == 0 || vs.block.Transactions[0] != vs.tx { + return ErrWrongCoinbaseTransaction + } - // Check the genesis block corresponds to a valid peg (only one for now) - if !bytes.Equal(peginWitness[1], []byte(consensus.ActiveNetParams.ParentGenesisBlockHash)) { - return errors.New("ParentGenesisBlockHash don't match") - } - // TODO Finally, validate peg-in via rpc call + if *e.WitnessDestination.Value.AssetId != *consensus.BTMAssetID { + return ErrWrongCoinbaseAsset + } - if util.ValidatePegin { - if err := util.IsConfirmedBytomBlock(blockHeader.Height, consensus.ActiveNetParams.PeginMinDepth); err != nil { - return err + if e.Arbitrary != nil && len(e.Arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit { + return ErrCoinbaseArbitraryOversize } - } - return nil -} + vs2 := *vs + vs2.destPos = 0 + if err = checkValidDest(&vs2, e.WitnessDestination); err != nil { + return errors.Wrap(err, "checking coinbase destination") + } + vs.gasStatus.StorageGas = 0 -func checkPeginTx(rawTx *bytomtypes.Tx, prevout *bc.Output, claimAmount uint64, claimScript []byte) error { - // Check the transaction nout/value matches - amount := rawTx.Outputs[prevout.Source.Position].Amount - if claimAmount != amount { - return errors.New("transaction nout/value do not matches") - } - // Check that the witness program matches the p2ch on the p2sh-p2wsh transaction output - //federationRedeemScript := vmutil.CalculateContract(consensus.ActiveNetParams.FedpegXPubs, claimScript) - //scriptHash := crypto.Sha256(federationRedeemScript) - peginContractPrograms, err := pegin_contract.GetPeginContractPrograms(claimScript) - if err != nil { - return err + default: + return fmt.Errorf("entry has unexpected type %T", e) } - scriptHash := crypto.Sha256(peginContractPrograms) - controlProg, err := vmutil.P2WSHProgram(scriptHash) - if err != nil { - return err - } - if !bytes.Equal(rawTx.Outputs[prevout.Source.Position].ControlProgram, controlProg) { - return errors.New("The output control program of transaction does not match the control program of the system's alliance contract") - } return nil } @@ -508,15 +430,21 @@ func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error { 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 @@ -531,13 +459,9 @@ func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error { 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 { @@ -567,7 +491,7 @@ func checkValidDest(vs *validationState, vd *bc.ValueDestination) error { 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] @@ -577,7 +501,19 @@ func checkValidDest(vs *validationState, vd *bc.ValueDestination) error { 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) } @@ -596,7 +532,7 @@ func checkValidDest(vs *validationState, vd *bc.ValueDestination) error { 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 { @@ -618,35 +554,10 @@ func checkValidDest(vs *validationState, vd *bc.ValueDestination) error { 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 @@ -660,6 +571,7 @@ func checkTimeRange(tx *bc.Tx, block *bc.Block) error { if tx.TimeRange < block.Height { return ErrBadTimeRange } + return nil } @@ -675,9 +587,10 @@ func ValidateTx(tx *bc.Tx, block *bc.Block) (*GasState, error) { 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, @@ -687,3 +600,71 @@ func ValidateTx(tx *bc.Tx, block *bc.Block) (*GasState, error) { } 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 +}