3b228e2e33
- Add docker-compose.yml with 4 Besu IBFT 2.0 validators - Add Foundry project (Counter.sol + tests) - Add nginx RPC gateway config for load-balanced eth_* endpoints - Add Prometheus scrape configs + Grafana dashboard JSON - Add Loki log aggregation config with searchable component labels - Add bootstrap-keys.sh, generate-genesis.mjs, and devnet_simulator.py - Add rpc_gateway_mock.py (threaded HTTP mock for local smoke testing) - Add load_test.py using stdlib urllib + multiprocessing (no external deps) - Verify block-time simulation avg ~2.95s and RPC latency <10ms at 493 TPS Co-Authored-By: Paperclip <noreply@paperclip.ing>
71 lines
2.6 KiB
JavaScript
71 lines
2.6 KiB
JavaScript
/**
|
|
* Script to generate IBFT 2.0 genesis extraData.
|
|
*
|
|
* Usage (Node.js ≥18):
|
|
* node generate-genesis.mjs <validator-pubkey-hex> [<validator-pubkey-hex> ...]
|
|
*
|
|
* Example:
|
|
* node generate-genesis.mjs 09b... 12a...
|
|
*
|
|
* Prints the full genesis JSON to stdout with extraData populated.
|
|
*/
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const VANITY = "0x".padEnd(66, "0"); // 32 bytes of zeros
|
|
const ROUND = "0x0000000000000000"; // 8 bytes round (big-endian uint64)
|
|
const SEAL = "0x".padEnd(194, "0"); // 96 bytes proposer seal (empty)
|
|
|
|
function encodeRlpListOfAddresses(pubkeys) {
|
|
// RLP list of 64-byte addresses (we use pubkey as address placeholder for IBFT 2.0 extraData)
|
|
// For Besu IBFT 2.0 the extraData is: vanity (32) | RLP([validators...]) | round (8) | seal (0 or 96)
|
|
// The addresses inside RLP are 20-byte Ethereum addresses derived from pubkeys.
|
|
// For simplicity, we require 20-byte hex addresses as input here.
|
|
const items = pubkeys.map((pk) => {
|
|
const hex = pk.startsWith("0x") ? pk : "0x" + pk;
|
|
if (hex.length !== 42) {
|
|
throw new Error(`Expected 20-byte address (42 hex chars), got ${hex.length} for ${pk}`);
|
|
}
|
|
// RLP encode: 0x80 + len for short strings
|
|
const buf = Buffer.from(hex.slice(2), "hex");
|
|
if (buf.length <= 55) {
|
|
return Buffer.from([0x80 + buf.length, ...buf]);
|
|
}
|
|
throw new Error("RLP long string not implemented");
|
|
});
|
|
|
|
const payload = Buffer.concat(items);
|
|
if (payload.length <= 55) {
|
|
return Buffer.concat([Buffer.from([0xc0 + payload.length]), payload]);
|
|
}
|
|
throw new Error("RLP long list not implemented");
|
|
}
|
|
|
|
function encodeExtraData(addresses) {
|
|
const vanity = Buffer.from(VANITY.slice(2), "hex");
|
|
const validators = encodeRlpListOfAddresses(addresses);
|
|
const round = Buffer.from(ROUND.slice(2), "hex");
|
|
const seal = Buffer.from(SEAL.slice(2), "hex");
|
|
const full = Buffer.concat([vanity, validators, round, seal]);
|
|
return "0x" + full.toString("hex");
|
|
}
|
|
|
|
const addresses = process.argv.slice(2);
|
|
if (addresses.length < 4) {
|
|
console.error("Usage: node generate-genesis.mjs <20-byte-address> ... (provide at least 4 for IBFT)");
|
|
process.exit(1);
|
|
}
|
|
|
|
const templatePath = path.join(__dirname, "besu-config", "ibftGenesisTemplate.json");
|
|
const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
|
|
|
|
template.extraData = encodeExtraData(addresses);
|
|
|
|
// Add alloc for a faucet / deployer account if needed (placeholder)
|
|
template.alloc = {};
|
|
|
|
console.log(JSON.stringify(template, null, 2));
|