Skip to content

Commit

Permalink
feat(rollapp): rollapp state finalization (#74)
Browse files Browse the repository at this point in the history
* ignite scaffold map block_height_to_finalization_queue finalization_queue:StateInfoIndex --index finalization_height:uint --no-message --module rollapp

* fix ignite command

* update finalizationQueue on UpdateState

* finalize states on EndBlocker

* add tests

* fix lint

* fix lint 2
  • Loading branch information
liorzilp committed Sep 4, 2022
1 parent 7350925 commit 8824ff7
Show file tree
Hide file tree
Showing 24 changed files with 2,769 additions and 277 deletions.
427 changes: 427 additions & 0 deletions docs/static/openapi.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions proto/rollapp/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ message GenesisState {
repeated Rollapp rollappList = 2 [(gogoproto.nullable) = false];
repeated StateInfo stateInfoList = 3 [(gogoproto.nullable) = false];
repeated StateInfoIndex latestStateInfoIndexList = 4 [(gogoproto.nullable) = false];
repeated BlockHeightToFinalizationQueue blockHeightToFinalizationQueueList = 5 [(gogoproto.nullable) = false];
// this line is used by starport scaffolding # genesis/proto/state
}
28 changes: 28 additions & 0 deletions proto/rollapp/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ service Query {
option (google.api.http).get = "/dymensionxyz/dymension/rollapp/latest_state_info_index";
}

// Queries a BlockHeightToFinalizationQueue by index.
rpc BlockHeightToFinalizationQueue(QueryGetBlockHeightToFinalizationQueueRequest) returns (QueryGetBlockHeightToFinalizationQueueResponse) {
option (google.api.http).get = "/dymensionxyz/dymension/rollapp/block_height_to_finalization_queue/{finalizationHeight}";
}

// Queries a list of BlockHeightToFinalizationQueue items.
rpc BlockHeightToFinalizationQueueAll(QueryAllBlockHeightToFinalizationQueueRequest) returns (QueryAllBlockHeightToFinalizationQueueResponse) {
option (google.api.http).get = "/dymensionxyz/dymension/rollapp/block_height_to_finalization_queue";
}

// this line is used by starport scaffolding # 2
}

Expand Down Expand Up @@ -113,4 +123,22 @@ message QueryAllLatestStateInfoIndexResponse {
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

message QueryGetBlockHeightToFinalizationQueueRequest {
uint64 finalizationHeight = 1;

}

message QueryGetBlockHeightToFinalizationQueueResponse {
BlockHeightToFinalizationQueue blockHeightToFinalizationQueue = 1 [(gogoproto.nullable) = false];
}

message QueryAllBlockHeightToFinalizationQueueRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

message QueryAllBlockHeightToFinalizationQueueResponse {
repeated BlockHeightToFinalizationQueue blockHeightToFinalizationQueue = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// this line is used by starport scaffolding # 3
9 changes: 9 additions & 0 deletions proto/rollapp/state_info.proto
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ message StateInfo {
// the list must be ordered by height, starting from startHeight to startHeight+numBlocks-1
BlockDescriptors BDs = 9 [(gogoproto.nullable) = false];
}

// BlockHeightToFinalizationQueue defines a map from block height to list of states to finalized
message BlockHeightToFinalizationQueue {
// finalizationHeight is the block height that the state should be finalized
uint64 finalizationHeight = 1;
// finalizationQueue is a list of states that are waiting to be finalized
// when the block height becomes finalizationHeight
repeated StateInfoIndex finalizationQueue = 2 [(gogoproto.nullable) = false];
}
36 changes: 36 additions & 0 deletions x/rollapp/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package rollapp

import (
"fmt"

abci "github.com/tendermint/tendermint/abci/types"

"github.com/dymensionxyz/dymension/x/rollapp/keeper"
"github.com/dymensionxyz/dymension/x/rollapp/types"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// BeginBlocker is called on every block.
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
}

// Called every block to finalize states that their dispute period over.
func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
// check to see if there are pending states to be finalized
blockHeightToFinalizationQueue, found := k.GetBlockHeightToFinalizationQueue(ctx, uint64(ctx.BlockHeight()))

if found {
// finalize pending states
for _, stateInfoIndex := range blockHeightToFinalizationQueue.FinalizationQueue {
stateInfo, found := k.GetStateInfo(ctx, stateInfoIndex.RollappId, stateInfoIndex.Index)
if !found {
panic(fmt.Errorf("invariant broken: rollapp %s should have state to be finalizaed in height %d. update state index is %d \n",
stateInfoIndex.RollappId, ctx.BlockHeight(), stateInfoIndex.Index))
}
stateInfo.Status = types.STATE_STATUS_FINALIZED
// write the new status
k.SetStateInfo(ctx, stateInfo)
}
}
}
2 changes: 2 additions & 0 deletions x/rollapp/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func GetQueryCmd(queryRoute string) *cobra.Command {
cmd.AddCommand(CmdShowStateInfo())
cmd.AddCommand(CmdListLatestStateInfoIndex())
cmd.AddCommand(CmdShowLatestStateInfoIndex())
cmd.AddCommand(CmdListBlockHeightToFinalizationQueue())
cmd.AddCommand(CmdShowBlockHeightToFinalizationQueue())
// this line is used by starport scaffolding # 1

return cmd
Expand Down
77 changes: 77 additions & 0 deletions x/rollapp/client/cli/query_block_height_to_finalization_queue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cli

import (
"context"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/dymensionxyz/dymension/x/rollapp/types"
"github.com/spf13/cast"
"github.com/spf13/cobra"
)

func CmdListBlockHeightToFinalizationQueue() *cobra.Command {
cmd := &cobra.Command{
Use: "list-block-height-to-finalization-queue",
Short: "list all block_height_to_finalization_queue",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

params := &types.QueryAllBlockHeightToFinalizationQueueRequest{
Pagination: pageReq,
}

res, err := queryClient.BlockHeightToFinalizationQueueAll(context.Background(), params)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddPaginationFlagsToCmd(cmd, cmd.Use)
flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func CmdShowBlockHeightToFinalizationQueue() *cobra.Command {
cmd := &cobra.Command{
Use: "show-block-height-to-finalization-queue [finalization-height]",
Short: "shows a block_height_to_finalization_queue",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
clientCtx := client.GetClientContextFromCmd(cmd)

queryClient := types.NewQueryClient(clientCtx)

argFinalizationHeight, err := cast.ToUint64E(args[0])
if err != nil {
return err
}

params := &types.QueryGetBlockHeightToFinalizationQueueRequest{
FinalizationHeight: argFinalizationHeight,
}

res, err := queryClient.BlockHeightToFinalizationQueue(context.Background(), params)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
163 changes: 163 additions & 0 deletions x/rollapp/client/cli/query_block_height_to_finalization_queue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package cli_test

import (
"fmt"
"strconv"
"testing"

"github.com/cosmos/cosmos-sdk/client/flags"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/stretchr/testify/require"
tmcli "github.com/tendermint/tendermint/libs/cli"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/dymensionxyz/dymension/testutil/network"
"github.com/dymensionxyz/dymension/testutil/nullify"
"github.com/dymensionxyz/dymension/x/rollapp/client/cli"
"github.com/dymensionxyz/dymension/x/rollapp/types"
)

// Prevent strconv unused error
var _ = strconv.IntSize

func networkWithBlockHeightToFinalizationQueueObjects(t *testing.T, n int) (*network.Network, []types.BlockHeightToFinalizationQueue) {
t.Helper()
cfg := network.DefaultConfig()
state := types.GenesisState{}
require.NoError(t, cfg.Codec.UnmarshalJSON(cfg.GenesisState[types.ModuleName], &state))

for i := 0; i < n; i++ {
blockHeightToFinalizationQueue := types.BlockHeightToFinalizationQueue{
FinalizationHeight: uint64(i),

}
nullify.Fill(&blockHeightToFinalizationQueue)
state.BlockHeightToFinalizationQueueList = append(state.BlockHeightToFinalizationQueueList, blockHeightToFinalizationQueue)
}
buf, err := cfg.Codec.MarshalJSON(&state)
require.NoError(t, err)
cfg.GenesisState[types.ModuleName] = buf
return network.New(t, cfg), state.BlockHeightToFinalizationQueueList
}

func TestShowBlockHeightToFinalizationQueue(t *testing.T) {
net, objs := networkWithBlockHeightToFinalizationQueueObjects(t, 2)

ctx := net.Validators[0].ClientCtx
common := []string{
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
}
for _, tc := range []struct {
desc string
idFinalizationHeight uint64

args []string
err error
obj types.BlockHeightToFinalizationQueue
}{
{
desc: "found",
idFinalizationHeight: objs[0].FinalizationHeight,

args: common,
obj: objs[0],
},
{
desc: "not found",
idFinalizationHeight: 100000,

args: common,
err: status.Error(codes.NotFound, "not found"),
},
} {
t.Run(tc.desc, func(t *testing.T) {
args := []string{
strconv.Itoa(int(tc.idFinalizationHeight)),

}
args = append(args, tc.args...)
out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowBlockHeightToFinalizationQueue(), args)
if tc.err != nil {
stat, ok := status.FromError(tc.err)
require.True(t, ok)
require.ErrorIs(t, stat.Err(), tc.err)
} else {
require.NoError(t, err)
var resp types.QueryGetBlockHeightToFinalizationQueueResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.NotNil(t, resp.BlockHeightToFinalizationQueue)
require.Equal(t,
nullify.Fill(&tc.obj),
nullify.Fill(&resp.BlockHeightToFinalizationQueue),
)
}
})
}
}

func TestListBlockHeightToFinalizationQueue(t *testing.T) {
net, objs := networkWithBlockHeightToFinalizationQueueObjects(t, 5)

ctx := net.Validators[0].ClientCtx
request := func(next []byte, offset, limit uint64, total bool) []string {
args := []string{
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
}
if next == nil {
args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset))
} else {
args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next))
}
args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit))
if total {
args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal))
}
return args
}
t.Run("ByOffset", func(t *testing.T) {
step := 2
for i := 0; i < len(objs); i += step {
args := request(nil, uint64(i), uint64(step), false)
out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListBlockHeightToFinalizationQueue(), args)
require.NoError(t, err)
var resp types.QueryAllBlockHeightToFinalizationQueueResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.LessOrEqual(t, len(resp.BlockHeightToFinalizationQueue), step)
require.Subset(t,
nullify.Fill(objs),
nullify.Fill(resp.BlockHeightToFinalizationQueue),
)
}
})
t.Run("ByKey", func(t *testing.T) {
step := 2
var next []byte
for i := 0; i < len(objs); i += step {
args := request(next, 0, uint64(step), false)
out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListBlockHeightToFinalizationQueue(), args)
require.NoError(t, err)
var resp types.QueryAllBlockHeightToFinalizationQueueResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.LessOrEqual(t, len(resp.BlockHeightToFinalizationQueue), step)
require.Subset(t,
nullify.Fill(objs),
nullify.Fill(resp.BlockHeightToFinalizationQueue),
)
next = resp.Pagination.NextKey
}
})
t.Run("Total", func(t *testing.T) {
args := request(nil, 0, uint64(len(objs)), true)
out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListBlockHeightToFinalizationQueue(), args)
require.NoError(t, err)
var resp types.QueryAllBlockHeightToFinalizationQueueResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.NoError(t, err)
require.Equal(t, len(objs), int(resp.Pagination.Total))
require.ElementsMatch(t,
nullify.Fill(objs),
nullify.Fill(resp.BlockHeightToFinalizationQueue),
)
})
}
5 changes: 5 additions & 0 deletions x/rollapp/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)
for _, elem := range genState.LatestStateInfoIndexList {
k.SetLatestStateInfoIndex(ctx, elem)
}
// Set all the blockHeightToFinalizationQueue
for _, elem := range genState.BlockHeightToFinalizationQueueList {
k.SetBlockHeightToFinalizationQueue(ctx, elem)
}
// this line is used by starport scaffolding # genesis/module/init
k.SetParams(ctx, genState.Params)
}
Expand All @@ -33,6 +37,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState {
genesis.RollappList = k.GetAllRollapp(ctx)
genesis.StateInfoList = k.GetAllStateInfo(ctx)
genesis.LatestStateInfoIndexList = k.GetAllLatestStateInfoIndex(ctx)
genesis.BlockHeightToFinalizationQueueList = k.GetAllBlockHeightToFinalizationQueue(ctx)
// this line is used by starport scaffolding # genesis/module/export

return genesis
Expand Down
13 changes: 11 additions & 2 deletions x/rollapp/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ func TestGenesis(t *testing.T) {
RollappId: "1",
},
},
// this line is used by starport scaffolding # genesis/test/state
BlockHeightToFinalizationQueueList: []types.BlockHeightToFinalizationQueue{
{
FinalizationHeight: 0,
},
{
FinalizationHeight: 1,
},
},
// this line is used by starport scaffolding # genesis/test/state
}

k, ctx := keepertest.RollappKeeper(t)
Expand All @@ -58,5 +66,6 @@ func TestGenesis(t *testing.T) {
require.ElementsMatch(t, genesisState.RollappList, got.RollappList)
require.ElementsMatch(t, genesisState.StateInfoList, got.StateInfoList)
require.ElementsMatch(t, genesisState.LatestStateInfoIndexList, got.LatestStateInfoIndexList)
// this line is used by starport scaffolding # genesis/test/assert
require.ElementsMatch(t, genesisState.BlockHeightToFinalizationQueueList, got.BlockHeightToFinalizationQueueList)
// this line is used by starport scaffolding # genesis/test/assert
}
Loading

0 comments on commit 8824ff7

Please sign in to comment.