9 log "github.com/sirupsen/logrus"
11 "github.com/bytom/account"
12 "github.com/bytom/blockchain/txbuilder"
13 "github.com/bytom/consensus"
14 "github.com/bytom/consensus/segwit"
15 "github.com/bytom/errors"
16 "github.com/bytom/net/http/reqid"
17 "github.com/bytom/protocol/bc"
18 "github.com/bytom/protocol/bc/types"
22 defaultTxTTL = 30 * time.Minute
23 defaultBaseRate = float64(100000)
26 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
27 decoders := map[string]func([]byte) (txbuilder.Action, error){
28 "control_address": txbuilder.DecodeControlAddressAction,
29 "control_program": txbuilder.DecodeControlProgramAction,
30 "issue": a.wallet.AssetReg.DecodeIssueAction,
31 "retire": txbuilder.DecodeRetireAction,
32 "spend_account": a.wallet.AccountMgr.DecodeSpendAction,
33 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
35 decoder, ok := decoders[action]
39 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
41 for i, act := range req.Actions {
42 actionType, ok := act["type"].(string)
44 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
47 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
52 return count == len(req.Actions), nil
55 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
56 if err := a.checkRequestValidity(ctx, req); err != nil {
59 actions, err := a.mergeSpendActions(req)
64 maxTime := time.Now().Add(req.TTL.Duration)
65 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
66 if errors.Root(err) == txbuilder.ErrAction {
67 // append each of the inner errors contained in the data.
70 for i, innerErr := range errors.Data(err)["actions"].([]error) {
72 rootErr = errors.Root(innerErr)
74 Errs = Errs + innerErr.Error()
76 err = errors.WithDetail(rootErr, Errs)
82 // ensure null is never returned for signing instructions
83 if tpl.SigningInstructions == nil {
84 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
89 // POST /build-transaction
90 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
91 subctx := reqid.NewSubContext(ctx, reqid.New())
92 tmpl, err := a.buildSingle(subctx, buildReqs)
94 return NewErrorResponse(err)
97 return NewSuccessResponse(tmpl)
99 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
100 if err := a.completeMissingIDs(ctx, req); err != nil {
104 if req.TTL.Duration == 0 {
105 req.TTL.Duration = defaultTxTTL
108 if ok, err := onlyHaveInputActions(req); err != nil {
111 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
116 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
117 actions := make([]txbuilder.Action, 0, len(req.Actions))
118 for i, act := range req.Actions {
119 typ, ok := act["type"].(string)
121 return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
123 decoder, ok := a.actionDecoder(typ)
125 return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
128 // Remarshal to JSON, the action may have been modified when we
130 b, err := json.Marshal(act)
134 action, err := decoder(b)
136 return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
138 actions = append(actions, action)
140 actions = account.MergeSpendAction(actions)
144 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
145 if err := a.checkRequestValidity(ctx, req); err != nil {
148 actions, err := a.mergeSpendActions(req)
153 builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
154 tpls := []*txbuilder.Template{}
155 for _, action := range actions {
156 if action.ActionType() == "spend_account" {
157 tpls, err = account.SpendAccountChain(ctx, builder, action)
159 err = action.Build(ctx, builder)
168 tpl, _, err := builder.Build()
174 tpls = append(tpls, tpl)
178 // POST /build-chain-transactions
179 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
180 subctx := reqid.NewSubContext(ctx, reqid.New())
181 tmpls, err := a.buildTxs(subctx, buildReqs)
183 return NewErrorResponse(err)
185 return NewSuccessResponse(tmpls)
188 type submitTxResp struct {
189 TxID *bc.Hash `json:"tx_id"`
192 // POST /submit-transaction
193 func (a *API) submit(ctx context.Context, ins struct {
194 Tx types.Tx `json:"raw_transaction"`
196 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
197 return NewErrorResponse(err)
200 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
201 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
204 type submitTxsResp struct {
205 TxID []*bc.Hash `json:"tx_id"`
208 // POST /submit-transactions
209 func (a *API) submitTxs(ctx context.Context, ins struct {
210 Tx []types.Tx `json:"raw_transactions"`
212 txHashs := []*bc.Hash{}
213 for i := range ins.Tx {
214 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
215 return NewErrorResponse(err)
217 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
218 txHashs = append(txHashs, &ins.Tx[i].ID)
220 return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
223 // EstimateTxGasResp estimate transaction consumed gas
224 type EstimateTxGasResp struct {
225 TotalNeu int64 `json:"total_neu"`
226 FlexibleNeu int64 `json:"flexible_neu"`
227 StorageNeu int64 `json:"storage_neu"`
228 VMNeu int64 `json:"vm_neu"`
231 // estimateTxGas estimate consumed neu for transaction
232 func estimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
233 var baseP2WSHSize, totalWitnessSize, baseP2WSHGas, totalP2WPKHGas, totalP2WSHGas int64
234 baseSize := int64(352) // inputSize(224) + outputSize(128)
235 baseP2WPKHSize := int64(196)
236 baseP2WPKHGas := int64(1409)
237 for pos, input := range template.Transaction.TxData.Inputs {
238 controlProgram := input.ControlProgram()
239 if segwit.IsP2WPKHScript(controlProgram) {
240 totalWitnessSize += baseP2WPKHSize
241 totalP2WPKHGas += baseP2WPKHGas
242 } else if segwit.IsP2WSHScript(controlProgram) {
243 baseP2WSHSize, baseP2WSHGas = estimateP2WSHGas(template.SigningInstructions[pos])
244 totalWitnessSize += baseP2WSHSize
245 totalP2WSHGas += baseP2WSHGas
249 totalTxSizeGas := (int64(template.Transaction.TxData.SerializedSize) + totalWitnessSize) * consensus.StorageGasRate
250 flexibleGas := int64(0)
251 if totalP2WPKHGas > 0 {
252 flexibleGas += baseP2WPKHGas + (baseSize+baseP2WPKHSize)*consensus.StorageGasRate
253 } else if totalP2WSHGas > 0 {
254 flexibleGas += baseP2WSHGas + (baseSize+baseP2WSHSize)*consensus.StorageGasRate
257 // the total transaction gas is composed of storage and virtual machines
258 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + flexibleGas
259 return &EstimateTxGasResp{
260 TotalNeu: totalGas * consensus.VMGasRate,
261 FlexibleNeu: flexibleGas * consensus.VMGasRate,
262 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
263 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
267 // estimateP2WSH return the witness size and the gas consumed to execute the virtual machine for P2WSH program
268 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) (int64, int64) {
269 var witnessSize, gas int64
270 for _, witness := range sigInst.WitnessComponents {
271 switch t := witness.(type) {
272 case *txbuilder.SignatureWitness:
273 witnessSize += 66*int64(len(t.Keys)) + 130*int64(t.Quorum)
274 gas += 1131*int64(len(t.Keys)) + 72*int64(t.Quorum) + 659
275 if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
278 case *txbuilder.RawTxSigWitness:
279 witnessSize += 66*int64(len(t.Keys)) + 130*int64(t.Quorum)
280 gas += 1131*int64(len(t.Keys)) + 72*int64(t.Quorum) + 659
281 if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
286 return witnessSize, gas
289 // POST /estimate-transaction-gas
290 func (a *API) estimateTxGas(ctx context.Context, in struct {
291 TxTemplate txbuilder.Template `json:"transaction_template"`
293 txGasResp, err := estimateTxGas(in.TxTemplate)
295 return NewErrorResponse(err)
297 return NewSuccessResponse(txGasResp)