Skip to content

Commit

Permalink
feat(global): adds first implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
allemanfredi committed Aug 20, 2024
1 parent 172ad41 commit 0708099
Show file tree
Hide file tree
Showing 14 changed files with 1,804 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
PORT=
SOURCE_RPC=""
TARGET_RPC=""
SOURCE_CHAIN_ID=""
TARGET_CHAIN_ID=""
LC_ADDRESS=""
SOURCE_BEACON_API_URL=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
.env
.DS_Store
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"bracketSpacing": true,
"printWidth": 120,
"trailingComma": "none"
}
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
# dendreth-proofs-api
# Dendreth Proofs API

Dendreth Proofs API is a Node.js-based API that provides functionality for generating Dendreth proofs to use with Hashi

## Prerequisites

Before running the application, ensure you have the following installed:

- [Node.js](https://nodejs.org/) (version 20.x or higher recommended)
- [Yarn](https://yarnpkg.com/) (version 1.x or higher)

## Installation

Clone the repository and navigate to the project directory:

```bash
git clone https://github.com/crosschain-alliance/dendreth-proofs-api
cd dendreth-proofs-api
```

Install the dependencies using Yarn:

```bash
yarn install
```

## Running the Application

### Development Mode

To start the application in development mode with hot-reloading, use the following command:

```bash
yarn start:dev
```

This command typically runs the server using a development configuration, where any code changes automatically restart the server.

### Production Mode

To start the application in production mode, use:

```bash
yarn start
```

This command runs the server with the production configuration, optimized for performance.

## Contributing

Feel free to submit issues and pull requests. For major changes, please open an issue first to discuss what you would like to change.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
4 changes: 4 additions & 0 deletions nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"watch": ["src"],
"exec": "node ./src/server.js | npx pino-pretty"
}
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "dendreth-proofs-api",
"version": "1.0.0",
"description": "",
"type": "module",
"scripts": {
"start": "node ./src/server.js | npx pino-pretty",
"format": "prettier --config ./.prettierrc --write \"./**/*.+(js|ts|json)\"",
"start:dev": "nodemon"
},
"author": "",
"license": "MIT",
"dependencies": {
"@chainsafe/persistent-merkle-tree": "^0.8.0",
"@ethereumjs/rlp": "^5.0.2",
"@ethereumjs/trie": "^6.2.1",
"@ethereumjs/tx": "^5.4.0",
"@lodestar/types": "^1.21.0",
"axios": "^1.6.8",
"dotenv": "^16.4.5",
"fastify": "^4.26.2",
"node-fetch": "^3.3.2",
"pino": "^8.19.0",
"pino-pretty": "^11.0.0",
"viem": "^2.9.9"
},
"devDependencies": {
"nodemon": "^3.1.0",
"prettier": "^3.2.5"
}
}
127 changes: 127 additions & 0 deletions src/routes/v1/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'dotenv/config'
import { createPublicClient, http } from 'viem'
import * as chains from 'viem/chains'
import axios from 'axios'
import { RLP } from '@ethereumjs/rlp'

import logger from '../../utils/logger.js'
import dendrethAbi from '../../utils/abi/dendreth.js'
import { getReceiptProof, getReceiptsRootProof } from '../../utils/proofs.js'
import sleep from '../../utils/sleep.js'

const MESSAGE_DISPATCHED_TOPIC = '0x218247aabc759e65b5bb92ccc074f9d62cd187259f2a0984c3c9cf91f67ff7cf'

const getMessageDispatchedProof = async (_request, _reply) => {
const { transactionHash } = _request.params

const sourceChain = Object.values(chains).find((_chain) => _chain.id === parseInt(process.env.SOURCE_CHAIN_ID))
const targetChain = Object.values(chains).find((_chain) => _chain.id === parseInt(process.env.TARGET_CHAIN_ID))

const sourceClient = createPublicClient({
chain: sourceChain,
transport: http(process.env.SOURCE_RPC)
})

const targetClient = createPublicClient({
chain: targetChain,
transport: http(process.env.TARGET_RPC)
})

const receipt = await sourceClient.getTransactionReceipt({
hash: transactionHash
})

logger.info('Checking finality ...')
let txSlot = null
const {
data: { data }
} = await axios.get(`https://sepolia.beaconcha.in/api/v1/execution/block/${receipt.blockNumber}`)
const [
{
posConsensus: { slot, finalized }
}
] = data
if (!finalized) {
return _reply.code(400).send({ error: 'Block not finalized' })
}
txSlot = slot

logger.info('Calculating receipt proof ...')
const { receiptProof, receiptsRoot } = await getReceiptProof(transactionHash, sourceClient)

logger.info('Getting the correct light client slot ...')
// NOTE: find the first slot > txSlot
const initialIndex = await targetClient.readContract({
address: process.env.LC_ADDRESS,
abi: dendrethAbi,
functionName: 'currentIndex'
})
let currentIndex = initialIndex
let inverted = false
let lastLightClientSlot = null
try {
while (true) {
const lightClientSlot = await targetClient.readContract({
address: process.env.LC_ADDRESS,
abi: dendrethAbi,
functionName: 'optimisticSlots',
args: [currentIndex]
})

if (txSlot > lightClientSlot) {
break
}
lastLightClientSlot = lightClientSlot

if (currentIndex === 0n) {
inverted = true
currentIndex = initialIndex
}
currentIndex = inverted ? (currentIndex += 1n) : currentIndex - 1n
await sleep(1000)
}
} catch (_err) {
return _reply.code(404).send({
error:
'The slot containing the block where the transaction emitted the MessageDispatched event has not been stored within the Light Client'
})
}

logger.info('Getting receipts root proof ...')
const { receiptsRootProof } = await getReceiptsRootProof(
Number(lastLightClientSlot),
Number(txSlot),
axios.create({
baseURL: process.env.SOURCE_BEACON_API_URL,
responseType: 'json',
headers: { 'Content-Type': 'application/json' }
})
)

logger.info('Getting log index ...')
const logIndex = receipt.logs.findIndex(({ topics }) => topics[0] === MESSAGE_DISPATCHED_TOPIC)
if (logIndex < 0) {
return _reply.code(404).send({ error: 'Log not found' })
}

const proof = [
parseInt(lastLightClientSlot),
parseInt(txSlot),
receiptsRootProof,
receiptsRoot,
receiptProof,
'0x' + Buffer.from(RLP.encode(receipt.transactionIndex)).toString('hex'),
logIndex
]

_reply.send({
proof
})
}

const handler = (_fastify, _opts, _done) => {
_fastify.get('/get-message-dispatched-proof/:transactionHash', getMessageDispatchedProof)
_done()
}

export default handler
46 changes: 46 additions & 0 deletions src/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import dotenv from 'dotenv'
dotenv.config()
import Fastify from 'fastify'

import routes from './routes/v1/index.js'

const fastify = Fastify({
logger: true,
requestTimeout: 30000,
exposeHeadRoutes: true
})

const port = process.env.PORT || 3002

fastify.route({
method: 'OPTIONS',
url: '/*',
handler: async (request, reply) => {
var reqAllowedHeaders = request.headers['access-control-request-headers']
if (reqAllowedHeaders !== undefined) {
reply.header('Access-Control-Allow-Headers', reqAllowedHeaders)
}
reply
.code(204)
.header('Content-Length', '0')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Allow-Credentials', true)
.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE')
.send()
}
})

fastify.addHook('onRequest', function (request, reply, next) {
reply.header('Access-Control-Allow-Origin', '*')
reply.header('Access-Control-Allow-Credentials', true)
next()
})

fastify.register(routes, { prefix: '/v1' })

fastify.listen({ port }, (_err, _address) => {
if (_err) {
fastify.log._error(_err)
}
fastify.log.info(`Fastify is listening on port: ${_address}`)
})
Loading

0 comments on commit 0708099

Please sign in to comment.