X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=protocol%2Fvalidation%2Ftx.go;h=d7cc42b5eb52af4247b5e438211515ffa63e8943;hb=33703f3e14106dbf91be25a78e7909c5664baf45;hp=526baa6db5bd35be6436eefd36d30c90468b5dcd;hpb=e58c4b1e2f0573318ffc699919599609ccf5da10;p=bytom%2Fvapor.git diff --git a/protocol/validation/tx.go b/protocol/validation/tx.go index 526baa6d..d7cc42b5 100644 --- a/protocol/validation/tx.go +++ b/protocol/validation/tx.go @@ -2,30 +2,26 @@ package validation import ( "bytes" - "encoding/json" "fmt" "math" - "strconv" + "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/bytom" - 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 ruleAA = 142500 + // 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") @@ -42,6 +38,7 @@ 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") ) // GasState record the gas usage status @@ -204,10 +201,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,50 +213,77 @@ 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.Retirement: + case *bc.CrossChainOutput: vs2 := *vs vs2.sourcePos = 0 if err = checkValidSrc(&vs2, e.Source); err != nil { - return errors.Wrap(err, "checking retirement source") + 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.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.Retirement: + vs2 := *vs + vs2.sourcePos = 0 + if err = checkValidSrc(&vs2, e.Source); err != nil { + return errors.Wrap(err, "checking retirement source") } - gasLeft, err := vm.Verify(NewTxVMContext(vs, e, e.WitnessAssetDefinition.IssuanceProgram, e.WitnessArguments), vs.gasStatus.GasLeft) + case *bc.CrossChainInput: + _, err := vm.Verify(NewTxVMContext(vs, e, e.ControlProgram, e.WitnessArguments), consensus.DefaultGasCredit) if err != nil { - return errors.Wrap(err, "checking issuance program") - } - if err = vs.gasStatus.updateUsage(gasLeft); err != nil { - return err + return errors.Wrap(err, "checking cross-chain input control program") } - destVS := *vs - destVS.destPos = 0 - if err = checkValidDest(&destVS, e.WitnessDestination); err != nil { - return errors.Wrap(err, "checking issuance destination") + vs2 := *vs + vs2.destPos = 0 + if err = checkValidDest(&vs2, e.WitnessDestination); err != nil { + return errors.Wrap(err, "checking cross-chain input destination") } case *bc.Spend: if e.SpentOutputId == nil { return errors.Wrap(ErrMissingField, "spend without spent output ID") } - spentOutput, err := vs.tx.Output(*e.SpentOutputId) + var ( + controlProgram *bc.Program + value *bc.AssetAmount + ) + entryOutput, err := vs.tx.Entry(*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) + switch output := entryOutput.(type) { + case *bc.IntraChainOutput: + controlProgram = output.ControlProgram + value = output.Source.Value + case *bc.VoteOutput: + if len(output.Vote) != 64 { + return ErrVotePubKey + } + controlProgram = output.ControlProgram + value = output.Source.Value + default: + return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", e.SpentOutputId.Bytes(), entryOutput) + } + + gasLeft, err := vm.Verify(NewTxVMContext(vs, e, controlProgram, e.WitnessArguments), vs.gasStatus.GasLeft) if err != nil { return errors.Wrap(err, "checking control program") } @@ -269,7 +291,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { return err } - eq, err := spentOutput.Source.Value.Equal(e.WitnessDestination.Value) + eq, err := value.Equal(e.WitnessDestination.Value) if err != nil { return err } @@ -277,13 +299,12 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { 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(), + value.Amount, + 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 { @@ -308,51 +329,8 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { if err = checkValidDest(&vs2, e.WitnessDestination); err != nil { return errors.Wrap(err, "checking coinbase destination") } + vs.gasStatus.StorageGas = 0 - // 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) - 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") - } - - if err := IsValidPeginWitness(stack, *spentOutput); err != nil { - return err - } - - // 判断cliam tx的输入是否已经被用 - - eq, err := spentOutput.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(), - 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") - } - vs.gasStatus.GasValid = true default: return fmt.Errorf("entry has unexpected type %T", e) } @@ -360,128 +338,6 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { return nil } -type MerkleBlock struct { - BlockHeader []byte `json:"block_header"` - TxHashes []*bytom.Hash `json:"tx_hashes"` - StatusHashes []*bytom.Hash `json:"status_hashes"` - Flags []uint32 `json:"flags"` - MatchedTxIDs []*bytom.Hash `json:"matched_tx_ids"` -} - -func IsValidPeginWitness(peginWitness [][]byte, prevout bc.Output) (err error) { - assetID := bytom.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 := bytom.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 := &bytom.AssetAmount{ - AssetId: &assetID, - Amount: prevout.Source.Value.Amount, - } - - src := &bytom.ValueSource{ - Ref: &sourceID, - Value: assetAmount, - Position: prevout.Source.Position, - } - prog := &bytom.Program{prevout.ControlProgram.VmVersion, prevout.ControlProgram.Code} - bytomPrevout := bytom.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 !bytomtypes.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 - } - - // 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 util.ValidatePegin { - if err := util.IsConfirmedBytomBlock(blockHeader.Height, consensus.ActiveNetParams.PeginMinDepth); err != nil { - return err - } - } - - return nil -} - -func checkPeginTx(rawTx *bytomtypes.Tx, prevout *bytom.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 - } - - 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 -} - func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error { if vs == nil { return errors.Wrap(ErrMissingField, "empty value source") @@ -512,9 +368,9 @@ func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error { } 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 @@ -529,13 +385,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 { @@ -565,7 +417,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] @@ -575,7 +427,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) } @@ -594,7 +458,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 { @@ -616,34 +480,53 @@ 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 +func checkFedaration(tx *bc.Tx) error { + for _, id := range tx.InputIDs { + switch inp := tx.Entries[id].(type) { + case *bc.CrossChainInput: + fedProg := config.FederationProgrom(config.CommonConfig) + if !bytes.Equal(inp.ControlProgram.Code, fedProg) { + return errors.New("The federal controlProgram is incorrect") + } + default: + continue } + } + return nil +} - if !segwit.IsP2WScript(spentOutput.ControlProgram.Code) { - return ErrNotStandardTx +func checkStandardTx(tx *bc.Tx, blockHeight uint64) error { + for _, id := range tx.InputIDs { + if blockHeight >= ruleAA && id.IsZero() { + return ErrEmptyInputIDs } } - for _, id := range tx.ResultIds { - e, ok := tx.Entries[*id] - if !ok { - return errors.Wrapf(bc.ErrMissingEntry, "id %x", id.Bytes()) - } + if err := checkFedaration(tx); err != nil { + return err + } - output, ok := e.(*bc.Output) - if !ok || *output.Source.Value.AssetId != *consensus.BTMAssetID { + for _, id := range tx.GasInputIDs { + spend, err := tx.Spend(id) + if err != nil { continue } - if !segwit.IsP2WScript(output.ControlProgram.Code) { + code := []byte{} + outputEntry, err := tx.Entry(*spend.SpentOutputId) + if err != nil { + return err + } + switch output := outputEntry.(type) { + case *bc.IntraChainOutput: + code = output.ControlProgram.Code + case *bc.VoteOutput: + code = output.ControlProgram.Code + default: + return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", id.Bytes(), outputEntry) + } + + if !segwit.IsP2WScript(code) { return ErrNotStandardTx } } @@ -658,6 +541,7 @@ func checkTimeRange(tx *bc.Tx, block *bc.Block) error { if tx.TimeRange < block.Height { return ErrBadTimeRange } + return nil } @@ -673,9 +557,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 := checkStandardTx(tx, block.Height); err != nil { return gasStatus, err } + vs := &validationState{ block: block, tx: tx,