Skip to content

Commit

Permalink
Add arbi validator (#19)
Browse files Browse the repository at this point in the history
* add playgorund for testing

* mvp arbi proof service

* add arb-verifier package

* add arb-gateway

* clean up workspace

* fix workspace

* stash outbox

* get rid of logging

* merkle tree wip

* checkpoint statevaldation

* checkpoint finish contract + gateway

* test for latest

* finish test for latest

* add test for name

* more tests

* comments

* add comments

* remove OpVerifier from arb-verifier dir

* add global run script

* update env in server

* renmae deploy script

* replace outbox with rollup

* clean up code

* test against nitro test node

* add comments

* remove old v5 dep

* remove logs

* change package order in global package.json

* add instructions to run the tests for arb verifier

* remove old hackathon test-script

* remove outdated comment

* remove helperAbi since its no longer needed

* revert liniting in other packages

* lint arb packages

* add comment reagarding the stateRoot

* use correct chainId for ArbitrumGoerli

* update README

* remove op contracts from arb-verifier package.json

* Add goerli deployment info

* Change worker name

* Add comma

* Change to single quote

* single quote

* single quote

* comma

* comma

* Update readme

* Support cors

* Add preflight

* Revert "Add preflight"

This reverts commit b43c139.

* Revert "Support cors"

This reverts commit b49dc6f.

* Update ccip-read-cf-worker to 0.0.2

* Update ccip-read-cf-worker to v 0.0.3

* Update README.md

* remove blank space

* move rollup address to env

* commander extra typings is a dev dependency now

* remove blockhash from calldata

* fix typo in ArbVerfifer

* add in memory cache

* rename cache to blockCache

* change method name to getL2BlockForNode

* adjust console.log statements from InMemoryBlockCache

---------

Co-authored-by: Makoto Inoue <2630+makoto@users.noreply.github.com>
  • Loading branch information
AlexNi245 and makoto committed Dec 10, 2023
1 parent e6147d9 commit 0c15039
Show file tree
Hide file tree
Showing 33 changed files with 4,850 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This repository implements a generic CCIP-Read gateway framework for fetching st
While this functionality is written primarily with read calls in mind, it also functions for transactions; using a compliant
library like Ethers, a transaction that includes relevant L2 proofs can be generated and signed.



## Usage

1. Have your contract extend `EVMFetcher`.
Expand Down
21 changes: 21 additions & 0 deletions arb-gateway/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Nick Johnson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
44 changes: 44 additions & 0 deletions arb-gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# @ensdomains/arb-gateway

An instantiation of [evm-gateway](https://github.com/ensdomains/evmgateway/tree/main/evm-gateway) that targets Arbitrum - that is, it implements a CCIP-Read gateway that generates proofs of contract state on Arbitrum.

For a detailed readme and usage instructions, see the [monorepo readme](https://github.com/ensdomains/evmgateway/tree/main).

To get started, you need to have an RPC URL for both Ethereum Mainnet and Arbitrum. You also need to provide an L2_ROLLUP address which is the Rollup contract deployed on Mainnet or the Nitro Node.

## How to use arb-gateway locally via cloudflare dev env (aka wrangler)

```
npm install -g bun
cd arb-gateway
bun install
touch .dev.vars
## set L1_PROVIDER_URL, L2_PROVIDER_URL, L2_ROLLUP
yarn dev
```

## How to deploy arb-gateway to cloudflare

```
cd arb-gateway
npm install -g wrangler
wrngler login
wrangler secret put L1_PROVIDER_URL
wrangler secret put L2_PROVIDER_URL
wrangler secret put L2_ROLLUP
yarn deploy
```

## How to test

1. Start the Nitro Test node. You can find instructions here: https://docs.arbitrum.io/node-running/how-tos/local-dev-node
2. Retrieve the Rollup address from the Node's Logs.
3. Copy the example.env file in both arb-gateway and arb-verifier, and add the Rollup address.
4. Build the Project.
5. Navigate to the Gateway directory using `cd ./arb-gateway`.
6. Start the Gateway by running `bun run start -u http://127.0.0.1:8545/ -v http://127.0.0.1:8547/ -p 8089`.
7. Open another Terminal Tab and navigate to the verifier directory using `cd ./arb-verifier/`.
8. Deploy contracts to the node using the command ` npx hardhat --network arbDevnetL2 deploy && npx hardhat --network arbDevnetL1 deploy `.
9. Run the test using the command `bun run test`.

Empty file added arb-gateway/example.env
Empty file.
70 changes: 70 additions & 0 deletions arb-gateway/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "@ensdomains/arb-gateway",
"version": "0.1.0",
"author": "Nick Johnson",
"license": "MIT",
"type": "module",
"main": "./_cjs/index.js",
"module": "./_esm/index.js",
"types": "./_types/index.d.ts",
"typings": "./_types/index.d.ts",
"bin": "./_cjs/server.js",
"sideEffects": false,
"files": [
"_esm",
"_cjs",
"_types",
"src",
"!**/*.tsbuildinfo"
],
"exports": {
".": {
"types": "./_types/index.d.ts",
"import": "./_esm/index.js",
"require": "./_cjs/index.js"
},
"./package.json": "./package.json"
},
"engines": {
"node": ">=10",
"bun": ">=1.0.4"
},
"peerDependencies": {
"typescript": ">=5.0.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"scripts": {
"start": "bun ./src/server.ts",
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./_cjs --removeComments --verbatimModuleSyntax false && echo > ./_cjs/package.json '{\"type\":\"commonjs\"}'",
"build:esm": "tsc --project tsconfig.build.json --module es2022 --outDir ./_esm && echo > ./_esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'",
"build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./_types --emitDeclarationOnly --declaration --declarationMap",
"build": "echo 'building arb-gateway...' && bun run clean && bun run build:cjs && bun run build:esm && bun run build:types",
"prepublishOnly": "bun ../scripts/prepublishOnly.ts",
"lint": "eslint . --ext .ts",
"prepare": "bun run build",
"clean": "rm -fr _cjs _esm _types"
},
"husky": {
"hooks": {
"pre-commit": "bun run lint"
}
},
"dependencies": {
"@chainlink/ccip-read-server": "^0.2.1",
"@ensdomains/evm-gateway": "^0.1.0",
"@ethereumjs/block": "^5.0.0",
"@nomicfoundation/ethereumjs-block": "^5.0.2",
"commander": "^11.0.0",
"ethers": "^6.7.1"
},
"devDependencies": {
"@commander-js/extra-typings": "^11.0.0"

}
}
160 changes: 160 additions & 0 deletions arb-gateway/src/ArbProofService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/* eslint-disable prettier/prettier */
import { EVMProofHelper, type IProofService } from '@ensdomains/evm-gateway';
import { AbiCoder, Contract, EventLog, ethers, toBeHex, type AddressLike, toNumber } from 'ethers';
import rollupAbi from "./abi/rollupABI.js";
import type { IBlockCache } from './blockCache/IBlockCache.js';
export interface ArbProvableBlock {
number: number
sendRoot: string,
nodeIndex: string,
rlpEncodedBlock: string
}


/**
* The proofService class can be used to calculate proofs for a given target and slot on the Arbitrum network.
* It's also capable of proofing long types such as mappings or string by using all included slots in the proof.
*
*/
export class ArbProofService implements IProofService<ArbProvableBlock> {
private readonly l2Provider: ethers.JsonRpcProvider;
private readonly rollup: Contract;
private readonly helper: EVMProofHelper;
private readonly cache: IBlockCache;


constructor(
l1Provider: ethers.JsonRpcProvider,
l2Provider: ethers.JsonRpcProvider,
l2RollupAddress: string,
cache: IBlockCache

) {
this.l2Provider = l2Provider;
this.rollup = new Contract(
l2RollupAddress,
rollupAbi,
l1Provider
);
this.helper = new EVMProofHelper(l2Provider);
this.cache = cache
}

async getStorageAt(block: ArbProvableBlock, address: AddressLike, slot: bigint): Promise<string> {
return this.helper.getStorageAt(block.number, address, slot);
}


/**
* @dev Fetches a set of proofs for the requested state slots.
* @param block A `ProvableBlock` returned by `getProvableBlock`.
* @param address The address of the contract to fetch data from.
* @param slots An array of slots to fetch data for.
* @returns A proof of the given slots, encoded in a manner that this service's
* corresponding decoding library will understand.
*/
async getProofs(
block: ArbProvableBlock,
address: AddressLike,
slots: bigint[]
): Promise<string> {
const proof = await this.helper.getProofs(block.number, address, slots);

return AbiCoder.defaultAbiCoder().encode(
[
'tuple(bytes32 version, bytes32 sendRoot, uint64 nodeIndex,bytes rlpEncodedBlock)',
'tuple(bytes[] stateTrieWitness, bytes[][] storageProofs)',
],
[
{
version:
'0x0000000000000000000000000000000000000000000000000000000000000000',
sendRoot: block.sendRoot,
nodeIndex: block.nodeIndex,
rlpEncodedBlock: block.rlpEncodedBlock
},
proof,
]
);
}
/**
* Retrieves information about the latest provable block in the Arbitrum Rollup.
*
* @returns { Promise<ArbProvableBlock> } A promise that resolves to an object containing information about the provable block.
* @throws Throws an error if any of the underlying operations fail.
*
* @typedef { Object } ArbProvableBlock
* @property { string } rlpEncodedBlock - The RLP - encoded block information.
* @property { string } sendRoot - The send root of the provable block.
* @property { string } blockHash - The hash of the provable block.
* @property { number } nodeIndex - The index of the node corresponding to the provable block.
* @property { number } number - The block number of the provable block.
*/
public async getProvableBlock(): Promise<ArbProvableBlock> {
//Retrieve the latest pending node that has been committed to the rollup.
const nodeIndex = await this.rollup.latestNodeCreated()
const [l2blockRaw, sendRoot] = await this.getL2BlockForNode(nodeIndex)

const blockarray = [
l2blockRaw.parentHash,
l2blockRaw.sha3Uncles,
l2blockRaw.miner,
l2blockRaw.stateRoot,
l2blockRaw.transactionsRoot,
l2blockRaw.receiptsRoot,
l2blockRaw.logsBloom,
toBeHex(l2blockRaw.difficulty),
toBeHex(l2blockRaw.number),
toBeHex(l2blockRaw.gasLimit),
toBeHex(l2blockRaw.gasUsed),
toBeHex(l2blockRaw.timestamp),
l2blockRaw.extraData,
l2blockRaw.mixHash,
l2blockRaw.nonce,
toBeHex(l2blockRaw.baseFeePerGas)
]

//Rlp encode the block to pass it as an argument
const rlpEncodedBlock = ethers.encodeRlp(blockarray)

return {
rlpEncodedBlock,
sendRoot,
nodeIndex: nodeIndex,
number: toNumber(l2blockRaw.number)
}
}
/**
* Fetches the corrospending L2 block for a given node index and returns it along with the send root.
* @param {bigint} nodeIndex - The index of the node for which to fetch the block.
* @returns {Promise<[Record<string, string>, string]>} A promise that resolves to a tuple containing the fetched block and the send root.
*/
private async getL2BlockForNode(nodeIndex: bigint): Promise<[Record<string, string>, string]> {

//We first check if we have the block cached
const cachedBlock = await this.cache.getBlock(nodeIndex)
if (cachedBlock) {
return [cachedBlock.block, cachedBlock.sendRoot]
}

//We fetch the node created event for the node index we just retrieved.
const nodeEventFilter = await this.rollup.filters.NodeCreated(nodeIndex);
const nodeEvents = await this.rollup.queryFilter(nodeEventFilter);
const assertion = (nodeEvents[0] as EventLog).args!.assertion
//Instead of using the AssertionHelper contract we can extract sendRoot from the assertion. Avoiding the deployment of the AssertionHelper contract and an additional RPC call.
const [blockHash, sendRoot] = assertion[1][0][0]


//The L1 rollup only provides us with the block hash. In order to ensure that the stateRoot we're using for the proof is indeed part of the block, we need to fetch the block. And provide it to the proof.
const l2blockRaw = await this.l2Provider.send('eth_getBlockByHash', [
blockHash,
false
]);

//Cache the block for future use
await this.cache.setBlock(nodeIndex, l2blockRaw, sendRoot)

return [l2blockRaw, sendRoot]
}

}

0 comments on commit 0c15039

Please sign in to comment.