7 cfg "github.com/vapor/config"
8 "github.com/vapor/consensus"
9 "github.com/vapor/errors"
10 "github.com/vapor/protocol"
11 "github.com/vapor/protocol/bc/types"
12 "github.com/vapor/protocol/vm"
16 // ErrRejected means the network rejected a tx (as a double-spend)
17 ErrRejected = errors.New("transaction rejected")
18 // ErrMissingRawTx means missing transaction
19 ErrMissingRawTx = errors.New("missing raw tx")
20 // ErrBadInstructionCount means too many signing instructions compare with inputs
21 ErrBadInstructionCount = errors.New("too many signing instructions in template")
22 // ErrOrphanTx means submit transaction is orphan
23 ErrOrphanTx = errors.New("finalize can't find transaction input utxo")
24 // ErrExtTxFee means transaction fee exceed max limit
25 ErrExtTxFee = errors.New("transaction fee exceed max limit")
28 // FinalizeTx validates a transaction signature template,
29 // assembles a fully signed tx, and stores the effects of
30 // its changes on the UTXO set.
31 func FinalizeTx(ctx context.Context, c *protocol.Chain, tx *types.Tx) error {
32 if fee := CalculateTxFee(tx); fee > cfg.CommonConfig.Wallet.MaxTxFee {
36 if err := checkTxSighashCommitment(tx); err != nil {
40 // This part is use for prevent tx size is 0
41 data, err := tx.TxData.MarshalText()
45 tx.TxData.SerializedSize = uint64(len(data))
46 tx.Tx.SerializedSize = uint64(len(data))
47 isOrphan, err := c.ValidateTx(tx)
48 if errors.Root(err) == protocol.ErrBadTx {
49 return errors.Sub(ErrRejected, err)
52 return errors.WithDetail(err, "tx rejected: "+err.Error())
61 // ErrNoTxSighashCommitment is returned when no input commits to the
62 // complete transaction.
63 // To permit idempotence of transaction submission, we require at
64 // least one input to commit to the complete transaction (what you get
65 // when you build a transaction with allow_additional_actions=false).
66 ErrNoTxSighashCommitment = errors.New("no commitment to tx sighash")
68 // ErrNoTxSighashAttempt is returned when there was no attempt made to sign
70 ErrNoTxSighashAttempt = errors.New("no tx sighash attempted")
72 // ErrTxSignatureFailure is returned when there was an attempt to sign this
73 // transaction, but it failed.
74 ErrTxSignatureFailure = errors.New("tx signature was attempted but failed")
77 func checkTxSighashCommitment(tx *types.Tx) error {
78 // TODO: this is the local sender check rules, we might don't need it due to the rule is difference
82 for i, inp := range tx.Inputs {
84 switch t := inp.TypedInput.(type) {
85 case *types.SpendInput:
87 case *types.IssuanceInput:
89 case *types.ClaimInput:
94 // Note: These numbers will need to change if more args are added such that the minimum length changes
96 // A conforming arguments list contains
97 // [... arg1 arg2 ... argN N sig1 sig2 ... sigM prog]
98 // The args are the opaque arguments to prog. In the case where
99 // N is 0 (prog takes no args), and assuming there must be at
100 // least one signature, args has a minimum length of 3.
102 lastError = ErrNoTxSighashAttempt
105 lastError = ErrTxSignatureFailure
108 lastError = ErrNoTxSighashCommitment
109 prog := args[len(args)-1]
113 if prog[0] != byte(vm.OP_DATA_32) {
116 if !bytes.Equal(prog[33:], []byte{byte(vm.OP_TXSIGHASH), byte(vm.OP_EQUAL)}) {
119 h := tx.SigHash(uint32(i))
120 if !bytes.Equal(h.Bytes(), prog[1:33]) {
123 // At least one input passes commitment checks
130 // CalculateTxFee calculate transaction fee
131 func CalculateTxFee(tx *types.Tx) (fee uint64) {
132 totalInputBTM := uint64(0)
133 totalOutputBTM := uint64(0)
135 for _, input := range tx.Inputs {
136 if input.InputType() != types.CoinbaseInputType && input.InputType() != types.DposInputType && input.AssetID() == *consensus.BTMAssetID {
137 totalInputBTM += input.Amount()
141 for _, output := range tx.Outputs {
142 if *output.AssetId == *consensus.BTMAssetID {
143 totalOutputBTM += output.Amount
146 fee = totalInputBTM - totalOutputBTM