OSDN Git Service

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