Skip to content

Commit 947b220

Browse files
authored
chore(port): forward-port v5-next e2e/testing backlog to next (#24930)
Forward-ports the **end-to-end / testing** slice of the v5-next → next backlog (work merged to `v5-next` after the ~2026-07-08 cut that reshaped `next`). ## Applied (clean cherry-picks, chronological) - #24518 docs(e2e): update READMEs for the consolidated suite layout - #24534 test(e2e): instrument and diagnose bot suite setup cost - #24566 perf(e2e): warp dead waits in multi-node recovery and proving tests - #24570 perf(e2e): shrink e2e slot times - #24564 perf(e2e): seed BananaFPC fee juice at genesis instead of bridging - #24568 perf(e2e): seed standard contracts at genesis - #24597 chore: add writing-e2e-tests skill - #24590 feat(e2e): interactive handshake e2e - #24671 test: fix proof_boundary startup race ## ⚠️ Needs owner conflict-resolution (conflict against reshaped `next`; not included here) Cherry-pick onto this branch and resolve: - [ ] #24569 `git cherry-pick -x 1d280af` — perf(e2e): overlap and batch setup txs in e2e harnesses - [ ] #24479 `git cherry-pick -x da9ac1c` — feat: assert non revertible phase when setting fee payer Part of the manual v5-next backlog sweep. Draft until owners resolve the conflicts above and CI is green.
2 parents 6295a38 + 55b1329 commit 947b220

56 files changed

Lines changed: 1400 additions & 156 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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
@@ -65,6 +65,11 @@ int parse_and_run_wsdb(int argc, char* argv[])
6565
msgpack_run_command->add_option(
6666
"--prefilled-public-data", prefilled_public_data_json, "Prefilled public data as JSON array");
6767

68+
// Prefilled nullifiers as JSON array of nullifier_hex strings
69+
std::string prefilled_nullifiers_json;
70+
msgpack_run_command->add_option(
71+
"--prefilled-nullifiers", prefilled_nullifiers_json, "Prefilled genesis nullifiers as JSON array");
72+
6873
uint64_t genesis_timestamp = 0;
6974
msgpack_run_command->add_option("--genesis-timestamp", genesis_timestamp, "Genesis block timestamp (default: 0)");
7075

@@ -98,6 +103,7 @@ int parse_and_run_wsdb(int argc, char* argv[])
98103
threads,
99104
initial_header_generator_point,
100105
prefilled_public_data_json,
106+
prefilled_nullifiers_json,
101107
genesis_timestamp,
102108
request_ring_size,
103109
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
@@ -130,6 +130,34 @@ static std::vector<PublicDataLeafValue> parse_prefilled_public_data(const std::s
130130
return result;
131131
}
132132

133+
// ---------------------------------------------------------------------------
134+
// Parse prefilled nullifiers from JSON: ["nullifier_hex",...]
135+
// Each hex string is a 64-char (32-byte) hex-encoded field element.
136+
// ---------------------------------------------------------------------------
137+
138+
static std::vector<fr> parse_prefilled_nullifiers(const std::string& json)
139+
{
140+
std::vector<fr> result;
141+
if (json.empty() || json == "[]") {
142+
return result;
143+
}
144+
145+
std::string current;
146+
bool in_string = false;
147+
148+
for (char c : json) {
149+
if (c == '"') {
150+
in_string = !in_string;
151+
} else if (in_string) {
152+
current += c;
153+
} else if ((c == ',' || c == ']') && !current.empty()) {
154+
result.push_back(hex_to_fr(current));
155+
current.clear();
156+
}
157+
}
158+
return result;
159+
}
160+
133161
// ---------------------------------------------------------------------------
134162
// IPC server execution
135163
// ---------------------------------------------------------------------------
@@ -142,6 +170,7 @@ int execute_wsdb_server(const std::string& input_path,
142170
uint32_t threads,
143171
uint32_t initial_header_generator_point,
144172
const std::string& prefilled_public_data_json,
173+
const std::string& prefilled_nullifiers_json,
145174
uint64_t genesis_timestamp,
146175
size_t request_ring_size,
147176
size_t response_ring_size)
@@ -173,6 +202,14 @@ int execute_wsdb_server(const std::string& input_path,
173202
std::cerr << "Parsed " << prefilled_public_data.size() << " prefilled public data entries" << '\n';
174203
}
175204

205+
// Parse prefilled nullifiers: JSON array of "nullifier_hex" strings. The caller (TS world-state) passes the same
206+
// canonical genesis nullifiers it seeds via the napi path, so the IPC genesis nullifier-tree root matches.
207+
std::vector<bb::fr> prefilled_nullifiers;
208+
if (!prefilled_nullifiers_json.empty()) {
209+
prefilled_nullifiers = parse_prefilled_nullifiers(prefilled_nullifiers_json);
210+
std::cerr << "Parsed " << prefilled_nullifiers.size() << " prefilled nullifiers" << '\n';
211+
}
212+
176213
// Create WorldState
177214
std::cerr << "Creating WorldState at " << data_dir << " with " << threads << " threads" << '\n';
178215
auto ws = std::make_unique<WorldState>(threads,
@@ -181,6 +218,7 @@ int execute_wsdb_server(const std::string& input_path,
181218
tree_height,
182219
tree_prefill,
183220
prefilled_public_data,
221+
prefilled_nullifiers,
184222
initial_header_generator_point,
185223
genesis_timestamp);
186224

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);

docs/docs-developers/docs/resources/migration_notes.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,6 @@ Registering classes and instances are now separate, unvalidated operations. `reg
186186
The new class is used automatically once the upgrade takes effect on chain; no further PXE action is needed. Registering it beforehand is harmless: until the update activates, the node still resolves the contract's current class to the previous one, so it keeps running its old code.
187187

188188
- `pxe.getContractInstance(address)` and `wallet.getContractMetadata(address).instance` now return the contract's **address preimage**, which no longer includes `currentContractClassId`.
189-
190-
191189
### [Aztec.js] `AccountWithSecretKey` removed, read account keys from the `AccountManager` or PXE
192190

193191
`AccountWithSecretKey` was a thin wrapper that bundled an account's transaction signer with its master secret key, used mainly to print or export the secret. It has been removed, and `AccountManager.getAccount()` now returns the plain `Account` signer. The wrapper's extra methods are no longer available on that value:

noir-projects/aztec-nr/aztec/src/context/private_context.nr

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,22 @@ impl PrivateContext {
538538
/// Only one contract per transaction can declare itself as the fee payer, and it must have sufficient fee-juice
539539
/// balance (>= the gas limits specified in the TxContext) by the time we reach the public setup phase of the tx.
540540
///
541+
/// The fee payer must be elected during the setup (non-revertible) phase, i.e. before
542+
/// [`end_setup`](PrivateContext::end_setup) is called - this function asserts so. This is because any compensation
543+
/// collected by the fee payer during the revertible phase can be discarded if a public call later reverts, while
544+
/// the protocol still debits the fee payer's fee-juice balance. Note that `end_setup` does not need to be called
545+
/// by the electing function itself: it can be called later in the transaction (e.g. by the fee-juice contract when
546+
/// claiming fee juice that pays for the very same transaction).
541547
pub fn set_as_fee_payer(&mut self) {
548+
assert(!self.in_revertible_phase(), "fee payer must be elected during the setup phase");
542549
aztecnr_trace_log_format!("Setting {0} as fee payer")([self.this_address().to_field()]);
543550
self.is_fee_payer = true;
544551
}
545552

553+
/// Returns whether execution is currently in the revertible (app) phase of the transaction.
554+
///
555+
/// A transaction is in the revertible phase if [`end_setup`](PrivateContext::end_setup) has already been called -
556+
/// potentially by a different function of the same transaction.
546557
pub fn in_revertible_phase(&mut self) -> bool {
547558
let current_counter = self.side_effect_counter;
548559

0 commit comments

Comments
 (0)