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

feat: consensus messages #141

Merged
merged 9 commits into from
Oct 21, 2024
Merged
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
27 changes: 26 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
evmosante "github.com/evmos/evmos/v12/app/ante"
"github.com/gogo/protobuf/proto"
"github.com/gorilla/mux"
"github.com/rakyll/statik/fs"
abci "github.com/tendermint/tendermint/abci/types"
Expand Down Expand Up @@ -146,6 +147,8 @@ import (
cwerrorsKeeper "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/keeper"
cwerrorsTypes "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types"

"github.com/dymensionxyz/dymension-rdk/server/consensus"

"github.com/dymensionxyz/dymension-rdk/x/rollappparams"
rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper"
rollappparamstypes "github.com/dymensionxyz/dymension-rdk/x/rollappparams/types"
Expand Down Expand Up @@ -319,6 +322,8 @@ type App struct {

// module configurator
configurator module.Configurator

consensusMessageAdmissionHandler consensus.AdmissionHandler
}

// NewRollapp returns a reference to an initialized blockchain app
Expand Down Expand Up @@ -834,6 +839,11 @@ func NewRollapp(
// upgrade.
app.setPostHandler()

// Admission handler for consensus messages
app.setAdmissionHandler(consensus.AllowedMessagesHandler([]string{
proto.MessageName(&banktypes.MsgSend{}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this message allowed?

}))

if loadLatest {
if err := app.LoadLatestVersion(); err != nil {
tmos.Exit(err.Error())
Expand Down Expand Up @@ -887,12 +897,27 @@ func (app *App) setPostHandler() {
app.SetPostHandler(postHandler)
}

func (app *App) setAdmissionHandler(handler consensus.AdmissionHandler) {
app.consensusMessageAdmissionHandler = handler
}

// Name returns the name of the App
func (app *App) Name() string { return app.BaseApp.Name() }

// BeginBlocker application updates every begin block
func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
return app.mm.BeginBlock(ctx, req)
consensusResponses := consensus.ProcessConsensusMessages(
ctx,
app.appCodec,
app.consensusMessageAdmissionHandler,
app.MsgServiceRouter(),
req.ConsensusMessages,
)

resp := app.mm.BeginBlock(ctx, req)
resp.ConsensusMessagesResponses = consensusResponses

return resp
}

// EndBlocker application updates every end block
Expand Down
100 changes: 100 additions & 0 deletions app/app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package app

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/gogo/protobuf/proto"
prototypes "github.com/gogo/protobuf/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/proto/tendermint/types"
)

func TestBeginBlocker(t *testing.T) {
app, valAccount := SetupWithOneValidator(t)
ctx := app.NewUncachedContext(true, types.Header{
Height: 1,
ChainID: "testchain_9000-1",
})

bankSend := &banktypes.MsgSend{
FromAddress: valAccount.GetAddress().String(),
ToAddress: valAccount.GetAddress().String(),
Amount: sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))),
}
msgBz, err := proto.Marshal(bankSend)
require.NoError(t, err)

goodMessage := &prototypes.Any{
TypeUrl: proto.MessageName(&banktypes.MsgSend{}),
Value: msgBz,
}

testCases := []struct {
name string
consensusMsgs []*prototypes.Any
expectError bool
}{
{
name: "ValidConsensusMessage",
consensusMsgs: []*prototypes.Any{
goodMessage,
},
expectError: false,
},
{
name: "InvalidUnpackMessage",
consensusMsgs: []*prototypes.Any{
{
TypeUrl: "/path.to.InvalidMsg",
Value: []byte("invalid unpack data"),
},
},
expectError: true,
},
{
name: "InvalidExecutionMessage",
consensusMsgs: []*prototypes.Any{
{
TypeUrl: "/path.to.ExecErrorMsg",
Value: []byte("execution error data"),
},
},
expectError: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := abci.RequestBeginBlock{
Header: types.Header{
Height: 1,
Time: ctx.BlockTime(),
ChainID: "testchain_9000-1",
},
LastCommitInfo: abci.LastCommitInfo{},
ByzantineValidators: []abci.Evidence{},
ConsensusMessages: tc.consensusMsgs,
}

res := app.BeginBlocker(ctx, req)
require.NotNil(t, res)

if tc.expectError {
require.NotEmpty(t, res.ConsensusMessagesResponses)
for _, response := range res.ConsensusMessagesResponses {
_, isError := response.Response.(*abci.ConsensusMessageResponse_Error)
require.True(t, isError, "Expected an error response but got a success")
Comment on lines +88 to +89
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd also suggest verifying the returned error string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danwt does not like to check error strings

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea but it's still better than nothing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think in theory you can check grpc status code
i haven't looked at it tho

}
} else {
require.NotEmpty(t, res.ConsensusMessagesResponses)
for _, response := range res.ConsensusMessagesResponses {
_, isOk := response.Response.(*abci.ConsensusMessageResponse_Ok)
require.True(t, isOk, "Expected a success response but got an error")
}
}
})
}
}
210 changes: 210 additions & 0 deletions app/test_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package app

import (
"encoding/json"
"fmt"
"testing"
"time"

appcodec "github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/testutil/mock"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/dymensionxyz/dymension-rdk/testutil/utils"
rollappparamstypes "github.com/dymensionxyz/dymension-rdk/x/rollappparams/types"
"github.com/tendermint/tendermint/crypto/encoding"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
types2 "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-db"

"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
)

const TestChainID = "testchain_9000-1"

func setup(withGenesis bool, invCheckPeriod uint) (*App, GenesisState) {
db := dbm.NewMemDB()

app := NewRollapp(
log.NewNopLogger(),
db,
nil,
true,
map[int64]bool{},
DefaultNodeHome,
invCheckPeriod,
MakeEncodingConfig(),
nil,
simapp.EmptyAppOptions{},
nil,
)

if withGenesis {
return app, NewDefaultGenesisState(app.appCodec)
}

return app, GenesisState{}
}

// SetupWithOneValidator initializes a new App. A Nop logger is set in App.
func SetupWithOneValidator(t *testing.T) (*App, authtypes.AccountI) {
t.Helper()

privVal := mock.NewPV()
pubKey, err := privVal.GetPubKey()
require.NoError(t, err)

// create validator set with single validator
validator := types2.NewValidator(pubKey, 1)
valSet := types2.NewValidatorSet([]*types2.Validator{validator})

// generate genesis account
senderPrivKey := secp256k1.GenPrivKey()
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
balance := banktypes.Balance{
Address: acc.GetAddress().String(),
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))),
}
fmt.Printf("address: %s\n", acc.GetAddress().String())

app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance)

return app, acc
}

// SetupWithGenesisValSet initializes a new SimApp with a validator set and genesis accounts
// that also act as delegators. For simplicity, each validator is bonded with a delegation
// of one consensus engine unit in the default token of the simapp from first genesis
// account. A Nop logger is set in SimApp.
func SetupWithGenesisValSet(t *testing.T, valSet *types2.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *App {
t.Helper()

app, genesisState := setup(true, 5)
genesisState = genesisStateWithValSet(t, app, genesisState, valSet, genAccs, balances...)

genesisState = setRollappVersion(app.appCodec, genesisState, "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0")

stateBytes, err := json.MarshalIndent(genesisState, "", " ")
require.NoError(t, err)

proto, err := encoding.PubKeyToProto(valSet.Validators[0].PubKey)
require.NoError(t, err)

// init chain will set the validator set and initialize the genesis accounts
app.InitChain(
abci.RequestInitChain{
Validators: []abci.ValidatorUpdate{
{
PubKey: proto,
Power: valSet.Validators[0].VotingPower,
},
},
ConsensusParams: utils.DefaultConsensusParams,
AppStateBytes: stateBytes,
ChainId: TestChainID,
},
)

// commit genesis changes
app.Commit()
app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{
ChainID: TestChainID,
Height: app.LastBlockHeight() + 1,
AppHash: app.LastCommitID().Hash,
ValidatorsHash: valSet.Hash(),
NextValidatorsHash: valSet.Hash(),
}})

return app
}

func genesisStateWithValSet(t *testing.T,
app *App, genesisState GenesisState,
valSet *types2.ValidatorSet, genAccs []authtypes.GenesisAccount,
balances ...banktypes.Balance,
) GenesisState {
// set genesis accounts
authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs)
genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis)

validators := make([]stakingtypes.Validator, 0, len(valSet.Validators))
delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators))

bondAmt := sdk.DefaultPowerReduction

for _, val := range valSet.Validators {
pk, err := codec.FromTmPubKeyInterface(val.PubKey)
require.NoError(t, err)
pkAny, err := codectypes.NewAnyWithValue(pk)
require.NoError(t, err)
validator := stakingtypes.Validator{
OperatorAddress: sdk.ValAddress(val.Address).String(),
ConsensusPubkey: pkAny,
Jailed: false,
Status: stakingtypes.Bonded,
Tokens: bondAmt,
DelegatorShares: sdk.OneDec(),
Description: stakingtypes.Description{},
UnbondingHeight: int64(0),
UnbondingTime: time.Unix(0, 0).UTC(),
Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
MinSelfDelegation: sdk.ZeroInt(),
}
validators = append(validators, validator)
delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec()))

}
// set validators and delegations
stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations)
genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis)

totalSupply := sdk.NewCoins()
for _, b := range balances {
// add genesis acc tokens to total supply
totalSupply = totalSupply.Add(b.Coins...)
}

for range delegations {
// add delegated tokens to total supply
totalSupply = totalSupply.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))
}

// add bonded amount to bonded pool module account
balances = append(balances, banktypes.Balance{
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)},
})

// update total supply
bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{})
genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis)

return genesisState
}

func setRollappVersion(appCodec appcodec.Codec, genesisState GenesisState, version string) GenesisState {
var rollappParamsGenesis rollappparamstypes.GenesisState
if genesisState["rollappparams"] != nil {
appCodec.MustUnmarshalJSON(genesisState["rollappparams"], &rollappParamsGenesis)
} else {
rollappParamsGenesis = rollappparamstypes.GenesisState{
Params: rollappparamstypes.Params{
Version: version,
},
}
}

rollappParamsGenesis.Params.Version = version

genesisState["rollappparams"] = appCodec.MustMarshalJSON(&rollappParamsGenesis)

return genesisState
}
Loading
Loading