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
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
std::unordered_map<MerkleTreeId, uint32_t> tree_height;
std::unordered_map<MerkleTreeId, index_t> tree_prefill;
std::vector<PublicDataLeafValue> prefilled_public_data;
std::vector<bb::fr> prefilled_nullifiers;
std::vector<MerkleTreeId> tree_ids{
MerkleTreeId::NULLIFIER_TREE, MerkleTreeId::NOTE_HASH_TREE, MerkleTreeId::PUBLIC_DATA_TREE,
MerkleTreeId::L1_TO_L2_MESSAGE_TREE, MerkleTreeId::ARCHIVE,
Expand Down Expand Up @@ -112,15 +113,36 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
throw Napi::TypeError::New(env, "Prefilled public data must be an array");
}

size_t initial_header_generator_point_index = 4;
size_t prefilled_nullifiers_index = 4;
if (info.Length() > prefilled_nullifiers_index && info[prefilled_nullifiers_index].IsArray()) {
Napi::Array arr = info[prefilled_nullifiers_index].As<Napi::Array>();
for (uint32_t i = 0; i < arr.Length(); ++i) {
if (!arr.Get(i).IsBuffer()) {
throw Napi::TypeError::New(env, "Prefilled nullifier value must be a buffer");
}
Napi::Buffer<uint8_t> nullifier_buf = arr.Get(i).As<Napi::Buffer<uint8_t>>();
if (nullifier_buf.Length() != 32) {
throw Napi::TypeError::New(env, "Prefilled nullifier value must be a 32-byte buffer");
}
uint256_t nullifier = 0;
for (size_t j = 0; j < 32; ++j) {
nullifier = (nullifier << 8) | nullifier_buf[j];
}
prefilled_nullifiers.emplace_back(nullifier);
}
} else {
throw Napi::TypeError::New(env, "Prefilled nullifiers must be an array");
}

size_t initial_header_generator_point_index = 5;
if (info.Length() > initial_header_generator_point_index && info[initial_header_generator_point_index].IsNumber()) {
initial_header_generator_point = info[initial_header_generator_point_index].As<Napi::Number>().Uint32Value();
} else {
throw Napi::TypeError::New(env, "Header generator point needs to be a number");
}

uint64_t genesis_timestamp = 0;
size_t genesis_timestamp_index = 5;
size_t genesis_timestamp_index = 6;
if (info.Length() > genesis_timestamp_index) {
if (info[genesis_timestamp_index].IsNumber()) {
genesis_timestamp = static_cast<uint64_t>(info[genesis_timestamp_index].As<Napi::Number>().Int64Value());
Expand All @@ -130,7 +152,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
}

// optional parameters
size_t map_size_index = 6;
size_t map_size_index = 7;
if (info.Length() > map_size_index) {
if (info[map_size_index].IsObject()) {
Napi::Object obj = info[map_size_index].As<Napi::Object>();
Expand Down Expand Up @@ -160,7 +182,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
}
}

size_t thread_pool_size_index = 7;
size_t thread_pool_size_index = 8;
if (info.Length() > thread_pool_size_index) {
if (!info[thread_pool_size_index].IsNumber()) {
throw Napi::TypeError::New(env, "Thread pool size must be a number");
Expand All @@ -173,7 +195,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
// commits never block on fsync, files stay sparse, and a crash mid-write yields an
// unrecoverable env. Intended for throwaway scratch state (TXE test sessions).
bool ephemeral = false;
size_t ephemeral_index = 8;
size_t ephemeral_index = 9;
if (info.Length() > ephemeral_index) {
if (!info[ephemeral_index].IsBoolean()) {
throw Napi::TypeError::New(env, "Ephemeral flag must be a boolean");
Expand All @@ -187,6 +209,7 @@ WorldStateWrapper::WorldStateWrapper(const Napi::CallbackInfo& info)
tree_height,
tree_prefill,
prefilled_public_data,
prefilled_nullifiers,
initial_header_generator_point,
genesis_timestamp,
ephemeral);
Expand Down
16 changes: 14 additions & 2 deletions barretenberg/cpp/src/barretenberg/world_state/world_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
const std::unordered_map<MerkleTreeId, uint32_t>& tree_heights,
const std::unordered_map<MerkleTreeId, index_t>& tree_prefill,
const std::vector<PublicDataLeafValue>& prefilled_public_data,
const std::vector<bb::fr>& prefilled_nullifiers,
uint32_t initial_header_generator_point,
uint64_t genesis_timestamp,
bool ephemeral)
Expand All @@ -51,7 +52,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
{
// We set the max readers to be high, at least the number of given threads or the default if higher
uint64_t maxReaders = std::max(thread_pool_size, DEFAULT_MIN_NUMBER_OF_READERS);
create_canonical_fork(data_dir, map_size, prefilled_public_data, maxReaders, ephemeral);
create_canonical_fork(data_dir, map_size, prefilled_public_data, prefilled_nullifiers, maxReaders, ephemeral);
try {
attempt_tree_resync();
} catch (std::exception& e) {
Expand All @@ -73,6 +74,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
tree_heights,
tree_prefill,
std::vector<PublicDataLeafValue>(),
std::vector<bb::fr>(),
initial_header_generator_point,
genesis_timestamp,
ephemeral)
Expand All @@ -84,6 +86,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
const std::unordered_map<MerkleTreeId, uint32_t>& tree_heights,
const std::unordered_map<MerkleTreeId, index_t>& tree_prefill,
const std::vector<PublicDataLeafValue>& prefilled_public_data,
const std::vector<bb::fr>& prefilled_nullifiers,
uint32_t initial_header_generator_point,
uint64_t genesis_timestamp,
bool ephemeral)
Expand All @@ -99,6 +102,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
tree_heights,
tree_prefill,
prefilled_public_data,
prefilled_nullifiers,
initial_header_generator_point,
genesis_timestamp,
ephemeral)
Expand All @@ -118,6 +122,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
tree_heights,
tree_prefill,
std::vector<PublicDataLeafValue>(),
std::vector<bb::fr>(),
initial_header_generator_point,
genesis_timestamp,
ephemeral)
Expand All @@ -126,6 +131,7 @@ WorldState::WorldState(uint64_t thread_pool_size,
void WorldState::create_canonical_fork(const std::string& dataDir,
const std::unordered_map<MerkleTreeId, uint64_t>& dbSize,
const std::vector<PublicDataLeafValue>& prefilled_public_data,
const std::vector<bb::fr>& prefilled_nullifiers,
uint64_t maxReaders,
bool ephemeral)
{
Expand All @@ -148,9 +154,15 @@ void WorldState::create_canonical_fork(const std::string& dataDir,
{
uint32_t levels = _tree_heights.at(MerkleTreeId::NULLIFIER_TREE);
index_t initial_size = _initial_tree_size.at(MerkleTreeId::NULLIFIER_TREE);
std::vector<NullifierLeafValue> prefilled_nullifier_leaves;
prefilled_nullifier_leaves.reserve(prefilled_nullifiers.size());
for (const auto& nullifier : prefilled_nullifiers) {
prefilled_nullifier_leaves.emplace_back(nullifier);
}
auto store = std::make_unique<NullifierStore>(
getMerkleTreeName(MerkleTreeId::NULLIFIER_TREE), levels, _persistentStores->nullifierStore);
auto tree = std::make_unique<NullifierTree>(std::move(store), _workers, initial_size);
auto tree =
std::make_unique<NullifierTree>(std::move(store), _workers, initial_size, prefilled_nullifier_leaves);
fork->_trees.insert({ MerkleTreeId::NULLIFIER_TREE, TreeWithStore(std::move(tree)) });
}
{
Expand Down
6 changes: 6 additions & 0 deletions barretenberg/cpp/src/barretenberg/world_state/world_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,15 @@ class WorldState {
const std::unordered_map<MerkleTreeId, uint32_t>& tree_heights,
const std::unordered_map<MerkleTreeId, index_t>& tree_prefill,
const std::vector<PublicDataLeafValue>& prefilled_public_data,
const std::vector<bb::fr>& prefilled_nullifiers,
uint32_t initial_header_generator_point,
uint64_t genesis_timestamp = 0,
bool ephemeral = false);

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

Expand Down
52 changes: 52 additions & 0 deletions barretenberg/cpp/src/barretenberg/world_state/world_state.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,57 @@ TEST_F(WorldStateTest, GetInitialTreeInfoForAllTrees)
}
}

TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledNullifiers)
{
// Prefilled nullifier leaves must be unique and strictly increasing, and larger than the padding leaves that fill
// the initial 128-leaf prefill region (whose keys are the low integers 0..127), so we use full-size field values.
std::vector<bb::fr> prefilled_nullifiers = {
bb::fr("0x073b5e41abe9d7f8466bca9c81c9572b558f953bbd70081317f6a80ac65f3dd5"),
bb::fr("0x0d99507b7ecac720c73bf197a0e7366a5ed80c1c1b0afe8ff8c6ecc7b5a7aefe"),
bb::fr("0x1c0bf82e0c51834780e61ef091b17e3a1d39ae891db7a70bfdb5221f134996ac"),
};

std::string data_dir_prefilled = random_temp_directory();
std::filesystem::create_directories(data_dir_prefilled);

WorldState ws_prefilled(thread_pool_size,
data_dir_prefilled,
map_size,
tree_heights,
tree_prefill,
std::vector<PublicDataLeafValue>(),
prefilled_nullifiers,
initial_header_generator_point);

// Baseline world state with no prefilled nullifiers (the canonical empty genesis).
WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);

auto prefilled = ws_prefilled.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);

// The prefilled nullifiers occupy the last slots of the 128-leaf initial prefill region (they replace padding
// leaves rather than being appended), so the tree size stays 128 for both.
EXPECT_EQ(prefilled.meta.size, 128);
EXPECT_EQ(info.meta.size, 128);

// Seeding the nullifiers changes the nullifier-tree root away from the empty-genesis baseline.
EXPECT_NE(prefilled.meta.root, info.meta.root);
// The empty-genesis baseline root is unchanged from the canonical value, confirming that a default (empty)
// prefilled-nullifiers list leaves the genesis nullifier-tree root bit-identical to today.
EXPECT_EQ(info.meta.root, bb::fr("0x18935581a8ed73d08ffd00386fba55ba6c89f3ab848a76b8fedfa9034cee0454"));

// The seeded nullifiers are present in the tree.
for (const auto& nullifier : prefilled_nullifiers) {
assert_leaf_exists<NullifierLeafValue>(ws_prefilled,
WorldStateRevision::committed(),
MerkleTreeId::NULLIFIER_TREE,
NullifierLeafValue(nullifier),
true);
}

std::filesystem::remove_all(data_dir_prefilled);
}

TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledPublicData)
{
std::string data_dir_prefilled = random_temp_directory();
Expand All @@ -225,6 +276,7 @@ TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledPublicData)
tree_heights,
tree_prefill,
prefilled_values,
std::vector<bb::fr>(),
initial_header_generator_point);

WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
Expand Down
6 changes: 6 additions & 0 deletions barretenberg/cpp/src/barretenberg/wsdb/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ int parse_and_run_wsdb(int argc, char* argv[])
msgpack_run_command->add_option(
"--prefilled-public-data", prefilled_public_data_json, "Prefilled public data as JSON array");

// Prefilled nullifiers as JSON array of nullifier_hex strings
std::string prefilled_nullifiers_json;
msgpack_run_command->add_option(
"--prefilled-nullifiers", prefilled_nullifiers_json, "Prefilled genesis nullifiers as JSON array");

uint64_t genesis_timestamp = 0;
msgpack_run_command->add_option("--genesis-timestamp", genesis_timestamp, "Genesis block timestamp (default: 0)");

Expand Down Expand Up @@ -121,6 +126,7 @@ int parse_and_run_wsdb(int argc, char* argv[])
threads,
initial_header_generator_point,
prefilled_public_data_json,
prefilled_nullifiers_json,
genesis_timestamp,
request_ring_size,
response_ring_size);
Expand Down
38 changes: 38 additions & 0 deletions barretenberg/cpp/src/barretenberg/wsdb/wsdb_ipc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,34 @@ static std::vector<PublicDataLeafValue> parse_prefilled_public_data(const std::s
return result;
}

// ---------------------------------------------------------------------------
// Parse prefilled nullifiers from JSON: ["nullifier_hex",...]
// Each hex string is a 64-char (32-byte) hex-encoded field element.
// ---------------------------------------------------------------------------

static std::vector<fr> parse_prefilled_nullifiers(const std::string& json)
{
std::vector<fr> result;
if (json.empty() || json == "[]") {
return result;
}

std::string current;
bool in_string = false;

for (char c : json) {
if (c == '"') {
in_string = !in_string;
} else if (in_string) {
current += c;
} else if ((c == ',' || c == ']') && !current.empty()) {
result.push_back(hex_to_fr(current));
current.clear();
}
}
return result;
}

// ---------------------------------------------------------------------------
// IPC server execution
// ---------------------------------------------------------------------------
Expand All @@ -180,6 +208,7 @@ int execute_wsdb_server(const std::string& input_path,
uint32_t threads,
uint32_t initial_header_generator_point,
const std::string& prefilled_public_data_json,
const std::string& prefilled_nullifiers_json,
uint64_t genesis_timestamp,
size_t request_ring_size,
size_t response_ring_size)
Expand Down Expand Up @@ -211,6 +240,14 @@ int execute_wsdb_server(const std::string& input_path,
std::cerr << "Parsed " << prefilled_public_data.size() << " prefilled public data entries" << '\n';
}

// Parse prefilled nullifiers: JSON array of "nullifier_hex" strings. The caller (TS world-state) passes the same
// canonical genesis nullifiers it seeds via the napi path, so the IPC genesis nullifier-tree root matches.
std::vector<bb::fr> prefilled_nullifiers;
if (!prefilled_nullifiers_json.empty()) {
prefilled_nullifiers = parse_prefilled_nullifiers(prefilled_nullifiers_json);
std::cerr << "Parsed " << prefilled_nullifiers.size() << " prefilled nullifiers" << '\n';
}

// Create WorldState
std::cerr << "Creating WorldState at " << data_dir << " with " << threads << " threads" << '\n';
auto ws = std::make_unique<WorldState>(threads,
Expand All @@ -219,6 +256,7 @@ int execute_wsdb_server(const std::string& input_path,
tree_height,
tree_prefill,
prefilled_public_data,
prefilled_nullifiers,
initial_header_generator_point,
genesis_timestamp);

Expand Down
1 change: 1 addition & 0 deletions barretenberg/cpp/src/barretenberg/wsdb/wsdb_ipc_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ int execute_wsdb_server(const std::string& input_path,
uint32_t threads,
uint32_t initial_header_generator_point,
const std::string& prefilled_public_data_json,
const std::string& prefilled_nullifiers_json,
uint64_t genesis_timestamp,
size_t request_ring_size,
size_t response_ring_size);
Expand Down
6 changes: 3 additions & 3 deletions barretenberg/sol/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ gas_limit = 900000000000000000
bytecode_hash = "none"
evm_version = "cancun"

[fuzz]
runs = 2

# Use the pre-downloaded solc binary from l1-contracts. l1-contracts' build is
# the single owner of the svm download; pointing other forge projects at the
# same binary avoids parallel svm downloads racing on ~/.svm.
solc = "../../l1-contracts/solc-0.8.30"

[fuzz]
runs = 2

[lint]
ignore = ["./lib/**"]
exclude_lints = [
Expand Down
Loading
Loading