From 5b4f946325522defebbc6c0be9ec6a59fdd7e02b Mon Sep 17 00:00:00 2001 From: huangyi Date: Thu, 26 Sep 2024 17:48:26 +0800 Subject: [PATCH] temp --- app/app.go | 45 ++++++++- app/proposal.go | 171 +++++++++++++++++++++++++++++++++ integration_tests/cosmoscli.py | 4 +- 3 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 app/proposal.go diff --git a/app/app.go b/app/app.go index f4148bffda..fce3ae9b68 100644 --- a/app/app.go +++ b/app/app.go @@ -15,6 +15,8 @@ import ( stdruntime "runtime" "sort" + "filippo.io/age" + abci "github.com/cometbft/cometbft/abci/types" tmos "github.com/cometbft/cometbft/libs/os" dbm "github.com/cosmos/cosmos-db" @@ -159,6 +161,7 @@ import ( cronosprecompiles "github.com/crypto-org-chain/cronos/v2/x/cronos/keeper/precompiles" "github.com/crypto-org-chain/cronos/v2/x/cronos/middleware" cronostypes "github.com/crypto-org-chain/cronos/v2/x/cronos/types" + e2eekeyring "github.com/crypto-org-chain/cronos/v2/x/e2ee/keyring" e2ee "github.com/crypto-org-chain/cronos/v2/x/e2ee" e2eekeeper "github.com/crypto-org-chain/cronos/v2/x/e2ee/keeper" @@ -360,23 +363,57 @@ func New( cdc := encodingConfig.Amino txConfig := encodingConfig.TxConfig interfaceRegistry := encodingConfig.InterfaceRegistry - + txDecoder := txConfig.TxDecoder() eip712.SetEncodingConfig(encodingConfig) + homePath := cast.ToString(appOpts.Get(flags.FlagHome)) + var identity age.Identity + { + if cast.ToString(appOpts.Get("mode")) == "validator" { + krBackend := cast.ToString(appOpts.Get(flags.FlagKeyringBackend)) + kr, err := e2eekeyring.New("cronosd", krBackend, homePath, os.Stdin) + if err != nil { + panic(err) + } + bz, err := kr.Get(e2eetypes.DefaultKeyringName) + if err != nil { + logger.Error("e2ee identity for validator not found", "error", err) + } else { + identity, err = age.ParseX25519Identity(string(bz)) + if err != nil { + panic(err) + } + } + } + } + + var mpool mempool.Mempool if maxTxs := cast.ToInt(appOpts.Get(server.FlagMempoolMaxTxs)); maxTxs >= 0 { // NOTE we use custom transaction decoder that supports the sdk.Tx interface instead of sdk.StdTx // Setup Mempool and Proposal Handlers - mempool := mempool.NewPriorityMempool(mempool.PriorityNonceMempoolConfig[int64]{ + mpool = mempool.NewPriorityMempool(mempool.PriorityNonceMempoolConfig[int64]{ TxPriority: mempool.NewDefaultTxPriority(), SignerExtractor: evmapp.NewEthSignerExtractionAdapter(mempool.NewDefaultSignerExtractionAdapter()), MaxTx: maxTxs, }) - baseAppOptions = append(baseAppOptions, baseapp.SetMempool(mempool)) + } else { + mpool = mempool.NoOpMempool{} } + blockProposalHandler := NewProposalHandler(txDecoder, identity) + baseAppOptions = append(baseAppOptions, func(app *baseapp.BaseApp) { + app.SetMempool(mpool) + + // Re-use the default prepare proposal handler, extend the transaction validation logic + defaultProposalHandler := baseapp.NewDefaultProposalHandler(mpool, app) + defaultProposalHandler.SetTxSelector(NewExtTxSelector( + baseapp.NewDefaultTxSelector(), + txDecoder, + blockProposalHandler.ValidateTransaction, + )) + }) blockSTMEnabled := cast.ToString(appOpts.Get(srvflags.EVMBlockExecutor)) == "block-stm" - homePath := cast.ToString(appOpts.Get(flags.FlagHome)) var cacheSize int if !blockSTMEnabled { // only enable memiavl cache if block-stm is not enabled, because it's not concurrency-safe. diff --git a/app/proposal.go b/app/proposal.go new file mode 100644 index 0000000000..2c439fb548 --- /dev/null +++ b/app/proposal.go @@ -0,0 +1,171 @@ +package app + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + + "filippo.io/age" + + "cosmossdk.io/core/address" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +type BlockList struct { + Addresses []string `mapstructure:"addresses"` +} + +var _ baseapp.TxSelector = &ExtTxSelector{} + +// ExtTxSelector extends a baseapp.TxSelector with extra tx validation method +type ExtTxSelector struct { + baseapp.TxSelector + TxDecoder sdk.TxDecoder + ValidateTx func(sdk.Tx) error +} + +func NewExtTxSelector(parent baseapp.TxSelector, txDecoder sdk.TxDecoder, validateTx func(sdk.Tx) error) *ExtTxSelector { + return &ExtTxSelector{ + TxSelector: parent, + TxDecoder: txDecoder, + ValidateTx: validateTx, + } +} + +func (ts *ExtTxSelector) SelectTxForProposal(ctx context.Context, maxTxBytes, maxBlockGas uint64, memTx sdk.Tx, txBz []byte, gasWanted uint64) bool { + var err error + if memTx == nil { + memTx, err = ts.TxDecoder(txBz) + if err != nil { + return false + } + } + + if err := ts.ValidateTx(memTx); err != nil { + return false + } + + // don't pass `memTx` to parent selector so it don't check tx gas wanted against block gas limit, + // it conflicts with the max-tx-gas-wanted logic. + return ts.TxSelector.SelectTxForProposal(ctx, maxTxBytes, maxBlockGas, nil, txBz, gasWanted) +} + +type ProposalHandler struct { + TxDecoder sdk.TxDecoder + // Identity is nil if it's not a validator node + Identity age.Identity + blocklist map[string]struct{} + lastBlockList []byte + addressCodec address.Codec +} + +func NewProposalHandler(txDecoder sdk.TxDecoder, identity age.Identity) *ProposalHandler { + return &ProposalHandler{ + TxDecoder: txDecoder, + Identity: identity, + blocklist: make(map[string]struct{}), + } +} + +// SetBlockList don't fail if the identity is not set or the block list is empty. +func (h *ProposalHandler) SetBlockList(blob []byte) error { + if h.Identity == nil { + return nil + } + + if bytes.Equal(h.lastBlockList, blob) { + return nil + } + h.lastBlockList = make([]byte, len(blob)) + copy(h.lastBlockList, blob) + + if len(blob) == 0 { + h.blocklist = make(map[string]struct{}) + return nil + } + + reader, err := age.Decrypt(bytes.NewBuffer(blob), h.Identity) + if err != nil { + return err + } + + data, err := io.ReadAll(reader) + if err != nil { + return err + } + + var blocklist BlockList + if err := json.Unmarshal(data, &blocklist); err != nil { + return err + } + + // convert to map + m := make(map[string]struct{}, len(blocklist.Addresses)) + for _, s := range blocklist.Addresses { + addr, err := h.addressCodec.StringToBytes(s) + if err != nil { + return fmt.Errorf("invalid bech32 address: %s, err: %w", s, err) + } + encoded, err := h.addressCodec.BytesToString(addr) + if err != nil { + return fmt.Errorf("invalid bech32 address: %s, err: %w", s, err) + } + m[encoded] = struct{}{} + } + + h.blocklist = m + return nil +} + +func (h *ProposalHandler) ValidateTransaction(tx sdk.Tx) error { + sigTx, ok := tx.(signing.SigVerifiableTx) + if !ok { + return fmt.Errorf("tx of type %T does not implement SigVerifiableTx", tx) + } + + signers, err := sigTx.GetSigners() + if err != nil { + return err + } + for _, signer := range signers { + encoded, err := h.addressCodec.BytesToString(signer) + if err != nil { + return fmt.Errorf("invalid bech32 address: %s, err: %w", signer, err) + } + if _, ok := h.blocklist[encoded]; ok { + return fmt.Errorf("signer is blocked: %s", encoded) + } + } + return nil +} + +func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { + return func(ctx sdk.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { + for _, txBz := range req.Txs { + memTx, err := h.TxDecoder(txBz) + if err != nil { + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } + + if err := h.ValidateTransaction(memTx); err != nil { + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } + } + + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil + } +} + +// noneIdentity is a dummy identity which postpone the failure to the decryption time +type noneIdentity struct{} + +var _ age.Identity = noneIdentity{} + +func (noneIdentity) Unwrap([]*age.Stanza) ([]byte, error) { + return nil, age.ErrIncorrectIdentity +} diff --git a/integration_tests/cosmoscli.py b/integration_tests/cosmoscli.py index 8d4ad25f1c..75868b5f40 100644 --- a/integration_tests/cosmoscli.py +++ b/integration_tests/cosmoscli.py @@ -257,7 +257,7 @@ def query_account(self, addr, **kwargs): home=self.data_dir, **kwargs, ) - ) + )["account"] def distribution_commission(self, addr): coin = json.loads( @@ -371,8 +371,8 @@ def transfer( to, coins, generate_only=False, - event_query_tx=True, fees=None, + event_query_tx=True, **kwargs, ): kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)