-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
test(server/v2/cometbft): Add abci unit tests #21020
Changes from 7 commits
f294fff
cbd05ce
587023f
116e283
462efb0
b17533e
2375aa7
44548be
eede22f
d008afd
9d604c9
d4866d9
9bfe6fe
07f4fa3
ee055b8
35787e2
71c4570
7bf135c
3b6e7ac
76ffcaf
c1c4159
337515c
5afbe85
1117fa7
48f13ac
6a512af
8a01fa2
12e2207
667c1e2
ed8d2bc
7f5c111
5578d48
0add40f
f4e1fa4
f5325d9
12be6e4
96d6f5b
8f7c343
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
package cometbft | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"testing" | ||
"time" | ||
|
||
appmodulev2 "cosmossdk.io/core/appmodule/v2" | ||
"cosmossdk.io/core/log" | ||
corestore "cosmossdk.io/core/store" | ||
am "cosmossdk.io/server/v2/appmanager" | ||
"cosmossdk.io/server/v2/cometbft/mempool" | ||
cometmock "cosmossdk.io/server/v2/cometbft/mock" | ||
"cosmossdk.io/server/v2/cometbft/types" | ||
"cosmossdk.io/server/v2/stf" | ||
"cosmossdk.io/server/v2/stf/branch" | ||
"cosmossdk.io/server/v2/stf/mock" | ||
abciproto "github.com/cometbft/cometbft/api/cometbft/abci/v1" | ||
v1 "github.com/cometbft/cometbft/api/cometbft/types/v1" | ||
|
||
"github.com/cosmos/gogoproto/proto" | ||
|
||
"encoding/json" | ||
|
||
consensustypes "cosmossdk.io/x/consensus/types" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func addMsgHandlerToSTF[T any, PT interface { | ||
*T | ||
proto.Message | ||
}, | ||
U any, UT interface { | ||
*U | ||
proto.Message | ||
}]( | ||
t *testing.T, | ||
s *stf.STF[mock.Tx], | ||
handler func(ctx context.Context, msg PT) (UT, error), | ||
) { | ||
t.Helper() | ||
msgRouterBuilder := stf.NewMsgRouterBuilder() | ||
err := msgRouterBuilder.RegisterHandler( | ||
proto.MessageName(PT(new(T))), | ||
func(ctx context.Context, msg appmodulev2.Message) (msgResp appmodulev2.Message, err error) { | ||
typedReq := msg.(PT) | ||
typedResp, err := handler(ctx, typedReq) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return typedResp, nil | ||
}, | ||
) | ||
require.NoError(t, err) | ||
|
||
msgRouter, err := msgRouterBuilder.Build() | ||
require.NoError(t, err) | ||
stf.SetMsgRouter(s, msgRouter) | ||
} | ||
|
||
func TestConsensus(t *testing.T) { | ||
// mockTx := mock.Tx{ | ||
// Sender: []byte("sender"), | ||
// Msg: &gogotypes.BoolValue{Value: true}, | ||
// GasLimit: 100_000, | ||
// } | ||
|
||
// sum := sha256.Sum256([]byte("test-hash")) | ||
|
||
s, err := stf.NewSTF( | ||
log.NewNopLogger().With("module", "stf"), | ||
stf.NewMsgRouterBuilder(), | ||
stf.NewMsgRouterBuilder(), | ||
func(ctx context.Context, txs []mock.Tx) error { return nil }, | ||
func(ctx context.Context) error { | ||
return kvSet(t, ctx, "begin-block") | ||
}, | ||
func(ctx context.Context) error { | ||
return kvSet(t, ctx, "end-block") | ||
}, | ||
func(ctx context.Context, tx mock.Tx) error { | ||
return kvSet(t, ctx, "validate") | ||
}, | ||
func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error) { return nil, nil }, | ||
func(ctx context.Context, tx mock.Tx, success bool) error { | ||
return kvSet(t, ctx, "post-tx-exec") | ||
}, | ||
branch.DefaultNewWriterMap, | ||
) | ||
|
||
ss := cometmock.NewMockStorage(log.NewNopLogger()) | ||
sc := cometmock.NewMockCommiter(log.NewNopLogger(), string(actorName), "stf") | ||
mockStore := cometmock.NewMockStore(ss, sc) | ||
|
||
b := am.Builder[mock.Tx]{ | ||
STF: s, | ||
DB: mockStore, | ||
ValidateTxGasLimit: 100_000, | ||
QueryGasLimit: 100_000, | ||
SimulationGasLimit: 100_000, | ||
InitGenesis: func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) error { | ||
return kvSet(t, ctx, "init-chain") | ||
}, | ||
} | ||
|
||
am, err := b.Build() | ||
require.NoError(t, err) | ||
|
||
c := NewConsensus[mock.Tx](am, mempool.NoOpMempool[mock.Tx]{}, mockStore, Config{}, mock.TxCodec{}, log.NewNopLogger()) | ||
|
||
t.Run("InitChain without update consensus params", func(t *testing.T) { | ||
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{ | ||
Time: time.Now(), | ||
ChainId: "test", | ||
InitialHeight: 1, | ||
}) | ||
require.NoError(t, err) | ||
stateStorageHas(t, mockStore, "init-chain", 1) | ||
stateStorageHas(t, mockStore, "end-block", 1) | ||
}) | ||
|
||
t.Run("InitChain with update consensus params", func(t *testing.T) { | ||
addMsgHandlerToSTF(t, s, func(ctx context.Context, msg *consensustypes.MsgUpdateParams) (*consensustypes.MsgUpdateParams, error) { | ||
kvSet(t, ctx, "exec") | ||
return nil, nil | ||
}) | ||
|
||
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{ | ||
Time: time.Now(), | ||
ChainId: "test", | ||
ConsensusParams: &v1.ConsensusParams{ | ||
Block: &v1.BlockParams{ | ||
MaxGas: 5000000, | ||
}, | ||
}, | ||
InitialHeight: 2, | ||
}) | ||
require.NoError(t, err) | ||
stateStorageHas(t, mockStore, "init-chain", 2) | ||
stateStorageHas(t, mockStore, "exec", 2) | ||
stateStorageHas(t, mockStore, "end-block", 2) | ||
|
||
stateCommitmentNoHas(t, mockStore, "init-chain", 2) | ||
stateStorageHas(t, mockStore, "exec", 2) | ||
stateCommitmentNoHas(t, mockStore, "end-block", 2) | ||
}) | ||
|
||
t.Run("FinalizeBlock genesis block", func(t *testing.T) { | ||
_, err := c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{ | ||
Time: time.Now(), | ||
Height: 2, | ||
}) | ||
require.NoError(t, err) | ||
stateStorageNoHas(t, mockStore, "begin-block", 2) | ||
stateStorageHas(t, mockStore, "end-block", 2) | ||
|
||
// commit genesis state | ||
stateCommitmentHas(t, mockStore, "init-chain", 2) | ||
stateCommitmentHas(t, mockStore, "exec", 2) | ||
stateCommitmentHas(t, mockStore, "end-block", 2) | ||
|
||
}) | ||
|
||
} | ||
|
||
var actorName = []byte("cookies") | ||
|
||
func kvSet(t *testing.T, ctx context.Context, v string) error { | ||
t.Helper() | ||
executionCtx := stf.GetExecutionContext(ctx) | ||
require.NotNil(t, executionCtx) | ||
state, err := stf.GetStateFromContext(executionCtx).GetWriter(actorName) | ||
require.NoError(t, err) | ||
return state.Set([]byte(v), []byte(v)) | ||
} | ||
|
||
func stateStorageHas(t *testing.T, store types.Store, key string, version uint64) { | ||
t.Helper() | ||
has, err := store.GetStateStorage().Has(actorName, version, []byte(key)) | ||
require.NoError(t, err) | ||
require.Truef(t, has, "state storage did not have key: %s", key) | ||
} | ||
|
||
func stateStorageNoHas(t *testing.T, store types.Store, key string, version uint64) { | ||
t.Helper() | ||
has, err := store.GetStateStorage().Has(actorName, version, []byte(key)) | ||
require.NoError(t, err) | ||
require.Falsef(t, has, "state storage had key: %s", key) | ||
} | ||
|
||
func stateCommitmentHas(t *testing.T, store types.Store, key string, version uint64) { | ||
t.Helper() | ||
bz, err := store.GetStateCommitment().Get(actorName, version, []byte(key)) | ||
require.NoError(t, err) | ||
require.NotEqual(t, len(bz), 0) | ||
require.Equal(t, bz, []byte(key)) | ||
} | ||
|
||
func stateCommitmentNoHas(t *testing.T, store types.Store, key string, version uint64) { | ||
t.Helper() | ||
bz, err := store.GetStateCommitment().Get(actorName, version, []byte(key)) | ||
// if not committed, should return version does not exist | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), "version does not exist") | ||
require.Equal(t, len(bz), 0) | ||
} | ||
|
||
func stateHas(t *testing.T, accountState corestore.ReaderMap, key string) { | ||
t.Helper() | ||
state, err := accountState.GetReader(actorName) | ||
require.NoError(t, err) | ||
has, err := state.Has([]byte(key)) | ||
require.NoError(t, err) | ||
require.Truef(t, has, "state did not have key: %s", key) | ||
} | ||
|
||
func stateNotHas(t *testing.T, accountState corestore.ReaderMap, key string) { | ||
t.Helper() | ||
state, err := accountState.GetReader(actorName) | ||
require.NoError(t, err) | ||
has, err := state.Has([]byte(key)) | ||
require.NoError(t, err) | ||
require.Falsef(t, has, "state was not supposed to have key: %s", key) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ replace ( | |
cosmossdk.io/log => ../../../log | ||
cosmossdk.io/server/v2 => ../ | ||
cosmossdk.io/server/v2/appmanager => ../appmanager | ||
cosmossdk.io/server/v2/stf => ../stf | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this be removed now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no we still need it for tests set up I think |
||
cosmossdk.io/store => ../../../store | ||
cosmossdk.io/store/v2 => ../../../store/v2 | ||
cosmossdk.io/x/accounts => ../../../x/accounts | ||
|
@@ -28,6 +29,7 @@ require ( | |
cosmossdk.io/errors v1.0.1 | ||
cosmossdk.io/server/v2 v2.0.0-00010101000000-000000000000 | ||
cosmossdk.io/server/v2/appmanager v0.0.0-00010101000000-000000000000 | ||
cosmossdk.io/server/v2/stf v0.0.0-20240708142107-25e99c54bac1 | ||
cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000 | ||
cosmossdk.io/x/auth v0.0.0-00010101000000-000000000000 | ||
cosmossdk.io/x/consensus v0.0.0-00010101000000-000000000000 | ||
|
@@ -41,6 +43,7 @@ require ( | |
github.com/spf13/cobra v1.8.1 | ||
github.com/spf13/pflag v1.0.5 | ||
github.com/spf13/viper v1.19.0 | ||
github.com/stretchr/testify v1.9.0 | ||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 | ||
google.golang.org/grpc v1.64.0 | ||
google.golang.org/protobuf v1.34.2 | ||
|
@@ -134,6 +137,7 @@ require ( | |
github.com/magiconair/properties v1.8.7 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
github.com/mattn/go-sqlite3 v1.14.22 // indirect | ||
github.com/minio/highwayhash v1.0.2 // indirect | ||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect | ||
github.com/mitchellh/mapstructure v1.5.0 // indirect | ||
|
@@ -159,7 +163,6 @@ require ( | |
github.com/sourcegraph/conc v0.3.0 // indirect | ||
github.com/spf13/afero v1.11.0 // indirect | ||
github.com/spf13/cast v1.6.0 // indirect | ||
github.com/stretchr/testify v1.9.0 // indirect | ||
github.com/subosito/gotenv v1.6.0 // indirect | ||
github.com/supranational/blst v0.3.12 // indirect | ||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package mock | ||
|
||
import ( | ||
corestore "cosmossdk.io/core/store" | ||
// ammstore "cosmossdk.io/server/v2/appmanager/store" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit, remove those commented imports |
||
// "cosmossdk.io/server/v2/stf/branch" | ||
) | ||
|
||
// ReaderMap defines an adapter around a RootStore that only exposes read-only | ||
// operations. This is useful for exposing a read-only view of the RootStore at | ||
// a specific version in history, which could also be the latest state. | ||
type ReaderMap struct { | ||
store *MockStore | ||
version uint64 | ||
} | ||
|
||
func NewMockReaderMap(v uint64, rs *MockStore) *ReaderMap { | ||
return &ReaderMap{ | ||
store: rs, | ||
version: v, | ||
} | ||
} | ||
|
||
func (roa *ReaderMap) GetReader(actor []byte) (corestore.Reader, error) { | ||
return NewMockReader(roa.version, roa.store, actor), nil | ||
} | ||
|
||
// Reader represents a read-only adapter for accessing data from the root store. | ||
type MockReader struct { | ||
version uint64 // The version of the data. | ||
store *MockStore // The root store to read data from. | ||
actor []byte // The actor associated with the data. | ||
} | ||
|
||
func NewMockReader(v uint64, rs *MockStore, actor []byte) *MockReader { | ||
return &MockReader{ | ||
version: v, | ||
store: rs, | ||
actor: actor, | ||
} | ||
} | ||
|
||
func (roa *MockReader) Has(key []byte) (bool, error) { | ||
val, err := roa.store.GetStateStorage().Has(roa.actor, roa.version, key) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return val, nil | ||
} | ||
|
||
func (roa *MockReader) Get(key []byte) ([]byte, error) { | ||
result, err := roa.store.GetStateStorage().Get(roa.actor, roa.version, key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
func (roa *MockReader) Iterator(start, end []byte) (corestore.Iterator, error) { | ||
return roa.store.GetStateStorage().Iterator(roa.actor, roa.version, start, end) | ||
} | ||
|
||
func (roa *MockReader) ReverseIterator(start, end []byte) (corestore.Iterator, error) { | ||
return roa.store.GetStateStorage().ReverseIterator(roa.actor, roa.version, start, end) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make lint-fix