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
21 changes: 21 additions & 0 deletions giga/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type StorageConfig struct {
RollbackWindow uint64 `mapstructure:"rollback_window"`
// LookbackWindow is how many queryable blocks are kept below the rollback window; -1 keeps all.
LookbackWindow int64 `mapstructure:"lookback_window"`
// StateStoreBackend is how the state store keeps its history: "pebbledb" stores every version of
// every key, "undolog" the value each block replaced, over the current state.
StateStoreBackend string `mapstructure:"state_store_backend"`

@yzang2019 yzang2019 Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan to support multiple backend in giga world? If undo log is the only SS we want to support moving forwards, I think we don't need to add this config at all

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i plan to remove the "old ss" at some point, and just support undo log for giga.

// PruneInterval is how often the storage garbage collector runs.
PruneInterval time.Duration `mapstructure:"prune_interval"`
// CheckpointTimeInterval is the wall-clock gap between state-commit checkpoints.
Expand Down Expand Up @@ -90,6 +93,7 @@ func defaultStorageConfig() StorageConfig {
Receipts: true,
RollbackWindow: gc.RollbackWindow,
LookbackWindow: gc.LookbackWindow,
StateStoreBackend: seidbconfig.DefaultSSBackend,
PruneInterval: gc.PruneInterval,
CheckpointTimeInterval: cp.TimeInterval,
CheckpointBlockInterval: cp.BlockInterval,
Expand All @@ -102,6 +106,7 @@ const (
FlagStorageReceipts = "giga.storage.receipts"
FlagStorageRollbackWindow = "giga.storage.rollback_window"
FlagStorageLookbackWindow = "giga.storage.lookback_window"
FlagStorageStateStoreBackend = "giga.storage.state_store_backend"
FlagStoragePruneInterval = "giga.storage.prune_interval"
FlagStorageCheckpointTimeInterval = "giga.storage.checkpoint_time_interval"
FlagStorageCheckpointBlockInterval = "giga.storage.checkpoint_block_interval"
Expand Down Expand Up @@ -137,6 +142,11 @@ func ReadConfig(opts AppOptions) (Config, error) {
return cfg, fmt.Errorf("%s: %w", FlagStorageLookbackWindow, err)
}
}
if v := opts.Get(FlagStorageStateStoreBackend); v != nil {
if cfg.Storage.StateStoreBackend, err = cast.ToStringE(v); err != nil {
return cfg, fmt.Errorf("%s: %w", FlagStorageStateStoreBackend, err)
}
}
if v := opts.Get(FlagStoragePruneInterval); v != nil {
if cfg.Storage.PruneInterval, err = cast.ToDurationE(v); err != nil {
return cfg, fmt.Errorf("%s: %w", FlagStoragePruneInterval, err)
Expand Down Expand Up @@ -197,6 +207,12 @@ func (c Config) Validate() error {
return fmt.Errorf("%s: must be >= 0, or -1 to keep all history, got %d", FlagStorageLookbackWindow,
c.Storage.LookbackWindow)
}
switch c.Storage.StateStoreBackend {
case seidbconfig.PebbleDBBackend, seidbconfig.PebbleDBUndoBackend:
default:
return fmt.Errorf("%s: %q is not %q or %q", FlagStorageStateStoreBackend, c.Storage.StateStoreBackend,
seidbconfig.PebbleDBBackend, seidbconfig.PebbleDBUndoBackend)
}
if c.Storage.PruneInterval <= 0 {
return fmt.Errorf("%s: must be positive, got %s", FlagStoragePruneInterval, c.Storage.PruneInterval)
}
Expand Down Expand Up @@ -257,6 +273,11 @@ rollback_window = {{ .Giga.Storage.RollbackWindow }}
# -1 keeps all history.
lookback_window = {{ .Giga.Storage.LookbackWindow }}

# state_store_backend is how the state store keeps its history. "pebbledb" stores every version
# of every key; "undolog" stores the value each block replaced, over the current state, and
# drops expired history a bucket at a time.
state_store_backend = "{{ .Giga.Storage.StateStoreBackend }}"

# prune_interval is how often the storage garbage collector runs.
prune_interval = "{{ .Giga.Storage.PruneInterval }}"

Expand Down
4 changes: 4 additions & 0 deletions giga/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func TestDefaultsMatchTheStorageDefaults(t *testing.T) {
require.True(t, s.Receipts)
require.Equal(t, gc.RollbackWindow, s.RollbackWindow)
require.Equal(t, gc.LookbackWindow, s.LookbackWindow)
require.Equal(t, seidbconfig.DefaultStateStoreConfig().Backend, s.StateStoreBackend)
require.Equal(t, gc.PruneInterval, s.PruneInterval)
require.Equal(t, cp.TimeInterval, s.CheckpointTimeInterval)
require.Equal(t, cp.BlockInterval, s.CheckpointBlockInterval)
Expand All @@ -38,6 +39,7 @@ func TestReadConfigReadsEveryKey(t *testing.T) {
"giga.storage.receipts": "false",
"giga.storage.rollback_window": "250",
"giga.storage.lookback_window": "-1",
"giga.storage.state_store_backend": "undolog",
"giga.storage.prune_interval": "90s",
"giga.storage.checkpoint_time_interval": "1h",
"giga.storage.checkpoint_block_interval": "5000",
Expand All @@ -55,6 +57,7 @@ func TestReadConfigReadsEveryKey(t *testing.T) {
Receipts: false,
RollbackWindow: 250,
LookbackWindow: -1,
StateStoreBackend: seidbconfig.PebbleDBUndoBackend,
PruneInterval: 90 * time.Second,
CheckpointTimeInterval: time.Hour,
CheckpointBlockInterval: 5000,
Expand All @@ -76,6 +79,7 @@ func TestReadConfigRejectsUnusableValues(t *testing.T) {
"unknown mode": {"giga.storage.mode": "archive"},
"non-boolean receipts": {"giga.storage.receipts": "maybe"},
"lookback below -1": {"giga.storage.lookback_window": "-2"},
"unknown backend": {"giga.storage.state_store_backend": "rocksdb"},
"zero prune interval": {"giga.storage.prune_interval": "0s"},
"negative occ workers": {"giga.execution.occ_workers": "-1"},
"empty result pool": {"giga.execution.block_result_pool_size": "0"},
Expand Down
1 change: 1 addition & 0 deletions giga/config/register_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func TestTheDeclaredKeysAreTheKeysThisReaderResolves(t *testing.T) {
FlagStorageReceipts,
FlagStorageRollbackWindow,
FlagStorageLookbackWindow,
FlagStorageStateStoreBackend,
FlagStoragePruneInterval,
FlagStorageCheckpointTimeInterval,
FlagStorageCheckpointBlockInterval,
Expand Down
44 changes: 41 additions & 3 deletions giga/evmonly/giga_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ var (

var _ StateReader = gigaSnapshotStateReader{}

// historyStore is a giga store that takes a block's change set alongside its changesets, to keep the
// values the block replaced.
type historyStore interface {
CommitStateChangesWithPrior(blockNum int64, changeset []*proto.NamedChangeSet, changes *gigatypes.StateChangeSet) error
}

// NamedChangeSetEncoder converts an executor-native state result into the on-disk changesets
// understood by a giga store. It must treat the input as immutable, and its output must not alias
// the input, since the commit outlives the pooled block result. It may read the store only to
Expand Down Expand Up @@ -240,15 +246,16 @@ func (e *Executor) awaitPipelineCommit() error {
// Commits stay ordered because only one is ever in flight: awaitPipelineCommit lands the previous
// one before this is called.
func (e *Executor) startPipelineCommit(blockNumber int64, changesets []*proto.NamedChangeSet, changes *StateChangeSet) error {
// The commit outlives the block result, which returns to a pool and is reset.
pending := changes.clone()
// Close sets closed before taking storeMu, which the caller holds, so a block that sees it
// unset is one Close waits for.
if e.closed.Load() {
if err := e.awaitPipelineCommit(); err != nil {
return err
}
return e.stateStore.CommitStateChanges(blockNumber, changesets)
return e.commitState(blockNumber, changesets, pending)
}
pending := changes.clone()
done := make(chan struct{})
e.pipelineMu.Lock()
if failure := e.pipelineFailure; failure != nil {
Expand All @@ -261,7 +268,7 @@ func (e *Executor) startPipelineCommit(blockNumber int64, changesets []*proto.Na
e.pipelineMu.Unlock()

go func() {
err := e.stateStore.CommitStateChanges(blockNumber, changesets)
err := e.commitState(blockNumber, changesets, pending)
e.pipelineMu.Lock()
e.pipelineErr = err
e.pipelineMu.Unlock()
Expand All @@ -270,6 +277,37 @@ func (e *Executor) startPipelineCommit(blockNumber int64, changesets []*proto.Na
return nil
}

// commitState writes the block to the store, handing over changes as well when the store keeps the
// values the block replaced.
func (e *Executor) commitState(blockNumber int64, changesets []*proto.NamedChangeSet, changes *StateChangeSet) error {
if store, ok := e.stateStore.(historyStore); ok {
return store.CommitStateChangesWithPrior(blockNumber, changesets, gigaChangeSet(changes))
}
return e.stateStore.CommitStateChanges(blockNumber, changesets)
}

// TODO: Temporal. At some point both struct will be reused to avoid extra copies and allocs.
func gigaChangeSet(changes *StateChangeSet) *gigatypes.StateChangeSet {
if changes == nil {
return nil
}
return &gigatypes.StateChangeSet{
Balances: convertAll(changes.Balances, func(c BalanceChange) gigatypes.BalanceChange { return gigatypes.BalanceChange(c) }),
Nonces: convertAll(changes.Nonces, func(c NonceChange) gigatypes.NonceChange { return gigatypes.NonceChange(c) }),
Code: convertAll(changes.Code, func(c CodeChange) gigatypes.CodeChange { return gigatypes.CodeChange(c) }),
StorageClears: changes.StorageClears,
Storage: convertAll(changes.Storage, func(c StorageChange) gigatypes.StorageChange { return gigatypes.StorageChange(c) }),
}
}

func convertAll[S, D any](values []S, convert func(S) D) []D {
out := make([]D, len(values))
for i, value := range values {
out[i] = convert(value)
}
return out
}

type gigaSnapshotStateReader struct {
snapshot gigatypes.EVMStateView
missingState StateReader
Expand Down
4 changes: 4 additions & 0 deletions giga/evmonly/giga_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import (
"github.com/stretchr/testify/require"

"github.com/sei-protocol/sei-chain/sei-db/proto"
"github.com/sei-protocol/sei-chain/sei-db/state_db/giga"
gigatypes "github.com/sei-protocol/sei-chain/sei-db/state_db/giga/types"
"github.com/sei-protocol/sei-chain/sei-db/state_db/sc/flatkv/lthash"
)

// The giga state DB must take each block's change set, or its undo log would silently go without it.
var _ historyStore = (*giga.StateDB)(nil)

type recordingGigaStore struct {
snapshot gigatypes.StateView
openCount int
Expand Down
12 changes: 6 additions & 6 deletions giga/evmonly/pipeline_overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,14 @@ func (c *StateChangeSet) clone() *StateChangeSet {
Storage: append([]StorageChange(nil), c.Storage...),
}
for i, change := range c.Balances {
cp.Balances[i] = BalanceChange{Address: change.Address, Balance: cloneBig(change.Balance)}
change.Balance = cloneBig(change.Balance)
change.PriorBalance = cloneBig(change.PriorBalance)
cp.Balances[i] = change
}
for i, change := range c.Code {
cp.Code[i] = CodeChange{
Address: change.Address,
Code: append([]byte(nil), change.Code...),
Delete: change.Delete,
}
change.Code = append([]byte(nil), change.Code...)
change.PriorCode = append([]byte(nil), change.PriorCode...)
cp.Code[i] = change
}
return cp
}
8 changes: 6 additions & 2 deletions giga/evmonly/pipeline_overlay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,19 @@ func TestOverlayReportsDeletedCodeAsAbsent(t *testing.T) {
func TestCloneSurvivesTheBlockResultBeingReused(t *testing.T) {
addr := common.HexToAddress("0x1")
original := &StateChangeSet{
Balances: []BalanceChange{{Address: addr, Balance: big.NewInt(5)}},
Code: []CodeChange{{Address: addr, Code: []byte{0x01}}},
Balances: []BalanceChange{{Address: addr, Balance: big.NewInt(5), PriorBalance: big.NewInt(3)}},
Code: []CodeChange{{Address: addr, Code: []byte{0x01}, PriorCode: []byte{0x02}}},
}
clone := original.clone()

original.Balances[0].Balance.SetInt64(999)
original.Balances[0].PriorBalance.SetInt64(999)
original.Code[0].Code[0] = 0xff
original.Code[0].PriorCode[0] = 0xff
original.Balances = original.Balances[:0]

require.Equal(t, big.NewInt(5), clone.Balances[0].Balance)
require.Equal(t, big.NewInt(3), clone.Balances[0].PriorBalance)
require.Equal(t, []byte{0x01}, clone.Code[0].Code)
require.Equal(t, []byte{0x02}, clone.Code[0].PriorCode)
}
8 changes: 8 additions & 0 deletions sei-db/config/giga_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ func AutobahnStorageConfig(homePath string) (*GigaStorageConfig, error) {
return storageConfig, nil
}

// WithStateStoreBackend has the state store keep its history as backend, in that backend's
// directory under the home path.
func (c *GigaStorageConfig) WithStateStoreBackend(backend string) *GigaStorageConfig {
c.SSConfig.Backend = backend
c.SSConfig.EVMDBDirectory = utils.GetEVMStateStorePath(c.HomePath, backend)
return c
}

func (c *GigaStorageConfig) WithAccountDBCacheSize(sizeInBytes uint64) *GigaStorageConfig {
c.FlatKVConfig.AccountStoreConfig.MaxSize = sizeInBytes
return c
Expand Down
Loading
Loading