10 log "github.com/sirupsen/logrus"
12 "github.com/vapor/account"
13 "github.com/vapor/blockchain/txbuilder"
14 "github.com/vapor/consensus"
15 "github.com/vapor/consensus/segwit"
16 "github.com/vapor/errors"
17 "github.com/vapor/math/checked"
18 "github.com/vapor/net/http/reqid"
19 "github.com/vapor/protocol/bc"
20 "github.com/vapor/protocol/bc/types"
24 defaultTxTTL = 5 * time.Minute
25 defaultBaseRate = float64(100000)
26 flexibleGas = int64(1800)
29 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
30 decoders := map[string]func([]byte) (txbuilder.Action, error){
31 "control_address": txbuilder.DecodeControlAddressAction,
32 "control_program": txbuilder.DecodeControlProgramAction,
33 "issue": a.wallet.AssetReg.DecodeIssueAction,
34 "retire": txbuilder.DecodeRetireAction,
35 "spend_account": a.wallet.AccountMgr.DecodeSpendAction,
36 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
37 "dpos_address": a.wallet.AccountMgr.DecodeDposAction,
38 "ipfs_data": txbuilder.DecodeIpfsDataAction,
40 decoder, ok := decoders[action]
44 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
46 for i, act := range req.Actions {
47 actionType, ok := act["type"].(string)
49 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
51 if strings.HasPrefix(actionType, "dpos_address") {
54 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
59 return count == len(req.Actions), nil
62 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
63 if err := a.checkRequestValidity(ctx, req); err != nil {
66 actions, err := a.mergeSpendActions(req)
71 maxTime := time.Now().Add(req.TTL.Duration)
72 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
73 if errors.Root(err) == txbuilder.ErrAction {
74 // append each of the inner errors contained in the data.
77 for i, innerErr := range errors.Data(err)["actions"].([]error) {
79 rootErr = errors.Root(innerErr)
81 Errs = Errs + innerErr.Error()
83 err = errors.WithDetail(rootErr, Errs)
89 // ensure null is never returned for signing instructions
90 if tpl.SigningInstructions == nil {
91 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
96 // POST /build-transaction
97 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
98 subctx := reqid.NewSubContext(ctx, reqid.New())
99 tmpl, err := a.buildSingle(subctx, buildReqs)
101 return NewErrorResponse(err)
104 return NewSuccessResponse(tmpl)
106 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
107 if err := a.completeMissingIDs(ctx, req); err != nil {
111 if req.TTL.Duration == 0 {
112 req.TTL.Duration = defaultTxTTL
115 if ok, err := onlyHaveInputActions(req); err != nil {
118 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
123 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
124 actions := make([]txbuilder.Action, 0, len(req.Actions))
125 for i, act := range req.Actions {
126 typ, ok := act["type"].(string)
128 return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
130 decoder, ok := a.actionDecoder(typ)
132 return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
135 // Remarshal to JSON, the action may have been modified when we
137 b, err := json.Marshal(act)
141 action, err := decoder(b)
143 return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
145 actions = append(actions, action)
147 actions = account.MergeSpendAction(actions)
151 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
152 if err := a.checkRequestValidity(ctx, req); err != nil {
155 actions, err := a.mergeSpendActions(req)
160 builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
161 tpls := []*txbuilder.Template{}
162 for _, action := range actions {
163 if action.ActionType() == "spend_account" {
164 tpls, err = account.SpendAccountChain(ctx, builder, action)
166 err = action.Build(ctx, builder)
175 tpl, _, err := builder.Build()
181 tpls = append(tpls, tpl)
185 // POST /build-chain-transactions
186 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
187 subctx := reqid.NewSubContext(ctx, reqid.New())
188 tmpls, err := a.buildTxs(subctx, buildReqs)
190 return NewErrorResponse(err)
192 return NewSuccessResponse(tmpls)
195 type submitTxResp struct {
196 TxID *bc.Hash `json:"tx_id"`
199 // POST /submit-transaction
200 func (a *API) submit(ctx context.Context, ins struct {
201 Tx types.Tx `json:"raw_transaction"`
203 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
204 return NewErrorResponse(err)
207 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
208 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
211 type submitTxsResp struct {
212 TxID []*bc.Hash `json:"tx_id"`
215 // POST /submit-transactions
216 func (a *API) submitTxs(ctx context.Context, ins struct {
217 Tx []types.Tx `json:"raw_transactions"`
219 txHashs := []*bc.Hash{}
220 for i := range ins.Tx {
221 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
222 return NewErrorResponse(err)
224 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
225 txHashs = append(txHashs, &ins.Tx[i].ID)
227 return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
230 // EstimateTxGasResp estimate transaction consumed gas
231 type EstimateTxGasResp struct {
232 TotalNeu int64 `json:"total_neu"`
233 StorageNeu int64 `json:"storage_neu"`
234 VMNeu int64 `json:"vm_neu"`
237 // EstimateTxGas estimate consumed neu for transaction
238 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
239 // base tx size and not include sign
240 data, err := template.Transaction.TxData.MarshalText()
244 baseTxSize := int64(len(data))
246 // extra tx size for sign witness parts
247 signSize := estimateSignSize(template.SigningInstructions)
249 // total gas for tx storage
250 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
252 return nil, errors.New("calculate txsize gas got a math error")
255 // consume gas for run VM
256 totalP2WPKHGas := int64(0)
257 totalP2WSHGas := int64(0)
258 baseP2WPKHGas := int64(1419)
259 // flexible Gas is used for handle need extra utxo situation
261 for pos, inpID := range template.Transaction.Tx.InputIDs {
262 sp, err := template.Transaction.Spend(inpID)
267 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
272 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
273 totalP2WPKHGas += baseP2WPKHGas
274 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
275 sigInst := template.SigningInstructions[pos]
276 totalP2WSHGas += estimateP2WSHGas(sigInst)
280 // total estimate gas
281 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + flexibleGas
283 // rounding totalNeu with base rate 100000
284 totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
285 roundingNeu := math.Ceil(totalNeu)
286 estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
290 return &EstimateTxGasResp{
291 TotalNeu: estimateNeu,
292 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
293 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
297 // estimate p2wsh gas.
298 // OP_CHECKMULTISIG consume (984 * a - 72 * b - 63) gas,
299 // where a represent the num of public keys, and b represent the num of quorum.
300 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) int64 {
302 baseP2WSHGas := int64(738)
304 for _, witness := range sigInst.WitnessComponents {
305 switch t := witness.(type) {
306 case *txbuilder.SignatureWitness:
307 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
308 case *txbuilder.RawTxSigWitness:
309 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
315 // estimate signature part size.
316 // if need multi-sign, calculate the size according to the length of keys.
317 func estimateSignSize(signingInstructions []*txbuilder.SigningInstruction) int64 {
319 baseWitnessSize := int64(300)
321 for _, sigInst := range signingInstructions {
322 for _, witness := range sigInst.WitnessComponents {
323 switch t := witness.(type) {
324 case *txbuilder.SignatureWitness:
325 signSize += int64(t.Quorum) * baseWitnessSize
326 case *txbuilder.RawTxSigWitness:
327 signSize += int64(t.Quorum) * baseWitnessSize
334 // POST /estimate-transaction-gas
335 func (a *API) estimateTxGas(ctx context.Context, in struct {
336 TxTemplate txbuilder.Template `json:"transaction_template"`
338 txGasResp, err := EstimateTxGas(in.TxTemplate)
340 return NewErrorResponse(err)
342 return NewSuccessResponse(txGasResp)