From adebbc06efa4e3d162e763cca61e536bf4c1e677 Mon Sep 17 00:00:00 2001 From: Facundo Date: Mon, 9 Dec 2024 18:41:36 +0100 Subject: [PATCH 01/14] progress --- server/v2/cometbft/abci.go | 2 +- server/v2/cometbft/handlers/defaults.go | 2 +- server/v2/cometbft/handlers/handlers.go | 4 ++-- server/v2/cometbft/options.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/v2/cometbft/abci.go b/server/v2/cometbft/abci.go index c376f4ac2690..e8fd297b2f97 100644 --- a/server/v2/cometbft/abci.go +++ b/server/v2/cometbft/abci.go @@ -74,7 +74,7 @@ type consensus[T transaction.Tx] struct { prepareProposalHandler handlers.PrepareHandler[T] processProposalHandler handlers.ProcessHandler[T] - verifyVoteExt handlers.VerifyVoteExtensionhandler + verifyVoteExt handlers.VerifyVoteExtensionHandler extendVote handlers.ExtendVoteHandler checkTxHandler handlers.CheckTxHandler[T] diff --git a/server/v2/cometbft/handlers/defaults.go b/server/v2/cometbft/handlers/defaults.go index d8f43bb2fd25..e9f90eecb1ca 100644 --- a/server/v2/cometbft/handlers/defaults.go +++ b/server/v2/cometbft/handlers/defaults.go @@ -197,7 +197,7 @@ func NoOpExtendVote() ExtendVoteHandler { // NoOpVerifyVoteExtensionHandler defines a no-op VerifyVoteExtension handler. It // will always return an ACCEPT status with no error. -func NoOpVerifyVoteExtensionHandler() VerifyVoteExtensionhandler { +func NoOpVerifyVoteExtensionHandler() VerifyVoteExtensionHandler { return func(context.Context, store.ReaderMap, *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) { return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT}, nil } diff --git a/server/v2/cometbft/handlers/handlers.go b/server/v2/cometbft/handlers/handlers.go index 015594f469f7..aa420da63389 100644 --- a/server/v2/cometbft/handlers/handlers.go +++ b/server/v2/cometbft/handlers/handlers.go @@ -19,10 +19,10 @@ type ( // If the verification of a transaction fails, the boolean is false and the error is non-nil. ProcessHandler[T transaction.Tx] func(context.Context, AppManager[T], transaction.Codec[T], *abci.ProcessProposalRequest) error - // VerifyVoteExtensionhandler is a function type that handles the verification of a vote extension request. + // VerifyVoteExtensionHandler is a function type that handles the verification of a vote extension request. // It takes a context, a store reader map, and a request to verify a vote extension. // It returns a response to verify the vote extension and an error if any. - VerifyVoteExtensionhandler func(context.Context, store.ReaderMap, *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) + VerifyVoteExtensionHandler func(context.Context, store.ReaderMap, *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) // ExtendVoteHandler is a function type that handles the extension of a vote. // It takes a context, a store reader map, and a request to extend a vote. diff --git a/server/v2/cometbft/options.go b/server/v2/cometbft/options.go index b5936148b5a5..5016495461e3 100644 --- a/server/v2/cometbft/options.go +++ b/server/v2/cometbft/options.go @@ -20,7 +20,7 @@ type ServerOptions[T transaction.Tx] struct { PrepareProposalHandler handlers.PrepareHandler[T] ProcessProposalHandler handlers.ProcessHandler[T] CheckTxHandler handlers.CheckTxHandler[T] - VerifyVoteExtensionHandler handlers.VerifyVoteExtensionhandler + VerifyVoteExtensionHandler handlers.VerifyVoteExtensionHandler ExtendVoteHandler handlers.ExtendVoteHandler KeygenF keyGenF From 79efd34cf886bd24385b79852f0bcaba4216919a Mon Sep 17 00:00:00 2001 From: Facundo Date: Wed, 11 Dec 2024 14:29:42 +0100 Subject: [PATCH 02/14] kind of working --- runtime/v2/builder.go | 25 ++++++++++++++++++++- server/v2/cometbft/abci.go | 2 ++ server/v2/cometbft/utils.go | 31 +++++++++++++++++++++++++ simapp/v2/app_di.go | 8 ++++++- simapp/v2/go.mod | 2 ++ simapp/v2/simdv2/cmd/config.go | 41 ++++++++++++++++++++++++++++++++++ x/auth/tx/gogotx.go | 7 ++---- 7 files changed, 109 insertions(+), 7 deletions(-) diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index 631ac550c0c6..e04424a65c5f 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -30,6 +30,7 @@ type AppBuilder[T transaction.Tx] struct { branch func(state store.ReaderMap) store.WriterMap txValidator func(ctx context.Context, tx T) error postTxExec func(ctx context.Context, tx T, success bool) error + preblocker func(ctx context.Context, txs []T) error } // RegisterModules registers the provided modules with the module manager. @@ -95,11 +96,23 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { endBlocker, valUpdate := a.app.moduleManager.EndBlock() + preblockerFn := func(ctx context.Context, txs []T) error { + if err := a.app.moduleManager.PreBlocker()(ctx, txs); err != nil { + return err + } + + if a.preblocker != nil { + return a.preblocker(ctx, txs) + } + + return nil + } + stf, err := stf.New[T]( a.app.logger.With("module", "stf"), a.app.msgRouterBuilder, a.app.queryRouterBuilder, - a.app.moduleManager.PreBlocker(), + preblockerFn, a.app.moduleManager.BeginBlock(), endBlocker, a.txValidator, @@ -219,3 +232,13 @@ func AppBuilderWithPostTxExec[T transaction.Tx]( a.postTxExec = postTxExec } } + +func AppBuilderWithPreblocker[T transaction.Tx]( + preblocker func( + ctx context.Context, txs []T, + ) error, +) AppBuilderOption[T] { + return func(a *AppBuilder[T]) { + a.preblocker = preblocker + } +} diff --git a/server/v2/cometbft/abci.go b/server/v2/cometbft/abci.go index 79eb8863d223..a87650843961 100644 --- a/server/v2/cometbft/abci.go +++ b/server/v2/cometbft/abci.go @@ -730,6 +730,8 @@ func decodeTxs[T transaction.Tx](logger log.Logger, rawTxs [][]byte, codec trans if err != nil { // do not return an error here, as we want to deliver the block even if some txs are invalid logger.Debug("failed to decode tx", "err", err) + txs[i] = RawTx(rawTx).(T) // allows getting the raw bytes down the line + continue } txs[i] = tx } diff --git a/server/v2/cometbft/utils.go b/server/v2/cometbft/utils.go index 81d47b7f8cb8..a06d97ba6cd9 100644 --- a/server/v2/cometbft/utils.go +++ b/server/v2/cometbft/utils.go @@ -2,6 +2,7 @@ package cometbft import ( "context" + "crypto/sha256" "errors" "fmt" "math" @@ -442,3 +443,33 @@ func uint64ToInt64(u uint64) int64 { } return int64(u) } + +// RawTx allows access to the raw bytes of a transaction even if it failed +// to decode. +func RawTx(tx []byte) transaction.Tx { + return InjectedTx(tx) +} + +type InjectedTx []byte + +var _ transaction.Tx = InjectedTx{} + +func (tx InjectedTx) Bytes() []byte { + return tx +} + +func (tx InjectedTx) Hash() [32]byte { + return sha256.Sum256(tx) +} + +func (tx InjectedTx) GetGasLimit() (uint64, error) { + return 0, nil +} + +func (tx InjectedTx) GetMessages() ([]transaction.Msg, error) { + return nil, nil +} + +func (tx InjectedTx) GetSenders() ([]transaction.Identity, error) { + return [][]byte{[]byte("cometbft")}, nil +} diff --git a/simapp/v2/app_di.go b/simapp/v2/app_di.go index bd0a39472a1c..b2566608ff4b 100644 --- a/simapp/v2/app_di.go +++ b/simapp/v2/app_di.go @@ -1,6 +1,7 @@ package simapp import ( + "context" _ "embed" "fmt" @@ -157,7 +158,12 @@ func NewSimApp[T transaction.Tx]( } var err error - app.App, err = appBuilder.Build() + app.App, err = appBuilder.Build(runtime.AppBuilderWithPreblocker( + func(ctx context.Context, txs []T) error { + fmt.Println("Preblocker!!!", txs) + return nil + }, + )) if err != nil { return nil, err } diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index d29decad721d..72a81daa94c8 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -279,6 +279,7 @@ replace ( cosmossdk.io/x/slashing => ../../x/slashing cosmossdk.io/x/staking => ../../x/staking cosmossdk.io/x/tx => ../../x/tx + cosmossdk.io/x/auth => ../../x/auth cosmossdk.io/x/upgrade => ../../x/upgrade ) @@ -299,6 +300,7 @@ replace ( replace ( cosmossdk.io/api => ../../api cosmossdk.io/core/testing => ../../core/testing + cosmossdk.io/core => ../../core cosmossdk.io/indexer/postgres => ../../indexer/postgres cosmossdk.io/runtime/v2 => ../../runtime/v2 cosmossdk.io/schema => ../../schema diff --git a/simapp/v2/simdv2/cmd/config.go b/simapp/v2/simdv2/cmd/config.go index 96325705b380..05cd7977a384 100644 --- a/simapp/v2/simdv2/cmd/config.go +++ b/simapp/v2/simdv2/cmd/config.go @@ -1,14 +1,19 @@ package cmd import ( + "context" + "fmt" "strings" "time" + v1 "github.com/cometbft/cometbft/api/cometbft/abci/v1" cmtcfg "github.com/cometbft/cometbft/config" + "cosmossdk.io/core/store" "cosmossdk.io/core/transaction" serverv2 "cosmossdk.io/server/v2" "cosmossdk.io/server/v2/cometbft" + "cosmossdk.io/server/v2/cometbft/handlers" clientconfig "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -92,6 +97,8 @@ func initCometConfig() cometbft.CfgOption { func initCometOptions[T transaction.Tx]() cometbft.ServerOptions[T] { serverOptions := cometbft.DefaultServerOptions[T]() + serverOptions.PrepareProposalHandler = CustomPrepareProposal[T]() + serverOptions.ExtendVoteHandler = CustomExtendVoteHandler[T]() // overwrite app mempool, using max-txs option // serverOptions.Mempool = func(cfg map[string]any) mempool.Mempool[T] { @@ -106,3 +113,37 @@ func initCometOptions[T transaction.Tx]() cometbft.ServerOptions[T] { return serverOptions } + +func CustomExtendVoteHandler[T transaction.Tx]() handlers.ExtendVoteHandler { + return func(ctx context.Context, rm store.ReaderMap, evr *v1.ExtendVoteRequest) (*v1.ExtendVoteResponse, error) { + return &v1.ExtendVoteResponse{ + VoteExtension: []byte("BTC=1234567.89;height=" + fmt.Sprint(evr.Height)), + }, nil + } +} + +func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] { + return func(ctx context.Context, app handlers.AppManager[T], codec transaction.Codec[T], req *v1.PrepareProposalRequest) ([]T, error) { + var txs []T + for _, tx := range req.Txs { + decTx, err := codec.Decode(tx) + if err != nil { + continue + } + + txs = append(txs, decTx) + } + + // Process vote extensions + injectedTx := []byte{} + for _, vote := range req.LocalLastCommit.Votes { + // TODO: add signature verification + injectedTx = append(injectedTx, vote.VoteExtension...) + } + + // put the injected tx into the first position + txs = append([]T{cometbft.RawTx(injectedTx).(T)}, txs...) + + return txs, nil + } +} diff --git a/x/auth/tx/gogotx.go b/x/auth/tx/gogotx.go index aa424f1c991f..1a386eb34ffd 100644 --- a/x/auth/tx/gogotx.go +++ b/x/auth/tx/gogotx.go @@ -32,7 +32,7 @@ func newWrapperFromDecodedTx( addrCodec address.Codec, cdc codec.BinaryCodec, decodedTx *decode.DecodedTx, ) (*gogoTxWrapper, error) { var ( - fees = make(sdk.Coins, len(decodedTx.Tx.AuthInfo.Fee.Amount)) + fees = sdk.Coins{} // decodedTx.Tx.AuthInfo.Fee.Amount might be nil err error ) for i, fee := range decodedTx.Tx.AuthInfo.Fee.Amount { @@ -43,10 +43,7 @@ func newWrapperFromDecodedTx( if err = sdk.ValidateDenom(fee.Denom); err != nil { return nil, fmt.Errorf("invalid fee coin denom at index %d: %w", i, err) } - fees[i] = sdk.Coin{ - Denom: fee.Denom, - Amount: amtInt, - } + fees.Add(sdk.NewCoin(fee.Denom, amtInt)) } if !fees.IsSorted() { return nil, fmt.Errorf("invalid not sorted tx fees: %s", fees.String()) From ed2e353c63992cf642bec2a68f61957485bf547e Mon Sep 17 00:00:00 2001 From: Facundo Date: Fri, 13 Dec 2024 18:07:59 +0100 Subject: [PATCH 03/14] working finally --- server/v2/cometbft/abci.go | 14 +- server/v2/cometbft/handlers/defaults.go | 8 +- server/v2/cometbft/handlers/handlers.go | 4 +- server/v2/cometbft/utils.go | 188 +++++++++++++++++++++++- simapp/v2/simdv2/cmd/commands.go | 2 +- simapp/v2/simdv2/cmd/config.go | 79 +++++++++- 6 files changed, 268 insertions(+), 27 deletions(-) diff --git a/server/v2/cometbft/abci.go b/server/v2/cometbft/abci.go index a87650843961..b2bafa507662 100644 --- a/server/v2/cometbft/abci.go +++ b/server/v2/cometbft/abci.go @@ -147,7 +147,7 @@ func (c *consensus[T]) Info(ctx context.Context, _ *abciproto.InfoRequest) (*abc // if height is 0, we dont know the consensus params var appVersion uint64 = 0 if version > 0 { - cp, err := c.GetConsensusParams(ctx) + cp, err := GetConsensusParams(ctx, c.app) // if the consensus params are not found, we set the app version to 0 // in the case that the start version is > 0 if cp == nil || errors.Is(err, collections.ErrNotFound) { @@ -415,7 +415,7 @@ func (c *consensus[T]) PrepareProposal( LastCommit: toCoreExtendedCommitInfo(req.LocalLastCommit), }) - txs, err := c.prepareProposalHandler(ciCtx, c.app, c.txCodec, req) + txs, err := c.prepareProposalHandler(ciCtx, c.app, c.txCodec, req, c.chainID) if err != nil { return nil, err } @@ -461,7 +461,7 @@ func (c *consensus[T]) ProcessProposal( LastCommit: toCoreCommitInfo(req.ProposedLastCommit), }) - err := c.processProposalHandler(ciCtx, c.app, c.txCodec, req) + err := c.processProposalHandler(ciCtx, c.app, c.txCodec, req, c.chainID) if err != nil { c.logger.Error("failed to process proposal", "height", req.Height, "time", req.Time, "hash", fmt.Sprintf("%X", req.Hash), "err", err) return &abciproto.ProcessProposalResponse{ @@ -561,7 +561,7 @@ func (c *consensus[T]) FinalizeBlock( c.lastCommittedHeight.Store(req.Height) - cp, err := c.GetConsensusParams(ctx) // we get the consensus params from the latest state because we committed state above + cp, err := GetConsensusParams(ctx, c.app) // we get the consensus params from the latest state because we committed state above if err != nil { return nil, err } @@ -628,7 +628,7 @@ func (c *consensus[T]) Commit(ctx context.Context, _ *abciproto.CommitRequest) ( c.snapshotManager.SnapshotIfApplicable(lastCommittedHeight) - cp, err := c.GetConsensusParams(ctx) + cp, err := GetConsensusParams(ctx, c.app) if err != nil { return nil, err } @@ -647,7 +647,7 @@ func (c *consensus[T]) VerifyVoteExtension( ) (*abciproto.VerifyVoteExtensionResponse, error) { // If vote extensions are not enabled, as a safety precaution, we return an // error. - cp, err := c.GetConsensusParams(ctx) + cp, err := GetConsensusParams(ctx, c.app) if err != nil { return nil, err } @@ -686,7 +686,7 @@ func (c *consensus[T]) VerifyVoteExtension( func (c *consensus[T]) ExtendVote(ctx context.Context, req *abciproto.ExtendVoteRequest) (*abciproto.ExtendVoteResponse, error) { // If vote extensions are not enabled, as a safety precaution, we return an // error. - cp, err := c.GetConsensusParams(ctx) + cp, err := GetConsensusParams(ctx, c.app) if err != nil { return nil, err } diff --git a/server/v2/cometbft/handlers/defaults.go b/server/v2/cometbft/handlers/defaults.go index e9f90eecb1ca..08403abd28ae 100644 --- a/server/v2/cometbft/handlers/defaults.go +++ b/server/v2/cometbft/handlers/defaults.go @@ -32,7 +32,7 @@ func NewDefaultProposalHandler[T transaction.Tx](mp mempool.Mempool[T]) *Default } func (h *DefaultProposalHandler[T]) PrepareHandler() PrepareHandler[T] { - return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.PrepareProposalRequest) ([]T, error) { + return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.PrepareProposalRequest, chainID string) ([]T, error) { var maxBlockGas uint64 res, err := app.Query(ctx, 0, &consensustypes.QueryParamsRequest{}) @@ -98,7 +98,7 @@ func (h *DefaultProposalHandler[T]) PrepareHandler() PrepareHandler[T] { } func (h *DefaultProposalHandler[T]) ProcessHandler() ProcessHandler[T] { - return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.ProcessProposalRequest) error { + return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.ProcessProposalRequest, chainID string) error { // If the mempool is nil we simply return ACCEPT, // because PrepareProposal may have included txs that could fail verification. _, isNoOp := h.mempool.(mempool.NoOpMempool[T]) @@ -174,7 +174,7 @@ func decodeTxs[T transaction.Tx](codec transaction.Codec[T], txsBz [][]byte) []T // NoOpPrepareProposal defines a no-op PrepareProposal handler. It will always // return the transactions sent by the client's request. func NoOpPrepareProposal[T transaction.Tx]() PrepareHandler[T] { - return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.PrepareProposalRequest) ([]T, error) { + return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.PrepareProposalRequest, chainID string) ([]T, error) { return decodeTxs(codec, req.Txs), nil } } @@ -182,7 +182,7 @@ func NoOpPrepareProposal[T transaction.Tx]() PrepareHandler[T] { // NoOpProcessProposal defines a no-op ProcessProposal Handler. It will always // return ACCEPT. func NoOpProcessProposal[T transaction.Tx]() ProcessHandler[T] { - return func(context.Context, AppManager[T], transaction.Codec[T], *abci.ProcessProposalRequest) error { + return func(context.Context, AppManager[T], transaction.Codec[T], *abci.ProcessProposalRequest, string) error { return nil } } diff --git a/server/v2/cometbft/handlers/handlers.go b/server/v2/cometbft/handlers/handlers.go index aa420da63389..e416c0de1104 100644 --- a/server/v2/cometbft/handlers/handlers.go +++ b/server/v2/cometbft/handlers/handlers.go @@ -13,11 +13,11 @@ import ( type ( // PrepareHandler passes in the list of Txs that are being proposed. The app can then do stateful operations // over the list of proposed transactions. It can return a modified list of txs to include in the proposal. - PrepareHandler[T transaction.Tx] func(context.Context, AppManager[T], transaction.Codec[T], *abci.PrepareProposalRequest) ([]T, error) + PrepareHandler[T transaction.Tx] func(context.Context, AppManager[T], transaction.Codec[T], *abci.PrepareProposalRequest, string) ([]T, error) // ProcessHandler is a function that takes a list of transactions and returns a boolean and an error. // If the verification of a transaction fails, the boolean is false and the error is non-nil. - ProcessHandler[T transaction.Tx] func(context.Context, AppManager[T], transaction.Codec[T], *abci.ProcessProposalRequest) error + ProcessHandler[T transaction.Tx] func(context.Context, AppManager[T], transaction.Codec[T], *abci.ProcessProposalRequest, string) error // VerifyVoteExtensionHandler is a function type that handles the verification of a vote extension request. // It takes a context, a store reader map, and a request to verify a vote extension. diff --git a/server/v2/cometbft/utils.go b/server/v2/cometbft/utils.go index a06d97ba6cd9..11db28f338b1 100644 --- a/server/v2/cometbft/utils.go +++ b/server/v2/cometbft/utils.go @@ -1,16 +1,23 @@ package cometbft import ( + "bytes" "context" "crypto/sha256" "errors" "fmt" "math" + "slices" "strings" "time" abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1" + cryptoenc "github.com/cometbft/cometbft/crypto/encoding" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + protoio "github.com/cosmos/gogoproto/io" + "github.com/cosmos/gogoproto/proto" gogoproto "github.com/cosmos/gogoproto/proto" gogoany "github.com/cosmos/gogoproto/types/any" "google.golang.org/grpc/codes" @@ -22,6 +29,7 @@ import ( "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" errorsmod "cosmossdk.io/errors" // we aren't using errors/v2 as it doesn't support grpc status codes + "cosmossdk.io/server/v2/cometbft/handlers" "cosmossdk.io/x/consensus/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -345,13 +353,8 @@ func (c *consensus[T]) validateFinalizeBlockHeight(req *abci.FinalizeBlockReques // GetConsensusParams makes a query to the consensus module in order to get the latest consensus // parameters from committed state -func (c *consensus[T]) GetConsensusParams(ctx context.Context) (*cmtproto.ConsensusParams, error) { - latestVersion, err := c.store.GetLatestVersion() - if err != nil { - return nil, err - } - - res, err := c.app.Query(ctx, latestVersion, &types.QueryParamsRequest{}) +func GetConsensusParams[T transaction.Tx](ctx context.Context, app handlers.AppManager[T]) (*cmtproto.ConsensusParams, error) { + res, err := app.Query(ctx, 0, &types.QueryParamsRequest{}) if err != nil { return nil, err } @@ -473,3 +476,174 @@ func (tx InjectedTx) GetMessages() ([]transaction.Msg, error) { func (tx InjectedTx) GetSenders() ([]transaction.Identity, error) { return [][]byte{[]byte("cometbft")}, nil } + +// ValidateVoteExtensions defines a helper function for verifying vote extension +// signatures that may be passed or manually injected into a block proposal from +// a proposer in PrepareProposal. It returns an error if any signature is invalid +// or if unexpected vote extensions and/or signatures are found or less than 2/3 +// power is received. +// If commitInfo is nil, this function can be used to check a set of vote extensions +// without comparing them to a commit. +func ValidateVoteExtensions[T transaction.Tx]( + ctx context.Context, + app handlers.AppManager[T], + chainID string, + validatorStore func(context.Context, []byte) (cryptotypes.PubKey, error), + extCommit abci.ExtendedCommitInfo, + currentHeight int64, + commitInfo *abci.CommitInfo, +) error { + cp, err := GetConsensusParams(ctx, app) + if err != nil { + return err + } + + if commitInfo != nil { + // Check that both extCommit + commit are ordered in accordance with vp/address. + if err := validateExtendedCommitAgainstLastCommit(extCommit, *commitInfo); err != nil { + return err + } + } + + // Start checking vote extensions only **after** the vote extensions enable + // height, because when `currentHeight == VoteExtensionsEnableHeight` + // PrepareProposal doesn't get any vote extensions in its request. + extsEnabled := cp.Feature != nil && cp.Feature.VoteExtensionsEnableHeight != nil && currentHeight > cp.Feature.VoteExtensionsEnableHeight.Value && cp.Feature.VoteExtensionsEnableHeight.Value != 0 + if !extsEnabled { + extsEnabled = cp.Abci != nil && currentHeight > cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0 + } + marshalDelimitedFn := func(msg proto.Message) ([]byte, error) { + var buf bytes.Buffer + if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil { + return nil, err + } + + return buf.Bytes(), nil + } + + var ( + // Total voting power of all vote extensions. + totalVP int64 + // Total voting power of all validators that submitted valid vote extensions. + sumVP int64 + ) + + for _, vote := range extCommit.Votes { + totalVP += vote.Validator.Power + + // Only check + include power if the vote is a commit vote. There must be super-majority, otherwise the + // previous block (the block the vote is for) could not have been committed. + if vote.BlockIdFlag != cmtproto.BlockIDFlagCommit { + continue + } + + if !extsEnabled { + if len(vote.VoteExtension) > 0 { + return fmt.Errorf("vote extensions disabled; received non-empty vote extension at height %d", currentHeight) + } + if len(vote.ExtensionSignature) > 0 { + return fmt.Errorf("vote extensions disabled; received non-empty vote extension signature at height %d", currentHeight) + } + + continue + } + + if len(vote.ExtensionSignature) == 0 { + return fmt.Errorf("vote extensions enabled; received empty vote extension signature at height %d", currentHeight) + } + + valConsAddr := sdk.ConsAddress(vote.Validator.Address) + + pubKeyProto, err := validatorStore(ctx, valConsAddr) + if err != nil { + return fmt.Errorf("failed to get validator %X public key: %w", valConsAddr, err) + } + + cmtpk, err := cryptocodec.ToCmtProtoPublicKey(pubKeyProto) + if err != nil { + return fmt.Errorf("failed to convert validator %X public key: %w", valConsAddr, err) + } + + cmtPubKey, err := cryptoenc.PubKeyFromProto(cmtpk) + if err != nil { + return fmt.Errorf("failed to convert validator %X public key: %w", valConsAddr, err) + } + + cve := cmtproto.CanonicalVoteExtension{ + Extension: vote.VoteExtension, + Height: currentHeight - 1, // the vote extension was signed in the previous height + Round: int64(extCommit.Round), + ChainId: chainID, + } + + extSignBytes, err := marshalDelimitedFn(&cve) + if err != nil { + return fmt.Errorf("failed to encode CanonicalVoteExtension: %w", err) + } + + if !cmtPubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) { + return fmt.Errorf("failed to verify validator %X vote extension signature", valConsAddr) + } + + sumVP += vote.Validator.Power + } + + // This check is probably unnecessary, but better safe than sorry. + if totalVP <= 0 { + return fmt.Errorf("total voting power must be positive, got: %d", totalVP) + } + + // If the sum of the voting power has not reached (2/3 + 1) we need to error. + if requiredVP := ((totalVP * 2) / 3) + 1; sumVP < requiredVP { + return fmt.Errorf( + "insufficient cumulative voting power received to verify vote extensions; got: %d, expected: >=%d", + sumVP, requiredVP, + ) + } + return nil +} + +// validateExtendedCommitAgainstLastCommit validates an ExtendedCommitInfo against a LastCommit. Specifically, +// it checks that the ExtendedCommit + LastCommit (for the same height), are consistent with each other + that +// they are ordered correctly (by voting power) in accordance with +// [comet](https://github.com/cometbft/cometbft/blob/4ce0277b35f31985bbf2c25d3806a184a4510010/types/validator_set.go#L784). +func validateExtendedCommitAgainstLastCommit(ec abci.ExtendedCommitInfo, lc abci.CommitInfo) error { + // check that the rounds are the same + if ec.Round != lc.Round { + return fmt.Errorf("extended commit round %d does not match last commit round %d", ec.Round, lc.Round) + } + + // check that the # of votes are the same + if len(ec.Votes) != len(lc.Votes) { + return fmt.Errorf("extended commit votes length %d does not match last commit votes length %d", len(ec.Votes), len(lc.Votes)) + } + + // check sort order of extended commit votes + if !slices.IsSortedFunc(ec.Votes, func(vote1, vote2 abci.ExtendedVoteInfo) int { + if vote1.Validator.Power == vote2.Validator.Power { + return bytes.Compare(vote1.Validator.Address, vote2.Validator.Address) // addresses sorted in ascending order (used to break vp conflicts) + } + return -int(vote1.Validator.Power - vote2.Validator.Power) // vp sorted in descending order + }) { + return errors.New("extended commit votes are not sorted by voting power") + } + + addressCache := make(map[string]struct{}, len(ec.Votes)) + // check consistency between LastCommit and ExtendedCommit + for i, vote := range ec.Votes { + // cache addresses to check for duplicates + if _, ok := addressCache[string(vote.Validator.Address)]; ok { + return fmt.Errorf("extended commit vote address %X is duplicated", vote.Validator.Address) + } + addressCache[string(vote.Validator.Address)] = struct{}{} + + if !bytes.Equal(vote.Validator.Address, lc.Votes[i].Validator.Address) { + return fmt.Errorf("extended commit vote address %X does not match last commit vote address %X", vote.Validator.Address, lc.Votes[i].Validator.Address) + } + if vote.Validator.Power != lc.Votes[i].Validator.Power { + return fmt.Errorf("extended commit vote power %d does not match last commit vote power %d", vote.Validator.Power, lc.Votes[i].Validator.Power) + } + } + + return nil +} diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index 28586e32c2de..8704f11493d5 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -119,7 +119,7 @@ func InitRootCmd[T transaction.Tx]( &client.DefaultTxDecoder[T]{TxConfig: deps.TxConfig}, simApp.App.QueryHandlers(), simApp.App.SchemaDecoderResolver(), - initCometOptions[T](), + initCometOptions[T](simApp), deps.GlobalConfig, ) if err != nil { diff --git a/simapp/v2/simdv2/cmd/config.go b/simapp/v2/simdv2/cmd/config.go index 05cd7977a384..da4e0c35161d 100644 --- a/simapp/v2/simdv2/cmd/config.go +++ b/simapp/v2/simdv2/cmd/config.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "encoding/json" "fmt" "strings" "time" @@ -14,6 +15,10 @@ import ( serverv2 "cosmossdk.io/server/v2" "cosmossdk.io/server/v2/cometbft" "cosmossdk.io/server/v2/cometbft/handlers" + "cosmossdk.io/simapp/v2" + staking "cosmossdk.io/x/staking/types" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" clientconfig "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -95,9 +100,10 @@ func initCometConfig() cometbft.CfgOption { return cometbft.OverwriteDefaultConfigTomlConfig(cfg) } -func initCometOptions[T transaction.Tx]() cometbft.ServerOptions[T] { +func initCometOptions[T transaction.Tx](simapp *simapp.SimApp[T]) cometbft.ServerOptions[T] { serverOptions := cometbft.DefaultServerOptions[T]() serverOptions.PrepareProposalHandler = CustomPrepareProposal[T]() + serverOptions.ProcessProposalHandler = CustomProcessProposalHandler[T](simapp) serverOptions.ExtendVoteHandler = CustomExtendVoteHandler[T]() // overwrite app mempool, using max-txs option @@ -123,7 +129,7 @@ func CustomExtendVoteHandler[T transaction.Tx]() handlers.ExtendVoteHandler { } func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] { - return func(ctx context.Context, app handlers.AppManager[T], codec transaction.Codec[T], req *v1.PrepareProposalRequest) ([]T, error) { + return func(ctx context.Context, app handlers.AppManager[T], codec transaction.Codec[T], req *v1.PrepareProposalRequest, chainID string) ([]T, error) { var txs []T for _, tx := range req.Txs { decTx, err := codec.Decode(tx) @@ -134,11 +140,11 @@ func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] { txs = append(txs, decTx) } - // Process vote extensions + // "Process" vote extensions (we'll just inject all votes) injectedTx := []byte{} - for _, vote := range req.LocalLastCommit.Votes { - // TODO: add signature verification - injectedTx = append(injectedTx, vote.VoteExtension...) + injectedTx, err := json.Marshal(req.LocalLastCommit) + if err != nil { + return nil, err } // put the injected tx into the first position @@ -147,3 +153,64 @@ func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] { return txs, nil } } + +func CustomProcessProposalHandler[T transaction.Tx](simapp *simapp.SimApp[T]) handlers.ProcessHandler[T] { + return func(ctx context.Context, am handlers.AppManager[T], c transaction.Codec[T], req *v1.ProcessProposalRequest, chainID string) error { + // Get all vote extensions from the first tx + + injectedTx := req.Txs[0] + var voteExts v1.ExtendedCommitInfo + if err := json.Unmarshal(injectedTx, &voteExts); err != nil { + return err + } + + // Get validators from the staking module + res, err := am.Query( + ctx, + 0, + &staking.QueryValidatorsRequest{}, + ) + if err != nil { + return err + } + + validatorsResponse := res.(*staking.QueryValidatorsResponse) + consAddrToPubkey := map[string]cryptotypes.PubKey{} + + for _, val := range validatorsResponse.GetValidators() { + cv := val.ConsensusPubkey.GetCachedValue() + if cv == nil { + return fmt.Errorf("public key cached value is nil") + } + + cpk, ok := cv.(cryptotypes.PubKey) + if ok { + consAddrToPubkey[string(cpk.Address().Bytes())] = cpk + } else { + return fmt.Errorf("invalid public key type") + } + } + + // First verify that the vote extensions injected by the proposer are correct + if err := cometbft.ValidateVoteExtensions( + ctx, + am, + chainID, + func(ctx context.Context, b []byte) (cryptotypes.PubKey, error) { + if _, ok := consAddrToPubkey[string(b)]; !ok { + return nil, fmt.Errorf("validator not found") + } + return consAddrToPubkey[string(b)], nil + }, + voteExts, + req.Height, + &req.ProposedLastCommit, + ); err != nil { + return err + } + + // TODO: do something with the vote extensions + + return nil + } +} From aaff4f5d25f6c211c6e0f454c78a7eb18d109f5a Mon Sep 17 00:00:00 2001 From: Facundo Date: Fri, 13 Dec 2024 18:13:03 +0100 Subject: [PATCH 04/14] cleanup --- simapp/v2/simdv2/cmd/commands.go | 2 +- simapp/v2/simdv2/cmd/config.go | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index 8704f11493d5..28586e32c2de 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -119,7 +119,7 @@ func InitRootCmd[T transaction.Tx]( &client.DefaultTxDecoder[T]{TxConfig: deps.TxConfig}, simApp.App.QueryHandlers(), simApp.App.SchemaDecoderResolver(), - initCometOptions[T](simApp), + initCometOptions[T](), deps.GlobalConfig, ) if err != nil { diff --git a/simapp/v2/simdv2/cmd/config.go b/simapp/v2/simdv2/cmd/config.go index da4e0c35161d..ed59bbe57041 100644 --- a/simapp/v2/simdv2/cmd/config.go +++ b/simapp/v2/simdv2/cmd/config.go @@ -15,7 +15,6 @@ import ( serverv2 "cosmossdk.io/server/v2" "cosmossdk.io/server/v2/cometbft" "cosmossdk.io/server/v2/cometbft/handlers" - "cosmossdk.io/simapp/v2" staking "cosmossdk.io/x/staking/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -100,10 +99,10 @@ func initCometConfig() cometbft.CfgOption { return cometbft.OverwriteDefaultConfigTomlConfig(cfg) } -func initCometOptions[T transaction.Tx](simapp *simapp.SimApp[T]) cometbft.ServerOptions[T] { +func initCometOptions[T transaction.Tx]() cometbft.ServerOptions[T] { serverOptions := cometbft.DefaultServerOptions[T]() serverOptions.PrepareProposalHandler = CustomPrepareProposal[T]() - serverOptions.ProcessProposalHandler = CustomProcessProposalHandler[T](simapp) + serverOptions.ProcessProposalHandler = CustomProcessProposalHandler[T]() serverOptions.ExtendVoteHandler = CustomExtendVoteHandler[T]() // overwrite app mempool, using max-txs option @@ -154,7 +153,7 @@ func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] { } } -func CustomProcessProposalHandler[T transaction.Tx](simapp *simapp.SimApp[T]) handlers.ProcessHandler[T] { +func CustomProcessProposalHandler[T transaction.Tx]() handlers.ProcessHandler[T] { return func(ctx context.Context, am handlers.AppManager[T], c transaction.Codec[T], req *v1.ProcessProposalRequest, chainID string) error { // Get all vote extensions from the first tx From 741cdeb8925b5796fc2795e3e242de48ee809137 Mon Sep 17 00:00:00 2001 From: Facundo Date: Mon, 16 Dec 2024 15:29:14 +0100 Subject: [PATCH 05/14] fix named params --- server/v2/cometbft/handlers/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/v2/cometbft/handlers/handlers.go b/server/v2/cometbft/handlers/handlers.go index e416c0de1104..b6cc213dc30f 100644 --- a/server/v2/cometbft/handlers/handlers.go +++ b/server/v2/cometbft/handlers/handlers.go @@ -13,11 +13,11 @@ import ( type ( // PrepareHandler passes in the list of Txs that are being proposed. The app can then do stateful operations // over the list of proposed transactions. It can return a modified list of txs to include in the proposal. - PrepareHandler[T transaction.Tx] func(context.Context, AppManager[T], transaction.Codec[T], *abci.PrepareProposalRequest, string) ([]T, error) + PrepareHandler[T transaction.Tx] func(ctx context.Context, app AppManager[T], cdc transaction.Codec[T], req *abci.PrepareProposalRequest, chainID string) ([]T, error) // ProcessHandler is a function that takes a list of transactions and returns a boolean and an error. // If the verification of a transaction fails, the boolean is false and the error is non-nil. - ProcessHandler[T transaction.Tx] func(context.Context, AppManager[T], transaction.Codec[T], *abci.ProcessProposalRequest, string) error + ProcessHandler[T transaction.Tx] func(ctx context.Context, app AppManager[T], cdc transaction.Codec[T], req *abci.ProcessProposalRequest, chainID string) error // VerifyVoteExtensionHandler is a function type that handles the verification of a vote extension request. // It takes a context, a store reader map, and a request to verify a vote extension. From ee4269725d0f734fd2ea7ba679431eed754d87ac Mon Sep 17 00:00:00 2001 From: Facundo Date: Mon, 16 Dec 2024 15:35:53 +0100 Subject: [PATCH 06/14] lint --- simapp/v2/simdv2/cmd/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/simapp/v2/simdv2/cmd/config.go b/simapp/v2/simdv2/cmd/config.go index ed59bbe57041..a5521a4821df 100644 --- a/simapp/v2/simdv2/cmd/config.go +++ b/simapp/v2/simdv2/cmd/config.go @@ -140,7 +140,6 @@ func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] { } // "Process" vote extensions (we'll just inject all votes) - injectedTx := []byte{} injectedTx, err := json.Marshal(req.LocalLastCommit) if err != nil { return nil, err From 97ec24b02e86ced8beae42a9bb5fddd531342a4f Mon Sep 17 00:00:00 2001 From: Facundo Date: Mon, 16 Dec 2024 15:59:08 +0100 Subject: [PATCH 07/14] lint --- baseapp/abci_utils.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/baseapp/abci_utils.go b/baseapp/abci_utils.go index bc0cb9daccdd..879e1e18d141 100644 --- a/baseapp/abci_utils.go +++ b/baseapp/abci_utils.go @@ -64,6 +64,11 @@ func ValidateVoteExtensions( if !extsEnabled { extsEnabled = cp.Abci != nil && currentHeight > cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0 } + + if !extsEnabled { + return nil + } + marshalDelimitedFn := func(msg proto.Message) ([]byte, error) { var buf bytes.Buffer if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil { From 0a13d5982bdd2e6a023c26c515619e174eab88b7 Mon Sep 17 00:00:00 2001 From: Facundo Date: Tue, 17 Dec 2024 17:00:54 +0100 Subject: [PATCH 08/14] fixing --- baseapp/abci_utils.go | 4 ---- server/v2/cometbft/utils.go | 8 ++++++-- simapp/v2/app.go | 1 - simapp/v2/simdv2/cmd/config.go | 3 +-- tests/integration/v2/gov/common_test.go | 1 - tests/integration/v2/gov/keeper/fixture_test.go | 1 - tests/integration/v2/services.go | 3 +-- 7 files changed, 8 insertions(+), 13 deletions(-) diff --git a/baseapp/abci_utils.go b/baseapp/abci_utils.go index 879e1e18d141..a0a7bf2949eb 100644 --- a/baseapp/abci_utils.go +++ b/baseapp/abci_utils.go @@ -65,10 +65,6 @@ func ValidateVoteExtensions( extsEnabled = cp.Abci != nil && currentHeight > cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0 } - if !extsEnabled { - return nil - } - marshalDelimitedFn := func(msg proto.Message) ([]byte, error) { var buf bytes.Buffer if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil { diff --git a/server/v2/cometbft/utils.go b/server/v2/cometbft/utils.go index 11db28f338b1..700a84b247ac 100644 --- a/server/v2/cometbft/utils.go +++ b/server/v2/cometbft/utils.go @@ -14,8 +14,6 @@ import ( abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1" cryptoenc "github.com/cometbft/cometbft/crypto/encoding" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" protoio "github.com/cosmos/gogoproto/io" "github.com/cosmos/gogoproto/proto" gogoproto "github.com/cosmos/gogoproto/proto" @@ -32,6 +30,8 @@ import ( "cosmossdk.io/server/v2/cometbft/handlers" "cosmossdk.io/x/consensus/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -512,6 +512,10 @@ func ValidateVoteExtensions[T transaction.Tx]( if !extsEnabled { extsEnabled = cp.Abci != nil && currentHeight > cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0 } + if !extsEnabled { + return nil + } + marshalDelimitedFn := func(msg proto.Message) ([]byte, error) { var buf bytes.Buffer if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil { diff --git a/simapp/v2/app.go b/simapp/v2/app.go index 6b4767b2760d..ec1232d9e98c 100644 --- a/simapp/v2/app.go +++ b/simapp/v2/app.go @@ -161,7 +161,6 @@ func NewSimApp[T transaction.Tx]( var err error app.App, err = appBuilder.Build(runtime.AppBuilderWithPreblocker( func(ctx context.Context, txs []T) error { - fmt.Println("Preblocker!!!", txs) return nil }, )) diff --git a/simapp/v2/simdv2/cmd/config.go b/simapp/v2/simdv2/cmd/config.go index a5521a4821df..27fe5f029a53 100644 --- a/simapp/v2/simdv2/cmd/config.go +++ b/simapp/v2/simdv2/cmd/config.go @@ -17,10 +17,9 @@ import ( "cosmossdk.io/server/v2/cometbft/handlers" staking "cosmossdk.io/x/staking/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - clientconfig "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) // initAppConfig helps to override default client config template and configs. diff --git a/tests/integration/v2/gov/common_test.go b/tests/integration/v2/gov/common_test.go index c630f8602f85..176a0589b0e0 100644 --- a/tests/integration/v2/gov/common_test.go +++ b/tests/integration/v2/gov/common_test.go @@ -203,5 +203,4 @@ func (s *suite) registerMsgRouterService(router *integration.RouterService) { } func (f *suite) registerQueryRouterService(router *integration.RouterService) { - } diff --git a/tests/integration/v2/gov/keeper/fixture_test.go b/tests/integration/v2/gov/keeper/fixture_test.go index d9ed3e807a5d..dc15c9e6737c 100644 --- a/tests/integration/v2/gov/keeper/fixture_test.go +++ b/tests/integration/v2/gov/keeper/fixture_test.go @@ -105,5 +105,4 @@ func (f *fixture) registerMsgRouterService(router *integration.RouterService) { } func (f *fixture) registerQueryRouterService(router *integration.RouterService) { - } diff --git a/tests/integration/v2/services.go b/tests/integration/v2/services.go index f608fd65fdb0..cf727b653433 100644 --- a/tests/integration/v2/services.go +++ b/tests/integration/v2/services.go @@ -160,8 +160,7 @@ var ( _ event.Manager = &eventManager{} ) -type eventService struct { -} +type eventService struct{} // EventManager implements event.Service. func (e *eventService) EventManager(ctx context.Context) event.Manager { From 7db5e2abff57c9822590f71a640767499587bba2 Mon Sep 17 00:00:00 2001 From: Facundo Date: Wed, 18 Dec 2024 09:40:52 +0100 Subject: [PATCH 09/14] attempt to debug test --- baseapp/abci_utils.go | 1 - x/auth/tx/gogotx.go | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/baseapp/abci_utils.go b/baseapp/abci_utils.go index a0a7bf2949eb..bc0cb9daccdd 100644 --- a/baseapp/abci_utils.go +++ b/baseapp/abci_utils.go @@ -64,7 +64,6 @@ func ValidateVoteExtensions( if !extsEnabled { extsEnabled = cp.Abci != nil && currentHeight > cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0 } - marshalDelimitedFn := func(msg proto.Message) ([]byte, error) { var buf bytes.Buffer if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil { diff --git a/x/auth/tx/gogotx.go b/x/auth/tx/gogotx.go index 1a386eb34ffd..5e4d72ea7da9 100644 --- a/x/auth/tx/gogotx.go +++ b/x/auth/tx/gogotx.go @@ -32,8 +32,9 @@ func newWrapperFromDecodedTx( addrCodec address.Codec, cdc codec.BinaryCodec, decodedTx *decode.DecodedTx, ) (*gogoTxWrapper, error) { var ( - fees = sdk.Coins{} // decodedTx.Tx.AuthInfo.Fee.Amount might be nil - err error + fees = make(sdk.Coins, len(decodedTx.Tx.AuthInfo.Fee.Amount)) + // fees = sdk.Coins{} // decodedTx.Tx.AuthInfo.Fee.Amount might be nil + err error ) for i, fee := range decodedTx.Tx.AuthInfo.Fee.Amount { amtInt, ok := math.NewIntFromString(fee.Amount) @@ -43,7 +44,11 @@ func newWrapperFromDecodedTx( if err = sdk.ValidateDenom(fee.Denom); err != nil { return nil, fmt.Errorf("invalid fee coin denom at index %d: %w", i, err) } - fees.Add(sdk.NewCoin(fee.Denom, amtInt)) + fees[i] = sdk.Coin{ + Denom: fee.Denom, + Amount: amtInt, + } + // fees.Add(sdk.NewCoin(fee.Denom, amtInt)) } if !fees.IsSorted() { return nil, fmt.Errorf("invalid not sorted tx fees: %s", fees.String()) From 6ee24dff171fa2a85fb0f0879bc807fd444d3247 Mon Sep 17 00:00:00 2001 From: Facundo Date: Wed, 18 Dec 2024 09:50:09 +0100 Subject: [PATCH 10/14] fix dumb error --- x/auth/tx/gogotx.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/x/auth/tx/gogotx.go b/x/auth/tx/gogotx.go index 5e4d72ea7da9..196b6a6827c7 100644 --- a/x/auth/tx/gogotx.go +++ b/x/auth/tx/gogotx.go @@ -32,9 +32,8 @@ func newWrapperFromDecodedTx( addrCodec address.Codec, cdc codec.BinaryCodec, decodedTx *decode.DecodedTx, ) (*gogoTxWrapper, error) { var ( - fees = make(sdk.Coins, len(decodedTx.Tx.AuthInfo.Fee.Amount)) - // fees = sdk.Coins{} // decodedTx.Tx.AuthInfo.Fee.Amount might be nil - err error + fees = sdk.Coins{} // decodedTx.Tx.AuthInfo.Fee.Amount might be nil + err error ) for i, fee := range decodedTx.Tx.AuthInfo.Fee.Amount { amtInt, ok := math.NewIntFromString(fee.Amount) @@ -44,11 +43,11 @@ func newWrapperFromDecodedTx( if err = sdk.ValidateDenom(fee.Denom); err != nil { return nil, fmt.Errorf("invalid fee coin denom at index %d: %w", i, err) } - fees[i] = sdk.Coin{ + + fees = fees.Add(sdk.Coin{ Denom: fee.Denom, Amount: amtInt, - } - // fees.Add(sdk.NewCoin(fee.Denom, amtInt)) + }) } if !fees.IsSorted() { return nil, fmt.Errorf("invalid not sorted tx fees: %s", fees.String()) From ad2801e0ff5f39c066e0d3425e176706e7ab719f Mon Sep 17 00:00:00 2001 From: Facundo Date: Wed, 18 Dec 2024 10:24:48 +0100 Subject: [PATCH 11/14] remove implementation --- simapp/v2/app.go | 7 +-- simapp/v2/go.mod | 3 - simapp/v2/simdv2/cmd/config.go | 109 ++------------------------------- 3 files changed, 5 insertions(+), 114 deletions(-) diff --git a/simapp/v2/app.go b/simapp/v2/app.go index ec1232d9e98c..601d641ad060 100644 --- a/simapp/v2/app.go +++ b/simapp/v2/app.go @@ -1,7 +1,6 @@ package simapp import ( - "context" _ "embed" "fmt" @@ -159,11 +158,7 @@ func NewSimApp[T transaction.Tx]( } var err error - app.App, err = appBuilder.Build(runtime.AppBuilderWithPreblocker( - func(ctx context.Context, txs []T) error { - return nil - }, - )) + app.App, err = appBuilder.Build() if err != nil { return nil, err } diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index f5e2884bc5b6..725f3fc4f7ed 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -280,7 +280,6 @@ replace ( cosmossdk.io/x/protocolpool => ../../x/protocolpool cosmossdk.io/x/slashing => ../../x/slashing cosmossdk.io/x/staking => ../../x/staking - cosmossdk.io/x/tx => ../../x/tx cosmossdk.io/x/auth => ../../x/auth cosmossdk.io/x/upgrade => ../../x/upgrade ) @@ -300,8 +299,6 @@ replace ( // server v2 integration replace ( - cosmossdk.io/core/testing => ../../core/testing - cosmossdk.io/core => ../../core cosmossdk.io/indexer/postgres => ../../indexer/postgres cosmossdk.io/runtime/v2 => ../../runtime/v2 cosmossdk.io/server/v2 => ../../server/v2 diff --git a/simapp/v2/simdv2/cmd/config.go b/simapp/v2/simdv2/cmd/config.go index 27fe5f029a53..ff3f7058a98e 100644 --- a/simapp/v2/simdv2/cmd/config.go +++ b/simapp/v2/simdv2/cmd/config.go @@ -1,25 +1,17 @@ package cmd import ( - "context" - "encoding/json" - "fmt" "strings" "time" - v1 "github.com/cometbft/cometbft/api/cometbft/abci/v1" cmtcfg "github.com/cometbft/cometbft/config" - "cosmossdk.io/core/store" "cosmossdk.io/core/transaction" serverv2 "cosmossdk.io/server/v2" "cosmossdk.io/server/v2/cometbft" - "cosmossdk.io/server/v2/cometbft/handlers" - staking "cosmossdk.io/x/staking/types" clientconfig "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/crypto/keyring" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) // initAppConfig helps to override default client config template and configs. @@ -100,9 +92,10 @@ func initCometConfig() cometbft.CfgOption { func initCometOptions[T transaction.Tx]() cometbft.ServerOptions[T] { serverOptions := cometbft.DefaultServerOptions[T]() - serverOptions.PrepareProposalHandler = CustomPrepareProposal[T]() - serverOptions.ProcessProposalHandler = CustomProcessProposalHandler[T]() - serverOptions.ExtendVoteHandler = CustomExtendVoteHandler[T]() + // Implement custom handlers (e.g. for Vote Extensions) + // serverOptions.PrepareProposalHandler = CustomPrepareProposal[T]() + // serverOptions.ProcessProposalHandler = CustomProcessProposalHandler[T]() + // serverOptions.ExtendVoteHandler = CustomExtendVoteHandler[T]() // overwrite app mempool, using max-txs option // serverOptions.Mempool = func(cfg map[string]any) mempool.Mempool[T] { @@ -117,97 +110,3 @@ func initCometOptions[T transaction.Tx]() cometbft.ServerOptions[T] { return serverOptions } - -func CustomExtendVoteHandler[T transaction.Tx]() handlers.ExtendVoteHandler { - return func(ctx context.Context, rm store.ReaderMap, evr *v1.ExtendVoteRequest) (*v1.ExtendVoteResponse, error) { - return &v1.ExtendVoteResponse{ - VoteExtension: []byte("BTC=1234567.89;height=" + fmt.Sprint(evr.Height)), - }, nil - } -} - -func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] { - return func(ctx context.Context, app handlers.AppManager[T], codec transaction.Codec[T], req *v1.PrepareProposalRequest, chainID string) ([]T, error) { - var txs []T - for _, tx := range req.Txs { - decTx, err := codec.Decode(tx) - if err != nil { - continue - } - - txs = append(txs, decTx) - } - - // "Process" vote extensions (we'll just inject all votes) - injectedTx, err := json.Marshal(req.LocalLastCommit) - if err != nil { - return nil, err - } - - // put the injected tx into the first position - txs = append([]T{cometbft.RawTx(injectedTx).(T)}, txs...) - - return txs, nil - } -} - -func CustomProcessProposalHandler[T transaction.Tx]() handlers.ProcessHandler[T] { - return func(ctx context.Context, am handlers.AppManager[T], c transaction.Codec[T], req *v1.ProcessProposalRequest, chainID string) error { - // Get all vote extensions from the first tx - - injectedTx := req.Txs[0] - var voteExts v1.ExtendedCommitInfo - if err := json.Unmarshal(injectedTx, &voteExts); err != nil { - return err - } - - // Get validators from the staking module - res, err := am.Query( - ctx, - 0, - &staking.QueryValidatorsRequest{}, - ) - if err != nil { - return err - } - - validatorsResponse := res.(*staking.QueryValidatorsResponse) - consAddrToPubkey := map[string]cryptotypes.PubKey{} - - for _, val := range validatorsResponse.GetValidators() { - cv := val.ConsensusPubkey.GetCachedValue() - if cv == nil { - return fmt.Errorf("public key cached value is nil") - } - - cpk, ok := cv.(cryptotypes.PubKey) - if ok { - consAddrToPubkey[string(cpk.Address().Bytes())] = cpk - } else { - return fmt.Errorf("invalid public key type") - } - } - - // First verify that the vote extensions injected by the proposer are correct - if err := cometbft.ValidateVoteExtensions( - ctx, - am, - chainID, - func(ctx context.Context, b []byte) (cryptotypes.PubKey, error) { - if _, ok := consAddrToPubkey[string(b)]; !ok { - return nil, fmt.Errorf("validator not found") - } - return consAddrToPubkey[string(b)], nil - }, - voteExts, - req.Height, - &req.ProposedLastCommit, - ); err != nil { - return err - } - - // TODO: do something with the vote extensions - - return nil - } -} From 28beb94bc056daa0a3dc90de98f62fc6ea0f0fa9 Mon Sep 17 00:00:00 2001 From: Facundo Date: Wed, 18 Dec 2024 10:42:00 +0100 Subject: [PATCH 12/14] fix --- simapp/v2/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index 725f3fc4f7ed..c81dc9007548 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -264,6 +264,7 @@ replace ( cosmossdk.io/x/accounts/defaults/base => ../../x/accounts/defaults/base cosmossdk.io/x/accounts/defaults/lockup => ../../x/accounts/defaults/lockup cosmossdk.io/x/accounts/defaults/multisig => ../../x/accounts/defaults/multisig + cosmossdk.io/x/auth => ../../x/auth cosmossdk.io/x/authz => ../../x/authz cosmossdk.io/x/bank => ../../x/bank cosmossdk.io/x/circuit => ../../x/circuit @@ -280,7 +281,6 @@ replace ( cosmossdk.io/x/protocolpool => ../../x/protocolpool cosmossdk.io/x/slashing => ../../x/slashing cosmossdk.io/x/staking => ../../x/staking - cosmossdk.io/x/auth => ../../x/auth cosmossdk.io/x/upgrade => ../../x/upgrade ) From f620313d25c9f2aa641fe67847368ef770a36e98 Mon Sep 17 00:00:00 2001 From: Facundo Date: Thu, 19 Dec 2024 13:17:15 +0100 Subject: [PATCH 13/14] suggestions --- runtime/v2/builder.go | 23 ++++++++++++++++------- simapp/v2/go.mod | 1 - 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index e04424a65c5f..62b997e20a6c 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -30,7 +30,7 @@ type AppBuilder[T transaction.Tx] struct { branch func(state store.ReaderMap) store.WriterMap txValidator func(ctx context.Context, tx T) error postTxExec func(ctx context.Context, tx T, success bool) error - preblocker func(ctx context.Context, txs []T) error + preblocker func(ctx context.Context, txs []T, mmPreblocker func()) error } // RegisterModules registers the provided modules with the module manager. @@ -97,12 +97,17 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { endBlocker, valUpdate := a.app.moduleManager.EndBlock() preblockerFn := func(ctx context.Context, txs []T) error { - if err := a.app.moduleManager.PreBlocker()(ctx, txs); err != nil { - return err - } - if a.preblocker != nil { - return a.preblocker(ctx, txs) + return a.preblocker(ctx, txs, func() { + if err := a.app.moduleManager.PreBlocker()(ctx, txs); err != nil { + panic(err) + } + }) + } else { + // if there is no preblocker set, call the module manager's preblocker directly + if err := a.app.moduleManager.PreBlocker()(ctx, txs); err != nil { + return err + } } return nil @@ -233,9 +238,13 @@ func AppBuilderWithPostTxExec[T transaction.Tx]( } } +// AppBuilderWithPreblocker sets logic that will be executed before each block. +// mmPreblocker can be used to call module manager's preblocker, so that it can be +// called before or after depending on the app's logic. +// This is especially useful when implementing vote extensions. func AppBuilderWithPreblocker[T transaction.Tx]( preblocker func( - ctx context.Context, txs []T, + ctx context.Context, txs []T, mmPreblocker func(), ) error, ) AppBuilderOption[T] { return func(a *AppBuilder[T]) { diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index c81dc9007548..7c56ddde9e97 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -264,7 +264,6 @@ replace ( cosmossdk.io/x/accounts/defaults/base => ../../x/accounts/defaults/base cosmossdk.io/x/accounts/defaults/lockup => ../../x/accounts/defaults/lockup cosmossdk.io/x/accounts/defaults/multisig => ../../x/accounts/defaults/multisig - cosmossdk.io/x/auth => ../../x/auth cosmossdk.io/x/authz => ../../x/authz cosmossdk.io/x/bank => ../../x/bank cosmossdk.io/x/circuit => ../../x/circuit From ca5d8f46f5e43bce44810c60c38ec11916a5a6a7 Mon Sep 17 00:00:00 2001 From: Facundo Date: Thu, 19 Dec 2024 13:25:33 +0100 Subject: [PATCH 14/14] fixes --- runtime/v2/builder.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index 62b997e20a6c..de9535dca257 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -30,7 +30,7 @@ type AppBuilder[T transaction.Tx] struct { branch func(state store.ReaderMap) store.WriterMap txValidator func(ctx context.Context, tx T) error postTxExec func(ctx context.Context, tx T, success bool) error - preblocker func(ctx context.Context, txs []T, mmPreblocker func()) error + preblocker func(ctx context.Context, txs []T, mmPreblocker func() error) error } // RegisterModules registers the provided modules with the module manager. @@ -98,19 +98,13 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { preblockerFn := func(ctx context.Context, txs []T) error { if a.preblocker != nil { - return a.preblocker(ctx, txs, func() { - if err := a.app.moduleManager.PreBlocker()(ctx, txs); err != nil { - panic(err) - } + return a.preblocker(ctx, txs, func() error { + return a.app.moduleManager.PreBlocker()(ctx, txs) }) - } else { - // if there is no preblocker set, call the module manager's preblocker directly - if err := a.app.moduleManager.PreBlocker()(ctx, txs); err != nil { - return err - } } - return nil + // if there is no preblocker set, call the module manager's preblocker directly + return a.app.moduleManager.PreBlocker()(ctx, txs) } stf, err := stf.New[T]( @@ -244,7 +238,7 @@ func AppBuilderWithPostTxExec[T transaction.Tx]( // This is especially useful when implementing vote extensions. func AppBuilderWithPreblocker[T transaction.Tx]( preblocker func( - ctx context.Context, txs []T, mmPreblocker func(), + ctx context.Context, txs []T, mmPreblocker func() error, ) error, ) AppBuilderOption[T] { return func(a *AppBuilder[T]) {