Skip to content

Commit 2f1cd59

Browse files
committed
add --payloadless flag
1 parent 553a243 commit 2f1cd59

8 files changed

Lines changed: 158 additions & 19 deletions

File tree

cmd/execution_builder.go

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848
"github.com/onflow/flow-go/engine/execution/checker"
4949
"github.com/onflow/flow-go/engine/execution/computation"
5050
"github.com/onflow/flow-go/engine/execution/computation/committer"
51+
"github.com/onflow/flow-go/engine/execution/computation/computer"
5152
txmetrics "github.com/onflow/flow-go/engine/execution/computation/metrics"
5253
"github.com/onflow/flow-go/engine/execution/ingestion"
5354
"github.com/onflow/flow-go/engine/execution/ingestion/fetcher"
@@ -64,6 +65,7 @@ import (
6465
"github.com/onflow/flow-go/fvm/storage/snapshot"
6566
"github.com/onflow/flow-go/fvm/systemcontracts"
6667
"github.com/onflow/flow-go/ledger"
68+
"github.com/onflow/flow-go/ledger/complete"
6769
"github.com/onflow/flow-go/ledger/complete/wal"
6870
ledgerfactory "github.com/onflow/flow-go/ledger/factory"
6971
modelbootstrap "github.com/onflow/flow-go/model/bootstrap"
@@ -125,12 +127,13 @@ type ExecutionNode struct {
125127

126128
ingestionUnit *engine.Unit
127129

128-
collector *metrics.ExecutionCollector
129-
executionState state.ExecutionState
130-
followerState protocol.FollowerState
131-
committee hotstuff.DynamicCommittee
132-
ledgerStorage ledger.Ledger
133-
registerStore *storehouse.RegisterStore
130+
collector *metrics.ExecutionCollector
131+
executionState state.ExecutionState
132+
followerState protocol.FollowerState
133+
committee hotstuff.DynamicCommittee
134+
ledgerStorage ledger.Ledger // set iff !exeConf.payloadless
135+
payloadlessLedger ledger.PayloadlessLedger // set iff exeConf.payloadless
136+
registerStore *storehouse.RegisterStore
134137

135138
// storage
136139
events storageerr.Events
@@ -626,7 +629,16 @@ func (exeNode *ExecutionNode) LoadProviderEngine(
626629
})
627630
}
628631

629-
ledgerViewCommitter := committer.NewLedgerViewCommitter(exeNode.ledgerStorage, node.Tracer)
632+
var ledgerViewCommitter computer.ViewCommitter
633+
if exeNode.exeConf.payloadless {
634+
ledgerViewCommitter = committer.NewPayloadlessLedgerViewCommitter(
635+
exeNode.payloadlessLedger,
636+
node.Tracer,
637+
complete.DefaultPathFinderVersion,
638+
)
639+
} else {
640+
ledgerViewCommitter = committer.NewLedgerViewCommitter(exeNode.ledgerStorage, node.Tracer)
641+
}
630642
exeNode.exeConf.computationConfig.TokenTrackingEnabled = exeNode.exeConf.tokenTrackingEnabled
631643
manager, err := computation.New(
632644
node.Logger,
@@ -801,8 +813,21 @@ func (exeNode *ExecutionNode) LoadExecutionState(
801813

802814
// migrate execution data for last sealed and executed block
803815

816+
// In full mode, both args are the same *complete.Ledger; in payloadless
817+
// mode the first is the payloadless ledger (narrow LedgerStateChecker)
818+
// and the snapshot-source slot is nil because storehouse is required.
819+
var stateChecker state.LedgerStateChecker
820+
var snapshotLedger ledger.Ledger
821+
if exeNode.exeConf.payloadless {
822+
stateChecker = exeNode.payloadlessLedger
823+
snapshotLedger = nil
824+
} else {
825+
stateChecker = exeNode.ledgerStorage
826+
snapshotLedger = exeNode.ledgerStorage
827+
}
804828
exeNode.executionState = state.NewExecutionState(
805-
exeNode.ledgerStorage,
829+
stateChecker,
830+
snapshotLedger,
806831
exeNode.commits,
807832
node.Storage.Blocks,
808833
node.Storage.Headers,
@@ -916,7 +941,41 @@ func (exeNode *ExecutionNode) LoadExecutionStateLedger(
916941
module.ReadyDoneAware,
917942
error,
918943
) {
919-
// Create ledger using factory
944+
if exeNode.exeConf.payloadless {
945+
// Payloadless mode. ValidateFlags enforces --enable-storehouse,
946+
// so the storehouse is the value source for reads.
947+
//
948+
// The factory call mirrors the full-mode call below: same Config,
949+
// same triggerCheckpoint. Today the factory body is a placeholder
950+
// (no WAL, no checkpoint load) — see TODOs at
951+
// ledgerfactory.NewPayloadlessLedger. When the WAL/checkpoint
952+
// pieces land, only the factory body changes; this call site stays
953+
// the same.
954+
pl, err := ledgerfactory.NewPayloadlessLedger(ledgerfactory.Config{
955+
LedgerServiceAddr: exeNode.exeConf.ledgerServiceAddr,
956+
LedgerMaxRequestSize: exeNode.exeConf.ledgerMaxRequestSize,
957+
LedgerMaxResponseSize: exeNode.exeConf.ledgerMaxResponseSize,
958+
Triedir: exeNode.exeConf.triedir,
959+
MTrieCacheSize: exeNode.exeConf.mTrieCacheSize,
960+
CheckpointDistance: exeNode.exeConf.checkpointDistance,
961+
CheckpointsToKeep: exeNode.exeConf.checkpointsToKeep,
962+
MetricsRegisterer: node.MetricsRegisterer,
963+
WALMetrics: exeNode.collector,
964+
LedgerMetrics: exeNode.collector,
965+
Logger: node.Logger,
966+
}, exeNode.toTriggerCheckpoint)
967+
if err != nil {
968+
return nil, fmt.Errorf("could not create payloadless ledger: %w", err)
969+
}
970+
exeNode.payloadlessLedger = pl
971+
// exeNode.ledgerStorage stays nil in payloadless mode; the
972+
// LedgerStateChecker slot in state.NewExecutionState receives the
973+
// payloadless ledger directly, and the snapshotLedger slot stays
974+
// nil because the storehouse is the value source.
975+
return pl, nil
976+
}
977+
978+
// Full mode (default): WAL-backed ledger via the factory.
920979
ledgerStorage, err := ledgerfactory.NewLedger(ledgerfactory.Config{
921980
LedgerServiceAddr: exeNode.exeConf.ledgerServiceAddr,
922981
LedgerMaxRequestSize: exeNode.exeConf.ledgerMaxRequestSize,
@@ -939,6 +998,7 @@ func (exeNode *ExecutionNode) LoadExecutionStateLedger(
939998
return exeNode.ledgerStorage, nil
940999
}
9411000

1001+
9421002
func (exeNode *ExecutionNode) LoadExecutionDataPruner(
9431003
node *NodeConfig,
9441004
) (

cmd/execution_config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type ExecutionConfig struct {
7474
// file descriptors causing connection failures.
7575
onflowOnlyLNs bool
7676
enableStorehouse bool
77+
payloadless bool
7778
enableBackgroundStorehouseIndexing bool
7879
backgroundIndexerHeightsPerSecond uint64
7980
enableChecker bool
@@ -154,6 +155,9 @@ func (exeConf *ExecutionConfig) SetupFlags(flags *pflag.FlagSet) {
154155

155156
flags.BoolVar(&exeConf.onflowOnlyLNs, "temp-onflow-only-lns", false, "do not use unless required. forces node to only request collections from onflow collection nodes")
156157
flags.BoolVar(&exeConf.enableStorehouse, "enable-storehouse", false, "enable storehouse to store registers on disk, default is false")
158+
flags.BoolVar(&exeConf.payloadless, "payloadless", false,
159+
"run the execution node with a payloadless ledger that stores only leaf hashes; "+
160+
"register values are read from the storehouse during execution. requires --enable-storehouse.")
157161
flags.BoolVar(&exeConf.enableBackgroundStorehouseIndexing, "enable-background-storehouse-indexing", false, "enable background indexing of storehouse data while storehouse is disabled to eliminate downtime when enabling it. default: false.")
158162
flags.Uint64Var(&exeConf.backgroundIndexerHeightsPerSecond, "background-indexer-heights-per-second", storehouse.DefaultHeightsPerSecond, fmt.Sprintf("rate limit for background indexer in heights per second. 0 means no rate limiting. default: %v", storehouse.DefaultHeightsPerSecond))
159163
flags.BoolVar(&exeConf.enableChecker, "enable-checker", true, "enable checker to check the correctness of the execution result, default is true")
@@ -198,5 +202,13 @@ func (exeConf *ExecutionConfig) ValidateFlags() error {
198202
if exeConf.enableStorehouse {
199203
exeConf.enableBackgroundStorehouseIndexing = false
200204
}
205+
// Payloadless requires storehouse: the payloadless ledger does not retain
206+
// register values; the storehouse is the only available value source for
207+
// both proof reconstruction and snapshot reads.
208+
if exeConf.payloadless && !exeConf.enableStorehouse {
209+
return errors.New("--payloadless requires --enable-storehouse: " +
210+
"the payloadless ledger does not store register values; " +
211+
"the storehouse must provide them at execution time")
212+
}
201213
return nil
202214
}

cmd/util/cmd/rollback-executed-height/cmd/rollback_executed_height_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func TestReExecuteBlock(t *testing.T) {
6767

6868
// create execution state module
6969
es := state.NewExecutionState(
70+
nil,
7071
nil,
7172
commits,
7273
nil,
@@ -229,6 +230,7 @@ func TestReExecuteBlockWithDifferentResult(t *testing.T) {
229230

230231
// create execution state module
231232
es := state.NewExecutionState(
233+
nil,
232234
nil,
233235
commits,
234236
nil,

engine/execution/state/state.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,18 @@ type ExecutionState interface {
9393
GetHighestFinalizedExecuted() (uint64, error)
9494
}
9595

96+
// LedgerStateChecker is the minimum ledger-side contract the execution state
97+
// always needs, regardless of register-store mode: a way to check whether a
98+
// given state commitment exists in the underlying trie. Both ledger.Ledger
99+
// and ledger.PayloadlessLedger satisfy this interface, which is how the
100+
// execution state can be backed by either.
101+
type LedgerStateChecker interface {
102+
HasState(state ledger.State) bool
103+
}
104+
96105
type state struct {
97106
tracer module.Tracer
98-
ls ledger.Ledger
107+
ls LedgerStateChecker
99108
commits storage.Commits
100109
blocks storage.Blocks
101110
headers storage.Headers
@@ -109,15 +118,22 @@ type state struct {
109118
getLatestFinalized func() (uint64, error)
110119
lockManager lockctx.Manager
111120

112-
registerStore execution.RegisterStore
113-
// when it is true, registers are stored in both register store and ledger
114-
// and register queries will send to the register store instead of ledger
121+
// snapshotLedger and registerStore are needed by the NewStorageSnapshot method,
122+
// when enableRegisterStore == false, registerStore is nil, snapshotLedger is used to read register values
123+
// when enableRegisterStore == true, snapshotLedger is nil, registerStore is used to read register values
124+
// note:
125+
// in payloadless ledger mode, enableRegisterStore must be true, therefore snapshotLedger is not needed and
126+
// registerStore is used to read register values
115127
enableRegisterStore bool
128+
snapshotLedger ledger.Ledger
129+
registerStore execution.RegisterStore
116130
}
117131

118-
// NewExecutionState returns a new execution state access layer for the given ledger storage.
132+
// NewExecutionState returns a new execution state access layer for the given
133+
// ledger storage.
119134
func NewExecutionState(
120-
ls ledger.Ledger,
135+
ls LedgerStateChecker,
136+
snapshotLedger ledger.Ledger,
121137
commits storage.Commits,
122138
blocks storage.Blocks,
123139
headers storage.Headers,
@@ -137,6 +153,7 @@ func NewExecutionState(
137153
return &state{
138154
tracer: tracer,
139155
ls: ls,
156+
snapshotLedger: snapshotLedger,
140157
commits: commits,
141158
blocks: blocks,
142159
headers: headers,
@@ -262,7 +279,7 @@ func (s *state) NewStorageSnapshot(
262279
if s.enableRegisterStore {
263280
return storehouse.NewBlockEndStateSnapshot(s.registerStore, blockID, height)
264281
}
265-
return NewLedgerStorageSnapshot(s.ls, commitment)
282+
return NewLedgerStorageSnapshot(s.snapshotLedger, commitment)
266283
}
267284

268285
func (s *state) CreateStorageSnapshot(

engine/execution/state/state_storehouse_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func prepareStorehouseTest(f func(t *testing.T, es state.ExecutionState, l *ledg
9696
}
9797

9898
es := state.NewExecutionState(
99-
ls, stateCommitments, blocks, headers, chunkDataPacks, results, myReceipts, events, serviceEvents, txResults, pebbleimpl.ToDB(pebbleDB),
99+
ls, ls, stateCommitments, blocks, headers, chunkDataPacks, results, myReceipts, events, serviceEvents, txResults, pebbleimpl.ToDB(pebbleDB),
100100
getLatestFinalized,
101101
trace.NewNoopTracer(),
102102
rs,

engine/execution/state/state_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func prepareTest(f func(t *testing.T, es state.ExecutionState, l *ledger.Ledger,
5454

5555
db := pebbleimpl.ToDB(pebbleDB)
5656
es := state.NewExecutionState(
57-
ls, stateCommitments, blocks, headers, chunkDataPacks, results, myReceipts, events, serviceEvents, txResults, db, getLatestFinalized, trace.NewNoopTracer(),
57+
ls, ls, stateCommitments, blocks, headers, chunkDataPacks, results, myReceipts, events, serviceEvents, txResults, db, getLatestFinalized, trace.NewNoopTracer(),
5858
nil,
5959
false,
6060
lockManager,

engine/testutil/nodes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ func ExecutionNode(t *testing.T, hub *stub.Hub, identity bootstrap.NodeInfo, ide
676676
}
677677

678678
execState := executionState.NewExecutionState(
679-
ls, commitsStorage, node.Blocks, node.Headers, chunkDataPackStorage, results, myReceipts, eventsStorage, serviceEventsStorage, txResultStorage, db, getLatestFinalized, node.Tracer,
679+
ls, ls, commitsStorage, node.Blocks, node.Headers, chunkDataPackStorage, results, myReceipts, eventsStorage, serviceEventsStorage, txResultStorage, db, getLatestFinalized, node.Tracer,
680680
// TODO: test with register store
681681
registerStore,
682682
storehouseEnabled,

ledger/factory/factory.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,51 @@ func newLocalLedger(config Config, triggerCheckpoint *atomic.Bool) (ledger.Ledge
115115

116116
return ledgerStorage, nil
117117
}
118+
119+
// NewPayloadlessLedger creates a payloadless ledger instance.
120+
//
121+
// This is the payloadless-mode counterpart of [NewLedger]. It mirrors that
122+
// function's signature so call sites in cmd/execution_builder.go can switch
123+
// between the two without changing how config is plumbed. The argument types
124+
// are deliberately identical (same Config struct, same triggerCheckpoint).
125+
//
126+
// TODO: payloadless WAL is not implemented yet. This factory currently
127+
// returns an in-memory payloadless ledger that does not persist updates to
128+
// a WAL. config.Triedir, config.CheckpointDistance, config.CheckpointsToKeep
129+
// and triggerCheckpoint are accepted for API parity but ignored.
130+
//
131+
// TODO: payloadless checkpoint loading is not implemented yet. The factory
132+
// does not read any checkpoint file at boot, so the trie starts empty on
133+
// every startup. To make payloadless nodes survive a restart, one of the
134+
// following must land:
135+
//
136+
// 1. A native payloadless checkpoint format with its own writer, reader,
137+
// and bootstrap path.
138+
// 2. A conversion path that reads the existing full V6 mtrie checkpoint
139+
// (the format LoadBootstrapper copies into triedir) and ingests its
140+
// (path, value) pairs into the payloadless trie at boot. This unblocks
141+
// payloadless boot from existing on-disk state without committing to a
142+
// payloadless checkpoint format.
143+
//
144+
// Until one of those is in place, --payloadless mode is suitable for
145+
// short-lived experimental nodes only; the trie has no state on first boot
146+
// and loses all state on restart.
147+
//
148+
// TODO: remote payloadless ledger client. When config.LedgerServiceAddr is
149+
// set, this factory should construct a remote.PayloadlessClient (Spec 004).
150+
// For now config.LedgerServiceAddr is ignored.
151+
func NewPayloadlessLedger(config Config, triggerCheckpoint *atomic.Bool) (ledger.PayloadlessLedger, error) {
152+
_ = triggerCheckpoint // TODO: drive payloadless checkpoint generation once a format exists
153+
154+
config.Logger.Warn().
155+
Str("triedir", config.Triedir).
156+
Msg("payloadless ledger has no WAL or checkpoint support yet; " +
157+
"trie state will not survive restart and will not be loaded from disk")
158+
159+
return complete.NewPayloadlessLedger(
160+
int(config.MTrieCacheSize),
161+
config.LedgerMetrics,
162+
config.Logger.With().Str("subcomponent", "ledger").Logger(),
163+
complete.DefaultPathFinderVersion,
164+
)
165+
}

0 commit comments

Comments
 (0)