Skip to content

Commit a4e3a44

Browse files
authored
feat(world-state): support prefilled nullifiers in genesis state (#24567)
## What Adds opt-in plumbing to seed nullifier leaves into the genesis nullifier tree, threaded through the C++ world state and the TypeScript world-state layer. The field is optional and defaults to empty, so **production genesis is unchanged** — every existing genesis root stays bit-identical to today. - **C++**: `WorldState` constructors, the napi binding, `create_canonical_fork`, and the standalone `aztec-wsdb` IPC server gain a `prefilled_nullifiers` input, inserted as the nullifier tree's initial leaves. The indexed nullifier tree requires those leaves to be unique and strictly increasing (and distinct from the padding leaves), which the tree already enforces; the TS side additionally checks it before the native call for a friendlier error. - **TS**: `GenesisData.prefilledNullifiers` (optional, defaults to `[]`) threads through `native_world_state_instance.ts`. `getGenesisValues` gains an optional `prefilledNullifiers` param (sorted ascending on the way in), and the `GENESIS_ARCHIVE_ROOT` fast-path in `generateGenesisValues` now also checks the nullifier list is empty, so the short-circuit only fires for the canonical empty genesis. ## Why Mechanism for round-4 e2e speedup PR 1b, which will seed the standard-contract (AuthRegistry / PublicChecks / HandshakeRegistry) registration nullifiers at genesis in the e2e fixtures so their publish txs short-circuit. This PR carries no consumers and is intentionally behavior- and timing-neutral: with the field empty (the only value ever passed in production), the native tree, all genesis roots, and the archive root are identical to before. ## Relationship to #24254 This is a deliberate subset port of #24254 ("seed protocol contract registration nullifiers at genesis"), kept shape- and signature-identical wherever it does not diverge, so that when #24254 itself reaches the v5 line the mechanism merges as "already applied". The intentional divergences: - `prefilledNullifiers` is **optional and defaults to empty** here (#24254 made it required and non-empty by default via `DEFAULT_GENESIS_DATA`). - This PR does **not** port `DEFAULT_GENESIS_DATA`, the protocol-contract nullifier generation (`protocol-contracts/src/genesis_data.ts`, `scripts/generate_data.ts`), or **any** constant recomputation (`GENESIS_ARCHIVE_ROOT`, `constants.nr`, `constants.gen.ts`, `ConstantsGen.sol`, `aztec_constants.hpp`, checkpoint / AVM golden fixtures). With the field empty, every genesis root is unchanged, which is the invariant CI must prove. ## Testing (local) - C++ `world_state_tests`: `GetInitialTreeInfoForAllTrees` (unchanged — still asserts the current empty-nullifier root `0x18935581…` and `GENESIS_ARCHIVE_ROOT`, proving empty-field behavior is bit-identical), `GetInitialTreeInfoWithPrefilledPublicData` (updated to pass an empty nullifier vector), and a new `GetInitialTreeInfoWithPrefilledNullifiers` (seeding changes the root, tree size stays 128, seeded leaves are present) — 3/3 pass. - `@aztec/world-state` `testing.test.ts` (7/7): empty genesis returns `GENESIS_ARCHIVE_ROOT` via the fast path; the empty-genesis computed root matches that constant; an explicit empty `prefilledNullifiers` yields the same root as omitting the field; a non-empty sorted list yields a deterministic root that differs from empty; `getGenesisValues` sorts and seeds; and non-strictly-increasing (descending / duplicate) lists are rejected by the defensive check. - Regression: `@aztec/world-state` `native_world_state.test.ts` (53/53), `@aztec/stdlib` `l2_block.test.ts` (3/3, confirms the genesis nullifier root is unchanged), `@aztec/prover-client` `lightweight_checkpoint_builder.test.ts` (7/7). - Full `yarn build`, plus `yarn format`/`yarn lint` on the touched packages, are clean. ## Notes This PR is intentionally behavior- and timing-neutral; no e2e behavior changes until PR 1b adopts the mechanism. ## Measured impact None expected, and none observed. `prefilledNullifiers` defaults to `[]`, so the production genesis payload and roots are bit-identical (asserted by the unchanged C++ empty-nullifier root and `GENESIS_ARCHIVE_ROOT` tests); no fixture opts into a non-empty list in this PR. CI timings confirm neutrality: per-suite beforeHooks deltas scatter in both directions within the per-suite noise floor — largest were runtime_config −12.8s, token_bridge −12.7s, account_init +11.5s, failures +11.0s — with the fees suites essentially flat (private_payments −3.4s, gas_estimation −0.2s) and no systematic direction. The summed beforeHooks delta over all 276 suites present in both runs is −122.8s (~−2%), well inside accumulated run-to-run variance. Baseline CI run 1783374468714213 (merge-base e1711d3, full). PR CI run 1783389062016888 (x-fast). Part of A-1404
1 parent 5d2b6df commit a4e3a44

11 files changed

Lines changed: 280 additions & 17 deletions

File tree

barretenberg/cpp/src/barretenberg/nodejs_module/world_state/world_state.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
4848
std::unordered_map<MerkleTreeId, uint32_t> tree_height;
4949
std::unordered_map<MerkleTreeId, index_t> tree_prefill;
5050
std::vector<PublicDataLeafValue> prefilled_public_data;
51+
std::vector<bb::fr> prefilled_nullifiers;
5152
std::vector<MerkleTreeId> tree_ids{
5253
MerkleTreeId::NULLIFIER_TREE, MerkleTreeId::NOTE_HASH_TREE, MerkleTreeId::PUBLIC_DATA_TREE,
5354
MerkleTreeId::L1_TO_L2_MESSAGE_TREE, MerkleTreeId::ARCHIVE,
@@ -112,15 +113,36 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
112113
throw Napi::TypeError::New(env, "Prefilled public data must be an array");
113114
}
114115

115-
size_t initial_header_generator_point_index = 4;
116+
size_t prefilled_nullifiers_index = 4;
117+
if (info.Length() > prefilled_nullifiers_index && info[prefilled_nullifiers_index].IsArray()) {
118+
Napi::Array arr = info[prefilled_nullifiers_index].As<Napi::Array>();
119+
for (uint32_t i = 0; i < arr.Length(); ++i) {
120+
if (!arr.Get(i).IsBuffer()) {
121+
throw Napi::TypeError::New(env, "Prefilled nullifier value must be a buffer");
122+
}
123+
Napi::Buffer<uint8_t> nullifier_buf = arr.Get(i).As<Napi::Buffer<uint8_t>>();
124+
if (nullifier_buf.Length() != 32) {
125+
throw Napi::TypeError::New(env, "Prefilled nullifier value must be a 32-byte buffer");
126+
}
127+
uint256_t nullifier = 0;
128+
for (size_t j = 0; j < 32; ++j) {
129+
nullifier = (nullifier << 8) | nullifier_buf[j];
130+
}
131+
prefilled_nullifiers.emplace_back(nullifier);
132+
}
133+
} else {
134+
throw Napi::TypeError::New(env, "Prefilled nullifiers must be an array");
135+
}
136+
137+
size_t initial_header_generator_point_index = 5;
116138
if (info.Length() > initial_header_generator_point_index && info[initial_header_generator_point_index].IsNumber()) {
117139
initial_header_generator_point = info[initial_header_generator_point_index].As<Napi::Number>().Uint32Value();
118140
} else {
119141
throw Napi::TypeError::New(env, "Header generator point needs to be a number");
120142
}
121143

122144
uint64_t genesis_timestamp = 0;
123-
size_t genesis_timestamp_index = 5;
145+
size_t genesis_timestamp_index = 6;
124146
if (info.Length() > genesis_timestamp_index) {
125147
if (info[genesis_timestamp_index].IsNumber()) {
126148
genesis_timestamp = static_cast<uint64_t>(info[genesis_timestamp_index].As<Napi::Number>().Int64Value());
@@ -130,7 +152,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
130152
}
131153

132154
// optional parameters
133-
size_t map_size_index = 6;
155+
size_t map_size_index = 7;
134156
if (info.Length() > map_size_index) {
135157
if (info[map_size_index].IsObject()) {
136158
Napi::Object obj = info[map_size_index].As<Napi::Object>();
@@ -160,7 +182,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
160182
}
161183
}
162184

163-
size_t thread_pool_size_index = 7;
185+
size_t thread_pool_size_index = 8;
164186
if (info.Length() > thread_pool_size_index) {
165187
if (!info[thread_pool_size_index].IsNumber()) {
166188
throw Napi::TypeError::New(env, "Thread pool size must be a number");
@@ -173,7 +195,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
173195
// commits never block on fsync, files stay sparse, and a crash mid-write yields an
174196
// unrecoverable env. Intended for throwaway scratch state (TXE test sessions).
175197
bool ephemeral = false;
176-
size_t ephemeral_index = 8;
198+
size_t ephemeral_index = 9;
177199
if (info.Length() > ephemeral_index) {
178200
if (!info[ephemeral_index].IsBoolean()) {
179201
throw Napi::TypeError::New(env, "Ephemeral flag must be a boolean");
@@ -187,6 +209,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
187209
tree_height,
188210
tree_prefill,
189211
prefilled_public_data,
212+
prefilled_nullifiers,
190213
initial_header_generator_point,
191214
genesis_timestamp,
192215
ephemeral);

barretenberg/cpp/src/barretenberg/world_state/world_state.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
3939
const std::unordered_map<MerkleTreeId, uint32_t>& tree_heights,
4040
const std::unordered_map<MerkleTreeId, index_t>& tree_prefill,
4141
const std::vector<PublicDataLeafValue>& prefilled_public_data,
42+
const std::vector<bb::fr>& prefilled_nullifiers,
4243
uint32_t initial_header_generator_point,
4344
uint64_t genesis_timestamp,
4445
bool ephemeral)
@@ -51,7 +52,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
5152
{
5253
// We set the max readers to be high, at least the number of given threads or the default if higher
5354
uint64_t maxReaders = std::max(thread_pool_size, DEFAULT_MIN_NUMBER_OF_READERS);
54-
create_canonical_fork(data_dir, map_size, prefilled_public_data, maxReaders, ephemeral);
55+
create_canonical_fork(data_dir, map_size, prefilled_public_data, prefilled_nullifiers, maxReaders, ephemeral);
5556
try {
5657
attempt_tree_resync();
5758
} catch (std::exception& e) {
@@ -73,6 +74,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
7374
tree_heights,
7475
tree_prefill,
7576
std::vector<PublicDataLeafValue>(),
77+
std::vector<bb::fr>(),
7678
initial_header_generator_point,
7779
genesis_timestamp,
7880
ephemeral)
@@ -84,6 +86,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
8486
const std::unordered_map<MerkleTreeId, uint32_t>& tree_heights,
8587
const std::unordered_map<MerkleTreeId, index_t>& tree_prefill,
8688
const std::vector<PublicDataLeafValue>& prefilled_public_data,
89+
const std::vector<bb::fr>& prefilled_nullifiers,
8790
uint32_t initial_header_generator_point,
8891
uint64_t genesis_timestamp,
8992
bool ephemeral)
@@ -99,6 +102,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
99102
tree_heights,
100103
tree_prefill,
101104
prefilled_public_data,
105+
prefilled_nullifiers,
102106
initial_header_generator_point,
103107
genesis_timestamp,
104108
ephemeral)
@@ -118,6 +122,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
118122
tree_heights,
119123
tree_prefill,
120124
std::vector<PublicDataLeafValue>(),
125+
std::vector<bb::fr>(),
121126
initial_header_generator_point,
122127
genesis_timestamp,
123128
ephemeral)
@@ -126,6 +131,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
126131
void WorldState::create_canonical_fork(const std::string& dataDir,
127132
const std::unordered_map<MerkleTreeId, uint64_t>& dbSize,
128133
const std::vector<PublicDataLeafValue>& prefilled_public_data,
134+
const std::vector<bb::fr>& prefilled_nullifiers,
129135
uint64_t maxReaders,
130136
bool ephemeral)
131137
{
@@ -148,9 +154,15 @@ void WorldState::create_canonical_fork(const std::string& dataDir,
148154
{
149155
uint32_t levels = _tree_heights.at(MerkleTreeId::NULLIFIER_TREE);
150156
index_t initial_size = _initial_tree_size.at(MerkleTreeId::NULLIFIER_TREE);
157+
std::vector<NullifierLeafValue> prefilled_nullifier_leaves;
158+
prefilled_nullifier_leaves.reserve(prefilled_nullifiers.size());
159+
for (const auto& nullifier : prefilled_nullifiers) {
160+
prefilled_nullifier_leaves.emplace_back(nullifier);
161+
}
151162
auto store = std::make_unique<NullifierStore>(
152163
getMerkleTreeName(MerkleTreeId::NULLIFIER_TREE), levels, _persistentStores->nullifierStore);
153-
auto tree = std::make_unique<NullifierTree>(std::move(store), _workers, initial_size);
164+
auto tree =
165+
std::make_unique<NullifierTree>(std::move(store), _workers, initial_size, prefilled_nullifier_leaves);
154166
fork->_trees.insert({ MerkleTreeId::NULLIFIER_TREE, TreeWithStore(std::move(tree)) });
155167
}
156168
{

barretenberg/cpp/src/barretenberg/world_state/world_state.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,15 @@ class WorldState {
8282
const std::unordered_map<MerkleTreeId, uint32_t>& tree_heights,
8383
const std::unordered_map<MerkleTreeId, index_t>& tree_prefill,
8484
const std::vector<PublicDataLeafValue>& prefilled_public_data,
85+
const std::vector<bb::fr>& prefilled_nullifiers,
8586
uint32_t initial_header_generator_point,
8687
uint64_t genesis_timestamp = 0,
8788
bool ephemeral = false);
8889

8990
/**
91+
* @param prefilled_nullifiers Nullifier leaves to pre-insert into the genesis nullifier tree (e.g. the protocol
92+
* contract registration nullifiers). Must be unique and strictly increasing in field value, and
93+
* distinct from the padding leaves implied by the nullifier tree prefill size.
9094
* @param ephemeral When true, every underlying LMDB env opens with `MDB_NOSYNC |
9195
* MDB_NOMETASYNC`. Commits return without waiting for fsync; the kernel
9296
* flushes lazily, files stay sparse. Intended for throwaway scratch
@@ -99,6 +103,7 @@ class WorldState {
99103
const std::unordered_map<MerkleTreeId, uint32_t>& tree_heights,
100104
const std::unordered_map<MerkleTreeId, index_t>& tree_prefill,
101105
const std::vector<PublicDataLeafValue>& prefilled_public_data,
106+
const std::vector<bb::fr>& prefilled_nullifiers,
102107
uint32_t initial_header_generator_point,
103108
uint64_t genesis_timestamp = 0,
104109
bool ephemeral = false);
@@ -326,6 +331,7 @@ class WorldState {
326331
void create_canonical_fork(const std::string& dataDir,
327332
const std::unordered_map<MerkleTreeId, uint64_t>& dbSize,
328333
const std::vector<PublicDataLeafValue>& prefilled_public_data,
334+
const std::vector<bb::fr>& prefilled_nullifiers,
329335
uint64_t maxReaders,
330336
bool ephemeral);
331337

barretenberg/cpp/src/barretenberg/world_state/world_state.test.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,57 @@ TEST_F(WorldStateTest, GetInitialTreeInfoForAllTrees)
211211
}
212212
}
213213

214+
TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledNullifiers)
215+
{
216+
// Prefilled nullifier leaves must be unique and strictly increasing, and larger than the padding leaves that fill
217+
// the initial 128-leaf prefill region (whose keys are the low integers 0..127), so we use full-size field values.
218+
std::vector<bb::fr> prefilled_nullifiers = {
219+
bb::fr("0x073b5e41abe9d7f8466bca9c81c9572b558f953bbd70081317f6a80ac65f3dd5"),
220+
bb::fr("0x0d99507b7ecac720c73bf197a0e7366a5ed80c1c1b0afe8ff8c6ecc7b5a7aefe"),
221+
bb::fr("0x1c0bf82e0c51834780e61ef091b17e3a1d39ae891db7a70bfdb5221f134996ac"),
222+
};
223+
224+
std::string data_dir_prefilled = random_temp_directory();
225+
std::filesystem::create_directories(data_dir_prefilled);
226+
227+
WorldState ws_prefilled(thread_pool_size,
228+
data_dir_prefilled,
229+
map_size,
230+
tree_heights,
231+
tree_prefill,
232+
std::vector<PublicDataLeafValue>(),
233+
prefilled_nullifiers,
234+
initial_header_generator_point);
235+
236+
// Baseline world state with no prefilled nullifiers (the canonical empty genesis).
237+
WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
238+
239+
auto prefilled = ws_prefilled.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
240+
auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
241+
242+
// The prefilled nullifiers occupy the last slots of the 128-leaf initial prefill region (they replace padding
243+
// leaves rather than being appended), so the tree size stays 128 for both.
244+
EXPECT_EQ(prefilled.meta.size, 128);
245+
EXPECT_EQ(info.meta.size, 128);
246+
247+
// Seeding the nullifiers changes the nullifier-tree root away from the empty-genesis baseline.
248+
EXPECT_NE(prefilled.meta.root, info.meta.root);
249+
// The empty-genesis baseline root is unchanged from the canonical value, confirming that a default (empty)
250+
// prefilled-nullifiers list leaves the genesis nullifier-tree root bit-identical to today.
251+
EXPECT_EQ(info.meta.root, bb::fr("0x18935581a8ed73d08ffd00386fba55ba6c89f3ab848a76b8fedfa9034cee0454"));
252+
253+
// The seeded nullifiers are present in the tree.
254+
for (const auto& nullifier : prefilled_nullifiers) {
255+
assert_leaf_exists<NullifierLeafValue>(ws_prefilled,
256+
WorldStateRevision::committed(),
257+
MerkleTreeId::NULLIFIER_TREE,
258+
NullifierLeafValue(nullifier),
259+
true);
260+
}
261+
262+
std::filesystem::remove_all(data_dir_prefilled);
263+
}
264+
214265
TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledPublicData)
215266
{
216267
std::string data_dir_prefilled = random_temp_directory();
@@ -225,6 +276,7 @@ TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledPublicData)
225276
tree_heights,
226277
tree_prefill,
227278
prefilled_values,
279+
std::vector<bb::fr>(),
228280
initial_header_generator_point);
229281

230282
WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);

barretenberg/cpp/src/barretenberg/wsdb/cli.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ int parse_and_run_wsdb(int argc, char* argv[])
8383
msgpack_run_command->add_option(
8484
"--prefilled-public-data", prefilled_public_data_json, "Prefilled public data as JSON array");
8585

86+
// Prefilled nullifiers as JSON array of nullifier_hex strings
87+
std::string prefilled_nullifiers_json;
88+
msgpack_run_command->add_option(
89+
"--prefilled-nullifiers", prefilled_nullifiers_json, "Prefilled genesis nullifiers as JSON array");
90+
8691
uint64_t genesis_timestamp = 0;
8792
msgpack_run_command->add_option("--genesis-timestamp", genesis_timestamp, "Genesis block timestamp (default: 0)");
8893

@@ -121,6 +126,7 @@ int parse_and_run_wsdb(int argc, char* argv[])
121126
threads,
122127
initial_header_generator_point,
123128
prefilled_public_data_json,
129+
prefilled_nullifiers_json,
124130
genesis_timestamp,
125131
request_ring_size,
126132
response_ring_size);

barretenberg/cpp/src/barretenberg/wsdb/wsdb_ipc_server.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,34 @@ static std::vector<PublicDataLeafValue> parse_prefilled_public_data(const std::s
168168
return result;
169169
}
170170

171+
// ---------------------------------------------------------------------------
172+
// Parse prefilled nullifiers from JSON: ["nullifier_hex",...]
173+
// Each hex string is a 64-char (32-byte) hex-encoded field element.
174+
// ---------------------------------------------------------------------------
175+
176+
static std::vector<fr> parse_prefilled_nullifiers(const std::string& json)
177+
{
178+
std::vector<fr> result;
179+
if (json.empty() || json == "[]") {
180+
return result;
181+
}
182+
183+
std::string current;
184+
bool in_string = false;
185+
186+
for (char c : json) {
187+
if (c == '"') {
188+
in_string = !in_string;
189+
} else if (in_string) {
190+
current += c;
191+
} else if ((c == ',' || c == ']') && !current.empty()) {
192+
result.push_back(hex_to_fr(current));
193+
current.clear();
194+
}
195+
}
196+
return result;
197+
}
198+
171199
// ---------------------------------------------------------------------------
172200
// IPC server execution
173201
// ---------------------------------------------------------------------------
@@ -180,6 +208,7 @@ int execute_wsdb_server(const std::string& input_path,
180208
uint32_t threads,
181209
uint32_t initial_header_generator_point,
182210
const std::string& prefilled_public_data_json,
211+
const std::string& prefilled_nullifiers_json,
183212
uint64_t genesis_timestamp,
184213
size_t request_ring_size,
185214
size_t response_ring_size)
@@ -211,6 +240,14 @@ int execute_wsdb_server(const std::string& input_path,
211240
std::cerr << "Parsed " << prefilled_public_data.size() << " prefilled public data entries" << '\n';
212241
}
213242

243+
// Parse prefilled nullifiers: JSON array of "nullifier_hex" strings. The caller (TS world-state) passes the same
244+
// canonical genesis nullifiers it seeds via the napi path, so the IPC genesis nullifier-tree root matches.
245+
std::vector<bb::fr> prefilled_nullifiers;
246+
if (!prefilled_nullifiers_json.empty()) {
247+
prefilled_nullifiers = parse_prefilled_nullifiers(prefilled_nullifiers_json);
248+
std::cerr << "Parsed " << prefilled_nullifiers.size() << " prefilled nullifiers" << '\n';
249+
}
250+
214251
// Create WorldState
215252
std::cerr << "Creating WorldState at " << data_dir << " with " << threads << " threads" << '\n';
216253
auto ws = std::make_unique<WorldState>(threads,
@@ -219,6 +256,7 @@ int execute_wsdb_server(const std::string& input_path,
219256
tree_height,
220257
tree_prefill,
221258
prefilled_public_data,
259+
prefilled_nullifiers,
222260
initial_header_generator_point,
223261
genesis_timestamp);
224262

barretenberg/cpp/src/barretenberg/wsdb/wsdb_ipc_server.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ int execute_wsdb_server(const std::string& input_path,
2020
uint32_t threads,
2121
uint32_t initial_header_generator_point,
2222
const std::string& prefilled_public_data_json,
23+
const std::string& prefilled_nullifiers_json,
2324
uint64_t genesis_timestamp,
2425
size_t request_ring_size,
2526
size_t response_ring_size);

yarn-project/stdlib/src/world-state/genesis_data.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1+
import type { Fr } from '@aztec/foundation/curves/bn254';
2+
13
import type { PublicDataTreeLeaf } from '../trees/index.js';
24

35
/** Data used to initialize the genesis block, including prefilled public state and an optional timestamp. */
46
export type GenesisData = {
57
/** Public data tree leaves to pre-populate in the genesis state (e.g. fee juice balances). */
68
prefilledPublicData: PublicDataTreeLeaf[];
9+
/**
10+
* Nullifiers to pre-insert into the genesis nullifier tree. Optional; defaults to an empty list, which leaves the
11+
* nullifier tree at its canonical empty-genesis state so that production genesis roots are unchanged. When non-empty,
12+
* the leaves must be unique and strictly increasing in field value (the native world state enforces this before
13+
* construction). Test networks pass a non-empty list to seed e.g. standard-contract registration nullifiers.
14+
*/
15+
prefilledNullifiers?: Fr[];
716
/** Timestamp for the genesis block header. Defaults to 0 (canonical empty genesis) in production. */
817
genesisTimestamp: bigint;
918
};
1019

1120
/** An empty genesis data with no prefilled state and a zero timestamp. */
1221
export const EMPTY_GENESIS_DATA: GenesisData = {
1322
prefilledPublicData: [],
23+
prefilledNullifiers: [],
1424
genesisTimestamp: 0n,
1525
};
1626

0 commit comments

Comments
 (0)