cyberia 3.0.3 → 3.2.5

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.
Files changed (296) hide show
  1. package/{.env.production → .env.example} +20 -4
  2. package/.github/workflows/engine-cyberia.cd.yml +43 -10
  3. package/.github/workflows/engine-cyberia.ci.yml +48 -26
  4. package/.github/workflows/ghpkg.ci.yml +5 -5
  5. package/.github/workflows/gitlab.ci.yml +1 -1
  6. package/.github/workflows/hardhat.ci.yml +82 -0
  7. package/.github/workflows/npmpkg.ci.yml +60 -14
  8. package/.github/workflows/publish.ci.yml +26 -7
  9. package/.github/workflows/publish.cyberia.ci.yml +5 -5
  10. package/.github/workflows/pwa-microservices-template-page.cd.yml +6 -7
  11. package/.github/workflows/pwa-microservices-template-test.ci.yml +4 -4
  12. package/.github/workflows/release.cd.yml +14 -8
  13. package/.vscode/extensions.json +9 -8
  14. package/.vscode/settings.json +3 -2
  15. package/CHANGELOG.md +643 -1
  16. package/CLI-HELP.md +132 -57
  17. package/Dockerfile +4 -2
  18. package/README.md +347 -22
  19. package/WHITE-PAPER.md +1540 -0
  20. package/bin/build.js +21 -12
  21. package/bin/cyberia.js +2640 -106
  22. package/bin/deploy.js +258 -372
  23. package/bin/file.js +5 -1
  24. package/bin/index.js +2640 -106
  25. package/bin/vs.js +3 -3
  26. package/conf.js +169 -105
  27. package/deployment.yaml +236 -20
  28. package/hardhat/.env.example +31 -0
  29. package/hardhat/README.md +531 -0
  30. package/hardhat/WHITE-PAPER.md +1540 -0
  31. package/hardhat/contracts/ObjectLayerToken.sol +391 -0
  32. package/hardhat/deployments/.gitkeep +0 -0
  33. package/hardhat/deployments/hardhat-ObjectLayerToken.json +11 -0
  34. package/hardhat/hardhat.config.js +136 -0
  35. package/hardhat/ignition/modules/ObjectLayerToken.js +21 -0
  36. package/hardhat/networks/besu-object-layer.network.json +138 -0
  37. package/hardhat/package-lock.json +4323 -0
  38. package/hardhat/package.json +36 -0
  39. package/hardhat/scripts/deployObjectLayerToken.js +98 -0
  40. package/hardhat/test/ObjectLayerToken.js +592 -0
  41. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  42. package/hardhat/types/ethers-contracts/common.ts +92 -0
  43. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  44. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  45. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  46. package/hardhat/types/ethers-contracts/index.ts +6 -0
  47. package/jsdoc.dd-cyberia.json +68 -0
  48. package/jsdoc.json +65 -49
  49. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -4
  50. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +5 -4
  51. package/manifests/deployment/dd-cyberia-development/deployment.yaml +562 -0
  52. package/manifests/deployment/dd-cyberia-development/proxy.yaml +297 -0
  53. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +132 -0
  54. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  55. package/manifests/deployment/dd-test-development/deployment.yaml +88 -74
  56. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  57. package/manifests/deployment/playwright/deployment.yaml +1 -1
  58. package/manifests/pv-pvc-dd.yaml +1 -1
  59. package/nodemon.json +1 -1
  60. package/package.json +60 -48
  61. package/proxy.yaml +118 -10
  62. package/pv-pvc.yaml +132 -0
  63. package/scripts/k3s-node-setup.sh +1 -1
  64. package/scripts/ports-ls.sh +2 -0
  65. package/scripts/rhel-grpc-setup.sh +56 -0
  66. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +47 -1
  67. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +17 -2
  68. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  69. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +80 -7
  70. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  71. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  72. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  73. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  74. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  75. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  76. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  77. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  78. package/src/api/cyberia-instance/cyberia-fallback-world.js +368 -0
  79. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  80. package/src/api/cyberia-instance/cyberia-instance.model.js +84 -0
  81. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  82. package/src/api/cyberia-instance/cyberia-instance.service.js +191 -0
  83. package/src/api/cyberia-instance/cyberia-portal-connector.js +486 -0
  84. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  85. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +413 -0
  86. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +228 -0
  87. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  88. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +42 -0
  89. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  90. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  91. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  92. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  93. package/src/api/document/document.service.js +1 -1
  94. package/src/api/file/file.controller.js +3 -1
  95. package/src/api/file/file.ref.json +18 -0
  96. package/src/api/file/file.service.js +28 -5
  97. package/src/api/ipfs/ipfs.controller.js +4 -25
  98. package/src/api/ipfs/ipfs.model.js +43 -34
  99. package/src/api/ipfs/ipfs.router.js +8 -13
  100. package/src/api/ipfs/ipfs.service.js +56 -104
  101. package/src/api/object-layer/README.md +347 -22
  102. package/src/api/object-layer/object-layer.controller.js +6 -2
  103. package/src/api/object-layer/object-layer.model.js +12 -8
  104. package/src/api/object-layer/object-layer.router.js +698 -42
  105. package/src/api/object-layer/object-layer.service.js +119 -37
  106. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +1 -2
  107. package/src/api/user/user.router.js +10 -5
  108. package/src/api/user/user.service.js +15 -14
  109. package/src/cli/baremetal.js +6 -10
  110. package/src/cli/cloud-init.js +0 -3
  111. package/src/cli/cluster.js +7 -7
  112. package/src/cli/db.js +723 -857
  113. package/src/cli/deploy.js +215 -105
  114. package/src/cli/env.js +34 -5
  115. package/src/cli/fs.js +5 -4
  116. package/src/cli/image.js +0 -3
  117. package/src/cli/index.js +83 -15
  118. package/src/cli/kubectl.js +211 -0
  119. package/src/cli/monitor.js +5 -6
  120. package/src/cli/release.js +284 -0
  121. package/src/cli/repository.js +708 -62
  122. package/src/cli/run.js +371 -151
  123. package/src/cli/secrets.js +73 -2
  124. package/src/cli/ssh.js +1 -1
  125. package/src/cli/test.js +3 -3
  126. package/src/client/Cryptokoyn.index.js +3 -4
  127. package/src/client/CyberiaPortal.index.js +3 -4
  128. package/src/client/Default.index.js +3 -4
  129. package/src/client/Itemledger.index.js +4 -963
  130. package/src/client/Underpost.index.js +3 -4
  131. package/src/client/components/core/AgGrid.js +20 -5
  132. package/src/client/components/core/Alert.js +2 -2
  133. package/src/client/components/core/AppStore.js +69 -0
  134. package/src/client/components/core/CalendarCore.js +2 -2
  135. package/src/client/components/core/Content.js +22 -3
  136. package/src/client/components/core/Docs.js +30 -6
  137. package/src/client/components/core/DropDown.js +137 -17
  138. package/src/client/components/core/FileExplorer.js +71 -4
  139. package/src/client/components/core/Input.js +1 -1
  140. package/src/client/components/core/Keyboard.js +2 -2
  141. package/src/client/components/core/LogIn.js +2 -2
  142. package/src/client/components/core/LogOut.js +2 -2
  143. package/src/client/components/core/Modal.js +20 -7
  144. package/src/client/components/core/Panel.js +0 -1
  145. package/src/client/components/core/PanelForm.js +19 -19
  146. package/src/client/components/core/RichText.js +1 -2
  147. package/src/client/components/core/SocketIo.js +82 -29
  148. package/src/client/components/core/SocketIoHandler.js +75 -0
  149. package/src/client/components/core/Stream.js +143 -95
  150. package/src/client/components/core/Webhook.js +40 -7
  151. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  152. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +3 -3
  153. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +2 -2
  154. package/src/client/components/cryptokoyn/MenuCryptokoyn.js +3 -3
  155. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  156. package/src/client/components/cyberia/InstanceEngineCyberia.js +700 -0
  157. package/src/client/components/cyberia/MapEngineCyberia.js +1359 -2
  158. package/src/client/components/cyberia/ObjectLayerEngineModal.js +17 -6
  159. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +92 -54
  160. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  161. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +217 -30
  162. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +44 -2
  163. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +3 -4
  164. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +2 -2
  165. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +104 -9
  166. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +5 -0
  167. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  168. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  169. package/src/client/components/default/AppStoreDefault.js +5 -0
  170. package/src/client/components/default/LogInDefault.js +3 -3
  171. package/src/client/components/default/LogOutDefault.js +2 -2
  172. package/src/client/components/default/MenuDefault.js +5 -5
  173. package/src/client/components/default/SocketIoDefault.js +3 -51
  174. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  175. package/src/client/components/itemledger/LogInItemledger.js +3 -3
  176. package/src/client/components/itemledger/LogOutItemledger.js +2 -2
  177. package/src/client/components/itemledger/MenuItemledger.js +3 -3
  178. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  179. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  180. package/src/client/components/underpost/CssUnderpost.js +59 -0
  181. package/src/client/components/underpost/LogInUnderpost.js +6 -3
  182. package/src/client/components/underpost/LogOutUnderpost.js +4 -2
  183. package/src/client/components/underpost/MenuUnderpost.js +104 -18
  184. package/src/client/components/underpost/RoutesUnderpost.js +2 -0
  185. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  186. package/src/client/public/cryptokoyn/assets/logo/base-icon.png +0 -0
  187. package/src/client/public/cryptokoyn/browserconfig.xml +12 -0
  188. package/src/client/public/cryptokoyn/microdata.json +85 -0
  189. package/src/client/public/cryptokoyn/site.webmanifest +57 -0
  190. package/src/client/public/cryptokoyn/sitemap +3 -3
  191. package/src/client/public/default/sitemap +3 -3
  192. package/src/client/public/itemledger/browserconfig.xml +2 -2
  193. package/src/client/public/itemledger/manifest.webmanifest +4 -4
  194. package/src/client/public/itemledger/microdata.json +71 -0
  195. package/src/client/public/itemledger/sitemap +3 -3
  196. package/src/client/public/itemledger/yandex-browser-manifest.json +2 -2
  197. package/src/client/public/test/sitemap +3 -3
  198. package/src/client/services/core/core.service.js +20 -8
  199. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +105 -0
  200. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  201. package/src/client/services/cyberia-entity/cyberia-entity.service.js +105 -0
  202. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  203. package/src/client/services/cyberia-instance/cyberia-instance.service.js +122 -0
  204. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +105 -0
  205. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  206. package/src/client/services/cyberia-map/cyberia-map.service.js +126 -0
  207. package/src/client/services/instance/instance.management.js +2 -2
  208. package/src/client/services/ipfs/ipfs.service.js +3 -23
  209. package/src/client/services/object-layer/object-layer.management.js +3 -3
  210. package/src/client/services/object-layer/object-layer.service.js +21 -0
  211. package/src/client/services/user/user.management.js +2 -2
  212. package/src/client/ssr/body/404.js +15 -11
  213. package/src/client/ssr/body/500.js +15 -11
  214. package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
  215. package/src/client/ssr/head/PwaItemledger.js +60 -0
  216. package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
  217. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  218. package/src/client/ssr/pages/Test.js +11 -10
  219. package/src/client.build.js +0 -3
  220. package/src/client.dev.js +0 -3
  221. package/src/db/DataBaseProvider.js +17 -2
  222. package/src/db/mariadb/MariaDB.js +14 -9
  223. package/src/db/mongo/MongooseDB.js +17 -1
  224. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +305 -0
  225. package/src/grpc/cyberia/README.md +326 -0
  226. package/src/grpc/cyberia/grpc-server.js +530 -0
  227. package/src/index.js +24 -1
  228. package/src/proxy.js +0 -3
  229. package/src/runtime/express/Dockerfile +4 -0
  230. package/src/runtime/express/Express.js +33 -10
  231. package/src/runtime/lampp/Dockerfile +13 -2
  232. package/src/runtime/lampp/Lampp.js +33 -17
  233. package/src/runtime/wp/Dockerfile +68 -0
  234. package/src/runtime/wp/Wp.js +639 -0
  235. package/src/server/auth.js +36 -15
  236. package/src/server/backup.js +39 -12
  237. package/src/server/besu-genesis-generator.js +1630 -0
  238. package/src/server/client-build-docs.js +133 -17
  239. package/src/server/client-build-live.js +9 -18
  240. package/src/server/client-build.js +229 -101
  241. package/src/server/client-dev-server.js +14 -13
  242. package/src/server/client-formatted.js +109 -57
  243. package/src/server/conf.js +391 -164
  244. package/src/server/cron.js +27 -24
  245. package/src/server/dns.js +29 -12
  246. package/src/server/downloader.js +0 -2
  247. package/src/server/ipfs-client.js +24 -1
  248. package/src/server/logger.js +27 -9
  249. package/src/server/object-layer.js +217 -103
  250. package/src/server/peer.js +8 -2
  251. package/src/server/process.js +1 -50
  252. package/src/server/proxy.js +4 -8
  253. package/src/server/runtime.js +30 -9
  254. package/src/server/semantic-layer-generator-floor.js +359 -0
  255. package/src/server/semantic-layer-generator-skin.js +1294 -0
  256. package/src/server/semantic-layer-generator.js +116 -555
  257. package/src/server/ssr.js +0 -3
  258. package/src/server/start.js +19 -12
  259. package/src/server/tls.js +0 -2
  260. package/src/server.js +0 -4
  261. package/src/ws/IoInterface.js +1 -10
  262. package/src/ws/IoServer.js +14 -33
  263. package/src/ws/core/channels/core.ws.chat.js +65 -20
  264. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  265. package/src/ws/core/channels/core.ws.stream.js +90 -31
  266. package/src/ws/core/core.ws.connection.js +12 -33
  267. package/src/ws/core/core.ws.emit.js +10 -26
  268. package/src/ws/core/core.ws.server.js +25 -58
  269. package/src/ws/default/channels/default.ws.main.js +53 -12
  270. package/src/ws/default/default.ws.connection.js +26 -13
  271. package/src/ws/default/default.ws.server.js +30 -12
  272. package/.env.development +0 -43
  273. package/.env.test +0 -43
  274. package/hardhat/contracts/CryptoKoyn.sol +0 -59
  275. package/hardhat/contracts/ItemLedger.sol +0 -73
  276. package/hardhat/contracts/Lock.sol +0 -34
  277. package/hardhat/hardhat.config.cjs +0 -45
  278. package/hardhat/ignition/modules/Lock.js +0 -18
  279. package/hardhat/networks/cryptokoyn-itemledger.network.json +0 -29
  280. package/hardhat/scripts/deployCryptokoyn.cjs +0 -25
  281. package/hardhat/scripts/deployItemledger.cjs +0 -25
  282. package/hardhat/test/Lock.js +0 -126
  283. package/hardhat/white-paper.md +0 -581
  284. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  285. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  286. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  287. package/src/client/components/default/ElementsDefault.js +0 -38
  288. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  289. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  290. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  291. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  292. package/src/ws/core/management/core.ws.chat.js +0 -8
  293. package/src/ws/core/management/core.ws.mailer.js +0 -16
  294. package/src/ws/core/management/core.ws.stream.js +0 -8
  295. package/src/ws/default/management/default.ws.main.js +0 -8
  296. 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
+ };