Files
jcc-blockchain/scripts/generate-static-keys-run.js
T
BlockchainEngineer 3b228e2e33 feat(protocol): scaffold devnet, monitoring, rpc gateway, and load tests
- 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>
2026-05-07 04:23:52 +00:00

126 lines
3.9 KiB
JavaScript

const { createHash } = require('crypto');
const fs = require('fs');
const path = require('path');
let secp256k1 = null;
let keccak256 = null;
let RLP = null;
try {
({ secp256k1 } = require('ethereum-cryptography/secp256k1'));
({ keccak256 } = require('ethereum-cryptography/keccak'));
RLP = require('@ethereumjs/rlp').RLP;
} catch (e) {
console.error('ERROR: key generation deps not available.');
console.error('Run: npm install ethereum-cryptography @ethereumjs/rlp');
process.exit(1);
}
function derivePrivateKey(seed, label) {
let buf = Buffer.from(`${seed}-${label}`, 'utf8');
for (let i = 0; i < 1000; i++) buf = createHash('sha256').update(buf).digest();
return '0x' + buf.toString('hex');
}
function seedToBuffer(s) {
if (typeof s === 'string') return Buffer.from(s.slice(2), 'hex');
return s;
}
function privateKeyToAddress(privKey) {
const pub = secp256k1.getPublicKey(seedToBuffer(privKey), false);
const pubNoHeader = pub.slice(1); // 64 bytes
const hash = keccak256(pubNoHeader);
return '0x' + Buffer.from(hash).subarray(-20).toString('hex');
}
function padHexTo(s, n) {
s = s.startsWith('0x') ? s.slice(2) : s;
return s.padStart(n * 2, '0');
}
const seed = process.env.DEVNET_SEED || 'jcc-devnet-v1';
const outDir = process.argv[2] || path.join(__dirname, '..', 'devnet');
// Node.js util for hex bytes to Buffer
function hexToBuf(hex) {
return Buffer.from(hex.startsWith('0x') ? hex.slice(2) : hex, 'hex');
}
const validators = [];
for (let i = 1; i <= 3; i++) {
const priv = derivePrivateKey(seed, `validator-${i}`);
const addr = privateKeyToAddress(priv);
validators.push({ name: `validator${i}`, privateKey: priv, address: addr });
}
// Write key files
for (const v of validators) {
const dir = path.join(outDir, 'validator-keys', v.name);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'key'), v.privateKey.slice(2), 'utf8');
fs.writeFileSync(path.join(dir, 'address'), v.address, 'utf8');
const pub = secp256k1.getPublicKey(seedToBuffer(v.privateKey), false);
fs.writeFileSync(path.join(dir, 'key.pub'), Buffer.from(pub).toString('hex'), 'utf8');
}
// Build extraData via RLP
const encodedValidators = validators.map(v => hexToBuf(padHexTo(v.address, 20)));
const extraDataItems = [
Buffer.alloc(32, 0), // vanity
encodedValidators,
[], // vote
Buffer.alloc(8, 0), // round
[] // seals
];
const extraData = '0x' + Buffer.from(RLP.encode(extraDataItems)).toString('hex');
// Build allocations
const alloc = {};
for (const v of validators) {
alloc[v.address.slice(2)] = { comment: v.name, balance: '0xad78ebc5ac6200000' };
}
alloc['f39Fd6e51aad88F6F4ce6aB8827279cffFb92266'] = { comment: 'Anvil default #0', balance: '0x56bc75e2d63100000' };
const genesis = {
config: {
chainId: 1337,
homesteadBlock: 0,
eip150Block: 0,
eip150Hash: '0x' + '00'.repeat(32),
eip155Block: 0,
eip158Block: 0,
byzantiumBlock: 0,
constantinopleBlock: 0,
petersburgBlock: 0,
istanbulBlock: 0,
muirGlacierBlock: 0,
berlinBlock: 0,
londonBlock: 0,
zeroBaseFee: true,
ibft2: {
blockperiodseconds: 2,
epochlength: 30000,
requesttimeoutseconds: 4,
blockreward: '0x0',
miningbeneficiary: '0x' + '00'.repeat(20)
}
},
nonce: '0x0',
timestamp: '0x' + Math.floor(Date.now() / 1000).toString(16),
gasLimit: '0x1fffffffffffff',
difficulty: '0x1',
mixHash: '0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365',
coinbase: '0x' + '00'.repeat(20),
alloc,
extraData
};
fs.writeFileSync(path.join(outDir, 'besu-config', 'genesis.json'), JSON.stringify(genesis, null, 2), 'utf8');
const summary = { seed, validators, extraData, genesisFile: 'besu-config/genesis.json' };
fs.writeFileSync(path.join(outDir, 'besu-config', 'nodes-summary.json'), JSON.stringify(summary, null, 2), 'utf8');
console.log('Devnet config generated successfully.');
console.log(JSON.stringify(summary, null, 2));