Files
jcc-blockchain/scripts/generate-static-keys-run.js
T
BlockchainEngineer 6af3f606a2 fix(devnet): correct p2p ports, env typo, and missing validator volumes
- Fix docker-compose.yml p2p port mappings (30304:30303, 30305:30303)
- Fix typo BESUS_GENESIS_FILE -> BESU_GENESIS_FILE
- Add missing volumes/environment to validator2 and validator3 services
- Remove outdated package-lock.json to let CI regenerate cleanly
- Validate genesis determinism and extraData alignment

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 05:03:07 +00:00

161 lines
4.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);
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');
}
function privateKeyToNodeId(privKey) {
const pub = secp256k1.getPublicKey(seedToBuffer(privKey), false);
const pubNoHeader = pub.slice(1);
const hash = keccak256(pubNoHeader);
return Buffer.from(hash).toString('hex');
}
function enodeFromPrivateKey(privKey, host, port = 30303) {
return `enode://${privateKeyToNodeId(privKey)}@${host}:${port}`;
}
const seed = process.env.DEVNET_SEED || 'jcc-devnet-v1';
const outDir = process.argv[2] || path.join(__dirname, '..', 'devnet');
function seedToTimestamp(s) {
let buf = Buffer.from(s, 'utf8');
for (let i = 0; i < 50; i++) buf = createHash('sha256').update(buf).digest();
const ts = buf.readUInt32BE(0);
return '0x' + ts.toString(16);
}
const DETERMINISTIC_TIMESTAMP = seedToTimestamp(seed);
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),
encodedValidators,
[],
Buffer.alloc(8, 0),
[]
];
const extraData = '0x' + Buffer.from(RLP.encode(extraDataItems)).toString('hex');
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: DETERMINISTIC_TIMESTAMP,
gasLimit: '0x1fffffffffffff',
difficulty: '0x1',
mixHash: '0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365',
coinbase: '0x' + '00'.repeat(20),
alloc,
extraData
};
const configDir = path.join(outDir, 'besu-config');
fs.mkdirSync(configDir, { recursive: true });
fs.writeFileSync(path.join(configDir, 'genesis.json'), JSON.stringify(genesis, null, 2), 'utf8');
const staticNodes = validators.map(v => enodeFromPrivateKey(v.privateKey, v.name, 30303));
fs.writeFileSync(path.join(configDir, 'static-nodes.json'), JSON.stringify(staticNodes, null, 2), 'utf8');
const summary = {
seed,
validators: validators.map(v => ({
name: v.name,
privateKey: v.privateKey,
address: v.address,
nodeId: privateKeyToNodeId(v.privateKey),
enode: enodeFromPrivateKey(v.privateKey, v.name, 30303)
})),
extraData,
genesisFile: 'besu-config/genesis.json',
staticNodesFile: 'besu-config/static-nodes.json'
};
fs.writeFileSync(path.join(configDir, 'nodes-summary.json'), JSON.stringify(summary, null, 2), 'utf8');
console.log('Devnet config generated successfully.');
console.log(JSON.stringify(summary, null, 2));