From 272fb50cc7e1edbf5116a50183c1a4836304547e Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Tue, 9 Aug 2022 17:29:48 +0530 Subject: [PATCH 01/13] Adding project structure --- .github/main.yaml | 93 +++ .gitignore | 115 +--- .pre-commit-config.yaml | 36 ++ requirements.txt | 1 + sentinel/abi/BuddleBridge.json | 454 +++++++++++++++ sentinel/abi/BuddleDest.json | 602 +++++++++++++++++++ sentinel/abi/BuddleSrc.json | 729 ++++++++++++++++++++++++ sentinel/listener.py | 1 + sentinel/sentinel.py | 0 sentinel/services/buddleNetwork.py | 23 + sentinel/services/buddleSrc.py | 14 + sentinel/services/configManager.py | 3 + sentinel/services/threadManager.py | 3 + sentinel/services/transactionManager.py | 15 + sentinel/types/transaction.py | 3 + sentinel/watcher.py | 55 ++ tests/testWatcher.py | 0 17 files changed, 2044 insertions(+), 103 deletions(-) create mode 100644 .github/main.yaml create mode 100644 .pre-commit-config.yaml create mode 100644 requirements.txt create mode 100644 sentinel/abi/BuddleBridge.json create mode 100644 sentinel/abi/BuddleDest.json create mode 100644 sentinel/abi/BuddleSrc.json create mode 100644 sentinel/listener.py create mode 100644 sentinel/sentinel.py create mode 100644 sentinel/services/buddleNetwork.py create mode 100644 sentinel/services/buddleSrc.py create mode 100644 sentinel/services/configManager.py create mode 100644 sentinel/services/threadManager.py create mode 100644 sentinel/services/transactionManager.py create mode 100644 sentinel/types/transaction.py create mode 100644 sentinel/watcher.py create mode 100644 tests/testWatcher.py diff --git a/.github/main.yaml b/.github/main.yaml new file mode 100644 index 0000000..6e1f16a --- /dev/null +++ b/.github/main.yaml @@ -0,0 +1,93 @@ +name: CI + +on: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + # Set up Python 3.6 environment + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: "3.8" + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache pip + uses: actions/cache@v1 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + # Install dependencies + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + # Run our unit tests + - name: Run unit tests + run: | + python test_application.py + deploy-to-test: + # Only run this job if "build" has ended successfully + needs: + - build + + runs-on: ubuntu-latest + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + # Set up Python 3.6 environment + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: "3.6" + + # Set up cache for pip + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip + uses: actions/cache@v1 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + # Elastic Beanstalk CLI version + - name: Get EB CLI version + run: | + python -m pip install --upgrade pip + pip install awsebcli --upgrade + eb --version + # Configure AWS Credentials + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + # Create the Elastic Beanstalk application + - name: Create EBS application + run: | + eb init -p python-3.6 hello-world --region us-east-1 + # Deploy to (or Create) the Elastic Beanstalk environment + - name: Create test environment & deploy + run: | + (eb use test-environment && eb status test-environment && eb deploy) || eb create test-environment diff --git a/.gitignore b/.gitignore index 6704566..b10bcf4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,104 +1,13 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-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/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# 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 variables file +*.py[co] +.tox +secrets + +.idea/ +.eggs/ +.coverage +.vagrant/ +*.egg-info +.mypy_cache/ +build/ +.DS_Store .env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# 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 - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..cc218de --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +# HOWTO: https://pre-commit.com/#usage +# pip3 install pre-commit +# pre-commit install -t pre-commit -t pre-push + +repos: + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + - repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.6.2 + hooks: + - id: prettier + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-added-large-files + - id: check-ast + - id: check-builtin-literals + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: detect-private-key + exclude: tests/conftest.py + - id: detect-aws-credentials + args: + - --allow-missing-credentials + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d4d3f55 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +web3=5.30.0 diff --git a/sentinel/abi/BuddleBridge.json b/sentinel/abi/BuddleBridge.json new file mode 100644 index 0000000..44fea24 --- /dev/null +++ b/sentinel/abi/BuddleBridge.json @@ -0,0 +1,454 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "destChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "ticketId", + "type": "bytes32" + } + ], + "name": "FundsBridged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "CHAIN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "addBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2TokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_l1TokenAddress", + "type": "address" + } + ], + "name": "addTokenMap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_root", + "type": "bytes32" + } + ], + "name": "approveRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "buddle", + "outputs": [ + { + "internalType": "address", + "name": "source", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buddleBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_ticket", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "_bounty", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "_firstIdForTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lastIdForTicket", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "claimBounty", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_version", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_messenger", + "type": "address" + }, + { + "internalType": "address", + "name": "_stdBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "knownBridges", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_dest", + "type": "address" + } + ], + "name": "setDestination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_src", + "type": "address" + } + ], + "name": "setSource", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stdBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenMap", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "bountySeeker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_ticket", + "type": "bytes32" + } + ], + "name": "transferFunds", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "updateBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newBridgeAddress", + "type": "address" + } + ], + "name": "updateStandardBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2TokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_l1TokenAddress", + "type": "address" + } + ], + "name": "updateTokenMap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newMessengerAddress", + "type": "address" + } + ], + "name": "updateXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/sentinel/abi/BuddleDest.json b/sentinel/abi/BuddleDest.json new file mode 100644 index 0000000..ee8382b --- /dev/null +++ b/sentinel/abi/BuddleDest.json @@ -0,0 +1,602 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "LiquidityOwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "RootApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + } + ], + "name": "TransferCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "claimer", + "type": "address" + } + ], + "name": "WithdrawalEvent", + "type": "event" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "approveStateRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "buddleBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "internalType": "struct IBuddleDestination.TransferData", + "name": "_data", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_transferID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "changeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_version", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_buddleBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "liquidityOwners", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_messenger", + "type": "address" + } + ], + "name": "setXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "transferFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newBridgeAddress", + "type": "address" + } + ], + "name": "updateBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newMessenger", + "type": "address" + } + ], + "name": "updateXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_node", + "type": "bytes32" + }, + { + "internalType": "bytes32[]", + "name": "_proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes32", + "name": "_root", + "type": "bytes32" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/sentinel/abi/BuddleSrc.json b/sentinel/abi/BuddleSrc.json new file mode 100644 index 0000000..68d050e --- /dev/null +++ b/sentinel/abi/BuddleSrc.json @@ -0,0 +1,729 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "ticket", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "TicketConfirmed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "ticket", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "destChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "bounty", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "firstIdForTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lastIdForTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "TicketCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IBuddleSource.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "srcChain", + "type": "uint256" + } + ], + "name": "TransferStarted", + "type": "event" + }, + { + "inputs": [], + "name": "CHAIN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONTRACT_FEE_BASIS_POINTS", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONTRACT_FEE_RAMP_UP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_DEPOSIT_COUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MERKLE_TREE_DEPTH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "addDestination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + } + ], + "name": "addL1Token", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + } + ], + "name": "addTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "arbSys", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "buddleBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buddleDestination", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_ticket", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_destChain", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_tokenAmounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "_bountyAmounts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "_firstTransferInTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lastTransferInTicket", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_provider", + "type": "address" + } + ], + "name": "confirmTicket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_destChain", + "type": "uint256" + } + ], + "name": "createTicket", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_destChain", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_version", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_feeBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_feeRampUp", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_buddleBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "l1TokenMap", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "lastConfirmedTransfer", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "router", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_arbSys", + "type": "address" + }, + { + "internalType": "address", + "name": "_gatewayRouter", + "type": "address" + } + ], + "name": "setXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenMapping", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokens", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "transferCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_arbSys", + "type": "address" + } + ], + "name": "updateArbSys", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newBridgeAddress", + "type": "address" + } + ], + "name": "updateBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newContractFeeBasisPoints", + "type": "uint256" + } + ], + "name": "updateContractFeeBasisPoints", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newContractFeeRampUp", + "type": "uint256" + } + ], + "name": "updateContractFeeRampUp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "updateDestination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_gatewayRouter", + "type": "address" + } + ], + "name": "updateGatewayRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + } + ], + "name": "updateL1Token", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/sentinel/listener.py b/sentinel/listener.py new file mode 100644 index 0000000..21b405d --- /dev/null +++ b/sentinel/listener.py @@ -0,0 +1 @@ +import os diff --git a/sentinel/sentinel.py b/sentinel/sentinel.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/services/buddleNetwork.py b/sentinel/services/buddleNetwork.py new file mode 100644 index 0000000..a6104b1 --- /dev/null +++ b/sentinel/services/buddleNetwork.py @@ -0,0 +1,23 @@ +from web3 import Web3 +from web3.types import BlockData +from web3.contract import Contract + + +class BuddleNetwork: + def __init__(self, rpcUrl: str) -> None: + self.rpcUrl = rpcUrl + self.connected = False + self.w3 = None + + def connect(self) -> bool: + if not self.w3.isConnected(): + self.w3 = Web3(Web3.HTTPProvider(self.rpcUrl)) + self.connected = True + + return self.connected + + def getBlock(self, blockNumber) -> BlockData: + return self.w3.eth.get_block(blockNumber, full_transactions=True) + + def getContract(self, contractAddress, abi) -> Contract: + return self.w3.eth.contract(address=contractAddress, abi=abi) diff --git a/sentinel/services/buddleSrc.py b/sentinel/services/buddleSrc.py new file mode 100644 index 0000000..e231813 --- /dev/null +++ b/sentinel/services/buddleSrc.py @@ -0,0 +1,14 @@ +from buddleNetwork import BuddleNetwork +from web3.utils.filters import LogFilter + + +class BuddleSrc(BuddleNetwork): + def __init__(self, rpcUrl): + super().__init__(rpcUrl) + self.eventFilters: dict[str, LogFilter] = {} + + def readNewEvents(self, eventName: str): + pass + + def readNewBlocks(self): + pass diff --git a/sentinel/services/configManager.py b/sentinel/services/configManager.py new file mode 100644 index 0000000..1760cda --- /dev/null +++ b/sentinel/services/configManager.py @@ -0,0 +1,3 @@ +class ConfigManager: + def __init__(self, rpcUrl: str) -> None: + pass diff --git a/sentinel/services/threadManager.py b/sentinel/services/threadManager.py new file mode 100644 index 0000000..f973ebe --- /dev/null +++ b/sentinel/services/threadManager.py @@ -0,0 +1,3 @@ +class ThreadManager: + def __init__(self) -> None: + pass diff --git a/sentinel/services/transactionManager.py b/sentinel/services/transactionManager.py new file mode 100644 index 0000000..90906f2 --- /dev/null +++ b/sentinel/services/transactionManager.py @@ -0,0 +1,15 @@ +from threadManager import ThreadManager + + +class TransactionManager(ThreadManager): + def __init__(self) -> None: + pass + + def queueTransaction(self, transaction: Transaction) -> None: + pass + + def processTransaction(self) -> None: + pass + + def startProcessThread(self) -> None: + pass diff --git a/sentinel/types/transaction.py b/sentinel/types/transaction.py new file mode 100644 index 0000000..a2827f2 --- /dev/null +++ b/sentinel/types/transaction.py @@ -0,0 +1,3 @@ +class Transaction: + def __init__(self) -> None: + pass diff --git a/sentinel/watcher.py b/sentinel/watcher.py new file mode 100644 index 0000000..ad1e266 --- /dev/null +++ b/sentinel/watcher.py @@ -0,0 +1,55 @@ +from web3 import Web3 +import time +import json + +w3 = Web3( + Web3.HTTPProvider( + "https://arb-rinkeby.g.alchemy.com/v2/YvBKFI1Om2hFbF878J6mrwbxZYtY1VF6" + ) +) +# w3 = Web3(EthereumTesterProvider()) +print(w3.isConnected()) + +sourceAbi = open("abi/BuddleSrc.json", "r") + +with open("abi/BuddleSrc.json") as f: + info_json = json.load(f) +abi = info_json + +# arbi source - 0x9869Fc26826172eB8fB334b39B8D865Be36b01C3 +# arbi destination - 0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00 +# arbi Bridge rinkeby - 0xe396721BF9FD7c320c3c528077428847c4940C65 + +contractAddress = "0x9869Fc26826172eB8fB334b39B8D865Be36b01C3" +contract = w3.eth.contract(address=contractAddress, abi=abi) +accounts = w3.eth.accounts + +print(contract.functions.CHAIN().call()) + + +def handle_event(event): + print("Event:", event["transactionHash"], end="\n\n") + receipt = w3.eth.waitForTransactionReceipt(event["transactionHash"]) + print("Receipt:", receipt, end="\n\n") + result = contract.events.TransferStarted().processReceipt(receipt) + # print("Result[0]['args']"result[0]['args'], end="\n\n") + print("Result:", result, end="\n\n") + + +def log_loop(event_filter, poll_interval): + while True: + print("Waiting for events...") + for event in event_filter.get_new_entries(): + print("Handling event...") + handle_event(event) + + time.sleep(poll_interval) + + +new_filter = contract.events.TransferStarted.createFilter( + fromBlock="latest" +) # 12110375 +print(log_loop(new_filter, 10)) + +# block_filter = w3.eth.filter({'fromBlock':'latest', 'address':contractAddress}) +# log_loop(block_filter, 2) diff --git a/tests/testWatcher.py b/tests/testWatcher.py new file mode 100644 index 0000000..e69de29 From 09d4b1e6168dc27cf6018b83da140cec272b8ae7 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Wed, 7 Sep 2022 04:17:28 +0530 Subject: [PATCH 02/13] Working prototype and some class abstractions --- .env.example | 0 .gitignore | 1 + requirements.txt | 3 +- sentinel/__init__.py | 0 sentinel/services/__init__.py | 0 sentinel/services/buddleNetwork.py | 67 +++++++++++++++++++-- sentinel/services/buddleSrc.py | 2 +- sentinel/services/configManager.py | 37 +++++++++++- sentinel/services/singletonFactory.py | 30 ++++++++++ sentinel/services/transactionManager.py | 22 +++++-- sentinel/services/web3Manager.py | 18 ++++++ sentinel/types/__init__.py | 0 sentinel/types/enums.py | 7 +++ sentinel/types/exceptions.py | 8 +++ sentinel/types/transferData.py | 48 +++++++++++++++ sentinel/watcher.py | 80 +++++++++++++++++++++---- 16 files changed, 300 insertions(+), 23 deletions(-) create mode 100644 .env.example create mode 100644 sentinel/__init__.py create mode 100644 sentinel/services/__init__.py create mode 100644 sentinel/services/singletonFactory.py create mode 100644 sentinel/services/web3Manager.py create mode 100644 sentinel/types/__init__.py create mode 100644 sentinel/types/enums.py create mode 100644 sentinel/types/exceptions.py create mode 100644 sentinel/types/transferData.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index b10bcf4..38dcae8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ secrets build/ .DS_Store .env +.pyenv diff --git a/requirements.txt b/requirements.txt index d4d3f55..2ffe9f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -web3=5.30.0 +web3==5.30.0 +python-dotenv===0.21.0 diff --git a/sentinel/__init__.py b/sentinel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/services/__init__.py b/sentinel/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/services/buddleNetwork.py b/sentinel/services/buddleNetwork.py index a6104b1..ef14c4b 100644 --- a/sentinel/services/buddleNetwork.py +++ b/sentinel/services/buddleNetwork.py @@ -1,16 +1,30 @@ from web3 import Web3 +import json from web3.types import BlockData from web3.contract import Contract +from eth_account.datastructures import SignedTransaction +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.services.configManager import ConfigManager +from sentinel.services.transactionManager import TransactionManager +from sentinel.types.enums import BuddleContract +from sentinel.types.transferData import TransferData class BuddleNetwork: - def __init__(self, rpcUrl: str) -> None: - self.rpcUrl = rpcUrl + def __init__(self, singletonFactory: SingletonFactory, chainID: int) -> None: + self.chainId = chainID self.connected = False self.w3 = None + self.contracts: dict[str, Contract] = {} + self._account = None + self.configService: ConfigManager = singletonFactory.getService(ConfigManager) + self.transactionService: TransactionManager = singletonFactory.getService( + TransactionManager + ) def connect(self) -> bool: if not self.w3.isConnected(): + self.rpcUrl = self.configService.getRpcUrl(self.chainId) self.w3 = Web3(Web3.HTTPProvider(self.rpcUrl)) self.connected = True @@ -19,5 +33,50 @@ def connect(self) -> bool: def getBlock(self, blockNumber) -> BlockData: return self.w3.eth.get_block(blockNumber, full_transactions=True) - def getContract(self, contractAddress, abi) -> Contract: - return self.w3.eth.contract(address=contractAddress, abi=abi) + def readAbi(self, buddleContract: BuddleContract) -> dict: + with open(f"./../abi/{buddleContract.value}.json") as f: + info_json = json.load(f) + + return info_json + + def getContract(self, buddleContract: BuddleContract) -> Contract: + contract = self.contracts.get(buddleContract.name) + if not contract: + contract = self.w3.eth.contract( + address=self.configService.getContractAddress( + buddleContract, self.chainId + ), + abi=self.readAbi(buddleContract), + ) + self.contracts[buddleContract.name] = contract + + return contract + + @property + def account(self): + if not self._account: + privateKey = self.configService.getPrivateKey(self.chainId) + self.account = self.w3.eth.account.privateKeyToAccount(privateKey) + + return self.account + + def depositOnDestination( + self, transferData: TransferData, transferId: int, chainId: int + ) -> SignedTransaction: + contract = self.getContract(BuddleContract.DESTINATION) + tx = contract.functions.deposit( + transferData.rawData, transferId, chainId + ).buildTransaction( + { + "from": self.account.address, + "value": transferData[2], + "nonce": self.w3.eth.get_transaction_count( + self.configService.getPrivateKey(self.chainId) + ), + } + ) + signedTx = self.w3.eth.account.sign_transaction( + tx, private_key=self.configService.getPrivateKey(self.chainId) + ) + + return signedTx diff --git a/sentinel/services/buddleSrc.py b/sentinel/services/buddleSrc.py index e231813..1be739e 100644 --- a/sentinel/services/buddleSrc.py +++ b/sentinel/services/buddleSrc.py @@ -3,7 +3,7 @@ class BuddleSrc(BuddleNetwork): - def __init__(self, rpcUrl): + def __init__(self, rpcUrl) -> None: super().__init__(rpcUrl) self.eventFilters: dict[str, LogFilter] = {} diff --git a/sentinel/services/configManager.py b/sentinel/services/configManager.py index 1760cda..65b5d08 100644 --- a/sentinel/services/configManager.py +++ b/sentinel/services/configManager.py @@ -1,3 +1,36 @@ +import os +from dotenv import load_dotenv +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.types.enums import BuddleContract + + class ConfigManager: - def __init__(self, rpcUrl: str) -> None: - pass + """ + This is a singleton class. Use the SingletonFactory to get an instance of this class. + """ + + def __init__(self, singletonFactory: SingletonFactory) -> None: + dotenv_path = os.path.join(os.path.dirname(__file__), "./../../.env") + load_dotenv(dotenv_path) + + def getPrivateKey(self, chainId: int) -> str: + key = os.environ.get(f"PRIVATE_KEY_{chainId}") + if not key: + key = os.environ.get(f"PRIVATE_KEY_DEFAULT") + + return key + + def getRpcUrl(self, chainId: int) -> str: + return os.environ.get(f"RPC_URL_{chainId}") + + def getContractAddress(self, buddleContract: BuddleContract, chainId: int) -> str: + value = os.environ.get(f"CONTRACT_ADDRESS_{buddleContract.name}_{chainId}") + + # Bridge contract for each chain MUST have a valid address in config as it is unique + if not value and buddleContract != BuddleContract.BUDDLE_BRIDGE: + value = os.environ.get(f"CONTRACT_ADDRESS_{buddleContract.name}_DEFAULT") + + return value + + def getEnv(self, key: str) -> any: + return os.environ.get(key) diff --git a/sentinel/services/singletonFactory.py b/sentinel/services/singletonFactory.py new file mode 100644 index 0000000..ef46800 --- /dev/null +++ b/sentinel/services/singletonFactory.py @@ -0,0 +1,30 @@ +from sentinel.types.exceptions import SingletonFactoryException + + +class SingletonFactory: + def __init__(self) -> None: + self.classes: dict[type, object] = {} + + def addService(self, cls: object) -> None: + self.classes[type(cls)] = cls + + def removeService(self, clsType: type) -> bool: + if self.classes.get(clsType): + del self.classes[clsType] + return True + + return False + + def getService(self, clsType: type) -> object: + cls = self.classes.get(clsType) + if not cls: + cls = clsType(self) + self.addService(cls) + + return cls + + def updateService(self, cls: object) -> bool: + if self.classes.get(type(cls)): + self.addClass(cls) + else: + raise SingletonFactoryException("Class not found in factory") diff --git a/sentinel/services/transactionManager.py b/sentinel/services/transactionManager.py index 90906f2..cea648c 100644 --- a/sentinel/services/transactionManager.py +++ b/sentinel/services/transactionManager.py @@ -1,12 +1,26 @@ +from sentinel.services.web3Manager import Web3Manager from threadManager import ThreadManager +from eth_account.datastructures import SignedTransaction +from sentinel.services.singletonFactory import SingletonFactory class TransactionManager(ThreadManager): - def __init__(self) -> None: - pass + """ + This is a singleton class. Use the SingletonFactory to get an instance of this class. + """ - def queueTransaction(self, transaction: Transaction) -> None: - pass + def __init__(self, singletonFactory: SingletonFactory) -> None: + self.web3Service: Web3Manager = singletonFactory.getService(Web3Manager) + + def queueTransaction(self, txn: SignedTransaction, chainId: int) -> str: + # TODO: This is test code. Write code to use a separate thread for transaction processing + + txnHash = self.web3Service.getInstance(chainId).eth.send_raw_transaction( + txn.rawTransaction + ) + self.web3Service.getInstance(chainId).eth.wait_for_transaction_receipt(txnHash) + + print(txnHash) def processTransaction(self) -> None: pass diff --git a/sentinel/services/web3Manager.py b/sentinel/services/web3Manager.py new file mode 100644 index 0000000..6b70e99 --- /dev/null +++ b/sentinel/services/web3Manager.py @@ -0,0 +1,18 @@ +from web3 import Web3 +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.services.configManager import ConfigManager + + +class Web3Manager: + def __init__(self, singletonFactory: SingletonFactory) -> None: + self.web3Instances: dict[int, Web3] = {} + self.configService: ConfigManager = singletonFactory.getService(ConfigManager) + + def getInstance(self, chainId: int) -> Web3: + web3Instance = self.web3Instances.get(chainId) + if not web3Instance: + web3Instance = Web3( + Web3.HTTPProvider(self.configService.getRpcUrl(chainId)) + ) + self.web3Instances[chainId] = web3Instance + return web3Instance diff --git a/sentinel/types/__init__.py b/sentinel/types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/types/enums.py b/sentinel/types/enums.py new file mode 100644 index 0000000..aef409a --- /dev/null +++ b/sentinel/types/enums.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class BuddleContract(Enum): + SOURCE = "BuddleSrc" + DESTINATION = "BuddleDest" + BRIDGE = "BuddleBridge" diff --git a/sentinel/types/exceptions.py b/sentinel/types/exceptions.py new file mode 100644 index 0000000..ebe04c6 --- /dev/null +++ b/sentinel/types/exceptions.py @@ -0,0 +1,8 @@ +class SingletonFactoryException(Exception): + def __init__(self, *args: object): + super().__init__(*args) + + +class RequiredConfigMissingException(Exception): + def __init__(self, *args: object): + super().__init__(*args) diff --git a/sentinel/types/transferData.py b/sentinel/types/transferData.py new file mode 100644 index 0000000..ca1764b --- /dev/null +++ b/sentinel/types/transferData.py @@ -0,0 +1,48 @@ +class TransferData: + def __init__(self, rawData: tuple) -> None: + self._rawData = rawData + self._tokenAddress: str = rawData[0] + self._destination: str = rawData[1] + self._amount: int = rawData[2] + self._fee: int = rawData[3] + self._startTime: int = rawData[4] + self._feeRampup: int = rawData[5] + self._chain: int = rawData[6] + + @property + def tokenAddress(self): + return self._tokenAddress + + @property + def destinationAddress(self): + return self._destination + + @property + def amount(self): + return self._amount + + @property + def fee(self): + return self._fee + + @property + def startTime(self): + return self._startTime + + @property + def feeRampup(self): + return self._feeRampup + + @property + def chain(self): + return self._chain + + @property + def rawData(self): + return self._rawData + + def __str__(self) -> str: + return str(self._rawData) + + def __repr__(self) -> str: + return self.__str__() diff --git a/sentinel/watcher.py b/sentinel/watcher.py index ad1e266..437197a 100644 --- a/sentinel/watcher.py +++ b/sentinel/watcher.py @@ -1,14 +1,30 @@ from web3 import Web3 import time import json +import asyncio +from dotenv import load_dotenv +from web3 import __version__ + +import os +from os.path import join, dirname +from dotenv import load_dotenv + +dotenv_path = join(dirname(__file__), "./../.env") +load_dotenv(dotenv_path) + +private_key = os.environ.get("PRIVATE_KEY") +print(private_key) w3 = Web3( - Web3.HTTPProvider( - "https://arb-rinkeby.g.alchemy.com/v2/YvBKFI1Om2hFbF878J6mrwbxZYtY1VF6" - ) + # Web3.WebsocketProvider("wss://rinkeby.arbitrum.io/feed") + Web3.HTTPProvider("https://rinkeby.arbitrum.io/rpc") ) + +w3_l1 = Web3(Web3.HTTPProvider("https://rpc.ankr.com/eth_rinkeby ")) + # w3 = Web3(EthereumTesterProvider()) print(w3.isConnected()) +print(__version__) sourceAbi = open("abi/BuddleSrc.json", "r") @@ -16,6 +32,14 @@ info_json = json.load(f) abi = info_json +with open("abi/BuddleDest.json") as f: + dest_json = json.load(f) +abiDest = dest_json + +with open("abi/BuddleBridge.json") as f: + bridge_json = json.load(f) +abiBridge = bridge_json + # arbi source - 0x9869Fc26826172eB8fB334b39B8D865Be36b01C3 # arbi destination - 0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00 # arbi Bridge rinkeby - 0xe396721BF9FD7c320c3c528077428847c4940C65 @@ -24,32 +48,66 @@ contract = w3.eth.contract(address=contractAddress, abi=abi) accounts = w3.eth.accounts -print(contract.functions.CHAIN().call()) +me = w3.eth.account.privateKeyToAccount(private_key) + +w3.eth.default_account = me + +destination_contract_address = "0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00" +contractDestination = w3.eth.contract(address=destination_contract_address, abi=abiDest) def handle_event(event): - print("Event:", event["transactionHash"], end="\n\n") receipt = w3.eth.waitForTransactionReceipt(event["transactionHash"]) - print("Receipt:", receipt, end="\n\n") result = contract.events.TransferStarted().processReceipt(receipt) # print("Result[0]['args']"result[0]['args'], end="\n\n") - print("Result:", result, end="\n\n") + # print("Result:", result, end="\n\n") + transferData = result[0]["args"]["transferData"] + transferID = result[0]["args"]["transferID"] + print("transferData:", transferData, end="\n\n") + print(type(transferData[2])) + + depositTxn = contractDestination.functions.deposit( + transferData, transferID, 421611 + ).buildTransaction( + { + "from": me.address, + "value": transferData[2], + "nonce": w3.eth.getTransactionCount(me.address), + } + ) + + signed = w3.eth.account.signTransaction(depositTxn, private_key) + test = w3.eth.sendRawTransaction(signed.rawTransaction) + w3.eth.wait_for_transaction_receipt(test) + + print(test) def log_loop(event_filter, poll_interval): while True: print("Waiting for events...") - for event in event_filter.get_new_entries(): + for event in event_filter.get_all_entries(): # get_new_entries(): print("Handling event...") handle_event(event) + break time.sleep(poll_interval) -new_filter = contract.events.TransferStarted.createFilter( - fromBlock="latest" -) # 12110375 +new_filter = contract.events.TransferStarted.createFilter(fromBlock="0x0") print(log_loop(new_filter, 10)) +# transferData +# address tokenAddress; +# address destination; +# uint256 amount; +# uint256 fee; +# uint256 startTime; +# uint256 feeRampup; +# uint256 chain; + +# TrabnsferID +# SourceChainID + # block_filter = w3.eth.filter({'fromBlock':'latest', 'address':contractAddress}) # log_loop(block_filter, 2) From 55c0405b42ebe7c35aaaab42bdb01fbb0381c948 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Wed, 7 Sep 2022 04:36:13 +0530 Subject: [PATCH 03/13] Adding black lint github action --- .github/black.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/black.yml diff --git a/.github/black.yml b/.github/black.yml new file mode 100644 index 0000000..6904b20 --- /dev/null +++ b/.github/black.yml @@ -0,0 +1,11 @@ +name: black-action +on: [push, pull_request] +jobs: + linter_name: + name: runner / black formatter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: rickstaa/action-black@v1 + with: + black_args: ". --check --diff" From a9538a7a6dd50b06b9dcfbdce651602438764aa9 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Wed, 7 Sep 2022 04:37:51 +0530 Subject: [PATCH 04/13] move github actions to workflows folder --- .github/main.yaml | 93 ------------------------------- .github/{ => workflows}/black.yml | 0 2 files changed, 93 deletions(-) delete mode 100644 .github/main.yaml rename .github/{ => workflows}/black.yml (100%) diff --git a/.github/main.yaml b/.github/main.yaml deleted file mode 100644 index 6e1f16a..0000000 --- a/.github/main.yaml +++ /dev/null @@ -1,93 +0,0 @@ -name: CI - -on: - push: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - # Checks-out your repository under $GITHUB_WORKSPACE - - uses: actions/checkout@v2 - - # Set up Python 3.6 environment - - name: Set up Python 3.8 - uses: actions/setup-python@v1 - with: - python-version: "3.8" - - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - - name: Cache pip - uses: actions/cache@v1 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - # Install dependencies - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - # Run our unit tests - - name: Run unit tests - run: | - python test_application.py - deploy-to-test: - # Only run this job if "build" has ended successfully - needs: - - build - - runs-on: ubuntu-latest - - steps: - # Checks-out your repository under $GITHUB_WORKSPACE - - uses: actions/checkout@v2 - - # Set up Python 3.6 environment - - name: Set up Python 3.6 - uses: actions/setup-python@v1 - with: - python-version: "3.6" - - # Set up cache for pip - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: Cache pip - uses: actions/cache@v1 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - # Elastic Beanstalk CLI version - - name: Get EB CLI version - run: | - python -m pip install --upgrade pip - pip install awsebcli --upgrade - eb --version - # Configure AWS Credentials - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - # Create the Elastic Beanstalk application - - name: Create EBS application - run: | - eb init -p python-3.6 hello-world --region us-east-1 - # Deploy to (or Create) the Elastic Beanstalk environment - - name: Create test environment & deploy - run: | - (eb use test-environment && eb status test-environment && eb deploy) || eb create test-environment diff --git a/.github/black.yml b/.github/workflows/black.yml similarity index 100% rename from .github/black.yml rename to .github/workflows/black.yml From e3e84c29c7d38ee541e9414198e3bd60b9a19442 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Wed, 7 Sep 2022 04:40:02 +0530 Subject: [PATCH 05/13] Addding prettier github action --- .github/workflows/prettier.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/prettier.yml diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 0000000..94868df --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,25 @@ +name: Continuous Integration + +on: + pull_request: + branches: [main] + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # Make sure the actual branch is checked out when running on pull requests + ref: ${{ github.head_ref }} + # This is important to fetch the changes to the previous commit + fetch-depth: 0 + + - name: Prettify code + uses: creyD/prettier_action@v4.2 + with: + # This part is also where you can pass other options, for example: + prettier_options: --write **/*.{js,md,py,yml} + only_changed: True From 5c24986c127e3886e1ff212904d95774fd547483 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Wed, 7 Sep 2022 04:43:50 +0530 Subject: [PATCH 06/13] remove unwanted options --- .github/workflows/prettier.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 94868df..b661c9e 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -1,8 +1,6 @@ name: Continuous Integration -on: - pull_request: - branches: [main] +on: [push, pull_request] jobs: prettier: @@ -21,5 +19,5 @@ jobs: uses: creyD/prettier_action@v4.2 with: # This part is also where you can pass other options, for example: - prettier_options: --write **/*.{js,md,py,yml} + prettier_options: --write **/*.{js,md} only_changed: True From 6b1877d85a96a22b7b05b9d38755cd804a9663dc Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Wed, 5 Oct 2022 18:34:52 +0530 Subject: [PATCH 07/13] Adding config json and initalizing sentinel --- sentinel/bounty.py | 3 +++ sentinel/liquidity.py | 6 ++++++ sentinel/listener.py | 11 ++++++++++- sentinel/sentinel.py | 11 +++++++++++ sentinel/{watcher.py => testing.py} | 2 +- 5 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 sentinel/bounty.py create mode 100644 sentinel/liquidity.py rename sentinel/{watcher.py => testing.py} (99%) diff --git a/sentinel/bounty.py b/sentinel/bounty.py new file mode 100644 index 0000000..db0269a --- /dev/null +++ b/sentinel/bounty.py @@ -0,0 +1,3 @@ +class Bounty: + def __init__(self) -> None: + pass diff --git a/sentinel/liquidity.py b/sentinel/liquidity.py new file mode 100644 index 0000000..3097946 --- /dev/null +++ b/sentinel/liquidity.py @@ -0,0 +1,6 @@ +import web3 + + +class Liquidity: + def __init__(self) -> None: + pass diff --git a/sentinel/listener.py b/sentinel/listener.py index 21b405d..bd928bd 100644 --- a/sentinel/listener.py +++ b/sentinel/listener.py @@ -1 +1,10 @@ -import os +class Listener(object): + def __init__(self, name, callback): + self.name = name + self.callback = callback + + def __str__(self): + return self.name + + def __call__(self, *args, **kwargs): + return self.callback(*args, **kwargs) diff --git a/sentinel/sentinel.py b/sentinel/sentinel.py index e69de29..cd73c73 100644 --- a/sentinel/sentinel.py +++ b/sentinel/sentinel.py @@ -0,0 +1,11 @@ +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.services.configManager import ConfigManager + + +class Sentinel: + def __init__(self) -> None: + self.singletonFactory = SingletonFactory() + self.configManager = self.singletonFactory.getService(ConfigManager) + + def start(self) -> None: + threadManager = self.singletonFactory.getService("ThreadManager") diff --git a/sentinel/watcher.py b/sentinel/testing.py similarity index 99% rename from sentinel/watcher.py rename to sentinel/testing.py index 437197a..9be0b9d 100644 --- a/sentinel/watcher.py +++ b/sentinel/testing.py @@ -20,7 +20,7 @@ Web3.HTTPProvider("https://rinkeby.arbitrum.io/rpc") ) -w3_l1 = Web3(Web3.HTTPProvider("https://rpc.ankr.com/eth_rinkeby ")) +w3_l1 = Web3(Web3.HTTPProvider("https://rpc.ankr.com/eth_rinkeby")) # w3 = Web3(EthereumTesterProvider()) print(w3.isConnected()) From df07f5b3a0c292e7886181e032d777700b87c2b0 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Thu, 6 Oct 2022 11:00:51 +0530 Subject: [PATCH 08/13] Adding config --- README.md | 2 +- env.json | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 env.json diff --git a/README.md b/README.md index 09702b0..1b2b6c1 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# sentinal \ No newline at end of file +# sentinel diff --git a/env.json b/env.json new file mode 100644 index 0000000..ba85ecd --- /dev/null +++ b/env.json @@ -0,0 +1,32 @@ +{ + "defaults": { + "privateKey": "0x00000000000000000000000", + "processes": ["bounty"] + }, + "networks": [ + { + "name": "ArbitriumRinkeby", + "chainId": "421611", + "type": "L2", + "rpc": "https://rinkeby.arbitrum.io/rpc", + "privateKey": "0x0000000000000000000000000000000000000000000000000000000000000001", + "srcContract": "0x9869Fc26826172eB8fB334b39B8D865Be36b01C3", + "destContract": "0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00", + "processes": ["bounty"] + }, + { + "name": "Rinkeby", + "chainId": "4", + "type": "L1", + "rpc": "https://rpc.ankr.com/eth_rinkeby", + "bridgeContract": "0xe396721BF9FD7c320c3c528077428847c4940C65" + }, + { + "name": "Goerli", + "chainId": "5", + "type": "L1", + "rpc": "https://rpc.ankr.com/eth_goerli", + "bridgeContract": "0x0000000000000000000000000000000000000000" + } + ] +} From de6527c321b6348edfeee5d96d6c3acabf0209f9 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Thu, 6 Oct 2022 19:57:25 +0530 Subject: [PATCH 09/13] Logging service setup done --- .gitignore | 1 + requirements.txt | 1 - sentinel/__main__.py | 1 + sentinel/sentinel.py | 17 +++- sentinel/services/configManager.py | 10 ++- sentinel/services/logManager.py | 114 ++++++++++++++++++++++++ sentinel/services/singletonFactory.py | 2 + sentinel/services/threadManager.py | 4 + sentinel/services/transactionManager.py | 5 +- sentinel/services/web3Manager.py | 4 +- sentinel/utils/__init__.py | 0 sentinel/utils/constants.py | 2 + sentinel/utils/fsUtils.py | 40 +++++++++ 13 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 sentinel/__main__.py create mode 100644 sentinel/services/logManager.py create mode 100644 sentinel/utils/__init__.py create mode 100644 sentinel/utils/constants.py create mode 100644 sentinel/utils/fsUtils.py diff --git a/.gitignore b/.gitignore index 38dcae8..a941810 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ build/ .DS_Store .env .pyenv +logs/ diff --git a/requirements.txt b/requirements.txt index 2ffe9f1..76ae21c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ web3==5.30.0 -python-dotenv===0.21.0 diff --git a/sentinel/__main__.py b/sentinel/__main__.py new file mode 100644 index 0000000..2dad67d --- /dev/null +++ b/sentinel/__main__.py @@ -0,0 +1 @@ +import sentinel.sentinel diff --git a/sentinel/sentinel.py b/sentinel/sentinel.py index cd73c73..a4f6247 100644 --- a/sentinel/sentinel.py +++ b/sentinel/sentinel.py @@ -1,11 +1,26 @@ from sentinel.services.singletonFactory import SingletonFactory from sentinel.services.configManager import ConfigManager +from sentinel.services.logManager import BuddleLogger + +import logging class Sentinel: def __init__(self) -> None: self.singletonFactory = SingletonFactory() + self.logger = BuddleLogger.setupLogger(None, "DEBUG") + self.singletonFactory.addService(self.logger) self.configManager = self.singletonFactory.getService(ConfigManager) + # self.logger = logging.getLogger(__name__) def start(self) -> None: - threadManager = self.singletonFactory.getService("ThreadManager") + self.logger.info("Staring Sentinel...") + + +# Start Sentinel +def main(): + sentinel = Sentinel() + sentinel.start() + + +main() diff --git a/sentinel/services/configManager.py b/sentinel/services/configManager.py index 65b5d08..d7d387b 100644 --- a/sentinel/services/configManager.py +++ b/sentinel/services/configManager.py @@ -1,7 +1,9 @@ import os -from dotenv import load_dotenv +import json +from sentinel.services.logManager import BuddleLogger from sentinel.services.singletonFactory import SingletonFactory from sentinel.types.enums import BuddleContract +import logging class ConfigManager: @@ -10,8 +12,10 @@ class ConfigManager: """ def __init__(self, singletonFactory: SingletonFactory) -> None: - dotenv_path = os.path.join(os.path.dirname(__file__), "./../../.env") - load_dotenv(dotenv_path) + configPath = os.path.join(os.path.dirname(__file__), "./../../env.json") + configDict = {} + self.logger = singletonFactory.getService(BuddleLogger) + self.logger.debug("ConfigManager initialized") def getPrivateKey(self, chainId: int) -> str: key = os.environ.get(f"PRIVATE_KEY_{chainId}") diff --git a/sentinel/services/logManager.py b/sentinel/services/logManager.py new file mode 100644 index 0000000..339b563 --- /dev/null +++ b/sentinel/services/logManager.py @@ -0,0 +1,114 @@ +import logging +import logging.handlers as handlers +import os + +from sentinel.utils.constants import Constants +from sentinel.utils.fsUtils import FSUtils + + +class BuddleLogger(logging.Logger): + def __init__(self, name, level=logging.NOTSET): + self.errors = [] + self.warnings = [] + self.allErrors = [] + + super(BuddleLogger, self).__init__(name, level) + + def debug(self, msg, *args, **kwargs): + return super(BuddleLogger, self).debug(msg, *args, **kwargs) + + def info(self, msg, *args, **kwargs): + return super(BuddleLogger, self).info(msg, *args, **kwargs) + + def warning(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.warnings.append(fmtmsg) + self.allErrors.append("WARN: " + fmtmsg) + + return super(BuddleLogger, self).warning(msg, *args, **kwargs) + + warn = warning + + def error(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.errors.append(fmtmsg) + self.allErrors.append("ERROR: " + fmtmsg) + + return super(BuddleLogger, self).error(msg, *args, **kwargs) + + def exception(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.errors.append(fmtmsg) + self.allErrors.append("ERROR: " + fmtmsg) + + kwargs["exc_info"] = 1 + return super(BuddleLogger, self).error(msg, *args, **kwargs) + + def critical(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.errors.append(fmtmsg) + self.allErrors.append("CRITICAL: " + fmtmsg) + + return super(BuddleLogger, self).critical(msg, *args, **kwargs) + + fatal = critical + + def log(self, level, msg, *args, **kwargs): + return super(BuddleLogger, self).log(level, msg, *args, **kwargs) + + def findCaller(self, stack_info=False, stacklevel=3): + """ + Find the stack frame of the caller so that we can note the source + file name, line number and function name. + We override this to get the caller 1 frame above the usual retrieved + by logging.findCaller. Without this, caller information will always + refer to the log functions implemented in this derived class. + """ + return super(BuddleLogger, self).findCaller(stack_info, 3) + + @staticmethod + def setupLogger( + logLocation, logLevel="INFO" + ): # DEBUG, INFO, WARNING, ERROR, CRITICAL + logging.setLoggerClass(BuddleLogger) + logger = logging.getLogger(__name__) + try: + logger.setLevel(logLevel.upper()) + except Exception: + logLevel = logging.INFO + + try: + if logLocation is not None: + logFileDir = logLocation + else: + logFileDir = os.path.join(FSUtils.getScriptPath(), "logs") + + logFilePath = os.path.join(logFileDir, Constants.LOG_FILE_NAME) + FSUtils.createDirectory(logFileDir) + fileLogFormat = logging.Formatter( + "%(process)-5d %(asctime)s %(levelname)-7s %(module)-15s %(funcName)-20s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + fileLogHandler = handlers.RotatingFileHandler( + logFilePath, maxBytes=2000000, backupCount=4 + ) + fileLogHandler.setLevel(logging.DEBUG) + fileLogHandler.setFormatter(fileLogFormat) + logger.addHandler(fileLogHandler) + except Exception: + logger.exception("Could not set up file logger") + + logger.debug("Logger setup done!") + return logger diff --git a/sentinel/services/singletonFactory.py b/sentinel/services/singletonFactory.py index ef46800..eadbc3c 100644 --- a/sentinel/services/singletonFactory.py +++ b/sentinel/services/singletonFactory.py @@ -6,6 +6,7 @@ def __init__(self) -> None: self.classes: dict[type, object] = {} def addService(self, cls: object) -> None: + print("Adding", type(cls), "to services") self.classes[type(cls)] = cls def removeService(self, clsType: type) -> bool: @@ -18,6 +19,7 @@ def removeService(self, clsType: type) -> bool: def getService(self, clsType: type) -> object: cls = self.classes.get(clsType) if not cls: + print("Service", clsType.__name__, "not found, initializing...") cls = clsType(self) self.addService(cls) diff --git a/sentinel/services/threadManager.py b/sentinel/services/threadManager.py index f973ebe..c0368de 100644 --- a/sentinel/services/threadManager.py +++ b/sentinel/services/threadManager.py @@ -1,3 +1,7 @@ +import logging + + class ThreadManager: def __init__(self) -> None: + self.logger = logging.getLogger(__name__) pass diff --git a/sentinel/services/transactionManager.py b/sentinel/services/transactionManager.py index cea648c..115f0ff 100644 --- a/sentinel/services/transactionManager.py +++ b/sentinel/services/transactionManager.py @@ -1,7 +1,8 @@ -from sentinel.services.web3Manager import Web3Manager -from threadManager import ThreadManager from eth_account.datastructures import SignedTransaction from sentinel.services.singletonFactory import SingletonFactory +from sentinel.services.web3Manager import Web3Manager + +from threadManager import ThreadManager class TransactionManager(ThreadManager): diff --git a/sentinel/services/web3Manager.py b/sentinel/services/web3Manager.py index 6b70e99..31a94c9 100644 --- a/sentinel/services/web3Manager.py +++ b/sentinel/services/web3Manager.py @@ -1,6 +1,6 @@ -from web3 import Web3 -from sentinel.services.singletonFactory import SingletonFactory from sentinel.services.configManager import ConfigManager +from sentinel.services.singletonFactory import SingletonFactory +from web3 import Web3 class Web3Manager: diff --git a/sentinel/utils/__init__.py b/sentinel/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/utils/constants.py b/sentinel/utils/constants.py new file mode 100644 index 0000000..5de9265 --- /dev/null +++ b/sentinel/utils/constants.py @@ -0,0 +1,2 @@ +class Constants: + LOG_FILE_NAME = "sentinel.log" diff --git a/sentinel/utils/fsUtils.py b/sentinel/utils/fsUtils.py new file mode 100644 index 0000000..00ec099 --- /dev/null +++ b/sentinel/utils/fsUtils.py @@ -0,0 +1,40 @@ +import os +import logging + +logger = logging.getLogger(__name__) + + +class FSUtils: + @staticmethod + def getScriptPath(): + scriptPath = "" + try: + scriptPath = os.path.dirname(os.path.realpath(__file__)) + scriptPath = os.path.dirname(scriptPath) + except OSError: + logger.exception( + "Could not retrieve script path, using current working directory" + ) + try: + scriptPath = os.getcwd() + except OSError: + logger.exception( + "Could not retrieve current working directory either :(" + ) + + return scriptPath + + @staticmethod + def createDirectory(path): + try: + path = os.path.abspath(path) + if os.path.exists(path): + return True + else: + logger.debug("Creating directory [%s]", path) + + os.makedirs(path) + except Exception: + logger.exception("Could not create directory [%s]", path) + return False + return True From 1eef9d049813dfb497a061595b74552354c6309e Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Thu, 6 Oct 2022 20:58:41 +0530 Subject: [PATCH 10/13] Reading json configs and deserializing json --- sentinel/sentinel.py | 11 ++++++---- sentinel/services/configManager.py | 29 +++++++++++++++++++++------ sentinel/services/singletonFactory.py | 7 ++++--- sentinel/types/configTypes.py | 28 ++++++++++++++++++++++++++ sentinel/types/constants.py | 2 ++ 5 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 sentinel/types/configTypes.py create mode 100644 sentinel/types/constants.py diff --git a/sentinel/sentinel.py b/sentinel/sentinel.py index a4f6247..fbfeb89 100644 --- a/sentinel/sentinel.py +++ b/sentinel/sentinel.py @@ -1,8 +1,8 @@ -from sentinel.services.singletonFactory import SingletonFactory +import logging + from sentinel.services.configManager import ConfigManager from sentinel.services.logManager import BuddleLogger - -import logging +from sentinel.services.singletonFactory import SingletonFactory class Sentinel: @@ -20,7 +20,10 @@ def start(self) -> None: # Start Sentinel def main(): sentinel = Sentinel() - sentinel.start() + try: + sentinel.start() + except Exception: + print("Sentinel exited abnormally") main() diff --git a/sentinel/services/configManager.py b/sentinel/services/configManager.py index d7d387b..439d0ea 100644 --- a/sentinel/services/configManager.py +++ b/sentinel/services/configManager.py @@ -1,9 +1,11 @@ -import os import json +import os + from sentinel.services.logManager import BuddleLogger from sentinel.services.singletonFactory import SingletonFactory +from sentinel.types.configTypes import BuddleConfig from sentinel.types.enums import BuddleContract -import logging +from sentinel.types.exceptions import RequiredConfigMissingException class ConfigManager: @@ -12,11 +14,25 @@ class ConfigManager: """ def __init__(self, singletonFactory: SingletonFactory) -> None: - configPath = os.path.join(os.path.dirname(__file__), "./../../env.json") - configDict = {} self.logger = singletonFactory.getService(BuddleLogger) + self.config: BuddleConfig = None + self.configDict = {} + if not self.readConfigFile(): + raise RequiredConfigMissingException("Error reading config file") self.logger.debug("ConfigManager initialized") + def readConfigFile(self) -> bool: + configPath = os.path.join(os.path.dirname(__file__), "./../../env.json") + try: + with open(configPath) as configFile: + self.configDict = json.load(configFile) + self.config = BuddleConfig(self.configDict) + except Exception: + self.logger.exception("Error reading config file") + return False + + return True + def getPrivateKey(self, chainId: int) -> str: key = os.environ.get(f"PRIVATE_KEY_{chainId}") if not key: @@ -36,5 +52,6 @@ def getContractAddress(self, buddleContract: BuddleContract, chainId: int) -> st return value - def getEnv(self, key: str) -> any: - return os.environ.get(key) + def getEnv(self, key: str) -> str: + print("Type: ", type(os.environ.get(key))) + return self.configDict[key] diff --git a/sentinel/services/singletonFactory.py b/sentinel/services/singletonFactory.py index eadbc3c..1182358 100644 --- a/sentinel/services/singletonFactory.py +++ b/sentinel/services/singletonFactory.py @@ -1,4 +1,7 @@ from sentinel.types.exceptions import SingletonFactoryException +from typing import TypeVar, Callable + +R, D = TypeVar("R"), TypeVar("D") class SingletonFactory: @@ -6,7 +9,6 @@ def __init__(self) -> None: self.classes: dict[type, object] = {} def addService(self, cls: object) -> None: - print("Adding", type(cls), "to services") self.classes[type(cls)] = cls def removeService(self, clsType: type) -> bool: @@ -16,10 +18,9 @@ def removeService(self, clsType: type) -> bool: return False - def getService(self, clsType: type) -> object: + def getService(self, clsType: Callable[[D], R]) -> R: cls = self.classes.get(clsType) if not cls: - print("Service", clsType.__name__, "not found, initializing...") cls = clsType(self) self.addService(cls) diff --git a/sentinel/types/configTypes.py b/sentinel/types/configTypes.py new file mode 100644 index 0000000..4af43cd --- /dev/null +++ b/sentinel/types/configTypes.py @@ -0,0 +1,28 @@ +class BuddleConfig: + def __init__(self, config: dict): + self.defaults = Defaults(config.get("defaults")) + self.networks: list[Network] = [ + Network(network, self.defaults) for network in config.get("networks") + ] + + +class Defaults: + def __init__(self, defaults): + if defaults is None: + return + + self.privateKey: str = defaults.get("privateKey", "") + self.processes: list[str] = defaults.get("processes", []) + + +class Network: + def __init__(self, network: dict, defaults: Defaults): + self.name: str = network.get("name") + self.chainId: str = network.get("chainId") + self.type: str = network.get("type") + self.rpc: str = network.get("rpc") + self.privateKey: str = network.get("privateKey", defaults.privateKey) + self.srcContract: str = network.get("srcContract") + self.destContract: str = network.get("destContract") + self.bridgeContract: str = network.get("bridgeContract") + self.processes: list[str] = network.get("processes", defaults.processes) diff --git a/sentinel/types/constants.py b/sentinel/types/constants.py new file mode 100644 index 0000000..b89d7f4 --- /dev/null +++ b/sentinel/types/constants.py @@ -0,0 +1,2 @@ +class Constants: + LOG_FILE_NAME = "sentiel.log" From d74ffb66ce622f14c6c1a2f9ace186589c260c12 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Thu, 6 Oct 2022 21:00:52 +0530 Subject: [PATCH 11/13] Reading json configs and deserializing json --- env.json | 2 +- sentinel/types/configTypes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/env.json b/env.json index ba85ecd..3a74579 100644 --- a/env.json +++ b/env.json @@ -1,5 +1,5 @@ { - "defaults": { + "networkDefaults": { "privateKey": "0x00000000000000000000000", "processes": ["bounty"] }, diff --git a/sentinel/types/configTypes.py b/sentinel/types/configTypes.py index 4af43cd..30f5645 100644 --- a/sentinel/types/configTypes.py +++ b/sentinel/types/configTypes.py @@ -1,6 +1,6 @@ class BuddleConfig: def __init__(self, config: dict): - self.defaults = Defaults(config.get("defaults")) + self.defaults = Defaults(config.get("networkDefaults")) self.networks: list[Network] = [ Network(network, self.defaults) for network in config.get("networks") ] From 231a2e5002f754b1c8e1acb08c604d8459cf68dd Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Thu, 6 Oct 2022 21:32:41 +0530 Subject: [PATCH 12/13] readme setup --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b2b6c1..d535d7b 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# sentinel +# Sentinel + +## Description + +## Setup From ddef5df9211cb64ee4ce3a14cfff9d26a1c04456 Mon Sep 17 00:00:00 2001 From: Shreyas Papinwar Date: Wed, 12 Oct 2022 22:25:30 +0530 Subject: [PATCH 13/13] Added threading class and event handlers --- .gitignore | 1 + env.json | 32 ---------- env.json.example | 38 ++++++++++++ sentinel/handlers/__init__.py | 0 sentinel/handlers/baseHandler.py | 11 ++++ sentinel/handlers/bountyHandler.py | 9 +++ sentinel/handlers/liquidityHandler.py | 10 +++ sentinel/listener.py | 17 +++++- sentinel/sentinel.py | 2 - sentinel/services/buddleNetwork.py | 29 +++++---- sentinel/services/configManager.py | 20 ++---- sentinel/services/threadManager.py | 88 +++++++++++++++++++++++++-- sentinel/services/web3Manager.py | 13 +++- sentinel/types/configTypes.py | 33 ++++++++-- sentinel/utils/threadable.py | 50 +++++++++++++++ 15 files changed, 278 insertions(+), 75 deletions(-) delete mode 100644 env.json create mode 100644 env.json.example create mode 100644 sentinel/handlers/__init__.py create mode 100644 sentinel/handlers/baseHandler.py create mode 100644 sentinel/handlers/bountyHandler.py create mode 100644 sentinel/handlers/liquidityHandler.py create mode 100644 sentinel/utils/threadable.py diff --git a/.gitignore b/.gitignore index a941810..911ff40 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ build/ .env .pyenv logs/ +env.json diff --git a/env.json b/env.json deleted file mode 100644 index 3a74579..0000000 --- a/env.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "networkDefaults": { - "privateKey": "0x00000000000000000000000", - "processes": ["bounty"] - }, - "networks": [ - { - "name": "ArbitriumRinkeby", - "chainId": "421611", - "type": "L2", - "rpc": "https://rinkeby.arbitrum.io/rpc", - "privateKey": "0x0000000000000000000000000000000000000000000000000000000000000001", - "srcContract": "0x9869Fc26826172eB8fB334b39B8D865Be36b01C3", - "destContract": "0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00", - "processes": ["bounty"] - }, - { - "name": "Rinkeby", - "chainId": "4", - "type": "L1", - "rpc": "https://rpc.ankr.com/eth_rinkeby", - "bridgeContract": "0xe396721BF9FD7c320c3c528077428847c4940C65" - }, - { - "name": "Goerli", - "chainId": "5", - "type": "L1", - "rpc": "https://rpc.ankr.com/eth_goerli", - "bridgeContract": "0x0000000000000000000000000000000000000000" - } - ] -} diff --git a/env.json.example b/env.json.example new file mode 100644 index 0000000..b15c26b --- /dev/null +++ b/env.json.example @@ -0,0 +1,38 @@ +{ + "logLevel": "info", + "maxThreads": 5, + "networkDefaults": { + "privateKey": "0x00000000000000000000000", + "processes": [ + "bounty" + ] + }, + "networks": [ + { + "name": "ArbitriumRinkeby", + "chainId": "421611", + "type": "L2", + "rpc": "https://rinkeby.arbitrum.io/rpc", + "privateKey": "0x0000000000000000000000000000000000000000000000000000000000000001", + "srcContract": "0x9869Fc26826172eB8fB334b39B8D865Be36b01C3", + "destContract": "0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00", + "processes": [ + "bounty" + ] + }, + { + "name": "Rinkeby", + "chainId": "4", + "type": "L1", + "rpc": "https://rpc.ankr.com/eth_rinkeby", + "bridgeContract": "0xe396721BF9FD7c320c3c528077428847c4940C65" + }, + { + "name": "Goerli", + "chainId": "5", + "type": "L1", + "rpc": "https://rpc.ankr.com/eth_goerli", + "bridgeContract": "0x0000000000000000000000000000000000000000" + } + ] +} diff --git a/sentinel/handlers/__init__.py b/sentinel/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/handlers/baseHandler.py b/sentinel/handlers/baseHandler.py new file mode 100644 index 0000000..17a8eac --- /dev/null +++ b/sentinel/handlers/baseHandler.py @@ -0,0 +1,11 @@ +from sentinel.services.buddleNetwork import BuddleNetwork +from sentinel.services.singletonFactory import SingletonFactory + + +class BaseHandler: + def __init__(self, singletonFactory: SingletonFactory): + self.singletonFactory = singletonFactory + + # TODO: eventData should be a generic type for all possible events + def handleEvent(self, eventData): + pass diff --git a/sentinel/handlers/bountyHandler.py b/sentinel/handlers/bountyHandler.py new file mode 100644 index 0000000..1122172 --- /dev/null +++ b/sentinel/handlers/bountyHandler.py @@ -0,0 +1,9 @@ +from sentinel.handlers.baseHandler import BaseHandler +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.types.configTypes import Network + + +class BountyHandler(BaseHandler): + def __init__(self, singletonFactory: SingletonFactory, network: Network): + super().__init__(singletonFactory) + pass diff --git a/sentinel/handlers/liquidityHandler.py b/sentinel/handlers/liquidityHandler.py new file mode 100644 index 0000000..56230e1 --- /dev/null +++ b/sentinel/handlers/liquidityHandler.py @@ -0,0 +1,10 @@ +from sentinel.handlers.baseHandler import BaseHandler +from sentinel.services.singletonFactory import SingletonFactory + + +class LiquidityHandler(BaseHandler): + def __init__(self, singletonFactory: SingletonFactory): + super().__init__(singletonFactory) + + def handleEvent(self, eventData): + pass diff --git a/sentinel/listener.py b/sentinel/listener.py index bd928bd..41144c5 100644 --- a/sentinel/listener.py +++ b/sentinel/listener.py @@ -1,10 +1,21 @@ -class Listener(object): - def __init__(self, name, callback): +from sentinel.handlers.baseHandler import BaseHandler +from sentinel.types.configTypes import Network +from sentinel.utils.threadable import Threadable + + +class Listener(Threadable): + def __init__(self, network: Network, handler: BaseHandler): self.name = name - self.callback = callback + self.handler = handler + + def startThread(self): + pass def __str__(self): return self.name def __call__(self, *args, **kwargs): return self.callback(*args, **kwargs) + + def processEvent(self): + self.handler.handleEvent([]) diff --git a/sentinel/sentinel.py b/sentinel/sentinel.py index fbfeb89..2569549 100644 --- a/sentinel/sentinel.py +++ b/sentinel/sentinel.py @@ -1,5 +1,3 @@ -import logging - from sentinel.services.configManager import ConfigManager from sentinel.services.logManager import BuddleLogger from sentinel.services.singletonFactory import SingletonFactory diff --git a/sentinel/services/buddleNetwork.py b/sentinel/services/buddleNetwork.py index ef14c4b..219a49a 100644 --- a/sentinel/services/buddleNetwork.py +++ b/sentinel/services/buddleNetwork.py @@ -1,13 +1,16 @@ -from web3 import Web3 import json -from web3.types import BlockData -from web3.contract import Contract + +from eth_account.account import LocalAccount from eth_account.datastructures import SignedTransaction -from sentinel.services.singletonFactory import SingletonFactory from sentinel.services.configManager import ConfigManager +from sentinel.services.singletonFactory import SingletonFactory from sentinel.services.transactionManager import TransactionManager +from sentinel.types.configTypes import Network from sentinel.types.enums import BuddleContract from sentinel.types.transferData import TransferData +from web3 import Web3 +from web3.contract import Contract +from web3.types import BlockData class BuddleNetwork: @@ -16,15 +19,16 @@ def __init__(self, singletonFactory: SingletonFactory, chainID: int) -> None: self.connected = False self.w3 = None self.contracts: dict[str, Contract] = {} - self._account = None + self._account: LocalAccount = None self.configService: ConfigManager = singletonFactory.getService(ConfigManager) self.transactionService: TransactionManager = singletonFactory.getService( TransactionManager ) + self.network: Network = self.configService.config.getNetwork(self.chainId) def connect(self) -> bool: if not self.w3.isConnected(): - self.rpcUrl = self.configService.getRpcUrl(self.chainId) + self.rpcUrl = self.network.rpc self.w3 = Web3(Web3.HTTPProvider(self.rpcUrl)) self.connected = True @@ -43,9 +47,7 @@ def getContract(self, buddleContract: BuddleContract) -> Contract: contract = self.contracts.get(buddleContract.name) if not contract: contract = self.w3.eth.contract( - address=self.configService.getContractAddress( - buddleContract, self.chainId - ), + address=self.network.getContractAddress(buddleContract), abi=self.readAbi(buddleContract), ) self.contracts[buddleContract.name] = contract @@ -53,13 +55,14 @@ def getContract(self, buddleContract: BuddleContract) -> Contract: return contract @property - def account(self): + def account(self) -> LocalAccount: if not self._account: - privateKey = self.configService.getPrivateKey(self.chainId) - self.account = self.w3.eth.account.privateKeyToAccount(privateKey) + privateKey = self.network.privateKey + self._account = self.w3.eth.account.privateKeyToAccount(privateKey) - return self.account + return self._account + # TODO: Relocate def depositOnDestination( self, transferData: TransferData, transferId: int, chainId: int ) -> SignedTransaction: diff --git a/sentinel/services/configManager.py b/sentinel/services/configManager.py index 439d0ea..4be2482 100644 --- a/sentinel/services/configManager.py +++ b/sentinel/services/configManager.py @@ -15,7 +15,7 @@ class ConfigManager: def __init__(self, singletonFactory: SingletonFactory) -> None: self.logger = singletonFactory.getService(BuddleLogger) - self.config: BuddleConfig = None + self._config: BuddleConfig = None self.configDict = {} if not self.readConfigFile(): raise RequiredConfigMissingException("Error reading config file") @@ -26,13 +26,17 @@ def readConfigFile(self) -> bool: try: with open(configPath) as configFile: self.configDict = json.load(configFile) - self.config = BuddleConfig(self.configDict) + self._config = BuddleConfig(self.configDict) except Exception: self.logger.exception("Error reading config file") return False return True + @property + def config(self) -> BuddleConfig: + return self._config + def getPrivateKey(self, chainId: int) -> str: key = os.environ.get(f"PRIVATE_KEY_{chainId}") if not key: @@ -40,18 +44,6 @@ def getPrivateKey(self, chainId: int) -> str: return key - def getRpcUrl(self, chainId: int) -> str: - return os.environ.get(f"RPC_URL_{chainId}") - - def getContractAddress(self, buddleContract: BuddleContract, chainId: int) -> str: - value = os.environ.get(f"CONTRACT_ADDRESS_{buddleContract.name}_{chainId}") - - # Bridge contract for each chain MUST have a valid address in config as it is unique - if not value and buddleContract != BuddleContract.BUDDLE_BRIDGE: - value = os.environ.get(f"CONTRACT_ADDRESS_{buddleContract.name}_DEFAULT") - - return value - def getEnv(self, key: str) -> str: print("Type: ", type(os.environ.get(key))) return self.configDict[key] diff --git a/sentinel/services/threadManager.py b/sentinel/services/threadManager.py index c0368de..394c724 100644 --- a/sentinel/services/threadManager.py +++ b/sentinel/services/threadManager.py @@ -1,7 +1,87 @@ -import logging +from concurrent.futures import ThreadPoolExecutor, Future +from threading import Event, Lock +from typing import Any + +from sentinel.services.configManager import ConfigManager +from sentinel.services.logManager import BuddleLogger +from sentinel.services.singletonFactory import SingletonFactory + + +# To signal the thread and gracefully shutdown +class Signal: + def __init__(self): + self._signalled: bool = False + + @property + def signalled(self) -> bool: + return self._signalled + + def signal(self): + self.signalled = True + + +class BuddleThread: + def __init__(self): + self.id: int = id + self.threadFuture: Future = None + self.event: Event = Event() class ThreadManager: - def __init__(self) -> None: - self.logger = logging.getLogger(__name__) - pass + def __init__(self, singletonFactory: SingletonFactory) -> None: + self.logger = singletonFactory.getService(BuddleLogger) + self.configService = singletonFactory.getService(ConfigManager) + self.maxThreads = self.configService.config.maxThreads + self.criticalSection = Lock() + self.threadPool = ThreadPoolExecutor(self.maxThreads) + # TODO: Maintain own thread pool. Use simple thread objects instead of submitting to threadPool as + # threadPool threads cannot be forcefully shut down. + self.threads: dict[int, BuddleThread] = {} + self.maxThreadId = 1 + + def startThread(self, startFunc: function, *args, **kwargs) -> int: + threadId = -1 + + with self.criticalSection: + threadId = self.maxThreadId + self.maxThreadId += 1 + + newThread = BuddleThread() + try: + threadFuture = self.threadPool.submit( + startFunc, newThread.event, *args, **kwargs + ) + newThread.id = threadId + newThread.threadFuture = threadFuture + self.threads[threadId] = newThread + except Exception: + self.logger.exception( + "Thread could not be started for function [%s]", startFunc.__name__ + ) + raise + + return threadId + + def stopThread(self, threadId: int, timeout: int = -1) -> bool: + curThread: BuddleThread = None + with self.criticalSection: + curThread = self.threads.get(threadId, None) + + if curThread is None: + self.logger.warn("Thread ID [%d] does not exist, nothing to do", threadId) + return True + + threadFuture = curThread.threadFuture + if threadFuture.done() or threadFuture.cancelled(): + self.logger.debug( + "Thread ID [%d] is already complete or cancelled", threadId + ) + + with self.criticalSection: + threadFuture.cancel() + curThread.event.set() + + if timeout > -1: + threadFuture.result(timeout=timeout) + # TODO: Join the thread with timeout and stop it otherwise + # Log if thread did not exit gracefully diff --git a/sentinel/services/web3Manager.py b/sentinel/services/web3Manager.py index 31a94c9..82682d3 100644 --- a/sentinel/services/web3Manager.py +++ b/sentinel/services/web3Manager.py @@ -1,3 +1,4 @@ +from sentinel.services.buddleNetwork import BuddleNetwork from sentinel.services.configManager import ConfigManager from sentinel.services.singletonFactory import SingletonFactory from web3 import Web3 @@ -5,14 +6,24 @@ class Web3Manager: def __init__(self, singletonFactory: SingletonFactory) -> None: + self.networks: dict[int, BuddleNetwork] = {} self.web3Instances: dict[int, Web3] = {} + self.singletonFactory: SingletonFactory = singletonFactory self.configService: ConfigManager = singletonFactory.getService(ConfigManager) + def getNetwork(self, chainId: int) -> BuddleNetwork: + buddleNetwork = self.networks.get(chainId) + if not buddleNetwork: + buddleNetwork = BuddleNetwork(self.singletonFactory, chainId) + self.networks[chainId] = buddleNetwork + + return buddleNetwork + def getInstance(self, chainId: int) -> Web3: web3Instance = self.web3Instances.get(chainId) if not web3Instance: web3Instance = Web3( - Web3.HTTPProvider(self.configService.getRpcUrl(chainId)) + Web3.HTTPProvider(self.configService.config.getNetwork(chainId).rpc) ) self.web3Instances[chainId] = web3Instance return web3Instance diff --git a/sentinel/types/configTypes.py b/sentinel/types/configTypes.py index 30f5645..5b7086f 100644 --- a/sentinel/types/configTypes.py +++ b/sentinel/types/configTypes.py @@ -1,9 +1,4 @@ -class BuddleConfig: - def __init__(self, config: dict): - self.defaults = Defaults(config.get("networkDefaults")) - self.networks: list[Network] = [ - Network(network, self.defaults) for network in config.get("networks") - ] +from sentinel.types.enums import BuddleContract class Defaults: @@ -26,3 +21,29 @@ def __init__(self, network: dict, defaults: Defaults): self.destContract: str = network.get("destContract") self.bridgeContract: str = network.get("bridgeContract") self.processes: list[str] = network.get("processes", defaults.processes) + + def getContractAddress(self, buddleContract: BuddleContract): + if buddleContract == BuddleContract.SOURCE: + return self.srcContract + elif buddleContract == BuddleContract.DESTINATION: + return self.destContract + elif buddleContract == BuddleContract.BRIDGE: + return self.bridgeContract + + return None + + +class BuddleConfig: + def __init__(self, config: dict): + self.defaults = Defaults(config.get("networkDefaults")) + self.networks: list[Network] = [ + Network(network, self.defaults) for network in config.get("networks") + ] + self.networksDict: dict[int, Network] = { + network.chainId: network for network in self.networks + } + self.logLevel: str = config.get("logLevel", "info") + self.maxThreads: int = config.get("maxThreads", 10) + + def getNetwork(self, chainId: int) -> Network: + return self.networksDict.get(chainId) diff --git a/sentinel/utils/threadable.py b/sentinel/utils/threadable.py new file mode 100644 index 0000000..6e484b4 --- /dev/null +++ b/sentinel/utils/threadable.py @@ -0,0 +1,50 @@ +# from multiprocessing import Event +# from concurrent.futures import Future + +# # https://docs.python.org/3/library/concurrent.futures.html + +# ''' +# class ThreadManager: +# self.threadSignals = {} + +# @threaded +# def listenEvent(self, signal, *args): +# while not signal.stop: +# self.doStuff(*args) + +# print("thread signalled") +# self.cleanup() +# print("thread exited gracefully") +# ''' + +# class Threadable: +# def __init__(self): +# self.threadStopped = False +# self.threadEvent = Event() + +# def startThread(self): +# thread = Thread(target=self.loop) +# thread.daemon = True +# thread.start() +# return thread + +# def stopThread(self): +# self.threadEvent.set() +# self.threadStopped = True + +# def loop(self): +# while not self.threadStopped: + +# self.threadEvent.wait(1) # Wait 1 seconds, returns bool of event set +# self.threadEvent.clear() # Reset event + +# self.processEvent() +# else: +# self.processStop() + +# def processEvent(self): +# """ This function must be overwritten """ +# pass + +# def processStop(self): +# pass