OSDN Git Service

fix test bug
[bytom/bytom.git] / blockchain / txbuilder / txbuilder.go
1 // Package txbuilder builds a Chain Protocol transaction from
2 // a list of actions.
3 package txbuilder
4
5 import (
6         "context"
7         "encoding/hex"
8         "encoding/json"
9         "time"
10
11         log "github.com/sirupsen/logrus"
12
13         "github.com/bytom/crypto/ed25519/chainkd"
14         "github.com/bytom/errors"
15         "github.com/bytom/math/checked"
16         "github.com/bytom/protocol/bc"
17         "github.com/bytom/protocol/bc/types"
18 )
19
20 // errors
21 var (
22         //ErrBadRefData means invalid reference data
23         ErrBadRefData = errors.New("transaction reference data does not match previous template's reference data")
24         //ErrBadTxInputIdx means unsigned tx input
25         ErrBadTxInputIdx = errors.New("unsigned tx missing input")
26         //ErrBadWitnessComponent means invalid witness component
27         ErrBadWitnessComponent = errors.New("invalid witness component")
28         //ErrBadAmount means invalid asset amount
29         ErrBadAmount = errors.New("bad asset amount")
30         //ErrBlankCheck means unsafe transaction
31         ErrBlankCheck = errors.New("unsafe transaction: leaves assets free to control")
32         //ErrAction means errors occurred in actions
33         ErrAction = errors.New("errors occurred in one or more actions")
34         //ErrMissingFields means missing required fields
35         ErrMissingFields = errors.New("required field is missing")
36         //ErrBadContractArgType means invalid contract argument type
37         ErrBadContractArgType = errors.New("invalid contract argument type")
38 )
39
40 // Build builds or adds on to a transaction.
41 // Initially, inputs are left unconsumed, and destinations unsatisfied.
42 // Build partners then satisfy and consume inputs and destinations.
43 // The final party must ensure that the transaction is
44 // balanced before calling finalize.
45 func Build(ctx context.Context, tx *types.TxData, actions []Action, maxTime time.Time, timeRange uint64) (*Template, error) {
46         builder := TemplateBuilder{
47                 base:      tx,
48                 maxTime:   maxTime,
49                 timeRange: timeRange,
50         }
51
52         // Build all of the actions, updating the builder.
53         var errs []error
54         for i, action := range actions {
55                 err := action.Build(ctx, &builder)
56                 if err != nil {
57                         log.WithFields(log.Fields{"action index": i, "error": err}).Error("Loop tx's action")
58                         errs = append(errs, errors.WithDetailf(err, "action index %v", i))
59                 }
60         }
61
62         // If there were any errors, rollback and return a composite error.
63         if len(errs) > 0 {
64                 builder.rollback()
65                 return nil, errors.WithData(ErrAction, "actions", errs)
66         }
67
68         // Build the transaction template.
69         tpl, tx, err := builder.Build()
70         if err != nil {
71                 builder.rollback()
72                 return nil, err
73         }
74
75         /*TODO: This part is use for check the balance, but now we are using btm as gas fee
76         the rule need to be rewrite when we have time
77         err = checkBlankCheck(tx)
78         if err != nil {
79                 builder.rollback()
80                 return nil, err
81         }*/
82
83         return tpl, nil
84 }
85
86 // Sign will try to sign all the witness
87 func Sign(ctx context.Context, tpl *Template, auth string, signFn SignFunc) error {
88         for i, sigInst := range tpl.SigningInstructions {
89                 for j, wc := range sigInst.WitnessComponents {
90                         switch sw := wc.(type) {
91                         case *SignatureWitness:
92                                 err := sw.sign(ctx, tpl, uint32(i), auth, signFn)
93                                 if err != nil {
94                                         return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
95                                 }
96                         case *RawTxSigWitness:
97                                 err := sw.sign(ctx, tpl, uint32(i), auth, signFn)
98                                 if err != nil {
99                                         return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
100                                 }
101                         }
102                 }
103         }
104         return materializeWitnesses(tpl)
105 }
106
107 func checkBlankCheck(tx *types.TxData) error {
108         assetMap := make(map[bc.AssetID]int64)
109         var ok bool
110         for _, in := range tx.Inputs {
111                 asset := in.AssetID() // AssetID() is calculated for IssuanceInputs, so grab once
112                 assetMap[asset], ok = checked.AddInt64(assetMap[asset], int64(in.Amount()))
113                 if !ok {
114                         return errors.WithDetailf(ErrBadAmount, "cumulative amounts for asset %x overflow the allowed asset amount 2^63", asset)
115                 }
116         }
117         for _, out := range tx.Outputs {
118                 assetMap[*out.AssetId], ok = checked.SubInt64(assetMap[*out.AssetId], int64(out.Amount))
119                 if !ok {
120                         return errors.WithDetailf(ErrBadAmount, "cumulative amounts for asset %x overflow the allowed asset amount 2^63", out.AssetId.Bytes())
121                 }
122         }
123
124         var requiresOutputs, requiresInputs bool
125         for _, amt := range assetMap {
126                 if amt > 0 {
127                         requiresOutputs = true
128                 }
129                 if amt < 0 {
130                         requiresInputs = true
131                 }
132         }
133
134         // 4 possible cases here:
135         // 1. requiresOutputs - false requiresInputs - false
136         //    This is a balanced transaction with no free assets to consume.
137         //    It could potentially be a complete transaction.
138         // 2. requiresOutputs - true requiresInputs - false
139         //    This is an unbalanced transaction with free assets to consume
140         // 3. requiresOutputs - false requiresInputs - true
141         //    This is an unbalanced transaction with a requiring assets to be spent
142         // 4. requiresOutputs - true requiresInputs - true
143         //    This is an unbalanced transaction with free assets to consume
144         //    and requiring assets to be spent.
145         // The only case that needs to be protected against is 2.
146         if requiresOutputs && !requiresInputs {
147                 return errors.Wrap(ErrBlankCheck)
148         }
149
150         return nil
151 }
152
153 // MissingFieldsError returns a wrapped error ErrMissingFields
154 // with a data item containing the given field names.
155 func MissingFieldsError(name ...string) error {
156         return errors.WithData(ErrMissingFields, "missing_fields", name)
157 }
158
159 // AddContractArgs add contract arguments
160 func AddContractArgs(sigInst *SigningInstruction, arguments []ContractArgument) error {
161         for _, arg := range arguments {
162                 switch arg.Type {
163                 case "raw_tx_signature":
164                         rawTxSig := &RawTxSigArgument{}
165                         if err := json.Unmarshal(arg.RawData, rawTxSig); err != nil {
166                                 return err
167                         }
168
169                         // convert path form chainjson.HexBytes to byte
170                         var path [][]byte
171                         for _, p := range rawTxSig.Path {
172                                 path = append(path, []byte(p))
173                         }
174                         sigInst.AddRawWitnessKeys([]chainkd.XPub{rawTxSig.RootXPub}, path, 1)
175
176                 case "data":
177                         data := &DataArgument{}
178                         if err := json.Unmarshal(arg.RawData, data); err != nil {
179                                 return err
180                         }
181
182                         value, err := hex.DecodeString(data.Value)
183                         if err != nil {
184                                 return err
185                         }
186                         sigInst.WitnessComponents = append(sigInst.WitnessComponents, DataWitness(value))
187
188                 default:
189                         return ErrBadContractArgType
190                 }
191         }
192
193         return nil
194 }