Skip to content

Commit

Permalink
Merge pull request #1 from vzotova/draft
Browse files Browse the repository at this point in the history
Draft of proof bot script using ape
  • Loading branch information
KPrasch authored Feb 14, 2024
2 parents ae5637c + ec4fbb7 commit 232819d
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
__pycache__
.env
build/
venv/
.python-version
bin/
.vscode/
.idea/
node_modules/
.build
contracts/.cache
env
.cache/
dist/
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
repos:
- repo: https://github.com/akaihola/darker
rev: 1.7.2
hooks:
- id: darker
args: ["--check"]
stages: [push]
- id: darker
stages: [commit]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.1.4'
hooks:
- id: ruff
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# State Transfer Bot Polygon → Ethereum

Stateless bot monitors GraphQL endpoint for new events `MessageSent` that occurs on Polygon network. And then transfers proof to Ethereum root contract.

## Installation

We use [Ape](https://docs.apeworx.io/ape/stable/index.html) as the testing and deployment framework of this project.

### Configuring Pre-commit

To install pre-commit locally:

```bash
pre-commit install
```

## Example of usage

```bash
export WEB3_INFURA_PROJECT_ID=<Infura project ID>
export APE_ACCOUNTS_BOT_PASSPHRASE=<Passphrase for account with alias BOT>

ape run proof_bot --fx-root-tunnel 0x720754c84f0b1737801bf63c950914E0C1d4aCa2 --graphql-endpoint https://api.studio.thegraph.com/query/24143/polygonchildmumbai/version/latest --proof-generator https://proof-generator.polygon.technology/api/v1/mumbai/exit-payload/ --network :goerli:infura --account BOT
```
14 changes: 14 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: train45
contracts_folder: contracts

plugins:
- name: solidity
- name: ape-etherscan

solidity:
version: 0.8.23
evm_version: paris

ethereum:
mainnet:
transaction_acceptance_timeout: 600 # 10 minutes
5 changes: 5 additions & 0 deletions contracts/IReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma solidity ^0.8.0;

interface IReceiver {
function receiveMessage(bytes memory inputData) external;
}
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-i https://pypi.org/simple
ape-infura==0.7.0
ape-solidity==0.7.1
ape-etherscan==0.7.1
eth-ape==0.7.7
eth-rlp==0.3.0 ; python_version >= '3.7' and python_version < '4'
165 changes: 165 additions & 0 deletions scripts/proof_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/usr/bin/python3

from urllib.parse import urljoin

import click
import requests
import rlp
from ape import project
from ape.api import AccountAPI
from ape.cli import ConnectedProviderCommand, account_option
from ape.contracts import ContractInstance
from ape.exceptions import ContractLogicError
from ape.logging import logger
from eth_typing import HexStr
from eth_utils import to_bytes, to_int

EVENT_SIGNATURE = "0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036"
EXIT_ALREADY_PROCESSED_ERROR = "EXIT_ALREADY_PROCESSED"


def hex_to_bytes(data: str) -> bytes:
return to_bytes(hexstr=HexStr(data))


def get_polygon_last_block_number(
account: AccountAPI, fx_base_channel_root_tunnel: ContractInstance
) -> int:
"""
Search in tx history for the last `receiveMessage` tx,
extracts block number (from Polygon) using that data
"""

last_blocknumber = 0
for tx in account.history:
if tx.method_called and tx.method_called.name == "receiveMessage":
last_proof_data = hex_to_bytes(tx.transaction.model_dump()["data"])
last_proof = fx_base_channel_root_tunnel.decode_input(last_proof_data)[1][
"inputData"
]
decoded = rlp.decode(last_proof)
blocknumber = to_int(decoded[2])
if blocknumber > last_blocknumber:
last_blocknumber = blocknumber

return last_blocknumber


def get_message_sent_events(graphql_endpoint: str, last_blocknumber: int) -> list[dict]:
"""
Queries GraphQL endpoint to retrieve all new `MessageSent` events
on Polygon network
"""

gql = (
"""
query AllMessagesSent {
messageSents(where: {blockNumber_gte: """
+ str(last_blocknumber)
+ """}, orderBy: blockNumber) {
transactionHash
}
}
"""
)

s = requests.session()
s.headers = {"Accept": "application/json", "Content-Type": "application/json"}

response = s.post(graphql_endpoint, json={"query": gql})

data = response.json()
messages = data["data"]["messageSents"]
return messages


def push_proof(
account: AccountAPI, fx_base_channel_root_tunnel: ContractInstance, proof: bytes
) -> bool:
"""Sends `receiveMessage` tx with the provided proof"""

try:
fx_base_channel_root_tunnel.receiveMessage(proof, sender=account)
return True
except ContractLogicError as e:
if e.message != EXIT_ALREADY_PROCESSED_ERROR:
raise e
logger.info("Transaction already processed")
return False


def get_and_push_proof(
account: AccountAPI,
fx_base_channel_root_tunnel: ContractInstance,
messages: list[dict],
event_signature: str,
proof_generator: str,
) -> int:
"""
Iterates over all new messages, checks proof for each of them
and executes tx on Ethereum side of the channel
"""

processed = 0
for event in messages:
txhash = event["transactionHash"]
s = requests.session()
response = s.get(
urljoin(proof_generator, txhash), params={"eventSignature": event_signature}
)
if response.status_code != 200:
logger.warning("Transaction is not checkpointed")
return processed

proof = response.json()["result"]
if push_proof(account, fx_base_channel_root_tunnel, proof):
processed += 1

return processed


@click.command(cls=ConnectedProviderCommand)
@account_option()
@click.option(
"--fx-root-tunnel",
"-fxrt",
help="Address of FxBaseRootTunnel contract",
default=None,
required=True,
type=click.STRING,
)
@click.option(
"--graphql-endpoint",
"-ge",
help="GraphQL endpoint",
default=None,
required=True,
type=click.STRING,
)
@click.option(
"--proof-generator",
"-pg",
help="Proof generator URI",
default=None,
required=True,
type=click.STRING,
)
def cli(account, fx_root_tunnel, graphql_endpoint, proof_generator):
"""Provides proof from Polygon network to Ethereum"""

account.set_autosign(enabled=True)
receiver = project.IReceiver.at(fx_root_tunnel)
last_blocknumber = get_polygon_last_block_number(account, receiver)
logger.debug("Last processed block number: %d", last_blocknumber)

messages = get_message_sent_events(graphql_endpoint, last_blocknumber)
logger.info("Got %d messages", len(messages))

if len(messages) == 0:
logger.info("No new transactions")
return

processed = get_and_push_proof(
account, receiver, messages, EVENT_SIGNATURE, proof_generator
)
logger.info("Processed %d transactions", processed)

0 comments on commit 232819d

Please sign in to comment.