cyberia 3.0.2 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{.env.production → .env.example} +20 -2
- package/.github/workflows/engine-cyberia.cd.yml +41 -10
- package/.github/workflows/engine-cyberia.ci.yml +53 -14
- package/.github/workflows/ghpkg.ci.yml +1 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/hardhat.ci.yml +82 -0
- package/.github/workflows/npmpkg.ci.yml +37 -8
- package/.github/workflows/publish.ci.yml +5 -5
- package/.github/workflows/publish.cyberia.ci.yml +5 -5
- package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -3
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +3 -2
- package/.vscode/extensions.json +9 -8
- package/.vscode/settings.json +3 -2
- package/CHANGELOG.md +533 -290
- package/CLI-HELP.md +79 -53
- package/WHITE-PAPER.md +1540 -0
- package/bin/build.js +16 -11
- package/bin/cyberia.js +959 -8
- package/bin/deploy.js +103 -270
- package/bin/file.js +2 -1
- package/bin/index.js +959 -8
- package/bin/vs.js +3 -3
- package/conf.js +277 -77
- package/deployment.yaml +218 -4
- package/hardhat/.env.example +31 -0
- package/hardhat/README.md +531 -0
- package/hardhat/WHITE-PAPER.md +1540 -0
- package/hardhat/contracts/ObjectLayerToken.sol +391 -0
- package/hardhat/deployments/.gitkeep +0 -0
- package/hardhat/deployments/hardhat-ObjectLayerToken.json +11 -0
- package/hardhat/hardhat.config.js +136 -0
- package/hardhat/ignition/modules/ObjectLayerToken.js +21 -0
- package/hardhat/networks/besu-object-layer.network.json +138 -0
- package/hardhat/package-lock.json +7628 -0
- package/hardhat/package.json +45 -0
- package/hardhat/scripts/deployObjectLayerToken.js +98 -0
- package/hardhat/test/ObjectLayerToken.js +590 -0
- package/jsdoc.dd-cyberia.json +59 -0
- package/jsdoc.json +20 -13
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +490 -0
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +261 -0
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +132 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +52 -52
- package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
- package/manifests/pv-pvc-dd.yaml +1 -1
- package/package.json +60 -50
- package/proxy.yaml +128 -9
- package/pv-pvc.yaml +132 -0
- package/scripts/k3s-node-setup.sh +1 -1
- package/scripts/ports-ls.sh +2 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +3 -1
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +1 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +40 -7
- package/src/api/document/document.service.js +1 -1
- package/src/api/file/file.controller.js +3 -1
- package/src/api/file/file.service.js +28 -5
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +6 -2
- package/src/api/object-layer/object-layer.model.js +67 -21
- package/src/api/object-layer/object-layer.router.js +668 -42
- package/src/api/object-layer/object-layer.service.js +10 -16
- package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +1 -2
- package/src/api/user/user.router.js +10 -5
- package/src/api/user/user.service.js +7 -7
- package/src/cli/baremetal.js +6 -10
- package/src/cli/cloud-init.js +0 -3
- package/src/cli/db.js +54 -71
- package/src/cli/deploy.js +64 -12
- package/src/cli/env.js +5 -5
- package/src/cli/fs.js +0 -2
- package/src/cli/image.js +0 -3
- package/src/cli/index.js +41 -13
- package/src/cli/monitor.js +5 -6
- package/src/cli/repository.js +329 -46
- package/src/cli/run.js +210 -122
- package/src/cli/secrets.js +1 -3
- package/src/cli/ssh.js +1 -1
- package/src/client/Itemledger.index.js +1 -959
- package/src/client/Underpost.index.js +36 -0
- package/src/client/components/core/AgGrid.js +20 -5
- package/src/client/components/core/Alert.js +2 -2
- package/src/client/components/core/Content.js +22 -3
- package/src/client/components/core/Docs.js +30 -6
- package/src/client/components/core/FileExplorer.js +71 -4
- package/src/client/components/core/Input.js +1 -1
- package/src/client/components/core/Modal.js +22 -6
- package/src/client/components/core/PublicProfile.js +3 -3
- package/src/client/components/core/RichText.js +1 -2
- package/src/client/components/core/Router.js +34 -1
- package/src/client/components/core/Worker.js +1 -1
- package/src/client/components/cryptokoyn/CssCryptokoyn.js +63 -1
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +145 -119
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +64 -6
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +1 -0
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +44 -2
- package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +0 -1
- package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +64 -2
- package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +1 -0
- package/src/client/components/itemledger/CssItemledger.js +62 -0
- package/src/client/components/underpost/CommonUnderpost.js +29 -0
- package/src/client/components/underpost/CssUnderpost.js +281 -0
- package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +879 -0
- package/src/client/components/underpost/DocumentSearchProvider.js +448 -0
- package/src/client/components/underpost/ElementsUnderpost.js +38 -0
- package/src/client/components/underpost/LabGalleryUnderpost.js +82 -0
- package/src/client/components/underpost/LogInUnderpost.js +23 -0
- package/src/client/components/underpost/LogOutUnderpost.js +15 -0
- package/src/client/components/underpost/MenuUnderpost.js +691 -0
- package/src/client/components/underpost/RoutesUnderpost.js +47 -0
- package/src/client/components/underpost/SettingsUnderpost.js +16 -0
- package/src/client/components/underpost/SignUpUnderpost.js +9 -0
- package/src/client/components/underpost/SocketIoUnderpost.js +54 -0
- package/src/client/components/underpost/TranslateUnderpost.js +10 -0
- package/src/client/public/cryptokoyn/assets/logo/base-icon.png +0 -0
- package/src/client/public/cryptokoyn/browserconfig.xml +12 -0
- package/src/client/public/cryptokoyn/microdata.json +85 -0
- package/src/client/public/cryptokoyn/site.webmanifest +57 -0
- package/src/client/public/cryptokoyn/sitemap +3 -3
- package/src/client/public/default/sitemap +3 -3
- package/src/client/public/itemledger/browserconfig.xml +2 -2
- package/src/client/public/itemledger/manifest.webmanifest +4 -4
- package/src/client/public/itemledger/microdata.json +71 -0
- package/src/client/public/itemledger/sitemap +3 -3
- package/src/client/public/itemledger/yandex-browser-manifest.json +2 -2
- package/src/client/public/test/sitemap +3 -3
- package/src/client/services/object-layer/object-layer.management.js +23 -4
- package/src/client/ssr/body/404.js +15 -11
- package/src/client/ssr/body/500.js +15 -11
- package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +83 -0
- package/src/client/ssr/head/PwaItemledger.js +60 -0
- package/src/client/ssr/head/UnderpostScripts.js +6 -0
- package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
- package/src/client/ssr/pages/Test.js +11 -10
- package/src/client.build.js +0 -3
- package/src/client.dev.js +0 -3
- package/src/db/DataBaseProvider.js +17 -2
- package/src/db/mariadb/MariaDB.js +14 -9
- package/src/db/mongo/MongooseDB.js +17 -1
- package/src/index.js +1 -1
- package/src/proxy.js +0 -3
- package/src/runtime/express/Express.js +15 -9
- package/src/runtime/lampp/Lampp.js +6 -13
- package/src/server/auth.js +12 -14
- package/src/server/backup.js +2 -3
- package/src/server/besu-genesis-generator.js +1630 -0
- package/src/server/client-build-docs.js +126 -17
- package/src/server/client-build-live.js +9 -18
- package/src/server/client-build.js +203 -75
- package/src/server/client-dev-server.js +14 -13
- package/src/server/conf.js +376 -164
- package/src/server/cron.js +2 -1
- package/src/server/dns.js +28 -12
- package/src/server/downloader.js +0 -2
- package/src/server/logger.js +27 -9
- package/src/server/object-layer.js +92 -16
- package/src/server/peer.js +0 -2
- package/src/server/process.js +1 -50
- package/src/server/proxy.js +4 -8
- package/src/server/runtime.js +5 -8
- package/src/server/semantic-layer-generator.js +1 -0
- package/src/server/ssr.js +0 -3
- package/src/server/start.js +19 -12
- package/src/server/tls.js +0 -2
- package/src/server.js +0 -4
- package/.env.development +0 -43
- package/.env.test +0 -43
- package/hardhat/contracts/CryptoKoyn.sol +0 -59
- package/hardhat/contracts/ItemLedger.sol +0 -73
- package/hardhat/contracts/Lock.sol +0 -34
- package/hardhat/hardhat.config.cjs +0 -45
- package/hardhat/ignition/modules/Lock.js +0 -18
- package/hardhat/networks/cryptokoyn-itemledger.network.json +0 -29
- package/hardhat/scripts/deployCryptokoyn.cjs +0 -25
- package/hardhat/scripts/deployItemledger.cjs +0 -25
- package/hardhat/test/Lock.js +0 -126
- package/hardhat/white-paper.md +0 -581
- package/white-paper.md +0 -581
|
@@ -0,0 +1,1630 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Hyperledger Besu IBFT2 genesis and Kubernetes manifest generator
|
|
3
|
+
* for the Cyberia Online Object Layer ERC-1155 ecosystem.
|
|
4
|
+
*
|
|
5
|
+
* Generates fresh secp256k1 validator keys, computes public keys, enode URLs,
|
|
6
|
+
* IBFT2 extraData, genesis.json, and all K8s manifests required to deploy a
|
|
7
|
+
* new Besu chain instance to a kubeadm cluster.
|
|
8
|
+
*
|
|
9
|
+
* This eliminates hardcoded keys/hashes in manifests/besu/ and ensures every
|
|
10
|
+
* new deployment gets a clean, unique chain identity.
|
|
11
|
+
*
|
|
12
|
+
* @module src/server/besu-genesis-generator.js
|
|
13
|
+
* @namespace BesuGenesisGenerator
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import crypto from 'crypto';
|
|
17
|
+
import fs from 'fs-extra';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { keccak256 as ethersKeccak256 } from 'ethers';
|
|
20
|
+
import { loggerFactory } from '../server/logger.js';
|
|
21
|
+
import { shellExec } from '../server/process.js';
|
|
22
|
+
|
|
23
|
+
const logger = loggerFactory(import.meta);
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// secp256k1 key utilities (Node.js native crypto — no ethers dependency)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate a random secp256k1 private key (32 bytes hex, no 0x prefix).
|
|
31
|
+
* @returns {string} 64-char lowercase hex private key.
|
|
32
|
+
* @memberof BesuGenesisGenerator
|
|
33
|
+
*/
|
|
34
|
+
function generatePrivateKey() {
|
|
35
|
+
const ecdh = crypto.createECDH('secp256k1');
|
|
36
|
+
ecdh.generateKeys();
|
|
37
|
+
return ecdh.getPrivateKey('hex').padStart(64, '0');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Derive the uncompressed public key (sans 04 prefix) from a private key.
|
|
42
|
+
* Besu node public keys are 128-char hex (64 bytes) — the x‖y coordinates.
|
|
43
|
+
* @param {string} privateKeyHex - 64-char hex private key (no 0x).
|
|
44
|
+
* @returns {string} 128-char lowercase hex public key.
|
|
45
|
+
* @memberof BesuGenesisGenerator
|
|
46
|
+
*/
|
|
47
|
+
function derivePublicKey(privateKeyHex) {
|
|
48
|
+
const ecdh = crypto.createECDH('secp256k1');
|
|
49
|
+
ecdh.setPrivateKey(Buffer.from(privateKeyHex, 'hex'));
|
|
50
|
+
// getPublicKey returns 65 bytes (04 || x || y); strip the 04 prefix
|
|
51
|
+
const uncompressed = ecdh.getPublicKey('hex');
|
|
52
|
+
// uncompressed starts with '04' (1 byte tag)
|
|
53
|
+
return uncompressed.slice(2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Derive the Ethereum address from an uncompressed secp256k1 public key.
|
|
58
|
+
*
|
|
59
|
+
* The address is the last 20 bytes of the Keccak-256 hash of the raw
|
|
60
|
+
* (uncompressed, sans 04 prefix) public key bytes:
|
|
61
|
+
* `address = keccak256(pubKeyBytes)[12..31]`
|
|
62
|
+
*
|
|
63
|
+
* **Important:** Ethereum uses the *original* Keccak-256 (the pre-NIST
|
|
64
|
+
* submission), NOT NIST SHA3-256. NIST added different domain-separation
|
|
65
|
+
* padding when standardising SHA-3, so `crypto.createHash('sha3-256')`
|
|
66
|
+
* produces **wrong** addresses. We use `ethers.keccak256` which bundles
|
|
67
|
+
* a correct Keccak-256 implementation.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} publicKeyHex - 128-char hex public key (no 0x prefix).
|
|
70
|
+
* @returns {string} 40-char lowercase hex Ethereum address (no 0x prefix).
|
|
71
|
+
* @memberof BesuGenesisGenerator
|
|
72
|
+
*/
|
|
73
|
+
function publicKeyToAddress(publicKeyHex) {
|
|
74
|
+
// Ethereum addresses are derived using Keccak-256 (the original Keccak submission),
|
|
75
|
+
// NOT NIST SHA3-256. NIST SHA3-256 added different domain-separation padding,
|
|
76
|
+
// so crypto.createHash('sha3-256') produces WRONG addresses.
|
|
77
|
+
//
|
|
78
|
+
// Node.js's OpenSSL does not expose the original Keccak-256, so we use ethers
|
|
79
|
+
// which bundles a correct implementation.
|
|
80
|
+
//
|
|
81
|
+
// ethersKeccak256 expects a 0x-prefixed hex string or Uint8Array and returns
|
|
82
|
+
// a 0x-prefixed 64-char hex hash. The Ethereum address is the last 20 bytes.
|
|
83
|
+
const hash = ethersKeccak256(Buffer.from(publicKeyHex, 'hex')); // 0x-prefixed 64-char hex
|
|
84
|
+
return hash.slice(26); // skip '0x' + first 24 hex chars → last 40 hex chars (20 bytes)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// RLP encoding helpers (minimal, sufficient for IBFT2 extraData)
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* RLP-encode a single Buffer item.
|
|
93
|
+
* @param {Buffer} buf
|
|
94
|
+
* @returns {Buffer}
|
|
95
|
+
* @memberof BesuGenesisGenerator
|
|
96
|
+
*/
|
|
97
|
+
function rlpEncodeItem(buf) {
|
|
98
|
+
if (buf.length === 1 && buf[0] < 0x80) {
|
|
99
|
+
return buf;
|
|
100
|
+
}
|
|
101
|
+
if (buf.length <= 55) {
|
|
102
|
+
return Buffer.concat([Buffer.from([0x80 + buf.length]), buf]);
|
|
103
|
+
}
|
|
104
|
+
const lenBytes = encodeLength(buf.length);
|
|
105
|
+
return Buffer.concat([Buffer.from([0xb7 + lenBytes.length]), lenBytes, buf]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* RLP-encode a list of already-encoded items.
|
|
110
|
+
* @param {Buffer[]} items - Array of RLP-encoded items.
|
|
111
|
+
* @returns {Buffer}
|
|
112
|
+
* @memberof BesuGenesisGenerator
|
|
113
|
+
*/
|
|
114
|
+
function rlpEncodeList(items) {
|
|
115
|
+
const payload = Buffer.concat(items);
|
|
116
|
+
if (payload.length <= 55) {
|
|
117
|
+
return Buffer.concat([Buffer.from([0xc0 + payload.length]), payload]);
|
|
118
|
+
}
|
|
119
|
+
const lenBytes = encodeLength(payload.length);
|
|
120
|
+
return Buffer.concat([Buffer.from([0xf7 + lenBytes.length]), lenBytes, payload]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Encode a length as big-endian bytes (no leading zeroes).
|
|
125
|
+
* @param {number} len
|
|
126
|
+
* @returns {Buffer}
|
|
127
|
+
* @memberof BesuGenesisGenerator
|
|
128
|
+
*/
|
|
129
|
+
function encodeLength(len) {
|
|
130
|
+
const hex = len.toString(16);
|
|
131
|
+
const padded = hex.length % 2 === 0 ? hex : '0' + hex;
|
|
132
|
+
return Buffer.from(padded, 'hex');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// IBFT2 Extra Data
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Compute the IBFT2 extraData field for the genesis block.
|
|
141
|
+
*
|
|
142
|
+
* IBFT2 extraData = RLP([
|
|
143
|
+
* 32-byte vanity,
|
|
144
|
+
* [ ...validator addresses (each 20 bytes) ],
|
|
145
|
+
* vote (empty bytes),
|
|
146
|
+
* round (4-byte big-endian int, 0 for genesis),
|
|
147
|
+
* seals (empty list)
|
|
148
|
+
* ])
|
|
149
|
+
*
|
|
150
|
+
* Besu's IbftExtraDataCodec.decodeRaw() calls readInt() on the round field,
|
|
151
|
+
* which expects exactly 4 bytes. Encoding round as empty bytes (0x80) causes:
|
|
152
|
+
* RLPException: Cannot read a 4-byte int, expecting 4 bytes but current element is 0 bytes long
|
|
153
|
+
*
|
|
154
|
+
* @param {string[]} validatorAddresses - Array of 40-char hex addresses (no 0x).
|
|
155
|
+
* @returns {string} hex string with 0x prefix.
|
|
156
|
+
* @memberof BesuGenesisGenerator
|
|
157
|
+
*/
|
|
158
|
+
function computeIbft2ExtraData(validatorAddresses) {
|
|
159
|
+
// 32-byte zero vanity
|
|
160
|
+
const vanity = rlpEncodeItem(Buffer.alloc(32, 0));
|
|
161
|
+
|
|
162
|
+
// Validator list
|
|
163
|
+
const validators = rlpEncodeList(validatorAddresses.map((addr) => rlpEncodeItem(Buffer.from(addr, 'hex'))));
|
|
164
|
+
|
|
165
|
+
// Vote (empty bytes)
|
|
166
|
+
const vote = rlpEncodeItem(Buffer.alloc(0));
|
|
167
|
+
|
|
168
|
+
// Round — must be a 4-byte big-endian integer (0 for genesis block).
|
|
169
|
+
// Besu's IbftExtraDataCodec calls readInt() which demands exactly 4 bytes.
|
|
170
|
+
const round = rlpEncodeItem(Buffer.from([0x00, 0x00, 0x00, 0x00]));
|
|
171
|
+
|
|
172
|
+
// Seals (empty list)
|
|
173
|
+
const seals = rlpEncodeList([]);
|
|
174
|
+
|
|
175
|
+
const extraData = rlpEncodeList([vanity, validators, vote, round, seals]);
|
|
176
|
+
return '0x' + extraData.toString('hex');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Validator node key set
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @typedef {Object} ValidatorKeySet
|
|
185
|
+
* @property {number} index - 1-based validator index.
|
|
186
|
+
* @property {string} privateKey - 64-char hex private key.
|
|
187
|
+
* @property {string} publicKey - 128-char hex public key.
|
|
188
|
+
* @property {string} address - 40-char hex Ethereum address.
|
|
189
|
+
* @property {string} enodeDns - Full enode URL with K8s DNS hostname.
|
|
190
|
+
* @memberof BesuGenesisGenerator
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generate a complete set of validator keys.
|
|
195
|
+
* @param {number} count - Number of validators (default 4).
|
|
196
|
+
* @param {string} namespace - K8s namespace (default 'besu').
|
|
197
|
+
* @returns {ValidatorKeySet[]}
|
|
198
|
+
* @memberof BesuGenesisGenerator
|
|
199
|
+
*/
|
|
200
|
+
function generateValidatorKeys(count = 4, namespace = 'besu') {
|
|
201
|
+
const validators = [];
|
|
202
|
+
for (let i = 1; i <= count; i++) {
|
|
203
|
+
const privateKey = generatePrivateKey();
|
|
204
|
+
const publicKey = derivePublicKey(privateKey);
|
|
205
|
+
const address = publicKeyToAddress(publicKey);
|
|
206
|
+
const dnsHost = `validator${i}-0.besu-validator${i}.${namespace}.svc.cluster.local`;
|
|
207
|
+
const enodeDns = `enode://${publicKey}@${dnsHost}:30303`;
|
|
208
|
+
validators.push({
|
|
209
|
+
index: i,
|
|
210
|
+
privateKey,
|
|
211
|
+
publicKey,
|
|
212
|
+
address,
|
|
213
|
+
enodeDns,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return validators;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Genesis JSON
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @typedef {Object} GenesisOptions
|
|
225
|
+
* @property {number} [chainId=777771] - Chain ID for the network.
|
|
226
|
+
* @property {number} [blockPeriodSeconds=5] - IBFT2 block period.
|
|
227
|
+
* @property {number} [epochLength=30000] - IBFT2 epoch length.
|
|
228
|
+
* @property {number} [requestTimeoutSeconds=10] - IBFT2 request timeout.
|
|
229
|
+
* @property {string} [coinbaseAddress] - Coinbase/deployer address (40-char hex, no 0x).
|
|
230
|
+
* @property {string} [coinbaseBalance] - Hex balance string for coinbase (with 0x).
|
|
231
|
+
* @property {Object<string,string>} [additionalAlloc] - Extra alloc entries {address: balance}.
|
|
232
|
+
* @property {string} [gasLimit] - Hex gas limit (with 0x).
|
|
233
|
+
* @memberof BesuGenesisGenerator
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Build the genesis.json content for a Besu IBFT2 network.
|
|
238
|
+
* @param {ValidatorKeySet[]} validators
|
|
239
|
+
* @param {GenesisOptions} [opts]
|
|
240
|
+
* @returns {Object} genesis JSON object.
|
|
241
|
+
* @memberof BesuGenesisGenerator
|
|
242
|
+
*/
|
|
243
|
+
function buildGenesis(validators, opts = {}) {
|
|
244
|
+
const {
|
|
245
|
+
chainId = 777771,
|
|
246
|
+
blockPeriodSeconds = 5,
|
|
247
|
+
epochLength = 30000,
|
|
248
|
+
requestTimeoutSeconds = 10,
|
|
249
|
+
coinbaseAddress = '',
|
|
250
|
+
coinbaseBalance = '0x200000000000000000000000000000000000000000000000000000000000000',
|
|
251
|
+
additionalAlloc = {},
|
|
252
|
+
gasLimit = '0x1fffffffffffff',
|
|
253
|
+
} = opts;
|
|
254
|
+
|
|
255
|
+
const addresses = validators.map((v) => v.address);
|
|
256
|
+
const extraData = computeIbft2ExtraData(addresses);
|
|
257
|
+
|
|
258
|
+
const alloc = {};
|
|
259
|
+
|
|
260
|
+
// Coinbase / deployer account
|
|
261
|
+
if (coinbaseAddress) {
|
|
262
|
+
const cleanAddr = coinbaseAddress.replace(/^0x/, '');
|
|
263
|
+
alloc[cleanAddr] = {
|
|
264
|
+
comment: 'Coinbase / deployer account - receives initial ETH for gas on Cyberia private network',
|
|
265
|
+
balance: coinbaseBalance,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Dev/test accounts (standard Besu dev accounts)
|
|
270
|
+
const devAccounts = {
|
|
271
|
+
fe3b557e8fb62b89f4916b721be55ceb828dbd73: {
|
|
272
|
+
comment: 'Dev/test account 1',
|
|
273
|
+
balance: '0xad78ebc5ac6200000',
|
|
274
|
+
},
|
|
275
|
+
'627306090abaB3A6e1400e9345bC60c78a8BEf57': {
|
|
276
|
+
comment: 'Dev/test account 2',
|
|
277
|
+
balance: '90000000000000000000000',
|
|
278
|
+
},
|
|
279
|
+
f17f52151EbEF6C7334FAD080c5704D77216b732: {
|
|
280
|
+
comment: 'Dev/test account 3',
|
|
281
|
+
balance: '90000000000000000000000',
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
Object.assign(alloc, devAccounts);
|
|
285
|
+
|
|
286
|
+
// Additional allocations
|
|
287
|
+
for (const [addr, balance] of Object.entries(additionalAlloc)) {
|
|
288
|
+
alloc[addr.replace(/^0x/, '')] = { balance };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const timestamp = '0x' + Math.floor(Date.now() / 1000).toString(16);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
config: {
|
|
295
|
+
chainId,
|
|
296
|
+
berlinBlock: 0,
|
|
297
|
+
londonBlock: 0,
|
|
298
|
+
shanghaiTime: 0,
|
|
299
|
+
ibft2: {
|
|
300
|
+
blockperiodseconds: blockPeriodSeconds,
|
|
301
|
+
epochlength: epochLength,
|
|
302
|
+
requesttimeoutseconds: requestTimeoutSeconds,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
nonce: '0x0',
|
|
306
|
+
timestamp,
|
|
307
|
+
extraData,
|
|
308
|
+
gasLimit,
|
|
309
|
+
difficulty: '0x1',
|
|
310
|
+
mixHash: '0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365',
|
|
311
|
+
coinbase: coinbaseAddress
|
|
312
|
+
? `0x${coinbaseAddress.replace(/^0x/, '')}`
|
|
313
|
+
: '0x0000000000000000000000000000000000000000',
|
|
314
|
+
number: '0x0',
|
|
315
|
+
gasUsed: '0x0',
|
|
316
|
+
parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
317
|
+
alloc,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
// Kubernetes manifest generators
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Generate the namespace YAML.
|
|
327
|
+
* @param {string} namespace
|
|
328
|
+
* @returns {string}
|
|
329
|
+
* @memberof BesuGenesisGenerator
|
|
330
|
+
*/
|
|
331
|
+
function generateNamespaceYaml(namespace = 'besu') {
|
|
332
|
+
return `---
|
|
333
|
+
apiVersion: v1
|
|
334
|
+
kind: Namespace
|
|
335
|
+
metadata:
|
|
336
|
+
name: ${namespace}
|
|
337
|
+
labels:
|
|
338
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
339
|
+
app.kubernetes.io/managed-by: underpost
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Generate the genesis ConfigMap YAML.
|
|
345
|
+
* @param {Object} genesis - The genesis JSON object.
|
|
346
|
+
* @param {string} namespace
|
|
347
|
+
* @returns {string}
|
|
348
|
+
* @memberof BesuGenesisGenerator
|
|
349
|
+
*/
|
|
350
|
+
function generateGenesisConfigMapYaml(genesis, namespace = 'besu') {
|
|
351
|
+
const genesisStr = JSON.stringify(genesis, null, 2).replace(/^/gm, ' ').trimStart();
|
|
352
|
+
return `apiVersion: v1
|
|
353
|
+
kind: ConfigMap
|
|
354
|
+
metadata:
|
|
355
|
+
name: besu-genesis-configmap
|
|
356
|
+
labels:
|
|
357
|
+
app.kubernetes.io/name: besu
|
|
358
|
+
app.kubernetes.io/component: genesis
|
|
359
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
360
|
+
namespace: ${namespace}
|
|
361
|
+
data:
|
|
362
|
+
genesis.json: |-
|
|
363
|
+
${genesisStr}
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Generate the validators public key ConfigMap YAML.
|
|
369
|
+
* @param {ValidatorKeySet[]} validators
|
|
370
|
+
* @param {string} namespace
|
|
371
|
+
* @returns {string}
|
|
372
|
+
* @memberof BesuGenesisGenerator
|
|
373
|
+
*/
|
|
374
|
+
function generateValidatorsConfigMapYaml(validators, namespace = 'besu') {
|
|
375
|
+
const entries = validators.map((v) => ` validator${v.index}PubKey: "${v.publicKey}"`).join('\n');
|
|
376
|
+
return `apiVersion: v1
|
|
377
|
+
kind: ConfigMap
|
|
378
|
+
metadata:
|
|
379
|
+
name: besu-validators-configmap
|
|
380
|
+
labels:
|
|
381
|
+
app.kubernetes.io/name: besu
|
|
382
|
+
app.kubernetes.io/component: validators-config
|
|
383
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
384
|
+
namespace: ${namespace}
|
|
385
|
+
data:
|
|
386
|
+
${entries}
|
|
387
|
+
`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Generate the TOML config ConfigMap YAML with dynamic static-nodes.
|
|
392
|
+
* @param {ValidatorKeySet[]} validators
|
|
393
|
+
* @param {string} namespace
|
|
394
|
+
* @returns {string}
|
|
395
|
+
* @memberof BesuGenesisGenerator
|
|
396
|
+
*/
|
|
397
|
+
function generateConfigTomlConfigMapYaml(validators, namespace = 'besu') {
|
|
398
|
+
const staticNodes = validators.map((v) => {
|
|
399
|
+
const dnsHost = `validator${v.index}-0.besu-validator${v.index}.${namespace}.svc.cluster.local`;
|
|
400
|
+
return ` "${`enode://${v.publicKey}@${dnsHost}:30303`}"`;
|
|
401
|
+
});
|
|
402
|
+
const staticNodesJson = `[\n${staticNodes.join(',\n')}\n ]`;
|
|
403
|
+
|
|
404
|
+
return `apiVersion: v1
|
|
405
|
+
kind: ConfigMap
|
|
406
|
+
metadata:
|
|
407
|
+
name: besu-config-toml-configmap
|
|
408
|
+
labels:
|
|
409
|
+
app: besu-config-toml-configmap
|
|
410
|
+
namespace: ${namespace}
|
|
411
|
+
data:
|
|
412
|
+
static-nodes.json: |-
|
|
413
|
+
${staticNodesJson}
|
|
414
|
+
|
|
415
|
+
config.toml: |-
|
|
416
|
+
# Hyperledger Besu IBFT2 configuration for Cyberia Online Object Layer ecosystem.
|
|
417
|
+
# Designed for kubeadm-managed Kubernetes clusters.
|
|
418
|
+
#
|
|
419
|
+
# This configuration provides the foundation for the ERC-1155 ObjectLayerToken
|
|
420
|
+
# contract deployment and interaction on a private permissioned network.
|
|
421
|
+
|
|
422
|
+
# Node Information
|
|
423
|
+
data-path="/data"
|
|
424
|
+
genesis-file="/etc/genesis/genesis.json"
|
|
425
|
+
static-nodes-file="/etc/besu/static-nodes.json"
|
|
426
|
+
|
|
427
|
+
logging="INFO"
|
|
428
|
+
|
|
429
|
+
# Gas - private permissioned network uses zero gas price
|
|
430
|
+
min-gas-price=0
|
|
431
|
+
|
|
432
|
+
# P2P network
|
|
433
|
+
p2p-enabled=true
|
|
434
|
+
discovery-enabled=true
|
|
435
|
+
p2p-port=30303
|
|
436
|
+
max-peers=25
|
|
437
|
+
host-allowlist=["*"]
|
|
438
|
+
|
|
439
|
+
# Sync — private IBFT2 networks have fewer peers than the default min (5).
|
|
440
|
+
# Without this, FullSyncTargetManager blocks forever with
|
|
441
|
+
# "Waiting for 5 peers minimum" when the network has only 4 validators.
|
|
442
|
+
sync-min-peers=1
|
|
443
|
+
|
|
444
|
+
# JSON-RPC
|
|
445
|
+
rpc-http-enabled=true
|
|
446
|
+
rpc-http-host="0.0.0.0"
|
|
447
|
+
rpc-http-port=8545
|
|
448
|
+
rpc-http-api=["DEBUG","ETH","ADMIN","WEB3","IBFT","NET","TXPOOL","MINER"]
|
|
449
|
+
rpc-http-cors-origins=["*"]
|
|
450
|
+
rpc-http-authentication-enabled=false
|
|
451
|
+
|
|
452
|
+
# WebSockets API
|
|
453
|
+
rpc-ws-enabled=true
|
|
454
|
+
rpc-ws-host="0.0.0.0"
|
|
455
|
+
rpc-ws-port=8546
|
|
456
|
+
rpc-ws-api=["DEBUG","ETH","ADMIN","WEB3","IBFT","NET","TXPOOL","MINER"]
|
|
457
|
+
rpc-ws-authentication-enabled=false
|
|
458
|
+
|
|
459
|
+
# GRAPHQL-RPC
|
|
460
|
+
graphql-http-enabled=false
|
|
461
|
+
graphql-http-host="0.0.0.0"
|
|
462
|
+
graphql-http-port=8547
|
|
463
|
+
graphql-http-cors-origins=["*"]
|
|
464
|
+
|
|
465
|
+
# Metrics - compatible with Prometheus/Grafana monitoring on kubeadm cluster
|
|
466
|
+
metrics-enabled=true
|
|
467
|
+
metrics-host="0.0.0.0"
|
|
468
|
+
metrics-port=9545
|
|
469
|
+
`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Generate the node permissions ConfigMap YAML.
|
|
474
|
+
* @param {ValidatorKeySet[]} validators
|
|
475
|
+
* @param {string} namespace
|
|
476
|
+
* @returns {string}
|
|
477
|
+
* @memberof BesuGenesisGenerator
|
|
478
|
+
*/
|
|
479
|
+
function generateNodePermissionsConfigMapYaml(validators, namespace = 'besu') {
|
|
480
|
+
const entries = validators.map((v) => {
|
|
481
|
+
const dnsHost = `validator${v.index}-0.besu-validator${v.index}.${namespace}.svc.cluster.local`;
|
|
482
|
+
return ` "enode://${v.publicKey}@${dnsHost}:30303"`;
|
|
483
|
+
});
|
|
484
|
+
return `apiVersion: v1
|
|
485
|
+
kind: ConfigMap
|
|
486
|
+
metadata:
|
|
487
|
+
name: besu-node-permissions-configmap
|
|
488
|
+
labels:
|
|
489
|
+
app: besu-node-permissions-configmap
|
|
490
|
+
namespace: ${namespace}
|
|
491
|
+
data:
|
|
492
|
+
nodes-allowlist.yml: |-
|
|
493
|
+
nodes-allowlist=[
|
|
494
|
+
|
|
495
|
+
${entries.join(',\n')}
|
|
496
|
+
|
|
497
|
+
]
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Generate the secrets YAML for all validator node keys.
|
|
503
|
+
* @param {ValidatorKeySet[]} validators
|
|
504
|
+
* @param {string} namespace
|
|
505
|
+
* @returns {string}
|
|
506
|
+
* @memberof BesuGenesisGenerator
|
|
507
|
+
*/
|
|
508
|
+
function generateSecretsYaml(validators, namespace = 'besu') {
|
|
509
|
+
const header = `# Hyperledger Besu validator node key secrets for Cyberia Online Object Layer ecosystem.
|
|
510
|
+
# Deployed on kubeadm-managed Kubernetes clusters.
|
|
511
|
+
#
|
|
512
|
+
# Each validator requires a unique secp256k1 private key (nodekey) that corresponds
|
|
513
|
+
# to the public keys declared in besu-validators-configmap.yaml and referenced in
|
|
514
|
+
# the genesis extraData field (IBFT2 validator set).
|
|
515
|
+
#
|
|
516
|
+
# These keys were dynamically generated by besu-genesis-generator.js.
|
|
517
|
+
# In production, consider using sealed-secrets or an external secrets manager.`;
|
|
518
|
+
|
|
519
|
+
const secrets = validators.map(
|
|
520
|
+
(v) => `---
|
|
521
|
+
apiVersion: v1
|
|
522
|
+
kind: Secret
|
|
523
|
+
metadata:
|
|
524
|
+
name: besu-validator${v.index}-key
|
|
525
|
+
labels:
|
|
526
|
+
app.kubernetes.io/name: besu
|
|
527
|
+
app.kubernetes.io/component: validator${v.index}
|
|
528
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
529
|
+
namespace: ${namespace}
|
|
530
|
+
type: Opaque
|
|
531
|
+
stringData:
|
|
532
|
+
nodekey: |-
|
|
533
|
+
${v.privateKey}`,
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
return header + '\n' + secrets.join('\n');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Generate services YAML for all validators + NodePort RPC gateway.
|
|
541
|
+
* @param {ValidatorKeySet[]} validators
|
|
542
|
+
* @param {string} namespace
|
|
543
|
+
* @param {number} [nodePortRpc=30545] - NodePort for external JSON-RPC.
|
|
544
|
+
* @param {number} [nodePortWs=30546] - NodePort for external WebSocket.
|
|
545
|
+
* @returns {string}
|
|
546
|
+
* @memberof BesuGenesisGenerator
|
|
547
|
+
*/
|
|
548
|
+
function generateServicesYaml(validators, namespace = 'besu', nodePortRpc = 30545, nodePortWs = 30546) {
|
|
549
|
+
const header = `# Besu validator and RPC services for kubeadm cluster.
|
|
550
|
+
# Provides ClusterIP services for inter-node communication and a NodePort
|
|
551
|
+
# service on validator1 for external Hardhat/ethers.js access from outside
|
|
552
|
+
# the cluster (e.g. cyberia CLI, deploy scripts).
|
|
553
|
+
#
|
|
554
|
+
# Compatible with: kubeadm + Calico CNI
|
|
555
|
+
# Part of: Cyberia Online Object Layer ERC-1155 ecosystem`;
|
|
556
|
+
|
|
557
|
+
const clusterIpServices = validators.map(
|
|
558
|
+
(v) => `
|
|
559
|
+
---
|
|
560
|
+
apiVersion: v1
|
|
561
|
+
kind: Service
|
|
562
|
+
metadata:
|
|
563
|
+
name: besu-validator${v.index}
|
|
564
|
+
labels:
|
|
565
|
+
app: validator${v.index}
|
|
566
|
+
app.kubernetes.io/name: besu
|
|
567
|
+
app.kubernetes.io/component: validator
|
|
568
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
569
|
+
namespace: ${namespace}
|
|
570
|
+
spec:
|
|
571
|
+
# Headless service (clusterIP: None) is required for StatefulSet pod-specific
|
|
572
|
+
# DNS entries like validator1-0.besu-validator1.besu.svc.cluster.local to resolve.
|
|
573
|
+
# Without this, enode URLs in --bootnodes and static-nodes.json won't work
|
|
574
|
+
# because K8s only creates per-pod DNS records for headless services.
|
|
575
|
+
clusterIP: None
|
|
576
|
+
type: ClusterIP
|
|
577
|
+
# Required for IBFT2 bootstrap: init containers in validators 2-4 must reach
|
|
578
|
+
# validator1 before it passes readiness probes (which require quorum).
|
|
579
|
+
# Without this, K8s removes the pod from endpoints when not-ready, causing a
|
|
580
|
+
# deadlock where validator1 can never get peers.
|
|
581
|
+
publishNotReadyAddresses: true
|
|
582
|
+
selector:
|
|
583
|
+
app: validator${v.index}
|
|
584
|
+
ports:
|
|
585
|
+
- port: 30303
|
|
586
|
+
targetPort: 30303
|
|
587
|
+
protocol: UDP
|
|
588
|
+
name: discovery
|
|
589
|
+
- port: 30303
|
|
590
|
+
targetPort: 30303
|
|
591
|
+
protocol: TCP
|
|
592
|
+
name: rlpx
|
|
593
|
+
- port: 8545
|
|
594
|
+
targetPort: 8545
|
|
595
|
+
protocol: TCP
|
|
596
|
+
name: json-rpc
|
|
597
|
+
- port: 8546
|
|
598
|
+
targetPort: 8546
|
|
599
|
+
protocol: TCP
|
|
600
|
+
name: ws${
|
|
601
|
+
v.index === 1
|
|
602
|
+
? `
|
|
603
|
+
- port: 8547
|
|
604
|
+
targetPort: 8547
|
|
605
|
+
protocol: TCP
|
|
606
|
+
name: graphql`
|
|
607
|
+
: ''
|
|
608
|
+
}
|
|
609
|
+
- port: 9545
|
|
610
|
+
targetPort: 9545
|
|
611
|
+
protocol: TCP
|
|
612
|
+
name: metrics`,
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
const nodePortService = `
|
|
616
|
+
---
|
|
617
|
+
# NodePort service exposing validator1 JSON-RPC for external Hardhat access.
|
|
618
|
+
# Maps internal port 8545 -> NodePort ${nodePortRpc} so that hardhat.config.js
|
|
619
|
+
# network "besu-k8s" (url: http://<node-ip>:${nodePortRpc}) can reach the chain
|
|
620
|
+
# from outside the kubeadm cluster.
|
|
621
|
+
#
|
|
622
|
+
# WebSocket is also exposed on NodePort ${nodePortWs} for subscription-based workflows.
|
|
623
|
+
apiVersion: v1
|
|
624
|
+
kind: Service
|
|
625
|
+
metadata:
|
|
626
|
+
name: besu-rpc-nodeport
|
|
627
|
+
labels:
|
|
628
|
+
app: validator1
|
|
629
|
+
app.kubernetes.io/name: besu
|
|
630
|
+
app.kubernetes.io/component: rpc-gateway
|
|
631
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
632
|
+
namespace: ${namespace}
|
|
633
|
+
spec:
|
|
634
|
+
type: NodePort
|
|
635
|
+
selector:
|
|
636
|
+
app: validator1
|
|
637
|
+
ports:
|
|
638
|
+
- port: 8545
|
|
639
|
+
targetPort: 8545
|
|
640
|
+
nodePort: ${nodePortRpc}
|
|
641
|
+
protocol: TCP
|
|
642
|
+
name: json-rpc
|
|
643
|
+
- port: 8546
|
|
644
|
+
targetPort: 8546
|
|
645
|
+
nodePort: ${nodePortWs}
|
|
646
|
+
protocol: TCP
|
|
647
|
+
name: ws`;
|
|
648
|
+
|
|
649
|
+
return header + clusterIpServices.join('') + nodePortService + '\n';
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Generate a single validator StatefulSet block (ServiceAccount, Role, RoleBinding, StatefulSet).
|
|
654
|
+
* @param {ValidatorKeySet} validator
|
|
655
|
+
* @param {ValidatorKeySet[]} allValidators - All validators (for bootnode references).
|
|
656
|
+
* @param {string} namespace
|
|
657
|
+
* @param {string} besuImage
|
|
658
|
+
* @param {string} curlImage
|
|
659
|
+
* @returns {string}
|
|
660
|
+
* @memberof BesuGenesisGenerator
|
|
661
|
+
*/
|
|
662
|
+
function generateValidatorStatefulSetYaml(
|
|
663
|
+
validator,
|
|
664
|
+
allValidators,
|
|
665
|
+
namespace = 'besu',
|
|
666
|
+
besuImage = 'hyperledger/besu:24.12.1',
|
|
667
|
+
curlImage = 'curlimages/curl:8.11.1',
|
|
668
|
+
) {
|
|
669
|
+
const i = validator.index;
|
|
670
|
+
const name = `validator${i}`;
|
|
671
|
+
|
|
672
|
+
// Bootnode references: validator1 and validator2 (or just validator1 if only 1)
|
|
673
|
+
const bootnodeIndices = allValidators.length >= 2 ? [1, 2] : [1];
|
|
674
|
+
const bootnodeArgs = bootnodeIndices
|
|
675
|
+
.map(
|
|
676
|
+
(bi) =>
|
|
677
|
+
`enode://\${VALIDATOR${bi}_PUBKEY}@validator${bi}-0.besu-validator${bi}.${namespace}.svc.cluster.local:30303`,
|
|
678
|
+
)
|
|
679
|
+
.join(',');
|
|
680
|
+
|
|
681
|
+
// Environment variables for bootnode public keys
|
|
682
|
+
const bootnodeEnvVars = bootnodeIndices
|
|
683
|
+
.map(
|
|
684
|
+
(bi) => ` - name: VALIDATOR${bi}_PUBKEY
|
|
685
|
+
valueFrom:
|
|
686
|
+
configMapKeyRef:
|
|
687
|
+
name: besu-validators-configmap
|
|
688
|
+
key: validator${bi}PubKey`,
|
|
689
|
+
)
|
|
690
|
+
.join('\n');
|
|
691
|
+
|
|
692
|
+
// Init container (wait for validator1) — only for validators 2+
|
|
693
|
+
const initContainer =
|
|
694
|
+
i > 1
|
|
695
|
+
? `
|
|
696
|
+
initContainers:
|
|
697
|
+
- name: wait-for-validator1
|
|
698
|
+
image: ${curlImage}
|
|
699
|
+
command:
|
|
700
|
+
- sh
|
|
701
|
+
- -c
|
|
702
|
+
- |
|
|
703
|
+
echo "Waiting for validator1 JSON-RPC to be available..."
|
|
704
|
+
until curl -sf -X POST --connect-timeout 3 \\
|
|
705
|
+
-H 'Content-Type: application/json' \\
|
|
706
|
+
-d '{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}' \\
|
|
707
|
+
http://besu-validator1.${namespace}.svc.cluster.local:8545; do
|
|
708
|
+
echo "validator1 not ready yet, retrying in 5s..."
|
|
709
|
+
sleep 5
|
|
710
|
+
done
|
|
711
|
+
echo "validator1 is available."`
|
|
712
|
+
: '';
|
|
713
|
+
|
|
714
|
+
// Validator1 gets graphql port, others don't
|
|
715
|
+
const graphqlPort =
|
|
716
|
+
i === 1
|
|
717
|
+
? `
|
|
718
|
+
- containerPort: 8547
|
|
719
|
+
name: graphql
|
|
720
|
+
protocol: TCP`
|
|
721
|
+
: '';
|
|
722
|
+
|
|
723
|
+
return `
|
|
724
|
+
# ${'='.repeat(77)}
|
|
725
|
+
# Validator ${i}${i === 1 ? ' - Primary RPC endpoint for Hardhat / ObjectLayerToken deploys' : ''}
|
|
726
|
+
# ${'='.repeat(77)}
|
|
727
|
+
---
|
|
728
|
+
apiVersion: v1
|
|
729
|
+
kind: ServiceAccount
|
|
730
|
+
metadata:
|
|
731
|
+
name: ${name}-sa
|
|
732
|
+
namespace: ${namespace}
|
|
733
|
+
labels:
|
|
734
|
+
app.kubernetes.io/name: besu
|
|
735
|
+
app.kubernetes.io/component: ${name}
|
|
736
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
740
|
+
kind: Role
|
|
741
|
+
metadata:
|
|
742
|
+
name: ${name}-key-read-role
|
|
743
|
+
namespace: ${namespace}
|
|
744
|
+
rules:
|
|
745
|
+
- apiGroups: [""]
|
|
746
|
+
resources: ["secrets"]
|
|
747
|
+
resourceNames: [besu-${name}-key]
|
|
748
|
+
verbs: ["get"]
|
|
749
|
+
- apiGroups: [""]
|
|
750
|
+
resources: ["services"]
|
|
751
|
+
verbs: ["get", "list"]
|
|
752
|
+
|
|
753
|
+
---
|
|
754
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
755
|
+
kind: RoleBinding
|
|
756
|
+
metadata:
|
|
757
|
+
name: ${name}-rb
|
|
758
|
+
namespace: ${namespace}
|
|
759
|
+
roleRef:
|
|
760
|
+
apiGroup: rbac.authorization.k8s.io
|
|
761
|
+
kind: Role
|
|
762
|
+
name: ${name}-key-read-role
|
|
763
|
+
subjects:
|
|
764
|
+
- kind: ServiceAccount
|
|
765
|
+
name: ${name}-sa
|
|
766
|
+
namespace: ${namespace}
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
apiVersion: apps/v1
|
|
770
|
+
kind: StatefulSet
|
|
771
|
+
metadata:
|
|
772
|
+
name: ${name}
|
|
773
|
+
labels:
|
|
774
|
+
app: ${name}
|
|
775
|
+
app.kubernetes.io/name: besu
|
|
776
|
+
app.kubernetes.io/component: ${name}
|
|
777
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
778
|
+
namespace: ${namespace}
|
|
779
|
+
spec:
|
|
780
|
+
replicas: 1
|
|
781
|
+
selector:
|
|
782
|
+
matchLabels:
|
|
783
|
+
app: ${name}
|
|
784
|
+
serviceName: besu-${name}
|
|
785
|
+
template:
|
|
786
|
+
metadata:
|
|
787
|
+
labels:
|
|
788
|
+
app: ${name}
|
|
789
|
+
app.kubernetes.io/name: besu
|
|
790
|
+
app.kubernetes.io/component: ${name}
|
|
791
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
792
|
+
annotations:
|
|
793
|
+
prometheus.io/scrape: "true"
|
|
794
|
+
prometheus.io/port: "9545"
|
|
795
|
+
prometheus.io/path: "/metrics"
|
|
796
|
+
spec:
|
|
797
|
+
serviceAccountName: ${name}-sa${initContainer}
|
|
798
|
+
containers:
|
|
799
|
+
- name: ${name}
|
|
800
|
+
image: ${besuImage}
|
|
801
|
+
imagePullPolicy: IfNotPresent
|
|
802
|
+
resources:
|
|
803
|
+
requests:
|
|
804
|
+
cpu: 100m
|
|
805
|
+
memory: 1024Mi
|
|
806
|
+
limits:
|
|
807
|
+
cpu: 500m
|
|
808
|
+
memory: 2048Mi
|
|
809
|
+
env:
|
|
810
|
+
- name: POD_IP
|
|
811
|
+
valueFrom:
|
|
812
|
+
fieldRef:
|
|
813
|
+
fieldPath: status.podIP
|
|
814
|
+
- name: POD_NAME
|
|
815
|
+
valueFrom:
|
|
816
|
+
fieldRef:
|
|
817
|
+
fieldPath: metadata.name
|
|
818
|
+
- name: BESU_P2P_HOST
|
|
819
|
+
valueFrom:
|
|
820
|
+
fieldRef:
|
|
821
|
+
fieldPath: status.podIP
|
|
822
|
+
${bootnodeEnvVars}
|
|
823
|
+
volumeMounts:
|
|
824
|
+
- name: key
|
|
825
|
+
mountPath: /secrets
|
|
826
|
+
readOnly: true
|
|
827
|
+
- name: genesis-config
|
|
828
|
+
mountPath: /etc/genesis
|
|
829
|
+
readOnly: true
|
|
830
|
+
- name: config-toml
|
|
831
|
+
mountPath: /etc/besu
|
|
832
|
+
readOnly: true
|
|
833
|
+
- name: node-permissions
|
|
834
|
+
mountPath: /etc/permissions
|
|
835
|
+
- name: data
|
|
836
|
+
mountPath: /data
|
|
837
|
+
ports:
|
|
838
|
+
- containerPort: 8545
|
|
839
|
+
name: json-rpc
|
|
840
|
+
protocol: TCP
|
|
841
|
+
- containerPort: 8546
|
|
842
|
+
name: ws
|
|
843
|
+
protocol: TCP${graphqlPort}
|
|
844
|
+
- containerPort: 30303
|
|
845
|
+
name: rlpx
|
|
846
|
+
protocol: TCP
|
|
847
|
+
- containerPort: 30303
|
|
848
|
+
name: discovery
|
|
849
|
+
protocol: UDP
|
|
850
|
+
- containerPort: 9545
|
|
851
|
+
name: metrics
|
|
852
|
+
protocol: TCP
|
|
853
|
+
command:
|
|
854
|
+
- /bin/sh
|
|
855
|
+
- -c
|
|
856
|
+
args:
|
|
857
|
+
- |
|
|
858
|
+
exec /opt/besu/bin/besu \\
|
|
859
|
+
--node-private-key-file=/secrets/nodekey \\
|
|
860
|
+
--config-file=/etc/besu/config.toml \\
|
|
861
|
+
--Xdns-enabled=true --Xdns-update-enabled=true \\
|
|
862
|
+
--bootnodes=${bootnodeArgs}
|
|
863
|
+
livenessProbe:
|
|
864
|
+
httpGet:
|
|
865
|
+
path: /liveness
|
|
866
|
+
port: 8545
|
|
867
|
+
initialDelaySeconds: 60
|
|
868
|
+
periodSeconds: 30
|
|
869
|
+
readinessProbe:
|
|
870
|
+
httpGet:
|
|
871
|
+
path: /readiness
|
|
872
|
+
port: 8545
|
|
873
|
+
initialDelaySeconds: 30
|
|
874
|
+
periodSeconds: 15
|
|
875
|
+
volumes:
|
|
876
|
+
- name: key
|
|
877
|
+
secret:
|
|
878
|
+
secretName: besu-${name}-key
|
|
879
|
+
- name: genesis-config
|
|
880
|
+
configMap:
|
|
881
|
+
name: besu-genesis-configmap
|
|
882
|
+
- name: config-toml
|
|
883
|
+
configMap:
|
|
884
|
+
name: besu-config-toml-configmap
|
|
885
|
+
- name: node-permissions
|
|
886
|
+
configMap:
|
|
887
|
+
name: besu-node-permissions-configmap
|
|
888
|
+
- name: data
|
|
889
|
+
emptyDir:
|
|
890
|
+
sizeLimit: 2Gi`;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Generate the complete validators YAML (all validator StatefulSets).
|
|
895
|
+
* @param {ValidatorKeySet[]} validators
|
|
896
|
+
* @param {string} namespace
|
|
897
|
+
* @param {string} besuImage
|
|
898
|
+
* @param {string} curlImage
|
|
899
|
+
* @returns {string}
|
|
900
|
+
* @memberof BesuGenesisGenerator
|
|
901
|
+
*/
|
|
902
|
+
function generateValidatorsYaml(
|
|
903
|
+
validators,
|
|
904
|
+
namespace = 'besu',
|
|
905
|
+
besuImage = 'hyperledger/besu:24.12.1',
|
|
906
|
+
curlImage = 'curlimages/curl:8.11.1',
|
|
907
|
+
) {
|
|
908
|
+
const header = `# Hyperledger Besu IBFT2 Validator StatefulSets for Cyberia Online Object Layer ecosystem.
|
|
909
|
+
# Designed for kubeadm-managed Kubernetes clusters with Calico CNI.
|
|
910
|
+
#
|
|
911
|
+
# Deploys ${validators.length} IBFT2 validators as StatefulSets with:
|
|
912
|
+
# - RBAC for secret access
|
|
913
|
+
# - Prometheus metrics scraping annotations
|
|
914
|
+
# - DNS-based peer discovery (Xdns-enabled)
|
|
915
|
+
# - Zero gas price for private permissioned network
|
|
916
|
+
# - Shared genesis, config, and permissions via ConfigMaps
|
|
917
|
+
#
|
|
918
|
+
# The validator1 node serves as the primary JSON-RPC endpoint for
|
|
919
|
+
# Hardhat contract deployments (ObjectLayerToken ERC-1155).
|
|
920
|
+
#
|
|
921
|
+
# Compatible with: kubeadm + Calico CNI + local-path-provisioner
|
|
922
|
+
# Part of: Cyberia Online Object Layer ERC-1155 ecosystem
|
|
923
|
+
#
|
|
924
|
+
# Generated dynamically by besu-genesis-generator.js — do not edit manually.`;
|
|
925
|
+
|
|
926
|
+
const statefulSets = validators.map((v) =>
|
|
927
|
+
generateValidatorStatefulSetYaml(v, validators, namespace, besuImage, curlImage),
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
return header + '\n' + statefulSets.join('\n') + '\n';
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Generate the kustomization.yaml.
|
|
935
|
+
* @param {string} namespace
|
|
936
|
+
* @returns {string}
|
|
937
|
+
* @memberof BesuGenesisGenerator
|
|
938
|
+
*/
|
|
939
|
+
function generateKustomizationYaml(namespace = 'besu') {
|
|
940
|
+
return `# Kustomization for Hyperledger Besu IBFT2 chain deployment on kubeadm clusters.
|
|
941
|
+
# Part of: Cyberia Online Object Layer ERC-1155 ecosystem
|
|
942
|
+
#
|
|
943
|
+
# Usage:
|
|
944
|
+
# kubectl apply -k manifests/besu
|
|
945
|
+
#
|
|
946
|
+
# This deploys a 4-validator IBFT2 Besu network with:
|
|
947
|
+
# - Dedicated '${namespace}' namespace
|
|
948
|
+
# - ConfigMaps for genesis, TOML config, validators, and node permissions
|
|
949
|
+
# - Secrets for validator node keys
|
|
950
|
+
# - ClusterIP services for inter-node communication
|
|
951
|
+
# - NodePort service (30545) for external Hardhat/ethers.js RPC access
|
|
952
|
+
# - StatefulSets for all 4 validators with health probes and Prometheus annotations
|
|
953
|
+
#
|
|
954
|
+
# Compatible with: kubeadm + Calico CNI + local-path-provisioner
|
|
955
|
+
#
|
|
956
|
+
# Generated dynamically by besu-genesis-generator.js
|
|
957
|
+
|
|
958
|
+
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
959
|
+
kind: Kustomization
|
|
960
|
+
|
|
961
|
+
namespace: ${namespace}
|
|
962
|
+
|
|
963
|
+
commonLabels:
|
|
964
|
+
app.kubernetes.io/managed-by: underpost
|
|
965
|
+
app.kubernetes.io/part-of: cyberia-besu
|
|
966
|
+
|
|
967
|
+
resources:
|
|
968
|
+
- namespace.yaml
|
|
969
|
+
- besu-genesis-configmap.yaml
|
|
970
|
+
- besu-config-toml-configmap.yaml
|
|
971
|
+
- besu-validators-configmap.yaml
|
|
972
|
+
- besu-node-permissions-configmap.yaml
|
|
973
|
+
- besu-secrets.yaml
|
|
974
|
+
- besu-services.yaml
|
|
975
|
+
- besu-validators.yaml
|
|
976
|
+
`;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// ---------------------------------------------------------------------------
|
|
980
|
+
// Network config JSON (for hardhat integration)
|
|
981
|
+
// ---------------------------------------------------------------------------
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Generate the besu-object-layer.network.json for the hardhat/networks/ directory.
|
|
985
|
+
* @param {ValidatorKeySet[]} validators
|
|
986
|
+
* @param {Object} genesis
|
|
987
|
+
* @param {GenesisOptions} [opts]
|
|
988
|
+
* @returns {Object}
|
|
989
|
+
* @memberof BesuGenesisGenerator
|
|
990
|
+
*/
|
|
991
|
+
function buildNetworkConfigJson(validators, genesis, opts = {}) {
|
|
992
|
+
const chainId = opts.chainId || 777771;
|
|
993
|
+
return {
|
|
994
|
+
genesis: genesis,
|
|
995
|
+
blockchain: {
|
|
996
|
+
nodes: {
|
|
997
|
+
generate: true,
|
|
998
|
+
count: validators.length,
|
|
999
|
+
},
|
|
1000
|
+
},
|
|
1001
|
+
network: {
|
|
1002
|
+
name: 'cyberia-besu',
|
|
1003
|
+
description:
|
|
1004
|
+
'Hyperledger Besu IBFT2 private network for Cyberia Online Object Layer ERC-1155 ecosystem on kubeadm cluster',
|
|
1005
|
+
consensus: 'IBFT2',
|
|
1006
|
+
chainId,
|
|
1007
|
+
clusterType: 'kubeadm',
|
|
1008
|
+
contracts: {
|
|
1009
|
+
ObjectLayerToken: {
|
|
1010
|
+
standard: 'ERC-1155',
|
|
1011
|
+
description:
|
|
1012
|
+
'Unified multi-token contract: fungible CryptoKoyn (token ID 0) + semi/non-fungible Object Layer items (token ID >= 1)',
|
|
1013
|
+
tokenIdScheme: "keccak256(abi.encodePacked('cyberia.object-layer:', itemId))",
|
|
1014
|
+
fungibleTokens: {
|
|
1015
|
+
CRYPTOKOYN: {
|
|
1016
|
+
tokenId: 0,
|
|
1017
|
+
symbol: 'CKY',
|
|
1018
|
+
decimals: 18,
|
|
1019
|
+
initialSupply: '10000000000000000000000000',
|
|
1020
|
+
},
|
|
1021
|
+
},
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
rpc: {
|
|
1025
|
+
'besu-ibft2': {
|
|
1026
|
+
description: 'Direct RPC access (e.g. kubectl port-forward or in-cluster)',
|
|
1027
|
+
url: 'http://127.0.0.1:8545',
|
|
1028
|
+
chainId,
|
|
1029
|
+
gasPrice: 0,
|
|
1030
|
+
},
|
|
1031
|
+
'besu-k8s': {
|
|
1032
|
+
description: 'kubeadm cluster NodePort access via besu-rpc-nodeport service (30545)',
|
|
1033
|
+
url: 'http://127.0.0.1:30545',
|
|
1034
|
+
chainId,
|
|
1035
|
+
gasPrice: 0,
|
|
1036
|
+
},
|
|
1037
|
+
},
|
|
1038
|
+
kubernetes: {
|
|
1039
|
+
clusterType: 'kubeadm',
|
|
1040
|
+
manifests: 'manifests/besu/',
|
|
1041
|
+
namespace: 'besu',
|
|
1042
|
+
validators: validators.length,
|
|
1043
|
+
image: 'hyperledger/besu:24.12.1',
|
|
1044
|
+
services: {
|
|
1045
|
+
clusterIP: validators.map((v) => `besu-validator${v.index}`),
|
|
1046
|
+
nodePort: {
|
|
1047
|
+
name: 'besu-rpc-nodeport',
|
|
1048
|
+
jsonRpc: 30545,
|
|
1049
|
+
ws: 30546,
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
monitoring: {
|
|
1053
|
+
prometheus: true,
|
|
1054
|
+
metricsPort: 9545,
|
|
1055
|
+
grafana: true,
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
validators: validators.map((v) => ({
|
|
1060
|
+
index: v.index,
|
|
1061
|
+
publicKey: v.publicKey,
|
|
1062
|
+
address: v.address,
|
|
1063
|
+
enode: v.enodeDns,
|
|
1064
|
+
})),
|
|
1065
|
+
generated: new Date().toISOString(),
|
|
1066
|
+
generatorVersion: '1.0.0',
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// ---------------------------------------------------------------------------
|
|
1071
|
+
// High-level orchestration
|
|
1072
|
+
// ---------------------------------------------------------------------------
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* @typedef {Object} GenerateBesuManifestsOptions
|
|
1076
|
+
* @property {string} [outputDir='./manifests/besu'] - Where to write the manifests.
|
|
1077
|
+
* @property {string} [networkConfigDir='./hardhat/networks'] - Where to write the network config JSON.
|
|
1078
|
+
* @property {number} [validatorCount=4] - Number of IBFT2 validators.
|
|
1079
|
+
* @property {string} [namespace='besu'] - Kubernetes namespace.
|
|
1080
|
+
* @property {number} [chainId=777771] - Chain ID.
|
|
1081
|
+
* @property {number} [blockPeriodSeconds=5] - IBFT2 block period.
|
|
1082
|
+
* @property {number} [epochLength=30000] - IBFT2 epoch length.
|
|
1083
|
+
* @property {number} [requestTimeoutSeconds=10] - IBFT2 request timeout.
|
|
1084
|
+
* @property {string} [coinbaseAddress=''] - Coinbase deployer address (reads from engine-private if empty).
|
|
1085
|
+
* @property {string} [coinbaseBalance] - Hex balance for coinbase.
|
|
1086
|
+
* @property {string} [besuImage='hyperledger/besu:24.12.1'] - Besu Docker image.
|
|
1087
|
+
* @property {string} [curlImage='curlimages/curl:8.11.1'] - Curl init container image.
|
|
1088
|
+
* @property {number} [nodePortRpc=30545] - NodePort for external RPC.
|
|
1089
|
+
* @property {number} [nodePortWs=30546] - NodePort for external WS.
|
|
1090
|
+
* @property {string} [gasLimit='0x1fffffffffffff'] - Genesis gas limit.
|
|
1091
|
+
* @property {boolean} [savePrivateKeys=true] - Also save validator private keys to engine-private.
|
|
1092
|
+
* @property {string} [privateKeysDir='./engine-private/eth-networks/besu/validators'] - Private keys output dir.
|
|
1093
|
+
* @memberof BesuGenesisGenerator
|
|
1094
|
+
*/
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Generate all Besu K8s manifests and network config from scratch.
|
|
1098
|
+
*
|
|
1099
|
+
* This is the main entry point: it creates fresh validator keys, computes all
|
|
1100
|
+
* derived data (public keys, addresses, extraData, enode URLs), and writes
|
|
1101
|
+
* every manifest file and the network config JSON.
|
|
1102
|
+
*
|
|
1103
|
+
* @param {GenerateBesuManifestsOptions} [opts]
|
|
1104
|
+
* @returns {Promise<{validators: ValidatorKeySet[], genesis: Object, manifestsPath: string}>}
|
|
1105
|
+
* @memberof BesuGenesisGenerator
|
|
1106
|
+
*/
|
|
1107
|
+
async function generateBesuManifests(opts = {}) {
|
|
1108
|
+
const {
|
|
1109
|
+
outputDir = './manifests/besu',
|
|
1110
|
+
networkConfigDir = './hardhat/networks',
|
|
1111
|
+
validatorCount = 4,
|
|
1112
|
+
namespace = 'besu',
|
|
1113
|
+
chainId = 777771,
|
|
1114
|
+
blockPeriodSeconds = 5,
|
|
1115
|
+
epochLength = 30000,
|
|
1116
|
+
requestTimeoutSeconds = 10,
|
|
1117
|
+
coinbaseAddress = '',
|
|
1118
|
+
coinbaseBalance = '0x200000000000000000000000000000000000000000000000000000000000000',
|
|
1119
|
+
besuImage = 'hyperledger/besu:24.12.1',
|
|
1120
|
+
curlImage = 'curlimages/curl:8.11.1',
|
|
1121
|
+
nodePortRpc = 30545,
|
|
1122
|
+
nodePortWs = 30546,
|
|
1123
|
+
gasLimit = '0x1fffffffffffff',
|
|
1124
|
+
savePrivateKeys = true,
|
|
1125
|
+
privateKeysDir = './engine-private/eth-networks/besu/validators',
|
|
1126
|
+
} = opts;
|
|
1127
|
+
|
|
1128
|
+
logger.info(`Generating Besu IBFT2 manifests for ${validatorCount} validators...`);
|
|
1129
|
+
logger.info(` Namespace: ${namespace}`);
|
|
1130
|
+
logger.info(` Chain ID: ${chainId}`);
|
|
1131
|
+
logger.info(` Output: ${outputDir}`);
|
|
1132
|
+
|
|
1133
|
+
// 1. Generate validator keys
|
|
1134
|
+
const validators = generateValidatorKeys(validatorCount, namespace);
|
|
1135
|
+
|
|
1136
|
+
for (const v of validators) {
|
|
1137
|
+
logger.info(` Validator ${v.index}:`);
|
|
1138
|
+
logger.info(` Public Key: ${v.publicKey.slice(0, 16)}...${v.publicKey.slice(-16)}`);
|
|
1139
|
+
logger.info(` Address: ${v.address}`);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// 2. Resolve coinbase address
|
|
1143
|
+
let resolvedCoinbase = coinbaseAddress.replace(/^0x/, '');
|
|
1144
|
+
if (!resolvedCoinbase) {
|
|
1145
|
+
// Try to read from engine-private coinbase file
|
|
1146
|
+
const coinbasePath = './engine-private/eth-networks/besu/coinbase';
|
|
1147
|
+
if (fs.existsSync(coinbasePath)) {
|
|
1148
|
+
try {
|
|
1149
|
+
const key = fs.readFileSync(coinbasePath, 'utf8').trim();
|
|
1150
|
+
const cleanKey = key.startsWith('0x') ? key.slice(2) : key;
|
|
1151
|
+
// Derive address from coinbase private key
|
|
1152
|
+
const ecdh = crypto.createECDH('secp256k1');
|
|
1153
|
+
ecdh.setPrivateKey(Buffer.from(cleanKey, 'hex'));
|
|
1154
|
+
const pubKey = ecdh.getPublicKey('hex').slice(2);
|
|
1155
|
+
resolvedCoinbase = publicKeyToAddress(pubKey);
|
|
1156
|
+
logger.info(` Coinbase address (from engine-private): ${resolvedCoinbase}`);
|
|
1157
|
+
} catch (e) {
|
|
1158
|
+
logger.warn(` Could not derive coinbase address from private key: ${e.message}`);
|
|
1159
|
+
logger.warn(
|
|
1160
|
+
' Using empty coinbase. Set --coinbase-address or create engine-private/eth-networks/besu/coinbase',
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
logger.warn(' No coinbase address provided and no coinbase key file found.');
|
|
1165
|
+
logger.warn(' Run "cyberia chain key-gen --save" and "cyberia chain set-coinbase" first.');
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// 3. Build genesis
|
|
1170
|
+
const genesisOpts = {
|
|
1171
|
+
chainId,
|
|
1172
|
+
blockPeriodSeconds,
|
|
1173
|
+
epochLength,
|
|
1174
|
+
requestTimeoutSeconds,
|
|
1175
|
+
coinbaseAddress: resolvedCoinbase,
|
|
1176
|
+
coinbaseBalance,
|
|
1177
|
+
gasLimit,
|
|
1178
|
+
};
|
|
1179
|
+
const genesis = buildGenesis(validators, genesisOpts);
|
|
1180
|
+
|
|
1181
|
+
// 4. Generate all manifest files
|
|
1182
|
+
fs.ensureDirSync(outputDir);
|
|
1183
|
+
|
|
1184
|
+
const files = {
|
|
1185
|
+
'namespace.yaml': generateNamespaceYaml(namespace),
|
|
1186
|
+
'besu-genesis-configmap.yaml': generateGenesisConfigMapYaml(genesis, namespace),
|
|
1187
|
+
'besu-config-toml-configmap.yaml': generateConfigTomlConfigMapYaml(validators, namespace),
|
|
1188
|
+
'besu-validators-configmap.yaml': generateValidatorsConfigMapYaml(validators, namespace),
|
|
1189
|
+
'besu-node-permissions-configmap.yaml': generateNodePermissionsConfigMapYaml(validators, namespace),
|
|
1190
|
+
'besu-secrets.yaml': generateSecretsYaml(validators, namespace),
|
|
1191
|
+
'besu-services.yaml': generateServicesYaml(validators, namespace, nodePortRpc, nodePortWs),
|
|
1192
|
+
'besu-validators.yaml': generateValidatorsYaml(validators, namespace, besuImage, curlImage),
|
|
1193
|
+
'kustomization.yaml': generateKustomizationYaml(namespace),
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
1197
|
+
const filePath = path.join(outputDir, filename);
|
|
1198
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
1199
|
+
logger.info(` Wrote: ${filePath}`);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// 5. Write network config JSON
|
|
1203
|
+
if (networkConfigDir) {
|
|
1204
|
+
fs.ensureDirSync(networkConfigDir);
|
|
1205
|
+
const networkConfig = buildNetworkConfigJson(validators, genesis, { chainId });
|
|
1206
|
+
const networkConfigPath = path.join(networkConfigDir, 'besu-object-layer.network.json');
|
|
1207
|
+
fs.writeJsonSync(networkConfigPath, networkConfig, { spaces: 2 });
|
|
1208
|
+
logger.info(` Wrote network config: ${networkConfigPath}`);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// 6. Optionally save private keys to engine-private for backup
|
|
1212
|
+
if (savePrivateKeys) {
|
|
1213
|
+
fs.ensureDirSync(privateKeysDir);
|
|
1214
|
+
for (const v of validators) {
|
|
1215
|
+
const keyPath = path.join(privateKeysDir, `validator${v.index}.key.json`);
|
|
1216
|
+
fs.writeJsonSync(
|
|
1217
|
+
keyPath,
|
|
1218
|
+
{
|
|
1219
|
+
index: v.index,
|
|
1220
|
+
privateKey: v.privateKey,
|
|
1221
|
+
publicKey: v.publicKey,
|
|
1222
|
+
address: v.address,
|
|
1223
|
+
enode: v.enodeDns,
|
|
1224
|
+
generated: new Date().toISOString(),
|
|
1225
|
+
},
|
|
1226
|
+
{ spaces: 2 },
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
logger.info(` Validator private keys saved to: ${privateKeysDir}`);
|
|
1230
|
+
logger.warn(' Keep the engine-private/ directory secure!');
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
logger.info('Besu manifest generation complete.');
|
|
1234
|
+
logger.info(` extraData: ${genesis.extraData.slice(0, 32)}...`);
|
|
1235
|
+
logger.info(` Chain ID: ${chainId}`);
|
|
1236
|
+
logger.info(` Validators: ${validatorCount}`);
|
|
1237
|
+
|
|
1238
|
+
return { validators, genesis, manifestsPath: outputDir };
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// ---------------------------------------------------------------------------
|
|
1242
|
+
// Deploy / Remove orchestration
|
|
1243
|
+
// ---------------------------------------------------------------------------
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* @typedef {Object} DeployBesuOptions
|
|
1247
|
+
* @property {boolean} [pullImage=false] - Pull Besu container images into containerd before deployment.
|
|
1248
|
+
* @property {number} [validators=4] - Number of IBFT2 validators.
|
|
1249
|
+
* @property {number} [chainId=777771] - Chain ID for the network.
|
|
1250
|
+
* @property {number} [blockPeriodSeconds=5] - IBFT2 block period.
|
|
1251
|
+
* @property {number} [epochLength=30000] - IBFT2 epoch length.
|
|
1252
|
+
* @property {string} [coinbaseAddress=''] - Coinbase deployer address.
|
|
1253
|
+
* @property {string} [besuImage='hyperledger/besu:24.12.1'] - Besu container image.
|
|
1254
|
+
* @property {string} [curlImage='curlimages/curl:8.11.1'] - Curl init container image.
|
|
1255
|
+
* @property {number} [nodePortRpc=30545] - NodePort for external JSON-RPC.
|
|
1256
|
+
* @property {number} [nodePortWs=30546] - NodePort for external WebSocket.
|
|
1257
|
+
* @property {string} [namespace='besu'] - Kubernetes namespace.
|
|
1258
|
+
* @property {boolean} [skipGenerate=false] - Skip manifest generation and use existing manifests as-is.
|
|
1259
|
+
* @property {boolean} [skipWait=false] - Skip waiting for validators to reach Running state.
|
|
1260
|
+
* @property {string} [manifestsPath='./manifests/besu'] - Path to write/read manifests.
|
|
1261
|
+
* @property {string} [networkConfigDir='./hardhat/networks'] - Path for Hardhat network config JSON.
|
|
1262
|
+
* @property {string} [privateKeysDir='./engine-private/eth-networks/besu/validators'] - Path for validator key backups.
|
|
1263
|
+
* @memberof BesuGenesisGenerator
|
|
1264
|
+
*/
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Deploys the Hyperledger Besu IBFT2 network to a kubeadm cluster using
|
|
1268
|
+
* dynamically generated manifests. This provides the blockchain layer
|
|
1269
|
+
* for the Cyberia Online Object Layer ERC-1155 ecosystem (ObjectLayerToken + CryptoKoyn).
|
|
1270
|
+
*
|
|
1271
|
+
* The deployment creates:
|
|
1272
|
+
* - A dedicated 'besu' namespace
|
|
1273
|
+
* - ConfigMaps for genesis (dynamic chainId), TOML config, validator keys, permissions
|
|
1274
|
+
* - Secrets for N IBFT2 validator node keys (freshly generated secp256k1)
|
|
1275
|
+
* - ClusterIP services for inter-validator communication
|
|
1276
|
+
* - NodePort service (30545) for external Hardhat/ethers.js RPC access
|
|
1277
|
+
* - N validator StatefulSets with health probes and Prometheus annotations
|
|
1278
|
+
*
|
|
1279
|
+
* After deployment, the Hardhat network "besu-k8s" (url: http://<node-ip>:30545) can
|
|
1280
|
+
* connect to the chain for ObjectLayerToken contract deployments.
|
|
1281
|
+
*
|
|
1282
|
+
* @param {DeployBesuOptions} [options]
|
|
1283
|
+
* @returns {Promise<{validators: ValidatorKeySet[], genesis: Object, manifestsPath: string} | undefined>}
|
|
1284
|
+
* @memberof BesuGenesisGenerator
|
|
1285
|
+
*/
|
|
1286
|
+
async function deployBesu(options = {}) {
|
|
1287
|
+
const {
|
|
1288
|
+
pullImage = false,
|
|
1289
|
+
validators: validatorCount = 4,
|
|
1290
|
+
chainId = 777771,
|
|
1291
|
+
blockPeriodSeconds = 5,
|
|
1292
|
+
epochLength = 30000,
|
|
1293
|
+
coinbaseAddress = '',
|
|
1294
|
+
besuImage = 'hyperledger/besu:24.12.1',
|
|
1295
|
+
curlImage = 'curlimages/curl:8.11.1',
|
|
1296
|
+
nodePortRpc = 30545,
|
|
1297
|
+
nodePortWs = 30546,
|
|
1298
|
+
namespace = 'besu',
|
|
1299
|
+
skipGenerate = false,
|
|
1300
|
+
skipWait = false,
|
|
1301
|
+
manifestsPath: besuManifestsPath = './manifests/besu',
|
|
1302
|
+
networkConfigDir = './hardhat/networks',
|
|
1303
|
+
privateKeysDir = './engine-private/eth-networks/besu/validators',
|
|
1304
|
+
} = options;
|
|
1305
|
+
|
|
1306
|
+
let generateResult;
|
|
1307
|
+
|
|
1308
|
+
// ── Step 0: Idempotency — detect existing deployment ───────────────────
|
|
1309
|
+
const existingNs = shellExec(
|
|
1310
|
+
`kubectl get namespace ${namespace} -o jsonpath='{.metadata.name}' 2>/dev/null || echo ""`,
|
|
1311
|
+
{ stdout: true, silent: true },
|
|
1312
|
+
)
|
|
1313
|
+
.trim()
|
|
1314
|
+
.replace(/'/g, '');
|
|
1315
|
+
|
|
1316
|
+
const existingKeysAvailable =
|
|
1317
|
+
fs.existsSync(privateKeysDir) && fs.readdirSync(privateKeysDir).some((f) => f.endsWith('.key.json'));
|
|
1318
|
+
|
|
1319
|
+
if (existingNs === namespace && existingKeysAvailable && !skipGenerate) {
|
|
1320
|
+
// Cluster already has a besu namespace and we have saved keys — check if pods are healthy
|
|
1321
|
+
const runningPods = shellExec(
|
|
1322
|
+
`kubectl get pods -n ${namespace} --field-selector=status.phase=Running -o name 2>/dev/null | wc -l`,
|
|
1323
|
+
{ stdout: true, silent: true },
|
|
1324
|
+
).trim();
|
|
1325
|
+
|
|
1326
|
+
const healthyCount = parseInt(runningPods, 10) || 0;
|
|
1327
|
+
|
|
1328
|
+
if (healthyCount >= validatorCount) {
|
|
1329
|
+
logger.info(`Besu network already running in namespace '${namespace}' with ${healthyCount} healthy pod(s).`);
|
|
1330
|
+
logger.info('Deployment is already up-to-date. Use "chain remove" first to redeploy with fresh keys.');
|
|
1331
|
+
logger.info(` Internal RPC: http://besu-validator1.${namespace}.svc.cluster.local:8545`);
|
|
1332
|
+
logger.info(` External RPC (NodePort): http://<node-ip>:${nodePortRpc}`);
|
|
1333
|
+
return { alreadyRunning: true, namespace, validators: healthyCount };
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// Namespace exists but pods aren't all healthy — tear down stale resources
|
|
1337
|
+
// and redeploy with existing keys if manifests are present
|
|
1338
|
+
logger.info(`Besu namespace '${namespace}' exists but only ${healthyCount}/${validatorCount} pods are healthy.`);
|
|
1339
|
+
logger.info('Cleaning up stale deployment before redeploying...');
|
|
1340
|
+
if (fs.existsSync(besuManifestsPath)) {
|
|
1341
|
+
shellExec(`kubectl delete -k ${besuManifestsPath} --ignore-not-found`);
|
|
1342
|
+
}
|
|
1343
|
+
shellExec(`kubectl delete namespace ${namespace} --ignore-not-found --wait=true`);
|
|
1344
|
+
// Brief pause to let namespace finalizers complete
|
|
1345
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
1346
|
+
logger.info('Stale resources cleaned up.');
|
|
1347
|
+
} else if (existingNs === namespace && !existingKeysAvailable && !skipGenerate) {
|
|
1348
|
+
// Namespace exists but no saved keys — clean slate needed
|
|
1349
|
+
logger.info(`Besu namespace '${namespace}' exists but no saved keys found. Cleaning up...`);
|
|
1350
|
+
if (fs.existsSync(besuManifestsPath)) {
|
|
1351
|
+
shellExec(`kubectl delete -k ${besuManifestsPath} --ignore-not-found`);
|
|
1352
|
+
}
|
|
1353
|
+
shellExec(`kubectl delete namespace ${namespace} --ignore-not-found --wait=true`);
|
|
1354
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
1355
|
+
logger.info('Stale resources cleaned up.');
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// ── Step 1: Generate manifests (unless skipGenerate) ───────────────────
|
|
1359
|
+
if (!skipGenerate) {
|
|
1360
|
+
logger.info('Generating fresh Besu IBFT2 manifests with new validator keys...');
|
|
1361
|
+
try {
|
|
1362
|
+
generateResult = await generateBesuManifests({
|
|
1363
|
+
outputDir: besuManifestsPath,
|
|
1364
|
+
networkConfigDir,
|
|
1365
|
+
validatorCount,
|
|
1366
|
+
namespace,
|
|
1367
|
+
chainId,
|
|
1368
|
+
blockPeriodSeconds,
|
|
1369
|
+
epochLength,
|
|
1370
|
+
requestTimeoutSeconds: 10,
|
|
1371
|
+
coinbaseAddress,
|
|
1372
|
+
besuImage,
|
|
1373
|
+
curlImage,
|
|
1374
|
+
nodePortRpc,
|
|
1375
|
+
nodePortWs,
|
|
1376
|
+
savePrivateKeys: true,
|
|
1377
|
+
privateKeysDir,
|
|
1378
|
+
});
|
|
1379
|
+
logger.info(`Generated ${generateResult.validators.length} validator key sets.`);
|
|
1380
|
+
logger.info(` extraData: ${generateResult.genesis.extraData.slice(0, 40)}...`);
|
|
1381
|
+
} catch (genErr) {
|
|
1382
|
+
logger.error(`Manifest generation failed: ${genErr.message}`);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
} else {
|
|
1386
|
+
if (!fs.existsSync(besuManifestsPath)) {
|
|
1387
|
+
logger.error(`Besu manifests not found at: ${besuManifestsPath}`);
|
|
1388
|
+
logger.error('Run without --skip-generate to create them, or provide manifests manually.');
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
logger.info('Using existing manifests (--skip-generate).');
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// ── Step 2: Pull container images if requested ─────────────────────────
|
|
1395
|
+
if (pullImage) {
|
|
1396
|
+
logger.info('Pulling Besu images via crictl...');
|
|
1397
|
+
shellExec(`sudo crictl pull ${besuImage}`);
|
|
1398
|
+
shellExec(`sudo crictl pull ${curlImage}`);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// ── Step 3: Apply all besu resources via kustomize ─────────────────────
|
|
1402
|
+
logger.info('Deploying Besu IBFT2 network to kubeadm cluster...');
|
|
1403
|
+
shellExec(`kubectl apply -k ${besuManifestsPath}`);
|
|
1404
|
+
|
|
1405
|
+
// ── Step 4: Wait for validators to become ready ────────────────────────
|
|
1406
|
+
if (!skipWait) {
|
|
1407
|
+
logger.info('Waiting for Besu validator1 to become ready...');
|
|
1408
|
+
|
|
1409
|
+
const maxAttempts = 120; // 10 minutes at 5s intervals
|
|
1410
|
+
let validator1Ready = false;
|
|
1411
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1412
|
+
const status = shellExec(
|
|
1413
|
+
`kubectl get pod validator1-0 -n ${namespace} -o jsonpath='{.status.phase}' 2>/dev/null || echo "NotFound"`,
|
|
1414
|
+
{ stdout: true, silent: true },
|
|
1415
|
+
)
|
|
1416
|
+
.trim()
|
|
1417
|
+
.replace(/'/g, '');
|
|
1418
|
+
if (status === 'Running') {
|
|
1419
|
+
// Also verify the container is not in CrashLoopBackOff
|
|
1420
|
+
const containerReady = shellExec(
|
|
1421
|
+
`kubectl get pod validator1-0 -n ${namespace} -o jsonpath='{.status.containerStatuses[0].ready}' 2>/dev/null || echo "false"`,
|
|
1422
|
+
{ stdout: true, silent: true },
|
|
1423
|
+
)
|
|
1424
|
+
.trim()
|
|
1425
|
+
.replace(/'/g, '');
|
|
1426
|
+
if (containerReady === 'true') {
|
|
1427
|
+
validator1Ready = true;
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
// Pod phase is Running but container not ready — could be CrashLoopBackOff
|
|
1431
|
+
const waitingReason = shellExec(
|
|
1432
|
+
`kubectl get pod validator1-0 -n ${namespace} -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}' 2>/dev/null || echo ""`,
|
|
1433
|
+
{ stdout: true, silent: true },
|
|
1434
|
+
)
|
|
1435
|
+
.trim()
|
|
1436
|
+
.replace(/'/g, '');
|
|
1437
|
+
if (waitingReason === 'CrashLoopBackOff') {
|
|
1438
|
+
logger.error('validator1-0 is in CrashLoopBackOff. Check logs: kubectl logs validator1-0 -n ' + namespace);
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (attempt % 12 === 0) {
|
|
1443
|
+
logger.info(` Still waiting for validator1-0... (status: ${status}, attempt ${attempt}/${maxAttempts})`);
|
|
1444
|
+
}
|
|
1445
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if (validator1Ready) {
|
|
1449
|
+
logger.info('Besu validator1 is running.');
|
|
1450
|
+
} else {
|
|
1451
|
+
logger.warn('Besu validator1 did not reach Running state within timeout.');
|
|
1452
|
+
logger.warn(`Check pod status manually: kubectl get pods -n ${namespace}`);
|
|
1453
|
+
logger.warn(`Check logs: kubectl logs validator1-0 -n ${namespace}`);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Wait for remaining validators
|
|
1457
|
+
logger.info('Waiting for remaining Besu validators...');
|
|
1458
|
+
for (let vi = 2; vi <= validatorCount; vi++) {
|
|
1459
|
+
const podName = `validator${vi}-0`;
|
|
1460
|
+
let ready = false;
|
|
1461
|
+
for (let attempt = 1; attempt <= 60; attempt++) {
|
|
1462
|
+
const status = shellExec(
|
|
1463
|
+
`kubectl get pod ${podName} -n ${namespace} -o jsonpath='{.status.phase}' 2>/dev/null || echo "NotFound"`,
|
|
1464
|
+
{ stdout: true, silent: true },
|
|
1465
|
+
)
|
|
1466
|
+
.trim()
|
|
1467
|
+
.replace(/'/g, '');
|
|
1468
|
+
if (status === 'Running') {
|
|
1469
|
+
const containerReady = shellExec(
|
|
1470
|
+
`kubectl get pod ${podName} -n ${namespace} -o jsonpath='{.status.containerStatuses[0].ready}' 2>/dev/null || echo "false"`,
|
|
1471
|
+
{ stdout: true, silent: true },
|
|
1472
|
+
)
|
|
1473
|
+
.trim()
|
|
1474
|
+
.replace(/'/g, '');
|
|
1475
|
+
if (containerReady === 'true') {
|
|
1476
|
+
ready = true;
|
|
1477
|
+
break;
|
|
1478
|
+
}
|
|
1479
|
+
const waitingReason = shellExec(
|
|
1480
|
+
`kubectl get pod ${podName} -n ${namespace} -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}' 2>/dev/null || echo ""`,
|
|
1481
|
+
{ stdout: true, silent: true },
|
|
1482
|
+
)
|
|
1483
|
+
.trim()
|
|
1484
|
+
.replace(/'/g, '');
|
|
1485
|
+
if (waitingReason === 'CrashLoopBackOff') {
|
|
1486
|
+
logger.error(`${podName} is in CrashLoopBackOff. Check logs: kubectl logs ${podName} -n ${namespace}`);
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (attempt % 12 === 0) {
|
|
1491
|
+
logger.info(` Still waiting for ${podName}... (status: ${status})`);
|
|
1492
|
+
}
|
|
1493
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
1494
|
+
}
|
|
1495
|
+
if (ready) {
|
|
1496
|
+
logger.info(` ${podName} is running.`);
|
|
1497
|
+
} else {
|
|
1498
|
+
logger.warn(` ${podName} did not reach Running state within timeout.`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
logger.info('');
|
|
1504
|
+
logger.info('Besu IBFT2 network deployment complete.');
|
|
1505
|
+
logger.info(` Validators: ${validatorCount} (IBFT2 consensus)`);
|
|
1506
|
+
logger.info(` Chain ID: ${chainId}`);
|
|
1507
|
+
logger.info(` Namespace: ${namespace}`);
|
|
1508
|
+
logger.info(` Internal RPC: http://besu-validator1.${namespace}.svc.cluster.local:8545`);
|
|
1509
|
+
logger.info(` External RPC (NodePort): http://<node-ip>:${nodePortRpc}`);
|
|
1510
|
+
logger.info('');
|
|
1511
|
+
logger.info('Next steps:');
|
|
1512
|
+
logger.info(' 1. Deploy ObjectLayerToken contract:');
|
|
1513
|
+
logger.info(' cyberia chain deploy-contract --network besu-k8s');
|
|
1514
|
+
logger.info(' 2. Check chain status:');
|
|
1515
|
+
logger.info(' cyberia chain status --network besu-k8s');
|
|
1516
|
+
|
|
1517
|
+
return generateResult;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* Removes the Hyperledger Besu IBFT2 network from the kubeadm cluster.
|
|
1522
|
+
* Deletes all resources in the 'besu' namespace created by deployBesu.
|
|
1523
|
+
*
|
|
1524
|
+
* @param {Object} [options]
|
|
1525
|
+
* @param {string} [options.namespace='besu'] - Kubernetes namespace.
|
|
1526
|
+
* @param {boolean} [options.cleanKeys=false] - Also remove generated validator keys from engine-private/.
|
|
1527
|
+
* @param {string} [options.manifestsPath='./manifests/besu'] - Path to the manifests directory.
|
|
1528
|
+
* @param {string} [options.privateKeysDir='./engine-private/eth-networks/besu/validators'] - Validator keys dir.
|
|
1529
|
+
* @param {boolean} [options.cleanManifests=false] - Also remove the generated manifests directory.
|
|
1530
|
+
* @memberof BesuGenesisGenerator
|
|
1531
|
+
*/
|
|
1532
|
+
function removeBesu(options = {}) {
|
|
1533
|
+
const {
|
|
1534
|
+
namespace = 'besu',
|
|
1535
|
+
cleanKeys = false,
|
|
1536
|
+
manifestsPath: besuManifestsPath = './manifests/besu',
|
|
1537
|
+
privateKeysDir = './engine-private/eth-networks/besu/validators',
|
|
1538
|
+
cleanManifests = false,
|
|
1539
|
+
} = options;
|
|
1540
|
+
|
|
1541
|
+
logger.info('Removing Besu IBFT2 network from kubeadm cluster...');
|
|
1542
|
+
|
|
1543
|
+
if (fs.existsSync(besuManifestsPath)) {
|
|
1544
|
+
shellExec(`kubectl delete -k ${besuManifestsPath} --ignore-not-found`);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// Also clean up any remaining resources in the besu namespace
|
|
1548
|
+
shellExec(`kubectl delete namespace ${namespace} --ignore-not-found`);
|
|
1549
|
+
|
|
1550
|
+
if (cleanKeys) {
|
|
1551
|
+
if (fs.existsSync(privateKeysDir)) {
|
|
1552
|
+
fs.removeSync(privateKeysDir);
|
|
1553
|
+
logger.info(`Removed validator keys from: ${privateKeysDir}`);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
if (cleanManifests) {
|
|
1558
|
+
if (fs.existsSync(besuManifestsPath)) {
|
|
1559
|
+
fs.removeSync(besuManifestsPath);
|
|
1560
|
+
logger.info(`Removed generated manifests from: ${besuManifestsPath}`);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
logger.info('Besu network removed.');
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// ---------------------------------------------------------------------------
|
|
1568
|
+
// Exports
|
|
1569
|
+
// ---------------------------------------------------------------------------
|
|
1570
|
+
|
|
1571
|
+
export {
|
|
1572
|
+
// Key utilities
|
|
1573
|
+
generatePrivateKey,
|
|
1574
|
+
derivePublicKey,
|
|
1575
|
+
publicKeyToAddress,
|
|
1576
|
+
|
|
1577
|
+
// IBFT2 extraData
|
|
1578
|
+
computeIbft2ExtraData,
|
|
1579
|
+
|
|
1580
|
+
// Key generation
|
|
1581
|
+
generateValidatorKeys,
|
|
1582
|
+
|
|
1583
|
+
// Genesis
|
|
1584
|
+
buildGenesis,
|
|
1585
|
+
|
|
1586
|
+
// Individual manifest generators
|
|
1587
|
+
generateNamespaceYaml,
|
|
1588
|
+
generateGenesisConfigMapYaml,
|
|
1589
|
+
generateValidatorsConfigMapYaml,
|
|
1590
|
+
generateConfigTomlConfigMapYaml,
|
|
1591
|
+
generateNodePermissionsConfigMapYaml,
|
|
1592
|
+
generateSecretsYaml,
|
|
1593
|
+
generateServicesYaml,
|
|
1594
|
+
generateValidatorsYaml,
|
|
1595
|
+
generateValidatorStatefulSetYaml,
|
|
1596
|
+
generateKustomizationYaml,
|
|
1597
|
+
|
|
1598
|
+
// Network config
|
|
1599
|
+
buildNetworkConfigJson,
|
|
1600
|
+
|
|
1601
|
+
// High-level orchestrator
|
|
1602
|
+
generateBesuManifests,
|
|
1603
|
+
|
|
1604
|
+
// Deploy / Remove
|
|
1605
|
+
deployBesu,
|
|
1606
|
+
removeBesu,
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
export default {
|
|
1610
|
+
generatePrivateKey,
|
|
1611
|
+
derivePublicKey,
|
|
1612
|
+
publicKeyToAddress,
|
|
1613
|
+
computeIbft2ExtraData,
|
|
1614
|
+
generateValidatorKeys,
|
|
1615
|
+
buildGenesis,
|
|
1616
|
+
generateNamespaceYaml,
|
|
1617
|
+
generateGenesisConfigMapYaml,
|
|
1618
|
+
generateValidatorsConfigMapYaml,
|
|
1619
|
+
generateConfigTomlConfigMapYaml,
|
|
1620
|
+
generateNodePermissionsConfigMapYaml,
|
|
1621
|
+
generateSecretsYaml,
|
|
1622
|
+
generateServicesYaml,
|
|
1623
|
+
generateValidatorsYaml,
|
|
1624
|
+
generateValidatorStatefulSetYaml,
|
|
1625
|
+
generateKustomizationYaml,
|
|
1626
|
+
buildNetworkConfigJson,
|
|
1627
|
+
generateBesuManifests,
|
|
1628
|
+
deployBesu,
|
|
1629
|
+
removeBesu,
|
|
1630
|
+
};
|