OSDN Git Service

Merge branch 'dev' into docker
[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         "time"
8
9         log "github.com/sirupsen/logrus"
10
11         "github.com/bytom/errors"
12         "github.com/bytom/math/checked"
13         "github.com/bytom/protocol/bc"
14         "github.com/bytom/protocol/bc/types"
15 )
16
17 // errors
18 var (
19         //ErrBadRefData means invalid reference data
20         ErrBadRefData = errors.New("transaction reference data does not match previous template's reference data")
21         //ErrBadTxInputIdx means unsigned tx input
22         ErrBadTxInputIdx = errors.New("unsigned tx missing input")
23         //ErrBadWitnessComponent means invalid witness component
24         ErrBadWitnessComponent = errors.New("invalid witness component")
25         //ErrBadAmount means invalid asset amount
26         ErrBadAmount = errors.New("bad asset amount")
27         //ErrBlankCheck means unsafe transaction
28         ErrBlankCheck = errors.New("unsafe transaction: leaves assets free to control")
29         //ErrAction means errors occurred in actions
30         ErrAction = errors.New("errors occurred in one or more actions")
31         //ErrMissingFields means missing required fields
32         ErrMissingFields = errors.New("required field is missing")
33 )
34
35 // Build builds or adds on to a transaction.
36 // Initially, inputs are left unconsumed, and destinations unsatisfied.
37 // Build partners then satisfy and consume inputs and destinations.
38 // The final party must ensure that the transaction is
39 // balanced before calling finalize.
40 func Build(ctx context.Context, tx *types.TxData, actions []Action, maxTime time.Time, timeRange uint64) (*Template, error) {
41         builder := TemplateBuilder{
42                 base:      tx,
43                 maxTime:   maxTime,
44                 timeRange: timeRange,
45         }
46
47         // Build all of the actions, updating the builder.
48         var errs []error
49         for i, action := range actions {
50                 err := action.Build(ctx, &builder)
51                 if err != nil {
52                         log.WithFields(log.Fields{"action index": i, "error": err}).Error("Loop tx's action")
53                         errs = append(errs, errors.WithDetailf(err, "action index %v", i))
54                 }
55         }
56
57         // If there were any errors, rollback and return a composite error.
58         if len(errs) > 0 {
59                 builder.rollback()
60                 return nil, errors.WithData(ErrAction, "actions", errs)
61         }
62
63         // Build the transaction template.
64         tpl, tx, err := builder.Build()
65         if err != nil {
66                 builder.rollback()
67                 return nil, err
68         }
69
70         /*TODO: This part is use for check the balance, but now we are using btm as gas fee
71         the rule need to be rewrite when we have time
72         err = checkBlankCheck(tx)
73         if err != nil {
74                 builder.rollback()
75                 return nil, err
76         }*/
77
78         return tpl, nil
79 }
80
81 // Sign will try to sign all the witness
82 func Sign(ctx context.Context, tpl *Template, auth string, signFn SignFunc) error {
83         for i, sigInst := range tpl.SigningInstructions {
84                 for j, wc := range sigInst.WitnessComponents {
85                         switch sw := wc.(type) {
86                         case *SignatureWitness:
87                                 err := sw.sign(ctx, tpl, uint32(i), auth, signFn)
88                                 if err != nil {
89                                         return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
90                                 }
91                         case *RawTxSigWitness:
92                                 err := sw.sign(ctx, tpl, uint32(i), auth, signFn)
93                                 if err != nil {
94                                         return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
95                                 }
96                         }
97                 }
98         }
99         return materializeWitnesses(tpl)
100 }
101
102 func checkBlankCheck(tx *types.TxData) error {
103         assetMap := make(map[bc.AssetID]int64)
104         var ok bool
105         for _, in := range tx.Inputs {
106                 asset := in.AssetID() // AssetID() is calculated for IssuanceInputs, so grab once
107                 assetMap[asset], ok = checked.AddInt64(assetMap[asset], int64(in.Amount()))
108                 if !ok {
109                         return errors.WithDetailf(ErrBadAmount, "cumulative amounts for asset %s overflow the allowed asset amount 2^63", asset)
110                 }
111         }
112         for _, out := range tx.Outputs {
113                 assetMap[*out.AssetId], ok = checked.SubInt64(assetMap[*out.AssetId], int64(out.Amount))
114                 if !ok {
115                         return errors.WithDetailf(ErrBadAmount, "cumulative amounts for asset %x overflow the allowed asset amount 2^63", out.AssetId.Bytes())
116                 }
117         }
118
119         var requiresOutputs, requiresInputs bool
120         for _, amt := range assetMap {
121                 if amt > 0 {
122                         requiresOutputs = true
123                 }
124                 if amt < 0 {
125                         requiresInputs = true
126                 }
127         }
128
129         // 4 possible cases here:
130         // 1. requiresOutputs - false requiresInputs - false
131         //    This is a balanced transaction with no free assets to consume.
132         //    It could potentially be a complete transaction.
133         // 2. requiresOutputs - true requiresInputs - false
134         //    This is an unbalanced transaction with free assets to consume
135         // 3. requiresOutputs - false requiresInputs - true
136         //    This is an unbalanced transaction with a requiring assets to be spent
137         // 4. requiresOutputs - true requiresInputs - true
138         //    This is an unbalanced transaction with free assets to consume
139         //    and requiring assets to be spent.
140         // The only case that needs to be protected against is 2.
141         if requiresOutputs && !requiresInputs {
142                 return errors.Wrap(ErrBlankCheck)
143         }
144
145         return nil
146 }
147
148 // MissingFieldsError returns a wrapped error ErrMissingFields
149 // with a data item containing the given field names.
150 func MissingFieldsError(name ...string) error {
151         return errors.WithData(ErrMissingFields, "missing_fields", name)
152 }