Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements
* [#3818](https://github.com/sei-protocol/sei-chain/pull/3818) feat(evmrpc): extend HTTP admission control (`max_request_body_bytes`, `max_concurrent_request_bytes`, `ws_admission_timeout`) to the WebSocket plane (:8546). WS oversize frames close with WebSocket close code 1009; budget-wait timeouts return JSON-RPC error `-32005` before the connection closes. `evmrpc_requests_rejected_total` gains a `protocol` label (`http` / `ws`).
* [#3984](https://github.com/sei-protocol/sei-chain/pull/3984) feat(query): origin-aware pagination limits for ABCI queries. Untrusted callers on the ABCI/gRPC query path get configurable `max-limit`, `max-offset`, and flat `max-iterations` (defaults: 1000 / 10000 / 11000); requests above the caps are rejected upfront, and an exhausted iteration budget returns a partial page with `next_key` instead of failing. Trusted origins (new `[query] trusted-cidrs`) and the `[query] disable-limits` kill switch bypass the caps; the consensus/EVM precompile path is unaffected.
* [#3990](https://github.com/sei-protocol/sei-chain/pull/3990) Freeze mode is limited to full nodes and disables transaction and evidence submission, mempool gossip, and state sync from startup while preserving query RPC and mempool-backed reads. Frozen and Autobahn nodes no longer advertise the unused mempool P2P channel.

### Upgrade guide
* **IBC transfer removal.** Removes ICS-20 execution, module APIs, CLI commands, CosmWasm transfer messages, transfer codecs, and transfer keeper integration, including the IBC EVM precompile's keeper injection. The transfer store and module account remain materialized for state compatibility. Transfer queries, historical transfer transaction decoding, and pre-v6.7 IBC precompile tracing must be served by v6.6 freeze nodes; v6.7 nodes do not provide them.
Expand Down
5 changes: 3 additions & 2 deletions sei-cosmos/server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ type BaseConfig struct {
// Note: Commitment of state will be attempted on the corresponding block.
HaltHeight uint64 `mapstructure:"halt-height"`

// FreezeHeight contains a non-zero block height at which the node stops
// before executing the block while continuing to serve RPC.
// FreezeHeight contains the first block height a full node must not execute.
// Query RPC remains available, while transaction and evidence submission,
// mempool gossip, and state sync are disabled from startup.
FreezeHeight uint64 `mapstructure:"freeze-height"`

// HaltTime contains a non-zero minimum block time (in Unix seconds) at which
Expand Down
5 changes: 3 additions & 2 deletions sei-cosmos/server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ occ-enabled = {{ .BaseConfig.OccEnabled }}
# Note: Commitment of state will be attempted on the corresponding block.
halt-height = {{ .BaseConfig.HaltHeight }}

# FreezeHeight contains a non-zero block height at which the node stops before
# executing the block while continuing to serve RPC.
# FreezeHeight contains the first block height a full node must not execute.
# Query RPC remains available, while transaction and evidence submission,
# mempool gossip, and state sync are disabled from startup.
freeze-height = {{ .BaseConfig.FreezeHeight }}

# HaltTime contains a non-zero minimum block time (in Unix seconds) at which
Expand Down
7 changes: 4 additions & 3 deletions sei-cosmos/server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ the ABCI Commit phase, the node will check if the current block height is greate
the halt-height or if the current block time is greater than or equal to the halt-time. If so, the
node will attempt to gracefully shutdown and the block will not be committed. In addition, the node
will not be able to commit subsequent blocks.
The '--freeze-height' flag instead keeps the process and RPC servers running while preventing block
sync and consensus from executing the block at the configured height or advancing beyond it.
The '--freeze-height' flag puts a full node in read-only freeze mode. Query RPC remains available,
but transaction and evidence submission, mempool gossip, and state sync are disabled from startup.
Block sync and consensus stop before executing the configured height.
For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag
which accepts a path for the resulting pprof file.
The node may be started in a 'query only' mode where only the gRPC and JSON HTTP
Expand Down Expand Up @@ -211,7 +212,7 @@ func addStartNodeFlags(cmd *cobra.Command, defaultNodeHome string) {
cmd.Flags().String(FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)")
cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary")
cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Uint64(FlagFreezeHeight, 0, "Block height to stop before executing while continuing to serve RPC")
cmd.Flags().Uint64(FlagFreezeHeight, 0, "Block height at which a full node stops executing while continuing to serve query RPC")
cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching")
cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file")
Expand Down
18 changes: 17 additions & 1 deletion sei-tendermint/internal/rpc/core/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
Expand Down Expand Up @@ -48,7 +49,12 @@ const (
genesisChunkSize = 16 * 1024 * 1024 // 16
)

var logger = seilog.NewLogger("tendermint", "internal", "rpc", "core")
var (
logger = seilog.NewLogger("tendermint", "internal", "rpc", "core")

// ErrReadOnly indicates that an RPC operation would mutate a read-only node.
ErrReadOnly = errors.New("RPC writes are disabled in freeze mode")
)

//----------------------------------------------
// These interfaces are used by RPC and must be thread safe
Expand Down Expand Up @@ -93,6 +99,9 @@ type Environment struct {

Config config.RPCConfig

// ReadOnly rejects RPC writes while preserving query and mempool reads.
ReadOnly bool

// cache of chunked genesis data.
genChunks []string
}
Expand Down Expand Up @@ -230,6 +239,13 @@ func (env *Environment) requireMempool() (*mempool.TxMempool, error) {
return nil, fmt.Errorf("mempool is not available")
}

func (env *Environment) requireWritable() error {
if env.ReadOnly {
return ErrReadOnly
}
return nil
}

func (env *Environment) requireEventLog() (*eventlog.Log, error) {
if lg, ok := env.EventLog.Get(); ok {
return lg, nil
Expand Down
3 changes: 3 additions & 0 deletions sei-tendermint/internal/rpc/core/evidence.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
// BroadcastEvidence broadcasts evidence of the misbehavior.
// More: https://docs.tendermint.com/master/rpc/#/Evidence/broadcast_evidence
func (env *Environment) BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) {
if err := env.requireWritable(); err != nil {
return nil, err
}
if req.Evidence == nil {
return nil, fmt.Errorf("%w: no evidence was provided", coretypes.ErrInvalidRequest)
}
Expand Down
9 changes: 9 additions & 0 deletions sei-tendermint/internal/rpc/core/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ func (env *Environment) EvmTxByHash(hash common.Hash) (types.Tx, bool) {
// https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async
// Deprecated and should be removed in 0.37
func (env *Environment) BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
if err := env.requireWritable(); err != nil {
return nil, err
}
if giga, ok := env.gigaRouter().Get(); ok {
v, ok := giga.Mempool().Get()
if !ok {
Expand All @@ -76,6 +79,9 @@ func (env *Environment) BroadcastTxSync(ctx context.Context, req *coretypes.Requ
// DeliverTx result.
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync
func (env *Environment) BroadcastTx(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
if err := env.requireWritable(); err != nil {
return nil, err
}
if giga, ok := env.gigaRouter().Get(); ok {
v, ok := giga.Mempool().Get()
if !ok {
Expand Down Expand Up @@ -113,6 +119,9 @@ func (env *Environment) BroadcastTx(ctx context.Context, req *coretypes.RequestB
// BroadcastTxCommit returns with the responses from CheckTx and DeliverTx.
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit
func (env *Environment) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) {
if err := env.requireWritable(); err != nil {
return nil, err
}
if timeout := env.Config.TimeoutBroadcastTxCommit; timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, timeout)
Expand Down
102 changes: 102 additions & 0 deletions sei-tendermint/node/freeze_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package node

import (
"errors"
"fmt"
"math"
"slices"
"testing"

"github.com/sei-protocol/sei-chain/sei-tendermint/config"
mempoolreactor "github.com/sei-protocol/sei-chain/sei-tendermint/internal/mempool/reactor"
rpccore "github.com/sei-protocol/sei-chain/sei-tendermint/internal/rpc/core"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/tcp"
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
"github.com/sei-protocol/sei-chain/sei-tendermint/types"
)

func TestValidateFreezeHeight(t *testing.T) {
Expand Down Expand Up @@ -39,3 +49,95 @@ func TestWithFreezeHeight(t *testing.T) {
t.Fatalf("freeze height = %d, want %d", got, height)
}
}

func TestValidateFreezeMode(t *testing.T) {
for _, tc := range []struct {
name string
mode string
freezeHeight uint64
wantErr bool
}{
{name: "disabled validator", mode: config.ModeValidator},
{name: "full node", mode: config.ModeFull, freezeHeight: 10},
{name: "validator", mode: config.ModeValidator, freezeHeight: 10, wantErr: true},
{name: "seed", mode: config.ModeSeed, freezeHeight: 10, wantErr: true},
{name: "unknown mode handled by node mode validation", mode: "unknown", freezeHeight: 10},
} {
t.Run(tc.name, func(t *testing.T) {
err := validateFreezeMode(tc.mode, tc.freezeHeight)
if (err != nil) != tc.wantErr {
t.Fatalf("validateFreezeMode() error = %v, wantErr %t", err, tc.wantErr)
}
})
}
}

func TestFreezeModeDisablesMempoolTraffic(t *testing.T) {
cfg, err := config.ResetTestRoot(t.TempDir(), "freeze_mempool_test")
if err != nil {
t.Fatal(err)
}
cfg.Mode = config.ModeFull
cfg.RPC.ListenAddress = fmt.Sprintf("tcp://%s", tcp.TestReserveAddr())
nodeService, err := newLocalNodeService(t.Context(), cfg, WithFreezeHeight(2))
if err != nil {
t.Fatal(err)
}
node := nodeService.(*nodeImpl)

if !node.mempool.IsPresent() {
t.Fatal("internal mempool is unavailable")
}
if !node.rpcEnv.Mempool.IsPresent() {
t.Fatal("RPC mempool reads are unavailable in freeze mode")
}
if !node.rpcEnv.ReadOnly {
t.Fatal("RPC writes are enabled in freeze mode")
}
txRequest := &coretypes.RequestBroadcastTx{Tx: types.Tx{1}}
for name, broadcast := range map[string]func() error{
"async": func() error {
_, err := node.rpcEnv.BroadcastTxAsync(t.Context(), txRequest)
return err
},
"sync": func() error {
_, err := node.rpcEnv.BroadcastTxSync(t.Context(), txRequest)
return err
},
"default": func() error {
_, err := node.rpcEnv.BroadcastTx(t.Context(), txRequest)
return err
},
"commit": func() error {
_, err := node.rpcEnv.BroadcastTxCommit(t.Context(), txRequest)
return err
},
} {
if err := broadcast(); !errors.Is(err, rpccore.ErrReadOnly) {
t.Fatalf("%s RPC transaction broadcast error = %v, want ErrReadOnly", name, err)
}
}
if _, err := node.rpcEnv.BroadcastEvidence(t.Context(), &coretypes.RequestBroadcastEvidence{}); !errors.Is(err, rpccore.ErrReadOnly) {
t.Fatalf("RPC evidence broadcast error = %v, want ErrReadOnly", err)
}
if pending, err := node.rpcEnv.UnconfirmedTxs(t.Context(), &coretypes.RequestUnconfirmedTxs{}); err != nil {
t.Fatalf("reading unconfirmed transactions: %v", err)
} else if pending.Total != 0 {
t.Fatalf("unconfirmed transaction total = %d, want 0", pending.Total)
}
for _, nodeService := range node.services {
if _, ok := nodeService.(*mempoolreactor.Reactor); ok {
t.Fatal("mempool reactor is enabled in freeze mode")
}
}
if err := node.Start(t.Context()); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
node.Stop()
node.Wait()
})
if slices.Contains(node.NodeInfo().Channels, byte(mempoolreactor.MempoolChannel)) {
t.Fatal("mempool channel is advertised in freeze mode")
}
}
42 changes: 24 additions & 18 deletions sei-tendermint/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,19 @@ type nodeImpl struct {
nodeKey types.NodeKey // our node privkey

// services
eventSinks []indexer.EventSink
initialState sm.State
stateStore sm.Store
blockStore *store.BlockStore // store the blockchain to disk
mempool utils.Option[*mempool.TxMempool]
evPool utils.Option[*evidence.Pool]
indexerService *indexer.Service
services []service.Service
rpcListeners []net.Listener // rpc servers
shutdownOps closer
rpcEnv *rpccore.Environment
prometheusSrv utils.Option[*http.Server]
eventSinks []indexer.EventSink
initialState sm.State
stateStore sm.Store
blockStore *store.BlockStore // store the blockchain to disk
mempool utils.Option[*mempool.TxMempool]
mempoolP2PEnabled bool
evPool utils.Option[*evidence.Pool]
indexerService *indexer.Service
services []service.Service
rpcListeners []net.Listener // rpc servers
shutdownOps closer
rpcEnv *rpccore.Environment
prometheusSrv utils.Option[*http.Server]
}

// makeNode returns a new, ready to go, Tendermint Node.
Expand Down Expand Up @@ -273,6 +274,8 @@ func makeNode(
EventBus: eventBus,
EventLog: eventLogOpt,
Config: *cfg.RPC,

ReadOnly: opts.freezeHeight > 0,
},
}

Expand Down Expand Up @@ -333,12 +336,15 @@ func makeNode(
mp := mempool.NewTxMempool(cfg.Mempool.ToMempoolConfig(), proxyApp, sm.TxConstraintsFetcherFromStore(stateStore))
node.mempool = utils.Some(mp)
node.rpcEnv.Mempool = utils.Some(mp)
mpReactor, err := mempoolreactor.NewReactor(cfg.Mempool, mp, router)
if err != nil {
return nil, fmt.Errorf("mempoolreactor.NewReactor(): %w", err)
if opts.freezeHeight == 0 {
Comment thread
seidroid[bot] marked this conversation as resolved.
Comment thread
seidroid[bot] marked this conversation as resolved.
mpReactor, err := mempoolreactor.NewReactor(cfg.Mempool, mp, router)
if err != nil {
return nil, fmt.Errorf("mempoolreactor.NewReactor(): %w", err)
}
mpReactor.MarkReadyToStart()
node.services = append(node.services, mpReactor)
Comment thread
cursor[bot] marked this conversation as resolved.
node.mempoolP2PEnabled = true
}
mpReactor.MarkReadyToStart()
node.services = append(node.services, mpReactor)

// make block executor for consensus and blockchain reactors to execute blocks
blockExec := sm.NewBlockExecutor(
Expand Down Expand Up @@ -562,7 +568,7 @@ func (n *nodeImpl) OnStart(ctx context.Context) (err error) {

// TODO: Fetch and provide real options and do proper p2p bootstrapping.
// TODO: Use a persistent peer database.
n.nodeInfo, err = makeNodeInfo(n.config, n.nodeKey, n.eventSinks, n.genesisDoc, state.Version.Consensus)
n.nodeInfo, err = makeNodeInfo(n.config, n.nodeKey, n.eventSinks, n.genesisDoc, state.Version.Consensus, n.mempoolP2PEnabled)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion sei-tendermint/node/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import (
"github.com/sei-protocol/sei-chain/sei-tendermint/types"
)

func newLocalNodeService(ctx context.Context, cfg *config.Config) (service.Service, error) {
func newLocalNodeService(ctx context.Context, cfg *config.Config, nodeOptions ...Option) (service.Service, error) {
app := kvstore.NewApplication()
app.SetValidators(utils.OrPanic1(types.GenesisDocFromFile(cfg.GenesisFile())).ValidatorUpdates())
return New(
Expand All @@ -50,6 +50,7 @@ func newLocalNodeService(ctx context.Context, cfg *config.Config) (service.Servi
nil,
nil,
types.DefaultConsensusPolicy(),
nodeOptions...,
)
}

Expand Down
19 changes: 16 additions & 3 deletions sei-tendermint/node/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func New(
if err := validateNodeSetupConfig(conf); err != nil {
return nil, err
}
opts := resolveOptions(nodeOptions...)
if err := validateFreezeMode(conf.Mode, opts.freezeHeight); err != nil {
return nil, err
}
app = prepareApplication(conf, app)
proxyApp := proxy.New(app)
nodeKey, err := tmtypes.LoadOrGenNodeKey(conf.NodeKeyFile())
Expand Down Expand Up @@ -90,9 +94,6 @@ func New(
nodeOptions...,
)
case config.ModeSeed:
if resolveOptions(nodeOptions...).freezeHeight > 0 {
return nil, fmt.Errorf("freeze height is not supported in seed mode")
}
return makeSeedNode(
conf,
config.DefaultDBProvider,
Expand All @@ -104,6 +105,18 @@ func New(
}
}

func validateFreezeMode(mode string, freezeHeight uint64) error {
if freezeHeight == 0 || mode == config.ModeFull {
return nil
}
switch mode {
case config.ModeValidator, config.ModeSeed:
return fmt.Errorf("freeze height is not supported in %s mode", mode)
default:
return nil
}
}

func validateNodeSetupConfig(conf *config.Config) error {
if conf.MockApp && conf.AutobahnConfigFile == "" {
return fmt.Errorf("mock-app requires autobahn-config-file")
Expand Down
Loading
Loading