From 2f3c6d4008b6c04cfb71b1b5043dd098953898fb Mon Sep 17 00:00:00 2001 From: Christopher Brumm <97845034+christopherbrumm@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:36:32 +0200 Subject: [PATCH] update: add ethereum-blobs. (#127) * update: add ethereum-blobs. * fix: set contract addresses to lower case. * fix: merkle root; include sequencer from and to txs. * fix: apply comments. --- integrations/ethereum-blobs/.gitignore | 190 ++++++++++++++++++ integrations/ethereum-blobs/CHANGELOG.md | 4 + integrations/ethereum-blobs/LICENSE | 201 ++++++++++++++++++++ integrations/ethereum-blobs/README.md | 118 ++++++++++++ integrations/ethereum-blobs/package.json | 35 ++++ integrations/ethereum-blobs/src/index.ts | 6 + integrations/ethereum-blobs/src/runtime.ts | 184 ++++++++++++++++++ integrations/ethereum-blobs/tsconfig.json | 11 ++ integrations/ethereum-blobs/utils/merkle.ts | 38 ++++ integrations/ethereum-blobs/utils/utils.ts | 38 ++++ 10 files changed, 825 insertions(+) create mode 100644 integrations/ethereum-blobs/.gitignore create mode 100644 integrations/ethereum-blobs/CHANGELOG.md create mode 100644 integrations/ethereum-blobs/LICENSE create mode 100644 integrations/ethereum-blobs/README.md create mode 100644 integrations/ethereum-blobs/package.json create mode 100644 integrations/ethereum-blobs/src/index.ts create mode 100644 integrations/ethereum-blobs/src/runtime.ts create mode 100644 integrations/ethereum-blobs/tsconfig.json create mode 100644 integrations/ethereum-blobs/utils/merkle.ts create mode 100644 integrations/ethereum-blobs/utils/utils.ts diff --git a/integrations/ethereum-blobs/.gitignore b/integrations/ethereum-blobs/.gitignore new file mode 100644 index 00000000..7210993e --- /dev/null +++ b/integrations/ethereum-blobs/.gitignore @@ -0,0 +1,190 @@ +# Created by https://www.toptal.com/developers/gitignore/api/node + +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* +.DS_Store/ + +### Node Patch + +# Serverless Webpack directories + +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output + +.svelte-kit +.idea +/common/types/client/ +/common/types/lcd/ + +# End of https://www.toptal.com/developers/gitignore/api/node \ No newline at end of file diff --git a/integrations/ethereum-blobs/CHANGELOG.md b/integrations/ethereum-blobs/CHANGELOG.md new file mode 100644 index 00000000..e9fb6ecf --- /dev/null +++ b/integrations/ethereum-blobs/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. \ No newline at end of file diff --git a/integrations/ethereum-blobs/LICENSE b/integrations/ethereum-blobs/LICENSE new file mode 100644 index 00000000..bf519b1c --- /dev/null +++ b/integrations/ethereum-blobs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2022 BCP Innovations UG (haftungsbeschränkt) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/integrations/ethereum-blobs/README.md b/integrations/ethereum-blobs/README.md new file mode 100644 index 00000000..2215138d --- /dev/null +++ b/integrations/ethereum-blobs/README.md @@ -0,0 +1,118 @@ +# @kyvejs/ethereum-blobs + +## Content + +- [Introduction](#introduction) +- [Use-cases](#use-cases) +- [Binary Installation](#binary-installation) + - [Build from source](#build-from-source) + - [Download prebuilt binary](#download-prebuilt-binary) + +## Introduction + +This runtime validates and archives blobs from the Ethereum beacon chain. It stores blobs that have been sent to specified contract/sequencer addresses +from and to a given height and makes them available to directly download them from the storage provider. This data can be used to spin up a trustless RPC +mocking the `/eth/v1/beacon/blob_sidecars/` endpoint. + +## Use cases + +As EIP-4844 enhances Ethereum's scalability and performance, it simultaneously burdens L2s with the responsibility of managing their own data blobs. +Without proper infrastructure, this requirement risks scalability and increases reliance on centralized storage solutions. + +KYVE offers a vital decentralized alternative, enabling L2s to preserve their historical data securely and permanently on Arweave*. +This ensures not only the integrity and availability of data as a public good but also supports the scalability and sustainability of the Ethereum +ecosystem as it evolves. [Vitalik Buterin has highlighted the necessity for decentralized solutions to support the ecosystem through this transition](https://notes.ethereum.org/@vbuterin/proto_danksharding_faq#If-data-is-deleted-after-30-days-how-would-users-access-older-blobs), +and KYVE is here to support that. + +## Architecture + +This section explains how the blobs of specified sequencer addresses are obtained in order to validate them properly. First, the pool +config provides a good overview about the requirements: + +``` +{ + "consensusRPC": ; + "executionRPC": ; + "finality": ; + "genesisTime": ; + "sequencer": ; +} +``` + +In addition to the configuration, the steps of the `getDataItem` method of the runtime also provides a clear understanding of the implementation: + +1. Get latest height of the execution client, wait if finality isn't reached yet. +2. Query the block and all transactions for the given height. +3. Filter all `type 3` txs that has been sent to a specified sequencer in the pool config. +4. For each filtered tx, execute `eth_getTxByHash` to get the commitment. +5. Calculate the slot number for the given height -> `(block_time - genesis_time) / 12` +6. Execute `consensusRPC/eth/v1/beacon/blob_sidecars/${slotNumber}` to get all blobs for the given height. +7. For each blob, take the KZG commitment and check if it matches with a first versioned hash of type3TxToSequencer. This means that the blob was actually sent to the sequencer address. If it matches, include the blob in data item, if not, skip blob. +8. Check if the length of the selected blob is equal to the length of all filtered txs from step 3 to verify that all required blobs are included. + +## Required Setup + +This runtime requires the node operator to run an Ethereum node which is used as the source and the KYVE protocol node. The Ethereum node +itself consists of the consensus layer (Lighthouse) and the execution layer (Geth). The minimum hardware requirements are at least the min requirements +of that Ethereum node. + +## Binary Installation + +This section explains how to install a protocol node with this runtime. This is only relevant for protocol node +operators who want to run a node in a pool which has this runtime. + +### Build from source + +The first option to install the binary is to build it from source. For that you have to execute the following +commands: + +```bash +git clone git@github.com:KYVENetwork/kyvejs.git +cd kyvejs +``` + +If you want to build a specific version you can checkout the tag and continue from the version branch. +If you want to build the latest version you can skip this step. + +```bash +git checkout tags/@kyvejs/ethereum-blobs@x.x.x -b x.x.x +``` + +After you have cloned the project and have the desired version the dependencies can be installed and the project build: + +```bash +yarn install +yarn setup +``` + +Finally, you can build the runtime binaries. + +**INFO**: During the binary build log warnings can occur. You can safely ignore them. + +```bash +cd integrations/ethereum-blobs +yarn build:binaries +``` + +You can verify the installation with printing the version: + +```bash +./out/kyve-linux-64 version +``` + +After the build succeeded you can find the binaries in the `out` folder where you can move them to use +desired location (like KYSOR). + +### Download prebuilt binary + +You can find all prebuilt binaries in the releases of the kyvejs repository. For this specific runtime they +can be found [here](https://github.com/KYVENetwork/kyvejs/releases?q=ethereum-blobs). + +You can verify the installation with printing the version: + +```bash +./kyve-linux-64 version +``` + +Once you have downloaded the binary for the correct platform and version you can simply unzip them and move them +to your desired location (like KYSOR). \ No newline at end of file diff --git a/integrations/ethereum-blobs/package.json b/integrations/ethereum-blobs/package.json new file mode 100644 index 00000000..2f84f52a --- /dev/null +++ b/integrations/ethereum-blobs/package.json @@ -0,0 +1,35 @@ +{ + "name": "@kyvejs/ethereum-blobs", + "version": "1.0.0-beta.0", + "license": "MIT", + "scripts": { + "build": "rimraf dist && tsc", + "build:binaries": "yarn build && rimraf out && pkg --no-bytecode --public-packages '*' --compress GZip --output out/kyve package.json && node ../../common/protocol/dist/src/scripts/checksum.js", + "start": "node ./dist/src/index.js", + "format": "prettier --write ." + }, + "bin": "./dist/src/index.js", + "pkg": { + "scripts": "./dist/src/index.js", + "targets": [ + "latest-linux-x64", + "latest-linux-arm64", + "latest-macos-x64" + ], + "outputPath": "out" + }, + "prettier": { + "singleQuote": true + }, + "dependencies": { + "@kyvejs/protocol": "1.1.6", + "axios": "^0.27.2", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "pkg": "^5.8.0", + "prettier": "^2.7.1", + "rimraf": "^3.0.2", + "typescript": "^4.7.4" + } +} \ No newline at end of file diff --git a/integrations/ethereum-blobs/src/index.ts b/integrations/ethereum-blobs/src/index.ts new file mode 100644 index 00000000..47f55e3d --- /dev/null +++ b/integrations/ethereum-blobs/src/index.ts @@ -0,0 +1,6 @@ +import { Validator } from '@kyvejs/protocol'; +import EthereumBlobs from "./runtime"; + +const runtime = new EthereumBlobs(); + +new Validator(runtime).bootstrap(); \ No newline at end of file diff --git a/integrations/ethereum-blobs/src/runtime.ts b/integrations/ethereum-blobs/src/runtime.ts new file mode 100644 index 00000000..4396d5a4 --- /dev/null +++ b/integrations/ethereum-blobs/src/runtime.ts @@ -0,0 +1,184 @@ +import { DataItem, IRuntime, Validator, VOTE } from "@kyvejs/protocol"; +import { name, version } from "../package.json"; +import axios from "axios"; +import { createVersionedHash, getTransactionByHash } from "../utils/utils"; +import { providers } from "ethers"; +import { hexValue } from "ethers/lib/utils"; +import { createHashesFromBundle, generateMerkleRoot } from "../utils/merkle"; + +// Ethereum Blobs config +interface IConfig { + consensusRPC: string; + executionRPC: string; + finality: number; + genesisTime: number; + sequencer: string[]; +} + +export default class EthereumBlobs implements IRuntime { + public name = name; + public version = version; + public config!: IConfig; + + async validateSetConfig(rawConfig: string): Promise { + const config: IConfig = JSON.parse(rawConfig); + + if (!config.finality) { + throw new Error(`Config does not have finality defined`); + } + + if (!config.consensusRPC) { + throw new Error(`Config does not have property "consensusRPC" defined`); + } + + if (!config.executionRPC) { + throw new Error(`Config does not have property "executionRPC" defined`); + } + + if (!config.genesisTime) { + throw new Error(`Config does not have property "genesisTime" defined`); + } + + if (!config.sequencer.length) { + throw new Error(`Config does not have property "sequencer" defined`); + } + + if (process.env.KYVEJS_ETHEREUM_BLOBS_EXECUTION_RPC) { + config.executionRPC = process.env.KYVEJS_ETHEREUM_BLOBS_EXECUTION_RPC; + + console.log("set config executionRPC to", config.executionRPC) + } + + if (process.env.KYVEJS_ETHEREUM_BLOBS_CONSENSUS_RPC) { + config.consensusRPC = process.env.KYVEJS_ETHEREUM_BLOBS_CONSENSUS_RPC; + + console.log("set config consensusRPC to", config.consensusRPC) + } + + this.config = config; + this.config.sequencer = config.sequencer.map(s => s.toLowerCase()) + } + + async getDataItem(_: Validator, key: string): Promise { + const provider = new providers.StaticJsonRpcProvider(this.config.executionRPC); + + const currentHeight = await provider.getBlockNumber(); + + // only validate if current height is already 'finalized' + if (parseInt(key) >= currentHeight - 256) { + throw new Error( + `Finality not reached yet; waiting for next block` + ) + } + + const hexKey = hexValue(+key); + + const block = await provider.getBlockWithTransactions(hexKey); + + // Get all type3 transactions that has been sent to the sequencer inbox + const filteredTransactions = block.transactions.filter( + (tx) => { + if (tx.type == 3) { + if (tx.to && tx.from) { + return this.config.sequencer.includes(tx.to.toLowerCase()) || this.config.sequencer.includes(tx.from.toLowerCase()) + } else if (tx.to && !tx.from) { + return this.config.sequencer.includes(tx.to.toLowerCase()) + } else if (!tx.to && tx.from) { + return this.config.sequencer.includes(tx.from.toLowerCase()) + } + } + return false + } + ); + + let type3TxsToSequencer: string[] = []; + for (const tx of filteredTransactions) { + const txDetail = await getTransactionByHash(this.config.executionRPC, tx.hash); + txDetail["blobVersionedHashes"].forEach((bHash: any) => type3TxsToSequencer.push(bHash)) + } + + let blobs: any[] = []; + + // Calculate corresponding slot number + const slotNumber = (block.timestamp - this.config.genesisTime) / 12 + + await axios.get(`${this.config.consensusRPC}/eth/v1/beacon/blob_sidecars/${slotNumber}`, { + headers: { + 'accept': 'application/json' + } + }).then((res) => { + blobs = res.data.data; + }).catch(err => { + throw new Error( + `Failed to query '/eth/v1/beacon/blob_sidecars/${slotNumber}': ${err}` + ); + }); + + let includedBlobs: any[] = []; + // For each blob, take KZG commitment and check if it matches with a first versioned hash of type3TxToSequencer. + // This means that the blob was actually sent to the sequencer address. + // If it matches, include the blob in data item, if not, skip blob. + blobs.forEach((b: any) => { + const commitment= createVersionedHash(b["kzg_commitment"]); + + if (type3TxsToSequencer.includes(commitment)) { + includedBlobs.push(b) + } + }); + + if (includedBlobs.length != type3TxsToSequencer.length) { + throw new Error( + `Length of included blobs and txs to sequencer is not equal` + ) + } + + return { + key, + value: { + slot: slotNumber, + blobs: includedBlobs + }, + }; + } + + + async prevalidateDataItem(_: Validator, item: DataItem): Promise { + // check if item value is not null + return item.value && item.value.slot + } + + async transformDataItem(_: Validator, item: DataItem): Promise { + // do not transform data item + return item; + } + + async validateDataItem( + _: Validator, + proposedDataItem: DataItem, + validationDataItem: DataItem + ): Promise { + // apply equal comparison + if ( + JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem) + ) { + return VOTE.VOTE_TYPE_VALID; + } + return VOTE.VOTE_TYPE_INVALID; + } + + async summarizeDataBundle(_: Validator, bundle: DataItem[]): Promise { + const hashes = createHashesFromBundle(bundle); + + const merkleRoot = generateMerkleRoot(hashes); + + return JSON.stringify({ + "from_slot": bundle.at(0)?.value.slot, + "to_slot": bundle.at(-1)?.value.slot, + "merkle_root": merkleRoot + }) + } + + async nextKey(_: Validator, key: string): Promise { + return (parseInt(key) + 1).toString(); + } +} \ No newline at end of file diff --git a/integrations/ethereum-blobs/tsconfig.json b/integrations/ethereum-blobs/tsconfig.json new file mode 100644 index 00000000..d2db7767 --- /dev/null +++ b/integrations/ethereum-blobs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2019", + "strict": true, + "outDir": "dist", + "esModuleInterop": true, + "resolveJsonModule": true + }, + "files": ["src/index.ts"] +} \ No newline at end of file diff --git a/integrations/ethereum-blobs/utils/merkle.ts b/integrations/ethereum-blobs/utils/merkle.ts new file mode 100644 index 00000000..de500199 --- /dev/null +++ b/integrations/ethereum-blobs/utils/merkle.ts @@ -0,0 +1,38 @@ +import * as crypto from '@cosmjs/crypto'; + +export function createHashesFromBundle(bundle: any[]): Uint8Array[] { + return bundle.map(dataItem => dataItemToSha256(dataItem)) +} + +function dataItemToSha256(data: any): Uint8Array { + // Encode the serialized object to UTF-8 + const encoded_obj: Uint8Array = Buffer.from(JSON.stringify(data), 'utf-8'); + // Calculate the SHA-256 hash + return crypto.sha256(encoded_obj) +} + +export function generateMerkleRoot(hashes: Uint8Array[]): string { + if (!hashes || hashes.length == 0) { + return ''; + } + + // Ensure number of hashes (leafs) are even by copying the + // last hash (the very right leaf) if the amount is odd + if (hashes.length % 2 !== 0) { + hashes.push(hashes[hashes.length - 1]); + } + + const combinedHashes: Uint8Array[] = []; + for(let i = 0; i < hashes.length; i += 2) { + const hashesConcatenated = new Uint8Array([ ...hashes[i], ...hashes[i+1]]) + const hash = crypto.sha256(hashesConcatenated); + combinedHashes.push(hash); + } + + // If the combinedHashes length is 1, it means that we have the merkle root already, + // and we can return the hex representation + if (combinedHashes.length === 1) { + return Buffer.from(combinedHashes[0]).toString('hex'); + } + return generateMerkleRoot(combinedHashes); +} \ No newline at end of file diff --git a/integrations/ethereum-blobs/utils/utils.ts b/integrations/ethereum-blobs/utils/utils.ts new file mode 100644 index 00000000..744233f9 --- /dev/null +++ b/integrations/ethereum-blobs/utils/utils.ts @@ -0,0 +1,38 @@ +import crypto from "crypto"; +import axios from "axios"; + +export async function getTransactionByHash(rpc: string, hash: string) { + const data = { + method: 'eth_getTransactionByHash', + params: [hash], + id: 1, + jsonrpc: '2.0' + }; + + try { + const response = await axios.post(rpc, data, { + headers: { + 'Content-Type': 'application/json' + } + }); + return response.data.result; + } catch (error) { + console.error('Error:', error); + } +} + +// Creates the versioned hash for a kzg_commitment to enable +// the comparison with a tx_hash. +export function createVersionedHash(hex: string): string { + if (hex.startsWith('0x')) { + hex = hex.slice(2); + } + + const inputBuffer = Buffer.from(hex, 'hex'); + + const hash = crypto.createHash('sha256').update(inputBuffer).digest('hex'); + + const formattedHash = '01' + hash.substring(2); + + return "0x" + formattedHash; +} \ No newline at end of file