Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: add SimulateAndExecute #24

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions client/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
rpcclient "github.com/tendermint/tendermint/rpc/client"

clientTx "github.com/irisnet/core-sdk-go/client/tx"
"github.com/irisnet/core-sdk-go/common"
commoncache "github.com/irisnet/core-sdk-go/common/cache"
commoncodec "github.com/irisnet/core-sdk-go/common/codec"
Expand Down Expand Up @@ -170,6 +171,18 @@ func (base *baseClient) BuildAndSend(msg []sdktypes.Msg, baseTx sdktypes.BaseTx)
if e != nil {
return e
}

// TODO 下面判断和返回的逻辑应该放到 broadcastTx 中,broadcastTx 传入的 ctx 是 Factory
// 判断是模拟交易返回计算的 gas 费,不在链上执行
if ctx.SimulateAndExecute() {
res = sdktypes.ResultTx{
GasWanted: int64(ctx.Gas()),
GasUsed: 0,
Data: txByte,
}
return nil
}

if res, e = base.broadcastTx(txByte, ctx.Mode()); e != nil {
address = ctx.Address()
return e
Expand Down Expand Up @@ -338,8 +351,8 @@ func (base baseClient) QueryStore(key sdktypes.HexBytes, storeName string, heigh
return resp, nil
}

func (base *baseClient) prepare(baseTx sdktypes.BaseTx) (*sdktypes.Factory, error) {
factory := sdktypes.NewFactory().
func (base *baseClient) prepare(baseTx sdktypes.BaseTx) (*clientTx.Factory, error) {
factory := clientTx.NewFactory().
WithChainID(base.cfg.ChainID).
WithKeyManager(base.AccountQuery.Km).
WithMode(base.cfg.Mode).
Expand Down Expand Up @@ -411,8 +424,8 @@ func (base *baseClient) prepare(baseTx sdktypes.BaseTx) (*sdktypes.Factory, erro
return factory, nil
}

func (base *baseClient) prepareWithAccount(addr string, accountNumber, sequence uint64, baseTx sdktypes.BaseTx) (*sdktypes.Factory, error) {
factory := sdktypes.NewFactory().
func (base *baseClient) prepareWithAccount(addr string, accountNumber, sequence uint64, baseTx sdktypes.BaseTx) (*clientTx.Factory, error) {
factory := clientTx.NewFactory().
WithChainID(base.cfg.ChainID).
WithKeyManager(base.AccountQuery.Km).
WithMode(base.cfg.Mode).
Expand Down
5 changes: 3 additions & 2 deletions client/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"errors"
clientTx "github.com/irisnet/core-sdk-go/client/tx"
"strings"
"time"

Expand Down Expand Up @@ -100,7 +101,7 @@ func (base baseClient) EstimateTxGas(txBytes []byte) (uint64, error) {
return adjusted, nil
}

func (base *baseClient) buildTx(msgs []sdk.Msg, baseTx sdk.BaseTx) ([]byte, *sdk.Factory, sdk.Error) {
func (base *baseClient) buildTx(msgs []sdk.Msg, baseTx sdk.BaseTx) ([]byte, *clientTx.Factory, sdk.Error) {
builder, err := base.prepare(baseTx)
if err != nil {
return nil, builder, sdk.Wrap(err)
Expand All @@ -113,7 +114,7 @@ func (base *baseClient) buildTx(msgs []sdk.Msg, baseTx sdk.BaseTx) ([]byte, *sdk
return txByte, builder, nil
}

func (base *baseClient) buildTxWithAccount(addr string, accountNumber, sequence uint64, msgs []sdk.Msg, baseTx sdk.BaseTx) ([]byte, *sdk.Factory, sdk.Error) {
func (base *baseClient) buildTxWithAccount(addr string, accountNumber, sequence uint64, msgs []sdk.Msg, baseTx sdk.BaseTx) ([]byte, *clientTx.Factory, sdk.Error) {
builder, err := base.prepareWithAccount(addr, accountNumber, sequence, baseTx)
if err != nil {
return nil, builder, sdk.Wrap(err)
Expand Down
146 changes: 118 additions & 28 deletions types/factory.go → client/tx/factory.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package types
package tx

import (
"errors"
"fmt"
codecTypes "github.com/irisnet/core-sdk-go/common/codec/types"
"github.com/irisnet/core-sdk-go/common/crypto/keys/sm2"
"github.com/irisnet/core-sdk-go/types"
"github.com/irisnet/core-sdk-go/types/tx"
"github.com/irisnet/core-sdk-go/types/tx/signing"
)

Expand All @@ -21,15 +25,15 @@ type (
gas uint64
gasAdjustment float64
simulateAndExecute bool
fees Coins
feeGranter AccAddress
feePayer AccAddress
gasPrices DecCoins
mode BroadcastMode
fees types.Coins
feeGranter types.AccAddress
feePayer types.AccAddress
gasPrices types.DecCoins
mode types.BroadcastMode
signMode signing.SignMode
signModeHandler SignModeHandler
keyManager KeyManager
txConfig TxConfig
signModeHandler types.SignModeHandler
keyManager types.KeyManager
txConfig types.TxConfig
queryFunc QueryWithData
}

Expand All @@ -52,7 +56,7 @@ func (f *Factory) Gas() uint64 { return f.gas }
func (f Factory) GasAdjustment() float64 { return f.gasAdjustment }

// Fees returns the fee of the transaction.
func (f *Factory) Fees() Coins { return f.fees }
func (f *Factory) Fees() types.Coins { return f.fees }

// Sequence returns the sequence of the account.
func (f *Factory) Sequence() uint64 { return f.sequence }
Expand All @@ -64,10 +68,10 @@ func (f *Factory) Memo() string { return f.memo }
func (f *Factory) AccountNumber() uint64 { return f.accountNumber }

// KeyManager returns keyManager.
func (f *Factory) KeyManager() KeyManager { return f.keyManager }
func (f *Factory) KeyManager() types.KeyManager { return f.keyManager }

// Mode returns mode.
func (f *Factory) Mode() BroadcastMode { return f.mode }
func (f *Factory) Mode() types.BroadcastMode { return f.mode }

// SimulateAndExecute returns the option to simulateAndExecute and then execute the transaction
// using the gas from the simulation results
Expand Down Expand Up @@ -98,19 +102,19 @@ func (f *Factory) WithGasAdjustment(gasAdjustment float64) *Factory {
}

// WithFee returns a pointer of the context with an updated Fee.
func (f *Factory) WithFee(fee Coins) *Factory {
func (f *Factory) WithFee(fee types.Coins) *Factory {
f.fees = fee
return f
}

// WithFeeGranter returns a pointer of the context with an updated FeeGranter.
func (f *Factory) WithFeeGranter(feeGranter AccAddress) *Factory {
func (f *Factory) WithFeeGranter(feeGranter types.AccAddress) *Factory {
f.feeGranter = feeGranter
return f
}

// WithFeePayer returns a pointer of the context with an updated FeePayer.
func (f *Factory) WithFeePayer(feePayer AccAddress) *Factory {
func (f *Factory) WithFeePayer(feePayer types.AccAddress) *Factory {
f.feePayer = feePayer
return f
}
Expand All @@ -133,14 +137,14 @@ func (f *Factory) WithAccountNumber(accnum uint64) *Factory {
return f
}

// WithKeyManager returns a pointer of the context with a KeyManager.
func (f *Factory) WithKeyManager(keyManager KeyManager) *Factory {
// WithKeyManager returns a pointer of the context with a types.KeyManager.
func (f *Factory) WithKeyManager(keyManager types.KeyManager) *Factory {
f.keyManager = keyManager
return f
}

// WithMode returns a pointer of the context with a Mode.
func (f *Factory) WithMode(mode BroadcastMode) *Factory {
func (f *Factory) WithMode(mode types.BroadcastMode) *Factory {
f.mode = mode
return f
}
Expand All @@ -163,14 +167,14 @@ func (f *Factory) WithAddress(address string) *Factory {
return f
}

// WithTxConfig returns a pointer of the context with an TxConfig
func (f *Factory) WithTxConfig(txConfig TxConfig) *Factory {
// WithTxConfig returns a pointer of the context with an types.TxConfig
func (f *Factory) WithTxConfig(txConfig types.TxConfig) *Factory {
f.txConfig = txConfig
return f
}

// WithSignModeHandler returns a pointer of the context with an signModeHandler.
func (f *Factory) WithSignModeHandler(signModeHandler SignModeHandler) *Factory {
func (f *Factory) WithSignModeHandler(signModeHandler types.SignModeHandler) *Factory {
f.signModeHandler = signModeHandler
return f
}
Expand All @@ -187,7 +191,22 @@ func (f *Factory) WithTimeout(height uint64) *Factory {
return f
}

func (f *Factory) BuildAndSign(name string, msgs []Msg, json bool) ([]byte, error) {
func (f *Factory) BuildAndSign(name string, msgs []Msg, json bool) ([]byte, error) { if f.SimulateAndExecute() {
_, adjusted, err := f.CalculateGas(msgs...)
if err != nil {
return nil, err
}
f.WithGas(adjusted)

// TODO 设置计算出的费用。下面这个就不用了,因为在 broadcastTx 直接就返回了,不会执行和扣费
//fee, _ := types.ParseDecCoins(fmt.Sprintf("%dugas", adjusted))
//fees, err := toMinCoin(fee...)
//if err != nil {
// return nil, err
//}
//f.WithFee(fees)
}

tx, err := f.BuildUnsignedTx(msgs)
if err != nil {
return nil, err
Expand All @@ -213,7 +232,7 @@ func (f *Factory) BuildAndSign(name string, msgs []Msg, json bool) ([]byte, erro
return txBytes, nil
}

func (f *Factory) BuildUnsignedTx(msgs []Msg) (TxBuilder, error) {
func (f *Factory) BuildUnsignedTx(msgs []types.Msg) (types.TxBuilder, error) {
if f.chainID == "" {
return nil, fmt.Errorf("chain ID required but not specified")
}
Expand All @@ -225,15 +244,15 @@ func (f *Factory) BuildUnsignedTx(msgs []Msg) (TxBuilder, error) {
return nil, errors.New("cannot provide both fees and gas prices")
}

glDec := NewDec(int64(f.gas))
glDec := types.NewDec(int64(f.gas))

// Derive the fees based on the provided gas prices, where
// fee = ceil(gasPrice * gasLimit).
fees = make(Coins, len(f.gasPrices))
fees = make(types.Coins, len(f.gasPrices))

for i, gp := range f.gasPrices {
fee := gp.Amount.Mul(glDec)
fees[i] = NewCoin(gp.Denom, fee.Ceil().RoundInt())
fees[i] = types.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}
}

Expand All @@ -254,15 +273,75 @@ func (f *Factory) BuildUnsignedTx(msgs []Msg) (TxBuilder, error) {
return tx, nil
}

// BuildSimTx creates an unsigned tx with an empty single signature and returns
// the encoded transaction or an error if the unsigned transaction cannot be
// built.
func (f *Factory) BuildSimTx(msgs ...types.Msg) ([]byte, error) {
txb, err := f.BuildUnsignedTx(msgs)
if err != nil {
return nil, err
}

// Create an empty signature literal as the ante handler will populate with a
// sentinel pubkey.
sig := signing.SignatureV2{
PubKey: &sm2.PubKey{},
Data: &signing.SingleSignatureData{
SignMode: f.signMode,
},
Sequence: f.Sequence(),
}

if err := txb.SetSignatures(sig); err != nil {
return nil, err
}

any, ok := txb.(codecTypes.IntoAny)
if !ok {
return nil, fmt.Errorf("cannot simulateAndExecute tx that cannot be wrapped into any")
}
cached := any.AsAny().GetCachedValue()
protoTx, ok := cached.(*tx.Tx)
if !ok {
return nil, fmt.Errorf("cannot simulateAndExecute amino tx")
}

simReq := tx.SimulateRequest{Tx: protoTx}

return simReq.Marshal()
}

// CalculateGas simulates the execution of a transaction and returns the
// simulation response obtained by the query and the adjusted gas amount.
func (f *Factory) CalculateGas(msgs ...types.Msg) (tx.SimulateResponse, uint64, error) {
txBytes, err := f.BuildSimTx(msgs...)
if err != nil {
return tx.SimulateResponse{}, 0, err
}

bz, _, err := f.queryFunc("/cosmos.tx.v1beta1.Service/Simulate", txBytes)
if err != nil {
return tx.SimulateResponse{}, 0, err
}

var simRes tx.SimulateResponse

if err := simRes.Unmarshal(bz); err != nil {
return tx.SimulateResponse{}, 0, err
}

return simRes, uint64(f.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil
}

// Sign signs a transaction given a name, passphrase, and a single message to
// signed. An error is returned if signing fails.
func (f *Factory) Sign(name string, txBuilder TxBuilder) error {
func (f *Factory) Sign(name string, txBuilder types.TxBuilder) error {
signMode := f.signMode
if signMode == signing.SignMode_SIGN_MODE_UNSPECIFIED {
// use the SignModeHandler's default mode if unspecified
signMode = f.txConfig.SignModeHandler().DefaultMode()
}
signerData := SignerData{
signerData := types.SignerData{
ChainID: f.chainID,
AccountNumber: f.accountNumber,
Sequence: f.sequence,
Expand Down Expand Up @@ -320,3 +399,14 @@ func (f *Factory) Sign(name string, txBuilder TxBuilder) error {
// And here the tx is populated with the signature
return txBuilder.SetSignatures(sig)
}

func toMinCoin(coins ...types.DecCoin) (types.Coins, types.Error) {
for i := range coins {
if coins[i].Denom == "iris" {
coins[i].Denom = "uiris"
coins[i].Amount = coins[i].Amount.MulInt(types.NewIntWithDecimal(1, 6))
}
}
ucoins, _ := types.DecCoins(coins).TruncateDecimal()
return ucoins, nil
}
33 changes: 33 additions & 0 deletions proto/cosmos/base/simulate/v1beta1/simlate.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
syntax = "proto3";
package cosmos.base.simulate.v1beta1;

import "google/api/annotations.proto";
import "cosmos/base/abci/v1beta1/abci.proto";
import "cosmos/tx/v1beta1/tx.proto";

option go_package = "github.com/bianjieai/core-sdk-go/types";

// SimulateService defines a gRPC service for simulating transactions.
// It may also support querying and broadcasting in the future.
service SimulateService {
// Simulate simulates executing a transaction for estimating gas usage.
rpc Simulate(SimulateRequest) returns (SimulateResponse) {
option (google.api.http).post = "/cosmos/base/simulate/v1beta1/simulate";
}
}

// SimulateRequest is the request type for the SimulateServiceService.Simulate
// RPC method.
message SimulateRequest {
// tx is the transaction to simulate.
cosmos.tx.v1beta1.Tx tx = 1;
}

// SimulateResponse is the response type for the
// SimulateServiceService.SimulateRPC method.
message SimulateResponse {
// gas_info is the information about gas used in the simulation.
cosmos.base.abci.v1beta1.GasInfo gas_info = 1;
// result is the result of the simulation.
cosmos.base.abci.v1beta1.Result result = 2;
}
Loading