OSDN Git Service

1cafe4f0727469c501debcbb519144a90c35d487
[bytom/vapor.git] / blockchain / txbuilder / finalize.go
1 package txbuilder
2
3 import (
4         "bytes"
5         "context"
6
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"
13 )
14
15 var (
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")
26 )
27
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 {
33                 return ErrExtTxFee
34         }
35
36         if err := checkTxSighashCommitment(tx); err != nil {
37                 return err
38         }
39
40         // This part is use for prevent tx size  is 0
41         data, err := tx.TxData.MarshalText()
42         if err != nil {
43                 return err
44         }
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)
50         }
51         if err != nil {
52                 return errors.WithDetail(err, "tx rejected: "+err.Error())
53         }
54         if isOrphan {
55                 return ErrOrphanTx
56         }
57         return nil
58 }
59
60 var (
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")
67
68         // ErrNoTxSighashAttempt is returned when there was no attempt made to sign
69         // this transaction.
70         ErrNoTxSighashAttempt = errors.New("no tx sighash attempted")
71
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")
75 )
76
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
79         return nil
80         var lastError error
81
82         for i, inp := range tx.Inputs {
83                 var args [][]byte
84                 switch t := inp.TypedInput.(type) {
85                 case *types.SpendInput:
86                         args = t.Arguments
87                 case *types.IssuanceInput:
88                         args = t.Arguments
89                 case *types.ClaimInput:
90                         args = t.Arguments
91                 case *types.DposTx:
92                         args = t.Arguments
93                 }
94                 // Note: These numbers will need to change if more args are added such that the minimum length changes
95                 switch {
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.
101                 case len(args) == 0:
102                         lastError = ErrNoTxSighashAttempt
103                         continue
104                 case len(args) < 3:
105                         lastError = ErrTxSignatureFailure
106                         continue
107                 }
108                 lastError = ErrNoTxSighashCommitment
109                 prog := args[len(args)-1]
110                 if len(prog) != 35 {
111                         continue
112                 }
113                 if prog[0] != byte(vm.OP_DATA_32) {
114                         continue
115                 }
116                 if !bytes.Equal(prog[33:], []byte{byte(vm.OP_TXSIGHASH), byte(vm.OP_EQUAL)}) {
117                         continue
118                 }
119                 h := tx.SigHash(uint32(i))
120                 if !bytes.Equal(h.Bytes(), prog[1:33]) {
121                         continue
122                 }
123                 // At least one input passes commitment checks
124                 return nil
125         }
126
127         return lastError
128 }
129
130 // CalculateTxFee calculate transaction fee
131 func CalculateTxFee(tx *types.Tx) (fee uint64) {
132         totalInputBTM := uint64(0)
133         totalOutputBTM := uint64(0)
134
135         for _, input := range tx.Inputs {
136                 if input.InputType() != types.CoinbaseInputType && input.InputType() != types.DposInputType && input.AssetID() == *consensus.BTMAssetID {
137                         totalInputBTM += input.Amount()
138                 }
139         }
140
141         for _, output := range tx.Outputs {
142                 if *output.AssetId == *consensus.BTMAssetID {
143                         totalOutputBTM += output.Amount
144                 }
145         }
146         fee = totalInputBTM - totalOutputBTM
147         return
148 }