OSDN Git Service

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