OSDN Git Service

Merge pull request #581 from Bytom/control-account
[bytom/bytom.git] / api / transact.go
1 package api
2
3 import (
4         "context"
5         "encoding/json"
6         "fmt"
7         "strings"
8         "time"
9
10         log "github.com/sirupsen/logrus"
11
12         "github.com/bytom/blockchain/txbuilder"
13         "github.com/bytom/errors"
14         "github.com/bytom/net/http/reqid"
15         "github.com/bytom/protocol/bc"
16         "github.com/bytom/protocol/bc/types"
17 )
18
19 var defaultTxTTL = 5 * time.Minute
20
21 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
22         var decoder func([]byte) (txbuilder.Action, error)
23         switch action {
24         case "control_address":
25                 decoder = txbuilder.DecodeControlAddressAction
26         case "control_program":
27                 decoder = txbuilder.DecodeControlProgramAction
28         case "control_receiver":
29                 decoder = txbuilder.DecodeControlReceiverAction
30         case "issue":
31                 decoder = a.wallet.AssetReg.DecodeIssueAction
32         case "retire":
33                 decoder = txbuilder.DecodeRetireAction
34         case "spend_account":
35                 decoder = a.wallet.AccountMgr.DecodeSpendAction
36         case "spend_account_unspent_output":
37                 decoder = a.wallet.AccountMgr.DecodeSpendUTXOAction
38         default:
39                 return nil, false
40         }
41         return decoder, true
42 }
43
44 func mergeActions(req *BuildRequest) []map[string]interface{} {
45         actions := make([]map[string]interface{}, 0)
46         actionMap := make(map[string]map[string]interface{})
47
48         for _, m := range req.Actions {
49                 if actionType := m["type"].(string); actionType != "spend_account" {
50                         actions = append(actions, m)
51                         continue
52                 }
53
54                 actionKey := m["asset_id"].(string) + m["account_id"].(string)
55                 amountNumber := m["amount"].(json.Number)
56                 amount, _ := amountNumber.Int64()
57
58                 if tmpM, ok := actionMap[actionKey]; ok {
59                         tmpNumber, _ := tmpM["amount"].(json.Number)
60                         tmpAmount, _ := tmpNumber.Int64()
61                         tmpM["amount"] = json.Number(fmt.Sprintf("%v", tmpAmount+amount))
62                 } else {
63                         actionMap[actionKey] = m
64                         actions = append(actions, m)
65                 }
66         }
67
68         return actions
69 }
70
71 func onlyHaveSpendActions(req *BuildRequest) bool {
72         count := 0
73         for _, m := range req.Actions {
74                 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
75                         count++
76                 }
77         }
78
79         return count == len(req.Actions)
80 }
81
82 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
83         err := a.filterAliases(ctx, req)
84         if err != nil {
85                 return nil, err
86         }
87
88         if onlyHaveSpendActions(req) {
89                 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
90         }
91
92         reqActions := mergeActions(req)
93         actions := make([]txbuilder.Action, 0, len(reqActions))
94         for i, act := range reqActions {
95                 typ, ok := act["type"].(string)
96                 if !ok {
97                         return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
98                 }
99                 decoder, ok := a.actionDecoder(typ)
100                 if !ok {
101                         return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
102                 }
103
104                 // Remarshal to JSON, the action may have been modified when we
105                 // filtered aliases.
106                 b, err := json.Marshal(act)
107                 if err != nil {
108                         return nil, err
109                 }
110                 action, err := decoder(b)
111                 if err != nil {
112                         return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
113                 }
114                 actions = append(actions, action)
115         }
116
117         ttl := req.TTL.Duration
118         if ttl == 0 {
119                 ttl = defaultTxTTL
120         }
121         maxTime := time.Now().Add(ttl)
122
123         tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
124         if errors.Root(err) == txbuilder.ErrAction {
125                 // append each of the inner errors contained in the data.
126                 var Errs string
127                 for _, innerErr := range errors.Data(err)["actions"].([]error) {
128                         Errs = Errs + "<" + innerErr.Error() + ">"
129                 }
130                 err = errors.New(err.Error() + "-" + Errs)
131         }
132         if err != nil {
133                 return nil, err
134         }
135
136         // ensure null is never returned for signing instructions
137         if tpl.SigningInstructions == nil {
138                 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
139         }
140         return tpl, nil
141 }
142
143 // POST /build-transaction
144 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
145         subctx := reqid.NewSubContext(ctx, reqid.New())
146
147         tmpl, err := a.buildSingle(subctx, buildReqs)
148         if err != nil {
149                 return NewErrorResponse(err)
150         }
151
152         return NewSuccessResponse(tmpl)
153 }
154
155 func (a *API) submitSingle(ctx context.Context, tpl *txbuilder.Template) (map[string]string, error) {
156         if tpl.Transaction == nil {
157                 return nil, errors.Wrap(txbuilder.ErrMissingRawTx)
158         }
159
160         if err := txbuilder.FinalizeTx(ctx, a.chain, tpl.Transaction); err != nil {
161                 return nil, errors.Wrapf(err, "tx %s", tpl.Transaction.ID.String())
162         }
163
164         return map[string]string{"tx_id": tpl.Transaction.ID.String()}, nil
165 }
166
167 type submitTxResp struct {
168         TxID *bc.Hash `json:"tx_id"`
169 }
170
171 // POST /submit-transaction
172 func (a *API) submit(ctx context.Context, ins struct {
173         Tx types.Tx `json:"raw_transaction"`
174 }) Response {
175         if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
176                 return NewErrorResponse(err)
177         }
178
179         log.WithField("tx_id", ins.Tx.ID).Info("submit single tx")
180         return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
181 }
182
183 // POST /sign-submit-transaction
184 func (a *API) signSubmit(ctx context.Context, x struct {
185         Password []string           `json:"password"`
186         Txs      txbuilder.Template `json:"transaction"`
187 }) Response {
188         if err := txbuilder.Sign(ctx, &x.Txs, nil, x.Password[0], a.pseudohsmSignTemplate); err != nil {
189                 log.WithField("build err", err).Error("fail on sign transaction.")
190                 return NewErrorResponse(err)
191         }
192         log.Info("Sign Transaction complete.")
193
194         txID, err := a.submitSingle(nil, &x.Txs)
195         if err != nil {
196                 log.WithField("err", err).Error("submit single tx")
197                 return NewErrorResponse(err)
198         }
199
200         log.WithField("tx_id", txID["tx_id"]).Info("submit single tx")
201         return NewSuccessResponse(txID)
202 }