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")
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
}
}
- 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.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")
}
return err
}
- eq, err := spentOutput.Source.Value.Equal(e.WitnessDestination.Value)
+ eq, err := value.Equal(e.WitnessDestination.Value)
if err != nil {
return err
}
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 {
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)
}
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")
}
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
+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
}
}
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 := checkStandardTx(tx, block.Height); err != nil {
return gasStatus, err
}
+
vs := &validationState{
block: block,
tx: tx,