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": a.wallet.AccountMgr.DecodeDposAction,
38 "ipfs_data": txbuilder.DecodeIpfsDataAction,
40 decoder, ok := decoders[action]
44 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
47 for i, act := range req.Actions {
48 actionType, ok := act["type"].(string)
50 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
52 if strings.HasPrefix(actionType, "dpos") {
55 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
60 if dpos == true && count == 0 {
64 return count == len(req.Actions), nil
67 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
68 if err := a.checkRequestValidity(ctx, req); err != nil {
71 actions, err := a.mergeSpendActions(req)
76 maxTime := time.Now().Add(req.TTL.Duration)
77 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
78 if errors.Root(err) == txbuilder.ErrAction {
79 // append each of the inner errors contained in the data.
82 for i, innerErr := range errors.Data(err)["actions"].([]error) {
84 rootErr = errors.Root(innerErr)
86 Errs = Errs + innerErr.Error()
88 err = errors.WithDetail(rootErr, Errs)
94 // ensure null is never returned for signing instructions
95 if tpl.SigningInstructions == nil {
96 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
101 // POST /build-transaction
102 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
103 subctx := reqid.NewSubContext(ctx, reqid.New())
104 tmpl, err := a.buildSingle(subctx, buildReqs)
106 return NewErrorResponse(err)
109 return NewSuccessResponse(tmpl)
111 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
112 if err := a.completeMissingIDs(ctx, req); err != nil {
116 if req.TTL.Duration == 0 {
117 req.TTL.Duration = defaultTxTTL
120 if ok, err := onlyHaveInputActions(req); err != nil {
123 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
128 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
129 actions := make([]txbuilder.Action, 0, len(req.Actions))
130 for i, act := range req.Actions {
131 typ, ok := act["type"].(string)
133 return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
135 decoder, ok := a.actionDecoder(typ)
137 return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
140 // Remarshal to JSON, the action may have been modified when we
142 b, err := json.Marshal(act)
146 action, err := decoder(b)
148 return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
150 actions = append(actions, action)
152 actions = account.MergeSpendAction(actions)
156 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
157 if err := a.checkRequestValidity(ctx, req); err != nil {
160 actions, err := a.mergeSpendActions(req)
165 builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
166 tpls := []*txbuilder.Template{}
167 for _, action := range actions {
168 if action.ActionType() == "spend_account" {
169 tpls, err = account.SpendAccountChain(ctx, builder, action)
171 err = action.Build(ctx, builder)
180 tpl, _, err := builder.Build()
186 tpls = append(tpls, tpl)
190 // POST /build-chain-transactions
191 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
192 subctx := reqid.NewSubContext(ctx, reqid.New())
193 tmpls, err := a.buildTxs(subctx, buildReqs)
195 return NewErrorResponse(err)
197 return NewSuccessResponse(tmpls)
200 type submitTxResp struct {
201 TxID *bc.Hash `json:"tx_id"`
204 // POST /submit-transaction
205 func (a *API) submit(ctx context.Context, ins struct {
206 Tx types.Tx `json:"raw_transaction"`
208 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
209 return NewErrorResponse(err)
212 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
213 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
216 type submitTxsResp struct {
217 TxID []*bc.Hash `json:"tx_id"`
220 // POST /submit-transactions
221 func (a *API) submitTxs(ctx context.Context, ins struct {
222 Tx []types.Tx `json:"raw_transactions"`
224 txHashs := []*bc.Hash{}
225 for i := range ins.Tx {
226 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
227 return NewErrorResponse(err)
229 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
230 txHashs = append(txHashs, &ins.Tx[i].ID)
232 return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
235 // EstimateTxGasResp estimate transaction consumed gas
236 type EstimateTxGasResp struct {
237 TotalNeu int64 `json:"total_neu"`
238 StorageNeu int64 `json:"storage_neu"`
239 VMNeu int64 `json:"vm_neu"`
242 // EstimateTxGas estimate consumed neu for transaction
243 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
244 // base tx size and not include sign
245 data, err := template.Transaction.TxData.MarshalText()
249 baseTxSize := int64(len(data))
251 // extra tx size for sign witness parts
252 signSize := estimateSignSize(template.SigningInstructions)
254 // total gas for tx storage
255 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
257 return nil, errors.New("calculate txsize gas got a math error")
260 // consume gas for run VM
261 totalP2WPKHGas := int64(0)
262 totalP2WSHGas := int64(0)
263 baseP2WPKHGas := int64(1419)
264 // flexible Gas is used for handle need extra utxo situation
266 for pos, inpID := range template.Transaction.Tx.InputIDs {
267 sp, err := template.Transaction.Spend(inpID)
272 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
277 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
278 totalP2WPKHGas += baseP2WPKHGas
279 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
280 sigInst := template.SigningInstructions[pos]
281 totalP2WSHGas += estimateP2WSHGas(sigInst)
285 // total estimate gas
286 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + flexibleGas
288 // rounding totalNeu with base rate 100000
289 totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
290 roundingNeu := math.Ceil(totalNeu)
291 estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
295 return &EstimateTxGasResp{
296 TotalNeu: estimateNeu,
297 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
298 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
302 // estimate p2wsh gas.
303 // OP_CHECKMULTISIG consume (984 * a - 72 * b - 63) gas,
304 // where a represent the num of public keys, and b represent the num of quorum.
305 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) int64 {
307 baseP2WSHGas := int64(738)
309 for _, witness := range sigInst.WitnessComponents {
310 switch t := witness.(type) {
311 case *txbuilder.SignatureWitness:
312 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
313 case *txbuilder.RawTxSigWitness:
314 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
320 // estimate signature part size.
321 // if need multi-sign, calculate the size according to the length of keys.
322 func estimateSignSize(signingInstructions []*txbuilder.SigningInstruction) int64 {
324 baseWitnessSize := int64(300)
326 for _, sigInst := range signingInstructions {
327 for _, witness := range sigInst.WitnessComponents {
328 switch t := witness.(type) {
329 case *txbuilder.SignatureWitness:
330 signSize += int64(t.Quorum) * baseWitnessSize
331 case *txbuilder.RawTxSigWitness:
332 signSize += int64(t.Quorum) * baseWitnessSize
339 // POST /estimate-transaction-gas
340 func (a *API) estimateTxGas(ctx context.Context, in struct {
341 TxTemplate txbuilder.Template `json:"transaction_template"`
343 txGasResp, err := EstimateTxGas(in.TxTemplate)
345 return NewErrorResponse(err)
347 return NewSuccessResponse(txGasResp)