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 #351

Merged
merged 15 commits into from
Oct 21, 2024
22 changes: 19 additions & 3 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"os"
"path/filepath"

"github.com/dymensionxyz/rollapp-evm/app/ante"

"github.com/cosmos/cosmos-sdk/x/authz"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
"github.com/dymensionxyz/dymension-rdk/server/consensus"
"github.com/dymensionxyz/rollapp-evm/app/ante"
"github.com/gorilla/mux"
"github.com/rakyll/statik/fs"
"github.com/spf13/cast"
Expand Down Expand Up @@ -341,6 +341,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 @@ -838,6 +840,11 @@ func NewRollapp(
app.SetAnteHandler(h)
app.setPostHandler()

// Admission handler for consensus messages
app.setAdmissionHandler(consensus.AllowedMessagesHandler([]string{
// proto.MessageName(&banktypes.MsgSend{}), // Example of message allowed as consensus message
}))

if loadLatest {
if err := app.LoadLatestVersion(); err != nil {
tmos.Exit(err.Error())
Expand All @@ -861,12 +868,21 @@ 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
Contributor

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 did not like to check the strings of errors

}
} 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")
}
}
})
}
}
208 changes: 208 additions & 0 deletions app/test_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
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(),
simapp.EmptyAppOptions{},
)

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