diff --git a/config/cspell-md.json b/config/cspell-md.json index a4c8a547df..34aee1aeb7 100644 --- a/config/cspell-md.json +++ b/config/cspell-md.json @@ -321,6 +321,13 @@ "beaconroot", "Grandine", "EVMBN", - "kaust" + "kaust", + "Scotty", + "ScottyPoi", + "snappystream", + "Unsnappy", + "unsnappy", + "ethportal", + "bytevector" ] } \ No newline at end of file diff --git a/config/cspell-ts.json b/config/cspell-ts.json index db83071fc1..a7ac46acf9 100644 --- a/config/cspell-ts.json +++ b/config/cspell-ts.json @@ -621,6 +621,14 @@ "Statetest", "testeth", "PUSHDATA", - "chunkified" + "chunkified", + "ScottyPoi", + "Scotty", + "snappystream", + "ethjs", + "Unsnappy", + "unsnappy", + "ethportal", + "bytevector" ] -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 4cd24fbf67..0051ef527c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1233,6 +1233,37 @@ "kuler": "^2.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", + "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", @@ -1688,6 +1719,10 @@ "resolved": "packages/devp2p", "link": true }, + "node_modules/@ethereumjs/era": { + "resolved": "packages/era", + "link": true + }, "node_modules/@ethereumjs/ethash": { "resolved": "packages/ethash", "link": true @@ -2340,6 +2375,226 @@ "uint8arrays": "^5.0.0" } }, + "node_modules/@napi-rs/snappy-android-arm-eabi": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.2.2.tgz", + "integrity": "sha512-H7DuVkPCK5BlAr1NfSU8bDEN7gYs+R78pSHhDng83QxRnCLmVIZk33ymmIwurmoA1HrdTxbkbuNl+lMvNqnytw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-android-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.2.2.tgz", + "integrity": "sha512-2R/A3qok+nGtpVK8oUMcrIi5OMDckGYNoBLFyli3zp8w6IArPRfg1yOfVUcHvpUDTo9T7LOS1fXgMOoC796eQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-darwin-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.2.2.tgz", + "integrity": "sha512-USgArHbfrmdbuq33bD5ssbkPIoT7YCXCRLmZpDS6dMDrx+iM7eD2BecNbOOo7/v1eu6TRmQ0xOzeQ6I/9FIi5g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-darwin-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.2.2.tgz", + "integrity": "sha512-0APDu8iO5iT0IJKblk2lH0VpWSl9zOZndZKnBYIc+ei1npw2L5QvuErFOTeTdHBtzvUHASB+9bvgaWnQo4PvTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-freebsd-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.2.2.tgz", + "integrity": "sha512-mRTCJsuzy0o/B0Hnp9CwNB5V6cOJ4wedDTWEthsdKHSsQlO7WU9W1yP7H3Qv3Ccp/ZfMyrmG98Ad7u7lG58WXA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm-gnueabihf": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.2.2.tgz", + "integrity": "sha512-v1uzm8+6uYjasBPcFkv90VLZ+WhLzr/tnfkZ/iD9mHYiULqkqpRuC8zvc3FZaJy5wLQE9zTDkTJN1IvUcZ+Vcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.2.2.tgz", + "integrity": "sha512-LrEMa5pBScs4GXWOn6ZYXfQ72IzoolZw5txqUHVGs8eK4g1HR9HTHhb2oY5ySNaKakG5sOgMsb1rwaEnjhChmQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.2.2.tgz", + "integrity": "sha512-3orWZo9hUpGQcB+3aTLW7UFDqNCQfbr0+MvV67x8nMNYj5eAeUtMmUE/HxLznHO4eZ1qSqiTwLbVx05/Socdlw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-x64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.2.2.tgz", + "integrity": "sha512-jZt8Jit/HHDcavt80zxEkDpH+R1Ic0ssiVCoueASzMXa7vwPJeF4ZxZyqUw4qeSy7n8UUExomu8G8ZbP6VKhgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-x64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.2.2.tgz", + "integrity": "sha512-Dh96IXgcZrV39a+Tej/owcd9vr5ihiZ3KRix11rr1v0MWtVb61+H1GXXlz6+Zcx9y8jM1NmOuiIuJwkV4vZ4WA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-arm64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.2.2.tgz", + "integrity": "sha512-9No0b3xGbHSWv2wtLEn3MO76Yopn1U2TdemZpCaEgOGccz1V+a/1d16Piz3ofSmnA13HGFz3h9NwZH9EOaIgYA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-ia32-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.2.2.tgz", + "integrity": "sha512-QiGe+0G86J74Qz1JcHtBwM3OYdTni1hX1PFyLRo3HhQUSpmi13Bzc1En7APn+6Pvo7gkrcy81dObGLDSxFAkQQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-x64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.2.2.tgz", + "integrity": "sha512-a43cyx1nK0daw6BZxVcvDEXxKMFLSBSDTAhsFD0VqSKcC7MGUBMaqyoWUcMiI7LBSz4bxUmxDWKfCYzpEmeb3w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.6.tgz", + "integrity": "sha512-z8YVS3XszxFTO73iwvFDNpQIzdMmSDTP/mB3E/ucR37V3Sx57hSExcXyMoNwaucWxnsWf4xfbZv0iZ30jr0M4Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.3.1", + "@emnapi/runtime": "^1.3.1", + "@tybys/wasm-util": "^0.9.0" + } + }, "node_modules/@noble/ciphers": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", @@ -2376,6 +2631,259 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@node-rs/crc32": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32/-/crc32-1.10.6.tgz", + "integrity": "sha512-+llXfqt+UzgoDzT9of5vPQPGqTAVCohU74I9zIBkNo5TH6s2P31DFJOGsJQKN207f0GHnYv5pV3wh3BCY/un/A==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/crc32-android-arm-eabi": "1.10.6", + "@node-rs/crc32-android-arm64": "1.10.6", + "@node-rs/crc32-darwin-arm64": "1.10.6", + "@node-rs/crc32-darwin-x64": "1.10.6", + "@node-rs/crc32-freebsd-x64": "1.10.6", + "@node-rs/crc32-linux-arm-gnueabihf": "1.10.6", + "@node-rs/crc32-linux-arm64-gnu": "1.10.6", + "@node-rs/crc32-linux-arm64-musl": "1.10.6", + "@node-rs/crc32-linux-x64-gnu": "1.10.6", + "@node-rs/crc32-linux-x64-musl": "1.10.6", + "@node-rs/crc32-wasm32-wasi": "1.10.6", + "@node-rs/crc32-win32-arm64-msvc": "1.10.6", + "@node-rs/crc32-win32-ia32-msvc": "1.10.6", + "@node-rs/crc32-win32-x64-msvc": "1.10.6" + } + }, + "node_modules/@node-rs/crc32-android-arm-eabi": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm-eabi/-/crc32-android-arm-eabi-1.10.6.tgz", + "integrity": "sha512-vZAMuJXm3TpWPOkkhxdrofWDv+Q+I2oO7ucLRbXyAPmXFNDhHtBxbO1rk9Qzz+M3eep8ieS4/+jCL1Q0zacNMQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-android-arm64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm64/-/crc32-android-arm64-1.10.6.tgz", + "integrity": "sha512-Vl/JbjCinCw/H9gEpZveWCMjxjcEChDcDBM8S4hKay5yyoRCUHJPuKr4sjVDBeOm+1nwU3oOm6Ca8dyblwp4/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-arm64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.10.6.tgz", + "integrity": "sha512-kARYANp5GnmsQiViA5Qu74weYQ3phOHSYQf0G+U5wB3NB5JmBHnZcOc46Ig21tTypWtdv7u63TaltJQE41noyg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-x64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.10.6.tgz", + "integrity": "sha512-Q99bevJVMfLTISpkpKBlXgtPUItrvTWKFyiqoKH5IvscZmLV++NH4V13Pa17GTBmv9n18OwzgQY4/SRq6PQNVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-freebsd-x64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-freebsd-x64/-/crc32-freebsd-x64-1.10.6.tgz", + "integrity": "sha512-66hpawbNjrgnS9EDMErta/lpaqOMrL6a6ee+nlI2viduVOmRZWm9Rg9XdGTK/+c4bQLdtC6jOd+Kp4EyGRYkAg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm-gnueabihf": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.10.6.tgz", + "integrity": "sha512-E8Z0WChH7X6ankbVm8J/Yym19Cq3otx6l4NFPS6JW/cWdjv7iw+Sps2huSug+TBprjbcEA+s4TvEwfDI1KScjg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-gnu": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.10.6.tgz", + "integrity": "sha512-LmWcfDbqAvypX0bQjQVPmQGazh4dLiVklkgHxpV4P0TcQ1DT86H/SWpMBMs/ncF8DGuCQ05cNyMv1iddUDugoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-musl": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.10.6.tgz", + "integrity": "sha512-k8ra/bmg0hwRrIEE8JL1p32WfaN9gDlUUpQRWsbxd1WhjqvXea7kKO6K4DwVxyxlPhBS9Gkb5Urq7Y4mXANzaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-x64-gnu": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.10.6.tgz", + "integrity": "sha512-IfjtqcuFK7JrSZ9mlAFhb83xgium30PguvRjIMI45C3FJwu18bnLk1oR619IYb/zetQT82MObgmqfKOtgemEKw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-x64-musl": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-musl/-/crc32-linux-x64-musl-1.10.6.tgz", + "integrity": "sha512-LbFYsA5M9pNunOweSt6uhxenYQF94v3bHDAQRPTQ3rnjn+mK6IC7YTAYoBjvoJP8lVzcvk9hRj8wp4Jyh6Y80g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-wasm32-wasi": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-wasm32-wasi/-/crc32-wasm32-wasi-1.10.6.tgz", + "integrity": "sha512-KaejdLgHMPsRaxnM+OG9L9XdWL2TabNx80HLdsCOoX9BVhEkfh39OeahBo8lBmidylKbLGMQoGfIKDjq0YMStw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/crc32-win32-arm64-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.10.6.tgz", + "integrity": "sha512-x50AXiSxn5Ccn+dCjLf1T7ZpdBiV1Sp5aC+H2ijhJO4alwznvXgWbopPRVhbp2nj0i+Gb6kkDUEyU+508KAdGQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-ia32-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.10.6.tgz", + "integrity": "sha512-DpDxQLaErJF9l36aghe1Mx+cOnYLKYo6qVPqPL9ukJ5rAGLtCdU0C+Zoi3gs9ySm8zmbFgazq/LvmsZYU42aBw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-x64-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.10.6.tgz", + "integrity": "sha512-5B1vXosIIBw1m2Rcnw62IIfH7W9s9f7H7Ma0rRuhT8HR4Xh8QCgw6NJSI2S2MCngsGktYnAhyUvs81b7efTyQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3307,6 +3815,16 @@ "optional": true, "peer": true }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -11118,6 +11636,15 @@ "node": ">= 0.4" } }, + "node_modules/maybe-combine-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz", + "integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/mcl-wasm": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-1.8.0.tgz", @@ -13675,11 +14202,52 @@ "npm": ">= 3.0.0" } }, + "node_modules/snappy": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.2.2.tgz", + "integrity": "sha512-iADMq1kY0v3vJmGTuKcFWSXt15qYUz7wFkArOrsSg0IFfI3nJqIJvK2/ZbEIndg7erIJLtAVX2nSOqPz7DcwbA==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/snappy-android-arm-eabi": "7.2.2", + "@napi-rs/snappy-android-arm64": "7.2.2", + "@napi-rs/snappy-darwin-arm64": "7.2.2", + "@napi-rs/snappy-darwin-x64": "7.2.2", + "@napi-rs/snappy-freebsd-x64": "7.2.2", + "@napi-rs/snappy-linux-arm-gnueabihf": "7.2.2", + "@napi-rs/snappy-linux-arm64-gnu": "7.2.2", + "@napi-rs/snappy-linux-arm64-musl": "7.2.2", + "@napi-rs/snappy-linux-x64-gnu": "7.2.2", + "@napi-rs/snappy-linux-x64-musl": "7.2.2", + "@napi-rs/snappy-win32-arm64-msvc": "7.2.2", + "@napi-rs/snappy-win32-ia32-msvc": "7.2.2", + "@napi-rs/snappy-win32-x64-msvc": "7.2.2" + } + }, "node_modules/snappyjs": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.6.1.tgz", "integrity": "sha512-YIK6I2lsH072UE0aOFxxY1dPDCS43I5ktqHpeAsuLNYWkE5pGxRGWfDM4/vSUfNzXjC1Ivzt3qx31PCLmc9yqg==" }, + "node_modules/snappystream": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snappystream/-/snappystream-2.1.1.tgz", + "integrity": "sha512-VaQhOYlyrrqmEmTdYW6rR6nKyQFbJ1DfX9U7P1NZafFh5RkoM1aSrizfP5BOB1h+9kwVk0pQV+OgZAK7S48PWw==", + "license": "MIT", + "dependencies": { + "@node-rs/crc32": "^1.7.2", + "snappy": "^7.2.2" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -16690,6 +17258,177 @@ "undici-types": "~6.20.0" } }, + "packages/era": { + "name": "@ethereumjs/era", + "version": "1.0.0-alpha.1", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/block": "^6.0.0-alpha.1", + "@ethereumjs/blockchain": "^8.0.0-alpha.1", + "@ethereumjs/rlp": "^6.0.0-alpha.1", + "@ethereumjs/util": "^10.0.0-alpha.1", + "level": "^8.0.0", + "micro-eth-signer": "^0.13.3", + "snappystream": "^2.1.1" + }, + "devDependencies": {}, + "engines": { + "node": ">=18" + } + }, + "packages/era/node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/era/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/era/node_modules/browser-level": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-2.0.0.tgz", + "integrity": "sha512-RuYSCHG/jwFCrK+KWA3dLSUNLKHEgIYhO5ORPjJMjCt7T3e+RzpIDmYKWRHxq2pfKGXjlRuEff7y7RESAAgzew==", + "license": "MIT", + "dependencies": { + "abstract-level": "^2.0.1" + } + }, + "packages/era/node_modules/browser-level/node_modules/abstract-level": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-2.0.2.tgz", + "integrity": "sha512-pPJixmXk/kTKLB2sSue7o4Uj6TlLD2XfaP2gWZomHVCC6cuUGX/VslQqKG1yZHfXwBb/3lS6oSTMPGzh1P1iig==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "is-buffer": "^2.0.5", + "level-supports": "^6.0.0", + "level-transcoder": "^1.0.1", + "maybe-combine-errors": "^1.0.0", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "packages/era/node_modules/classic-level": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-2.0.0.tgz", + "integrity": "sha512-ftiMvKgCQK+OppXcvMieDoYlYLYWhScK6yZRFBrrlHQRbm4k6Gr+yDgu/wt3V0k1/jtNbuiXAsRmuAFcD0Tx5Q==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "abstract-level": "^2.0.0", + "module-error": "^1.0.1", + "napi-macros": "^2.2.2", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/era/node_modules/classic-level/node_modules/abstract-level": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-2.0.2.tgz", + "integrity": "sha512-pPJixmXk/kTKLB2sSue7o4Uj6TlLD2XfaP2gWZomHVCC6cuUGX/VslQqKG1yZHfXwBb/3lS6oSTMPGzh1P1iig==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "is-buffer": "^2.0.5", + "level-supports": "^6.0.0", + "level-transcoder": "^1.0.1", + "maybe-combine-errors": "^1.0.0", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "packages/era/node_modules/level": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-9.0.0.tgz", + "integrity": "sha512-n+mVuf63mUEkd8NUx7gwxY+QF5vtkibv6fXTGUgtHWLPDaA5/XZjLcI/Q1nQ8k6OttHT6Ezt+7nSEXsRUfHtOQ==", + "license": "MIT", + "dependencies": { + "abstract-level": "^2.0.1", + "browser-level": "^2.0.0", + "classic-level": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "packages/era/node_modules/level-supports": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-6.2.0.tgz", + "integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "packages/era/node_modules/level/node_modules/abstract-level": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-2.0.2.tgz", + "integrity": "sha512-pPJixmXk/kTKLB2sSue7o4Uj6TlLD2XfaP2gWZomHVCC6cuUGX/VslQqKG1yZHfXwBb/3lS6oSTMPGzh1P1iig==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "is-buffer": "^2.0.5", + "level-supports": "^6.0.0", + "level-transcoder": "^1.0.1", + "maybe-combine-errors": "^1.0.0", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "packages/era/node_modules/micro-eth-signer": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.13.3.tgz", + "integrity": "sha512-C1AxZMw10VGQrdox/6rtl94aUYOceEnPUR+ADkm35/z4HPqb6ijIDF6G/KySuG2QveH9dqOohKzaHoru/A3xSw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "micro-packed": "~0.7.2" + } + }, + "packages/era/node_modules/micro-packed": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.2.tgz", + "integrity": "sha512-HJ/u8+tMzgrJVAl6P/4l8KGjJSA3SCZaRb1m4wpbovNScCSmVOGUYbkkcoPPcknCHWPpRAdjy+yqXqyQWf+k8g==", + "license": "MIT", + "dependencies": { + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/ethash": { "name": "@ethereumjs/ethash", "version": "4.0.0-alpha.1", diff --git a/packages/client/src/util/index.ts b/packages/client/src/util/index.ts index de076c6471..59635e92cc 100644 --- a/packages/client/src/util/index.ts +++ b/packages/client/src/util/index.ts @@ -10,7 +10,6 @@ import { fileURLToPath } from 'url' export * from './inclineClient.js' export * from './parse.js' export * from './rpc.js' - // See: https://stackoverflow.com/a/50053801 const __dirname = dirname(fileURLToPath(import.meta.url)) diff --git a/packages/era/.c8rc.json b/packages/era/.c8rc.json new file mode 100644 index 0000000000..52eb43c23b --- /dev/null +++ b/packages/era/.c8rc.json @@ -0,0 +1,4 @@ +{ + "extends": "../../config/.c8rc.json", + "include": ["src/**/*.ts"] +} diff --git a/packages/era/.eslintrc.cjs b/packages/era/.eslintrc.cjs new file mode 100644 index 0000000000..19094c2277 --- /dev/null +++ b/packages/era/.eslintrc.cjs @@ -0,0 +1,15 @@ +module.exports = { + extends: '../../config/eslint.cjs', + parserOptions: { + project: ['./tsconfig.lint.json'], + }, + overrides: [ + { + files: ['examples/**/*'], + rules: { + 'no-console': 'off', + '@typescript-eslint/no-unused-vars': 'off', + }, + }, + ], +} diff --git a/packages/era/.gitignore b/packages/era/.gitignore new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/packages/era/.gitignore @@ -0,0 +1 @@ + diff --git a/packages/era/CHANGELOG.md b/packages/era/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/era/LICENSE b/packages/era/LICENSE new file mode 100644 index 0000000000..a612ad9813 --- /dev/null +++ b/packages/era/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/packages/era/README.md b/packages/era/README.md new file mode 100644 index 0000000000..c6f354912b --- /dev/null +++ b/packages/era/README.md @@ -0,0 +1,94 @@ +# @ethereumjs/era + +[![NPM Package][era-npm-badge]][era-npm-link] +[![GitHub Issues][era-issues-badge]][era-issues-link] +[![Actions Status][era-actions-badge]][era-actions-link] +[![Code Coverage][era-coverage-badge]][era-coverage-link] +[![Discord][discord-badge]][discord-link] + +| A collection of utility functions for Ethereum. | +| ----------------------------------------------- | + +## Installation + +To obtain the latest version, simply require the project using `npm`: + +```shell +npm install @ethereumjs/era +``` + +## Usage + +All helpers are re-exported from the root level and deep imports are not necessary. So an import can be done like this: + +```ts +import { formatEntry } from "@ethereumjs/era"; +``` + +#### Export History as Era1 + +Export history in epochs of 8192 blocks as Era1 files + +```ts +import { exportEpochAsEra1 } from "@ethereumjs/era"; + +const dataDir = PATH_TO_ETHEREUMJS_CLIENT_DB; +const epoch = 0; + +// generates ${dataDir}/era1/epoch-0.era1 +await exportEpochAsEra1(epoch, dataDir); +``` + +#### Read Era1 file + +`readERA1` returns an async iterator of block tuples (header + body + receipts + totalDifficulty) + +```ts +import { + readBinaryFile, + validateERA1, + readERA1, + parseBlockTuple, + blockFromTuple, + getHeaderRecords, + EpochAccumulator, +} from "@ethereumjs/era"; + +const era1File = readBinaryFile(PATH_TO_ERA1_FILE); + +// validate era1 file +const isValid = validateERA1(era1File); + +// read blocks from era1 file +const blocks = readERA1(era1File); + +for await (const blockTuple of blocks) { + const { header, body, receipts } = await parseBlockTuple(blockTuple); + const block = blockFromTuple({ header, body }); + console.log(block.header.number); +} + +// reconstruct epoch accumulator +const headerRecords = await getHeaderRecords(era1File); +const epochAccumulator = EpochAccumulator.encode(headerRecords); +const epochAccumulatorRoot = EpochAccumulator.merkleRoot(headerRecords); +``` + +## EthereumJS + +See our organizational [documentation](https://ethereumjs.readthedocs.io) for an introduction to `EthereumJS` as well as information on current standards and best practices. If you want to join for work or carry out improvements on the libraries, please review our [contribution guidelines](https://ethereumjs.readthedocs.io/en/latest/contributing.html) first. + +## License + +[MPL-2.0]() + +[era-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/era.svg +[era-npm-link]: https://www.npmjs.org/package/@ethereumjs/era +[era-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-monorepo/package:%20era?label=issues +[era-issues-link]: https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aopen+is%3Aissue+label%3A"package%3A+era" +[era-actions-badge]: https://github.com/ethereumjs/ethereumjs-monorepo/workflows/Era/badge.svg +[era-actions-link]: https://github.com/ethereumjs/ethereumjs-monorepo/actions?query=workflow%3A%22Era%22 +[era-coverage-badge]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/branch/master/graph/badge.svg?flag=era +[era-coverage-link]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/tree/master/packages/era +[discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue +[discord-link]: https://discord.gg/TNwARpR diff --git a/packages/era/package.json b/packages/era/package.json new file mode 100644 index 0000000000..962ae5c3e6 --- /dev/null +++ b/packages/era/package.json @@ -0,0 +1,62 @@ +{ + "name": "@ethereumjs/era", + "version": "1.0.0-alpha.1", + "description": "Era file support for EthereumJS", + "keywords": ["ethereum", "era", "era1"], + "homepage": "https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/era#readme", + "bugs": { + "url": "https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aissue+label%3A%22package%3A+era%22" + }, + "repository": { + "type": "git", + "url": "https://github.com/ethereumjs/ethereumjs-monorepo.git" + }, + "license": "MPL-2.0", + "author": "EthereumJS Team", + "contributors": [ + { + "name": "ScottyPoi", + "url": "https://github.com/scottypoi" + } + ], + "type": "module", + "sideEffects": false, + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": ["dist", "src"], + "scripts": { + "biome": "npx @biomejs/biome check", + "biome:fix": "npx @biomejs/biome check --write", + "build": "../../config/cli/ts-build.sh", + "clean": "../../config/cli/clean-package.sh", + "coverage": "DEBUG=ethjs npx vitest run -c ../../config/vitest.config.coverage.mts", + "docs:build": "npx typedoc --options typedoc.cjs", + "examples": "tsx ../../scripts/examples-runner.ts -- util", + "examples:build": "npx embedme README.md", + "lint": "npm run biome && eslint --config .eslintrc.cjs . --ext .js,.ts", + "lint:fix": "npm run biome:fix && eslint --fix --config .eslintrc.cjs . --ext .js,.ts", + "prepublishOnly": "../../config/cli/prepublish.sh", + "test": "npm run test:node", + "test:node": "npx vitest run", + "tsc": "../../config/cli/ts-compile.sh" + }, + "dependencies": { + "level": "^8.0.0", + "@ethereumjs/block": "^6.0.0-alpha.1", + "@ethereumjs/blockchain": "^8.0.0-alpha.1", + "@ethereumjs/rlp": "^6.0.0-alpha.1", + "micro-eth-signer": "^0.13.3", + "snappystream": "^2.1.1", + "@ethereumjs/util": "^10.0.0-alpha.1" + }, + "devDependencies": {}, + "engines": { + "node": ">=18" + } +} diff --git a/packages/era/src/blockTuple.ts b/packages/era/src/blockTuple.ts new file mode 100644 index 0000000000..07d282f802 --- /dev/null +++ b/packages/era/src/blockTuple.ts @@ -0,0 +1,85 @@ +import { type Block, type BlockBytes, createBlockFromBytesArray } from '@ethereumjs/block' +import { RLP } from '@ethereumjs/rlp' + +import { parseEntry, readEntry } from './e2store.js' + +import type { e2StoreEntry } from './types.js' + +export async function createBlockTuples(blocks: Block[], blockReceipts: Uint8Array[], td: bigint) { + const blockTuples: { + header: Uint8Array + body: Uint8Array + receipts: Uint8Array + totalDifficulty: bigint + }[] = [] + const headerRecords: { + blockHash: Uint8Array + totalDifficulty: bigint + }[] = [] + for (const [i, block] of blocks.entries()) { + td += block.header.difficulty + headerRecords.push({ + blockHash: block.hash(), + totalDifficulty: td, + }) + const receipts = blockReceipts[i] + + const body = [ + block.transactions.map((tx) => tx.serialize()), + block.uncleHeaders.map((uh) => uh.raw()), + ] + if (block.withdrawals) { + body.push(block.withdrawals.map((w) => w.raw())) + } + blockTuples.push({ + header: block.header.serialize(), + body: RLP.encode(body), + receipts, + totalDifficulty: td, + }) + } + return { + headerRecords, + blockTuples, + totalDifficulty: td, + } +} + +export async function parseBlockTuple({ + headerEntry, + bodyEntry, + receiptsEntry, + totalDifficultyEntry, +}: { + headerEntry: e2StoreEntry + bodyEntry: e2StoreEntry + receiptsEntry: e2StoreEntry + totalDifficultyEntry: e2StoreEntry +}): Promise<{ header: any; body: any; receipts: any; totalDifficulty: any }> { + const header = await parseEntry(headerEntry) + const body = await parseEntry(bodyEntry) + const receipts = await parseEntry(receiptsEntry) + const totalDifficulty = await parseEntry(totalDifficultyEntry) + return { header, body, receipts, totalDifficulty } +} + +export function readBlockTupleAtOffset(bytes: Uint8Array, recordStart: number, offset: number) { + const headerEntry = readEntry(bytes.slice(recordStart + offset)) + const headerLength = headerEntry.data.length + 8 + const bodyEntry = readEntry(bytes.slice(recordStart + offset + headerLength)) + const bodyLength = bodyEntry.data.length + 8 + const receiptsEntry = readEntry(bytes.slice(recordStart + offset + headerLength + bodyLength)) + const receiptsLength = receiptsEntry.data.length + 8 + const totalDifficultyEntry = readEntry( + bytes.slice(recordStart + offset + headerLength + bodyLength + receiptsEntry.data.length + 8), + ) + const totalDifficultyLength = totalDifficultyEntry.data.length + 8 + const totalLength = headerLength + bodyLength + receiptsLength + totalDifficultyLength + return { headerEntry, bodyEntry, receiptsEntry, totalDifficultyEntry, length: totalLength } +} + +export function blockFromTuple({ header, body }: { header: any; body: any }) { + const valuesArray = [header.data, body.data.txs, body.data.uncles, body.data.withdrawals] + const block = createBlockFromBytesArray(valuesArray as BlockBytes, { setHardfork: true }) + return block +} diff --git a/packages/era/src/e2store.ts b/packages/era/src/e2store.ts new file mode 100644 index 0000000000..39a3a7d326 --- /dev/null +++ b/packages/era/src/e2store.ts @@ -0,0 +1,85 @@ +import { RLP } from '@ethereumjs/rlp' +import { bigInt64ToBytes, bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util' +import { uint256 } from 'micro-eth-signer/ssz' + +import { compressData, decompressData } from './snappy.js' +import { Era1Types } from './types.js' + +import type { e2StoreEntry } from './types.js' + +export async function parseEntry(entry: e2StoreEntry) { + if (equalsBytes(entry.type, Era1Types.TotalDifficulty)) { + return { type: entry.type, data: uint256.decode(entry.data) } + } + const decompressed = await decompressData(entry.data) + let data + switch (bytesToHex(entry.type)) { + case bytesToHex(Era1Types.CompressedHeader): + data = RLP.decode(decompressed) + break + case bytesToHex(Era1Types.CompressedBody): { + const [txs, uncles, withdrawals] = RLP.decode(decompressed) + data = { txs, uncles, withdrawals } + break + } + case bytesToHex(Era1Types.CompressedReceipts): + data = decompressed + data = RLP.decode(decompressed) + break + case bytesToHex(Era1Types.AccumulatorRoot): + data = decompressed + break + default: + throw new Error(`unknown entry type - ${bytesToHex(entry.type)}`) + } + return { type: entry.type, data } +} + +/** + * Reads the first e2Store formatted entry from a string of bytes + * @param bytes a Uint8Array containing one or more serialized {@link e2StoreEntry} + * @returns a deserialized {@link e2StoreEntry} + * @throws if the length of the entry read is greater than the possible number of bytes in the data element + */ +export const readEntry = (bytes: Uint8Array): e2StoreEntry => { + if (bytes.length < 8) + throw new Error(`invalid data length, got ${bytes.length}, expected at least 8`) + const type = bytes.slice(0, 2) + const lengthBytes = concatBytes(bytes.subarray(2, 8), new Uint8Array([0, 0])) + const length = Number( + new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getBigUint64(0, true), + ) + if (length > bytes.length) { + // Check for overflow + throw new Error(`invalid data length, got ${length}, expected max of ${bytes.length - 8}`) + } + + const data = length > 0 ? bytes.subarray(8, 8 + length) : new Uint8Array() + return { type, data } +} + +/** + * Format e2store entry + * @param entry { type: entry type, data: uncompressed data } + * @returns serialized entry + */ +export const formatEntry = async ({ + type, + data, +}: { + type: Uint8Array + data: Uint8Array +}) => { + const compressed = equalsBytes(type, Era1Types.TotalDifficulty) + ? data + : equalsBytes(type, Era1Types.AccumulatorRoot) + ? data + : equalsBytes(type, Era1Types.Version) + ? data + : equalsBytes(type, Era1Types.BlockIndex) + ? data + : await compressData(data) + const length = compressed.length + const lengthBytes = bigInt64ToBytes(BigInt(length), true).slice(0, 6) + return concatBytes(type, lengthBytes, compressed) +} diff --git a/packages/era/src/era1.ts b/packages/era/src/era1.ts new file mode 100644 index 0000000000..ef778db91d --- /dev/null +++ b/packages/era/src/era1.ts @@ -0,0 +1,184 @@ +import { bigInt64ToBytes, bytesToBigInt64, concatBytes, equalsBytes } from '@ethereumjs/util' +import * as ssz from 'micro-eth-signer/ssz' + +import { blockFromTuple, parseBlockTuple, readBlockTupleAtOffset } from './blockTuple.js' +import { formatEntry, readEntry } from './e2store.js' +import { EpochAccumulator, Era1Types, VERSION } from './types.js' + +/** + * Format era1 from epoch of history data + * @param blockTuples header, body, receipts, totalDifficulty + * @param headerRecords array of Header Records { blockHash: Uint8Array, totalDifficulty: bigint } + * @param epoch epoch index + * @returns serialized era1 file + */ +export const formatEra1 = async ( + blockTuples: { + header: Uint8Array + body: Uint8Array + receipts: Uint8Array + totalDifficulty: bigint + }[], + headerRecords: { + blockHash: Uint8Array + totalDifficulty: bigint + }[], + epoch: number, +) => { + const version = await formatEntry(VERSION) + const blocks = [] + for (const { header, body, receipts, totalDifficulty } of blockTuples) { + const compressedHeader = await formatEntry({ + type: Era1Types.CompressedHeader, + data: header, + }) + const compressedBody = await formatEntry({ + type: Era1Types.CompressedBody, + data: body, + }) + const compressedReceipts = await formatEntry({ + type: Era1Types.CompressedReceipts, + data: receipts, + }) + const compressedTotalDifficulty = await formatEntry({ + type: Era1Types.TotalDifficulty, + data: ssz.uint256.encode(totalDifficulty), + }) + const entry = concatBytes( + compressedHeader, + compressedBody, + compressedReceipts, + compressedTotalDifficulty, + ) + blocks.push(entry) + } + const blocksLength = blocks.reduce((acc, b) => acc + b.length, 0) + + const epochAccumulatorRoot = EpochAccumulator.merkleRoot(headerRecords) + + const accumulatorEntry = await formatEntry({ + type: Era1Types.AccumulatorRoot, + data: epochAccumulatorRoot, + }) + + const startingNumber = bigInt64ToBytes(BigInt(epoch * 8192), true) + const count = bigInt64ToBytes(BigInt(blocks.length), true) + + const blockIndexLength = 8 * blocks.length + 24 + + const eraLength = version.length + blocksLength + accumulatorEntry.length + blockIndexLength + + const recordStart = eraLength - blockIndexLength + const offsetBigInt: bigint[] = [] + for (let i = 0; i < blocks.length; i++) { + if (i === 0) { + const offset = 8 - recordStart + offsetBigInt.push(BigInt(offset)) + } else { + const offset = offsetBigInt[i - 1] + BigInt(blocks[i - 1].length) + offsetBigInt.push(offset) + } + } + + const offsets: Uint8Array[] = offsetBigInt.map((o) => bigInt64ToBytes(o, true)) + + // startingNumber | index | index | index ... | count + const blockIndex = await formatEntry({ + type: Era1Types.BlockIndex, + data: concatBytes(startingNumber, ...offsets, count), + }) + + // version | block-tuple* | other-entries | Accumulator | BLockIndex + const era1 = concatBytes(version, ...blocks, accumulatorEntry, blockIndex) + return era1 +} + +export function getBlockIndex(bytes: Uint8Array) { + const count = Number(bytesToBigInt64(bytes.slice(-8), true)) + const recordLength = 8 * count + 24 + const recordEnd = bytes.length + const recordStart = recordEnd - recordLength + const { data, type } = readEntry(bytes.subarray(recordStart, recordEnd)) + if (!equalsBytes(type, Era1Types.BlockIndex)) { + throw new Error('not a valid block index') + } + return { data, type, count, recordStart } +} + +export function readBlockIndex(data: Uint8Array, count: number) { + const startingNumber = Number(bytesToBigInt64(data.slice(0, 8), true)) + const offsets: number[] = [] + for (let i = 0; i < count; i++) { + const slotEntry = data.subarray((i + 1) * 8, (i + 2) * 8) + const offset = Number(new DataView(slotEntry.slice(0, 8).buffer).getBigInt64(0, true)) + offsets.push(offset) + } + return { + startingNumber, + offsets, + } +} + +export async function* readBlockTuplesFromERA1( + bytes: Uint8Array, + count: number, + offsets: number[], + recordStart: number, +) { + for (let x = 0; x < count; x++) { + try { + const { headerEntry, bodyEntry, receiptsEntry, totalDifficultyEntry } = + readBlockTupleAtOffset(bytes, recordStart, offsets[x]) + yield { headerEntry, bodyEntry, receiptsEntry, totalDifficultyEntry } + } catch { + // noop - we skip empty slots + } + } +} + +export async function readOtherEntries(bytes: Uint8Array) { + const { data, count, recordStart } = getBlockIndex(bytes) + const { offsets } = readBlockIndex(data, count) + const lastTuple = readBlockTupleAtOffset(bytes, recordStart, offsets[count - 1]) + const otherEntries = [] + let next = recordStart + offsets[count - 1] + lastTuple.length + let nextEntry = readEntry(bytes.slice(next)) + while (!equalsBytes(nextEntry.type, Era1Types.AccumulatorRoot)) { + otherEntries.push(nextEntry) + next = next + nextEntry.data.length + 8 + nextEntry = readEntry(bytes.slice(next)) + } + return { accumulatorRoot: nextEntry.data, otherEntries } +} +export async function readAccumulatorRoot(bytes: Uint8Array) { + const { accumulatorRoot } = await readOtherEntries(bytes) + return accumulatorRoot +} + +export async function readERA1(bytes: Uint8Array) { + const { data, count, recordStart } = getBlockIndex(bytes) + const { offsets } = readBlockIndex(data, count) + return readBlockTuplesFromERA1(bytes, count, offsets, recordStart) +} + +export async function getHeaderRecords(bytes: Uint8Array) { + const blockTuples = await readERA1(bytes) + const headerRecords = [] + for await (const tuple of blockTuples) { + const { header, body, totalDifficulty } = await parseBlockTuple(tuple) + const block = blockFromTuple({ header, body }) + const headerRecord = { + blockHash: block.header.hash(), + totalDifficulty: totalDifficulty.data, + } + headerRecords.push(headerRecord) + } + return headerRecords +} + +export async function validateERA1(bytes: Uint8Array) { + const accumulatorRoot = await readAccumulatorRoot(bytes) + const headerRecords = await getHeaderRecords(bytes) + const epochAccumulatorRoot = EpochAccumulator.merkleRoot(headerRecords) + return equalsBytes(epochAccumulatorRoot, accumulatorRoot) +} diff --git a/packages/era/src/exportHistory.ts b/packages/era/src/exportHistory.ts new file mode 100644 index 0000000000..89b2174a1b --- /dev/null +++ b/packages/era/src/exportHistory.ts @@ -0,0 +1,159 @@ +import { DBOp } from '@ethereumjs/blockchain' +import { RLP } from '@ethereumjs/rlp' +import { bytesToBigInt, concatBytes, intToBytes } from '@ethereumjs/util' +import { existsSync, mkdirSync, writeFileSync } from 'fs' +import { Level } from 'level' + +import { formatEra1 } from './era1.js' + +import type { BlockBodyBytes } from '@ethereumjs/block' + +type DatabaseKey = { + blockNumber?: bigint + blockHash?: Uint8Array +} + +enum DBTarget { + NumberToHash = 4, + TotalDifficulty = 5, + Body = 6, + Header = 7, +} + +export enum DBKey { + Receipts = 0, +} + +type BlockDB = Level + +async function dbGet(DB: BlockDB, dbOperationTarget: DBTarget, key?: DatabaseKey): Promise { + const dbGetOperation = DBOp.get(dbOperationTarget, key) + return DB.get(dbGetOperation.baseDBOp.key, { + keyEncoding: dbGetOperation.baseDBOp.keyEncoding, + valueEncoding: dbGetOperation.baseDBOp.valueEncoding, + }) +} + +export async function numberToHash(DB: BlockDB, number: bigint): Promise { + const hash = await dbGet(DB, DBTarget.NumberToHash, { blockNumber: number }) + return hash +} + +export async function getHeader(DB: BlockDB, hash: Uint8Array, number: bigint) { + const header = await dbGet(DB, DBTarget.Header, { blockHash: hash, blockNumber: number }) + return header as Uint8Array +} + +/** + * Fetches body of a block given its hash and number. + */ +export async function getBody( + DB: BlockDB, + blockHash: Uint8Array, + blockNumber: bigint, +): Promise { + const body = await dbGet(DB, DBTarget.Body, { blockHash, blockNumber }) + return body !== undefined ? (RLP.decode(body) as BlockBodyBytes) : undefined +} + +export async function getTotalDifficulty(DB: BlockDB, hash: Uint8Array, number: bigint) { + const td = await dbGet(DB, DBTarget.TotalDifficulty, { blockHash: hash, blockNumber: number }) + return bytesToBigInt(RLP.decode(td) as Uint8Array) +} + +export async function getBlock(DB: BlockDB, number: bigint) { + const hash = await numberToHash(DB, number) + const header = await getHeader(DB, hash, number) + let body: BlockBodyBytes | undefined | Uint8Array = await getBody(DB, hash, number) + if (body === undefined) { + body = [[], [], []] as BlockBodyBytes + } + body = RLP.encode(body) + return { header, body } +} + +export async function getBlockReceipts(DB: BlockDB, blockHash: Uint8Array): Promise { + const dbKey = concatBytes(intToBytes(DBKey.Receipts), blockHash) + const receipts = await DB.get(dbKey) + return (receipts ?? RLP.encode([])) as Uint8Array +} + +export async function getBlockTuple(chainDB: BlockDB, metaDB: BlockDB, number: bigint) { + const blockHash = await numberToHash(chainDB, number) + const { header, body } = await getBlock(chainDB, number) + const totalDifficulty = await getTotalDifficulty(chainDB, blockHash, number) + const receipts = await getBlockReceipts(metaDB, blockHash) + return { blockHash, header, body, totalDifficulty, receipts } +} + +export async function getBlocks(chainDB: BlockDB, metaDB: BlockDB, start: bigint, number: number) { + const blocks: { + blockHash: Uint8Array + header: Uint8Array + body: Uint8Array + totalDifficulty: bigint + receipts: Uint8Array + }[] = [] + for (let i = 0; i < number; i++) { + const { blockHash, header, body, totalDifficulty, receipts } = await getBlockTuple( + chainDB, + metaDB, + start + BigInt(i), + ) + blocks.push({ blockHash, header, body, totalDifficulty, receipts }) + } + return blocks +} + +async function getEpoch(chainDB: BlockDB, metaDB: BlockDB, index: number) { + const blocks = await getBlocks(chainDB, metaDB, BigInt(index * 8192), 8192) + const headerRecords = blocks.map((block) => { + return { + blockHash: block.blockHash, + totalDifficulty: block.totalDifficulty, + } + }) + const blockTuples = blocks.map((block) => { + return { + header: block.header, + body: block.body, + receipts: block.receipts, + totalDifficulty: block.totalDifficulty, + } + }) + return { blockTuples, headerRecords } +} + +function initDBs(dataDir: string, chain: string) { + const chainDir = `${dataDir}/${chain}` + // Chain DB + const chainDataDir = `${chainDir}/chain` + const chainDB = new Level(chainDataDir) + + // Meta DB (receipts, logs, indexes, skeleton chain) + const metaDataDir = `${chainDir}/meta` + const metaDB = new Level(metaDataDir) + + return { chainDB, metaDB } +} + +export async function exportEpochAsEra1( + epoch: number, + dataDir: string, + outputDir: string = dataDir, + chain = 'mainnet', +) { + const { chainDB, metaDB } = initDBs(dataDir, chain) + await chainDB.open() + await metaDB.open() + const { blockTuples, headerRecords } = await getEpoch(chainDB, metaDB, epoch) + const era1 = await formatEra1(blockTuples, headerRecords, epoch) + + // Create era1 directory if it doesn't exist + const era1Dir = `${outputDir}/era1` + if (!existsSync(era1Dir)) { + mkdirSync(era1Dir, { recursive: true }) + } + + writeFileSync(`${era1Dir}/epoch-${epoch}.era1`, era1) +} diff --git a/packages/era/src/index.ts b/packages/era/src/index.ts new file mode 100644 index 0000000000..439cd9ae4b --- /dev/null +++ b/packages/era/src/index.ts @@ -0,0 +1,12 @@ +import { readFileSync } from 'fs' + +export * from './blockTuple.js' +export * from './e2store.js' +export * from './era1.js' +export * from './exportHistory.js' +export * from './snappy.js' +export * from './types.js' + +export function readBinaryFile(path: string) { + return new Uint8Array(readFileSync(path)) +} diff --git a/packages/era/src/snappy.ts b/packages/era/src/snappy.ts new file mode 100644 index 0000000000..0311c9722c --- /dev/null +++ b/packages/era/src/snappy.ts @@ -0,0 +1,86 @@ +import { SnappyStream, UnsnappyStream } from 'snappystream' +import { Duplex, Writable } from 'stream' + +/** + * Compress data using snappy + * @param uncompressedData + * @returns compressed data + */ +export async function compressData(uncompressedData: Uint8Array): Promise { + return new Promise((resolve, reject) => { + const compressedChunks: Uint8Array[] = [] + const writableStream = new Writable({ + write(chunk: Uint8Array, encoding: string, callback: () => void) { + compressedChunks.push(new Uint8Array(chunk)) + callback() + }, + }) + + const compress = new SnappyStream() + + compress.on('error', reject) + writableStream.on('error', reject) + writableStream.on('finish', () => { + const totalLength = compressedChunks.reduce((sum, chunk) => sum + chunk.length, 0) + const result = new Uint8Array(totalLength) + let offset = 0 + for (const chunk of compressedChunks) { + result.set(chunk, offset) + offset += chunk.length + } + resolve(result) + }) + + compress.pipe(writableStream) + + compress.end(uncompressedData) + }) +} + +export async function decompressData(compressedData: Uint8Array) { + const unsnappy = new UnsnappyStream() + const stream = new Duplex() + const destroy = () => { + unsnappy.destroy() + stream.destroy() + } + stream.on('error', (err) => { + if (err.message.includes('_read() method is not implemented')) { + // ignore errors about unimplemented methods + return + } else { + throw err + } + }) + + stream.push(compressedData) + const data: Uint8Array = await new Promise((resolve, reject) => { + unsnappy.on('data', (data: Uint8Array) => { + try { + destroy() + resolve(data) + // eslint-disable-next-line + } catch {} + }) + unsnappy.on('end', (data: any) => { + try { + destroy() + resolve(data) + } catch (err: any) { + destroy() + reject(`unable to deserialize data with reason - ${err.message}`) + } + }) + unsnappy.on('close', (data: any) => { + try { + destroy() + resolve(data) + } catch (err: any) { + destroy() + reject(`unable to deserialize data with reason - ${err.message}`) + } + }) + stream.pipe(unsnappy) + }) + return data +} diff --git a/packages/era/src/types.ts b/packages/era/src/types.ts new file mode 100644 index 0000000000..a8b9e188bc --- /dev/null +++ b/packages/era/src/types.ts @@ -0,0 +1,28 @@ +import * as ssz from 'micro-eth-signer/ssz' + +export type e2StoreEntry = { + type: Uint8Array + data: Uint8Array +} + +export const Era1Types = { + Version: new Uint8Array([0x65, 0x32]), + CompressedHeader: new Uint8Array([0x03, 0x00]), + CompressedBody: new Uint8Array([0x04, 0x00]), + CompressedReceipts: new Uint8Array([0x05, 0x00]), + TotalDifficulty: new Uint8Array([0x06, 0x00]), + AccumulatorRoot: new Uint8Array([0x07, 0x00]), + BlockIndex: new Uint8Array([0x66, 0x32]), +} as const + +export const VERSION = { + type: Era1Types.Version, + data: new Uint8Array([]), +} + +export const HeaderRecord = ssz.container({ + blockHash: ssz.bytevector(32), + totalDifficulty: ssz.uint256, +}) + +export const EpochAccumulator = ssz.list(8192, HeaderRecord) diff --git a/packages/era/test/era1.spec.ts b/packages/era/test/era1.spec.ts new file mode 100644 index 0000000000..3797d18d53 --- /dev/null +++ b/packages/era/test/era1.spec.ts @@ -0,0 +1,63 @@ +import { createBlockHeaderFromBytesArray } from '@ethereumjs/block' +import { bytesToHex } from '@ethereumjs/util' +import { readFileSync } from 'fs' +import { assert, describe, expect, it } from 'vitest' + +import { + Era1Types, + getBlockIndex, + parseBlockTuple, + readBlockIndex, + readBlockTupleAtOffset, + readOtherEntries, + validateERA1, +} from '../src/index.js' + +// Reference file downloaded from era1.ethportal.net +const filePath = './test/mainnet-00000-5ec1ffb8.era1' +const expectedLength = 3891337 + +function readBinaryFile(filePath: string): Uint8Array { + const buffer = readFileSync(filePath) + return new Uint8Array(buffer) +} + +describe('Read Era1', async () => { + const era1File = readBinaryFile(filePath) + it('should read the file', () => { + expect(era1File.length).toEqual(expectedLength) + }) + const blockIndex = getBlockIndex(era1File) + it('should have block index type', () => { + assert.deepEqual(blockIndex.type, Era1Types.BlockIndex) + }) + it('should have correct count', () => { + expect(blockIndex.count).toEqual(8192) + }) + const { startingNumber, offsets } = readBlockIndex(blockIndex.data, blockIndex.count) + it('should read block index', () => { + expect(startingNumber).toEqual(0) + expect(offsets.length).toEqual(8192) + }) + const { accumulatorRoot, otherEntries } = await readOtherEntries(era1File) + it('should read accumulator root', () => { + assert.equal( + bytesToHex(accumulatorRoot), + '0x5ec1ffb8c3b146f42606c74ced973dc16ec5a107c0345858c343fc94780b4218', + ) + }) + it('should read other entries', () => { + expect(otherEntries.length).toEqual(0) + }) + it('should read first block tuple', async () => { + const tupleEntry = readBlockTupleAtOffset(era1File, blockIndex.recordStart, offsets[0]) + const { header, totalDifficulty } = await parseBlockTuple(tupleEntry) + const blockHeader = createBlockHeaderFromBytesArray(header.data, { setHardfork: true }) + expect(blockHeader.number).toEqual(0n) + expect(totalDifficulty.data).toEqual(blockHeader.difficulty) + }) + const valid = await validateERA1(era1File) + it('should validate era1 file', () => { + expect(valid).toEqual(true) + }) +}) diff --git a/packages/era/test/exportHistory.spec.ts b/packages/era/test/exportHistory.spec.ts new file mode 100644 index 0000000000..c3dcc9cfb3 --- /dev/null +++ b/packages/era/test/exportHistory.spec.ts @@ -0,0 +1,40 @@ +import { bytesToBigInt64, hexToBytes } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { compressData, formatEntry } from '../src/index.js' +describe('era1', async () => { + const test = { + compressed: + '0xff060000734e6150705900cb0000ec6da9a6970410f90214a0007a010088a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794004a4200f043a0d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0567a210004b9014a7800fe0100fe0100fe0100b6010004850401f098808213888080a011bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa82ea0120880000000000000042', + decompressed: + '0xf90214a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000850400000000808213888080a011bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82faa00000000000000000000000000000000000000000000000000000000000000000880000000000000042', + } + const compressedHeader = Uint8Array.from(await compressData(hexToBytes(test.decompressed))) + it('should compress header data', () => { + assert.deepEqual(compressedHeader, hexToBytes(test.compressed)) + }) + + const headerEntry = await formatEntry({ + type: new Uint8Array([0x03, 0x00]), + data: hexToBytes(test.decompressed), + }) + + const typeBytes = headerEntry.slice(0, 2) + it('first two bytes should be entry type', () => { + assert.deepEqual(typeBytes, new Uint8Array([0x03, 0x00])) + }) + + const lengthBytes = Uint8Array.from([...headerEntry.slice(2, 8)]) + + const compressedHeaderLength = bytesToBigInt64(lengthBytes, true) + + it('should have length bytes', () => { + assert.deepEqual(lengthBytes, Uint8Array.from([217, 0, 0, 0, 0, 0])) + }) + it(`length bytes should equal: ${compressedHeader.length}`, () => { + assert.equal(Number(compressedHeaderLength), compressedHeader.length) + }) + it('first 8 bytes should match test', () => { + assert.deepEqual(headerEntry.slice(0, 8), Uint8Array.from([3, 0, 217, 0, 0, 0, 0, 0])) + }) +}) diff --git a/packages/era/test/mainnet-00000-5ec1ffb8.era1 b/packages/era/test/mainnet-00000-5ec1ffb8.era1 new file mode 100644 index 0000000000..dbc1d316cf Binary files /dev/null and b/packages/era/test/mainnet-00000-5ec1ffb8.era1 differ diff --git a/packages/era/tsconfig.json b/packages/era/tsconfig.json new file mode 100644 index 0000000000..ec77a108e2 --- /dev/null +++ b/packages/era/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../config/tsconfig.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/packages/era/tsconfig.lint.json b/packages/era/tsconfig.lint.json new file mode 100644 index 0000000000..3698f4f0be --- /dev/null +++ b/packages/era/tsconfig.lint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../config/tsconfig.lint.json" +} diff --git a/packages/era/tsconfig.prod.cjs.json b/packages/era/tsconfig.prod.cjs.json new file mode 100644 index 0000000000..a9905b02ce --- /dev/null +++ b/packages/era/tsconfig.prod.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/tsconfig.prod.cjs.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist/cjs", + "composite": true + }, + "include": ["src/**/*.ts"], + "references": [{ "path": "../rlp/tsconfig.prod.cjs.json" }] +} diff --git a/packages/era/tsconfig.prod.esm.json b/packages/era/tsconfig.prod.esm.json new file mode 100644 index 0000000000..34bcb04e4a --- /dev/null +++ b/packages/era/tsconfig.prod.esm.json @@ -0,0 +1,14 @@ +{ + "extends": "../../config/tsconfig.prod.esm.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist/esm", + "composite": true + }, + "include": ["src/**/*.ts"], + "references": [ + { "path": "../rlp/tsconfig.prod.esm.json" }, + { "path": "../block/tsconfig.prod.esm.json" }, + { "path": "../blockchain/tsconfig.prod.esm.json" } + ] +} diff --git a/packages/era/typedoc.cjs b/packages/era/typedoc.cjs new file mode 100644 index 0000000000..0c5a50bf95 --- /dev/null +++ b/packages/era/typedoc.cjs @@ -0,0 +1,6 @@ +module.exports = { + extends: '../../config/typedoc.cjs', + entryPoints: ['src'], + out: 'docs', + exclude: ['test/*.ts'], +}