Skip to content

Commit

Permalink
feat: simulate nested messages (#20291)
Browse files Browse the repository at this point in the history
  • Loading branch information
JulianToledano authored Jul 26, 2024
1 parent cc5c4d0 commit fe8474e
Show file tree
Hide file tree
Showing 11 changed files with 1,416 additions and 55 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i

### Features

* (baseapp) [#20291](https://github.com/cosmos/cosmos-sdk/pull/20291) Simulate nested messages.
* (tests) [#20013](https://github.com/cosmos/cosmos-sdk/pull/20013) Introduce system tests to run multi node local testnet in CI
* (runtime) [#19953](https://github.com/cosmos/cosmos-sdk/pull/19953) Implement `core/transaction.Service` in runtime.
* (client) [#19905](https://github.com/cosmos/cosmos-sdk/pull/19905) Add grpc client config to `client.toml`.
Expand Down
33 changes: 33 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,39 @@ Note, always read the **SimApp** section for more information on application wir

## [Unreleased]

### BaseApp

#### Nested Messages Simulation

Now it is possible to simulate the nested messages of a message, providing developers with a powerful tool for
testing and predicting the behavior of complex transactions. This feature allows for a more comprehensive
evaluation of gas consumption, state changes, and potential errors that may occur when executing nested
messages. However, it's important to note that while the simulation can provide valuable insights, it does not
guarantee the correct execution of the nested messages in the future. Factors such as changes in the
blockchain state or updates to the protocol could potentially affect the actual execution of these nested
messages when the transaction is finally processed on the network.

For example, consider a governance proposal that includes nested messages to update multiple protocol
parameters. At the time of simulation, the blockchain state may be suitable for executing all these nested
messages successfully. However, by the time the actual governance proposal is executed (which could be days or
weeks later), the blockchain state might have changed significantly. As a result, while the simulation showed
a successful execution, the actual governance proposal might fail when it's finally processed.

By default, when simulating transactions, the gas cost of nested messages is not calculated. This means that
only the gas cost of the top-level message is considered. However, this behavior can be customized using the
`SetIncludeNestedMsgsGas` option when building the BaseApp. By providing a list of message types to this option,
you can specify which messages should have their nested message gas costs included in the simulation. This
allows for more accurate gas estimation for transactions involving specific message types that contain nested
messages, while maintaining the default behavior for other message types.

Here is an example on how `SetIncludeNestedMsgsGas` option could be set to calculate the gas of a gov proposal
nested messages:
```go
baseAppOptions = append(baseAppOptions, baseapp.SetIncludeNestedMsgsGas([]sdk.Message{&gov.MsgSubmitProposal{}}))
// ...
app.App = appBuilder.Build(db, traceStore, baseAppOptions...)
```

### SimApp

In this section we describe the changes made in Cosmos SDK' SimApp.
Expand Down
236 changes: 236 additions & 0 deletions baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/cosmos/gogoproto/jsonpb"
"github.com/cosmos/gogoproto/proto"
gogotypes "github.com/cosmos/gogoproto/types"
any "github.com/cosmos/gogoproto/types/any"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

Expand All @@ -38,6 +39,7 @@ import (
"github.com/cosmos/cosmos-sdk/baseapp"
baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil"
"github.com/cosmos/cosmos-sdk/baseapp/testutil/mock"
"github.com/cosmos/cosmos-sdk/codec"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
Expand Down Expand Up @@ -760,6 +762,240 @@ func TestABCI_FinalizeBlock_MultiMsg(t *testing.T) {
require.Equal(t, int64(2), msgCounter2)
}

func anyMessage(t *testing.T, cdc codec.Codec, msg *baseapptestutil.MsgSend) *any.Any {
t.Helper()
b, err := cdc.Marshal(msg)
require.NoError(t, err)
return &any.Any{
TypeUrl: sdk.MsgTypeURL(msg),
Value: b,
}
}

func TestABCI_Query_SimulateNestedMessagesTx(t *testing.T) {
anteOpt := func(bapp *baseapp.BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(uint64(15)))
return
})
}
suite := NewBaseAppSuite(t, anteOpt)

_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
ConsensusParams: &cmtproto.ConsensusParams{},
})
require.NoError(t, err)

baseapptestutil.RegisterNestedMessagesServer(suite.baseApp.MsgServiceRouter(), NestedMessgesServerImpl{})
baseapptestutil.RegisterSendServer(suite.baseApp.MsgServiceRouter(), SendServerImpl{})

_, _, addr := testdata.KeyTestPubAddr()
_, _, toAddr := testdata.KeyTestPubAddr()
tests := []struct {
name string
message sdk.Msg
wantErr bool
}{
{
name: "ok nested message",
message: &baseapptestutil.MsgSend{
From: addr.String(),
To: toAddr.String(),
Amount: "10000stake",
},
},
{
name: "different signers",
message: &baseapptestutil.MsgSend{
From: toAddr.String(),
To: addr.String(),
Amount: "10000stake",
},
wantErr: true,
},
{
name: "empty from",
message: &baseapptestutil.MsgSend{
From: "",
To: toAddr.String(),
Amount: "10000stake",
},
wantErr: true,
},
{
name: "empty to",
message: &baseapptestutil.MsgSend{
From: addr.String(),
To: "",
Amount: "10000stake",
},
wantErr: true,
},
{
name: "negative amount",
message: &baseapptestutil.MsgSend{
From: addr.String(),
To: toAddr.String(),
Amount: "-10000stake",
},
wantErr: true,
},
{
name: "with nested messages",
message: &baseapptestutil.MsgNestedMessages{
Signer: addr.String(),
Messages: []*any.Any{
anyMessage(t, suite.cdc, &baseapptestutil.MsgSend{
From: addr.String(),
To: toAddr.String(),
Amount: "10000stake",
}),
},
},
},
{
name: "with invalid nested messages",
message: &baseapptestutil.MsgNestedMessages{
Signer: addr.String(),
Messages: []*any.Any{
anyMessage(t, suite.cdc, &baseapptestutil.MsgSend{
From: "",
To: toAddr.String(),
Amount: "10000stake",
}),
},
},
wantErr: true,
},
{
name: "with different signer ",
message: &baseapptestutil.MsgNestedMessages{
Signer: addr.String(),
Messages: []*any.Any{
anyMessage(t, suite.cdc, &baseapptestutil.MsgSend{
From: toAddr.String(),
To: addr.String(),
Amount: "10000stake",
}),
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
nestedMessages := make([]*any.Any, 1)
b, err := suite.cdc.Marshal(tt.message)
require.NoError(t, err)
nestedMessages[0] = &any.Any{
TypeUrl: sdk.MsgTypeURL(tt.message),
Value: b,
}

msg := &baseapptestutil.MsgNestedMessages{
Messages: nestedMessages,
Signer: addr.String(),
}

builder := suite.txConfig.NewTxBuilder()
err = builder.SetMsgs(msg)
require.NoError(t, err)
setTxSignature(t, builder, 0)
tx := builder.GetTx()

txBytes, err := suite.txConfig.TxEncoder()(tx)
require.Nil(t, err)

_, result, err := suite.baseApp.Simulate(txBytes)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, result)
} else {
require.NoError(t, err)
require.NotNil(t, result)
}
})
}
}

func TestABCI_Query_SimulateNestedMessagesGas(t *testing.T) {
anteOpt := func(bapp *baseapp.BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(uint64(10)))
return
})
}

_, _, addr := testdata.KeyTestPubAddr()
_, _, toAddr := testdata.KeyTestPubAddr()

tests := []struct {
name string
suite *BaseAppSuite
message sdk.Msg
consumedGas uint64
}{
{
name: "don't add gas",
suite: NewBaseAppSuite(t, anteOpt),
message: &baseapptestutil.MsgSend{
From: addr.String(),
To: toAddr.String(),
Amount: "10000stake",
},
consumedGas: 5,
},
{
name: "add gas",
suite: NewBaseAppSuite(t, anteOpt, baseapp.SetIncludeNestedMsgsGas([]sdk.Msg{&baseapptestutil.MsgNestedMessages{}})),
message: &baseapptestutil.MsgSend{
From: addr.String(),
To: toAddr.String(),
Amount: "10000stake",
},
consumedGas: 10,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.suite.baseApp.InitChain(&abci.InitChainRequest{
ConsensusParams: &cmtproto.ConsensusParams{},
})
require.NoError(t, err)

baseapptestutil.RegisterNestedMessagesServer(tt.suite.baseApp.MsgServiceRouter(), NestedMessgesServerImpl{})
baseapptestutil.RegisterSendServer(tt.suite.baseApp.MsgServiceRouter(), SendServerImpl{})

nestedMessages := make([]*any.Any, 1)
b, err := tt.suite.cdc.Marshal(tt.message)
require.NoError(t, err)
nestedMessages[0] = &any.Any{
TypeUrl: sdk.MsgTypeURL(tt.message),
Value: b,
}

msg := &baseapptestutil.MsgNestedMessages{
Messages: nestedMessages,
Signer: addr.String(),
}

builder := tt.suite.txConfig.NewTxBuilder()
err = builder.SetMsgs(msg)
require.NoError(t, err)
setTxSignature(t, builder, 0)
tx := builder.GetTx()

txBytes, err := tt.suite.txConfig.TxEncoder()(tx)
require.Nil(t, err)

gas, result, err := tt.suite.baseApp.Simulate(txBytes)
require.NoError(t, err)
require.NotNil(t, result)
require.True(t, gas.GasUsed == tt.consumedGas)
})
}
}

func TestABCI_Query_SimulateTx(t *testing.T) {
gasConsumed := uint64(5)
anteOpt := func(bapp *baseapp.BaseApp) {
Expand Down
Loading

0 comments on commit fe8474e

Please sign in to comment.