Skip to content
Draft
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
11 changes: 6 additions & 5 deletions app/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,9 @@ func (app *App) BeginBlock(
}
}

// applyMigrationBatchSize paces the SC store's background data migration at the network-agreed rate.
// The NumKeysToMigratePerBlock gov param is read from chain state so every node
// applies the same value each block; a per-node rate would diverge the
// AppHash. 0 (the default until a gov proposal raises it) leaves the migration
// paused; it is the sole source of the rate (there is no node-local fallback).
// applyMigrationBatchSize paces the SC store's background data migration at the
// rate the NumKeysToMigratePerBlock gov param holds in chain state, so every
// node applies the same value each block. 0 leaves the migration paused.
func (app *App) applyMigrationBatchSize(ctx sdk.Context) {
if app.rootStore == nil {
return
Expand All @@ -78,6 +76,9 @@ func (app *App) applyMigrationBatchSize(ctx sdk.Context) {
}
subspace.GetIfExists(ctx, migration.KeyNumKeysToMigratePerBlock, &numKeys)
}
// A per-node rate diverges the AppHash, so production builds return numKeys
// unchanged here; only mock_block_validation builds may substitute one.
numKeys = unsafeMigrationBatchSizeOverride(numKeys)
// Defense-in-depth: gov validation already rejects values above
// MaxNumKeysToMigratePerBlock, but clamp here too so an out-of-range value
// reaching state via any path can never overflow the int cast or trigger an
Expand Down
9 changes: 9 additions & 0 deletions app/unsafe_migration_batch_size.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build !mock_block_validation

package app

// unsafeMigrationBatchSizeOverride returns numKeys unchanged. Only a
// mock_block_validation build carries a node-local migration rate.
func unsafeMigrationBatchSizeOverride(numKeys uint64) uint64 {
return numKeys
}
53 changes: 53 additions & 0 deletions app/unsafe_migration_batch_size_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//go:build mock_block_validation

package app

import (
"fmt"
"os"
"strconv"
"sync"
)

// UnsafeMigrationBatchSizeEnv names the environment variable that replaces the
// on-chain NumKeysToMigratePerBlock rate with a node-local one. Only
// mock_block_validation builds read it.
const UnsafeMigrationBatchSizeEnv = "SEI_UNSAFE_MIGRATION_BATCH_SIZE"

var unsafeMigrationBatchSize, unsafeMigrationBatchSizeSet = readUnsafeMigrationBatchSize(
os.Getenv(UnsafeMigrationBatchSizeEnv))

var logUnsafeMigrationBatchSizeOnce sync.Once

// readUnsafeMigrationBatchSize parses the override value. An empty value means
// unset; any other value must be a base-10 unsigned integer.
func readUnsafeMigrationBatchSize(raw string) (uint64, bool) {
if raw == "" {
return 0, false
}
n, err := strconv.ParseUint(raw, 10, 64)
if err != nil {
// Parsed at process start so a typo fails every subcommand loudly rather
// than leaving a migration silently paused at the on-chain rate of 0.
panic(fmt.Sprintf("%s=%q is not a base-10 unsigned integer: %v", UnsafeMigrationBatchSizeEnv, raw, err))
}
return n, true
}

// unsafeMigrationBatchSizeOverride returns the node-local migration rate when
// SEI_UNSAFE_MIGRATION_BATCH_SIZE is set, and numKeys otherwise.
func unsafeMigrationBatchSizeOverride(numKeys uint64) uint64 {
if !unsafeMigrationBatchSizeSet {
return numKeys
}
logUnsafeMigrationBatchSizeOnce.Do(func() {
// A node-local rate makes this node's migration writes, and so its
// AppHash, differ from the network's from the first migration commit.
// The mock_block_validation consensus policy swallows that mismatch,
// which is the only reason this override can exist in this build.
logger.Error("UNSAFE: SC migration batch size overridden by environment; this node's AppHash "+
"diverges from the network once migration starts",
"env", UnsafeMigrationBatchSizeEnv, "batchSize", unsafeMigrationBatchSize, "onChain", numKeys)
})
return unsafeMigrationBatchSize
}
37 changes: 37 additions & 0 deletions app/unsafe_migration_batch_size_mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build mock_block_validation

package app

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestReadUnsafeMigrationBatchSize(t *testing.T) {
v, ok := readUnsafeMigrationBatchSize("")
require.False(t, ok)
require.Zero(t, v)

v, ok = readUnsafeMigrationBatchSize("250000")
require.True(t, ok)
require.Equal(t, uint64(250000), v)

v, ok = readUnsafeMigrationBatchSize("0")
require.True(t, ok)
require.Zero(t, v)

require.Panics(t, func() { readUnsafeMigrationBatchSize("lots") })
require.Panics(t, func() { readUnsafeMigrationBatchSize("-1") })
}

func TestUnsafeMigrationBatchSizeOverride_FollowsProcessEnv(t *testing.T) {
// The override is fixed at process start, so assert against whatever this
// test process was started with rather than mutating the environment.
got := unsafeMigrationBatchSizeOverride(7)
if unsafeMigrationBatchSizeSet {
require.Equal(t, unsafeMigrationBatchSize, got)
return
}
require.Equal(t, uint64(7), got)
}
15 changes: 15 additions & 0 deletions app/unsafe_migration_batch_size_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build !mock_block_validation

package app

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestUnsafeMigrationBatchSizeOverride_ProductionBuildIgnoresEnv(t *testing.T) {
t.Setenv("SEI_UNSAFE_MIGRATION_BATCH_SIZE", "12345")
require.Equal(t, uint64(7), unsafeMigrationBatchSizeOverride(7))
require.Equal(t, uint64(0), unsafeMigrationBatchSizeOverride(0))
}
Loading