From 5230c8cbfd413603195789c4a52ad88b170c0a44 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 26 Jun 2018 12:54:42 -0300 Subject: [PATCH 001/119] Update native-to-erc abi, event and methods --- ...json => ForeignBridgeNativeToErc.abi.json} | 283 +++++-------- ...bi.json => HomeBridgeNativeToErc.abi.json} | 390 +++++++++--------- config/collected-signatures-watcher-config.js | 2 +- config/deposit-watcher-config.js | 4 +- config/withdraw-watcher-config.js | 4 +- src/events/processCollectedSignatures.js | 8 +- src/events/processDeposits.js | 2 +- src/events/processWithdraw.js | 6 +- 8 files changed, 321 insertions(+), 378 deletions(-) rename abis/{ForeignBridge.abi.json => ForeignBridgeNativeToErc.abi.json} (86%) rename abis/{HomeBridge.abi.json => HomeBridgeNativeToErc.abi.json} (94%) diff --git a/abis/ForeignBridge.abi.json b/abis/ForeignBridgeNativeToErc.abi.json similarity index 86% rename from abis/ForeignBridge.abi.json rename to abis/ForeignBridgeNativeToErc.abi.json index 70accc7..64d0eb2 100644 --- a/abis/ForeignBridge.abi.json +++ b/abis/ForeignBridgeNativeToErc.abi.json @@ -1,4 +1,63 @@ [ + { + "constant": true, + "inputs": [], + "name": "erc677token", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_txHash", + "type": "bytes32" + } + ], + "name": "relayedMessages", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "vs", + "type": "uint8[]" + }, + { + "name": "rs", + "type": "bytes32[]" + }, + { + "name": "ss", + "type": "bytes32[]" + }, + { + "name": "message", + "type": "bytes" + } + ], + "name": "executeSignatures", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, "inputs": [ @@ -74,6 +133,24 @@ "stateMutability": "view", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "claimTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, "inputs": [], @@ -130,6 +207,33 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "", + "type": "bytes" + } + ], + "name": "onTokenTransfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": false, "inputs": [ @@ -259,14 +363,9 @@ "indexed": false, "name": "value", "type": "uint256" - }, - { - "indexed": false, - "name": "transactionHash", - "type": "bytes32" } ], - "name": "Deposit", + "name": "UserRequestForAffirmation", "type": "event" }, { @@ -281,26 +380,14 @@ "indexed": false, "name": "value", "type": "uint256" - } - ], - "name": "Withdraw", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "gasLimitDepositRelay", - "type": "uint256" }, { "indexed": false, - "name": "gasLimitWithdrawConfirm", - "type": "uint256" + "name": "transactionHash", + "type": "bytes32" } ], - "name": "GasConsumptionLimitsUpdated", + "name": "RelayedMessage", "type": "event" }, { @@ -336,7 +423,7 @@ "type": "uint256" } ], - "name": "DailyLimit", + "name": "DailyLimitChanged", "type": "event" }, { @@ -382,51 +469,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - }, - { - "name": "", - "type": "bytes" - } - ], - "name": "onTokenTransfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_to", - "type": "address" - } - ], - "name": "claimTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": false, "inputs": [ @@ -444,110 +486,5 @@ "payable": false, "stateMutability": "nonpayable", "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "gasLimitDepositRelay", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "gasLimitWithdrawConfirm", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "erc677token", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_gasLimitDepositRelay", - "type": "uint256" - }, - { - "name": "_gasLimitWithdrawConfirm", - "type": "uint256" - } - ], - "name": "setGasLimits", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "vs", - "type": "uint8[]" - }, - { - "name": "rs", - "type": "bytes32[]" - }, - { - "name": "ss", - "type": "bytes32[]" - }, - { - "name": "message", - "type": "bytes" - } - ], - "name": "deposit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_withdraw", - "type": "bytes32" - } - ], - "name": "deposits", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" } -] \ No newline at end of file +] diff --git a/abis/HomeBridge.abi.json b/abis/HomeBridgeNativeToErc.abi.json similarity index 94% rename from abis/HomeBridge.abi.json rename to abis/HomeBridgeNativeToErc.abi.json index f478152..f5f8b18 100644 --- a/abis/HomeBridge.abi.json +++ b/abis/HomeBridgeNativeToErc.abi.json @@ -1,4 +1,46 @@ [ + { + "constant": true, + "inputs": [ + { + "name": "_message", + "type": "bytes32" + } + ], + "name": "numMessagesSigned", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "_index", + "type": "uint256" + } + ], + "name": "signature", + "outputs": [ + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [ @@ -60,6 +102,43 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "message", + "outputs": [ + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "signature", + "type": "bytes" + }, + { + "name": "message", + "type": "bytes" + } + ], + "name": "submitSignature", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, "inputs": [], @@ -74,6 +153,62 @@ "stateMutability": "view", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "claimTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_withdrawal", + "type": "bytes32" + } + ], + "name": "numAffirmationsSigned", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_withdrawal", + "type": "bytes32" + } + ], + "name": "affirmationsSigned", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [], @@ -88,6 +223,25 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [ + { + "name": "_message", + "type": "bytes32" + } + ], + "name": "messagesSigned", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [], @@ -102,6 +256,28 @@ "stateMutability": "view", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "recipient", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "transactionHash", + "type": "bytes32" + } + ], + "name": "executeAffirmation", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, "inputs": [], @@ -248,21 +424,28 @@ "type": "function" }, { - "payable": true, - "stateMutability": "payable", - "type": "fallback" - }, - { - "anonymous": false, + "constant": true, "inputs": [ { - "indexed": false, - "name": "gas", + "name": "_number", "type": "uint256" } ], - "name": "GasConsumptionLimitsUpdated", - "type": "event" + "name": "isAlreadyProcessed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" }, { "anonymous": false, @@ -278,7 +461,7 @@ "type": "uint256" } ], - "name": "Deposit", + "name": "UserRequestForSignature", "type": "event" }, { @@ -300,7 +483,7 @@ "type": "bytes32" } ], - "name": "Withdraw", + "name": "AffirmationCompleted", "type": "event" }, { @@ -317,7 +500,7 @@ "type": "bytes32" } ], - "name": "SignedForDeposit", + "name": "SignedForUserRequest", "type": "event" }, { @@ -334,7 +517,7 @@ "type": "bytes32" } ], - "name": "SignedForWithdraw", + "name": "SignedForAffirmation", "type": "event" }, { @@ -392,7 +575,7 @@ "type": "uint256" } ], - "name": "DailyLimit", + "name": "DailyLimitChanged", "type": "event" }, { @@ -433,182 +616,5 @@ "payable": false, "stateMutability": "nonpayable", "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "recipient", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - }, - { - "name": "transactionHash", - "type": "bytes32" - } - ], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_withdrawal", - "type": "bytes32" - } - ], - "name": "numWithdrawalsSigned", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_withdrawal", - "type": "bytes32" - } - ], - "name": "withdrawalsSigned", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "signature", - "type": "bytes" - }, - { - "name": "message", - "type": "bytes" - } - ], - "name": "submitSignature", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_hash", - "type": "bytes32" - }, - { - "name": "_index", - "type": "uint256" - } - ], - "name": "signature", - "outputs": [ - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_message", - "type": "bytes32" - } - ], - "name": "messagesSigned", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_hash", - "type": "bytes32" - } - ], - "name": "message", - "outputs": [ - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_number", - "type": "uint256" - } - ], - "name": "isAlreadyProcessed", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_message", - "type": "bytes32" - } - ], - "name": "numMessagesSigned", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" } ] diff --git a/config/collected-signatures-watcher-config.js b/config/collected-signatures-watcher-config.js index fd7df0f..e92d015 100644 --- a/config/collected-signatures-watcher-config.js +++ b/config/collected-signatures-watcher-config.js @@ -1,6 +1,6 @@ require('dotenv').config() -const HomeABI = require('../abis/HomeBridge.abi') +const HomeABI = require('../abis/HomeBridgeNativeToErc.abi') module.exports = { event: 'CollectedSignatures', diff --git a/config/deposit-watcher-config.js b/config/deposit-watcher-config.js index 05a4ed2..c227751 100644 --- a/config/deposit-watcher-config.js +++ b/config/deposit-watcher-config.js @@ -1,9 +1,9 @@ require('dotenv').config() -const HomeABI = require('../abis/HomeBridge.abi') +const HomeABI = require('../abis/HomeBridgeNativeToErc.abi') module.exports = { - event: 'Deposit', + event: 'UserRequestForSignature', url: process.env.HOME_RPC_URL, contractAddress: process.env.HOME_BRIDGE_ADDRESS, abi: HomeABI, diff --git a/config/withdraw-watcher-config.js b/config/withdraw-watcher-config.js index 358279e..4c7f666 100644 --- a/config/withdraw-watcher-config.js +++ b/config/withdraw-watcher-config.js @@ -1,9 +1,9 @@ require('dotenv').config() -const ForeignABI = require('../abis/ForeignBridge.abi') +const ForeignABI = require('../abis/ForeignBridgeNativeToErc.abi') module.exports = { - event: 'Withdraw', + event: 'UserRequestForAffirmation', url: process.env.FOREIGN_RPC_URL, contractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, abi: ForeignABI, diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index cdc18f2..87daa7e 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -12,13 +12,13 @@ const { const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridge.abi') +const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) const web3Foreign = new Web3(foreignProvider) -const ForeignABI = require('../../abis/ForeignBridge.abi') +const ForeignABI = require('../../abis/ForeignBridgeNativeToErc.abi') const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS) @@ -51,12 +51,12 @@ async function processCollectedSignatures(signatures) { let gasEstimate try { - gasEstimate = await foreignBridge.methods.deposit(v, r, s, message).estimateGas() + gasEstimate = await foreignBridge.methods.executeSignatures(v, r, s, message).estimateGas() } catch (e) { console.log(indexSig + 1, ' # already processed col sig', colSignature.transactionHash) return } - const data = await foreignBridge.methods.deposit(v, r, s, message).encodeABI() + const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() txToSend.push({ data, gasEstimate, diff --git a/src/events/processDeposits.js b/src/events/processDeposits.js index 7fdd094..abc725f 100644 --- a/src/events/processDeposits.js +++ b/src/events/processDeposits.js @@ -11,7 +11,7 @@ const { const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridge.abi') +const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) diff --git a/src/events/processWithdraw.js b/src/events/processWithdraw.js index dbc718d..048737e 100644 --- a/src/events/processWithdraw.js +++ b/src/events/processWithdraw.js @@ -5,7 +5,7 @@ const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, VALIDATOR_ADDRESS } = process.env const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridge.abi') +const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) @@ -18,7 +18,7 @@ async function processWithdraw(withdrawals) { let gasEstimate try { gasEstimate = await homeBridge.methods - .withdraw(recipient, value, withdrawal.transactionHash) + .executeAffirmation(recipient, value, withdrawal.transactionHash) .estimateGas({ from: VALIDATOR_ADDRESS }) } catch (e) { console.log(index + 1, '# already processed withdrawal', withdrawal.transactionHash) @@ -26,7 +26,7 @@ async function processWithdraw(withdrawals) { } const data = await homeBridge.methods - .withdraw(recipient, value, withdrawal.transactionHash) + .executeAffirmation(recipient, value, withdrawal.transactionHash) .encodeABI({ from: VALIDATOR_ADDRESS }) txToSend.push({ From 3aa1255c18d1ced372a8255f76a3f0a85c39dbce Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 28 Jun 2018 10:27:11 -0300 Subject: [PATCH 002/119] Update files names --- Dockerfile | 4 ++-- README.md | 18 ++++++++--------- ... => affirmation-request-watcher-config.js} | 2 +- ...js => signature-request-watcher-config.js} | 2 +- e2e/Dockerfile | 2 +- e2e/envs/contracts-deploy.env | 1 + e2e/run-tests.sh | 4 ++-- e2e/test/transactions.js | 2 +- package.json | 4 ++-- ...hdraw.js => processAffirmationRequests.js} | 20 +++++++++++-------- ...eposits.js => processSignatureRequests.js} | 18 ++++++++++------- src/watcher.js | 12 +++++------ 12 files changed, 49 insertions(+), 40 deletions(-) rename config/{withdraw-watcher-config.js => affirmation-request-watcher-config.js} (92%) rename config/{deposit-watcher-config.js => signature-request-watcher-config.js} (92%) rename src/events/{processWithdraw.js => processAffirmationRequests.js} (55%) rename src/events/{processDeposits.js => processSignatureRequests.js} (69%) diff --git a/Dockerfile b/Dockerfile index f0cc8bd..ca692c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,8 @@ WORKDIR /bridge COPY . . RUN npm install CMD echo "To start a bridge process run:" \ - "docker-compose run bridge npm run watcher:deposit" \ + "docker-compose run bridge npm run watcher:signature-request" \ "docker-compose run bridge npm run watcher:collected-signatures" \ - "docker-compose run bridge npm run watcher:withdraw" \ + "docker-compose run bridge npm run watcher:affirmation-request" \ "docker-compose run bridge npm run sender:home" \ "docker-compose run bridge npm run sender:foreign" diff --git a/README.md b/README.md index ba49413..148ea6b 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ A watcher listens for a certain event and creates proper jobs in the queue. These jobs contain the transaction data (without the nonce) and the transaction hash for the related event. The watcher runs on a given frequency, keeping track of the last processed block. There are three Watchers: -- Deposit Watcher: Listens to `Deposit` events on Home network. +- Signature Request Watcher: Listens to `UserRequestForSignature` events on Home network. - Collected Signatures Watcher: Listens to `CollectedSignatures` events on Home network. -- Withdraw Watcher: Listens to `Withdraw` events on Foreign network. +- Affirmation Request Watcher: Listens to `UserRequestForAffirmation` events on Foreign network. ### Sender A sender subscribes to the queue and keeps track of the nonce. It takes jobs from the queue, extract transaction data, adds proper nonce and sends it to the network. @@ -35,18 +35,18 @@ There are two Senders: 3. Create a `.env` file: `cp .env.example .env` and fill in the information using the output data from previous deploy step. Check the `.env.example` file to see the required variables. 4. To run the processes: - - `npm run watcher:deposit` + - `npm run watcher:signature-request` - `npm run watcher:collected-signatures` - - `npm run watcher:withdraw` + - `npm run watcher:affirmation-request` - `npm run sender:home` - `npm run sender:foreign` ### Run with Docker - Start RabbitMQ and Redis: `docker-compose up -d` - - `docker-compose run bridge npm run watcher:deposit` + - `docker-compose run bridge npm run watcher:signature-request` - `docker-compose run bridge npm run watcher:collected-signatures` - - `docker-compose run bridge npm run watcher:withdraw` + - `docker-compose run bridge npm run watcher:affirmation-request` - `docker-compose run bridge npm run sender:home` - `docker-compose run bridge npm run sender:foreign` @@ -76,9 +76,9 @@ Use `redis-cli` Command | Description --- | --- `KEYS *` | Returns all keys -`SET deposit:lastProcessedBlock 1234` | Set key to hold the string value. -`GET deposit:lastProcessedBlock` | Get the value of key. -`DEL deposit:lastProcessedBlock` | Removes the specified key. +`SET signature-request:lastProcessedBlock 1234` | Set key to hold the string value. +`GET signature-request:lastProcessedBlock` | Get the value of key. +`DEL signature-request:lastProcessedBlock` | Removes the specified key. `FLUSHALL` | Delete all the keys of all the existing databases diff --git a/config/withdraw-watcher-config.js b/config/affirmation-request-watcher-config.js similarity index 92% rename from config/withdraw-watcher-config.js rename to config/affirmation-request-watcher-config.js index 4c7f666..4f798cd 100644 --- a/config/withdraw-watcher-config.js +++ b/config/affirmation-request-watcher-config.js @@ -8,6 +8,6 @@ module.exports = { contractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, abi: ForeignABI, queue: 'home', - id: 'withdraw', + id: 'affirmation-request', pollingInterval: process.env.FOREIGN_POLLING_INTERVAL } diff --git a/config/deposit-watcher-config.js b/config/signature-request-watcher-config.js similarity index 92% rename from config/deposit-watcher-config.js rename to config/signature-request-watcher-config.js index c227751..3afe70a 100644 --- a/config/deposit-watcher-config.js +++ b/config/signature-request-watcher-config.js @@ -8,6 +8,6 @@ module.exports = { contractAddress: process.env.HOME_BRIDGE_ADDRESS, abi: HomeABI, queue: 'home', - id: 'deposit', + id: 'signature-request', pollingInterval: process.env.HOME_POLLING_INTERVAL } diff --git a/e2e/Dockerfile b/e2e/Dockerfile index c0e849a..6df8553 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -10,7 +10,7 @@ RUN git clone https://github.com/poanetwork/poa-bridge-contracts.git RUN mkdir submodules && \ mv poa-bridge-contracts submodules && \ cd submodules/poa-bridge-contracts && \ - git checkout v2 + git checkout refactor_v1 RUN npm install --unsafe-perm diff --git a/e2e/envs/contracts-deploy.env b/e2e/envs/contracts-deploy.env index d1eb985..ed7f46a 100644 --- a/e2e/envs/contracts-deploy.env +++ b/e2e/envs/contracts-deploy.env @@ -1,3 +1,4 @@ +BRIDGE_MODE=NATIVE_TO_ERC DEPLOYMENT_ACCOUNT_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 DEPLOYMENT_GAS_LIMIT=4000000 diff --git a/e2e/run-tests.sh b/e2e/run-tests.sh index 9c3a1d8..8ba574f 100755 --- a/e2e/run-tests.sh +++ b/e2e/run-tests.sh @@ -1,8 +1,8 @@ docker-compose up -d --build docker-compose run e2e npm run deploy -docker-compose run -d bridge npm run watcher:deposit +docker-compose run -d bridge npm run watcher:signature-request docker-compose run -d bridge npm run watcher:collected-signatures -docker-compose run -d bridge npm run watcher:withdraw +docker-compose run -d bridge npm run watcher:affirmation-request docker-compose run -d bridge npm run sender:home docker-compose run -d bridge npm run sender:foreign docker-compose run e2e npm start diff --git a/e2e/test/transactions.js b/e2e/test/transactions.js index f3bad81..b4ff220 100644 --- a/e2e/test/transactions.js +++ b/e2e/test/transactions.js @@ -18,7 +18,7 @@ const { toBN } = foreignWeb3.utils homeWeb3.eth.accounts.wallet.add(user.privateKey) foreignWeb3.eth.accounts.wallet.add(user.privateKey) -const tokenAbi = require(path.join(abisDir, 'POA20.json')).abi +const tokenAbi = require(path.join(abisDir, 'ERC677BridgeToken.json')).abi const token = new foreignWeb3.eth.Contract(tokenAbi, '0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B') describe('transactions', () => { diff --git a/package.json b/package.json index e5c5bf2..dd43a80 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,9 @@ "main": "index.js", "scripts": { "lint": "eslint .", - "watcher:deposit": "node src/watcher.js deposit-watcher-config.js", + "watcher:signature-request": "node src/watcher.js signature-request-watcher-config.js", "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher-config.js", - "watcher:withdraw": "node src/watcher.js withdraw-watcher-config.js", + "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher-config.js", "sender:home": "node src/sender.js home-sender-config.js", "sender:foreign": "node src/sender.js foreign-sender-config.js" }, diff --git a/src/events/processWithdraw.js b/src/events/processAffirmationRequests.js similarity index 55% rename from src/events/processWithdraw.js rename to src/events/processAffirmationRequests.js index 048737e..dd0a843 100644 --- a/src/events/processWithdraw.js +++ b/src/events/processAffirmationRequests.js @@ -9,30 +9,34 @@ const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) -async function processWithdraw(withdrawals) { +async function processAffirmationRequests(affirmationRequests) { const txToSend = [] - const callbacks = withdrawals.map(async (withdrawal, index) => { - const { recipient, value } = withdrawal.returnValues + const callbacks = affirmationRequests.map(async (affirmationRequest, index) => { + const { recipient, value } = affirmationRequest.returnValues let gasEstimate try { gasEstimate = await homeBridge.methods - .executeAffirmation(recipient, value, withdrawal.transactionHash) + .executeAffirmation(recipient, value, affirmationRequest.transactionHash) .estimateGas({ from: VALIDATOR_ADDRESS }) } catch (e) { - console.log(index + 1, '# already processed withdrawal', withdrawal.transactionHash) + console.log( + index + 1, + '# already processed UserRequestForAffirmation', + affirmationRequest.transactionHash + ) return } const data = await homeBridge.methods - .executeAffirmation(recipient, value, withdrawal.transactionHash) + .executeAffirmation(recipient, value, affirmationRequest.transactionHash) .encodeABI({ from: VALIDATOR_ADDRESS }) txToSend.push({ data, gasEstimate, - transactionReference: withdrawal.transactionHash + transactionReference: affirmationRequest.transactionHash }) }) @@ -40,4 +44,4 @@ async function processWithdraw(withdrawals) { return txToSend } -module.exports = processWithdraw +module.exports = processAffirmationRequests diff --git a/src/events/processDeposits.js b/src/events/processSignatureRequests.js similarity index 69% rename from src/events/processDeposits.js rename to src/events/processSignatureRequests.js index abc725f..7849791 100644 --- a/src/events/processDeposits.js +++ b/src/events/processSignatureRequests.js @@ -15,16 +15,16 @@ const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) -async function processDeposits(deposits) { +async function processSignatureRequests(signatureRequests) { const txToSend = [] - const callbacks = deposits.map(async (deposit, index) => { - const { recipient, value } = deposit.returnValues + const callbacks = signatureRequests.map(async (signatureRequest, index) => { + const { recipient, value } = signatureRequest.returnValues const message = createMessage({ recipient, value, - transactionHash: deposit.transactionHash + transactionHash: signatureRequest.transactionHash }) const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) @@ -35,7 +35,11 @@ async function processDeposits(deposits) { .submitSignature(signature.signature, message) .estimateGas({ from: VALIDATOR_ADDRESS }) } catch (e) { - console.log(index + 1, '# already processed deposit ', deposit.transactionHash) + console.log( + index + 1, + '# already processed UserRequestForSignature ', + signatureRequest.transactionHash + ) return } @@ -46,7 +50,7 @@ async function processDeposits(deposits) { txToSend.push({ data, gasEstimate, - transactionReference: deposit.transactionHash + transactionReference: signatureRequest.transactionHash }) }) @@ -54,4 +58,4 @@ async function processDeposits(deposits) { return txToSend } -module.exports = processDeposits +module.exports = processSignatureRequests diff --git a/src/watcher.js b/src/watcher.js index 46aaa5d..bc8e8fe 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -3,9 +3,9 @@ const path = require('path') const Web3 = require('web3') const { connectWatcherToQueue } = require('./services/amqpClient') const { getBlockNumber } = require('./tx/web3') -const processDeposits = require('./events/processDeposits') +const processSignatureRequests = require('./events/processSignatureRequests') const processCollectedSignatures = require('./events/processCollectedSignatures') -const processWithdraw = require('./events/processWithdraw') +const processAffirmationRequests = require('./events/processAffirmationRequests') const { redis } = require('./services/redisClient') const { getRequiredBlockConfirmations } = require('./tx/web3') const { checkHTTPS } = require('./utils/utils') @@ -67,12 +67,12 @@ function updateLastProcessedBlock(lastBlockNumber) { function processEvents(events) { switch (config.id) { - case 'deposit': - return processDeposits(events) + case 'signature-request': + return processSignatureRequests(events) case 'collected-signatures': return processCollectedSignatures(events) - case 'withdraw': - return processWithdraw(events) + case 'affirmation-request': + return processAffirmationRequests(events) default: return [] } From 57ecd75958239e0dc27c671ebc0cd9a034ace75f Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Fri, 29 Jun 2018 10:22:12 -0300 Subject: [PATCH 003/119] Update image on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 148ea6b..f68a985 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Architecture -![bridge architecture](https://user-images.githubusercontent.com/417134/40630604-4a41c986-62aa-11e8-999e-08dca615532d.png) +![bridge architecture](https://user-images.githubusercontent.com/4614574/42094368-f260f648-7b85-11e8-91d4-e602253a6560.png) ### Watcher A watcher listens for a certain event and creates proper jobs in the queue. These jobs contain the transaction data (without the nonce) and the transaction hash for the related event. The watcher runs on a given frequency, keeping track of the last processed block. From 20c511b0982b488c2bea7a3aeecce65c626dee0c Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 2 Jul 2018 16:01:39 -0300 Subject: [PATCH 004/119] Add erc to erc abi files --- abis/ERC20.abi.json | 175 ++++++++ abis/ForeignBridgeErcToErc.abi.json | 409 +++++++++++++++++ abis/HomeBridgeErcToErc.abi.json | 665 ++++++++++++++++++++++++++++ 3 files changed, 1249 insertions(+) create mode 100644 abis/ERC20.abi.json create mode 100644 abis/ForeignBridgeErcToErc.abi.json create mode 100644 abis/HomeBridgeErcToErc.abi.json diff --git a/abis/ERC20.abi.json b/abis/ERC20.abi.json new file mode 100644 index 0000000..49cac88 --- /dev/null +++ b/abis/ERC20.abi.json @@ -0,0 +1,175 @@ +[ + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "who", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abis/ForeignBridgeErcToErc.abi.json b/abis/ForeignBridgeErcToErc.abi.json new file mode 100644 index 0000000..604eef2 --- /dev/null +++ b/abis/ForeignBridgeErcToErc.abi.json @@ -0,0 +1,409 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "_txHash", + "type": "bytes32" + } + ], + "name": "relayedMessages", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "vs", + "type": "uint8[]" + }, + { + "name": "rs", + "type": "bytes32[]" + }, + { + "name": "ss", + "type": "bytes32[]" + }, + { + "name": "message", + "type": "bytes" + } + ], + "name": "executeSignatures", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_day", + "type": "uint256" + } + ], + "name": "totalSpentPerDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isInitialized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "requiredBlockConfirmations", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "dailyLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "requiredSignatures", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "validatorContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "deployedAtBlock", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_minPerTx", + "type": "uint256" + } + ], + "name": "setMinPerTx", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_blockConfirmations", + "type": "uint256" + } + ], + "name": "setRequiredBlockConfirmations", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_dailyLimit", + "type": "uint256" + } + ], + "name": "setDailyLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_gasPrice", + "type": "uint256" + } + ], + "name": "setGasPrice", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxPerTx", + "type": "uint256" + } + ], + "name": "setMaxPerTx", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minPerTx", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "withinLimit", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "maxPerTx", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "gasPrice", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "transactionHash", + "type": "bytes32" + } + ], + "name": "RelayedMessage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "gasPrice", + "type": "uint256" + } + ], + "name": "GasPriceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "requiredBlockConfirmations", + "type": "uint256" + } + ], + "name": "RequiredBlockConfirmationChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newLimit", + "type": "uint256" + } + ], + "name": "DailyLimitChanged", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "_validatorContract", + "type": "address" + }, + { + "name": "_erc20token", + "type": "address" + } + ], + "name": "initialize", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "claimTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "erc20token", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ] + \ No newline at end of file diff --git a/abis/HomeBridgeErcToErc.abi.json b/abis/HomeBridgeErcToErc.abi.json new file mode 100644 index 0000000..6956bc6 --- /dev/null +++ b/abis/HomeBridgeErcToErc.abi.json @@ -0,0 +1,665 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "_message", + "type": "bytes32" + } + ], + "name": "numMessagesSigned", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "_index", + "type": "uint256" + } + ], + "name": "signature", + "outputs": [ + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "erc677token", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_day", + "type": "uint256" + } + ], + "name": "totalSpentPerDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isInitialized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "requiredBlockConfirmations", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "message", + "outputs": [ + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "signature", + "type": "bytes" + }, + { + "name": "message", + "type": "bytes" + } + ], + "name": "submitSignature", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "dailyLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "claimTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_withdrawal", + "type": "bytes32" + } + ], + "name": "numAffirmationsSigned", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_withdrawal", + "type": "bytes32" + } + ], + "name": "affirmationsSigned", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "requiredSignatures", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_message", + "type": "bytes32" + } + ], + "name": "messagesSigned", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "validatorContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "recipient", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "transactionHash", + "type": "bytes32" + } + ], + "name": "executeAffirmation", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "deployedAtBlock", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_minPerTx", + "type": "uint256" + } + ], + "name": "setMinPerTx", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "", + "type": "bytes" + } + ], + "name": "onTokenTransfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_blockConfirmations", + "type": "uint256" + } + ], + "name": "setRequiredBlockConfirmations", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_dailyLimit", + "type": "uint256" + } + ], + "name": "setDailyLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_gasPrice", + "type": "uint256" + } + ], + "name": "setGasPrice", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxPerTx", + "type": "uint256" + } + ], + "name": "setMaxPerTx", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minPerTx", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "withinLimit", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "maxPerTx", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "gasPrice", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_number", + "type": "uint256" + } + ], + "name": "isAlreadyProcessed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "payable": false, + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "UserRequestForSignature", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "transactionHash", + "type": "bytes32" + } + ], + "name": "AffirmationCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "signer", + "type": "address" + }, + { + "indexed": false, + "name": "messageHash", + "type": "bytes32" + } + ], + "name": "SignedForUserRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "signer", + "type": "address" + }, + { + "indexed": false, + "name": "transactionHash", + "type": "bytes32" + } + ], + "name": "SignedForAffirmation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "authorityResponsibleForRelay", + "type": "address" + }, + { + "indexed": false, + "name": "messageHash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "NumberOfCollectedSignatures", + "type": "uint256" + } + ], + "name": "CollectedSignatures", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "gasPrice", + "type": "uint256" + } + ], + "name": "GasPriceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "requiredBlockConfirmations", + "type": "uint256" + } + ], + "name": "RequiredBlockConfirmationChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newLimit", + "type": "uint256" + } + ], + "name": "DailyLimitChanged", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "_validatorContract", + "type": "address" + }, + { + "name": "_dailyLimit", + "type": "uint256" + }, + { + "name": "_maxPerTx", + "type": "uint256" + }, + { + "name": "_minPerTx", + "type": "uint256" + }, + { + "name": "_homeGasPrice", + "type": "uint256" + }, + { + "name": "_requiredBlockConfirmations", + "type": "uint256" + }, + { + "name": "_erc677token", + "type": "address" + } + ], + "name": "initialize", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] From 572c93245f1f7bc4b9eed40a0c95b99aa16c879e Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jul 2018 11:12:27 -0300 Subject: [PATCH 005/119] Refactor config parameters --- config/affirmation-request-watcher-config.js | 11 +- config/base-native-erc-watcher.config.js | 36 ++++++ config/collected-signatures-watcher-config.js | 11 +- config/foreign-sender-config.js | 1 - config/home-sender-config.js | 1 - config/signature-request-watcher-config.js | 11 +- src/events/processAffirmationRequests.js | 79 ++++++------- src/events/processCollectedSignatures.js | 110 +++++++++--------- src/events/processSignatureRequests.js | 100 ++++++++-------- src/sender.js | 2 +- src/watcher.js | 15 ++- 11 files changed, 200 insertions(+), 177 deletions(-) create mode 100644 config/base-native-erc-watcher.config.js diff --git a/config/affirmation-request-watcher-config.js b/config/affirmation-request-watcher-config.js index 4f798cd..a13a9c5 100644 --- a/config/affirmation-request-watcher-config.js +++ b/config/affirmation-request-watcher-config.js @@ -1,13 +1,10 @@ require('dotenv').config() - -const ForeignABI = require('../abis/ForeignBridgeNativeToErc.abi') +const nativeErcConfig = require('./base-native-erc-watcher.config') module.exports = { + ...nativeErcConfig.bridgeConfig, + ...nativeErcConfig.foreignConfig, event: 'UserRequestForAffirmation', - url: process.env.FOREIGN_RPC_URL, - contractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, - abi: ForeignABI, queue: 'home', - id: 'affirmation-request', - pollingInterval: process.env.FOREIGN_POLLING_INTERVAL + id: 'affirmation-request' } diff --git a/config/base-native-erc-watcher.config.js b/config/base-native-erc-watcher.config.js new file mode 100644 index 0000000..7c50f98 --- /dev/null +++ b/config/base-native-erc-watcher.config.js @@ -0,0 +1,36 @@ +require('dotenv').config() + +const homeAbi = require('../abis/HomeBridgeNativeToErc.abi') +const foreignAbi = require('../abis/ForeignBridgeNativeToErc.abi') + +const bridgeConfig = { + homeBridgeAddress: process.env.HOME_BRIDGE_ADDRESS, + homeBridgeAbi: homeAbi, + foreignBridgeAddress: process.env.FOREIGN_BRIDGE_ADDRESS, + foreignBridgeAbi: foreignAbi, + eventFilter: {} +} + +const homeConfig = { + url: process.env.HOME_RPC_URL, + eventContractAddress: process.env.HOME_BRIDGE_ADDRESS, + eventAbi: homeAbi, + bridgeContractAddress: process.env.HOME_BRIDGE_ADDRESS, + bridgeAbi: homeAbi, + pollingInterval: process.env.HOME_POLLING_INTERVAL +} + +const foreignConfig = { + url: process.env.FOREIGN_RPC_URL, + eventContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, + eventAbi: foreignAbi, + bridgeContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, + bridgeAbi: foreignAbi, + pollingInterval: process.env.FOREIGN_POLLING_INTERVAL +} + +module.exports = { + bridgeConfig, + homeConfig, + foreignConfig +} diff --git a/config/collected-signatures-watcher-config.js b/config/collected-signatures-watcher-config.js index e92d015..a4fa23f 100644 --- a/config/collected-signatures-watcher-config.js +++ b/config/collected-signatures-watcher-config.js @@ -1,13 +1,10 @@ require('dotenv').config() - -const HomeABI = require('../abis/HomeBridgeNativeToErc.abi') +const nativeErcConfig = require('./base-native-erc-watcher.config') module.exports = { + ...nativeErcConfig.bridgeConfig, + ...nativeErcConfig.homeConfig, event: 'CollectedSignatures', - url: process.env.HOME_RPC_URL, - contractAddress: process.env.HOME_BRIDGE_ADDRESS, - abi: HomeABI, queue: 'foreign', - id: 'collected-signatures', - pollingInterval: process.env.HOME_POLLING_INTERVAL + id: 'collected-signatures' } diff --git a/config/foreign-sender-config.js b/config/foreign-sender-config.js index 1de7c5f..6789bb8 100644 --- a/config/foreign-sender-config.js +++ b/config/foreign-sender-config.js @@ -2,7 +2,6 @@ require('dotenv').config() module.exports = { url: process.env.FOREIGN_RPC_URL, - contractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, queue: 'foreign', id: 'foreign' } diff --git a/config/home-sender-config.js b/config/home-sender-config.js index b415839..e0e8aac 100644 --- a/config/home-sender-config.js +++ b/config/home-sender-config.js @@ -2,7 +2,6 @@ require('dotenv').config() module.exports = { url: process.env.HOME_RPC_URL, - contractAddress: process.env.HOME_BRIDGE_ADDRESS, queue: 'home', id: 'home' } diff --git a/config/signature-request-watcher-config.js b/config/signature-request-watcher-config.js index 3afe70a..c28ffef 100644 --- a/config/signature-request-watcher-config.js +++ b/config/signature-request-watcher-config.js @@ -1,13 +1,10 @@ require('dotenv').config() - -const HomeABI = require('../abis/HomeBridgeNativeToErc.abi') +const nativeErcConfig = require('./base-native-erc-watcher.config') module.exports = { + ...nativeErcConfig.bridgeConfig, + ...nativeErcConfig.homeConfig, event: 'UserRequestForSignature', - url: process.env.HOME_RPC_URL, - contractAddress: process.env.HOME_BRIDGE_ADDRESS, - abi: HomeABI, queue: 'home', - id: 'signature-request', - pollingInterval: process.env.HOME_POLLING_INTERVAL + id: 'signature-request' } diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js index dd0a843..df68fcb 100644 --- a/src/events/processAffirmationRequests.js +++ b/src/events/processAffirmationRequests.js @@ -1,47 +1,48 @@ require('dotenv').config() const Web3 = require('web3') -const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, VALIDATOR_ADDRESS } = process.env - -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) -const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') - -const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) - -async function processAffirmationRequests(affirmationRequests) { - const txToSend = [] - - const callbacks = affirmationRequests.map(async (affirmationRequest, index) => { - const { recipient, value } = affirmationRequest.returnValues - - let gasEstimate - try { - gasEstimate = await homeBridge.methods +const { HOME_RPC_URL, VALIDATOR_ADDRESS } = process.env + +function processAffirmationRequestsBuilder(config) { + const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const web3Home = new Web3(homeProvider) + const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + + return async function processAffirmationRequests(affirmationRequests) { + const txToSend = [] + + const callbacks = affirmationRequests.map(async (affirmationRequest, index) => { + const { recipient, value } = affirmationRequest.returnValues + + let gasEstimate + try { + gasEstimate = await homeBridge.methods + .executeAffirmation(recipient, value, affirmationRequest.transactionHash) + .estimateGas({ from: VALIDATOR_ADDRESS }) + } catch (e) { + console.log( + index + 1, + '# already processed UserRequestForAffirmation', + affirmationRequest.transactionHash + ) + return + } + + const data = await homeBridge.methods .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - console.log( - index + 1, - '# already processed UserRequestForAffirmation', - affirmationRequest.transactionHash - ) - return - } - - const data = await homeBridge.methods - .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: affirmationRequest.transactionHash + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: affirmationRequest.transactionHash, + to: config.homeBridgeAddress + }) }) - }) - await Promise.all(callbacks) - return txToSend + await Promise.all(callbacks) + return txToSend + } } -module.exports = processAffirmationRequests +module.exports = processAffirmationRequestsBuilder diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index 87daa7e..b583158 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -2,72 +2,70 @@ require('dotenv').config() const Web3 = require('web3') const { signatureToVRS } = require('../utils/message') -const { - HOME_RPC_URL, - FOREIGN_RPC_URL, - FOREIGN_BRIDGE_ADDRESS, - HOME_BRIDGE_ADDRESS, - VALIDATOR_ADDRESS -} = process.env +const { HOME_RPC_URL, FOREIGN_RPC_URL, VALIDATOR_ADDRESS } = process.env -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) -const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') +function processCollectedSignaturesBuilder(config) { + const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const web3Home = new Web3(homeProvider) + const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) -const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) + const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) + const web3Foreign = new Web3(foreignProvider) + const foreignBridge = new web3Foreign.eth.Contract( + config.foreignBridgeAbi, + config.foreignBridgeAddress + ) -const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) -const web3Foreign = new Web3(foreignProvider) -const ForeignABI = require('../../abis/ForeignBridgeNativeToErc.abi') + return async function processCollectedSignatures(signatures) { + const txToSend = [] + const callbacks = signatures.map(async (colSignature, indexSig) => { + const { + authorityResponsibleForRelay, + messageHash, + NumberOfCollectedSignatures + } = colSignature.returnValues -const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS) + if (authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(VALIDATOR_ADDRESS)) { + const message = await homeBridge.methods.message(messageHash).call() -async function processCollectedSignatures(signatures) { - const txToSend = [] - const callbacks = signatures.map(async (colSignature, indexSig) => { - const { - authorityResponsibleForRelay, - messageHash, - NumberOfCollectedSignatures - } = colSignature.returnValues + const requiredSignatures = [] + requiredSignatures.length = NumberOfCollectedSignatures + requiredSignatures.fill(0) - if (authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(VALIDATOR_ADDRESS)) { - const message = await homeBridge.methods.message(messageHash).call() + const [v, r, s] = [[], [], []] + const signaturePromises = requiredSignatures.map(async (el, index) => { + const signature = await homeBridge.methods.signature(messageHash, index).call() + const recover = signatureToVRS(signature) + v.push(recover.v) + r.push(recover.r) + s.push(recover.s) + }) - const requiredSignatures = [] - requiredSignatures.length = NumberOfCollectedSignatures - requiredSignatures.fill(0) + await Promise.all(signaturePromises) - const [v, r, s] = [[], [], []] - const signaturePromises = requiredSignatures.map(async (el, index) => { - const signature = await homeBridge.methods.signature(messageHash, index).call() - const recover = signatureToVRS(signature) - v.push(recover.v) - r.push(recover.r) - s.push(recover.s) - }) - - await Promise.all(signaturePromises) - - let gasEstimate - try { - gasEstimate = await foreignBridge.methods.executeSignatures(v, r, s, message).estimateGas() - } catch (e) { - console.log(indexSig + 1, ' # already processed col sig', colSignature.transactionHash) - return + let gasEstimate + try { + gasEstimate = await foreignBridge.methods + .executeSignatures(v, r, s, message) + .estimateGas() + } catch (e) { + console.log(indexSig + 1, ' # already processed col sig', colSignature.transactionHash) + return + } + const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() + txToSend.push({ + data, + gasEstimate, + transactionReference: colSignature.transactionHash, + to: config.foreignBridgeAddress + }) } - const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() - txToSend.push({ - data, - gasEstimate, - transactionReference: colSignature.transactionHash - }) - } - }) + }) - await Promise.all(callbacks) + await Promise.all(callbacks) - return txToSend + return txToSend + } } -module.exports = processCollectedSignatures +module.exports = processCollectedSignaturesBuilder diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 7849791..a426272 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -2,60 +2,56 @@ require('dotenv').config() const Web3 = require('web3') const { createMessage } = require('../utils/message') -const { - HOME_RPC_URL, - HOME_BRIDGE_ADDRESS, - VALIDATOR_ADDRESS, - VALIDATOR_ADDRESS_PRIVATE_KEY -} = process.env - -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) -const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridgeNativeToErc.abi') - -const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) - -async function processSignatureRequests(signatureRequests) { - const txToSend = [] - - const callbacks = signatureRequests.map(async (signatureRequest, index) => { - const { recipient, value } = signatureRequest.returnValues - - const message = createMessage({ - recipient, - value, - transactionHash: signatureRequest.transactionHash - }) - - const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) - - let gasEstimate - try { - gasEstimate = await homeBridge.methods +const { HOME_RPC_URL, VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env + +function processSignatureRequestsBuilder(config) { + const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const web3Home = new Web3(homeProvider) + const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + + return async function processSignatureRequests(signatureRequests) { + const txToSend = [] + + const callbacks = signatureRequests.map(async (signatureRequest, index) => { + const { recipient, value } = signatureRequest.returnValues + + const message = createMessage({ + recipient, + value, + transactionHash: signatureRequest.transactionHash + }) + + const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) + + let gasEstimate + try { + gasEstimate = await homeBridge.methods + .submitSignature(signature.signature, message) + .estimateGas({ from: VALIDATOR_ADDRESS }) + } catch (e) { + console.log( + index + 1, + '# already processed UserRequestForSignature ', + signatureRequest.transactionHash + ) + return + } + + const data = await homeBridge.methods .submitSignature(signature.signature, message) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - console.log( - index + 1, - '# already processed UserRequestForSignature ', - signatureRequest.transactionHash - ) - return - } - - const data = await homeBridge.methods - .submitSignature(signature.signature, message) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: signatureRequest.transactionHash + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: signatureRequest.transactionHash, + to: config.homeBridgeAddress + }) }) - }) - await Promise.all(callbacks) - return txToSend + await Promise.all(callbacks) + return txToSend + } } -module.exports = processSignatureRequests +module.exports = processSignatureRequestsBuilder diff --git a/src/sender.js b/src/sender.js index adf5934..fe17991 100644 --- a/src/sender.js +++ b/src/sender.js @@ -89,7 +89,7 @@ async function main({ msg, ackMsg, nackMsg, sendToQueue }) { amount: '0', gasLimit: job.gasEstimate + 200000, privateKey: VALIDATOR_ADDRESS_PRIVATE_KEY, - to: config.contractAddress, + to: job.to, chainId, web3: web3Instance }) diff --git a/src/watcher.js b/src/watcher.js index bc8e8fe..160323a 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -3,9 +3,6 @@ const path = require('path') const Web3 = require('web3') const { connectWatcherToQueue } = require('./services/amqpClient') const { getBlockNumber } = require('./tx/web3') -const processSignatureRequests = require('./events/processSignatureRequests') -const processCollectedSignatures = require('./events/processCollectedSignatures') -const processAffirmationRequests = require('./events/processAffirmationRequests') const { redis } = require('./services/redisClient') const { getRequiredBlockConfirmations } = require('./tx/web3') const { checkHTTPS } = require('./utils/utils') @@ -17,9 +14,14 @@ if (process.argv.length < 3) { const config = require(path.join('../config/', process.argv[2])) +const processSignatureRequests = require('./events/processSignatureRequests')(config) +const processCollectedSignatures = require('./events/processCollectedSignatures')(config) +const processAffirmationRequests = require('./events/processAffirmationRequests')(config) + const provider = new Web3.providers.HttpProvider(config.url) const web3Instance = new Web3(provider) -const bridgeContract = new web3Instance.eth.Contract(config.abi, config.contractAddress) +const bridgeContract = new web3Instance.eth.Contract(config.bridgeAbi, config.bridgeContractAddress) +const eventContract = new web3Instance.eth.Contract(config.eventAbi, config.eventContractAddress) const lastBlockRedisKey = `${config.id}:lastProcessedBlock` let lastProcessedBlock = 0 @@ -95,9 +97,10 @@ async function main({ sendToQueue }) { if (lastBlockToProcess <= lastProcessedBlock) { return } - const events = await bridgeContract.getPastEvents(config.event, { + const events = await eventContract.getPastEvents(config.event, { fromBlock: lastProcessedBlock + 1, - toBlock: lastBlockToProcess + toBlock: lastBlockToProcess, + filter: config.eventFilter }) console.log(`Found ${events.length} ${config.event}`) From 64550fdc06d2f0d63489687e1c51260cefb49a02 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jul 2018 14:21:43 -0300 Subject: [PATCH 006/119] Rename config files --- ...t-watcher-config.js => affirmation-request-watcher.config.js} | 1 - ...-watcher-config.js => collected-signatures-watcher.config.js} | 1 - config/{foreign-sender-config.js => foreign-sender.config.js} | 0 config/{home-sender-config.js => home-sender.config.js} | 0 ...est-watcher-config.js => signature-request-watcher.config.js} | 1 - 5 files changed, 3 deletions(-) rename config/{affirmation-request-watcher-config.js => affirmation-request-watcher.config.js} (90%) rename config/{collected-signatures-watcher-config.js => collected-signatures-watcher.config.js} (89%) rename config/{foreign-sender-config.js => foreign-sender.config.js} (100%) rename config/{home-sender-config.js => home-sender.config.js} (100%) rename config/{signature-request-watcher-config.js => signature-request-watcher.config.js} (89%) diff --git a/config/affirmation-request-watcher-config.js b/config/affirmation-request-watcher.config.js similarity index 90% rename from config/affirmation-request-watcher-config.js rename to config/affirmation-request-watcher.config.js index a13a9c5..a15f1e5 100644 --- a/config/affirmation-request-watcher-config.js +++ b/config/affirmation-request-watcher.config.js @@ -1,4 +1,3 @@ -require('dotenv').config() const nativeErcConfig = require('./base-native-erc-watcher.config') module.exports = { diff --git a/config/collected-signatures-watcher-config.js b/config/collected-signatures-watcher.config.js similarity index 89% rename from config/collected-signatures-watcher-config.js rename to config/collected-signatures-watcher.config.js index a4fa23f..7a6fcd2 100644 --- a/config/collected-signatures-watcher-config.js +++ b/config/collected-signatures-watcher.config.js @@ -1,4 +1,3 @@ -require('dotenv').config() const nativeErcConfig = require('./base-native-erc-watcher.config') module.exports = { diff --git a/config/foreign-sender-config.js b/config/foreign-sender.config.js similarity index 100% rename from config/foreign-sender-config.js rename to config/foreign-sender.config.js diff --git a/config/home-sender-config.js b/config/home-sender.config.js similarity index 100% rename from config/home-sender-config.js rename to config/home-sender.config.js diff --git a/config/signature-request-watcher-config.js b/config/signature-request-watcher.config.js similarity index 89% rename from config/signature-request-watcher-config.js rename to config/signature-request-watcher.config.js index c28ffef..cbaf640 100644 --- a/config/signature-request-watcher-config.js +++ b/config/signature-request-watcher.config.js @@ -1,4 +1,3 @@ -require('dotenv').config() const nativeErcConfig = require('./base-native-erc-watcher.config') module.exports = { From eeb3bd1b0d40eeaf1ecfd03039d78ad1a5aef9f4 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jul 2018 14:24:10 -0300 Subject: [PATCH 007/119] Add erc-to-erc config files and scripts --- .env.example | 5 ++- config/base-erc-erc-watcher.config.js | 36 +++++++++++++++++++ ...erc-collected-signatures-watcher.config.js | 9 +++++ .../erc-signature-request-watcher.config.js | 9 +++++ config/erc-transfer-watcher.config.js | 14 ++++++++ package.json | 13 ++++--- 6 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 config/base-erc-erc-watcher.config.js create mode 100644 config/erc-collected-signatures-watcher.config.js create mode 100644 config/erc-signature-request-watcher.config.js create mode 100644 config/erc-transfer-watcher.config.js diff --git a/.env.example b/.env.example index 22e441c..cca956d 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,9 @@ HOME_RPC_URL=https://kovan.infura.io/mew FOREIGN_RPC_URL=https://kovan.infura.io/mew HOME_BRIDGE_ADDRESS=0x86b621b839Ff86Bb59fA57015b02318f9a870d80 FOREIGN_BRIDGE_ADDRESS=0x8dDCa2eB86D22FcB93342753515986e258A90169 -POA20_ADDRESS=0x7AA0A62497B5f4120fbe5D26c95bC612c2b96C14 +ERC20_TOKEN_ADDRESS=0x7AA0A62497B5f4120fbe5D26c95bC612c2b96C14 +ERC_HOME_BRIDGE_ADDRESS= +ERC_FOREIGN_BRIDGE_ADDRESS= VALIDATOR_ADDRESS=0xb8988B690910913c97A090c3a6f80FAD8b3A4683 VALIDATOR_ADDRESS_PRIVATE_KEY= @@ -24,3 +26,4 @@ USER_ADDRESS=0x59c4474184579b9c31b5e51445b6eef91cebf370 USER_ADDRESS_PRIVATE_KEY= HOME_MIN_AMOUNT_PER_TX=0.001 FOREIGN_MIN_AMOUNT_PER_TX=0.001 +BRIDGEABLE_TOKEN_ADDRESS= diff --git a/config/base-erc-erc-watcher.config.js b/config/base-erc-erc-watcher.config.js new file mode 100644 index 0000000..e53fc44 --- /dev/null +++ b/config/base-erc-erc-watcher.config.js @@ -0,0 +1,36 @@ +require('dotenv').config() + +const homeAbi = require('../abis/HomeBridgeErcToErc.abi') +const foreignAbi = require('../abis/ForeignBridgeErcToErc.abi') + +const bridgeConfig = { + homeBridgeAddress: process.env.ERC_HOME_BRIDGE_ADDRESS, + homeBridgeAbi: homeAbi, + foreignBridgeAddress: process.env.ERC_FOREIGN_BRIDGE_ADDRESS, + foreignBridgeAbi: foreignAbi, + eventFilter: {} +} + +const homeConfig = { + url: process.env.HOME_RPC_URL, + eventContractAddress: process.env.ERC_HOME_BRIDGE_ADDRESS, + eventAbi: homeAbi, + bridgeContractAddress: process.env.ERC_HOME_BRIDGE_ADDRESS, + bridgeAbi: homeAbi, + pollingInterval: process.env.HOME_POLLING_INTERVAL +} + +const foreignConfig = { + url: process.env.FOREIGN_RPC_URL, + eventContractAddress: process.env.ERC_FOREIGN_BRIDGE_ADDRESS, + eventAbi: foreignAbi, + bridgeContractAddress: process.env.ERC_FOREIGN_BRIDGE_ADDRESS, + bridgeAbi: foreignAbi, + pollingInterval: process.env.FOREIGN_POLLING_INTERVAL +} + +module.exports = { + bridgeConfig, + homeConfig, + foreignConfig +} diff --git a/config/erc-collected-signatures-watcher.config.js b/config/erc-collected-signatures-watcher.config.js new file mode 100644 index 0000000..5ebb5b6 --- /dev/null +++ b/config/erc-collected-signatures-watcher.config.js @@ -0,0 +1,9 @@ +const ercErcConfig = require('./base-erc-erc-watcher.config') + +module.exports = { + ...ercErcConfig.bridgeConfig, + ...ercErcConfig.homeConfig, + event: 'CollectedSignatures', + queue: 'foreign', + id: 'erc-collected-signatures' +} diff --git a/config/erc-signature-request-watcher.config.js b/config/erc-signature-request-watcher.config.js new file mode 100644 index 0000000..5d400b2 --- /dev/null +++ b/config/erc-signature-request-watcher.config.js @@ -0,0 +1,9 @@ +const ercErcConfig = require('./base-erc-erc-watcher.config') + +module.exports = { + ...ercErcConfig.bridgeConfig, + ...ercErcConfig.homeConfig, + event: 'UserRequestForSignature', + queue: 'home', + id: 'erc-signature-request' +} diff --git a/config/erc-transfer-watcher.config.js b/config/erc-transfer-watcher.config.js new file mode 100644 index 0000000..0ce78ee --- /dev/null +++ b/config/erc-transfer-watcher.config.js @@ -0,0 +1,14 @@ +require('dotenv').config() +const ercErcConfig = require('./base-erc-erc-watcher.config') +const erc20Abi = require('../abis/ERC20.abi') + +module.exports = { + ...ercErcConfig.bridgeConfig, + ...ercErcConfig.foreignConfig, + event: 'Transfer', + eventContractAddress: process.env.ERC20_TOKEN_ADDRESS, + eventAbi: erc20Abi, + eventFilter: { to: process.env.ERC_FOREIGN_BRIDGE_ADDRESS }, + queue: 'home', + id: 'erc-transfer' +} diff --git a/package.json b/package.json index dd43a80..4ba21e5 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,14 @@ "main": "index.js", "scripts": { "lint": "eslint .", - "watcher:signature-request": "node src/watcher.js signature-request-watcher-config.js", - "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher-config.js", - "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher-config.js", - "sender:home": "node src/sender.js home-sender-config.js", - "sender:foreign": "node src/sender.js foreign-sender-config.js" + "watcher:signature-request": "node src/watcher.js signature-request-watcher.config.js", + "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher.config.js", + "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js", + "watcher:erc:signature-request": "node src/watcher.js erc-signature-request-watcher.config.js", + "watcher:erc:collected-signatures": "node src/watcher.js erc-collected-signatures-watcher.config.js", + "watcher:erc:transfer": "node src/watcher.js erc-transfer-watcher.config.js", + "sender:home": "node src/sender.js home-sender.config.js", + "sender:foreign": "node src/sender.js foreign-sender.config.js" }, "author": "", "license": "ISC", From 95e9fa00d5295778ab151f9778002f5fd9a3b17b Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jul 2018 14:24:54 -0300 Subject: [PATCH 008/119] Add transfer event processor --- src/events/processTransfers.js | 44 ++++++++++++++++++++++++++++++++++ src/watcher.js | 5 ++++ 2 files changed, 49 insertions(+) create mode 100644 src/events/processTransfers.js diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js new file mode 100644 index 0000000..db80854 --- /dev/null +++ b/src/events/processTransfers.js @@ -0,0 +1,44 @@ +require('dotenv').config() +const Web3 = require('web3') + +const { HOME_RPC_URL, VALIDATOR_ADDRESS } = process.env + +function processTransfersBuilder(config) { + const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const web3Home = new Web3(homeProvider) + const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + + return async function processTransfers(transfers) { + const txToSend = [] + + const callbacks = transfers.map(async (transfer, index) => { + const { from, value } = transfer.returnValues + + let gasEstimate + try { + gasEstimate = await homeBridge.methods + .executeAffirmation(from, value, transfer.transactionHash) + .estimateGas({ from: VALIDATOR_ADDRESS }) + } catch (e) { + console.log(index + 1, '# already processed Transfer', transfer.transactionHash) + return + } + + const data = await homeBridge.methods + .executeAffirmation(from, value, transfer.transactionHash) + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: transfer.transactionHash, + to: config.homeBridgeAddress + }) + }) + + await Promise.all(callbacks) + return txToSend + } +} + +module.exports = processTransfersBuilder diff --git a/src/watcher.js b/src/watcher.js index 160323a..e48b14a 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -17,6 +17,7 @@ const config = require(path.join('../config/', process.argv[2])) const processSignatureRequests = require('./events/processSignatureRequests')(config) const processCollectedSignatures = require('./events/processCollectedSignatures')(config) const processAffirmationRequests = require('./events/processAffirmationRequests')(config) +const processTransfers = require('./events/processTransfers')(config) const provider = new Web3.providers.HttpProvider(config.url) const web3Instance = new Web3(provider) @@ -70,11 +71,15 @@ function updateLastProcessedBlock(lastBlockNumber) { function processEvents(events) { switch (config.id) { case 'signature-request': + case 'erc-signature-request': return processSignatureRequests(events) case 'collected-signatures': + case 'erc-collected-signatures': return processCollectedSignatures(events) case 'affirmation-request': return processAffirmationRequests(events) + case 'erc-transfer': + return processTransfers(events) default: return [] } From 30469402ac10892f5a0ccbb25cb595ad3cbafa71 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jul 2018 14:26:17 -0300 Subject: [PATCH 009/119] Add scripts to send transactions to bridge --- tests/sendUserTxToErcForeign.js | 90 +++++++++++++++++++ tests/sendUserTxToErcHome.js | 90 +++++++++++++++++++ ...eignWithdraw.js => sendUserTxToForeign.js} | 8 +- 3 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 tests/sendUserTxToErcForeign.js create mode 100644 tests/sendUserTxToErcHome.js rename tests/{sendUserTxToForeignWithdraw.js => sendUserTxToForeign.js} (93%) diff --git a/tests/sendUserTxToErcForeign.js b/tests/sendUserTxToErcForeign.js new file mode 100644 index 0000000..6427615 --- /dev/null +++ b/tests/sendUserTxToErcForeign.js @@ -0,0 +1,90 @@ +require('dotenv').config() +const Web3 = require('web3') +const Web3Utils = require('web3-utils') +const { sendTx, sendRawTx } = require('../src/tx/sendTx') + +const { + USER_ADDRESS, + USER_ADDRESS_PRIVATE_KEY, + ERC_FOREIGN_BRIDGE_ADDRESS, + FOREIGN_RPC_URL, + FOREIGN_MIN_AMOUNT_PER_TX, + ERC20_TOKEN_ADDRESS, + NUMBER_OF_DEPOSITS_TO_SEND +} = process.env + +const ERC20_ABI = [ + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + } + ], + name: 'transfer', + outputs: [ + { + name: '', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + } +] + +const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) +const web3Foreign = new Web3(foreignProvider) + +const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) + +async function main() { + try { + const foreignChaindId = await sendRawTx({ + url: FOREIGN_RPC_URL, + params: [], + method: 'net_version' + }) + let nonce = await sendRawTx({ + url: FOREIGN_RPC_URL, + method: 'eth_getTransactionCount', + params: [USER_ADDRESS, 'latest'] + }) + nonce = Web3Utils.hexToNumber(nonce) + let actualSent = 0 + for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { + const gasLimit = await poa20.methods + .transfer(ERC_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) + .estimateGas({ from: USER_ADDRESS }) + const data = await poa20.methods + .transfer(ERC_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) + .encodeABI({ from: USER_ADDRESS }) + const txHash = await sendTx({ + rpcUrl: FOREIGN_RPC_URL, + privateKey: USER_ADDRESS_PRIVATE_KEY, + data, + nonce, + gasPrice: '1', + amount: '0', + gasLimit, + to: ERC20_TOKEN_ADDRESS, + web3: web3Foreign, + chainId: foreignChaindId + }) + if (txHash !== undefined) { + nonce++ + actualSent++ + console.log(actualSent, ' # ', txHash) + } + } + } catch (e) { + console.log(e) + } +} +main() diff --git a/tests/sendUserTxToErcHome.js b/tests/sendUserTxToErcHome.js new file mode 100644 index 0000000..62b5dff --- /dev/null +++ b/tests/sendUserTxToErcHome.js @@ -0,0 +1,90 @@ +require('dotenv').config() +const Web3 = require('web3') +const Web3Utils = require('web3-utils') +const { sendTx, sendRawTx } = require('../src/tx/sendTx') + +const { + USER_ADDRESS, + USER_ADDRESS_PRIVATE_KEY, + ERC_HOME_BRIDGE_ADDRESS, + HOME_RPC_URL, + HOME_MIN_AMOUNT_PER_TX, + BRIDGEABLE_TOKEN_ADDRESS, + NUMBER_OF_WITHDRAWALS_TO_SEND +} = process.env + +const BRIDGEBLE_TOKEN_ABI = [ + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + }, + { + name: '_data', + type: 'bytes' + } + ], + name: 'transferAndCall', + outputs: [ + { + name: '', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + } +] + +const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) +const web3Home = new Web3(homeProvider) + +const erc677 = new web3Home.eth.Contract(BRIDGEBLE_TOKEN_ABI, BRIDGEABLE_TOKEN_ADDRESS) + +async function main() { + const homeChainId = await sendRawTx({ + url: HOME_RPC_URL, + params: [], + method: 'net_version' + }) + let nonce = await sendRawTx({ + url: HOME_RPC_URL, + method: 'eth_getTransactionCount', + params: [USER_ADDRESS, 'latest'] + }) + nonce = Web3Utils.hexToNumber(nonce) + let actualSent = 0 + for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) { + const gasLimit = await erc677.methods + .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') + .estimateGas({ from: USER_ADDRESS }) + const data = await erc677.methods + .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') + .encodeABI({ from: USER_ADDRESS }) + const txHash = await sendTx({ + rpcUrl: HOME_RPC_URL, + privateKey: USER_ADDRESS_PRIVATE_KEY, + data, + nonce, + gasPrice: '1', + amount: '0', + gasLimit, + to: BRIDGEABLE_TOKEN_ADDRESS, + web3: web3Home, + chainId: homeChainId + }) + if (txHash !== undefined) { + nonce++ + actualSent++ + console.log(actualSent, ' # ', txHash) + } + } +} +main() diff --git a/tests/sendUserTxToForeignWithdraw.js b/tests/sendUserTxToForeign.js similarity index 93% rename from tests/sendUserTxToForeignWithdraw.js rename to tests/sendUserTxToForeign.js index 8ecf11a..91acb78 100644 --- a/tests/sendUserTxToForeignWithdraw.js +++ b/tests/sendUserTxToForeign.js @@ -9,11 +9,11 @@ const { FOREIGN_BRIDGE_ADDRESS, FOREIGN_RPC_URL, FOREIGN_MIN_AMOUNT_PER_TX, - POA20_ADDRESS, + ERC20_TOKEN_ADDRESS, NUMBER_OF_WITHDRAWALS_TO_SEND } = process.env -const POA20_ABI = [ +const ERC20_ABI = [ { constant: false, inputs: [ @@ -46,7 +46,7 @@ const POA20_ABI = [ const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) const web3Foreign = new Web3(foreignProvider) -const poa20 = new web3Foreign.eth.Contract(POA20_ABI, POA20_ADDRESS) +const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { const foreignChaindId = await sendRawTx({ @@ -76,7 +76,7 @@ async function main() { gasPrice: '1', amount: '0', gasLimit, - to: POA20_ADDRESS, + to: ERC20_TOKEN_ADDRESS, web3: web3Foreign, chainId: foreignChaindId }) From 56008419436bca9784d04109cf006b57adcaf6c6 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jul 2018 17:01:17 -0300 Subject: [PATCH 010/119] Add erc-to-erc e2e tests --- e2e/deploy.js | 3 + e2e/docker-compose.yml | 5 +- e2e/envs/bridge.env | 2 +- e2e/envs/erc-contracts-deploy.env | 30 ++++++ e2e/run-tests.sh | 3 + e2e/test/ercToErc.js | 108 +++++++++++++++++++ e2e/test/{transactions.js => nativeToErc.js} | 2 +- 7 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 e2e/envs/erc-contracts-deploy.env create mode 100644 e2e/test/ercToErc.js rename e2e/test/{transactions.js => nativeToErc.js} (98%) diff --git a/e2e/deploy.js b/e2e/deploy.js index 423a59b..2e1b75b 100644 --- a/e2e/deploy.js +++ b/e2e/deploy.js @@ -7,3 +7,6 @@ const deployContractsDir = path.join(__dirname, 'submodules/poa-bridge-contracts shell.cp(path.join(envsDir, 'contracts-deploy.env'), path.join(deployContractsDir, '.env')) shell.cd(deployContractsDir) shell.exec('node deploy.js') +shell.rm('.env') +shell.cp(path.join(envsDir, 'erc-contracts-deploy.env'), path.join(deployContractsDir, '.env')) +shell.exec('node deploy.js') diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index 98d04d4..045ce30 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -18,7 +18,10 @@ services: - FOREIGN_RPC_URL=http://parity2:8545 - HOME_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624 - FOREIGN_BRIDGE_ADDRESS=0x2B6871b9B02F73fa24F4864322CdC78604207769 - - POA20_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B + - ERC20_TOKEN_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B + - ERC_HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E + - ERC_FOREIGN_BRIDGE_ADDRESS=0x8397be90BCF57b0B71219f555Fe121b22e5a994C + - BRIDGEABLE_TOKEN_ADDRESS=0x792455a6bCb62Ed4C4362D323E0590654CA4765c - VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b - VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 - REDIS_LOCK_TTL=1000 diff --git a/e2e/envs/bridge.env b/e2e/envs/bridge.env index 4234dda..b975a52 100644 --- a/e2e/envs/bridge.env +++ b/e2e/envs/bridge.env @@ -2,7 +2,7 @@ HOME_RPC_URL=http://parity1:8545 FOREIGN_RPC_URL=http://parity2:8545 HOME_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624 FOREIGN_BRIDGE_ADDRESS=0x2B6871b9B02F73fa24F4864322CdC78604207769 -POA20_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B +ERC20_TOKEN_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 diff --git a/e2e/envs/erc-contracts-deploy.env b/e2e/envs/erc-contracts-deploy.env new file mode 100644 index 0000000..2b236c0 --- /dev/null +++ b/e2e/envs/erc-contracts-deploy.env @@ -0,0 +1,30 @@ +BRIDGE_MODE=ERC_TO_ERC +DEPLOYMENT_ACCOUNT_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 +DEPLOYMENT_GAS_LIMIT=4000000 +DEPLOYMENT_GAS_PRICE=10 +GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 + +HOME_RPC_URL=http://parity1:8545 +HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_UPGRADEABLE_ADMIN_BRIDGE=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_DAILY_LIMIT=30000000000000000000000000 +HOME_MAX_AMOUNT_PER_TX=1500000000000000000000000 +HOME_MIN_AMOUNT_PER_TX=10000000000000000 +HOME_REQUIRED_BLOCK_CONFIRMATIONS=1 +HOME_GAS_PRICE=10 + +FOREIGN_RPC_URL=http://parity2:8545 +FOREIGN_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_UPGRADEABLE_ADMIN_BRIDGE=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_DAILY_LIMIT=15000000000000000000000000 +FOREIGN_MAX_AMOUNT_PER_TX=750000000000000000000000 +FOREIGN_MIN_AMOUNT_PER_TX=10000000000000000 +FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1 +FOREIGN_GAS_PRICE=10 +ERC20_TOKEN_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B + +REQUIRED_NUMBER_OF_VALIDATORS=1 +VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" diff --git a/e2e/run-tests.sh b/e2e/run-tests.sh index 8ba574f..c17a077 100755 --- a/e2e/run-tests.sh +++ b/e2e/run-tests.sh @@ -3,6 +3,9 @@ docker-compose run e2e npm run deploy docker-compose run -d bridge npm run watcher:signature-request docker-compose run -d bridge npm run watcher:collected-signatures docker-compose run -d bridge npm run watcher:affirmation-request +docker-compose run -d bridge npm run watcher:erc:signature-request +docker-compose run -d bridge npm run watcher:erc:collected-signatures +docker-compose run -d bridge npm run watcher:erc:transfer docker-compose run -d bridge npm run sender:home docker-compose run -d bridge npm run sender:foreign docker-compose run e2e npm start diff --git a/e2e/test/ercToErc.js b/e2e/test/ercToErc.js new file mode 100644 index 0000000..bad77df --- /dev/null +++ b/e2e/test/ercToErc.js @@ -0,0 +1,108 @@ +require('./nativeToErc') +const path = require('path') +const Web3 = require('web3') +const assert = require('assert') +const promiseRetry = require('promise-retry') +const { user } = require('../constants.json') +const { generateNewBlock } = require('../utils/utils') + +const abisDir = path.join(__dirname, '..', 'submodules/poa-bridge-contracts/build/contracts') + +const homeWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity1:8545')) +const foreignWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity2:8545')) + +const HOME_BRIDGE_ADDRESS = '0x1feB40aD9420b186F019A717c37f5546165d411E' +const FOREIGN_BRIDGE_ADDRESS = '0x8397be90BCF57b0B71219f555Fe121b22e5a994C' + +const { toBN } = foreignWeb3.utils + +homeWeb3.eth.accounts.wallet.add(user.privateKey) +foreignWeb3.eth.accounts.wallet.add(user.privateKey) + +const tokenAbi = require(path.join(abisDir, 'ERC677BridgeToken.json')).abi +const erc20Token = new foreignWeb3.eth.Contract( + tokenAbi, + '0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B' +) +const erc677Token = new homeWeb3.eth.Contract( + tokenAbi, + '0x792455a6bCb62Ed4C4362D323E0590654CA4765c' +) + +describe('erc to erc', () => { + it('should convert tokens in foreign to tokens in home', async () => { + const balance = await erc20Token.methods.balanceOf(user.address).call() + assert(!toBN(balance).isZero(), 'Account should have tokens') + + // send tokens to foreign bridge + await erc20Token.methods + .transfer(FOREIGN_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01')) + .send({ + from: user.address, + gas: '1000000' + }) + .catch(e => { + console.error(e) + }) + + // Send a trivial transaction to generate a new block since the watcher + // is configured to wait 1 confirmation block + await generateNewBlock(foreignWeb3, user.address) + + // check that balance increases + await promiseRetry(async retry => { + const balance = await erc677Token.methods.balanceOf(user.address).call() + if (toBN(balance).isZero()) { + retry() + } + }) + }) + it('should convert tokens in home to tokens in foreign', async () => { + const originalBalance = await erc20Token.methods.balanceOf(user.address).call() + + // check that account has tokens in home chain + const balance = await erc677Token.methods.balanceOf(user.address).call() + assert(!toBN(balance).isZero(), 'Account should have tokens') + + // send transaction to home bridge + const depositTx = await erc677Token.methods + .transferAndCall(HOME_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'), '0x') + .send({ + from: user.address, + gas: '1000000' + }) + .catch(e => { + console.error(e) + }) + + // Send a trivial transaction to generate a new block since the watcher + // is configured to wait 1 confirmation block + await generateNewBlock(homeWeb3, user.address) + + // The bridge should create a new transaction with a CollectedSignatures + // event so we generate another trivial transaction + await promiseRetry( + async retry => { + const lastBlockNumber = await homeWeb3.eth.getBlockNumber() + if (lastBlockNumber >= depositTx.blockNumber + 2) { + await generateNewBlock(homeWeb3, user.address) + } else { + retry() + } + }, + { + forever: true, + factor: 1, + minTimeout: 500 + } + ) + + // check that balance increases + await promiseRetry(async retry => { + const balance = await erc20Token.methods.balanceOf(user.address).call() + if (toBN(balance).lte(toBN(originalBalance))) { + retry() + } + }) + }) +}) diff --git a/e2e/test/transactions.js b/e2e/test/nativeToErc.js similarity index 98% rename from e2e/test/transactions.js rename to e2e/test/nativeToErc.js index b4ff220..3d30ddd 100644 --- a/e2e/test/transactions.js +++ b/e2e/test/nativeToErc.js @@ -21,7 +21,7 @@ foreignWeb3.eth.accounts.wallet.add(user.privateKey) const tokenAbi = require(path.join(abisDir, 'ERC677BridgeToken.json')).abi const token = new foreignWeb3.eth.Contract(tokenAbi, '0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B') -describe('transactions', () => { +describe('native to erc', () => { it('should convert eth in home to tokens in foreign', async () => { // check that account has zero tokens in the foreign chain const balance = await token.methods.balanceOf(user.address).call() From 5f8e5f8c3970aad62d80b0957139118668c870e4 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 4 Jul 2018 11:36:50 -0300 Subject: [PATCH 011/119] Fix transaction scripts --- tests/sendUserTxToErcHome.js | 76 +++++++++++++++++++----------------- tests/sendUserTxToForeign.js | 72 ++++++++++++++++++---------------- tests/sendUserTxToHome.js | 61 ++++++++++++++++------------- 3 files changed, 111 insertions(+), 98 deletions(-) diff --git a/tests/sendUserTxToErcHome.js b/tests/sendUserTxToErcHome.js index 62b5dff..96dd697 100644 --- a/tests/sendUserTxToErcHome.js +++ b/tests/sendUserTxToErcHome.js @@ -13,7 +13,7 @@ const { NUMBER_OF_WITHDRAWALS_TO_SEND } = process.env -const BRIDGEBLE_TOKEN_ABI = [ +const BRIDGEABLE_TOKEN_ABI = [ { constant: false, inputs: [ @@ -46,45 +46,49 @@ const BRIDGEBLE_TOKEN_ABI = [ const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) const web3Home = new Web3(homeProvider) -const erc677 = new web3Home.eth.Contract(BRIDGEBLE_TOKEN_ABI, BRIDGEABLE_TOKEN_ADDRESS) +const erc677 = new web3Home.eth.Contract(BRIDGEABLE_TOKEN_ABI, BRIDGEABLE_TOKEN_ADDRESS) async function main() { - const homeChainId = await sendRawTx({ - url: HOME_RPC_URL, - params: [], - method: 'net_version' - }) - let nonce = await sendRawTx({ - url: HOME_RPC_URL, - method: 'eth_getTransactionCount', - params: [USER_ADDRESS, 'latest'] - }) - nonce = Web3Utils.hexToNumber(nonce) - let actualSent = 0 - for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) { - const gasLimit = await erc677.methods - .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') - .estimateGas({ from: USER_ADDRESS }) - const data = await erc677.methods - .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') - .encodeABI({ from: USER_ADDRESS }) - const txHash = await sendTx({ - rpcUrl: HOME_RPC_URL, - privateKey: USER_ADDRESS_PRIVATE_KEY, - data, - nonce, - gasPrice: '1', - amount: '0', - gasLimit, - to: BRIDGEABLE_TOKEN_ADDRESS, - web3: web3Home, - chainId: homeChainId + try { + const homeChainId = await sendRawTx({ + url: HOME_RPC_URL, + params: [], + method: 'net_version' }) - if (txHash !== undefined) { - nonce++ - actualSent++ - console.log(actualSent, ' # ', txHash) + let nonce = await sendRawTx({ + url: HOME_RPC_URL, + method: 'eth_getTransactionCount', + params: [USER_ADDRESS, 'latest'] + }) + nonce = Web3Utils.hexToNumber(nonce) + let actualSent = 0 + for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) { + const gasLimit = await erc677.methods + .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') + .estimateGas({ from: USER_ADDRESS }) + const data = await erc677.methods + .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') + .encodeABI({ from: USER_ADDRESS }) + const txHash = await sendTx({ + rpcUrl: HOME_RPC_URL, + privateKey: USER_ADDRESS_PRIVATE_KEY, + data, + nonce, + gasPrice: '1', + amount: '0', + gasLimit, + to: BRIDGEABLE_TOKEN_ADDRESS, + web3: web3Home, + chainId: homeChainId + }) + if (txHash !== undefined) { + nonce++ + actualSent++ + console.log(actualSent, ' # ', txHash) + } } + } catch (e) { + console.log(e) } } main() diff --git a/tests/sendUserTxToForeign.js b/tests/sendUserTxToForeign.js index 91acb78..413453b 100644 --- a/tests/sendUserTxToForeign.js +++ b/tests/sendUserTxToForeign.js @@ -49,42 +49,46 @@ const web3Foreign = new Web3(foreignProvider) const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { - const foreignChaindId = await sendRawTx({ - url: FOREIGN_RPC_URL, - params: [], - method: 'net_version' - }) - let nonce = await sendRawTx({ - url: FOREIGN_RPC_URL, - method: 'eth_getTransactionCount', - params: [USER_ADDRESS, 'latest'] - }) - nonce = Web3Utils.hexToNumber(nonce) - let actualSent = 0 - for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) { - const gasLimit = await poa20.methods - .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') - .estimateGas({ from: USER_ADDRESS }) - const data = await poa20.methods - .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') - .encodeABI({ from: USER_ADDRESS }) - const txHash = await sendTx({ - rpcUrl: FOREIGN_RPC_URL, - privateKey: USER_ADDRESS_PRIVATE_KEY, - data, - nonce, - gasPrice: '1', - amount: '0', - gasLimit, - to: ERC20_TOKEN_ADDRESS, - web3: web3Foreign, - chainId: foreignChaindId + try { + const foreignChaindId = await sendRawTx({ + url: FOREIGN_RPC_URL, + params: [], + method: 'net_version' }) - if (txHash !== undefined) { - nonce++ - actualSent++ - console.log(actualSent, ' # ', txHash) + let nonce = await sendRawTx({ + url: FOREIGN_RPC_URL, + method: 'eth_getTransactionCount', + params: [USER_ADDRESS, 'latest'] + }) + nonce = Web3Utils.hexToNumber(nonce) + let actualSent = 0 + for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) { + const gasLimit = await poa20.methods + .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') + .estimateGas({ from: USER_ADDRESS }) + const data = await poa20.methods + .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') + .encodeABI({ from: USER_ADDRESS }) + const txHash = await sendTx({ + rpcUrl: FOREIGN_RPC_URL, + privateKey: USER_ADDRESS_PRIVATE_KEY, + data, + nonce, + gasPrice: '1', + amount: '0', + gasLimit, + to: ERC20_TOKEN_ADDRESS, + web3: web3Foreign, + chainId: foreignChaindId + }) + if (txHash !== undefined) { + nonce++ + actualSent++ + console.log(actualSent, ' # ', txHash) + } } + } catch (e) { + console.log(e) } } main() diff --git a/tests/sendUserTxToHome.js b/tests/sendUserTxToHome.js index 9ccd00d..f77e0ac 100644 --- a/tests/sendUserTxToHome.js +++ b/tests/sendUserTxToHome.js @@ -16,36 +16,41 @@ const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) const web3Home = new Web3(homeProvider) async function main() { - const homeChaindId = await sendRawTx({ - url: HOME_RPC_URL, - params: [], - method: 'net_version' - }) - let nonce = await sendRawTx({ - url: HOME_RPC_URL, - method: 'eth_getTransactionCount', - params: [USER_ADDRESS, 'latest'] - }) - nonce = Web3Utils.hexToNumber(nonce) - let actualSent = 0 - for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { - const txHash = await sendTx({ - rpcUrl: HOME_RPC_URL, - privateKey: USER_ADDRESS_PRIVATE_KEY, - data: '0x', - nonce, - gasPrice: '1', - amount: HOME_MIN_AMOUNT_PER_TX, - gasLimit: 50000, - to: HOME_BRIDGE_ADDRESS, - web3: web3Home, - chainId: homeChaindId + try { + console.log(HOME_RPC_URL) + const homeChaindId = await sendRawTx({ + url: HOME_RPC_URL, + params: [], + method: 'net_version' }) - if (txHash !== undefined) { - nonce++ - actualSent++ - console.log(actualSent, ' # ', txHash) + let nonce = await sendRawTx({ + url: HOME_RPC_URL, + method: 'eth_getTransactionCount', + params: [USER_ADDRESS, 'latest'] + }) + nonce = Web3Utils.hexToNumber(nonce) + let actualSent = 0 + for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { + const txHash = await sendTx({ + rpcUrl: HOME_RPC_URL, + privateKey: USER_ADDRESS_PRIVATE_KEY, + data: '0x', + nonce, + gasPrice: '1', + amount: HOME_MIN_AMOUNT_PER_TX, + gasLimit: 50000, + to: HOME_BRIDGE_ADDRESS, + web3: web3Home, + chainId: homeChaindId + }) + if (txHash !== undefined) { + nonce++ + actualSent++ + console.log(actualSent, ' # ', txHash) + } } + } catch (e) { + console.log(e) } } main() From f8b64d02f30792191afbdd275c133a890b5a6f01 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 4 Jul 2018 11:37:05 -0300 Subject: [PATCH 012/119] Update README --- README.md | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f68a985..8adfbef 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,44 @@ There are two Senders: 3. Create a `.env` file: `cp .env.example .env` and fill in the information using the output data from previous deploy step. Check the `.env.example` file to see the required variables. -4. To run the processes: +## Run the processes + +### Native to Erc mode - `npm run watcher:signature-request` - `npm run watcher:collected-signatures` - `npm run watcher:affirmation-request` - `npm run sender:home` - `npm run sender:foreign` +To send deposits to home contract run `node tests/sendUserTxToHome.js` + +To send withdrawals to foreign contract run `node tests/sendUserTxToForeign.js` + +Make sure your `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is same as in your .env deployment contract + + +### Erc to Erc mode + - `npm run watcher:erc:signature-request` + - `npm run watcher:erc:collected-signatures` + - `npm run watcher:erc:transfer` + - `npm run sender:home` + - `npm run sender:foreign` + +To send deposits to home contract run `node tests/sendUserTxToErcHome.js` + +To send withdrawals to foreign contract run `node tests/sendUserTxToErcForeign.js` + +### Run both bridge modes together + + - `npm run watcher:signature-request` + - `npm run watcher:collected-signatures` + - `npm run watcher:affirmation-request` + - `npm run watcher:erc:signature-request` + - `npm run watcher:erc:collected-signatures` + - `npm run watcher:erc:transfer` + - `npm run sender:home` + - `npm run sender:foreign` + ### Run with Docker - Start RabbitMQ and Redis: `docker-compose up -d` @@ -51,14 +82,6 @@ There are two Senders: - `docker-compose run bridge npm run sender:foreign` -In order to quickly send deposits run -`node tests/sendUserTxToHome.js` -this will send small deposits to home contract -Make sure your HOME\_MIN\_AMOUNT\_PER\_TX is same as in your .env deployment contract - -In order to quickly send withdrawals run -`node tests/sendUserTxToForeignWithdraw.js` - To use the bridge UI, clone [the repo](https://github.com/poanetwork/bridge-ui/), create a `.env` using the same values as before, and run `npm start`. From a649de0ac62145bc11264600ee53a431a8f1be75 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 5 Jul 2018 14:17:04 -0300 Subject: [PATCH 013/119] Add erc20 deploy for erc-to-erc e2e test --- e2e/deploy.js | 4 +++ e2e/docker-compose.yml | 4 +-- e2e/envs/erc-contracts-deploy.env | 2 +- e2e/package-lock.json | 5 ++++ e2e/package.json | 1 + e2e/scripts/deployERC20.js | 47 +++++++++++++++++++++++++++++++ e2e/test/ercToErc.js | 5 ++-- 7 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 e2e/scripts/deployERC20.js diff --git a/e2e/deploy.js b/e2e/deploy.js index 2e1b75b..b41e806 100644 --- a/e2e/deploy.js +++ b/e2e/deploy.js @@ -3,10 +3,14 @@ const shell = require('shelljs') const envsDir = path.join(__dirname, 'envs') const deployContractsDir = path.join(__dirname, 'submodules/poa-bridge-contracts/deploy') +const erc20ScriptDir = path.join(__dirname, 'scripts') shell.cp(path.join(envsDir, 'contracts-deploy.env'), path.join(deployContractsDir, '.env')) shell.cd(deployContractsDir) shell.exec('node deploy.js') +shell.cd(erc20ScriptDir) +shell.exec('node deployERC20.js') +shell.cd(deployContractsDir) shell.rm('.env') shell.cp(path.join(envsDir, 'erc-contracts-deploy.env'), path.join(deployContractsDir, '.env')) shell.exec('node deploy.js') diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index 045ce30..f5acd25 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -18,9 +18,9 @@ services: - FOREIGN_RPC_URL=http://parity2:8545 - HOME_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624 - FOREIGN_BRIDGE_ADDRESS=0x2B6871b9B02F73fa24F4864322CdC78604207769 - - ERC20_TOKEN_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B + - ERC20_TOKEN_ADDRESS=0x7777D2BF48993088dC1ceD832863b80427Ff5Ec9 - ERC_HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E - - ERC_FOREIGN_BRIDGE_ADDRESS=0x8397be90BCF57b0B71219f555Fe121b22e5a994C + - ERC_FOREIGN_BRIDGE_ADDRESS=0xD0B9745831dDA9cbb47D0dEa904972cDcecc52e8 - BRIDGEABLE_TOKEN_ADDRESS=0x792455a6bCb62Ed4C4362D323E0590654CA4765c - VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b - VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 diff --git a/e2e/envs/erc-contracts-deploy.env b/e2e/envs/erc-contracts-deploy.env index 2b236c0..35705b5 100644 --- a/e2e/envs/erc-contracts-deploy.env +++ b/e2e/envs/erc-contracts-deploy.env @@ -24,7 +24,7 @@ FOREIGN_MAX_AMOUNT_PER_TX=750000000000000000000000 FOREIGN_MIN_AMOUNT_PER_TX=10000000000000000 FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1 FOREIGN_GAS_PRICE=10 -ERC20_TOKEN_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B +ERC20_TOKEN_ADDRESS=0x7777D2BF48993088dC1ceD832863b80427Ff5Ec9 REQUIRED_NUMBER_OF_VALIDATORS=1 VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 9733150..0f11218 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -579,6 +579,11 @@ "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" }, + "dotenv": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", + "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", diff --git a/e2e/package.json b/e2e/package.json index f39f8dd..62a8c2f 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "chalk": "^2.4.1", + "dotenv": "^6.0.0", "mocha": "^5.2.0", "promise-retry": "^1.1.1", "shelljs": "^0.8.2", diff --git a/e2e/scripts/deployERC20.js b/e2e/scripts/deployERC20.js new file mode 100644 index 0000000..9baded8 --- /dev/null +++ b/e2e/scripts/deployERC20.js @@ -0,0 +1,47 @@ +const path = require('path') +require('dotenv').config({ + path: path.join(__dirname, '../submodules/poa-bridge-contracts/deploy/.env') +}) + +const { + deployContract, + sendRawTx +} = require('../submodules/poa-bridge-contracts/deploy/src/deploymentUtils') +const { + web3Foreign, + deploymentPrivateKey, + FOREIGN_RPC_URL +} = require('../submodules/poa-bridge-contracts/deploy/src/web3') +const POA20 = require('../submodules/poa-bridge-contracts/build/contracts/ERC677BridgeToken.json') +const { user } = require('../constants.json') + +const { DEPLOYMENT_ACCOUNT_ADDRESS } = process.env + +async function deployErc20() { + try { + let foreignNonce = await web3Foreign.eth.getTransactionCount(DEPLOYMENT_ACCOUNT_ADDRESS) + console.log('\n[Foreign] Deploying POA20 Test token') + const poa20foreign = await deployContract(POA20, ['POA ERC20 Test', 'POA20', 18], { + from: DEPLOYMENT_ACCOUNT_ADDRESS, + network: 'foreign', + nonce: foreignNonce + }) + foreignNonce++ + console.log('[Foreign] POA20 Test: ', poa20foreign.options.address) + + const mintData = await poa20foreign.methods + .mint(user.address, '1000000000000000000') + .encodeABI({ from: DEPLOYMENT_ACCOUNT_ADDRESS }) + await sendRawTx({ + data: mintData, + nonce: foreignNonce, + to: poa20foreign.options.address, + privateKey: deploymentPrivateKey, + url: FOREIGN_RPC_URL + }) + } catch (e) { + console.log(e) + } +} + +deployErc20() diff --git a/e2e/test/ercToErc.js b/e2e/test/ercToErc.js index bad77df..4dfc5da 100644 --- a/e2e/test/ercToErc.js +++ b/e2e/test/ercToErc.js @@ -1,4 +1,3 @@ -require('./nativeToErc') const path = require('path') const Web3 = require('web3') const assert = require('assert') @@ -12,7 +11,7 @@ const homeWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity1:8545') const foreignWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity2:8545')) const HOME_BRIDGE_ADDRESS = '0x1feB40aD9420b186F019A717c37f5546165d411E' -const FOREIGN_BRIDGE_ADDRESS = '0x8397be90BCF57b0B71219f555Fe121b22e5a994C' +const FOREIGN_BRIDGE_ADDRESS = '0xD0B9745831dDA9cbb47D0dEa904972cDcecc52e8' const { toBN } = foreignWeb3.utils @@ -22,7 +21,7 @@ foreignWeb3.eth.accounts.wallet.add(user.privateKey) const tokenAbi = require(path.join(abisDir, 'ERC677BridgeToken.json')).abi const erc20Token = new foreignWeb3.eth.Contract( tokenAbi, - '0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B' + '0x7777D2BF48993088dC1ceD832863b80427Ff5Ec9' ) const erc677Token = new homeWeb3.eth.Contract( tokenAbi, From f7ab337dce9c9e7874d88389568bf951eb078059 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 5 Jul 2018 15:30:20 -0300 Subject: [PATCH 014/119] Fix merge conflicts --- src/tx/web3.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tx/web3.js b/src/tx/web3.js index 32c0e54..d9fb140 100644 --- a/src/tx/web3.js +++ b/src/tx/web3.js @@ -30,9 +30,9 @@ async function getRequiredBlockConfirmations(contract) { } } -async function getEvents({ contract, event, fromBlock, toBlock }) { +async function getEvents({ contract, event, fromBlock, toBlock, filter }) { try { - return await contract.getPastEvents(event, { fromBlock, toBlock }) + return await contract.getPastEvents(event, { fromBlock, toBlock, filter }) } catch (e) { throw new Error(`${event} events cannot be obtained`) } From 1952d1f67054ce453e1c985d1fd3fbcad131c65e Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 10 Jul 2018 11:40:53 -0300 Subject: [PATCH 015/119] Update transfer name to affirmation-request --- README.md | 4 ++-- ...er.config.js => erc-affirmation-request-watcher.config.js} | 0 e2e/run-tests.sh | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename config/{erc-transfer-watcher.config.js => erc-affirmation-request-watcher.config.js} (100%) diff --git a/README.md b/README.md index 8adfbef..273589e 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Make sure your `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is same ### Erc to Erc mode - `npm run watcher:erc:signature-request` - `npm run watcher:erc:collected-signatures` - - `npm run watcher:erc:transfer` + - `npm run watcher:erc:affirmation-request` - `npm run sender:home` - `npm run sender:foreign` @@ -68,7 +68,7 @@ To send withdrawals to foreign contract run `node tests/sendUserTxToErcForeign.j - `npm run watcher:affirmation-request` - `npm run watcher:erc:signature-request` - `npm run watcher:erc:collected-signatures` - - `npm run watcher:erc:transfer` + - `npm run watcher:erc:affirmation-request` - `npm run sender:home` - `npm run sender:foreign` diff --git a/config/erc-transfer-watcher.config.js b/config/erc-affirmation-request-watcher.config.js similarity index 100% rename from config/erc-transfer-watcher.config.js rename to config/erc-affirmation-request-watcher.config.js diff --git a/e2e/run-tests.sh b/e2e/run-tests.sh index c17a077..78b40c1 100755 --- a/e2e/run-tests.sh +++ b/e2e/run-tests.sh @@ -5,7 +5,7 @@ docker-compose run -d bridge npm run watcher:collected-signatures docker-compose run -d bridge npm run watcher:affirmation-request docker-compose run -d bridge npm run watcher:erc:signature-request docker-compose run -d bridge npm run watcher:erc:collected-signatures -docker-compose run -d bridge npm run watcher:erc:transfer +docker-compose run -d bridge npm run watcher:erc:affirmation-request docker-compose run -d bridge npm run sender:home docker-compose run -d bridge npm run sender:foreign docker-compose run e2e npm start diff --git a/package.json b/package.json index 4ba21e5..444af04 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js", "watcher:erc:signature-request": "node src/watcher.js erc-signature-request-watcher.config.js", "watcher:erc:collected-signatures": "node src/watcher.js erc-collected-signatures-watcher.config.js", - "watcher:erc:transfer": "node src/watcher.js erc-transfer-watcher.config.js", + "watcher:erc:affirmation-request": "node src/watcher.js erc-affirmation-request-watcher.config.js", "sender:home": "node src/sender.js home-sender.config.js", "sender:foreign": "node src/sender.js foreign-sender.config.js" }, From 3a0102d2cd6ff3d8ae85aafeaae4bf519f1633f7 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 11 Jul 2018 15:51:25 -0300 Subject: [PATCH 016/119] Refactor mode config --- .env.example | 3 +- README.md | 18 +++------- config/affirmation-request-watcher.config.js | 29 ++++++++++----- config/base-erc-erc-watcher.config.js | 36 ------------------- ...tcher.config.js => base-watcher.config.js} | 15 ++++++-- config/collected-signatures-watcher.config.js | 8 ++--- .../erc-affirmation-request-watcher.config.js | 14 -------- ...erc-collected-signatures-watcher.config.js | 9 ----- .../erc-signature-request-watcher.config.js | 9 ----- config/signature-request-watcher.config.js | 8 ++--- e2e/docker-compose.yml | 23 ++++++++++-- e2e/run-tests.sh | 6 ++-- package.json | 3 -- src/sender.js | 4 ++- src/watcher.js | 2 +- tests/sendUserTxToErcForeign.js | 6 ++-- tests/sendUserTxToErcHome.js | 6 ++-- tests/sendUserTxToHome.js | 1 - 18 files changed, 81 insertions(+), 119 deletions(-) delete mode 100644 config/base-erc-erc-watcher.config.js rename config/{base-native-erc-watcher.config.js => base-watcher.config.js} (62%) delete mode 100644 config/erc-affirmation-request-watcher.config.js delete mode 100644 config/erc-collected-signatures-watcher.config.js delete mode 100644 config/erc-signature-request-watcher.config.js diff --git a/.env.example b/.env.example index cca956d..c5a632f 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +#BRIDGE_MODE=ERC_TO_ERC HOME_POLLING_INTERVAL=5000 FOREIGN_POLLING_INTERVAL=1000 ALLOW_HTTP=no @@ -6,8 +7,6 @@ FOREIGN_RPC_URL=https://kovan.infura.io/mew HOME_BRIDGE_ADDRESS=0x86b621b839Ff86Bb59fA57015b02318f9a870d80 FOREIGN_BRIDGE_ADDRESS=0x8dDCa2eB86D22FcB93342753515986e258A90169 ERC20_TOKEN_ADDRESS=0x7AA0A62497B5f4120fbe5D26c95bC612c2b96C14 -ERC_HOME_BRIDGE_ADDRESS= -ERC_FOREIGN_BRIDGE_ADDRESS= VALIDATOR_ADDRESS=0xb8988B690910913c97A090c3a6f80FAD8b3A4683 VALIDATOR_ADDRESS_PRIVATE_KEY= diff --git a/README.md b/README.md index 273589e..33d9c05 100644 --- a/README.md +++ b/README.md @@ -51,27 +51,19 @@ Make sure your `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is same ### Erc to Erc mode - - `npm run watcher:erc:signature-request` - - `npm run watcher:erc:collected-signatures` - - `npm run watcher:erc:affirmation-request` - - `npm run sender:home` - - `npm run sender:foreign` - -To send deposits to home contract run `node tests/sendUserTxToErcHome.js` - -To send withdrawals to foreign contract run `node tests/sendUserTxToErcForeign.js` -### Run both bridge modes together +On `.env` file set `BRIDGE_MODE=ERC_TO_ERC` - `npm run watcher:signature-request` - `npm run watcher:collected-signatures` - `npm run watcher:affirmation-request` - - `npm run watcher:erc:signature-request` - - `npm run watcher:erc:collected-signatures` - - `npm run watcher:erc:affirmation-request` - `npm run sender:home` - `npm run sender:foreign` +To send deposits to home contract run `node tests/sendUserTxToErcHome.js` + +To send withdrawals to foreign contract run `node tests/sendUserTxToErcForeign.js` + ### Run with Docker - Start RabbitMQ and Redis: `docker-compose up -d` diff --git a/config/affirmation-request-watcher.config.js b/config/affirmation-request-watcher.config.js index a15f1e5..9b76013 100644 --- a/config/affirmation-request-watcher.config.js +++ b/config/affirmation-request-watcher.config.js @@ -1,9 +1,22 @@ -const nativeErcConfig = require('./base-native-erc-watcher.config') +require('dotenv').config() +const baseConfig = require('./base-watcher.config') +const erc20Abi = require('../abis/ERC20.abi') -module.exports = { - ...nativeErcConfig.bridgeConfig, - ...nativeErcConfig.foreignConfig, - event: 'UserRequestForAffirmation', - queue: 'home', - id: 'affirmation-request' -} +module.exports = baseConfig.isErcToErc + ? { + ...baseConfig.bridgeConfig, + ...baseConfig.foreignConfig, + event: 'Transfer', + eventContractAddress: process.env.ERC20_TOKEN_ADDRESS, + eventAbi: erc20Abi, + eventFilter: { to: process.env.FOREIGN_BRIDGE_ADDRESS }, + queue: 'home', + id: 'erc-affirmation-request' + } + : { + ...baseConfig.bridgeConfig, + ...baseConfig.foreignConfig, + event: 'UserRequestForAffirmation', + queue: 'home', + id: 'affirmation-request' + } diff --git a/config/base-erc-erc-watcher.config.js b/config/base-erc-erc-watcher.config.js deleted file mode 100644 index e53fc44..0000000 --- a/config/base-erc-erc-watcher.config.js +++ /dev/null @@ -1,36 +0,0 @@ -require('dotenv').config() - -const homeAbi = require('../abis/HomeBridgeErcToErc.abi') -const foreignAbi = require('../abis/ForeignBridgeErcToErc.abi') - -const bridgeConfig = { - homeBridgeAddress: process.env.ERC_HOME_BRIDGE_ADDRESS, - homeBridgeAbi: homeAbi, - foreignBridgeAddress: process.env.ERC_FOREIGN_BRIDGE_ADDRESS, - foreignBridgeAbi: foreignAbi, - eventFilter: {} -} - -const homeConfig = { - url: process.env.HOME_RPC_URL, - eventContractAddress: process.env.ERC_HOME_BRIDGE_ADDRESS, - eventAbi: homeAbi, - bridgeContractAddress: process.env.ERC_HOME_BRIDGE_ADDRESS, - bridgeAbi: homeAbi, - pollingInterval: process.env.HOME_POLLING_INTERVAL -} - -const foreignConfig = { - url: process.env.FOREIGN_RPC_URL, - eventContractAddress: process.env.ERC_FOREIGN_BRIDGE_ADDRESS, - eventAbi: foreignAbi, - bridgeContractAddress: process.env.ERC_FOREIGN_BRIDGE_ADDRESS, - bridgeAbi: foreignAbi, - pollingInterval: process.env.FOREIGN_POLLING_INTERVAL -} - -module.exports = { - bridgeConfig, - homeConfig, - foreignConfig -} diff --git a/config/base-native-erc-watcher.config.js b/config/base-watcher.config.js similarity index 62% rename from config/base-native-erc-watcher.config.js rename to config/base-watcher.config.js index 7c50f98..c9d79ae 100644 --- a/config/base-native-erc-watcher.config.js +++ b/config/base-watcher.config.js @@ -1,7 +1,15 @@ require('dotenv').config() -const homeAbi = require('../abis/HomeBridgeNativeToErc.abi') -const foreignAbi = require('../abis/ForeignBridgeNativeToErc.abi') +const homeNativeAbi = require('../abis/HomeBridgeNativeToErc.abi') +const foreignNativeAbi = require('../abis/ForeignBridgeNativeToErc.abi') + +const homeErcAbi = require('../abis/HomeBridgeErcToErc.abi') +const foreignErcAbi = require('../abis/ForeignBridgeErcToErc.abi') + +const isErcToErc = process.env.BRIDGE_MODE && process.env.BRIDGE_MODE === 'ERC_TO_ERC' + +const homeAbi = isErcToErc ? homeErcAbi : homeNativeAbi +const foreignAbi = isErcToErc ? foreignErcAbi : foreignNativeAbi const bridgeConfig = { homeBridgeAddress: process.env.HOME_BRIDGE_ADDRESS, @@ -32,5 +40,6 @@ const foreignConfig = { module.exports = { bridgeConfig, homeConfig, - foreignConfig + foreignConfig, + isErcToErc } diff --git a/config/collected-signatures-watcher.config.js b/config/collected-signatures-watcher.config.js index 7a6fcd2..fe02538 100644 --- a/config/collected-signatures-watcher.config.js +++ b/config/collected-signatures-watcher.config.js @@ -1,9 +1,9 @@ -const nativeErcConfig = require('./base-native-erc-watcher.config') +const baseConfig = require('./base-watcher.config') module.exports = { - ...nativeErcConfig.bridgeConfig, - ...nativeErcConfig.homeConfig, + ...baseConfig.bridgeConfig, + ...baseConfig.homeConfig, event: 'CollectedSignatures', queue: 'foreign', - id: 'collected-signatures' + id: baseConfig.isErcToErc ? 'erc-collected-signatures' : 'collected-signatures' } diff --git a/config/erc-affirmation-request-watcher.config.js b/config/erc-affirmation-request-watcher.config.js deleted file mode 100644 index 0ce78ee..0000000 --- a/config/erc-affirmation-request-watcher.config.js +++ /dev/null @@ -1,14 +0,0 @@ -require('dotenv').config() -const ercErcConfig = require('./base-erc-erc-watcher.config') -const erc20Abi = require('../abis/ERC20.abi') - -module.exports = { - ...ercErcConfig.bridgeConfig, - ...ercErcConfig.foreignConfig, - event: 'Transfer', - eventContractAddress: process.env.ERC20_TOKEN_ADDRESS, - eventAbi: erc20Abi, - eventFilter: { to: process.env.ERC_FOREIGN_BRIDGE_ADDRESS }, - queue: 'home', - id: 'erc-transfer' -} diff --git a/config/erc-collected-signatures-watcher.config.js b/config/erc-collected-signatures-watcher.config.js deleted file mode 100644 index 5ebb5b6..0000000 --- a/config/erc-collected-signatures-watcher.config.js +++ /dev/null @@ -1,9 +0,0 @@ -const ercErcConfig = require('./base-erc-erc-watcher.config') - -module.exports = { - ...ercErcConfig.bridgeConfig, - ...ercErcConfig.homeConfig, - event: 'CollectedSignatures', - queue: 'foreign', - id: 'erc-collected-signatures' -} diff --git a/config/erc-signature-request-watcher.config.js b/config/erc-signature-request-watcher.config.js deleted file mode 100644 index 5d400b2..0000000 --- a/config/erc-signature-request-watcher.config.js +++ /dev/null @@ -1,9 +0,0 @@ -const ercErcConfig = require('./base-erc-erc-watcher.config') - -module.exports = { - ...ercErcConfig.bridgeConfig, - ...ercErcConfig.homeConfig, - event: 'UserRequestForSignature', - queue: 'home', - id: 'erc-signature-request' -} diff --git a/config/signature-request-watcher.config.js b/config/signature-request-watcher.config.js index cbaf640..d460351 100644 --- a/config/signature-request-watcher.config.js +++ b/config/signature-request-watcher.config.js @@ -1,9 +1,9 @@ -const nativeErcConfig = require('./base-native-erc-watcher.config') +const baseConfig = require('./base-watcher.config') module.exports = { - ...nativeErcConfig.bridgeConfig, - ...nativeErcConfig.homeConfig, + ...baseConfig.bridgeConfig, + ...baseConfig.homeConfig, event: 'UserRequestForSignature', queue: 'home', - id: 'signature-request' + id: baseConfig.isErcToErc ? 'erc-signature-request' : 'signature-request' } diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index f5acd25..02b0946 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -18,9 +18,28 @@ services: - FOREIGN_RPC_URL=http://parity2:8545 - HOME_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624 - FOREIGN_BRIDGE_ADDRESS=0x2B6871b9B02F73fa24F4864322CdC78604207769 + - ERC20_TOKEN_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B + - VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b + - VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 + - REDIS_LOCK_TTL=1000 + - GAS_PRICE_SPEED_TYPE=standard + - GAS_PRICE_FALLBACK=1 + - HOME_POLLING_INTERVAL=500 + - FOREIGN_POLLING_INTERVAL=500 + - ALLOW_HTTP=yes + command: "true" + bridge-erc: + build: .. + environment: + - NODE_ENV=production + - BRIDGE_MODE=ERC_TO_ERC + - QUEUE_URL=amqp://rabbit + - REDIS_URL=redis://redis + - HOME_RPC_URL=http://parity1:8545 + - FOREIGN_RPC_URL=http://parity2:8545 + - HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E + - FOREIGN_BRIDGE_ADDRESS=0xD0B9745831dDA9cbb47D0dEa904972cDcecc52e8 - ERC20_TOKEN_ADDRESS=0x7777D2BF48993088dC1ceD832863b80427Ff5Ec9 - - ERC_HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E - - ERC_FOREIGN_BRIDGE_ADDRESS=0xD0B9745831dDA9cbb47D0dEa904972cDcecc52e8 - BRIDGEABLE_TOKEN_ADDRESS=0x792455a6bCb62Ed4C4362D323E0590654CA4765c - VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b - VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 diff --git a/e2e/run-tests.sh b/e2e/run-tests.sh index 78b40c1..094bce3 100755 --- a/e2e/run-tests.sh +++ b/e2e/run-tests.sh @@ -3,9 +3,9 @@ docker-compose run e2e npm run deploy docker-compose run -d bridge npm run watcher:signature-request docker-compose run -d bridge npm run watcher:collected-signatures docker-compose run -d bridge npm run watcher:affirmation-request -docker-compose run -d bridge npm run watcher:erc:signature-request -docker-compose run -d bridge npm run watcher:erc:collected-signatures -docker-compose run -d bridge npm run watcher:erc:affirmation-request +docker-compose run -d bridge-erc npm run watcher:signature-request +docker-compose run -d bridge-erc npm run watcher:collected-signatures +docker-compose run -d bridge-erc npm run watcher:affirmation-request docker-compose run -d bridge npm run sender:home docker-compose run -d bridge npm run sender:foreign docker-compose run e2e npm start diff --git a/package.json b/package.json index 444af04..5f518cb 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,6 @@ "watcher:signature-request": "node src/watcher.js signature-request-watcher.config.js", "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher.config.js", "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js", - "watcher:erc:signature-request": "node src/watcher.js erc-signature-request-watcher.config.js", - "watcher:erc:collected-signatures": "node src/watcher.js erc-collected-signatures-watcher.config.js", - "watcher:erc:affirmation-request": "node src/watcher.js erc-affirmation-request-watcher.config.js", "sender:home": "node src/sender.js home-sender.config.js", "sender:foreign": "node src/sender.js foreign-sender.config.js" }, diff --git a/src/sender.js b/src/sender.js index 058e144..b199ce8 100644 --- a/src/sender.js +++ b/src/sender.js @@ -99,7 +99,9 @@ async function main({ msg, ackMsg, nackMsg, sendToQueue }) { } catch (e) { console.error(e.message) console.error(`Tx Failed for event Tx ${job.transactionReference}`) - failedTx.push(job) + if (!e.message.includes('Transaction with the same hash was already imported')) { + failedTx.push(job) + } if ( e.message.includes('Transaction nonce is too low') || diff --git a/src/watcher.js b/src/watcher.js index 2622d62..083e734 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -78,7 +78,7 @@ function processEvents(events) { return processCollectedSignatures(events) case 'affirmation-request': return processAffirmationRequests(events) - case 'erc-transfer': + case 'erc-affirmation-request': return processTransfers(events) default: return [] diff --git a/tests/sendUserTxToErcForeign.js b/tests/sendUserTxToErcForeign.js index 6427615..86240ea 100644 --- a/tests/sendUserTxToErcForeign.js +++ b/tests/sendUserTxToErcForeign.js @@ -6,7 +6,7 @@ const { sendTx, sendRawTx } = require('../src/tx/sendTx') const { USER_ADDRESS, USER_ADDRESS_PRIVATE_KEY, - ERC_FOREIGN_BRIDGE_ADDRESS, + FOREIGN_BRIDGE_ADDRESS, FOREIGN_RPC_URL, FOREIGN_MIN_AMOUNT_PER_TX, ERC20_TOKEN_ADDRESS, @@ -60,10 +60,10 @@ async function main() { let actualSent = 0 for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { const gasLimit = await poa20.methods - .transfer(ERC_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) + .transfer(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) .estimateGas({ from: USER_ADDRESS }) const data = await poa20.methods - .transfer(ERC_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) + .transfer(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ rpcUrl: FOREIGN_RPC_URL, diff --git a/tests/sendUserTxToErcHome.js b/tests/sendUserTxToErcHome.js index 96dd697..e8db6d4 100644 --- a/tests/sendUserTxToErcHome.js +++ b/tests/sendUserTxToErcHome.js @@ -6,7 +6,7 @@ const { sendTx, sendRawTx } = require('../src/tx/sendTx') const { USER_ADDRESS, USER_ADDRESS_PRIVATE_KEY, - ERC_HOME_BRIDGE_ADDRESS, + HOME_BRIDGE_ADDRESS, HOME_RPC_URL, HOME_MIN_AMOUNT_PER_TX, BRIDGEABLE_TOKEN_ADDRESS, @@ -64,10 +64,10 @@ async function main() { let actualSent = 0 for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) { const gasLimit = await erc677.methods - .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') + .transferAndCall(HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') .estimateGas({ from: USER_ADDRESS }) const data = await erc677.methods - .transferAndCall(ERC_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') + .transferAndCall(HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ rpcUrl: HOME_RPC_URL, diff --git a/tests/sendUserTxToHome.js b/tests/sendUserTxToHome.js index f77e0ac..edd192f 100644 --- a/tests/sendUserTxToHome.js +++ b/tests/sendUserTxToHome.js @@ -17,7 +17,6 @@ const web3Home = new Web3(homeProvider) async function main() { try { - console.log(HOME_RPC_URL) const homeChaindId = await sendRawTx({ url: HOME_RPC_URL, params: [], From 211b463c7dde44f57a2c9c91739814190f6c7be7 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 26 Jul 2018 11:24:48 -0300 Subject: [PATCH 017/119] Fix merge conflicts --- config/affirmation-request-watcher.config.js | 8 ++- config/base-watcher.config.js | 6 +- config/collected-signatures-watcher-config.js | 15 ---- config/collected-signatures-watcher.config.js | 5 +- config/deposit-watcher-config.js | 15 ---- config/signature-request-watcher.config.js | 5 +- config/withdraw-watcher-config.js | 15 ---- package.json | 2 +- {tests => scripts}/sendUserTxToErcForeign.js | 5 +- {tests => scripts}/sendUserTxToErcHome.js | 6 +- {tests => scripts}/sendUserTxToForeign.js | 6 +- src/events/processAffirmationRequests.js | 20 ++++-- src/events/processCollectedSignatures.js | 60 ++++++++-------- src/events/processDeposits.js | 69 ------------------- src/events/processSignatureRequests.js | 18 +++-- src/events/processWithdraw.js | 55 --------------- 16 files changed, 90 insertions(+), 220 deletions(-) delete mode 100644 config/collected-signatures-watcher-config.js delete mode 100644 config/deposit-watcher-config.js delete mode 100644 config/withdraw-watcher-config.js rename {tests => scripts}/sendUserTxToErcForeign.js (94%) rename {tests => scripts}/sendUserTxToErcHome.js (94%) rename {tests => scripts}/sendUserTxToForeign.js (94%) delete mode 100644 src/events/processDeposits.js delete mode 100644 src/events/processWithdraw.js diff --git a/config/affirmation-request-watcher.config.js b/config/affirmation-request-watcher.config.js index 9b76013..f90f055 100644 --- a/config/affirmation-request-watcher.config.js +++ b/config/affirmation-request-watcher.config.js @@ -2,6 +2,8 @@ require('dotenv').config() const baseConfig = require('./base-watcher.config') const erc20Abi = require('../abis/ERC20.abi') +const id = baseConfig.isErcToErc ? 'erc-affirmation-request' : 'affirmation-request' + module.exports = baseConfig.isErcToErc ? { ...baseConfig.bridgeConfig, @@ -11,12 +13,14 @@ module.exports = baseConfig.isErcToErc eventAbi: erc20Abi, eventFilter: { to: process.env.FOREIGN_BRIDGE_ADDRESS }, queue: 'home', - id: 'erc-affirmation-request' + name: `watcher-${id}`, + id } : { ...baseConfig.bridgeConfig, ...baseConfig.foreignConfig, event: 'UserRequestForAffirmation', queue: 'home', - id: 'affirmation-request' + name: `watcher-${id}`, + id } diff --git a/config/base-watcher.config.js b/config/base-watcher.config.js index c9d79ae..91d87d3 100644 --- a/config/base-watcher.config.js +++ b/config/base-watcher.config.js @@ -25,7 +25,8 @@ const homeConfig = { eventAbi: homeAbi, bridgeContractAddress: process.env.HOME_BRIDGE_ADDRESS, bridgeAbi: homeAbi, - pollingInterval: process.env.HOME_POLLING_INTERVAL + pollingInterval: process.env.HOME_POLLING_INTERVAL, + startBlock: process.env.HOME_START_BLOCK } const foreignConfig = { @@ -34,7 +35,8 @@ const foreignConfig = { eventAbi: foreignAbi, bridgeContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, bridgeAbi: foreignAbi, - pollingInterval: process.env.FOREIGN_POLLING_INTERVAL + pollingInterval: process.env.FOREIGN_POLLING_INTERVAL, + startBlock: process.env.FOREIGN_START_BLOCK } module.exports = { diff --git a/config/collected-signatures-watcher-config.js b/config/collected-signatures-watcher-config.js deleted file mode 100644 index c1bff45..0000000 --- a/config/collected-signatures-watcher-config.js +++ /dev/null @@ -1,15 +0,0 @@ -require('dotenv').config() - -const HomeABI = require('../abis/HomeBridge.abi') - -module.exports = { - event: 'CollectedSignatures', - name: 'watcher-collected-signatures', - url: process.env.HOME_RPC_URL, - contractAddress: process.env.HOME_BRIDGE_ADDRESS, - abi: HomeABI, - startBlock: process.env.HOME_START_BLOCK, - queue: 'foreign', - id: 'collected-signatures', - pollingInterval: process.env.HOME_POLLING_INTERVAL -} diff --git a/config/collected-signatures-watcher.config.js b/config/collected-signatures-watcher.config.js index fe02538..2925fc4 100644 --- a/config/collected-signatures-watcher.config.js +++ b/config/collected-signatures-watcher.config.js @@ -1,9 +1,12 @@ const baseConfig = require('./base-watcher.config') +const id = baseConfig.isErcToErc ? 'erc-collected-signatures' : 'collected-signatures' + module.exports = { ...baseConfig.bridgeConfig, ...baseConfig.homeConfig, event: 'CollectedSignatures', queue: 'foreign', - id: baseConfig.isErcToErc ? 'erc-collected-signatures' : 'collected-signatures' + name: `watcher-${id}`, + id } diff --git a/config/deposit-watcher-config.js b/config/deposit-watcher-config.js deleted file mode 100644 index 4c5e9c4..0000000 --- a/config/deposit-watcher-config.js +++ /dev/null @@ -1,15 +0,0 @@ -require('dotenv').config() - -const HomeABI = require('../abis/HomeBridge.abi') - -module.exports = { - event: 'Deposit', - name: 'watcher-deposit', - url: process.env.HOME_RPC_URL, - contractAddress: process.env.HOME_BRIDGE_ADDRESS, - abi: HomeABI, - startBlock: process.env.HOME_START_BLOCK, - queue: 'home', - id: 'deposit', - pollingInterval: process.env.HOME_POLLING_INTERVAL -} diff --git a/config/signature-request-watcher.config.js b/config/signature-request-watcher.config.js index d460351..19d7871 100644 --- a/config/signature-request-watcher.config.js +++ b/config/signature-request-watcher.config.js @@ -1,9 +1,12 @@ const baseConfig = require('./base-watcher.config') +const id = baseConfig.isErcToErc ? 'erc-signature-request' : 'signature-request' + module.exports = { ...baseConfig.bridgeConfig, ...baseConfig.homeConfig, event: 'UserRequestForSignature', queue: 'home', - id: baseConfig.isErcToErc ? 'erc-signature-request' : 'signature-request' + name: `watcher-${id}`, + id } diff --git a/config/withdraw-watcher-config.js b/config/withdraw-watcher-config.js deleted file mode 100644 index 5fa7fc3..0000000 --- a/config/withdraw-watcher-config.js +++ /dev/null @@ -1,15 +0,0 @@ -require('dotenv').config() - -const ForeignABI = require('../abis/ForeignBridge.abi') - -module.exports = { - event: 'Withdraw', - name: 'watcher-withdraw', - url: process.env.FOREIGN_RPC_URL, - contractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, - abi: ForeignABI, - startBlock: process.env.FOREIGN_START_BLOCK, - queue: 'home', - id: 'withdraw', - pollingInterval: process.env.FOREIGN_POLLING_INTERVAL -} diff --git a/package.json b/package.json index e61257a..a278aa9 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js", "sender:home": "node src/sender.js home-sender.config.js", "sender:foreign": "node src/sender.js foreign-sender.config.js", - "dev": "concurrently -n 'watcher:deposit,watcher:collected-signatures,watcher:withdraw,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:deposit' 'npm run watcher:collected-signatures' 'npm run watcher:withdraw' 'npm run sender:home' 'npm run sender:foreign'", + "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:signature-request' 'npm run watcher:collected-signatures' 'npm run watcher:affirmation-request' 'npm run sender:home' 'npm run sender:foreign'", "test": "NODE_ENV=test mocha", "coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha", "postinstall": "npm install --prefix e2e" diff --git a/tests/sendUserTxToErcForeign.js b/scripts/sendUserTxToErcForeign.js similarity index 94% rename from tests/sendUserTxToErcForeign.js rename to scripts/sendUserTxToErcForeign.js index 86240ea..03f5abf 100644 --- a/tests/sendUserTxToErcForeign.js +++ b/scripts/sendUserTxToErcForeign.js @@ -9,10 +9,11 @@ const { FOREIGN_BRIDGE_ADDRESS, FOREIGN_RPC_URL, FOREIGN_MIN_AMOUNT_PER_TX, - ERC20_TOKEN_ADDRESS, - NUMBER_OF_DEPOSITS_TO_SEND + ERC20_TOKEN_ADDRESS } = process.env +const NUMBER_OF_DEPOSITS_TO_SEND = process.argv[2] || process.env.NUMBER_OF_DEPOSITS_TO_SEND || 1 + const ERC20_ABI = [ { constant: false, diff --git a/tests/sendUserTxToErcHome.js b/scripts/sendUserTxToErcHome.js similarity index 94% rename from tests/sendUserTxToErcHome.js rename to scripts/sendUserTxToErcHome.js index e8db6d4..b354af3 100644 --- a/tests/sendUserTxToErcHome.js +++ b/scripts/sendUserTxToErcHome.js @@ -9,10 +9,12 @@ const { HOME_BRIDGE_ADDRESS, HOME_RPC_URL, HOME_MIN_AMOUNT_PER_TX, - BRIDGEABLE_TOKEN_ADDRESS, - NUMBER_OF_WITHDRAWALS_TO_SEND + BRIDGEABLE_TOKEN_ADDRESS } = process.env +const NUMBER_OF_WITHDRAWALS_TO_SEND = + process.argv[2] || process.env.NUMBER_OF_WITHDRAWALS_TO_SEND || 1 + const BRIDGEABLE_TOKEN_ABI = [ { constant: false, diff --git a/tests/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js similarity index 94% rename from tests/sendUserTxToForeign.js rename to scripts/sendUserTxToForeign.js index 413453b..63497ce 100644 --- a/tests/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -9,10 +9,12 @@ const { FOREIGN_BRIDGE_ADDRESS, FOREIGN_RPC_URL, FOREIGN_MIN_AMOUNT_PER_TX, - ERC20_TOKEN_ADDRESS, - NUMBER_OF_WITHDRAWALS_TO_SEND + ERC20_TOKEN_ADDRESS } = process.env +const NUMBER_OF_WITHDRAWALS_TO_SEND = + process.argv[2] || process.env.NUMBER_OF_WITHDRAWALS_TO_SEND || 1 + const ERC20_ABI = [ { constant: false, diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js index df68fcb..6c33ae9 100644 --- a/src/events/processAffirmationRequests.js +++ b/src/events/processAffirmationRequests.js @@ -1,5 +1,6 @@ require('dotenv').config() const Web3 = require('web3') +const logger = require('../services/logger') const { HOME_RPC_URL, VALIDATOR_ADDRESS } = process.env @@ -11,19 +12,28 @@ function processAffirmationRequestsBuilder(config) { return async function processAffirmationRequests(affirmationRequests) { const txToSend = [] - const callbacks = affirmationRequests.map(async (affirmationRequest, index) => { + const callbacks = affirmationRequests.map(async affirmationRequest => { const { recipient, value } = affirmationRequest.returnValues + logger.info( + { eventTransactionHash: affirmationRequest.transactionHash, sender: recipient, value }, + `Processing affirmationRequest ${affirmationRequest.transactionHash}` + ) + let gasEstimate try { gasEstimate = await homeBridge.methods .executeAffirmation(recipient, value, affirmationRequest.transactionHash) .estimateGas({ from: VALIDATOR_ADDRESS }) } catch (e) { - console.log( - index + 1, - '# already processed UserRequestForAffirmation', - affirmationRequest.transactionHash + if (e.message.includes('Invalid JSON RPC response')) { + throw new Error( + `RPC Connection Error: executeAffirmation Gas Estimate cannot be obtained.` + ) + } + logger.info( + { eventTransactionHash: affirmationRequest.transactionHash }, + `Already processed affirmationRequest ${affirmationRequest.transactionHash}` ) return } diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index 1ccecba..9ed527d 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -28,10 +28,10 @@ function processCollectedSignaturesBuilder(config) { if (authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(VALIDATOR_ADDRESS)) { logger.info( - { eventTransactionHash: colSignature.transactionHash }, - `Processing CollectedSignatures ${colSignature.transactionHash}` - ) - const message = await homeBridge.methods.message(messageHash).call() + { eventTransactionHash: colSignature.transactionHash }, + `Processing CollectedSignatures ${colSignature.transactionHash}` + ) + const message = await homeBridge.methods.message(messageHash).call() const requiredSignatures = [] requiredSignatures.length = NumberOfCollectedSignatures @@ -48,35 +48,39 @@ function processCollectedSignaturesBuilder(config) { await Promise.all(signaturePromises) - let gasEstimate - try { - gasEstimate = await foreignBridge.methods - .executeSignatures(v, r, s, message) - .estimateGas() - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error(`RPC Connection Error: deposit Gas Estimate cannot be obtained.`) + let gasEstimate + try { + gasEstimate = await foreignBridge.methods + .executeSignatures(v, r, s, message) + .estimateGas() + } catch (e) { + if (e.message.includes('Invalid JSON RPC response')) { + throw new Error( + `RPC Connection Error: executeSignatures Gas Estimate cannot be obtained.` + ) + } + logger.info( + { eventTransactionHash: colSignature.transactionHash }, + `Already processed CollectedSignatures ${colSignature.transactionHash}` + ) + return } + const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() + txToSend.push({ + data, + gasEstimate, + transactionReference: colSignature.transactionHash, + to: config.foreignBridgeAddress + }) + } else { logger.info( { eventTransactionHash: colSignature.transactionHash }, - `Already processed CollectedSignatures ${colSignature.transactionHash}` + `Validator not responsible for relaying CollectedSignatures ${ + colSignature.transactionHash + }` ) - return } - const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() - txToSend.push({ - data, - gasEstimate, - transactionReference: colSignature.transactionHash, - to: config.foreignBridgeAddress - }) - } else { - logger.info( - { eventTransactionHash: colSignature.transactionHash }, - `Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}` - ) - } - }) + }) await Promise.all(callbacks) diff --git a/src/events/processDeposits.js b/src/events/processDeposits.js deleted file mode 100644 index ee3934a..0000000 --- a/src/events/processDeposits.js +++ /dev/null @@ -1,69 +0,0 @@ -require('dotenv').config() -const Web3 = require('web3') -const logger = require('../services/logger') -const { createMessage } = require('../utils/message') - -const { - HOME_RPC_URL, - HOME_BRIDGE_ADDRESS, - VALIDATOR_ADDRESS, - VALIDATOR_ADDRESS_PRIVATE_KEY -} = process.env - -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) -const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridge.abi') - -const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) - -async function processDeposits(deposits) { - const txToSend = [] - - const callbacks = deposits.map(async deposit => { - const { recipient, value } = deposit.returnValues - - logger.info( - { eventTransactionHash: deposit.transactionHash, sender: recipient, value }, - `Processing Deposit ${deposit.transactionHash}` - ) - - const message = createMessage({ - recipient, - value, - transactionHash: deposit.transactionHash - }) - - const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) - - let gasEstimate - try { - gasEstimate = await homeBridge.methods - .submitSignature(signature.signature, message) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error(`RPC Connection Error: submitSignature Gas Estimate cannot be obtained.`) - } - logger.info( - { eventTransactionHash: deposit.transactionHash }, - `Already processed Deposit ${deposit.transactionHash}` - ) - return - } - - const data = await homeBridge.methods - .submitSignature(signature.signature, message) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: deposit.transactionHash - }) - }) - - await Promise.all(callbacks) - return txToSend -} - -module.exports = processDeposits diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index a426272..d6c6c04 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,5 +1,6 @@ require('dotenv').config() const Web3 = require('web3') +const logger = require('../services/logger') const { createMessage } = require('../utils/message') const { HOME_RPC_URL, VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env @@ -12,9 +13,14 @@ function processSignatureRequestsBuilder(config) { return async function processSignatureRequests(signatureRequests) { const txToSend = [] - const callbacks = signatureRequests.map(async (signatureRequest, index) => { + const callbacks = signatureRequests.map(async signatureRequest => { const { recipient, value } = signatureRequest.returnValues + logger.info( + { eventTransactionHash: signatureRequest.transactionHash, sender: recipient, value }, + `Processing signatureRequest ${signatureRequest.transactionHash}` + ) + const message = createMessage({ recipient, value, @@ -29,10 +35,12 @@ function processSignatureRequestsBuilder(config) { .submitSignature(signature.signature, message) .estimateGas({ from: VALIDATOR_ADDRESS }) } catch (e) { - console.log( - index + 1, - '# already processed UserRequestForSignature ', - signatureRequest.transactionHash + if (e.message.includes('Invalid JSON RPC response')) { + throw new Error(`RPC Connection Error: submitSignature Gas Estimate cannot be obtained.`) + } + logger.info( + { eventTransactionHash: signatureRequest.transactionHash }, + `Already processed signatureRequest ${signatureRequest.transactionHash}` ) return } diff --git a/src/events/processWithdraw.js b/src/events/processWithdraw.js deleted file mode 100644 index 53ca28b..0000000 --- a/src/events/processWithdraw.js +++ /dev/null @@ -1,55 +0,0 @@ -require('dotenv').config() -const Web3 = require('web3') -const logger = require('../services/logger') - -const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, VALIDATOR_ADDRESS } = process.env - -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) -const web3Home = new Web3(homeProvider) -const HomeABI = require('../../abis/HomeBridge.abi') - -const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) - -async function processWithdraw(withdrawals) { - const txToSend = [] - - const callbacks = withdrawals.map(async withdrawal => { - const { recipient, value } = withdrawal.returnValues - - logger.info( - { eventTransactionHash: withdrawal.transactionHash, sender: recipient, value }, - `Processing Withdraw ${withdrawal.transactionHash}` - ) - - let gasEstimate - try { - gasEstimate = await homeBridge.methods - .withdraw(recipient, value, withdrawal.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error(`RPC Connection Error: withdraw Gas Estimate cannot be obtained.`) - } - logger.info( - { eventTransactionHash: withdrawal.transactionHash }, - `Already processed Withdraw ${withdrawal.transactionHash}` - ) - return - } - - const data = await homeBridge.methods - .withdraw(recipient, value, withdrawal.transactionHash) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: withdrawal.transactionHash - }) - }) - - await Promise.all(callbacks) - return txToSend -} - -module.exports = processWithdraw From 2452a0b0587e845cce9d3af0a2373beb646cf833 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 26 Jul 2018 11:47:20 -0300 Subject: [PATCH 018/119] Fix gasPrice abi config --- config/affirmation-request-watcher.config.js | 2 +- config/{base-watcher.config.js => base.config.js} | 0 config/collected-signatures-watcher.config.js | 2 +- config/signature-request-watcher.config.js | 2 +- src/services/gasPrice.js | 10 ++++++++-- 5 files changed, 11 insertions(+), 5 deletions(-) rename config/{base-watcher.config.js => base.config.js} (100%) diff --git a/config/affirmation-request-watcher.config.js b/config/affirmation-request-watcher.config.js index f90f055..b1961aa 100644 --- a/config/affirmation-request-watcher.config.js +++ b/config/affirmation-request-watcher.config.js @@ -1,5 +1,5 @@ require('dotenv').config() -const baseConfig = require('./base-watcher.config') +const baseConfig = require('./base.config') const erc20Abi = require('../abis/ERC20.abi') const id = baseConfig.isErcToErc ? 'erc-affirmation-request' : 'affirmation-request' diff --git a/config/base-watcher.config.js b/config/base.config.js similarity index 100% rename from config/base-watcher.config.js rename to config/base.config.js diff --git a/config/collected-signatures-watcher.config.js b/config/collected-signatures-watcher.config.js index 2925fc4..dcca2a7 100644 --- a/config/collected-signatures-watcher.config.js +++ b/config/collected-signatures-watcher.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('./base-watcher.config') +const baseConfig = require('./base.config') const id = baseConfig.isErcToErc ? 'erc-collected-signatures' : 'collected-signatures' diff --git a/config/signature-request-watcher.config.js b/config/signature-request-watcher.config.js index 19d7871..d80e371 100644 --- a/config/signature-request-watcher.config.js +++ b/config/signature-request-watcher.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('./base-watcher.config') +const baseConfig = require('./base.config') const id = baseConfig.isErcToErc ? 'erc-signature-request' : 'signature-request' diff --git a/src/services/gasPrice.js b/src/services/gasPrice.js index 6d13243..388c811 100644 --- a/src/services/gasPrice.js +++ b/src/services/gasPrice.js @@ -1,10 +1,16 @@ require('dotenv').config() const Web3 = require('web3') const fetch = require('node-fetch') -const HomeABI = require('../../abis/HomeBridge.abi') -const ForeignABI = require('../../abis/ForeignBridge.abi') +const { isErcToErc } = require('../../config/base.config') +const HomeNativeABI = require('../../abis/HomeBridgeNativeToErc.abi') +const ForeignNativeABI = require('../../abis/ForeignBridgeNativeToErc.abi') +const HomeErcABI = require('../../abis/HomeBridgeErcToErc.abi') +const ForeignErcABI = require('../../abis/ForeignBridgeErcToErc.abi') const logger = require('../services/logger') +const HomeABI = isErcToErc ? HomeErcABI : HomeNativeABI +const ForeignABI = isErcToErc ? ForeignNativeABI : ForeignErcABI + const { FOREIGN_BRIDGE_ADDRESS, FOREIGN_GAS_PRICE_FALLBACK, From 8260ef2cc5c06bf4f964534dd338d8014fb95874 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 26 Jul 2018 12:23:23 -0300 Subject: [PATCH 019/119] Remove unused script --- scripts/sendUserTxToForeignWithdraw.js | 91 -------------------------- 1 file changed, 91 deletions(-) delete mode 100644 scripts/sendUserTxToForeignWithdraw.js diff --git a/scripts/sendUserTxToForeignWithdraw.js b/scripts/sendUserTxToForeignWithdraw.js deleted file mode 100644 index 6974322..0000000 --- a/scripts/sendUserTxToForeignWithdraw.js +++ /dev/null @@ -1,91 +0,0 @@ -require('dotenv').config() -const Web3 = require('web3') -const Web3Utils = require('web3-utils') -const { sendTx, sendRawTx } = require('../src/tx/sendTx') - -const { - USER_ADDRESS, - USER_ADDRESS_PRIVATE_KEY, - FOREIGN_BRIDGE_ADDRESS, - FOREIGN_RPC_URL, - FOREIGN_MIN_AMOUNT_PER_TX, - POA20_ADDRESS -} = process.env - -const NUMBER_OF_WITHDRAWALS_TO_SEND = process.argv[2] || 1 - -const POA20_ABI = [ - { - constant: false, - inputs: [ - { - name: '_to', - type: 'address' - }, - { - name: '_value', - type: 'uint256' - }, - { - name: '_data', - type: 'bytes' - } - ], - name: 'transferAndCall', - outputs: [ - { - name: '', - type: 'bool' - } - ], - payable: false, - stateMutability: 'nonpayable', - type: 'function' - } -] - -const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) -const web3Foreign = new Web3(foreignProvider) - -const poa20 = new web3Foreign.eth.Contract(POA20_ABI, POA20_ADDRESS) - -async function main() { - const foreignChaindId = await sendRawTx({ - url: FOREIGN_RPC_URL, - params: [], - method: 'net_version' - }) - let nonce = await sendRawTx({ - url: FOREIGN_RPC_URL, - method: 'eth_getTransactionCount', - params: [USER_ADDRESS, 'latest'] - }) - nonce = Web3Utils.hexToNumber(nonce) - let actualSent = 0 - for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) { - const gasLimit = await poa20.methods - .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') - .estimateGas({ from: USER_ADDRESS }) - const data = await poa20.methods - .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') - .encodeABI({ from: USER_ADDRESS }) - const txHash = await sendTx({ - rpcUrl: FOREIGN_RPC_URL, - privateKey: USER_ADDRESS_PRIVATE_KEY, - data, - nonce, - gasPrice: '1', - amount: '0', - gasLimit, - to: POA20_ADDRESS, - web3: web3Foreign, - chainId: foreignChaindId - }) - if (txHash !== undefined) { - nonce++ - actualSent++ - console.log(actualSent, ' # ', txHash) - } - } -} -main() From afa4b358476713aac2b90bbd1240c3d5f4ef0944 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 26 Jul 2018 14:10:01 -0300 Subject: [PATCH 020/119] Fix start block script --- e2e/scripts/deployERC20.js | 1 + scripts/getValidatorStartBlocks.js | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/e2e/scripts/deployERC20.js b/e2e/scripts/deployERC20.js index 9baded8..5a8202f 100644 --- a/e2e/scripts/deployERC20.js +++ b/e2e/scripts/deployERC20.js @@ -1,3 +1,4 @@ +/* eslint import/no-unresolved: 0 node/no-missing-require: 0 */ const path = require('path') require('dotenv').config({ path: path.join(__dirname, '../submodules/poa-bridge-contracts/deploy/.env') diff --git a/scripts/getValidatorStartBlocks.js b/scripts/getValidatorStartBlocks.js index 7b4730f..fb94445 100644 --- a/scripts/getValidatorStartBlocks.js +++ b/scripts/getValidatorStartBlocks.js @@ -3,10 +3,17 @@ require('dotenv').config({ path: path.join(__dirname, '../.env') }) const Web3 = require('web3') -const homeABI = require('../abis/HomeBridge.abi') -const foreignABI = require('../abis/ForeignBridge.abi') +const HomeNativeABI = require('../abis/HomeBridgeNativeToErc.abi') +const ForeignNativeABI = require('../abis/ForeignBridgeNativeToErc.abi') +const HomeErcABI = require('../abis/HomeBridgeErcToErc.abi') +const ForeignErcABI = require('../abis/ForeignBridgeErcToErc.abi') const bridgeValidatorsABI = require('../abis/BridgeValidators.abi') +const isErcToErc = process.env.BRIDGE_MODE && process.env.BRIDGE_MODE === 'ERC_TO_ERC' + +const homeABI = isErcToErc ? HomeErcABI : HomeNativeABI +const foreignABI = isErcToErc ? ForeignNativeABI : ForeignErcABI + async function getStartBlock(rpcUrl, bridgeAddress, bridgeAbi) { try { const web3Provider = new Web3.providers.HttpProvider(rpcUrl) From 9c9b118935babc71698355f2b098e832127160be Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Fri, 27 Jul 2018 16:43:14 -0300 Subject: [PATCH 021/119] Add reset last block script --- reset-lastBlock.sh | 1 + scripts/resetLastBlock.js | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100755 reset-lastBlock.sh create mode 100644 scripts/resetLastBlock.js diff --git a/reset-lastBlock.sh b/reset-lastBlock.sh new file mode 100755 index 0000000..ac1b58a --- /dev/null +++ b/reset-lastBlock.sh @@ -0,0 +1 @@ +node scripts/resetLastBlock.js $1 $2 diff --git a/scripts/resetLastBlock.js b/scripts/resetLastBlock.js new file mode 100644 index 0000000..efa647b --- /dev/null +++ b/scripts/resetLastBlock.js @@ -0,0 +1,60 @@ +const Redis = require('ioredis') +const path = require('path') +require('dotenv').config({ + path: path.join(__dirname, '../.env') +}) + +const redis = new Redis(process.env.REDIS_URL) + +redis.on('error', () => { + logError('Error: Cannot connect to redis') +}) + +if (process.argv.length < 4) { + logError( + 'Please provide process key and new block value. Example:' + + '\n signature-request 12345 ' + + '\n collected-signatures 12345 ' + + '\n affirmation-request 12345' + ) +} + +function logError(message) { + console.log(message) + process.exit(1) +} + +function getRedisKey(name) { + const isErcToErc = process.env.BRIDGE_MODE && process.env.BRIDGE_MODE === 'ERC_TO_ERC' + const prefix = isErcToErc ? 'erc-' : '' + return `${prefix}${name}:lastProcessedBlock` +} + +async function main() { + try { + const processName = process.argv[2] + const newBlockValue = process.argv[3] + const lastBlockRedisKey = getRedisKey(processName) + + const value = await redis.get(lastBlockRedisKey) + + if (!value) { + logError( + 'Error: Process key not found on redis. Please provide one of the following:' + + '\n signature-request' + + '\n collected-signatures' + + '\n affirmation-request' + ) + } + + await redis.set(lastBlockRedisKey, newBlockValue) + + console.log(`${processName} last block updated to ${newBlockValue}`) + + redis.disconnect() + } catch (e) { + console.log(e) + } +} + +main() From 62b88ab0a7d2328d4f1b90fcbfbcedf1437c468d Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 30 Jul 2018 11:18:02 -0300 Subject: [PATCH 022/119] Add integer check on last block reset script --- scripts/resetLastBlock.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/resetLastBlock.js b/scripts/resetLastBlock.js index efa647b..4ed6be5 100644 --- a/scripts/resetLastBlock.js +++ b/scripts/resetLastBlock.js @@ -33,7 +33,13 @@ function getRedisKey(name) { async function main() { try { const processName = process.argv[2] - const newBlockValue = process.argv[3] + const rawBlockValue = process.argv[3] + + const newBlockValue = Number(rawBlockValue) + if (!Number.isInteger(newBlockValue)) { + logError('Expecting new block value to be an integer!') + } + const lastBlockRedisKey = getRedisKey(processName) const value = await redis.get(lastBlockRedisKey) From 2803616f3205bcf5bfddd3b13d6984b061bc299c Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 30 Jul 2018 12:21:35 -0300 Subject: [PATCH 023/119] Move contracts submodule up --- .gitmodules | 2 +- {e2e/submodules => submodules}/poa-bridge-contracts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {e2e/submodules => submodules}/poa-bridge-contracts (100%) diff --git a/.gitmodules b/.gitmodules index edeade4..2b3bc3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "e2e/submodules/poa-bridge-contracts"] - path = e2e/submodules/poa-bridge-contracts + path = submodules/poa-bridge-contracts url = https://github.com/poanetwork/poa-bridge-contracts.git diff --git a/e2e/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts similarity index 100% rename from e2e/submodules/poa-bridge-contracts rename to submodules/poa-bridge-contracts From 92ade340cdc131f07559f23d594bfd867ec60a68 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 30 Jul 2018 15:39:54 -0300 Subject: [PATCH 024/119] Move postinstall to Dockerfile --- e2e/Dockerfile | 6 ++++++ e2e/package.json | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/e2e/Dockerfile b/e2e/Dockerfile index 6df8553..f451e21 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -14,6 +14,12 @@ RUN mkdir submodules && \ RUN npm install --unsafe-perm +RUN cd submodules/poa-bridge-contracts && \ + npm install && \ + ./node_modules/.bin/truffle compile && \ + cd deploy && \ + npm install + ADD . . diff --git a/e2e/package.json b/e2e/package.json index 62a8c2f..e14281c 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -4,7 +4,6 @@ "description": "", "main": "index.js", "scripts": { - "postinstall": "cd submodules/poa-bridge-contracts && npm install && ./node_modules/.bin/truffle compile && cd deploy && npm install", "deploy": "node deploy.js", "start": "mocha" }, From 144d511012dfd4879bdb25545bd42c453d379908 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 30 Jul 2018 16:55:32 -0300 Subject: [PATCH 025/119] Include bridge address in the message --- src/events/processSignatureRequests.js | 3 +- src/utils/message.js | 9 ++-- test/message.test.js | 73 +++++++++++++++++++++++--- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index d6c6c04..ff5f899 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -24,7 +24,8 @@ function processSignatureRequestsBuilder(config) { const message = createMessage({ recipient, value, - transactionHash: signatureRequest.transactionHash + transactionHash: signatureRequest.transactionHash, + bridgeAddress: config.foreignBridgeAddress }) const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) diff --git a/src/utils/message.js b/src/utils/message.js index fea3d0d..03a0206 100644 --- a/src/utils/message.js +++ b/src/utils/message.js @@ -5,7 +5,7 @@ function strip0x(input) { return input.replace(/^0x/, '') } -function createMessage({ recipient, value, transactionHash }) { +function createMessage({ recipient, value, transactionHash, bridgeAddress }) { recipient = strip0x(recipient) assert.equal(recipient.length, 20 * 2) @@ -18,8 +18,11 @@ function createMessage({ recipient, value, transactionHash }) { transactionHash = strip0x(transactionHash) assert.equal(transactionHash.length, 32 * 2) - const message = `0x${recipient}${value}${transactionHash}` - const expectedMessageLength = (20 + 32 + 32) * 2 + 2 + bridgeAddress = strip0x(bridgeAddress) + assert.equal(bridgeAddress.length, 20 * 2) + + const message = `0x${recipient}${value}${transactionHash}${bridgeAddress}` + const expectedMessageLength = (20 + 32 + 32 + 20) * 2 + 2 assert.equal(message.length, expectedMessageLength) return message } diff --git a/test/message.test.js b/test/message.test.js index e274a1a..f435418 100644 --- a/test/message.test.js +++ b/test/message.test.js @@ -8,16 +8,18 @@ describe('message utils', () => { const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' const value = '42' const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) // then expect(message).to.equal( [ '0xe3D952Ad4B96A756D65790393128FA359a7CD888', '000000000000000000000000000000000000000000000000000000000000002a', - '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a', + 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' ].join('') ) }) @@ -27,16 +29,18 @@ describe('message utils', () => { const recipient = 'e3D952Ad4B96A756D65790393128FA359a7CD888' const value = '42' const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) // then expect(message).to.equal( [ '0xe3D952Ad4B96A756D65790393128FA359a7CD888', '000000000000000000000000000000000000000000000000000000000000002a', - '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a', + 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' ].join('') ) }) @@ -46,16 +50,18 @@ describe('message utils', () => { const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' const value = '0x2a' const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) // then expect(message).to.equal( [ '0xe3D952Ad4B96A756D65790393128FA359a7CD888', '000000000000000000000000000000000000000000000000000000000000002a', - '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a', + 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' ].join('') ) }) @@ -65,16 +71,39 @@ describe('message utils', () => { const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' const value = '42' const transactionHash = '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) // then expect(message).to.equal( [ '0xe3D952Ad4B96A756D65790393128FA359a7CD888', '000000000000000000000000000000000000000000000000000000000000002a', - '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a', + 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' + ].join('') + ) + }) + + it('should work if the bridge address hash is not prefixed with 0x', () => { + // given + const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' + const value = '42' + const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const bridgeAddress = 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' + + // when + const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + + // then + expect(message).to.equal( + [ + '0xe3D952Ad4B96A756D65790393128FA359a7CD888', + '000000000000000000000000000000000000000000000000000000000000002a', + '4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a', + 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' ].join('') ) }) @@ -130,6 +159,34 @@ describe('message utils', () => { // then expect(messageThunk).to.throw() }) + + it('should fail if the bridge address is too short', () => { + // given + const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' + const value = '42' + const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a' + + // when + const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress }) + + // then + expect(messageThunk).to.throw() + }) + + it('should fail if the bridge address is too long', () => { + // given + const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' + const value = '42' + const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a11' + + // when + const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress }) + + // then + expect(messageThunk).to.throw() + }) }) describe('signatureToVRS', () => { it('should return the v, r, s values', () => { From 16820905309302279b972d0f2d1fd6cb4dad0124 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 30 Jul 2018 16:58:33 -0300 Subject: [PATCH 026/119] Use refactor_v1 branch in submodules --- submodules/poa-bridge-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 39c23a8..c62451c 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 39c23a874c22c812e52cec1576d960084ba2bc3e +Subproject commit c62451c4d658b249adc2a20bd3be23734c152f9d From 1d20a9e185729e365156f4a9d929aaa335cce038 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 30 Jul 2018 16:59:00 -0300 Subject: [PATCH 027/119] Use updated contracts in e2e tests --- e2e/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/Dockerfile b/e2e/Dockerfile index f451e21..9e49549 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -6,11 +6,11 @@ WORKDIR /stuff COPY package.json . COPY package-lock.json . -RUN git clone https://github.com/poanetwork/poa-bridge-contracts.git +RUN git clone https://github.com/rstormsf/poa-parity-bridge-contracts.git RUN mkdir submodules && \ - mv poa-bridge-contracts submodules && \ + mv poa-parity-bridge-contracts submodules/poa-bridge-contracts && \ cd submodules/poa-bridge-contracts && \ - git checkout refactor_v1 + git checkout 57dfe2285f18f5ecd2f9f0ee7a111b1fbcaaf307 RUN npm install --unsafe-perm From bd2ee418c47eb14c227ecb821a132ffae5c2a42a Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 31 Jul 2018 09:50:37 -0300 Subject: [PATCH 028/119] Add coverage and submodules to .eslintignore --- .eslintignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 6c3b5ef..d7d48d5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ node_modules -e2e/submodules +submodules +coverage From 2c3a2e6dca8c7036786b894a4ed21308ae0a7eb3 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 25 Jul 2018 15:48:59 -0300 Subject: [PATCH 029/119] Add tryEach function --- src/utils/tryEach.js | 14 ++++++++++ test/tryEach.test.js | 65 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/utils/tryEach.js create mode 100644 test/tryEach.test.js diff --git a/src/utils/tryEach.js b/src/utils/tryEach.js new file mode 100644 index 0000000..8b99494 --- /dev/null +++ b/src/utils/tryEach.js @@ -0,0 +1,14 @@ +module.exports = async (array, f) => { + const errors = [] + + for (let i = 0; i < array.length; i++) { + try { + const res = await f(array[i]) + return [res, i] + } catch (e) { + errors.push(e) + } + } + + return Promise.reject(errors) +} diff --git a/test/tryEach.test.js b/test/tryEach.test.js new file mode 100644 index 0000000..5044a28 --- /dev/null +++ b/test/tryEach.test.js @@ -0,0 +1,65 @@ +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +const tryEach = require('../src/utils/tryEach') + +chai.use(chaiAsPromised) +const { expect } = chai + +describe('tryEach', () => { + it('should work with the first element', async () => { + // given + const array = [1, 2, 3] + const f = x => (x === 1 ? Promise.resolve(2 * x) : Promise.reject()) + + // when + const [result, index] = await tryEach(array, f) + + // then + expect(result).to.equal(2) + expect(index).to.equal(0) + }) + + it('should work with the second element', async () => { + // given + const array = [1, 2, 3] + const f = x => (x === 2 ? Promise.resolve(2 * x) : Promise.reject()) + + // when + const [result, index] = await tryEach(array, f) + + // then + expect(result).to.equal(4) + expect(index).to.equal(1) + }) + + it('should work with the last element', async () => { + // given + const array = [1, 2, 3] + const f = x => (x === array[array.length - 1] ? Promise.resolve(2 * x) : Promise.reject()) + + // when + const [result, index] = await tryEach(array, f) + + // then + expect(result).to.equal(6) + expect(index).to.equal(2) + }) + + it('should return array with errors if all elements fail', async () => { + // given + const array = [1, 2, 3] + const f = x => Promise.reject(-x) // eslint-disable-line prefer-promise-reject-errors + + // when + return expect(tryEach(array, f)).to.be.rejected.and.eventually.deep.equal([-1, -2, -3]) + }) + + it('should return an empty array if input array is empty', async () => { + // given + const array = [] + const f = x => Promise.resolve(x) + + // when + return expect(tryEach(array, f)).to.be.rejected.and.eventually.deep.equal([]) + }) +}) From 0ad9908347ef06117a3968da22fae2db336c4dcb Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 31 Jul 2018 12:13:10 -0300 Subject: [PATCH 030/119] Centralize the access to RPC URLs Add a RpcUrlsManager that, given the (comma separated) lists of home and foreign RPC URLs, parses them and exposes them. This does not change the behavior of the system, since only the first URL is used. --- config/base.config.js | 9 +++++++-- config/foreign-sender.config.js | 4 +++- config/home-sender.config.js | 4 +++- e2e/scripts/deployERC20.js | 9 ++++++--- scripts/getValidatorStartBlocks.js | 10 +++++++--- scripts/sendUserTxToErcForeign.js | 11 ++++++----- scripts/sendUserTxToErcHome.js | 11 ++++++----- scripts/sendUserTxToForeign.js | 11 ++++++----- scripts/sendUserTxToHome.js | 11 ++++++----- src/events/processAffirmationRequests.js | 6 ++++-- src/events/processCollectedSignatures.js | 9 ++++++--- src/events/processSignatureRequests.js | 6 ++++-- src/events/processTransfers.js | 6 ++++-- src/sender.js | 5 +++-- src/services/RpcUrlsManager.js | 21 +++++++++++++++++++++ src/services/gasPrice.js | 11 ++++++----- src/services/getRpcUrlsManager.js | 3 +++ src/watcher.js | 5 +++-- 18 files changed, 104 insertions(+), 48 deletions(-) create mode 100644 src/services/RpcUrlsManager.js create mode 100644 src/services/getRpcUrlsManager.js diff --git a/config/base.config.js b/config/base.config.js index 91d87d3..9397949 100644 --- a/config/base.config.js +++ b/config/base.config.js @@ -6,6 +6,8 @@ const foreignNativeAbi = require('../abis/ForeignBridgeNativeToErc.abi') const homeErcAbi = require('../abis/HomeBridgeErcToErc.abi') const foreignErcAbi = require('../abis/ForeignBridgeErcToErc.abi') +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') + const isErcToErc = process.env.BRIDGE_MODE && process.env.BRIDGE_MODE === 'ERC_TO_ERC' const homeAbi = isErcToErc ? homeErcAbi : homeNativeAbi @@ -19,8 +21,11 @@ const bridgeConfig = { eventFilter: {} } +const homeRpcUrl = rpcUrlsManager.getHomeUrl() +const foreignRpcUrl = rpcUrlsManager.getForeignUrl() + const homeConfig = { - url: process.env.HOME_RPC_URL, + url: homeRpcUrl, eventContractAddress: process.env.HOME_BRIDGE_ADDRESS, eventAbi: homeAbi, bridgeContractAddress: process.env.HOME_BRIDGE_ADDRESS, @@ -30,7 +35,7 @@ const homeConfig = { } const foreignConfig = { - url: process.env.FOREIGN_RPC_URL, + url: foreignRpcUrl, eventContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, eventAbi: foreignAbi, bridgeContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, diff --git a/config/foreign-sender.config.js b/config/foreign-sender.config.js index 5f204b6..94de6a8 100644 --- a/config/foreign-sender.config.js +++ b/config/foreign-sender.config.js @@ -1,7 +1,9 @@ require('dotenv').config() +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') + module.exports = { - url: process.env.FOREIGN_RPC_URL, + url: rpcUrlsManager.getForeignUrl(), queue: 'foreign', id: 'foreign', name: 'sender-foreign' diff --git a/config/home-sender.config.js b/config/home-sender.config.js index be56627..3d5182e 100644 --- a/config/home-sender.config.js +++ b/config/home-sender.config.js @@ -1,7 +1,9 @@ require('dotenv').config() +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') + module.exports = { - url: process.env.HOME_RPC_URL, + url: rpcUrlsManager.getHomeUrl(), queue: 'home', id: 'home', name: 'sender-home' diff --git a/e2e/scripts/deployERC20.js b/e2e/scripts/deployERC20.js index 5a8202f..54d1068 100644 --- a/e2e/scripts/deployERC20.js +++ b/e2e/scripts/deployERC20.js @@ -10,8 +10,7 @@ const { } = require('../submodules/poa-bridge-contracts/deploy/src/deploymentUtils') const { web3Foreign, - deploymentPrivateKey, - FOREIGN_RPC_URL + deploymentPrivateKey } = require('../submodules/poa-bridge-contracts/deploy/src/web3') const POA20 = require('../submodules/poa-bridge-contracts/build/contracts/ERC677BridgeToken.json') const { user } = require('../constants.json') @@ -30,6 +29,10 @@ async function deployErc20() { foreignNonce++ console.log('[Foreign] POA20 Test: ', poa20foreign.options.address) + // TODO: Fix this when the e2e are modified to use the whole repo as docker context. + // Right now, we can't require the RpcUrlManager from here. + const foreignRpcUrl = process.env.FOREIGN_RPC_URL.split(',')[0] + const mintData = await poa20foreign.methods .mint(user.address, '1000000000000000000') .encodeABI({ from: DEPLOYMENT_ACCOUNT_ADDRESS }) @@ -38,7 +41,7 @@ async function deployErc20() { nonce: foreignNonce, to: poa20foreign.options.address, privateKey: deploymentPrivateKey, - url: FOREIGN_RPC_URL + url: foreignRpcUrl }) } catch (e) { console.log(e) diff --git a/scripts/getValidatorStartBlocks.js b/scripts/getValidatorStartBlocks.js index fb94445..b3af471 100644 --- a/scripts/getValidatorStartBlocks.js +++ b/scripts/getValidatorStartBlocks.js @@ -9,6 +9,8 @@ const HomeErcABI = require('../abis/HomeBridgeErcToErc.abi') const ForeignErcABI = require('../abis/ForeignBridgeErcToErc.abi') const bridgeValidatorsABI = require('../abis/BridgeValidators.abi') +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') + const isErcToErc = process.env.BRIDGE_MODE && process.env.BRIDGE_MODE === 'ERC_TO_ERC' const homeABI = isErcToErc ? HomeErcABI : HomeNativeABI @@ -42,10 +44,12 @@ async function getStartBlock(rpcUrl, bridgeAddress, bridgeAbi) { } async function main() { - const { HOME_RPC_URL, FOREIGN_RPC_URL, HOME_BRIDGE_ADDRESS, FOREIGN_BRIDGE_ADDRESS } = process.env + const { HOME_BRIDGE_ADDRESS, FOREIGN_BRIDGE_ADDRESS } = process.env - const homeStartBlock = await getStartBlock(HOME_RPC_URL, HOME_BRIDGE_ADDRESS, homeABI) - const foreignStartBlock = await getStartBlock(FOREIGN_RPC_URL, FOREIGN_BRIDGE_ADDRESS, foreignABI) + const homeRpcUrl = rpcUrlsManager.getHomeUrl() + const foreignRpcUrl = rpcUrlsManager.getForeignUrl() + const homeStartBlock = await getStartBlock(homeRpcUrl, HOME_BRIDGE_ADDRESS, homeABI) + const foreignStartBlock = await getStartBlock(foreignRpcUrl, FOREIGN_BRIDGE_ADDRESS, foreignABI) const result = { homeStartBlock, foreignStartBlock diff --git a/scripts/sendUserTxToErcForeign.js b/scripts/sendUserTxToErcForeign.js index 03f5abf..6da5d8a 100644 --- a/scripts/sendUserTxToErcForeign.js +++ b/scripts/sendUserTxToErcForeign.js @@ -1,13 +1,13 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') const { sendTx, sendRawTx } = require('../src/tx/sendTx') const { USER_ADDRESS, USER_ADDRESS_PRIVATE_KEY, FOREIGN_BRIDGE_ADDRESS, - FOREIGN_RPC_URL, FOREIGN_MIN_AMOUNT_PER_TX, ERC20_TOKEN_ADDRESS } = process.env @@ -40,7 +40,8 @@ const ERC20_ABI = [ } ] -const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) +const foreignRpcUrl = rpcUrlsManager.getForeignUrl() +const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) const web3Foreign = new Web3(foreignProvider) const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) @@ -48,12 +49,12 @@ const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { try { const foreignChaindId = await sendRawTx({ - url: FOREIGN_RPC_URL, + url: foreignRpcUrl, params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: FOREIGN_RPC_URL, + url: foreignRpcUrl, method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -67,7 +68,7 @@ async function main() { .transfer(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrl: FOREIGN_RPC_URL, + rpcUrl: foreignRpcUrl, privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToErcHome.js b/scripts/sendUserTxToErcHome.js index b354af3..d96044f 100644 --- a/scripts/sendUserTxToErcHome.js +++ b/scripts/sendUserTxToErcHome.js @@ -1,13 +1,13 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') const { sendTx, sendRawTx } = require('../src/tx/sendTx') const { USER_ADDRESS, USER_ADDRESS_PRIVATE_KEY, HOME_BRIDGE_ADDRESS, - HOME_RPC_URL, HOME_MIN_AMOUNT_PER_TX, BRIDGEABLE_TOKEN_ADDRESS } = process.env @@ -45,7 +45,8 @@ const BRIDGEABLE_TOKEN_ABI = [ } ] -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) +const homeRpcUrl = rpcUrlsManager.getHomeUrl() +const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) const erc677 = new web3Home.eth.Contract(BRIDGEABLE_TOKEN_ABI, BRIDGEABLE_TOKEN_ADDRESS) @@ -53,12 +54,12 @@ const erc677 = new web3Home.eth.Contract(BRIDGEABLE_TOKEN_ABI, BRIDGEABLE_TOKEN_ async function main() { try { const homeChainId = await sendRawTx({ - url: HOME_RPC_URL, + url: homeRpcUrl, params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: HOME_RPC_URL, + url: homeRpcUrl, method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -72,7 +73,7 @@ async function main() { .transferAndCall(HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrl: HOME_RPC_URL, + rpcUrl: homeRpcUrl, privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js index 63497ce..4e3170f 100644 --- a/scripts/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -1,13 +1,13 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') const { sendTx, sendRawTx } = require('../src/tx/sendTx') const { USER_ADDRESS, USER_ADDRESS_PRIVATE_KEY, FOREIGN_BRIDGE_ADDRESS, - FOREIGN_RPC_URL, FOREIGN_MIN_AMOUNT_PER_TX, ERC20_TOKEN_ADDRESS } = process.env @@ -45,7 +45,8 @@ const ERC20_ABI = [ } ] -const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) +const foreignRpcUrl = rpcUrlsManager.getForeignUrl() +const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) const web3Foreign = new Web3(foreignProvider) const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) @@ -53,12 +54,12 @@ const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { try { const foreignChaindId = await sendRawTx({ - url: FOREIGN_RPC_URL, + url: foreignRpcUrl, params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: FOREIGN_RPC_URL, + url: foreignRpcUrl, method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -72,7 +73,7 @@ async function main() { .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrl: FOREIGN_RPC_URL, + rpcUrl: foreignRpcUrl, privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToHome.js b/scripts/sendUserTxToHome.js index 9d7a2b6..151434f 100644 --- a/scripts/sendUserTxToHome.js +++ b/scripts/sendUserTxToHome.js @@ -2,29 +2,30 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') const { sendTx, sendRawTx } = require('../src/tx/sendTx') +const rpcUrlsManager = require('../src/services/getRpcUrlsManager') const { USER_ADDRESS, USER_ADDRESS_PRIVATE_KEY, HOME_BRIDGE_ADDRESS, - HOME_RPC_URL, HOME_MIN_AMOUNT_PER_TX } = process.env const NUMBER_OF_DEPOSITS_TO_SEND = process.argv[2] || 1 -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) +const homeRpcUrl = rpcUrlsManager.getHomeUrl() +const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) async function main() { try { const homeChaindId = await sendRawTx({ - url: HOME_RPC_URL, + url: homeRpcUrl, params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: HOME_RPC_URL, + url: homeRpcUrl, method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -32,7 +33,7 @@ async function main() { let actualSent = 0 for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { const txHash = await sendTx({ - rpcUrl: HOME_RPC_URL, + rpcUrl: homeRpcUrl, privateKey: USER_ADDRESS_PRIVATE_KEY, data: '0x', nonce, diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js index 6c33ae9..315c0c3 100644 --- a/src/events/processAffirmationRequests.js +++ b/src/events/processAffirmationRequests.js @@ -1,11 +1,13 @@ require('dotenv').config() const Web3 = require('web3') const logger = require('../services/logger') +const rpcUrlsManager = require('../services/getRpcUrlsManager') -const { HOME_RPC_URL, VALIDATOR_ADDRESS } = process.env +const { VALIDATOR_ADDRESS } = process.env function processAffirmationRequestsBuilder(config) { - const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const homeRpcUrl = rpcUrlsManager.getHomeUrl() + const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index 9ed527d..10cfc07 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -1,16 +1,19 @@ require('dotenv').config() const Web3 = require('web3') const logger = require('../services/logger') +const rpcUrlsManager = require('../services/getRpcUrlsManager') const { signatureToVRS } = require('../utils/message') -const { HOME_RPC_URL, FOREIGN_RPC_URL, VALIDATOR_ADDRESS } = process.env +const { VALIDATOR_ADDRESS } = process.env function processCollectedSignaturesBuilder(config) { - const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const homeRpcUrl = rpcUrlsManager.getHomeUrl() + const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) + const foreignRpcUrl = rpcUrlsManager.getForeignUrl() + const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) const web3Foreign = new Web3(foreignProvider) const foreignBridge = new web3Foreign.eth.Contract( config.foreignBridgeAbi, diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index d6c6c04..350f117 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,12 +1,14 @@ require('dotenv').config() const Web3 = require('web3') const logger = require('../services/logger') +const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') -const { HOME_RPC_URL, VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env +const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env function processSignatureRequestsBuilder(config) { - const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const homeRpcUrl = rpcUrlsManager.getHomeUrl() + const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js index db80854..50849ab 100644 --- a/src/events/processTransfers.js +++ b/src/events/processTransfers.js @@ -1,10 +1,12 @@ require('dotenv').config() const Web3 = require('web3') +const rpcUrlsManager = require('../services/getRpcUrlsManager') -const { HOME_RPC_URL, VALIDATOR_ADDRESS } = process.env +const { VALIDATOR_ADDRESS } = process.env function processTransfersBuilder(config) { - const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) + const homeRpcUrl = rpcUrlsManager.getHomeUrl() + const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) diff --git a/src/sender.js b/src/sender.js index 2e5db91..e582195 100644 --- a/src/sender.js +++ b/src/sender.js @@ -5,6 +5,7 @@ const { connectSenderToQueue } = require('./services/amqpClient') const { redis, redlock } = require('./services/redisClient') const GasPrice = require('./services/gasPrice') const logger = require('./services/logger') +const rpcUrlsManager = require('./services/getRpcUrlsManager') const { sendTx } = require('./tx/sendTx') const { getNonce, getChainId } = require('./tx/web3') const { addExtraGas, checkHTTPS, syncForEach, waitForFunds } = require('./utils/utils') @@ -29,8 +30,8 @@ async function initialize() { try { const checkHttps = checkHTTPS(process.env.ALLOW_HTTP) - checkHttps(process.env.HOME_RPC_URL) - checkHttps(process.env.FOREIGN_RPC_URL) + rpcUrlsManager.homeUrls.forEach(checkHttps) + rpcUrlsManager.foreignUrls.forEach(checkHttps) GasPrice.start(config.id) diff --git a/src/services/RpcUrlsManager.js b/src/services/RpcUrlsManager.js new file mode 100644 index 0000000..ea758c3 --- /dev/null +++ b/src/services/RpcUrlsManager.js @@ -0,0 +1,21 @@ +function RpcUrlsManager(homeUrls, foreignUrls) { + if (!homeUrls) { + throw new Error(`Invalid homeUrls: '${homeUrls}'`) + } + if (!foreignUrls) { + throw new Error(`Invalid foreignUrls: '${foreignUrls}'`) + } + + this.homeUrls = homeUrls.split(',') + this.foreignUrls = foreignUrls.split(',') +} + +RpcUrlsManager.prototype.getHomeUrl = function() { + return this.homeUrls[0] +} + +RpcUrlsManager.prototype.getForeignUrl = function() { + return this.foreignUrls[0] +} + +module.exports = RpcUrlsManager diff --git a/src/services/gasPrice.js b/src/services/gasPrice.js index 388c811..bc84cfb 100644 --- a/src/services/gasPrice.js +++ b/src/services/gasPrice.js @@ -7,6 +7,7 @@ const ForeignNativeABI = require('../../abis/ForeignBridgeNativeToErc.abi') const HomeErcABI = require('../../abis/HomeBridgeErcToErc.abi') const ForeignErcABI = require('../../abis/ForeignBridgeErcToErc.abi') const logger = require('../services/logger') +const rpcUrlsManager = require('../services/getRpcUrlsManager') const HomeABI = isErcToErc ? HomeErcABI : HomeNativeABI const ForeignABI = isErcToErc ? ForeignNativeABI : ForeignErcABI @@ -17,20 +18,20 @@ const { FOREIGN_GAS_PRICE_ORACLE_URL, FOREIGN_GAS_PRICE_SPEED_TYPE, FOREIGN_GAS_PRICE_UPDATE_INTERVAL, - FOREIGN_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_GAS_PRICE_FALLBACK, HOME_GAS_PRICE_ORACLE_URL, HOME_GAS_PRICE_SPEED_TYPE, - HOME_GAS_PRICE_UPDATE_INTERVAL, - HOME_RPC_URL + HOME_GAS_PRICE_UPDATE_INTERVAL } = process.env -const homeProvider = new Web3.providers.HttpProvider(HOME_RPC_URL) +const homeRpcUrl = rpcUrlsManager.getHomeUrl() +const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) -const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) +const foreignRpcUrl = rpcUrlsManager.getForeignUrl() +const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) const web3Foreign = new Web3(foreignProvider) const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS) diff --git a/src/services/getRpcUrlsManager.js b/src/services/getRpcUrlsManager.js new file mode 100644 index 0000000..5163f6b --- /dev/null +++ b/src/services/getRpcUrlsManager.js @@ -0,0 +1,3 @@ +const RpcUrlsManager = require('./RpcUrlsManager') + +module.exports = new RpcUrlsManager(process.env.HOME_RPC_URL, process.env.FOREIGN_RPC_URL) diff --git a/src/watcher.js b/src/watcher.js index 74d73c3..c5ca9a1 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -5,6 +5,7 @@ const { connectWatcherToQueue, connection } = require('./services/amqpClient') const { getBlockNumber } = require('./tx/web3') const { redis } = require('./services/redisClient') const logger = require('./services/logger') +const rpcUrlsManager = require('./services/getRpcUrlsManager') const { getRequiredBlockConfirmations, getEvents } = require('./tx/web3') const { checkHTTPS } = require('./utils/utils') @@ -31,8 +32,8 @@ async function initialize() { try { const checkHttps = checkHTTPS(process.env.ALLOW_HTTP) - checkHttps(process.env.HOME_RPC_URL) - checkHttps(process.env.FOREIGN_RPC_URL) + rpcUrlsManager.homeUrls.forEach(checkHttps) + rpcUrlsManager.foreignUrls.forEach(checkHttps) await getLastProcessedBlock() connectWatcherToQueue({ From 7c98abb7e71cac771191505f4e452ea41eb76d7d Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 31 Jul 2018 15:24:49 -0300 Subject: [PATCH 031/119] Use multiple RPC URLs in both web3 and in raw requests --- config/base.config.js | 7 ++--- config/foreign-sender.config.js | 2 +- config/home-sender.config.js | 2 +- e2e/scripts/deployERC20.js | 6 +--- scripts/sendUserTxToErcForeign.js | 6 ++-- scripts/sendUserTxToErcHome.js | 6 ++-- scripts/sendUserTxToForeign.js | 6 ++-- scripts/sendUserTxToHome.js | 6 ++-- src/events/processAffirmationRequests.js | 4 +-- src/events/processCollectedSignatures.js | 7 ++--- src/events/processSignatureRequests.js | 4 +-- src/events/processTransfers.js | 4 +-- src/sender.js | 5 ++-- src/services/gasPrice.js | 7 ++--- src/tx/sendTx.js | 34 +++++++++++++---------- src/utils/HttpListProvider.js | 35 ++++++++++++++++++++++++ src/watcher.js | 3 +- 17 files changed, 88 insertions(+), 56 deletions(-) create mode 100644 src/utils/HttpListProvider.js diff --git a/config/base.config.js b/config/base.config.js index 9397949..e041b33 100644 --- a/config/base.config.js +++ b/config/base.config.js @@ -21,11 +21,8 @@ const bridgeConfig = { eventFilter: {} } -const homeRpcUrl = rpcUrlsManager.getHomeUrl() -const foreignRpcUrl = rpcUrlsManager.getForeignUrl() - const homeConfig = { - url: homeRpcUrl, + urls: rpcUrlsManager.homeUrls, eventContractAddress: process.env.HOME_BRIDGE_ADDRESS, eventAbi: homeAbi, bridgeContractAddress: process.env.HOME_BRIDGE_ADDRESS, @@ -35,7 +32,7 @@ const homeConfig = { } const foreignConfig = { - url: foreignRpcUrl, + urls: rpcUrlsManager.foreignUrls, eventContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, eventAbi: foreignAbi, bridgeContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, diff --git a/config/foreign-sender.config.js b/config/foreign-sender.config.js index 94de6a8..958177e 100644 --- a/config/foreign-sender.config.js +++ b/config/foreign-sender.config.js @@ -3,7 +3,7 @@ require('dotenv').config() const rpcUrlsManager = require('../src/services/getRpcUrlsManager') module.exports = { - url: rpcUrlsManager.getForeignUrl(), + urls: rpcUrlsManager.foreignUrls, queue: 'foreign', id: 'foreign', name: 'sender-foreign' diff --git a/config/home-sender.config.js b/config/home-sender.config.js index 3d5182e..90b3480 100644 --- a/config/home-sender.config.js +++ b/config/home-sender.config.js @@ -3,7 +3,7 @@ require('dotenv').config() const rpcUrlsManager = require('../src/services/getRpcUrlsManager') module.exports = { - url: rpcUrlsManager.getHomeUrl(), + urls: rpcUrlsManager.homeUrls, queue: 'home', id: 'home', name: 'sender-home' diff --git a/e2e/scripts/deployERC20.js b/e2e/scripts/deployERC20.js index 54d1068..d6a67d8 100644 --- a/e2e/scripts/deployERC20.js +++ b/e2e/scripts/deployERC20.js @@ -29,10 +29,6 @@ async function deployErc20() { foreignNonce++ console.log('[Foreign] POA20 Test: ', poa20foreign.options.address) - // TODO: Fix this when the e2e are modified to use the whole repo as docker context. - // Right now, we can't require the RpcUrlManager from here. - const foreignRpcUrl = process.env.FOREIGN_RPC_URL.split(',')[0] - const mintData = await poa20foreign.methods .mint(user.address, '1000000000000000000') .encodeABI({ from: DEPLOYMENT_ACCOUNT_ADDRESS }) @@ -41,7 +37,7 @@ async function deployErc20() { nonce: foreignNonce, to: poa20foreign.options.address, privateKey: deploymentPrivateKey, - url: foreignRpcUrl + url: process.env.FOREIGN_RPC_URL }) } catch (e) { console.log(e) diff --git a/scripts/sendUserTxToErcForeign.js b/scripts/sendUserTxToErcForeign.js index 6da5d8a..4285e2c 100644 --- a/scripts/sendUserTxToErcForeign.js +++ b/scripts/sendUserTxToErcForeign.js @@ -49,12 +49,12 @@ const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { try { const foreignChaindId = await sendRawTx({ - url: foreignRpcUrl, + urls: [foreignRpcUrl], params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: foreignRpcUrl, + urls: [foreignRpcUrl], method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -68,7 +68,7 @@ async function main() { .transfer(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrl: foreignRpcUrl, + rpcUrls: [foreignRpcUrl], privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToErcHome.js b/scripts/sendUserTxToErcHome.js index d96044f..fa407e1 100644 --- a/scripts/sendUserTxToErcHome.js +++ b/scripts/sendUserTxToErcHome.js @@ -54,12 +54,12 @@ const erc677 = new web3Home.eth.Contract(BRIDGEABLE_TOKEN_ABI, BRIDGEABLE_TOKEN_ async function main() { try { const homeChainId = await sendRawTx({ - url: homeRpcUrl, + urls: [homeRpcUrl], params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: homeRpcUrl, + urls: [homeRpcUrl], method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -73,7 +73,7 @@ async function main() { .transferAndCall(HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrl: homeRpcUrl, + rpcUrls: [homeRpcUrl], privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js index 4e3170f..2a28d77 100644 --- a/scripts/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -54,12 +54,12 @@ const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { try { const foreignChaindId = await sendRawTx({ - url: foreignRpcUrl, + urls: [foreignRpcUrl], params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: foreignRpcUrl, + urls: [foreignRpcUrl], method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -73,7 +73,7 @@ async function main() { .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrl: foreignRpcUrl, + rpcUrls: [foreignRpcUrl], privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToHome.js b/scripts/sendUserTxToHome.js index 151434f..a539517 100644 --- a/scripts/sendUserTxToHome.js +++ b/scripts/sendUserTxToHome.js @@ -20,12 +20,12 @@ const web3Home = new Web3(homeProvider) async function main() { try { const homeChaindId = await sendRawTx({ - url: homeRpcUrl, + urls: [homeRpcUrl], params: [], method: 'net_version' }) let nonce = await sendRawTx({ - url: homeRpcUrl, + urls: [homeRpcUrl], method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -33,7 +33,7 @@ async function main() { let actualSent = 0 for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { const txHash = await sendTx({ - rpcUrl: homeRpcUrl, + rpcUrls: [homeRpcUrl], privateKey: USER_ADDRESS_PRIVATE_KEY, data: '0x', nonce, diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js index 315c0c3..bf0b0dd 100644 --- a/src/events/processAffirmationRequests.js +++ b/src/events/processAffirmationRequests.js @@ -1,13 +1,13 @@ require('dotenv').config() const Web3 = require('web3') +const HttpListProvider = require('../utils/HttpListProvider') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { VALIDATOR_ADDRESS } = process.env function processAffirmationRequestsBuilder(config) { - const homeRpcUrl = rpcUrlsManager.getHomeUrl() - const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) + const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index 10cfc07..8991f27 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -1,5 +1,6 @@ require('dotenv').config() const Web3 = require('web3') +const HttpListProvider = require('../utils/HttpListProvider') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { signatureToVRS } = require('../utils/message') @@ -7,13 +8,11 @@ const { signatureToVRS } = require('../utils/message') const { VALIDATOR_ADDRESS } = process.env function processCollectedSignaturesBuilder(config) { - const homeRpcUrl = rpcUrlsManager.getHomeUrl() - const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) + const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - const foreignRpcUrl = rpcUrlsManager.getForeignUrl() - const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) + const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) const web3Foreign = new Web3(foreignProvider) const foreignBridge = new web3Foreign.eth.Contract( config.foreignBridgeAbi, diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 350f117..80853b0 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,5 +1,6 @@ require('dotenv').config() const Web3 = require('web3') +const HttpListProvider = require('../utils/HttpListProvider') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') @@ -7,8 +8,7 @@ const { createMessage } = require('../utils/message') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env function processSignatureRequestsBuilder(config) { - const homeRpcUrl = rpcUrlsManager.getHomeUrl() - const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) + const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js index 50849ab..3a3e661 100644 --- a/src/events/processTransfers.js +++ b/src/events/processTransfers.js @@ -1,12 +1,12 @@ require('dotenv').config() const Web3 = require('web3') +const HttpListProvider = require('../utils/HttpListProvider') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { VALIDATOR_ADDRESS } = process.env function processTransfersBuilder(config) { - const homeRpcUrl = rpcUrlsManager.getHomeUrl() - const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) + const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) diff --git a/src/sender.js b/src/sender.js index e582195..9ed2795 100644 --- a/src/sender.js +++ b/src/sender.js @@ -1,6 +1,7 @@ require('dotenv').config() const path = require('path') const Web3 = require('web3') +const HttpListProvider = require('./utils/HttpListProvider') const { connectSenderToQueue } = require('./services/amqpClient') const { redis, redlock } = require('./services/redisClient') const GasPrice = require('./services/gasPrice') @@ -20,7 +21,7 @@ if (process.argv.length < 3) { const config = require(path.join('../config/', process.argv[2])) -const provider = new Web3.providers.HttpProvider(config.url) +const provider = new HttpListProvider(config.urls) const web3Instance = new Web3(provider) const nonceLock = `lock:${config.id}:nonce` const nonceKey = `${config.id}:nonce` @@ -90,7 +91,7 @@ async function main({ msg, ackMsg, nackMsg, sendToQueue, channel }) { try { const txHash = await sendTx({ - rpcUrl: config.url, + rpcUrls: config.urls, data: job.data, nonce, gasPrice: gasPrice.toString(10), diff --git a/src/services/gasPrice.js b/src/services/gasPrice.js index bc84cfb..d59a40d 100644 --- a/src/services/gasPrice.js +++ b/src/services/gasPrice.js @@ -1,6 +1,7 @@ require('dotenv').config() const Web3 = require('web3') const fetch = require('node-fetch') +const HttpListProvider = require('../utils/HttpListProvider') const { isErcToErc } = require('../../config/base.config') const HomeNativeABI = require('../../abis/HomeBridgeNativeToErc.abi') const ForeignNativeABI = require('../../abis/ForeignBridgeNativeToErc.abi') @@ -25,13 +26,11 @@ const { HOME_GAS_PRICE_UPDATE_INTERVAL } = process.env -const homeRpcUrl = rpcUrlsManager.getHomeUrl() -const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) +const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) -const foreignRpcUrl = rpcUrlsManager.getForeignUrl() -const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) +const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) const web3Foreign = new Web3(foreignProvider) const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS) diff --git a/src/tx/sendTx.js b/src/tx/sendTx.js index 18d5675..2aeb49a 100644 --- a/src/tx/sendTx.js +++ b/src/tx/sendTx.js @@ -1,9 +1,10 @@ const Web3Utils = require('web3-utils') const fetch = require('node-fetch') +const tryEach = require('../utils/tryEach') // eslint-disable-next-line consistent-return async function sendTx({ - rpcUrl, + rpcUrls, privateKey, data, nonce, @@ -28,28 +29,31 @@ async function sendTx({ ) return sendRawTx({ - url: rpcUrl, + urls: rpcUrls, method: 'eth_sendRawTransaction', params: [serializedTx.rawTransaction] }) } // eslint-disable-next-line consistent-return -async function sendRawTx({ url, params, method }) { - // curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}' - const request = await fetch(url, { - headers: { - 'Content-type': 'application/json' - }, - method: 'POST', - body: JSON.stringify({ - jsonrpc: '2.0', - method, - params, - id: Math.floor(Math.random() * 100) + 1 +async function sendRawTx({ urls, params, method }) { + const [result] = await tryEach(urls, async url => { + // curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}' + return fetch(url, { + headers: { + 'Content-type': 'application/json' + }, + method: 'POST', + body: JSON.stringify({ + jsonrpc: '2.0', + method, + params, + id: Math.floor(Math.random() * 100) + 1 + }) }) }) - const json = await request.json() + + const json = await result.json() if (json.error) { throw json.error } diff --git a/src/utils/HttpListProvider.js b/src/utils/HttpListProvider.js new file mode 100644 index 0000000..1b0a25e --- /dev/null +++ b/src/utils/HttpListProvider.js @@ -0,0 +1,35 @@ +const fetch = require('node-fetch') + +function HttpListProvider(urls) { + if (!urls || !urls.length) { + throw new Error(`Invalid URLs: '${urls}'`) + } + + this.urls = urls + this.currentIndex = 0 +} + +HttpListProvider.prototype.send = async function(payload, callback, retries = 0) { + if (retries === this.urls.length) { + callback(new Error('Request failed for all urls')) + } + + const url = this.urls[this.currentIndex] + + try { + const result = await fetch(url, { + headers: { + 'Content-type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(payload) + }).then(request => request.json()) + + callback(null, result) + } catch (e) { + this.currentIndex = (this.currentIndex + 1) % this.urls.length + this.send(payload, callback, retries + 1) + } +} + +module.exports = HttpListProvider diff --git a/src/watcher.js b/src/watcher.js index c5ca9a1..679b0fd 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -1,6 +1,7 @@ require('dotenv').config() const path = require('path') const Web3 = require('web3') +const HttpListProvider = require('./utils/HttpListProvider') const { connectWatcherToQueue, connection } = require('./services/amqpClient') const { getBlockNumber } = require('./tx/web3') const { redis } = require('./services/redisClient') @@ -21,7 +22,7 @@ const processCollectedSignatures = require('./events/processCollectedSignatures' const processAffirmationRequests = require('./events/processAffirmationRequests')(config) const processTransfers = require('./events/processTransfers')(config) -const provider = new Web3.providers.HttpProvider(config.url) +const provider = new HttpListProvider(config.urls) const web3Instance = new Web3(provider) const bridgeContract = new web3Instance.eth.Contract(config.bridgeAbi, config.bridgeContractAddress) const eventContract = new web3Instance.eth.Contract(config.eventAbi, config.eventContractAddress) From 1339b8409da5fb41724d77a7234b5bda62eed6c0 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 1 Aug 2018 11:37:51 -0300 Subject: [PATCH 032/119] Add log when all blocks are already processed --- src/watcher.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/watcher.js b/src/watcher.js index 679b0fd..d8e9753 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -103,6 +103,7 @@ async function main({ sendToQueue }) { try { const lastBlockToProcess = await getLastBlockToProcess() if (lastBlockToProcess <= lastProcessedBlock) { + logger.info('All blocks already processed') return } const events = await getEvents({ From d33880d93d3fc3b72873a6697d048ea30c55c9d1 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 1 Aug 2018 11:38:07 -0300 Subject: [PATCH 033/119] Use fallback URLs in scripts --- scripts/sendUserTxToForeign.js | 10 +++++----- scripts/sendUserTxToHome.js | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js index 2a28d77..77f7f11 100644 --- a/scripts/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -1,6 +1,7 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') +const HttpListProvider = require('../src/utils/HttpListProvider') const rpcUrlsManager = require('../src/services/getRpcUrlsManager') const { sendTx, sendRawTx } = require('../src/tx/sendTx') @@ -45,8 +46,7 @@ const ERC20_ABI = [ } ] -const foreignRpcUrl = rpcUrlsManager.getForeignUrl() -const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) +const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) const web3Foreign = new Web3(foreignProvider) const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) @@ -54,12 +54,12 @@ const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { try { const foreignChaindId = await sendRawTx({ - urls: [foreignRpcUrl], + urls: rpcUrlsManager.foreignUrls, params: [], method: 'net_version' }) let nonce = await sendRawTx({ - urls: [foreignRpcUrl], + urls: rpcUrlsManager.foreignUrls, method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -73,7 +73,7 @@ async function main() { .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrls: [foreignRpcUrl], + rpcUrls: rpcUrlsManager.foreignUrls, privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToHome.js b/scripts/sendUserTxToHome.js index a539517..0cdf5a7 100644 --- a/scripts/sendUserTxToHome.js +++ b/scripts/sendUserTxToHome.js @@ -1,6 +1,7 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') +const HttpListProvider = require('../src/utils/HttpListProvider') const { sendTx, sendRawTx } = require('../src/tx/sendTx') const rpcUrlsManager = require('../src/services/getRpcUrlsManager') @@ -13,19 +14,18 @@ const { const NUMBER_OF_DEPOSITS_TO_SEND = process.argv[2] || 1 -const homeRpcUrl = rpcUrlsManager.getHomeUrl() -const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) +const homeProvider = HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) async function main() { try { const homeChaindId = await sendRawTx({ - urls: [homeRpcUrl], + urls: rpcUrlsManager.homeUrls, params: [], method: 'net_version' }) let nonce = await sendRawTx({ - urls: [homeRpcUrl], + urls: rpcUrlsManager.homeUrls, method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -33,7 +33,7 @@ async function main() { let actualSent = 0 for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { const txHash = await sendTx({ - rpcUrls: [homeRpcUrl], + rpcUrls: rpcUrlsManager.homeUrls, privateKey: USER_ADDRESS_PRIVATE_KEY, data: '0x', nonce, From 6b0180aa488728b6f23c63093c5e53320867620a Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 1 Aug 2018 11:39:43 -0300 Subject: [PATCH 034/119] Remove getHomeUrl and getForeignUrl functions This way is more explicit that only one of several possible URLs is being used. Besides, ideally the full list of URLs should be used everywhere. --- scripts/getValidatorStartBlocks.js | 4 ++-- scripts/sendUserTxToErcForeign.js | 2 +- scripts/sendUserTxToErcHome.js | 2 +- src/services/RpcUrlsManager.js | 8 -------- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/scripts/getValidatorStartBlocks.js b/scripts/getValidatorStartBlocks.js index b3af471..c8bf491 100644 --- a/scripts/getValidatorStartBlocks.js +++ b/scripts/getValidatorStartBlocks.js @@ -46,8 +46,8 @@ async function getStartBlock(rpcUrl, bridgeAddress, bridgeAbi) { async function main() { const { HOME_BRIDGE_ADDRESS, FOREIGN_BRIDGE_ADDRESS } = process.env - const homeRpcUrl = rpcUrlsManager.getHomeUrl() - const foreignRpcUrl = rpcUrlsManager.getForeignUrl() + const homeRpcUrl = rpcUrlsManager.homeUrls[0] + const foreignRpcUrl = rpcUrlsManager.foreignUrls[0] const homeStartBlock = await getStartBlock(homeRpcUrl, HOME_BRIDGE_ADDRESS, homeABI) const foreignStartBlock = await getStartBlock(foreignRpcUrl, FOREIGN_BRIDGE_ADDRESS, foreignABI) const result = { diff --git a/scripts/sendUserTxToErcForeign.js b/scripts/sendUserTxToErcForeign.js index 4285e2c..3f77e17 100644 --- a/scripts/sendUserTxToErcForeign.js +++ b/scripts/sendUserTxToErcForeign.js @@ -40,7 +40,7 @@ const ERC20_ABI = [ } ] -const foreignRpcUrl = rpcUrlsManager.getForeignUrl() +const foreignRpcUrl = rpcUrlsManager.foreignUrls[0] const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl) const web3Foreign = new Web3(foreignProvider) diff --git a/scripts/sendUserTxToErcHome.js b/scripts/sendUserTxToErcHome.js index fa407e1..46a7d26 100644 --- a/scripts/sendUserTxToErcHome.js +++ b/scripts/sendUserTxToErcHome.js @@ -45,7 +45,7 @@ const BRIDGEABLE_TOKEN_ABI = [ } ] -const homeRpcUrl = rpcUrlsManager.getHomeUrl() +const homeRpcUrl = rpcUrlsManager.homeUrls[0] const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl) const web3Home = new Web3(homeProvider) diff --git a/src/services/RpcUrlsManager.js b/src/services/RpcUrlsManager.js index ea758c3..a83884e 100644 --- a/src/services/RpcUrlsManager.js +++ b/src/services/RpcUrlsManager.js @@ -10,12 +10,4 @@ function RpcUrlsManager(homeUrls, foreignUrls) { this.foreignUrls = foreignUrls.split(',') } -RpcUrlsManager.prototype.getHomeUrl = function() { - return this.homeUrls[0] -} - -RpcUrlsManager.prototype.getForeignUrl = function() { - return this.foreignUrls[0] -} - module.exports = RpcUrlsManager From dbca152f2d38be11c2ed9a0bad65a025bdd7ba77 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 1 Aug 2018 14:25:44 -0300 Subject: [PATCH 035/119] Avoid race condition in HttpListProvider --- src/utils/HttpListProvider.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/HttpListProvider.js b/src/utils/HttpListProvider.js index 1b0a25e..84ab1f2 100644 --- a/src/utils/HttpListProvider.js +++ b/src/utils/HttpListProvider.js @@ -10,11 +10,14 @@ function HttpListProvider(urls) { } HttpListProvider.prototype.send = async function(payload, callback, retries = 0) { + // save the currentIndex to avoid race condition + const currentIndex = this.currentIndex // eslint-disable-line prefer-destructuring + if (retries === this.urls.length) { callback(new Error('Request failed for all urls')) } - const url = this.urls[this.currentIndex] + const url = this.urls[currentIndex] try { const result = await fetch(url, { @@ -27,7 +30,7 @@ HttpListProvider.prototype.send = async function(payload, callback, retries = 0) callback(null, result) } catch (e) { - this.currentIndex = (this.currentIndex + 1) % this.urls.length + this.currentIndex = (currentIndex + 1) % this.urls.length this.send(payload, callback, retries + 1) } } From f7645e2aea03b8f20360e805780b8c0c2c3cc85e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 1 Aug 2018 16:37:16 -0300 Subject: [PATCH 036/119] Add tryEach method to RpcUrlsManager and use it in sendRawTx --- scripts/sendUserTxToErcForeign.js | 6 +++--- scripts/sendUserTxToErcHome.js | 6 +++--- scripts/sendUserTxToForeign.js | 6 +++--- scripts/sendUserTxToHome.js | 6 +++--- src/sender.js | 2 +- src/services/RpcUrlsManager.js | 35 +++++++++++++++++++++++++++++++ src/tx/sendTx.js | 10 ++++----- 7 files changed, 53 insertions(+), 18 deletions(-) diff --git a/scripts/sendUserTxToErcForeign.js b/scripts/sendUserTxToErcForeign.js index 3f77e17..e057fd0 100644 --- a/scripts/sendUserTxToErcForeign.js +++ b/scripts/sendUserTxToErcForeign.js @@ -49,12 +49,12 @@ const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { try { const foreignChaindId = await sendRawTx({ - urls: [foreignRpcUrl], + chain: 'foreign', params: [], method: 'net_version' }) let nonce = await sendRawTx({ - urls: [foreignRpcUrl], + chain: 'foreign', method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -68,7 +68,7 @@ async function main() { .transfer(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX)) .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrls: [foreignRpcUrl], + chain: 'foreign', privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToErcHome.js b/scripts/sendUserTxToErcHome.js index 46a7d26..18a729d 100644 --- a/scripts/sendUserTxToErcHome.js +++ b/scripts/sendUserTxToErcHome.js @@ -54,12 +54,12 @@ const erc677 = new web3Home.eth.Contract(BRIDGEABLE_TOKEN_ABI, BRIDGEABLE_TOKEN_ async function main() { try { const homeChainId = await sendRawTx({ - urls: [homeRpcUrl], + chain: 'home', params: [], method: 'net_version' }) let nonce = await sendRawTx({ - urls: [homeRpcUrl], + chain: 'home', method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -73,7 +73,7 @@ async function main() { .transferAndCall(HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrls: [homeRpcUrl], + chain: 'home', privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js index 77f7f11..483b433 100644 --- a/scripts/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -54,12 +54,12 @@ const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { try { const foreignChaindId = await sendRawTx({ - urls: rpcUrlsManager.foreignUrls, + chain: 'foreign', params: [], method: 'net_version' }) let nonce = await sendRawTx({ - urls: rpcUrlsManager.foreignUrls, + chain: 'foreign', method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -73,7 +73,7 @@ async function main() { .transferAndCall(FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x') .encodeABI({ from: USER_ADDRESS }) const txHash = await sendTx({ - rpcUrls: rpcUrlsManager.foreignUrls, + chain: 'foreign', privateKey: USER_ADDRESS_PRIVATE_KEY, data, nonce, diff --git a/scripts/sendUserTxToHome.js b/scripts/sendUserTxToHome.js index 0cdf5a7..bf746bf 100644 --- a/scripts/sendUserTxToHome.js +++ b/scripts/sendUserTxToHome.js @@ -20,12 +20,12 @@ const web3Home = new Web3(homeProvider) async function main() { try { const homeChaindId = await sendRawTx({ - urls: rpcUrlsManager.homeUrls, + chain: 'home', params: [], method: 'net_version' }) let nonce = await sendRawTx({ - urls: rpcUrlsManager.homeUrls, + chain: 'home', method: 'eth_getTransactionCount', params: [USER_ADDRESS, 'latest'] }) @@ -33,7 +33,7 @@ async function main() { let actualSent = 0 for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) { const txHash = await sendTx({ - rpcUrls: rpcUrlsManager.homeUrls, + chain: 'home', privateKey: USER_ADDRESS_PRIVATE_KEY, data: '0x', nonce, diff --git a/src/sender.js b/src/sender.js index 9ed2795..8db6cba 100644 --- a/src/sender.js +++ b/src/sender.js @@ -91,7 +91,7 @@ async function main({ msg, ackMsg, nackMsg, sendToQueue, channel }) { try { const txHash = await sendTx({ - rpcUrls: config.urls, + chain: config.id, data: job.data, nonce, gasPrice: gasPrice.toString(10), diff --git a/src/services/RpcUrlsManager.js b/src/services/RpcUrlsManager.js index a83884e..f3893a9 100644 --- a/src/services/RpcUrlsManager.js +++ b/src/services/RpcUrlsManager.js @@ -1,3 +1,5 @@ +const tryEach = require('../utils/tryEach') + function RpcUrlsManager(homeUrls, foreignUrls) { if (!homeUrls) { throw new Error(`Invalid homeUrls: '${homeUrls}'`) @@ -10,4 +12,37 @@ function RpcUrlsManager(homeUrls, foreignUrls) { this.foreignUrls = foreignUrls.split(',') } +async function tryEachHelper(originalUrls, f) { + // save homeUrls to avoid race condition + const urls = JSON.parse(JSON.stringify(originalUrls)) + + const [result, index] = await tryEach(urls, f) + + if (index > 0) { + // rotate urls + const failed = urls.splice(0, index) + urls.push(...failed) + } + + return [result, urls] +} + +RpcUrlsManager.prototype.tryEach = async function(chain, f) { + if (chain !== 'home' && chain !== 'foreign') { + throw new Error(`Invalid argument chain: '${chain}'`) + } + + const urls = chain === 'home' ? this.homeUrls : this.foreignUrls + + const [result, newUrls] = await tryEachHelper(urls, f) + + if (chain === 'home') { + this.homeUrls = newUrls + } else { + this.foreignUrls = newUrls + } + + return result +} + module.exports = RpcUrlsManager diff --git a/src/tx/sendTx.js b/src/tx/sendTx.js index 2aeb49a..ff5a03d 100644 --- a/src/tx/sendTx.js +++ b/src/tx/sendTx.js @@ -1,10 +1,10 @@ const Web3Utils = require('web3-utils') const fetch = require('node-fetch') -const tryEach = require('../utils/tryEach') +const rpcUrlsManager = require('../services/getRpcUrlsManager') // eslint-disable-next-line consistent-return async function sendTx({ - rpcUrls, + chain, privateKey, data, nonce, @@ -29,15 +29,15 @@ async function sendTx({ ) return sendRawTx({ - urls: rpcUrls, + chain, method: 'eth_sendRawTransaction', params: [serializedTx.rawTransaction] }) } // eslint-disable-next-line consistent-return -async function sendRawTx({ urls, params, method }) { - const [result] = await tryEach(urls, async url => { +async function sendRawTx({ chain, params, method }) { + const result = await rpcUrlsManager.tryEach(chain, async url => { // curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}' return fetch(url, { headers: { From 6c7df7ffc8a59972130a0323e3e487a313cf0f5e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 2 Aug 2018 07:55:12 -0300 Subject: [PATCH 037/119] Remove tryEachHelper and clone urls with lodash --- package-lock.json | 3 +-- package.json | 1 + src/services/RpcUrlsManager.js | 25 +++++++++---------------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87f50df..fc62071 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2397,8 +2397,7 @@ "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "lodash.assign": { "version": "4.2.0", diff --git a/package.json b/package.json index a278aa9..07ed479 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", "ioredis": "^3.2.2", + "lodash": "^4.17.10", "node-fetch": "^2.1.2", "pino": "^4.17.3", "promise-retry": "^1.1.1", diff --git a/src/services/RpcUrlsManager.js b/src/services/RpcUrlsManager.js index f3893a9..8d19d90 100644 --- a/src/services/RpcUrlsManager.js +++ b/src/services/RpcUrlsManager.js @@ -1,3 +1,4 @@ +const _ = require('lodash') const tryEach = require('../utils/tryEach') function RpcUrlsManager(homeUrls, foreignUrls) { @@ -12,9 +13,13 @@ function RpcUrlsManager(homeUrls, foreignUrls) { this.foreignUrls = foreignUrls.split(',') } -async function tryEachHelper(originalUrls, f) { +RpcUrlsManager.prototype.tryEach = async function(chain, f) { + if (chain !== 'home' && chain !== 'foreign') { + throw new Error(`Invalid argument chain: '${chain}'`) + } + // save homeUrls to avoid race condition - const urls = JSON.parse(JSON.stringify(originalUrls)) + const urls = chain === 'home' ? _.cloneDeep(this.homeUrls) : _.cloneDeep(this.foreignUrls) const [result, index] = await tryEach(urls, f) @@ -24,22 +29,10 @@ async function tryEachHelper(originalUrls, f) { urls.push(...failed) } - return [result, urls] -} - -RpcUrlsManager.prototype.tryEach = async function(chain, f) { - if (chain !== 'home' && chain !== 'foreign') { - throw new Error(`Invalid argument chain: '${chain}'`) - } - - const urls = chain === 'home' ? this.homeUrls : this.foreignUrls - - const [result, newUrls] = await tryEachHelper(urls, f) - if (chain === 'home') { - this.homeUrls = newUrls + this.homeUrls = urls } else { - this.foreignUrls = newUrls + this.foreignUrls = urls } return result From f9d2a3694f18b0549abdb599f0da9f455c5f7c45 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Sun, 5 Aug 2018 17:46:23 -0300 Subject: [PATCH 038/119] Use refactor_v1 branch in the submodules and e2e --- e2e/Dockerfile | 7 ++++--- submodules/poa-bridge-contracts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/e2e/Dockerfile b/e2e/Dockerfile index 9e49549..5507ead 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -6,11 +6,12 @@ WORKDIR /stuff COPY package.json . COPY package-lock.json . -RUN git clone https://github.com/rstormsf/poa-parity-bridge-contracts.git +RUN git clone https://github.com/poanetwork/poa-bridge-contracts.git + RUN mkdir submodules && \ - mv poa-parity-bridge-contracts submodules/poa-bridge-contracts && \ + mv poa-bridge-contracts submodules && \ cd submodules/poa-bridge-contracts && \ - git checkout 57dfe2285f18f5ecd2f9f0ee7a111b1fbcaaf307 + git checkout refactor_v1 RUN npm install --unsafe-perm diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index c62451c..57d16f2 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit c62451c4d658b249adc2a20bd3be23734c152f9d +Subproject commit 57d16f2b8140e2bf237dd960972127590589dc4a From 34a2f2f47ae206b3c0f025bad8cdc53b56768922 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 6 Aug 2018 13:24:31 -0300 Subject: [PATCH 039/119] Add env variables to Travis so that tests pass --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index f7508b5..5324136 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,3 +15,7 @@ script: - npm run lint - npm test - cd e2e && ./run-tests.sh + +env: + - HOME_RPC_URL=http://example.com + - FOREIGN_RPC_URL=http://example.com From 7d6133476ae4c230e14bf565432813a741846754 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 6 Aug 2018 14:37:27 -0300 Subject: [PATCH 040/119] Put all env variables in the same item --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5324136..040d443 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,5 +17,4 @@ script: - cd e2e && ./run-tests.sh env: - - HOME_RPC_URL=http://example.com - - FOREIGN_RPC_URL=http://example.com + - HOME_RPC_URL=http://example.com FOREIGN_RPC_URL=http://example.com From d8b6cb35cb9175f46e64b4c48d43f22346297f12 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 10 Aug 2018 11:02:25 -0300 Subject: [PATCH 041/119] Pipe logs to disk and add script to compute basic statistics --- .gitignore | 1 + package-lock.json | 26 ++++++++++++++++++++++ package.json | 15 ++++++++----- scripts/.eslintrc | 7 ++++++ scripts/compute-stats.js | 47 ++++++++++++++++++++++++++++++++++++++++ src/services/logger.js | 1 - 6 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 scripts/.eslintrc create mode 100644 scripts/compute-stats.js diff --git a/.gitignore b/.gitignore index 6b5d592..4a4805f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules .idea .nyc_output coverage/ +logs/ diff --git a/package-lock.json b/package-lock.json index 87f50df..ac7c380 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2738,6 +2738,26 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "ndjson": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-1.5.0.tgz", + "integrity": "sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=", + "dev": true, + "requires": { + "json-stringify-safe": "^5.0.1", + "minimist": "^1.2.0", + "split2": "^2.1.0", + "through2": "^2.0.3" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -5673,6 +5693,12 @@ "simple-concat": "^1.0.0" } }, + "simple-statistics": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-6.1.0.tgz", + "integrity": "sha512-Vi/xPPuiEIizXOCDx3N8LjcIqC7Z8euiIfvmj5oYimm9/KmXNKeK4aHiVMeE9q7e9wfg1i9YRoWnUNURq54ZFg==", + "dev": true + }, "sinon": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.1.0.tgz", diff --git a/package.json b/package.json index a278aa9..6ddb33d 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,15 @@ "main": "index.js", "scripts": { "lint": "eslint .", - "watcher:signature-request": "node src/watcher.js signature-request-watcher.config.js", - "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher.config.js", - "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js", - "sender:home": "node src/sender.js home-sender.config.js", - "sender:foreign": "node src/sender.js foreign-sender.config.js", + "watcher:signature-request": "node src/watcher.js signature-request-watcher.config.js | tee -a logs/watcher-signature-request.txt | pino", + "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher.config.js | tee -a logs/watcher-collected-signatures.txt | pino", + "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js | tee -a logs/watcher-affirmation-request.txt | pino", + "sender:home": "node src/sender.js home-sender.config.js | tee -a logs/sender-home.txt | pino", + "sender:foreign": "node src/sender.js foreign-sender.config.js | tee -a logs/sender-foreign.txt | pino", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:signature-request' 'npm run watcher:collected-signatures' 'npm run watcher:affirmation-request' 'npm run sender:home' 'npm run sender:foreign'", "test": "NODE_ENV=test mocha", "coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha", - "postinstall": "npm install --prefix e2e" + "postinstall": "npm install --prefix e2e && mkdirp logs" }, "author": "", "license": "ISC", @@ -23,6 +23,7 @@ "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", "ioredis": "^3.2.2", + "mkdirp": "^0.5.1", "node-fetch": "^2.1.2", "pino": "^4.17.3", "promise-retry": "^1.1.1", @@ -41,9 +42,11 @@ "eslint-plugin-node": "^6.0.1", "eslint-plugin-prettier": "^2.6.0", "mocha": "^5.2.0", + "ndjson": "^1.5.0", "nyc": "^12.0.2", "prettier": "^1.12.1", "proxyquire": "^2.0.1", + "simple-statistics": "^6.1.0", "sinon": "^6.1.0" }, "engines": { diff --git a/scripts/.eslintrc b/scripts/.eslintrc new file mode 100644 index 0000000..c09aec6 --- /dev/null +++ b/scripts/.eslintrc @@ -0,0 +1,7 @@ +{ + "extends": "../.eslintrc", + "rules": { + "import/no-extraneous-dependencies": "off", + "node/no-unpublished-require": "off" + } +} diff --git a/scripts/compute-stats.js b/scripts/compute-stats.js new file mode 100644 index 0000000..fc47318 --- /dev/null +++ b/scripts/compute-stats.js @@ -0,0 +1,47 @@ +const fs = require('fs') +const path = require('path') +const ndjson = require('ndjson') +const { mean, median, min, max } = require('simple-statistics') + +const readFile = (...paths) => { + const a = [] + const filename = path.join(...paths) + return new Promise(resolve => { + fs + .createReadStream(filename) + .pipe(ndjson.parse()) + .on('data', obj => a.push(obj)) + .on('end', () => resolve(a)) + }) +} + +function computeSignatureRequestStats(signatureRequests, senderHome) { + const processingLogs = signatureRequests.filter(x => x.eventTransactionHash) + const txSentMap = senderHome.filter(x => x.eventTransactionHash).reduce((acc, x) => { + acc[x.eventTransactionHash] = x + return acc + }, {}) + + const times = processingLogs.map(x => txSentMap[x.eventTransactionHash].time - x.time) + + return { + mean: mean(times), + median: median(times), + min: min(times), + max: max(times) + } +} + +async function main() { + const signatureRequests = await readFile(__dirname, '../logs/watcher-signature-request.txt') + // const collectedSignatures = await readFile(__dirname, 'logs/watcher-collected-signatures.txt') + // const affirmationRequests = await readFile(__dirname, 'logs/watcher-affirmation-request.txt') + const senderHome = await readFile(__dirname, '../logs/sender-home.txt') + // const senderForeign = await readFile(__dirname, 'logs/sender-foreign.txt') + + const signatureRequestsStats = computeSignatureRequestStats(signatureRequests, senderHome) + + console.log(signatureRequestsStats) +} + +main() diff --git a/src/services/logger.js b/src/services/logger.js index 5f0ced3..7a81172 100644 --- a/src/services/logger.js +++ b/src/services/logger.js @@ -6,7 +6,6 @@ const config = const logger = pino({ enabled: process.env.NODE_ENV !== 'test', - prettyPrint: process.env.NODE_ENV !== 'production', name: config.name, base: process.env.NODE_ENV === 'production' From 8a90f78904f95c2fcea27a4f0f570572b27b8408 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 10 Aug 2018 11:06:34 -0300 Subject: [PATCH 042/119] Add instructions on how to do stress testing --- docs/stress-testing.md | 86 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/stress-testing.md diff --git a/docs/stress-testing.md b/docs/stress-testing.md new file mode 100644 index 0000000..99a8b80 --- /dev/null +++ b/docs/stress-testing.md @@ -0,0 +1,86 @@ +# Stress testing + +## Prerequisites + +- [Geth](https://geth.ethereum.org/) +- [Parity](https://www.parity.io/) + +## Geth (home) setup + +In one terminal, initialize the geth chain using this `genesis.json` + +```json +{ + "config": { + "chainId": 15, + "homesteadBlock": 0, + "eip155Block": 0, + "eip158Block": 0, + "eip160Block": 0, + "byzantiumBlock": 0 + }, + "difficulty": "100000", + "gasLimit": "0x1000000000", + "alloc": { + "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b": { "balance": "1000000000000000000000" }, + "0xbb140FbA6242a1c3887A7823F7750a73101383e3": { "balance": "1000000000000000000000" } + } +} +``` + +and run this command: + +``` +geth --datadir ./data init genesis.json +``` + +Then start the node: + +``` +geth --datadir ./data --networkid 15 --rpc --rpcapi eth,web3,net +``` + +After doing this, you can enable the mining mode by doing: + +``` +geth attach --datadir ./data --exec "miner.setEtherbase('0xaaB52d66283F7A1D5978bcFcB55721ACB467384b'); miner.start(1)" +``` + +And you can disable it by doing: + +``` +geth attach --datadir ./data --exec "miner.stop()" +``` + +## Parity (foreign) setup + +Start a parity node using the docker image in the `e2e/parity` directory. +Assuming that this image has been built and that the image name is `my-parity`, +run: + +``` +docker run -p 8546:8545 my-parity +``` + +## Deploy contracts and start the bridge + +Set the geth node in mining mode. Set `http://localhost:8545` as the home RPC +URL, `http://localhost:8546` as the foreign RPC URL and +`0xaaB52d66283F7A1D5978bcFcB55721ACB467384b` as the deployer and validator. +Deploy the contracts. + +To start the bridge, you can do `npm run dev`, or you can start all the scripts +separately. + +## Generate a block with several transactions + +Stop the mining in the node. Then, generate 1000 transactions by doing +`node scripts/sendUserTxToHome.js 1000`. After checking that the node received +all the transactions, start the mining again. + +This will generate a block with 1000 transactions that the bridge will process. + +## Generate statistics + +After doing all of this, run `node scripts/compute-stats.js`. This will print +some statistics about the processed transactions. From 31d55077fc1fc8615d685559fd18f7e6de1b4b8e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 13 Aug 2018 12:59:40 -0300 Subject: [PATCH 043/119] Compute statistics for collected signatures --- scripts/compute-stats.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/scripts/compute-stats.js b/scripts/compute-stats.js index fc47318..026e76f 100644 --- a/scripts/compute-stats.js +++ b/scripts/compute-stats.js @@ -32,16 +32,40 @@ function computeSignatureRequestStats(signatureRequests, senderHome) { } } +function computeCollectedSignaturesStats(collectedSignatures, senderForeign) { + const processingLogs = collectedSignatures.filter(x => x.eventTransactionHash) + const txSentMap = senderForeign.filter(x => x.eventTransactionHash).reduce((acc, x) => { + acc[x.eventTransactionHash] = x + return acc + }, {}) + + const times = processingLogs.map(x => txSentMap[x.eventTransactionHash].time - x.time) + + return { + mean: mean(times), + median: median(times), + min: min(times), + max: max(times) + } +} + async function main() { const signatureRequests = await readFile(__dirname, '../logs/watcher-signature-request.txt') - // const collectedSignatures = await readFile(__dirname, 'logs/watcher-collected-signatures.txt') - // const affirmationRequests = await readFile(__dirname, 'logs/watcher-affirmation-request.txt') + const collectedSignatures = await readFile(__dirname, '../logs/watcher-collected-signatures.txt') + // const affirmationRequests = await readFile(__dirname, '../logs/watcher-affirmation-request.txt') const senderHome = await readFile(__dirname, '../logs/sender-home.txt') - // const senderForeign = await readFile(__dirname, 'logs/sender-foreign.txt') + const senderForeign = await readFile(__dirname, '../logs/sender-foreign.txt') const signatureRequestsStats = computeSignatureRequestStats(signatureRequests, senderHome) - + console.log('Signature Requests') console.log(signatureRequestsStats) + + const collectedSignaturesStats = computeCollectedSignaturesStats( + collectedSignatures, + senderForeign + ) + console.log('Collected Signatures') + console.log(collectedSignaturesStats) } main() From caa37a0a5a46944dab5e93c2442482c9f443cbcc Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 13 Aug 2018 14:01:14 -0300 Subject: [PATCH 044/119] Use http-list-provider as dependency --- package-lock.json | 15 ++++++++++ package.json | 1 + scripts/sendUserTxToForeign.js | 2 +- scripts/sendUserTxToHome.js | 2 +- src/events/processAffirmationRequests.js | 2 +- src/events/processCollectedSignatures.js | 2 +- src/events/processSignatureRequests.js | 2 +- src/events/processTransfers.js | 2 +- src/sender.js | 2 +- src/services/gasPrice.js | 2 +- src/utils/HttpListProvider.js | 38 ------------------------ src/watcher.js | 2 +- 12 files changed, 25 insertions(+), 47 deletions(-) delete mode 100644 src/utils/HttpListProvider.js diff --git a/package-lock.json b/package-lock.json index fc62071..98e8695 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1999,6 +1999,21 @@ "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" }, + "http-list-provider": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.2.tgz", + "integrity": "sha512-yUaAO/JJ4fJuUefgYZ/uQwW8o9PAWN6SkQhXOud+C0SJtcuTsTXGuxvVJFM5d/FE4HsyqP6iRdtTS4srxz76ow==", + "requires": { + "node-fetch": "^2.2.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", + "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==" + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", diff --git a/package.json b/package.json index 07ed479..c30c761 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "amqplib": "^0.5.2", "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", + "http-list-provider": "0.0.2", "ioredis": "^3.2.2", "lodash": "^4.17.10", "node-fetch": "^2.1.2", diff --git a/scripts/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js index 483b433..d709426 100644 --- a/scripts/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -1,7 +1,7 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') -const HttpListProvider = require('../src/utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const rpcUrlsManager = require('../src/services/getRpcUrlsManager') const { sendTx, sendRawTx } = require('../src/tx/sendTx') diff --git a/scripts/sendUserTxToHome.js b/scripts/sendUserTxToHome.js index bf746bf..89156a7 100644 --- a/scripts/sendUserTxToHome.js +++ b/scripts/sendUserTxToHome.js @@ -1,7 +1,7 @@ require('dotenv').config() const Web3 = require('web3') const Web3Utils = require('web3-utils') -const HttpListProvider = require('../src/utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const { sendTx, sendRawTx } = require('../src/tx/sendTx') const rpcUrlsManager = require('../src/services/getRpcUrlsManager') diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js index bf0b0dd..3ecf9c3 100644 --- a/src/events/processAffirmationRequests.js +++ b/src/events/processAffirmationRequests.js @@ -1,6 +1,6 @@ require('dotenv').config() const Web3 = require('web3') -const HttpListProvider = require('../utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index 8991f27..2147229 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -1,6 +1,6 @@ require('dotenv').config() const Web3 = require('web3') -const HttpListProvider = require('../utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { signatureToVRS } = require('../utils/message') diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index f18391f..c0f7a35 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,6 +1,6 @@ require('dotenv').config() const Web3 = require('web3') -const HttpListProvider = require('../utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js index 3a3e661..06ed981 100644 --- a/src/events/processTransfers.js +++ b/src/events/processTransfers.js @@ -1,6 +1,6 @@ require('dotenv').config() const Web3 = require('web3') -const HttpListProvider = require('../utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { VALIDATOR_ADDRESS } = process.env diff --git a/src/sender.js b/src/sender.js index 8db6cba..afdbe29 100644 --- a/src/sender.js +++ b/src/sender.js @@ -1,7 +1,7 @@ require('dotenv').config() const path = require('path') const Web3 = require('web3') -const HttpListProvider = require('./utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const { connectSenderToQueue } = require('./services/amqpClient') const { redis, redlock } = require('./services/redisClient') const GasPrice = require('./services/gasPrice') diff --git a/src/services/gasPrice.js b/src/services/gasPrice.js index d59a40d..37c3f90 100644 --- a/src/services/gasPrice.js +++ b/src/services/gasPrice.js @@ -1,7 +1,7 @@ require('dotenv').config() const Web3 = require('web3') const fetch = require('node-fetch') -const HttpListProvider = require('../utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const { isErcToErc } = require('../../config/base.config') const HomeNativeABI = require('../../abis/HomeBridgeNativeToErc.abi') const ForeignNativeABI = require('../../abis/ForeignBridgeNativeToErc.abi') diff --git a/src/utils/HttpListProvider.js b/src/utils/HttpListProvider.js deleted file mode 100644 index 84ab1f2..0000000 --- a/src/utils/HttpListProvider.js +++ /dev/null @@ -1,38 +0,0 @@ -const fetch = require('node-fetch') - -function HttpListProvider(urls) { - if (!urls || !urls.length) { - throw new Error(`Invalid URLs: '${urls}'`) - } - - this.urls = urls - this.currentIndex = 0 -} - -HttpListProvider.prototype.send = async function(payload, callback, retries = 0) { - // save the currentIndex to avoid race condition - const currentIndex = this.currentIndex // eslint-disable-line prefer-destructuring - - if (retries === this.urls.length) { - callback(new Error('Request failed for all urls')) - } - - const url = this.urls[currentIndex] - - try { - const result = await fetch(url, { - headers: { - 'Content-type': 'application/json' - }, - method: 'POST', - body: JSON.stringify(payload) - }).then(request => request.json()) - - callback(null, result) - } catch (e) { - this.currentIndex = (currentIndex + 1) % this.urls.length - this.send(payload, callback, retries + 1) - } -} - -module.exports = HttpListProvider diff --git a/src/watcher.js b/src/watcher.js index d8e9753..f618a56 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -1,7 +1,7 @@ require('dotenv').config() const path = require('path') const Web3 = require('web3') -const HttpListProvider = require('./utils/HttpListProvider') +const HttpListProvider = require('http-list-provider') const { connectWatcherToQueue, connection } = require('./services/amqpClient') const { getBlockNumber } = require('./tx/web3') const { redis } = require('./services/redisClient') From ff8fccae73e95e848e23765174fa54bb70722201 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 14 Aug 2018 10:53:11 -0300 Subject: [PATCH 045/119] Don't prettify the logs in production --- package-lock.json | 51 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 11 +++++----- scripts/prettify.sh | 9 ++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100755 scripts/prettify.sh diff --git a/package-lock.json b/package-lock.json index ac7c380..f32c187 100644 --- a/package-lock.json +++ b/package-lock.json @@ -257,6 +257,17 @@ "sprintf-js": "~1.0.2" } }, + "args": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.0.tgz", + "integrity": "sha512-eCZo33yLdQ3DiG/Ko5n11uPonyYofYd9F2cqWID8TKGZwK/Z2ZcUj/oZ1HNMeNL2lgraPnv3JBZumfbUMqmZtg==", + "requires": { + "camelcase": "5.0.0", + "chalk": "2.4.1", + "leven": "2.1.0", + "mri": "1.1.1" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -593,6 +604,11 @@ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -970,6 +986,11 @@ "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==", "dev": true }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2260,6 +2281,11 @@ "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", "dev": true }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-sha3": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.3.1.tgz", @@ -2354,6 +2380,11 @@ "sha3": "^1.1.0" } }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -2701,6 +2732,11 @@ "resolved": "https://registry.npmjs.org/mout/-/mout-0.11.1.tgz", "integrity": "sha1-ujYR318OWx/7/QEWa48C0fX6K5k=" }, + "mri": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.1.tgz", + "integrity": "sha1-haom09ru7t+A3FmEr5XMXKXK2fE=" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5120,6 +5156,21 @@ "split2": "^2.2.0" } }, + "pino-pretty": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-2.0.1.tgz", + "integrity": "sha512-lpSLp5CpHA20BiwRKHsgPjb8ANBlapqen2hWKYEqHCWp3uguPzj2y/Ie4RnLgZVP66eTYYipaVtpZQ0wn0s0QA==", + "requires": { + "args": "^5.0.0", + "chalk": "^2.3.2", + "dateformat": "^3.0.3", + "fast-json-parse": "^1.0.3", + "jmespath": "^0.15.0", + "pump": "^3.0.0", + "split2": "^2.2.0", + "through2": "^2.0.3" + } + }, "pino-std-serializers": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.1.0.tgz", diff --git a/package.json b/package.json index 6ddb33d..709d4c3 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "main": "index.js", "scripts": { "lint": "eslint .", - "watcher:signature-request": "node src/watcher.js signature-request-watcher.config.js | tee -a logs/watcher-signature-request.txt | pino", - "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher.config.js | tee -a logs/watcher-collected-signatures.txt | pino", - "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js | tee -a logs/watcher-affirmation-request.txt | pino", - "sender:home": "node src/sender.js home-sender.config.js | tee -a logs/sender-home.txt | pino", - "sender:foreign": "node src/sender.js foreign-sender.config.js | tee -a logs/sender-foreign.txt | pino", + "watcher:signature-request": "node src/watcher.js signature-request-watcher.config.js | tee -a logs/watcher-signature-request.txt | scripts/prettify.sh", + "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher.config.js | tee -a logs/watcher-collected-signatures.txt | scripts/prettify.sh", + "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js | tee -a logs/watcher-affirmation-request.txt | scripts/prettify.sh", + "sender:home": "node src/sender.js home-sender.config.js | tee -a logs/sender-home.txt | scripts/prettify.sh", + "sender:foreign": "node src/sender.js foreign-sender.config.js | tee -a logs/sender-foreign.txt | scripts/prettify.sh", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:signature-request' 'npm run watcher:collected-signatures' 'npm run watcher:affirmation-request' 'npm run sender:home' 'npm run sender:foreign'", "test": "NODE_ENV=test mocha", "coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha", @@ -26,6 +26,7 @@ "mkdirp": "^0.5.1", "node-fetch": "^2.1.2", "pino": "^4.17.3", + "pino-pretty": "^2.0.1", "promise-retry": "^1.1.1", "redlock": "^3.1.2", "web3": "^1.0.0-beta.34", diff --git a/scripts/prettify.sh b/scripts/prettify.sh new file mode 100755 index 0000000..b2675c5 --- /dev/null +++ b/scripts/prettify.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Prettify the input only if we are not in production +if [ "$NODE_ENV" = "production" ] +then + cat +else + pino-pretty +fi From 548bbca486c9ec9be830a78a1935ce7031277104 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 14 Aug 2018 10:56:08 -0300 Subject: [PATCH 046/119] Remove mkdirp dependency --- package-lock.json | 18 ++++++++++++++++-- package.json | 3 +-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f32c187..26f64e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2020,6 +2020,21 @@ "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" }, + "http-list-provider": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.2.tgz", + "integrity": "sha512-yUaAO/JJ4fJuUefgYZ/uQwW8o9PAWN6SkQhXOud+C0SJtcuTsTXGuxvVJFM5d/FE4HsyqP6iRdtTS4srxz76ow==", + "requires": { + "node-fetch": "^2.2.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", + "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==" + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -2428,8 +2443,7 @@ "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "lodash.assign": { "version": "4.2.0", diff --git a/package.json b/package.json index ecde46f..73bc8ca 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:signature-request' 'npm run watcher:collected-signatures' 'npm run watcher:affirmation-request' 'npm run sender:home' 'npm run sender:foreign'", "test": "NODE_ENV=test mocha", "coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha", - "postinstall": "npm install --prefix e2e && mkdirp logs" + "postinstall": "npm install --prefix e2e && mkdir -p logs" }, "author": "", "license": "ISC", @@ -24,7 +24,6 @@ "dotenv": "^5.0.1", "http-list-provider": "0.0.2", "ioredis": "^3.2.2", - "mkdirp": "^0.5.1", "lodash": "^4.17.10", "node-fetch": "^2.1.2", "pino": "^4.17.3", From 93e663c2e4f0f0559b390f6184d36c06a5271163 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 15 Aug 2018 10:59:28 -0300 Subject: [PATCH 047/119] Show number of events used for computing stats --- scripts/compute-stats.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/compute-stats.js b/scripts/compute-stats.js index 026e76f..6378798 100644 --- a/scripts/compute-stats.js +++ b/scripts/compute-stats.js @@ -25,6 +25,7 @@ function computeSignatureRequestStats(signatureRequests, senderHome) { const times = processingLogs.map(x => txSentMap[x.eventTransactionHash].time - x.time) return { + count: times.length, mean: mean(times), median: median(times), min: min(times), @@ -42,6 +43,7 @@ function computeCollectedSignaturesStats(collectedSignatures, senderForeign) { const times = processingLogs.map(x => txSentMap[x.eventTransactionHash].time - x.time) return { + count: times.length, mean: mean(times), median: median(times), min: min(times), From a3a3b1b86544d44c553b60655806b2ba97c9d719 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 15 Aug 2018 10:59:52 -0300 Subject: [PATCH 048/119] Log potential errors when computing stats --- scripts/compute-stats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/compute-stats.js b/scripts/compute-stats.js index 6378798..7a23416 100644 --- a/scripts/compute-stats.js +++ b/scripts/compute-stats.js @@ -70,4 +70,4 @@ async function main() { console.log(collectedSignaturesStats) } -main() +main().catch(console.error) From b5683a4c1a6b06380128294690499f7ffc78939d Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 15 Aug 2018 11:08:15 -0300 Subject: [PATCH 049/119] Add script to start workers --- package.json | 10 +++++----- scripts/prettify.sh | 9 --------- scripts/start-worker.sh | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 14 deletions(-) delete mode 100755 scripts/prettify.sh create mode 100755 scripts/start-worker.sh diff --git a/package.json b/package.json index 73bc8ca..292f012 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "main": "index.js", "scripts": { "lint": "eslint .", - "watcher:signature-request": "node src/watcher.js signature-request-watcher.config.js | tee -a logs/watcher-signature-request.txt | scripts/prettify.sh", - "watcher:collected-signatures": "node src/watcher.js collected-signatures-watcher.config.js | tee -a logs/watcher-collected-signatures.txt | scripts/prettify.sh", - "watcher:affirmation-request": "node src/watcher.js affirmation-request-watcher.config.js | tee -a logs/watcher-affirmation-request.txt | scripts/prettify.sh", - "sender:home": "node src/sender.js home-sender.config.js | tee -a logs/sender-home.txt | scripts/prettify.sh", - "sender:foreign": "node src/sender.js foreign-sender.config.js | tee -a logs/sender-foreign.txt | scripts/prettify.sh", + "watcher:signature-request": "./scripts/start-worker.sh watcher signature-request-watcher", + "watcher:collected-signatures": "./scripts/start-worker.sh watcher collected-signatures-watcher", + "watcher:affirmation-request": "./scripts/start-worker.sh watcher affirmation-request-watcher", + "sender:home": "./scripts/start-worker.sh sender home-sender", + "sender:foreign": "./scripts/start-worker.sh sender foreign-sender", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:signature-request' 'npm run watcher:collected-signatures' 'npm run watcher:affirmation-request' 'npm run sender:home' 'npm run sender:foreign'", "test": "NODE_ENV=test mocha", "coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha", diff --git a/scripts/prettify.sh b/scripts/prettify.sh deleted file mode 100755 index b2675c5..0000000 --- a/scripts/prettify.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -# Prettify the input only if we are not in production -if [ "$NODE_ENV" = "production" ] -then - cat -else - pino-pretty -fi diff --git a/scripts/start-worker.sh b/scripts/start-worker.sh new file mode 100755 index 0000000..6d0641b --- /dev/null +++ b/scripts/start-worker.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +WORKERS_DIR="src/" +LOGS_DIR="logs/" + +WORKER="${WORKERS_DIR}${1}.js" +CONFIG="${2}.config.js" +LOG="${LOGS_DIR}${2}.txt" + +if [ "${NODE_ENV}" = "production" ]; then + exec node "${WORKER}" "${CONFIG}" +else + node "${WORKER}" "${CONFIG}" | tee -a "${LOG}" | pino-pretty +fi From 705f54869e39308c4953f3ab5e72ba131ea79c4d Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 15 Aug 2018 11:10:23 -0300 Subject: [PATCH 050/119] Update compute-stats script to use new log files names --- scripts/compute-stats.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/compute-stats.js b/scripts/compute-stats.js index 7a23416..3e23fb0 100644 --- a/scripts/compute-stats.js +++ b/scripts/compute-stats.js @@ -52,11 +52,11 @@ function computeCollectedSignaturesStats(collectedSignatures, senderForeign) { } async function main() { - const signatureRequests = await readFile(__dirname, '../logs/watcher-signature-request.txt') - const collectedSignatures = await readFile(__dirname, '../logs/watcher-collected-signatures.txt') - // const affirmationRequests = await readFile(__dirname, '../logs/watcher-affirmation-request.txt') - const senderHome = await readFile(__dirname, '../logs/sender-home.txt') - const senderForeign = await readFile(__dirname, '../logs/sender-foreign.txt') + const signatureRequests = await readFile(__dirname, '../logs/signature-request-watcher.txt') + const collectedSignatures = await readFile(__dirname, '../logs/collected-signatures-watcher.txt') + // const affirmationRequests = await readFile(__dirname, '../logs/affirmation-request-watcher.txt') + const senderHome = await readFile(__dirname, '../logs/home-sender.txt') + const senderForeign = await readFile(__dirname, '../logs/foreign-sender.txt') const signatureRequestsStats = computeSignatureRequestStats(signatureRequests, senderHome) console.log('Signature Requests') From 6632574218f92417edb8fe7e7251b8ff23f5817e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 15 Aug 2018 15:54:25 -0300 Subject: [PATCH 051/119] Upgrade http-list-provider --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98e8695..a7f04d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2000,9 +2000,9 @@ "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" }, "http-list-provider": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.2.tgz", - "integrity": "sha512-yUaAO/JJ4fJuUefgYZ/uQwW8o9PAWN6SkQhXOud+C0SJtcuTsTXGuxvVJFM5d/FE4HsyqP6iRdtTS4srxz76ow==", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.3.tgz", + "integrity": "sha512-+l3UPTtL8F5yjRstgZS/XbJ4ez7hirveDyhxoU5zfeiebNpJPgoslF0Owd1rGTQhic2/QuoM9AnWYby8H5NnOw==", "requires": { "node-fetch": "^2.2.0" }, diff --git a/package.json b/package.json index c30c761..2b3df21 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "amqplib": "^0.5.2", "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", - "http-list-provider": "0.0.2", + "http-list-provider": "0.0.3", "ioredis": "^3.2.2", "lodash": "^4.17.10", "node-fetch": "^2.1.2", From 23c95a243e625d12406e9ff442a221d7254896d7 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 16 Aug 2018 15:10:32 -0300 Subject: [PATCH 052/119] Take required message length from contract --- abis/HomeBridgeErcToErc.abi.json | 14 ++++++++++++++ abis/HomeBridgeNativeToErc.abi.json | 14 ++++++++++++++ src/events/processSignatureRequests.js | 5 ++++- src/utils/message.js | 12 +++++++++--- test/message.test.js | 24 +++++++++++++----------- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/abis/HomeBridgeErcToErc.abi.json b/abis/HomeBridgeErcToErc.abi.json index 6956bc6..3b4e908 100644 --- a/abis/HomeBridgeErcToErc.abi.json +++ b/abis/HomeBridgeErcToErc.abi.json @@ -116,6 +116,20 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "requiredMessageLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, { "constant": true, "inputs": [ diff --git a/abis/HomeBridgeNativeToErc.abi.json b/abis/HomeBridgeNativeToErc.abi.json index f5f8b18..a441427 100644 --- a/abis/HomeBridgeNativeToErc.abi.json +++ b/abis/HomeBridgeNativeToErc.abi.json @@ -102,6 +102,20 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "requiredMessageLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, { "constant": true, "inputs": [ diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index c0f7a35..7b5a7d6 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -15,6 +15,8 @@ function processSignatureRequestsBuilder(config) { return async function processSignatureRequests(signatureRequests) { const txToSend = [] + const expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() + const callbacks = signatureRequests.map(async signatureRequest => { const { recipient, value } = signatureRequest.returnValues @@ -27,7 +29,8 @@ function processSignatureRequestsBuilder(config) { recipient, value, transactionHash: signatureRequest.transactionHash, - bridgeAddress: config.foreignBridgeAddress + bridgeAddress: config.foreignBridgeAddress, + expectedMessageLength }) const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) diff --git a/src/utils/message.js b/src/utils/message.js index 03a0206..24be74c 100644 --- a/src/utils/message.js +++ b/src/utils/message.js @@ -1,11 +1,18 @@ const assert = require('assert') const Web3Utils = require('web3-utils') + // strips leading "0x" if present function strip0x(input) { return input.replace(/^0x/, '') } -function createMessage({ recipient, value, transactionHash, bridgeAddress }) { +function createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength +}) { recipient = strip0x(recipient) assert.equal(recipient.length, 20 * 2) @@ -22,8 +29,7 @@ function createMessage({ recipient, value, transactionHash, bridgeAddress }) { assert.equal(bridgeAddress.length, 20 * 2) const message = `0x${recipient}${value}${transactionHash}${bridgeAddress}` - const expectedMessageLength = (20 + 32 + 32 + 20) * 2 + 2 - assert.equal(message.length, expectedMessageLength) + assert.equal(message.length, 2 + 2 * expectedMessageLength) return message } diff --git a/test/message.test.js b/test/message.test.js index f435418..c111c55 100644 --- a/test/message.test.js +++ b/test/message.test.js @@ -3,6 +3,8 @@ const { createMessage, signatureToVRS } = require('../src/utils/message') describe('message utils', () => { describe('createMessage', () => { + const expectedMessageLength = 104 + it('should create a message when receiving valid values', () => { // given const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' @@ -11,7 +13,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -32,7 +34,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -53,7 +55,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -74,7 +76,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -95,7 +97,7 @@ describe('message utils', () => { const bridgeAddress = 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -115,7 +117,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -128,7 +130,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -141,7 +143,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -154,7 +156,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5aa' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -168,7 +170,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -182,7 +184,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a11' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() From 527a1e8c358c84770bc2a3e6ac4f78b066fa95b5 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Sat, 18 Aug 2018 14:46:21 -0700 Subject: [PATCH 053/119] Add a few helpful commands --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33d9c05..4d10eb0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ There are two Senders: 2. Install [RabbitMQ](https://www.rabbitmq.com/) and [Redis](https://redis.io/) - RabbitMQ version: `3.7` - Redis version: `4.0` - + 3. Create a `.env` file: `cp .env.example .env` and fill in the information using the output data from previous deploy step. Check the `.env.example` file to see the required variables. ## Run the processes @@ -84,6 +84,8 @@ Command | Description --- | --- `rabbitmqctl list_queues` | List all queues `rabbitmqctl purge_queue home` | Remove all messages from `home` queue +`rabbitmqctl status` | check if rabbitmq server is currently running +`rabbitmq-server` | start rabbitMQ server #### Redis Use `redis-cli` @@ -95,6 +97,8 @@ Command | Description `GET signature-request:lastProcessedBlock` | Get the value of key. `DEL signature-request:lastProcessedBlock` | Removes the specified key. `FLUSHALL` | Delete all the keys of all the existing databases +`redis-cli ping` | check if redis is running +`redis-server` | starts redis server ### Env Variables From acedb31eae67524a32f6ed25d6486d54c53821ee Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Sat, 18 Aug 2018 23:21:46 -0700 Subject: [PATCH 054/119] Update readme instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 33d9c05..8fd040c 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,9 @@ On `.env` file set `BRIDGE_MODE=ERC_TO_ERC` - `npm run sender:home` - `npm run sender:foreign` -To send deposits to home contract run `node tests/sendUserTxToErcHome.js` +To deposit from Foreign to Home contract run `node scripts/sendUserTxToErcForeign.js 10` where `10` is how many tx you would like to send out -To send withdrawals to foreign contract run `node tests/sendUserTxToErcForeign.js` +To withdrawal to Home to Foreign contract run `node scripts/sendUserTxToErcHome.js 10` where `10` is how many tx you would like to send out ### Run with Docker From 49523298fe7391c9b96e9a71860eaf5bc3a7a2dd Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 21 Aug 2018 16:18:10 -0300 Subject: [PATCH 055/119] Update contracts submodule --- submodules/poa-bridge-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 57d16f2..2609875 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 57d16f2b8140e2bf237dd960972127590589dc4a +Subproject commit 2609875974d35873de83001aaf76a8fdbbffdf7e From 0f2e792c340dae0b1374431efc29d22ca9aa0b1f Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:50:37 -0300 Subject: [PATCH 056/119] Add bridgeable token parameters to env files --- e2e/envs/contracts-deploy.env | 4 ++++ e2e/envs/erc-contracts-deploy.env | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/e2e/envs/contracts-deploy.env b/e2e/envs/contracts-deploy.env index ed7f46a..b5c0370 100644 --- a/e2e/envs/contracts-deploy.env +++ b/e2e/envs/contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b diff --git a/e2e/envs/erc-contracts-deploy.env b/e2e/envs/erc-contracts-deploy.env index 35705b5..5aa00e2 100644 --- a/e2e/envs/erc-contracts-deploy.env +++ b/e2e/envs/erc-contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b From 8ac0d9e7bf17dec361e0f5bda10f5158b2651d40 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:50:56 -0300 Subject: [PATCH 057/119] Upgrade http-list-provider version --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 962e70f..c9759de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2021,9 +2021,9 @@ "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" }, "http-list-provider": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.3.tgz", - "integrity": "sha512-+l3UPTtL8F5yjRstgZS/XbJ4ez7hirveDyhxoU5zfeiebNpJPgoslF0Owd1rGTQhic2/QuoM9AnWYby8H5NnOw==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.4.tgz", + "integrity": "sha512-UjGwD8+xNTyx1JUwlukwBnicias7lLVWobQewHKLvfpDfAPAYeb9dt03aWGcZuvR+NtbirPWtPkHoZFkalqrVg==", "requires": { "node-fetch": "^2.2.0" }, diff --git a/package.json b/package.json index 2e525f1..765ff97 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "amqplib": "^0.5.2", "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", - "http-list-provider": "0.0.3", + "http-list-provider": "0.0.4", "ioredis": "^3.2.2", "lodash": "^4.17.10", "node-fetch": "^2.1.2", From 342ebd809f18fbfefe0d39926dcf71f09c726bf3 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:51:33 -0300 Subject: [PATCH 058/119] Update poa-bridge-contracts submodule --- submodules/poa-bridge-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 57d16f2..4e869c0 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 57d16f2b8140e2bf237dd960972127590589dc4a +Subproject commit 4e869c0c06dcff4d1d38ca65e9a62e973f5c3e97 From a9098c8e326dd0588c611708412dd2b3935c5002 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:51:48 -0300 Subject: [PATCH 059/119] Add module with custom errors --- src/utils/errors.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/utils/errors.js diff --git a/src/utils/errors.js b/src/utils/errors.js new file mode 100644 index 0000000..24252b1 --- /dev/null +++ b/src/utils/errors.js @@ -0,0 +1,7 @@ +class AlreadyProcessedError extends Error {} +class InvalidValidatorError extends Error {} + +module.exports = { + AlreadyProcessedError, + InvalidValidatorError +} From 7acd0852f1469610b7b6725fd9b66d729719653e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:52:21 -0300 Subject: [PATCH 060/119] Check if validator is correct when transaction fails --- src/events/processSignatureRequests.js | 57 ++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index c0f7a35..1ec3580 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,11 +1,36 @@ require('dotenv').config() const Web3 = require('web3') const HttpListProvider = require('http-list-provider') +const bridgeValidatorsABI = require('../../abis/BridgeValidators.abi') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') +const { AlreadyProcessedError, InvalidValidatorError } = require('../utils/errors') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env +const { HttpListProviderError } = HttpListProvider + +async function estimateGas(web3, bridgeContract, address, method, options) { + try { + const gasEstimate = await method.estimateGas(options) + return gasEstimate + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + const validatorContractAddress = await bridgeContract.methods.validatorContract().call() + const validatorContract = new web3.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + + const isValidator = await validatorContract.methods.isValidator(address).call() + + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) + } + + throw new AlreadyProcessedError(e.message) + } +} function processSignatureRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) @@ -34,18 +59,32 @@ function processSignatureRequestsBuilder(config) { let gasEstimate try { - gasEstimate = await homeBridge.methods - .submitSignature(signature.signature, message) - .estimateGas({ from: VALIDATOR_ADDRESS }) + gasEstimate = await estimateGas( + web3Home, + homeBridge, + VALIDATOR_ADDRESS, + homeBridge.methods.submitSignature(signature.signature, message), + { + from: VALIDATOR_ADDRESS + } + ) + logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { + if (e instanceof HttpListProviderError) { throw new Error(`RPC Connection Error: submitSignature Gas Estimate cannot be obtained.`) + } else if (e instanceof InvalidValidatorError) { + logger.warn({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + throw new Error('Current address does not correspond to a validator') + } else if (e instanceof AlreadyProcessedError) { + logger.info( + { eventTransactionHash: signatureRequest.transactionHash }, + `Already processed signatureRequest ${signatureRequest.transactionHash}` + ) + return + } else { + logger.error(e, 'Unknown error while processing transaction') + return } - logger.info( - { eventTransactionHash: signatureRequest.transactionHash }, - `Already processed signatureRequest ${signatureRequest.transactionHash}` - ) - return } const data = await homeBridge.methods From 3a127ff8fbe742e920ad7608460453e7f43ab0b9 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 16:15:22 -0300 Subject: [PATCH 061/119] Read required message length just once --- src/events/processSignatureRequests.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 7b5a7d6..aca1c17 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -7,6 +7,8 @@ const { createMessage } = require('../utils/message') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env +let expectedMessageLength = null + function processSignatureRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) @@ -15,7 +17,9 @@ function processSignatureRequestsBuilder(config) { return async function processSignatureRequests(signatureRequests) { const txToSend = [] - const expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() + if (expectedMessageLength === null) { + expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() + } const callbacks = signatureRequests.map(async signatureRequest => { const { recipient, value } = signatureRequest.returnValues From 518294414df348097e23a46d457932e914365f68 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 23 Aug 2018 10:15:25 -0300 Subject: [PATCH 062/119] Add bridgeable token parameters to env files --- e2e/envs/contracts-deploy.env | 4 ++++ e2e/envs/erc-contracts-deploy.env | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/e2e/envs/contracts-deploy.env b/e2e/envs/contracts-deploy.env index ed7f46a..b5c0370 100644 --- a/e2e/envs/contracts-deploy.env +++ b/e2e/envs/contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b diff --git a/e2e/envs/erc-contracts-deploy.env b/e2e/envs/erc-contracts-deploy.env index 35705b5..5aa00e2 100644 --- a/e2e/envs/erc-contracts-deploy.env +++ b/e2e/envs/erc-contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b From 2e09b8c480cda7102f8f24f45113675882572119 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 23 Aug 2018 16:37:07 -0300 Subject: [PATCH 063/119] Limit number of concurrent events processed --- package-lock.json | 5 ++ package.json | 1 + src/events/processAffirmationRequests.js | 70 ++++++++------- src/events/processCollectedSignatures.js | 109 ++++++++++++----------- src/events/processSignatureRequests.js | 80 +++++++++-------- src/events/processTransfers.js | 50 ++++++----- src/utils/constants.js | 3 +- 7 files changed, 176 insertions(+), 142 deletions(-) diff --git a/package-lock.json b/package-lock.json index 962e70f..4085b30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5243,6 +5243,11 @@ "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-4.1.11.tgz", "integrity": "sha512-pzp+uNPxHofRhgZ27y+94Kj87fNX2Mu9A9HGpTQQWdyzMpfYT419lhE32zWnqhUCQVrXmvYPDw76pnrwWc+BpQ==" }, + "promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==" + }, "promise-retry": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", diff --git a/package.json b/package.json index 2e525f1..d3ef68d 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "node-fetch": "^2.1.2", "pino": "^4.17.3", "pino-pretty": "^2.0.1", + "promise-limit": "^2.7.0", "promise-retry": "^1.1.1", "redlock": "^3.1.2", "web3": "^1.0.0-beta.34", diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js index 3ecf9c3..bfb532c 100644 --- a/src/events/processAffirmationRequests.js +++ b/src/events/processAffirmationRequests.js @@ -1,11 +1,15 @@ require('dotenv').config() const Web3 = require('web3') const HttpListProvider = require('http-list-provider') +const promiseLimit = require('promise-limit') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') +const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS } = process.env +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + function processAffirmationRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) @@ -14,43 +18,45 @@ function processAffirmationRequestsBuilder(config) { return async function processAffirmationRequests(affirmationRequests) { const txToSend = [] - const callbacks = affirmationRequests.map(async affirmationRequest => { - const { recipient, value } = affirmationRequest.returnValues + const callbacks = affirmationRequests.map(affirmationRequest => + limit(async () => { + const { recipient, value } = affirmationRequest.returnValues - logger.info( - { eventTransactionHash: affirmationRequest.transactionHash, sender: recipient, value }, - `Processing affirmationRequest ${affirmationRequest.transactionHash}` - ) + logger.info( + { eventTransactionHash: affirmationRequest.transactionHash, sender: recipient, value }, + `Processing affirmationRequest ${affirmationRequest.transactionHash}` + ) - let gasEstimate - try { - gasEstimate = await homeBridge.methods - .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error( - `RPC Connection Error: executeAffirmation Gas Estimate cannot be obtained.` + let gasEstimate + try { + gasEstimate = await homeBridge.methods + .executeAffirmation(recipient, value, affirmationRequest.transactionHash) + .estimateGas({ from: VALIDATOR_ADDRESS }) + } catch (e) { + if (e.message.includes('Invalid JSON RPC response')) { + throw new Error( + `RPC Connection Error: executeAffirmation Gas Estimate cannot be obtained.` + ) + } + logger.info( + { eventTransactionHash: affirmationRequest.transactionHash }, + `Already processed affirmationRequest ${affirmationRequest.transactionHash}` ) + return } - logger.info( - { eventTransactionHash: affirmationRequest.transactionHash }, - `Already processed affirmationRequest ${affirmationRequest.transactionHash}` - ) - return - } - - const data = await homeBridge.methods - .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: affirmationRequest.transactionHash, - to: config.homeBridgeAddress + + const data = await homeBridge.methods + .executeAffirmation(recipient, value, affirmationRequest.transactionHash) + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: affirmationRequest.transactionHash, + to: config.homeBridgeAddress + }) }) - }) + ) await Promise.all(callbacks) return txToSend diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index 2147229..9f24c85 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -1,12 +1,16 @@ require('dotenv').config() const Web3 = require('web3') const HttpListProvider = require('http-list-provider') +const promiseLimit = require('promise-limit') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { signatureToVRS } = require('../utils/message') +const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS } = process.env +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + function processCollectedSignaturesBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) @@ -21,68 +25,71 @@ function processCollectedSignaturesBuilder(config) { return async function processCollectedSignatures(signatures) { const txToSend = [] - const callbacks = signatures.map(async colSignature => { - const { - authorityResponsibleForRelay, - messageHash, - NumberOfCollectedSignatures - } = colSignature.returnValues - if (authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(VALIDATOR_ADDRESS)) { - logger.info( - { eventTransactionHash: colSignature.transactionHash }, - `Processing CollectedSignatures ${colSignature.transactionHash}` - ) - const message = await homeBridge.methods.message(messageHash).call() + const callbacks = signatures.map(colSignature => + limit(async () => { + const { + authorityResponsibleForRelay, + messageHash, + NumberOfCollectedSignatures + } = colSignature.returnValues + + if (authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(VALIDATOR_ADDRESS)) { + logger.info( + { eventTransactionHash: colSignature.transactionHash }, + `Processing CollectedSignatures ${colSignature.transactionHash}` + ) + const message = await homeBridge.methods.message(messageHash).call() - const requiredSignatures = [] - requiredSignatures.length = NumberOfCollectedSignatures - requiredSignatures.fill(0) + const requiredSignatures = [] + requiredSignatures.length = NumberOfCollectedSignatures + requiredSignatures.fill(0) - const [v, r, s] = [[], [], []] - const signaturePromises = requiredSignatures.map(async (el, index) => { - const signature = await homeBridge.methods.signature(messageHash, index).call() - const recover = signatureToVRS(signature) - v.push(recover.v) - r.push(recover.r) - s.push(recover.s) - }) + const [v, r, s] = [[], [], []] + const signaturePromises = requiredSignatures.map(async (el, index) => { + const signature = await homeBridge.methods.signature(messageHash, index).call() + const recover = signatureToVRS(signature) + v.push(recover.v) + r.push(recover.r) + s.push(recover.s) + }) - await Promise.all(signaturePromises) + await Promise.all(signaturePromises) - let gasEstimate - try { - gasEstimate = await foreignBridge.methods - .executeSignatures(v, r, s, message) - .estimateGas() - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error( - `RPC Connection Error: executeSignatures Gas Estimate cannot be obtained.` + let gasEstimate + try { + gasEstimate = await foreignBridge.methods + .executeSignatures(v, r, s, message) + .estimateGas() + } catch (e) { + if (e.message.includes('Invalid JSON RPC response')) { + throw new Error( + `RPC Connection Error: executeSignatures Gas Estimate cannot be obtained.` + ) + } + logger.info( + { eventTransactionHash: colSignature.transactionHash }, + `Already processed CollectedSignatures ${colSignature.transactionHash}` ) + return } + const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() + txToSend.push({ + data, + gasEstimate, + transactionReference: colSignature.transactionHash, + to: config.foreignBridgeAddress + }) + } else { logger.info( { eventTransactionHash: colSignature.transactionHash }, - `Already processed CollectedSignatures ${colSignature.transactionHash}` + `Validator not responsible for relaying CollectedSignatures ${ + colSignature.transactionHash + }` ) - return } - const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() - txToSend.push({ - data, - gasEstimate, - transactionReference: colSignature.transactionHash, - to: config.foreignBridgeAddress - }) - } else { - logger.info( - { eventTransactionHash: colSignature.transactionHash }, - `Validator not responsible for relaying CollectedSignatures ${ - colSignature.transactionHash - }` - ) - } - }) + }) + ) await Promise.all(callbacks) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index c0f7a35..45f029d 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,12 +1,16 @@ require('dotenv').config() const Web3 = require('web3') const HttpListProvider = require('http-list-provider') +const promiseLimit = require('promise-limit') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') +const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + function processSignatureRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) @@ -15,50 +19,54 @@ function processSignatureRequestsBuilder(config) { return async function processSignatureRequests(signatureRequests) { const txToSend = [] - const callbacks = signatureRequests.map(async signatureRequest => { - const { recipient, value } = signatureRequest.returnValues + const callbacks = signatureRequests.map(signatureRequest => + limit(async () => { + const { recipient, value } = signatureRequest.returnValues - logger.info( - { eventTransactionHash: signatureRequest.transactionHash, sender: recipient, value }, - `Processing signatureRequest ${signatureRequest.transactionHash}` - ) + logger.info( + { eventTransactionHash: signatureRequest.transactionHash, sender: recipient, value }, + `Processing signatureRequest ${signatureRequest.transactionHash}` + ) - const message = createMessage({ - recipient, - value, - transactionHash: signatureRequest.transactionHash, - bridgeAddress: config.foreignBridgeAddress - }) + const message = createMessage({ + recipient, + value, + transactionHash: signatureRequest.transactionHash, + bridgeAddress: config.foreignBridgeAddress + }) - const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) + const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) - let gasEstimate - try { - gasEstimate = await homeBridge.methods - .submitSignature(signature.signature, message) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error(`RPC Connection Error: submitSignature Gas Estimate cannot be obtained.`) + let gasEstimate + try { + gasEstimate = await homeBridge.methods + .submitSignature(signature.signature, message) + .estimateGas({ from: VALIDATOR_ADDRESS }) + } catch (e) { + if (e.message.includes('Invalid JSON RPC response')) { + throw new Error( + `RPC Connection Error: submitSignature Gas Estimate cannot be obtained.` + ) + } + logger.info( + { eventTransactionHash: signatureRequest.transactionHash }, + `Already processed signatureRequest ${signatureRequest.transactionHash}` + ) + return } - logger.info( - { eventTransactionHash: signatureRequest.transactionHash }, - `Already processed signatureRequest ${signatureRequest.transactionHash}` - ) - return - } - const data = await homeBridge.methods - .submitSignature(signature.signature, message) - .encodeABI({ from: VALIDATOR_ADDRESS }) + const data = await homeBridge.methods + .submitSignature(signature.signature, message) + .encodeABI({ from: VALIDATOR_ADDRESS }) - txToSend.push({ - data, - gasEstimate, - transactionReference: signatureRequest.transactionHash, - to: config.homeBridgeAddress + txToSend.push({ + data, + gasEstimate, + transactionReference: signatureRequest.transactionHash, + to: config.homeBridgeAddress + }) }) - }) + ) await Promise.all(callbacks) return txToSend diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js index 06ed981..0b59ee9 100644 --- a/src/events/processTransfers.js +++ b/src/events/processTransfers.js @@ -1,10 +1,14 @@ require('dotenv').config() const Web3 = require('web3') const HttpListProvider = require('http-list-provider') +const promiseLimit = require('promise-limit') const rpcUrlsManager = require('../services/getRpcUrlsManager') +const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS } = process.env +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + function processTransfersBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) @@ -13,30 +17,32 @@ function processTransfersBuilder(config) { return async function processTransfers(transfers) { const txToSend = [] - const callbacks = transfers.map(async (transfer, index) => { - const { from, value } = transfer.returnValues - - let gasEstimate - try { - gasEstimate = await homeBridge.methods + const callbacks = transfers.map((transfer, index) => + limit(async () => { + const { from, value } = transfer.returnValues + + let gasEstimate + try { + gasEstimate = await homeBridge.methods + .executeAffirmation(from, value, transfer.transactionHash) + .estimateGas({ from: VALIDATOR_ADDRESS }) + } catch (e) { + console.log(index + 1, '# already processed Transfer', transfer.transactionHash) + return + } + + const data = await homeBridge.methods .executeAffirmation(from, value, transfer.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - console.log(index + 1, '# already processed Transfer', transfer.transactionHash) - return - } - - const data = await homeBridge.methods - .executeAffirmation(from, value, transfer.transactionHash) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: transfer.transactionHash, - to: config.homeBridgeAddress + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: transfer.transactionHash, + to: config.homeBridgeAddress + }) }) - }) + ) await Promise.all(callbacks) return txToSend diff --git a/src/utils/constants.js b/src/utils/constants.js index 1c57078..e86c322 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,3 +1,4 @@ module.exports = { - EXTRA_GAS_PERCENTAGE: 0.25 + EXTRA_GAS_PERCENTAGE: 0.25, + MAX_CONCURRENT_EVENTS: 50 } From a71086658b98a351be1231be53615b98da5ccf16 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 27 Aug 2018 10:06:04 -0300 Subject: [PATCH 064/119] Add bridgeable token parameters to env files --- e2e/envs/contracts-deploy.env | 4 ++++ e2e/envs/erc-contracts-deploy.env | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/e2e/envs/contracts-deploy.env b/e2e/envs/contracts-deploy.env index ed7f46a..b5c0370 100644 --- a/e2e/envs/contracts-deploy.env +++ b/e2e/envs/contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b diff --git a/e2e/envs/erc-contracts-deploy.env b/e2e/envs/erc-contracts-deploy.env index 35705b5..5aa00e2 100644 --- a/e2e/envs/erc-contracts-deploy.env +++ b/e2e/envs/erc-contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b From b4dd3afb389116162af1078881c3ce38d3d66cd2 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 27 Aug 2018 11:10:39 -0300 Subject: [PATCH 065/119] Fix formatting --- test/message.test.js | 58 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/test/message.test.js b/test/message.test.js index c111c55..7640824 100644 --- a/test/message.test.js +++ b/test/message.test.js @@ -13,7 +13,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -34,7 +40,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -55,7 +67,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -76,7 +94,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -97,7 +121,13 @@ describe('message utils', () => { const bridgeAddress = 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -117,7 +147,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -130,7 +161,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -143,7 +175,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -156,7 +189,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5aa' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -170,7 +204,8 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -184,7 +219,8 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a11' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() From fb8f9b21d95ec517616bac40fd516a070f328f34 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 16 Aug 2018 15:10:32 -0300 Subject: [PATCH 066/119] Take required message length from contract --- abis/HomeBridgeErcToErc.abi.json | 14 ++++++++++++++ abis/HomeBridgeNativeToErc.abi.json | 14 ++++++++++++++ src/events/processSignatureRequests.js | 5 ++++- src/utils/message.js | 12 +++++++++--- test/message.test.js | 24 +++++++++++++----------- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/abis/HomeBridgeErcToErc.abi.json b/abis/HomeBridgeErcToErc.abi.json index 6956bc6..3b4e908 100644 --- a/abis/HomeBridgeErcToErc.abi.json +++ b/abis/HomeBridgeErcToErc.abi.json @@ -116,6 +116,20 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "requiredMessageLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, { "constant": true, "inputs": [ diff --git a/abis/HomeBridgeNativeToErc.abi.json b/abis/HomeBridgeNativeToErc.abi.json index f5f8b18..a441427 100644 --- a/abis/HomeBridgeNativeToErc.abi.json +++ b/abis/HomeBridgeNativeToErc.abi.json @@ -102,6 +102,20 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "requiredMessageLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, { "constant": true, "inputs": [ diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index c0f7a35..7b5a7d6 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -15,6 +15,8 @@ function processSignatureRequestsBuilder(config) { return async function processSignatureRequests(signatureRequests) { const txToSend = [] + const expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() + const callbacks = signatureRequests.map(async signatureRequest => { const { recipient, value } = signatureRequest.returnValues @@ -27,7 +29,8 @@ function processSignatureRequestsBuilder(config) { recipient, value, transactionHash: signatureRequest.transactionHash, - bridgeAddress: config.foreignBridgeAddress + bridgeAddress: config.foreignBridgeAddress, + expectedMessageLength }) const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) diff --git a/src/utils/message.js b/src/utils/message.js index 03a0206..24be74c 100644 --- a/src/utils/message.js +++ b/src/utils/message.js @@ -1,11 +1,18 @@ const assert = require('assert') const Web3Utils = require('web3-utils') + // strips leading "0x" if present function strip0x(input) { return input.replace(/^0x/, '') } -function createMessage({ recipient, value, transactionHash, bridgeAddress }) { +function createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength +}) { recipient = strip0x(recipient) assert.equal(recipient.length, 20 * 2) @@ -22,8 +29,7 @@ function createMessage({ recipient, value, transactionHash, bridgeAddress }) { assert.equal(bridgeAddress.length, 20 * 2) const message = `0x${recipient}${value}${transactionHash}${bridgeAddress}` - const expectedMessageLength = (20 + 32 + 32 + 20) * 2 + 2 - assert.equal(message.length, expectedMessageLength) + assert.equal(message.length, 2 + 2 * expectedMessageLength) return message } diff --git a/test/message.test.js b/test/message.test.js index f435418..c111c55 100644 --- a/test/message.test.js +++ b/test/message.test.js @@ -3,6 +3,8 @@ const { createMessage, signatureToVRS } = require('../src/utils/message') describe('message utils', () => { describe('createMessage', () => { + const expectedMessageLength = 104 + it('should create a message when receiving valid values', () => { // given const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' @@ -11,7 +13,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -32,7 +34,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -53,7 +55,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -74,7 +76,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -95,7 +97,7 @@ describe('message utils', () => { const bridgeAddress = 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress }) + const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(message).to.equal( @@ -115,7 +117,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -128,7 +130,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -141,7 +143,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -154,7 +156,7 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5aa' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -168,7 +170,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -182,7 +184,7 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a11' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress }) + const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() From f2d0ba437ea254ba64a779a858efe9fc1295d6e0 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 21 Aug 2018 16:18:10 -0300 Subject: [PATCH 067/119] Update contracts submodule --- submodules/poa-bridge-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 57d16f2..2609875 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 57d16f2b8140e2bf237dd960972127590589dc4a +Subproject commit 2609875974d35873de83001aaf76a8fdbbffdf7e From 7e11b9b0aec87d49d5ca7b913c408d53d31c8bc4 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 16:15:22 -0300 Subject: [PATCH 068/119] Read required message length just once --- src/events/processSignatureRequests.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 7b5a7d6..aca1c17 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -7,6 +7,8 @@ const { createMessage } = require('../utils/message') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env +let expectedMessageLength = null + function processSignatureRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) const web3Home = new Web3(homeProvider) @@ -15,7 +17,9 @@ function processSignatureRequestsBuilder(config) { return async function processSignatureRequests(signatureRequests) { const txToSend = [] - const expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() + if (expectedMessageLength === null) { + expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() + } const callbacks = signatureRequests.map(async signatureRequest => { const { recipient, value } = signatureRequest.returnValues From b3ad1703591a955233c07e3e73bcbb2bd0644b2d Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 23 Aug 2018 10:15:25 -0300 Subject: [PATCH 069/119] Add bridgeable token parameters to env files --- e2e/envs/contracts-deploy.env | 4 ++++ e2e/envs/erc-contracts-deploy.env | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/e2e/envs/contracts-deploy.env b/e2e/envs/contracts-deploy.env index ed7f46a..b5c0370 100644 --- a/e2e/envs/contracts-deploy.env +++ b/e2e/envs/contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b diff --git a/e2e/envs/erc-contracts-deploy.env b/e2e/envs/erc-contracts-deploy.env index 35705b5..5aa00e2 100644 --- a/e2e/envs/erc-contracts-deploy.env +++ b/e2e/envs/erc-contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b From d28be8f94339c6f7a2823f2cd51f07d42883e127 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 27 Aug 2018 11:10:39 -0300 Subject: [PATCH 070/119] Fix formatting --- test/message.test.js | 58 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/test/message.test.js b/test/message.test.js index c111c55..7640824 100644 --- a/test/message.test.js +++ b/test/message.test.js @@ -13,7 +13,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -34,7 +40,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -55,7 +67,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -76,7 +94,13 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -97,7 +121,13 @@ describe('message utils', () => { const bridgeAddress = 'fA79875FB0828c1FBD438583ED23fF5a956D80a1' // when - const message = createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const message = createMessage({ + recipient, + value, + transactionHash, + bridgeAddress, + expectedMessageLength + }) // then expect(message).to.equal( @@ -117,7 +147,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -130,7 +161,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -143,7 +175,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -156,7 +189,8 @@ describe('message utils', () => { const transactionHash = '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5aa' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -170,7 +204,8 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() @@ -184,7 +219,8 @@ describe('message utils', () => { const bridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a11' // when - const messageThunk = () => createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) + const messageThunk = () => + createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) // then expect(messageThunk).to.throw() From 1c797363b097d6b37e39abf61cbd99d8090e9b1d Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 28 Aug 2018 13:27:44 -0300 Subject: [PATCH 071/119] Use retry in RpcUrlsManager --- src/services/RpcUrlsManager.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/services/RpcUrlsManager.js b/src/services/RpcUrlsManager.js index 8d19d90..7211638 100644 --- a/src/services/RpcUrlsManager.js +++ b/src/services/RpcUrlsManager.js @@ -1,4 +1,5 @@ const _ = require('lodash') +const promiseRetry = require('promise-retry') const tryEach = require('../utils/tryEach') function RpcUrlsManager(homeUrls, foreignUrls) { @@ -21,7 +22,11 @@ RpcUrlsManager.prototype.tryEach = async function(chain, f) { // save homeUrls to avoid race condition const urls = chain === 'home' ? _.cloneDeep(this.homeUrls) : _.cloneDeep(this.foreignUrls) - const [result, index] = await tryEach(urls, f) + const [result, index] = await promiseRetry(retry => + tryEach(urls, f).catch(() => { + retry() + }) + ) if (index > 0) { // rotate urls From f93131d6861c0071e90c83bd5ffa0e4aca17cdef Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 28 Aug 2018 15:19:20 -0300 Subject: [PATCH 072/119] Throw if RPC if response is not 200 --- src/tx/sendTx.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tx/sendTx.js b/src/tx/sendTx.js index ff5a03d..a1a27c3 100644 --- a/src/tx/sendTx.js +++ b/src/tx/sendTx.js @@ -39,7 +39,7 @@ async function sendTx({ async function sendRawTx({ chain, params, method }) { const result = await rpcUrlsManager.tryEach(chain, async url => { // curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}' - return fetch(url, { + const response = await fetch(url, { headers: { 'Content-type': 'application/json' }, @@ -51,6 +51,12 @@ async function sendRawTx({ chain, params, method }) { id: Math.floor(Math.random() * 100) + 1 }) }) + + if (!response.ok) { + throw new Error(response.statusText) + } + + return response }) const json = await result.json() From cc3c4a380f8cffd64744af5f7e420bb9fc6765b5 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 28 Aug 2018 13:25:51 -0300 Subject: [PATCH 073/119] Instantiate web3 in a single place --- config/base.config.js | 12 ++++++------ config/foreign-sender.config.js | 6 +++--- config/home-sender.config.js | 6 +++--- scripts/sendUserTxToForeign.js | 7 +------ scripts/sendUserTxToHome.js | 7 +------ src/events/processAffirmationRequests.js | 6 +----- src/events/processCollectedSignatures.js | 8 +------- src/events/processSignatureRequests.js | 6 +----- src/events/processTransfers.js | 6 +----- src/sender.js | 5 +---- src/services/gasPrice.js | 8 +------- src/services/web3.js | 14 ++++++++++++++ src/watcher.js | 5 +---- 13 files changed, 35 insertions(+), 61 deletions(-) create mode 100644 src/services/web3.js diff --git a/config/base.config.js b/config/base.config.js index e041b33..171113f 100644 --- a/config/base.config.js +++ b/config/base.config.js @@ -1,13 +1,13 @@ require('dotenv').config() +const { web3Home, web3Foreign } = require('../src/services/web3') + const homeNativeAbi = require('../abis/HomeBridgeNativeToErc.abi') const foreignNativeAbi = require('../abis/ForeignBridgeNativeToErc.abi') const homeErcAbi = require('../abis/HomeBridgeErcToErc.abi') const foreignErcAbi = require('../abis/ForeignBridgeErcToErc.abi') -const rpcUrlsManager = require('../src/services/getRpcUrlsManager') - const isErcToErc = process.env.BRIDGE_MODE && process.env.BRIDGE_MODE === 'ERC_TO_ERC' const homeAbi = isErcToErc ? homeErcAbi : homeNativeAbi @@ -22,23 +22,23 @@ const bridgeConfig = { } const homeConfig = { - urls: rpcUrlsManager.homeUrls, eventContractAddress: process.env.HOME_BRIDGE_ADDRESS, eventAbi: homeAbi, bridgeContractAddress: process.env.HOME_BRIDGE_ADDRESS, bridgeAbi: homeAbi, pollingInterval: process.env.HOME_POLLING_INTERVAL, - startBlock: process.env.HOME_START_BLOCK + startBlock: process.env.HOME_START_BLOCK, + web3: web3Home } const foreignConfig = { - urls: rpcUrlsManager.foreignUrls, eventContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, eventAbi: foreignAbi, bridgeContractAddress: process.env.FOREIGN_BRIDGE_ADDRESS, bridgeAbi: foreignAbi, pollingInterval: process.env.FOREIGN_POLLING_INTERVAL, - startBlock: process.env.FOREIGN_START_BLOCK + startBlock: process.env.FOREIGN_START_BLOCK, + web3: web3Foreign } module.exports = { diff --git a/config/foreign-sender.config.js b/config/foreign-sender.config.js index 958177e..472c873 100644 --- a/config/foreign-sender.config.js +++ b/config/foreign-sender.config.js @@ -1,10 +1,10 @@ require('dotenv').config() -const rpcUrlsManager = require('../src/services/getRpcUrlsManager') +const { web3Foreign } = require('../src/services/web3') module.exports = { - urls: rpcUrlsManager.foreignUrls, queue: 'foreign', id: 'foreign', - name: 'sender-foreign' + name: 'sender-foreign', + web3: web3Foreign } diff --git a/config/home-sender.config.js b/config/home-sender.config.js index 90b3480..6aa65aa 100644 --- a/config/home-sender.config.js +++ b/config/home-sender.config.js @@ -1,10 +1,10 @@ require('dotenv').config() -const rpcUrlsManager = require('../src/services/getRpcUrlsManager') +const { web3Home } = require('../src/services/web3') module.exports = { - urls: rpcUrlsManager.homeUrls, queue: 'home', id: 'home', - name: 'sender-home' + name: 'sender-home', + web3: web3Home } diff --git a/scripts/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js index d709426..4671c92 100644 --- a/scripts/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -1,8 +1,6 @@ require('dotenv').config() -const Web3 = require('web3') const Web3Utils = require('web3-utils') -const HttpListProvider = require('http-list-provider') -const rpcUrlsManager = require('../src/services/getRpcUrlsManager') +const { web3Foreign } = require('../src/services/web3') const { sendTx, sendRawTx } = require('../src/tx/sendTx') const { @@ -46,9 +44,6 @@ const ERC20_ABI = [ } ] -const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) -const web3Foreign = new Web3(foreignProvider) - const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, ERC20_TOKEN_ADDRESS) async function main() { diff --git a/scripts/sendUserTxToHome.js b/scripts/sendUserTxToHome.js index 89156a7..601c376 100644 --- a/scripts/sendUserTxToHome.js +++ b/scripts/sendUserTxToHome.js @@ -1,9 +1,7 @@ require('dotenv').config() -const Web3 = require('web3') const Web3Utils = require('web3-utils') -const HttpListProvider = require('http-list-provider') +const { web3Home } = require('../src/services/web3') const { sendTx, sendRawTx } = require('../src/tx/sendTx') -const rpcUrlsManager = require('../src/services/getRpcUrlsManager') const { USER_ADDRESS, @@ -14,9 +12,6 @@ const { const NUMBER_OF_DEPOSITS_TO_SEND = process.argv[2] || 1 -const homeProvider = HttpListProvider(rpcUrlsManager.homeUrls) -const web3Home = new Web3(homeProvider) - async function main() { try { const homeChaindId = await sendRawTx({ diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js index 3ecf9c3..7654cce 100644 --- a/src/events/processAffirmationRequests.js +++ b/src/events/processAffirmationRequests.js @@ -1,14 +1,10 @@ require('dotenv').config() -const Web3 = require('web3') -const HttpListProvider = require('http-list-provider') const logger = require('../services/logger') -const rpcUrlsManager = require('../services/getRpcUrlsManager') +const { web3Home } = require('../services/web3') const { VALIDATOR_ADDRESS } = process.env function processAffirmationRequestsBuilder(config) { - const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) - const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) return async function processAffirmationRequests(affirmationRequests) { diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures.js index 2147229..5454559 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures.js @@ -1,19 +1,13 @@ require('dotenv').config() -const Web3 = require('web3') -const HttpListProvider = require('http-list-provider') const logger = require('../services/logger') -const rpcUrlsManager = require('../services/getRpcUrlsManager') +const { web3Home, web3Foreign } = require('../services/web3') const { signatureToVRS } = require('../utils/message') const { VALIDATOR_ADDRESS } = process.env function processCollectedSignaturesBuilder(config) { - const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) - const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) - const web3Foreign = new Web3(foreignProvider) const foreignBridge = new web3Foreign.eth.Contract( config.foreignBridgeAbi, config.foreignBridgeAddress diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index aca1c17..50e50e0 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,8 +1,6 @@ require('dotenv').config() -const Web3 = require('web3') -const HttpListProvider = require('http-list-provider') const logger = require('../services/logger') -const rpcUrlsManager = require('../services/getRpcUrlsManager') +const { web3Home } = require('../services/web3') const { createMessage } = require('../utils/message') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env @@ -10,8 +8,6 @@ const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env let expectedMessageLength = null function processSignatureRequestsBuilder(config) { - const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) - const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) return async function processSignatureRequests(signatureRequests) { diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js index 06ed981..1d7fe5b 100644 --- a/src/events/processTransfers.js +++ b/src/events/processTransfers.js @@ -1,13 +1,9 @@ require('dotenv').config() -const Web3 = require('web3') -const HttpListProvider = require('http-list-provider') -const rpcUrlsManager = require('../services/getRpcUrlsManager') +const { web3Home } = require('../services/web3') const { VALIDATOR_ADDRESS } = process.env function processTransfersBuilder(config) { - const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) - const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) return async function processTransfers(transfers) { diff --git a/src/sender.js b/src/sender.js index afdbe29..4e7afc9 100644 --- a/src/sender.js +++ b/src/sender.js @@ -1,7 +1,5 @@ require('dotenv').config() const path = require('path') -const Web3 = require('web3') -const HttpListProvider = require('http-list-provider') const { connectSenderToQueue } = require('./services/amqpClient') const { redis, redlock } = require('./services/redisClient') const GasPrice = require('./services/gasPrice') @@ -21,8 +19,7 @@ if (process.argv.length < 3) { const config = require(path.join('../config/', process.argv[2])) -const provider = new HttpListProvider(config.urls) -const web3Instance = new Web3(provider) +const web3Instance = config.web3 const nonceLock = `lock:${config.id}:nonce` const nonceKey = `${config.id}:nonce` let chainId = 0 diff --git a/src/services/gasPrice.js b/src/services/gasPrice.js index 37c3f90..4768122 100644 --- a/src/services/gasPrice.js +++ b/src/services/gasPrice.js @@ -1,14 +1,12 @@ require('dotenv').config() -const Web3 = require('web3') const fetch = require('node-fetch') -const HttpListProvider = require('http-list-provider') +const { web3Home, web3Foreign } = require('../services/web3') const { isErcToErc } = require('../../config/base.config') const HomeNativeABI = require('../../abis/HomeBridgeNativeToErc.abi') const ForeignNativeABI = require('../../abis/ForeignBridgeNativeToErc.abi') const HomeErcABI = require('../../abis/HomeBridgeErcToErc.abi') const ForeignErcABI = require('../../abis/ForeignBridgeErcToErc.abi') const logger = require('../services/logger') -const rpcUrlsManager = require('../services/getRpcUrlsManager') const HomeABI = isErcToErc ? HomeErcABI : HomeNativeABI const ForeignABI = isErcToErc ? ForeignNativeABI : ForeignErcABI @@ -26,12 +24,8 @@ const { HOME_GAS_PRICE_UPDATE_INTERVAL } = process.env -const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) -const web3Home = new Web3(homeProvider) const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS) -const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) -const web3Foreign = new Web3(foreignProvider) const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS) let cachedGasPrice = null diff --git a/src/services/web3.js b/src/services/web3.js new file mode 100644 index 0000000..1d6cf4a --- /dev/null +++ b/src/services/web3.js @@ -0,0 +1,14 @@ +const HttpListProvider = require('http-list-provider') +const Web3 = require('web3') +const rpcUrlsManager = require('./getRpcUrlsManager') + +const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) +const web3Home = new Web3(homeProvider) + +const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) +const web3Foreign = new Web3(foreignProvider) + +module.exports = { + web3Home, + web3Foreign +} diff --git a/src/watcher.js b/src/watcher.js index f618a56..3e748c6 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -1,7 +1,5 @@ require('dotenv').config() const path = require('path') -const Web3 = require('web3') -const HttpListProvider = require('http-list-provider') const { connectWatcherToQueue, connection } = require('./services/amqpClient') const { getBlockNumber } = require('./tx/web3') const { redis } = require('./services/redisClient') @@ -22,8 +20,7 @@ const processCollectedSignatures = require('./events/processCollectedSignatures' const processAffirmationRequests = require('./events/processAffirmationRequests')(config) const processTransfers = require('./events/processTransfers')(config) -const provider = new HttpListProvider(config.urls) -const web3Instance = new Web3(provider) +const web3Instance = config.web3 const bridgeContract = new web3Instance.eth.Contract(config.bridgeAbi, config.bridgeContractAddress) const eventContract = new web3Instance.eth.Contract(config.eventAbi, config.eventContractAddress) const lastBlockRedisKey = `${config.id}:lastProcessedBlock` From d0b9a24685ce4a40377d5d1977df76323badd95f Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 28 Aug 2018 15:19:35 -0300 Subject: [PATCH 074/119] Add retrying to web3 --- src/services/web3.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/services/web3.js b/src/services/web3.js index 1d6cf4a..48a5ce4 100644 --- a/src/services/web3.js +++ b/src/services/web3.js @@ -2,10 +2,18 @@ const HttpListProvider = require('http-list-provider') const Web3 = require('web3') const rpcUrlsManager = require('./getRpcUrlsManager') -const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) +const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls, { + retry: { + retries: 10 + } +}) const web3Home = new Web3(homeProvider) -const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls) +const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls, { + retry: { + retries: 10 + } +}) const web3Foreign = new Web3(foreignProvider) module.exports = { From ff2350e82a295f6d21778622eced8a34e3bec107 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 29 Aug 2018 13:52:55 -0300 Subject: [PATCH 075/119] Fix missing argument to createMessage --- src/events/processSignatureRequests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index bac9a68..f9be8cb 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -38,7 +38,8 @@ function processSignatureRequestsBuilder(config) { recipient, value, transactionHash: signatureRequest.transactionHash, - bridgeAddress: config.foreignBridgeAddress + bridgeAddress: config.foreignBridgeAddress, + expectedMessageLength }) const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`) From acb87388f9b652354879d96429a1e67de6ea7a0c Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 30 Aug 2018 17:20:48 -0300 Subject: [PATCH 076/119] Change retry config and move it to constants --- src/services/RpcUrlsManager.js | 3 ++- src/services/web3.js | 9 +++------ src/utils/constants.js | 8 +++++++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/services/RpcUrlsManager.js b/src/services/RpcUrlsManager.js index 7211638..225ed30 100644 --- a/src/services/RpcUrlsManager.js +++ b/src/services/RpcUrlsManager.js @@ -1,6 +1,7 @@ const _ = require('lodash') const promiseRetry = require('promise-retry') const tryEach = require('../utils/tryEach') +const { RETRY_CONFIG } = require('../utils/constants') function RpcUrlsManager(homeUrls, foreignUrls) { if (!homeUrls) { @@ -25,7 +26,7 @@ RpcUrlsManager.prototype.tryEach = async function(chain, f) { const [result, index] = await promiseRetry(retry => tryEach(urls, f).catch(() => { retry() - }) + }, RETRY_CONFIG) ) if (index > 0) { diff --git a/src/services/web3.js b/src/services/web3.js index 48a5ce4..6745827 100644 --- a/src/services/web3.js +++ b/src/services/web3.js @@ -1,18 +1,15 @@ const HttpListProvider = require('http-list-provider') const Web3 = require('web3') const rpcUrlsManager = require('./getRpcUrlsManager') +const { RETRY_CONFIG } = require('../utils/constants') const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls, { - retry: { - retries: 10 - } + retry: RETRY_CONFIG }) const web3Home = new Web3(homeProvider) const foreignProvider = new HttpListProvider(rpcUrlsManager.foreignUrls, { - retry: { - retries: 10 - } + retry: RETRY_CONFIG }) const web3Foreign = new Web3(foreignProvider) diff --git a/src/utils/constants.js b/src/utils/constants.js index e86c322..cfc389b 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,4 +1,10 @@ module.exports = { EXTRA_GAS_PERCENTAGE: 0.25, - MAX_CONCURRENT_EVENTS: 50 + MAX_CONCURRENT_EVENTS: 50, + RETRY_CONFIG: { + retries: 20, + factor: 1.4, + maxTimeout: 360000, + randomize: true + } } From 2d0e6e9715f1c5e306916a0eba386e6083e52d7f Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 30 Aug 2018 17:22:11 -0300 Subject: [PATCH 077/119] Upgrade http-list-provider --- package-lock.json | 15 +++++++++++---- package.json | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4085b30..f483de5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1108,6 +1108,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.1.tgz", + "integrity": "sha512-urQxA1smbLZ2cBbXbaYObM1dJ82aJ2H57A1C/Kklfh/ZN1bgH4G/n5KWhdNfOK11W98gqZfyYj7W4frJJRwA2w==" + }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", @@ -2021,11 +2026,13 @@ "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" }, "http-list-provider": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.3.tgz", - "integrity": "sha512-+l3UPTtL8F5yjRstgZS/XbJ4ez7hirveDyhxoU5zfeiebNpJPgoslF0Owd1rGTQhic2/QuoM9AnWYby8H5NnOw==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.5.tgz", + "integrity": "sha512-s6hMhJLQmCtF3EbM5xDZW97Urk8jG2wfXDVOCANcF0DO/tXPq1iACZ7eFb9+obYVd7459w1mvCgiDUskIschmg==", "requires": { - "node-fetch": "^2.2.0" + "deepmerge": "^2.1.1", + "node-fetch": "^2.2.0", + "promise-retry": "^1.1.1" }, "dependencies": { "node-fetch": { diff --git a/package.json b/package.json index d3ef68d..6812ca9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "amqplib": "^0.5.2", "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", - "http-list-provider": "0.0.3", + "http-list-provider": "0.0.5", "ioredis": "^3.2.2", "lodash": "^4.17.10", "node-fetch": "^2.1.2", From 88e7a0b4e68301b9fc708503a89f93e164d8a6da Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 3 Sep 2018 08:55:28 -0300 Subject: [PATCH 078/119] Add another account to docker test chain --- e2e/parity/chain.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/parity/chain.json b/e2e/parity/chain.json index 294c7e8..322a415 100644 --- a/e2e/parity/chain.json +++ b/e2e/parity/chain.json @@ -47,6 +47,7 @@ "0000000000000000000000000000000000000008": { "balance": "1", "builtin": { "name": "alt_bn128_pairing", "activate_at": 0, "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } } }, "0000000000000000000000000000000000001337": { "balance": "1", "constructor": "0x606060405233600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550670de0b6b3a764000060035534610000575b612904806100666000396000f3006060604052361561013c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306b2ff471461014157806313af40351461018c57806319362a28146101bf5780633f3935d114610248578063432ced04146102b75780634f39ca59146102eb5780636795dbcd1461032457806369fe0e2d146103c857806379ce9fac146103fd5780638da5cb5b1461045557806390b97fc1146104a457806392698814146105245780639890220b1461055d578063ac4e73f914610584578063ac72c12014610612578063c3a358251461064b578063ddca3f43146106c3578063deb931a2146106e6578063df57b74214610747578063e30bd740146107a8578063eadf976014610862578063ef5454d6146108e7578063f25eb5c114610975578063f6d339e414610984575b610000565b3461000057610172600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a1f565b604051808215151515815260200191505060405180910390f35b34610000576101bd600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a81565b005b346100005761022e60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803560001916906020019091905050610ba2565b604051808215151515815260200191505060405180910390f35b346100005761029d600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610dc9565b604051808215151515815260200191505060405180910390f35b6102d1600480803560001916906020019091905050611035565b604051808215151515815260200191505060405180910390f35b346100005761030a60048080356000191690602001909190505061115f565b604051808215151515815260200191505060405180910390f35b346100005761038660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611378565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576103e3600480803590602001909190505061140d565b604051808215151515815260200191505060405180910390f35b346100005761043b60048080356000191690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506114b4565b604051808215151515815260200191505060405180910390f35b34610000576104626115fb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b346100005761050660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611621565b60405180826000191660001916815260200191505060405180910390f35b34610000576105436004808035600019169060200190919050506116b2565b604051808215151515815260200191505060405180910390f35b346100005761056a611715565b604051808215151515815260200191505060405180910390f35b34610000576105f8600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611824565b604051808215151515815260200191505060405180910390f35b3461000057610631600480803560001916906020019091905050611d8b565b604051808215151515815260200191505060405180910390f35b34610000576106ad60048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611dee565b6040518082815260200191505060405180910390f35b34610000576106d0611e83565b6040518082815260200191505060405180910390f35b3461000057610705600480803560001916906020019091905050611e89565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610766600480803560001916906020019091905050611ed2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576107d9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611f1b565b6040518080602001828103825283818151815260200191508051906020019080838360008314610828575b80518252602083111561082857602082019150602081019050602083039250610804565b505050905090810190601f1680156108545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576108cd60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001909190505061200c565b604051808215151515815260200191505060405180910390f35b346100005761095b600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612236565b604051808215151515815260200191505060405180910390f35b3461000057610982612425565b005b3461000057610a0560048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612698565b604051808215151515815260200191505060405180910390f35b60006000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290049050141590505b919050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610add57610b9f565b8073ffffffffffffffffffffffffffffffffffffffff16600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236460405180905060405180910390a380600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5b50565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610c1d57610dc1565b82600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b60208310610c705780518252602082019150602081019050602083039250610c4d565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b60208310610cdf5780518252602082019150602081019050602083039250610cbc565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314610d82575b805182526020831115610d8257602082019150602081019050602083039250610d5e565b505050905090810190601f168015610dae5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836040518082805190602001908083835b60208310610e1b5780518252602082019150602081019050602083039250610df8565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390206000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610ea45761102f565b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610f2d57805160ff1916838001178555610f5b565b82800160010185558215610f5b579182015b82811115610f5a578251825591602001919060010190610f3f565b5b509050610f8091905b80821115610f7c576000816000905550600101610f64565b5090565b50503373ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b60208310610fcd5780518252602082019150602081019050602083039250610faa565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600191505b5b50919050565b600081600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561109b57611159565b6003543410156110aa57611158565b3360016000856000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503373ffffffffffffffffffffffffffffffffffffffff1683600019167f4963513eca575aba66fdcd25f267aae85958fe6fb97e75fa25d783f1a091a22160405180905060405180910390a3600191505b5b5b50919050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156111da57611372565b6002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061127c57506112b3565b601f0160209004906000526020600020908101906112b291905b808211156112ae576000816000905550600101611296565b5090565b5b5060016000846000191660001916815260200190815260200160002060006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550503373ffffffffffffffffffffffffffffffffffffffff1683600019167fef1961b4d2909dc23643b309bfe5c3e5646842d98c3a58517037ef3871185af360405180905060405180910390a3600191505b5b50919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106113cc57805182526020820191506020810190506020830392506113a9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561146b576114af565b816003819055507f6bbc57480a46553fa4d156ce702beef5f3ad66303b0ed1a5d4cb44966c6584c3826040518082815260200191505060405180910390a1600190505b5b919050565b6000823373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561152f576115f4565b8260016000866000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1685600019167f7b97c62130aa09acbbcbf7482630e756592496f1759eaf702f469cf64dfb779460405180905060405180910390a4600191505b5b5092915050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106116755780518252602082019150602081019050602083039250611652565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390205490505b92915050565b6000600060016000846000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561177357611821565b7fdef931299fe61d176f949118058530c1f3f539dcb6950b4e372c9b835c33ca073073ffffffffffffffffffffffffffffffffffffffff16316040518082815260200191505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051809050600060405180830381858888f19350505050151561181b57610000565b600190505b5b90565b60006000836040518082805190602001908083835b6020831061185c5780518252602082019150602081019050602083039250611839565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390203373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561190157611d83565b846040518082805190602001908083835b602083106119355780518252602082019150602081019050602083039250611912565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390209150600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015611ab4575081600019166002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518082805460018160011615610100020316600290048015611aa15780601f10611a7f576101008083540402835291820191611aa1565b820191906000526020600020905b815481529060010190602001808311611a8d575b5050915050604051809103902060001916145b15611c79576002600060016000856000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f10611b5b5750611b92565b601f016020900490600052602060002090810190611b9191905b80821115611b8d576000816000905550600101611b75565b5090565b5b5060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611c1c5780518252602082019150602081019050602083039250611bf9565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a35b8360016000846000191660001916815260200190815260200160002060010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508373ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611d215780518252602082019150602081019050602083039250611cfe565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f728435a0031f6a04538fcdd24922a7e06bc7bc945db03e83d22122d1bc5f28df60405180905060405180910390a3600192505b5b505092915050565b6000600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b60208310611e425780518252602082019150602081019050602083039250611e1f565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b60035481565b600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b600060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b6020604051908101604052806000815250600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611fff5780601f10611fd457610100808354040283529160200191611fff565b820191906000526020600020905b815481529060010190602001808311611fe257829003601f168201915b505050505090505b919050565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156120875761222e565b82600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b602083106120dd57805182526020820191506020810190506020830392506120ba565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b6020831061214c5780518252602082019150602081019050602083039250612129565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea8660405180806020018281038252838181518152602001915080519060200190808383600083146121ef575b8051825260208311156121ef576020820191506020810190506020830392506121cb565b505050905090810190601f16801561221b5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156122945761241f565b82600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061231d57805160ff191683800117855561234b565b8280016001018555821561234b579182015b8281111561234a57825182559160200191906001019061232f565b5b50905061237091905b8082111561236c576000816000905550600101612354565b5090565b50508173ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b602083106123bd578051825260208201915060208101905060208303925061239a565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600190505b5b92915050565b3373ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156124d65780601f106124b45761010080835404028352918201916124d6565b820191906000526020600020905b8154815290600101906020018083116124c2575b505091505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a360016000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156125b05780601f1061258e5761010080835404028352918201916125b0565b820191906000526020600020905b81548152906001019060200180831161259c575b505091505060405180910390206000191660001916815260200190815260200160002060010160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061265d5750612694565b601f01602090049060005260206000209081019061269391905b8082111561268f576000816000905550600101612677565b5090565b5b505b565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515612713576128d0565b8273ffffffffffffffffffffffffffffffffffffffff16600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b6020831061277f578051825260208201915060208101905060208303925061275c565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b602083106127ee57805182526020820191506020810190506020830392506127cb565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314612891575b8051825260208311156128915760208201915060208101905060208303925061286d565b505050905090810190601f1680156128bd5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b5093925050505600a165627a7a7230582066b2da4773a0f1d81efe071c66b51c46868a871661efd18c0f629353ff4c1f9b0029" }, "aaB52d66283F7A1D5978bcFcB55721ACB467384b": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }, - "bb140FbA6242a1c3887A7823F7750a73101383e3": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } + "bb140FbA6242a1c3887A7823F7750a73101383e3": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }, + "cc6A89217193773Bf97DA4c32C23C33e944B12C8": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } } } From 164295fb454e053a97fab98b1063e727f7cc63b6 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 3 Sep 2018 08:56:09 -0300 Subject: [PATCH 079/119] Improve error checks in processSignatureRequests --- src/events/processSignatureRequests.js | 58 ++++++++++++++++++++------ src/utils/errors.js | 2 + 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 9a074ad..253e737 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -6,21 +6,30 @@ const bridgeValidatorsABI = require('../../abis/BridgeValidators.abi') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') -const { AlreadyProcessedError, InvalidValidatorError } = require('../utils/errors') +const { + AlreadyProcessedError, + AlreadySignedError, + InvalidValidatorError +} = require('../utils/errors') const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const { HttpListProviderError } = HttpListProvider -async function estimateGas(web3, bridgeContract, address, method, options) { +async function estimateGas(web3, bridgeContract, signature, message, address) { try { - const gasEstimate = await method.estimateGas(options) + const gasEstimate = await bridgeContract.methods + .submitSignature(signature, message) + .estimateGas({ + from: address + }) return gasEstimate } catch (e) { if (e instanceof HttpListProviderError) { throw e } + // Check if address is validator const validatorContractAddress = await bridgeContract.methods.validatorContract().call() const validatorContract = new web3.eth.Contract(bridgeValidatorsABI, validatorContractAddress) @@ -30,7 +39,26 @@ async function estimateGas(web3, bridgeContract, address, method, options) { throw new InvalidValidatorError(`${address} is not a validator`) } - throw new AlreadyProcessedError(e.message) + // Check if transaction was already signed by this validator + const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) + const alreadySigned = await bridgeContract.methods.messagesSigned(validatorMessageHash).call() + + if (alreadySigned) { + throw new AlreadySignedError(e.message) + } + + // Check if minimum number of validations was already reached + const messageHash = web3.utils.soliditySha3(message) + const numMessagesSigned = await bridgeContract.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await bridgeContract.methods + .isAlreadyProcessed(numMessagesSigned) + .call() + + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) + } + + throw new Error('Unknown error while processing message') } } @@ -74,30 +102,36 @@ function processSignatureRequestsBuilder(config) { gasEstimate = await estimateGas( web3Home, homeBridge, - VALIDATOR_ADDRESS, - homeBridge.methods.submitSignature(signature.signature, message), - { - from: VALIDATOR_ADDRESS - } + signature.signature, + message, + VALIDATOR_ADDRESS ) logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { if (e instanceof HttpListProviderError) { throw new Error( - `RPC Connection Error: submitSignature Gas Estimate cannot be obtained.` + 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' ) } else if (e instanceof InvalidValidatorError) { logger.warn({ address: VALIDATOR_ADDRESS }, 'Invalid validator') throw new Error('Current address does not correspond to a validator') + } else if (e instanceof AlreadySignedError) { + logger.info( + { eventTransactionHash: signatureRequest.transactionHash }, + `Already signed signatureRequest ${signatureRequest.transactionHash}` + ) + return } else if (e instanceof AlreadyProcessedError) { logger.info( { eventTransactionHash: signatureRequest.transactionHash }, - `Already processed signatureRequest ${signatureRequest.transactionHash}` + `signatureRequest ${ + signatureRequest.transactionHash + } was already processed by other validators` ) return } else { logger.error(e, 'Unknown error while processing transaction') - return + throw e } } diff --git a/src/utils/errors.js b/src/utils/errors.js index 24252b1..a9aa54a 100644 --- a/src/utils/errors.js +++ b/src/utils/errors.js @@ -1,7 +1,9 @@ class AlreadyProcessedError extends Error {} +class AlreadySignedError extends Error {} class InvalidValidatorError extends Error {} module.exports = { AlreadyProcessedError, + AlreadySignedError, InvalidValidatorError } From e9d9378979b97a5afb86307413df7ea77edc1fa3 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 3 Sep 2018 12:49:01 -0300 Subject: [PATCH 080/119] Receive validator contract as argument in gasEstimate --- src/events/processSignatureRequests.js | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 253e737..25ec833 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -16,13 +16,11 @@ const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const { HttpListProviderError } = HttpListProvider -async function estimateGas(web3, bridgeContract, signature, message, address) { +async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) { try { - const gasEstimate = await bridgeContract.methods - .submitSignature(signature, message) - .estimateGas({ - from: address - }) + const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({ + from: address + }) return gasEstimate } catch (e) { if (e instanceof HttpListProviderError) { @@ -30,9 +28,6 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { } // Check if address is validator - const validatorContractAddress = await bridgeContract.methods.validatorContract().call() - const validatorContract = new web3.eth.Contract(bridgeValidatorsABI, validatorContractAddress) - const isValidator = await validatorContract.methods.isValidator(address).call() if (!isValidator) { @@ -41,7 +36,7 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { // Check if transaction was already signed by this validator const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) - const alreadySigned = await bridgeContract.methods.messagesSigned(validatorMessageHash).call() + const alreadySigned = await homeBridge.methods.messagesSigned(validatorMessageHash).call() if (alreadySigned) { throw new AlreadySignedError(e.message) @@ -49,10 +44,8 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { // Check if minimum number of validations was already reached const messageHash = web3.utils.soliditySha3(message) - const numMessagesSigned = await bridgeContract.methods.numMessagesSigned(messageHash).call() - const alreadyProcessed = await bridgeContract.methods - .isAlreadyProcessed(numMessagesSigned) - .call() + const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() if (alreadyProcessed) { throw new AlreadyProcessedError(e.message) @@ -65,6 +58,7 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { const limit = promiseLimit(MAX_CONCURRENT_EVENTS) let expectedMessageLength = null +let validatorContract = null function processSignatureRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) @@ -78,6 +72,11 @@ function processSignatureRequestsBuilder(config) { expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() } + if (validatorContract === null) { + const validatorContractAddress = await homeBridge.methods.validatorContract().call() + validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + } + const callbacks = signatureRequests.map(signatureRequest => limit(async () => { const { recipient, value } = signatureRequest.returnValues @@ -99,13 +98,14 @@ function processSignatureRequestsBuilder(config) { let gasEstimate try { - gasEstimate = await estimateGas( - web3Home, + gasEstimate = await estimateGas({ + web3: web3Home, homeBridge, - signature.signature, + validatorContract, + signature: signature.signature, message, - VALIDATOR_ADDRESS - ) + address: VALIDATOR_ADDRESS + }) logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { if (e instanceof HttpListProviderError) { From 23bf5b7c28d57489cccea34dbd657cacd5cfa897 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 4 Sep 2018 08:59:46 -0300 Subject: [PATCH 081/119] Add LICENSE Closes #76. @akolotov I used MIT because is the same that is used in the Token Wizard. Let me know if a different one should be used. --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9570874 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 POA Network + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From e715fd8a88a46d8da0bc9ac2e5d6551adbc5613e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 5 Sep 2018 12:53:00 -0300 Subject: [PATCH 082/119] Split signature requests watcher in two files --- .../processSignatureRequests/estimateGas.js | 47 +++++++++++++++++ .../index.js} | 52 +++---------------- 2 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 src/events/processSignatureRequests/estimateGas.js rename src/events/{processSignatureRequests.js => processSignatureRequests/index.js} (68%) diff --git a/src/events/processSignatureRequests/estimateGas.js b/src/events/processSignatureRequests/estimateGas.js new file mode 100644 index 0000000..d1f85bc --- /dev/null +++ b/src/events/processSignatureRequests/estimateGas.js @@ -0,0 +1,47 @@ +const { HttpListProviderError } = require('http-list-provider') +const { + AlreadyProcessedError, + AlreadySignedError, + InvalidValidatorError +} = require('../../utils/errors') + +async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) { + try { + const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({ + from: address + }) + return gasEstimate + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + // Check if address is validator + const isValidator = await validatorContract.methods.isValidator(address).call() + + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) + } + + // Check if transaction was already signed by this validator + const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) + const alreadySigned = await homeBridge.methods.messagesSigned(validatorMessageHash).call() + + if (alreadySigned) { + throw new AlreadySignedError(e.message) + } + + // Check if minimum number of validations was already reached + const messageHash = web3.utils.soliditySha3(message) + const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() + + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) + } + + throw new Error('Unknown error while processing message') + } +} + +module.exports = estimateGas diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests/index.js similarity index 68% rename from src/events/processSignatureRequests.js rename to src/events/processSignatureRequests/index.js index fa61c34..17a9513 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests/index.js @@ -1,58 +1,20 @@ require('dotenv').config() const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('http-list-provider') -const bridgeValidatorsABI = require('../../abis/BridgeValidators.abi') -const logger = require('../services/logger') -const { web3Home } = require('../services/web3') -const { createMessage } = require('../utils/message') +const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi') +const logger = require('../../services/logger') +const { web3Home } = require('../../services/web3') +const { createMessage } = require('../../utils/message') +const estimateGas = require('./estimateGas') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError -} = require('../utils/errors') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') +} = require('../../utils/errors') +const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env -async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) { - try { - const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({ - from: address - }) - return gasEstimate - } catch (e) { - if (e instanceof HttpListProviderError) { - throw e - } - - // Check if address is validator - const isValidator = await validatorContract.methods.isValidator(address).call() - - if (!isValidator) { - throw new InvalidValidatorError(`${address} is not a validator`) - } - - // Check if transaction was already signed by this validator - const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) - const alreadySigned = await homeBridge.methods.messagesSigned(validatorMessageHash).call() - - if (alreadySigned) { - throw new AlreadySignedError(e.message) - } - - // Check if minimum number of validations was already reached - const messageHash = web3.utils.soliditySha3(message) - const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() - const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() - - if (alreadyProcessed) { - throw new AlreadyProcessedError(e.message) - } - - throw new Error('Unknown error while processing message') - } -} - const limit = promiseLimit(MAX_CONCURRENT_EVENTS) let expectedMessageLength = null From 44405b8d345a8998225ae04c9842d9ee6f962077 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 5 Sep 2018 14:47:35 -0300 Subject: [PATCH 083/119] Change license to GPL3 --- LICENSE | 695 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 674 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index 9570874..94a9ed0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,674 @@ -MIT License - -Copyright (c) 2018 POA Network - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 1f7cca6caebe9a7105081b1bb1d4cd925cdaea84 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 5 Sep 2018 16:49:40 -0300 Subject: [PATCH 084/119] Change order of checks when processSignatureRequests fails --- .../processSignatureRequests/estimateGas.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/events/processSignatureRequests/estimateGas.js b/src/events/processSignatureRequests/estimateGas.js index d1f85bc..666f43f 100644 --- a/src/events/processSignatureRequests/estimateGas.js +++ b/src/events/processSignatureRequests/estimateGas.js @@ -16,11 +16,13 @@ async function estimateGas({ web3, homeBridge, validatorContract, signature, mes throw e } - // Check if address is validator - const isValidator = await validatorContract.methods.isValidator(address).call() + // Check if minimum number of validations was already reached + const messageHash = web3.utils.soliditySha3(message) + const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() - if (!isValidator) { - throw new InvalidValidatorError(`${address} is not a validator`) + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) } // Check if transaction was already signed by this validator @@ -31,13 +33,11 @@ async function estimateGas({ web3, homeBridge, validatorContract, signature, mes throw new AlreadySignedError(e.message) } - // Check if minimum number of validations was already reached - const messageHash = web3.utils.soliditySha3(message) - const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() - const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() + // Check if address is validator + const isValidator = await validatorContract.methods.isValidator(address).call() - if (alreadyProcessed) { - throw new AlreadyProcessedError(e.message) + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) } throw new Error('Unknown error while processing message') From e50cbde9203ea08bf34bcb79f600c217e94ab922 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 6 Sep 2018 15:42:04 -0300 Subject: [PATCH 085/119] Add tests for estimateGas of processSignatureRequests --- test/processSignatureRequests.test.js | 142 ++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 test/processSignatureRequests.test.js diff --git a/test/processSignatureRequests.test.js b/test/processSignatureRequests.test.js new file mode 100644 index 0000000..ae5eee1 --- /dev/null +++ b/test/processSignatureRequests.test.js @@ -0,0 +1,142 @@ +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +const sinon = require('sinon') +const Web3 = require('web3') +const { HttpListProviderError } = require('http-list-provider') +const estimateGas = require('../src/events/processSignatureRequests/estimateGas') +const errors = require('../src/utils/errors') + +chai.use(chaiAsPromised) +const { expect } = chai + +const web3 = new Web3() + +describe.only('processSignatureRequests', () => { + describe('estimateGas', () => { + const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' + + it('should return the gas estimate', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.withArgs(sinon.match({ from: address })).resolves(1000) + estimateGasStub.rejects() + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const gasEstimate = await estimateGas({ web3, homeBridge, address }) + + // then + expect(gasEstimate).to.equal(1000) + }) + + it('should rethrow the error if it was a HttpListProviderError', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new HttpListProviderError('connection error')) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address }) + + // then + await expect(result).to.be.rejectedWith(HttpListProviderError) + }) + + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + }) + + it('should throw an AlreadySignedError if the transaction was not processed but signed by this validator', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + messagesSigned: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadySignedError) + }) + + it('should throw an InvalidValidatorError if the transaction was not processed nor signed, by the validator is invalid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + messagesSigned: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(false) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, validatorContract, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + }) + + it('should throw an Error if the validator is valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + messagesSigned: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, validatorContract, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(Error, 'Unknown error while processing message') + }) + }) +}) From a77fa2ad6141efc86e678671e77e329513e77dca Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 6 Sep 2018 15:59:46 -0300 Subject: [PATCH 086/119] Exit when the current address is not a valid validator --- scripts/start-worker.sh | 2 ++ src/events/processSignatureRequests/index.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/start-worker.sh b/scripts/start-worker.sh index 6d0641b..d0c7dec 100755 --- a/scripts/start-worker.sh +++ b/scripts/start-worker.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -o pipefail + WORKERS_DIR="src/" LOGS_DIR="logs/" diff --git a/src/events/processSignatureRequests/index.js b/src/events/processSignatureRequests/index.js index 17a9513..290a51b 100644 --- a/src/events/processSignatureRequests/index.js +++ b/src/events/processSignatureRequests/index.js @@ -71,8 +71,8 @@ function processSignatureRequestsBuilder(config) { 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' ) } else if (e instanceof InvalidValidatorError) { - logger.warn({ address: VALIDATOR_ADDRESS }, 'Invalid validator') - throw new Error('Current address does not correspond to a validator') + logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + process.exit(10) } else if (e instanceof AlreadySignedError) { logger.info( { eventTransactionHash: signatureRequest.transactionHash }, From ac864522b13290d655628136bcf49e7c8f2f348e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 6 Sep 2018 16:54:28 -0300 Subject: [PATCH 087/119] Remove .only in test --- test/processSignatureRequests.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/processSignatureRequests.test.js b/test/processSignatureRequests.test.js index ae5eee1..6393396 100644 --- a/test/processSignatureRequests.test.js +++ b/test/processSignatureRequests.test.js @@ -11,7 +11,7 @@ const { expect } = chai const web3 = new Web3() -describe.only('processSignatureRequests', () => { +describe('processSignatureRequests', () => { describe('estimateGas', () => { const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' From d2d900a13101592cdea5a9b97cb4d81967514b1b Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 10 Sep 2018 17:24:24 -0300 Subject: [PATCH 088/119] Improve error checks for CollectedSignatures --- .../processCollectedSignatures/estimateGas.js | 51 ++++++++++++++++ .../index.js} | 60 ++++++++++++++----- src/utils/errors.js | 2 + src/utils/message.js | 17 ++++++ 4 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 src/events/processCollectedSignatures/estimateGas.js rename src/events/{processCollectedSignatures.js => processCollectedSignatures/index.js} (55%) diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js new file mode 100644 index 0000000..08fb458 --- /dev/null +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -0,0 +1,51 @@ +const { HttpListProviderError } = require('http-list-provider') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') +const { parseMessage } = require('../../utils/message') + +async function estimateGas({ + foreignBridge, + validatorContract, + message, + numberOfCollectedSignatures, + v, + r, + s, + address +}) { + try { + const gasEstimate = await foreignBridge.methods + .executeSignatures(v, r, s, message) + .estimateGas() + return gasEstimate + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + const requiredSignatures = await foreignBridge.methods.requiredSignatures().call() + if (requiredSignatures.toString() !== numberOfCollectedSignatures.toString()) { + throw new IncompatibleContractError('The number of collected signatures does not match') + } + + const { txHash } = parseMessage(message) + const alreadyProcessed = await foreignBridge.methods.relayedMessages(txHash).call() + if (alreadyProcessed) { + throw new AlreadyProcessedError() + } + + // Check if address is validator + const isValidator = await validatorContract.methods.isValidator(address).call() + + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) + } + + throw new Error('Unknown error while processing message') + } +} + +module.exports = estimateGas diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures/index.js similarity index 55% rename from src/events/processCollectedSignatures.js rename to src/events/processCollectedSignatures/index.js index 96e52ba..9407465 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures/index.js @@ -1,14 +1,24 @@ require('dotenv').config() const promiseLimit = require('promise-limit') -const logger = require('../services/logger') -const { web3Home, web3Foreign } = require('../services/web3') -const { signatureToVRS } = require('../utils/message') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') +const { HttpListProviderError } = require('http-list-provider') +const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi') +const logger = require('../../services/logger') +const { web3Home, web3Foreign } = require('../../services/web3') +const { signatureToVRS } = require('../../utils/message') +const estimateGas = require('./estimateGas') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') +const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS } = process.env const limit = promiseLimit(MAX_CONCURRENT_EVENTS) +let validatorContract = null + function processCollectedSignaturesBuilder(config) { const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) @@ -20,6 +30,11 @@ function processCollectedSignaturesBuilder(config) { return async function processCollectedSignatures(signatures) { const txToSend = [] + if (validatorContract === null) { + const validatorContractAddress = await foreignBridge.methods.validatorContract().call() + validatorContract = new web3Foreign.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + } + const callbacks = signatures.map(colSignature => limit(async () => { const { @@ -52,20 +67,37 @@ function processCollectedSignaturesBuilder(config) { let gasEstimate try { - gasEstimate = await foreignBridge.methods - .executeSignatures(v, r, s, message) - .estimateGas() + gasEstimate = await estimateGas({ + foreignBridge, + validatorContract, + v, + r, + s, + message, + numberOfCollectedSignatures: NumberOfCollectedSignatures, + address: VALIDATOR_ADDRESS + }) } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { + if (e instanceof HttpListProviderError) { throw new Error( - `RPC Connection Error: executeSignatures Gas Estimate cannot be obtained.` + 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' + ) + } else if (e instanceof AlreadyProcessedError) { + logger.info( + { eventTransactionHash: colSignature.transactionHash }, + `Already processed CollectedSignatures ${colSignature.transactionHash}` ) + return + } else if (e instanceof IncompatibleContractError) { + logger.fatal(`The contract is not compatible: ${e.message}`) + process.exit(10) + } else if (e instanceof InvalidValidatorError) { + logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + process.exit(10) + } else { + logger.error(e, 'Unknown error while processing transaction') + throw e } - logger.info( - { eventTransactionHash: colSignature.transactionHash }, - `Already processed CollectedSignatures ${colSignature.transactionHash}` - ) - return } const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() txToSend.push({ diff --git a/src/utils/errors.js b/src/utils/errors.js index a9aa54a..4ad1545 100644 --- a/src/utils/errors.js +++ b/src/utils/errors.js @@ -1,9 +1,11 @@ class AlreadyProcessedError extends Error {} class AlreadySignedError extends Error {} +class IncompatibleContractError extends Error {} class InvalidValidatorError extends Error {} module.exports = { AlreadyProcessedError, AlreadySignedError, + IncompatibleContractError, InvalidValidatorError } diff --git a/src/utils/message.js b/src/utils/message.js index 24be74c..d66d043 100644 --- a/src/utils/message.js +++ b/src/utils/message.js @@ -33,6 +33,22 @@ function createMessage({ return message } +function parseMessage(message) { + message = strip0x(message) + + const recipient = '0x' + message.slice(0, 40) + const amount = '0x' + message.slice(40, 40 + 32 * 2) + const txHash = '0x' + message.slice(40 + 32 * 2, 40 + 32 * 2 + 32 * 2) + const contractAddress = '0x' + message.slice(40 + 32 * 2 + 32 * 2, message.length) + + return { + recipient, + amount, + txHash, + contractAddress + } +} + function signatureToVRS(signature) { assert.equal(signature.length, 2 + 32 * 2 + 32 * 2 + 2) signature = strip0x(signature) @@ -44,5 +60,6 @@ function signatureToVRS(signature) { module.exports = { createMessage, + parseMessage, signatureToVRS } From 34990bfe9b614e8c49ab096beacd1184b0f327ee Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 11 Sep 2018 12:39:56 -0300 Subject: [PATCH 089/119] Add test for parseMessage and refactor --- package-lock.json | 6 ++++ package.json | 1 + .../processCollectedSignatures/index.js | 5 ++- src/utils/message.js | 22 +++++++++--- test/message.test.js | 35 ++++++++++++++++--- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f483de5..a7109b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -429,6 +429,12 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, + "bn-chai": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bn-chai/-/bn-chai-1.0.1.tgz", + "integrity": "sha512-7rJXt21DwYiLLpvzLaACixBBoUGkRV1iuFD3wElEhw8Ji9IiY/QsJRtvW+c7ChRgEOyLQkGaSGFUUqBKm21SNA==", + "dev": true + }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", diff --git a/package.json b/package.json index 6812ca9..904c32c 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "web3-utils": "^1.0.0-beta.34" }, "devDependencies": { + "bn-chai": "^1.0.1", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "concurrently": "^3.6.0", diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index 9407465..cedcd3c 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -32,7 +32,10 @@ function processCollectedSignaturesBuilder(config) { if (validatorContract === null) { const validatorContractAddress = await foreignBridge.methods.validatorContract().call() - validatorContract = new web3Foreign.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + validatorContract = new web3Foreign.eth.Contract( + bridgeValidatorsABI, + validatorContractAddress + ) } const callbacks = signatures.map(colSignature => diff --git a/src/utils/message.js b/src/utils/message.js index d66d043..2b0dbdd 100644 --- a/src/utils/message.js +++ b/src/utils/message.js @@ -36,10 +36,24 @@ function createMessage({ function parseMessage(message) { message = strip0x(message) - const recipient = '0x' + message.slice(0, 40) - const amount = '0x' + message.slice(40, 40 + 32 * 2) - const txHash = '0x' + message.slice(40 + 32 * 2, 40 + 32 * 2 + 32 * 2) - const contractAddress = '0x' + message.slice(40 + 32 * 2 + 32 * 2, message.length) + const recipientStart = 0 + const recipientLength = 40 + const recipient = `0x${message.slice(recipientStart, recipientStart + recipientLength)}` + + const amountStart = recipientStart + recipientLength + const amountLength = 32 * 2 + const amount = `0x${message.slice(amountStart, amountStart + amountLength)}` + + const txHashStart = amountStart + amountLength + const txHashLength = 32 * 2 + const txHash = `0x${message.slice(txHashStart, txHashStart + txHashLength)}` + + const contractAddressStart = txHashStart + txHashLength + const contractAddressLength = 32 * 2 + const contractAddress = `0x${message.slice( + contractAddressStart, + contractAddressStart + contractAddressLength + )}` return { recipient, diff --git a/test/message.test.js b/test/message.test.js index 7640824..1953b81 100644 --- a/test/message.test.js +++ b/test/message.test.js @@ -1,10 +1,11 @@ -const { expect } = require('chai') -const { createMessage, signatureToVRS } = require('../src/utils/message') +const { BN, toBN } = require('web3').utils +const { expect } = require('chai').use(require('bn-chai')(BN)) +const { createMessage, parseMessage, signatureToVRS } = require('../src/utils/message') describe('message utils', () => { - describe('createMessage', () => { - const expectedMessageLength = 104 + const expectedMessageLength = 104 + describe('createMessage', () => { it('should create a message when receiving valid values', () => { // given const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' @@ -226,6 +227,32 @@ describe('message utils', () => { expect(messageThunk).to.throw() }) }) + describe('parseMessage', () => { + it('should return the same values that were used to create the message', () => { + // given + const originalRecipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' + const originalValue = '0x2a' + const originalTransactionHash = + '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const originalBridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' + + // when + const message = createMessage({ + recipient: originalRecipient, + value: originalValue, + transactionHash: originalTransactionHash, + bridgeAddress: originalBridgeAddress, + expectedMessageLength + }) + const { recipient, amount, txHash, contractAddress } = parseMessage(message) + + // then + expect(recipient).to.equal(originalRecipient) + expect(toBN(amount)).to.eq.BN(toBN(originalValue)) + expect(txHash).to.equal(originalTransactionHash) + expect(contractAddress).to.equal(originalBridgeAddress) + }) + }) describe('signatureToVRS', () => { it('should return the v, r, s values', () => { // given From d39c67c202c4ff94d46b087b3ab1fd356e5734bb Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 11 Sep 2018 13:40:06 -0300 Subject: [PATCH 090/119] Add tests for processCollectedSignatures --- test/processCollectedSignatures.test.js | 161 ++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 test/processCollectedSignatures.test.js diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js new file mode 100644 index 0000000..1a4e904 --- /dev/null +++ b/test/processCollectedSignatures.test.js @@ -0,0 +1,161 @@ +const { expect } = require('chai').use(require('chai-as-promised')) +const sinon = require('sinon') +const Web3 = require('web3') +const { HttpListProviderError } = require('http-list-provider') +const { createMessage } = require('../src/utils/message') +const estimateGas = require('../src/events/processCollectedSignatures/estimateGas') +const errors = require('../src/utils/errors') + +const web3 = new Web3() + +const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' + +describe('processCollectedSignatures', () => { + describe('estimateGas', () => { + it('should return the gas estimate', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.resolves(1000) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const gasEstimate = await estimateGas({ web3, foreignBridge }) + + // then + expect(gasEstimate).to.equal(1000) + }) + + it('should rethrow the error if it was a HttpListProviderError', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new HttpListProviderError('connection error')) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const result = estimateGas({ web3, foreignBridge }) + + // then + await expect(result).to.be.rejectedWith(HttpListProviderError) + }) + + it("should throw an IncompatibleContractError if the number of signatures doesn't match", async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(2) }) + } + } + + // when + const result = estimateGas({ web3, foreignBridge, numberOfCollectedSignatures: 1 }) + + // then + await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) + }) + + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), + relayedMessages: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ + web3, + foreignBridge, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + }) + + it('should throw an InvalidValidatorError if the validator is not valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), + relayedMessages: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(false) }) + } + } + + // when + const result = estimateGas({ + web3, + foreignBridge, + validatorContract, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) + + // then + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + }) + + it('should throw an Error if the validator is valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), + relayedMessages: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ + web3, + foreignBridge, + validatorContract, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) + + // then + await expect(result).to.be.rejectedWith(Error, 'Unknown error while processing message') + }) + }) +}) + +function randomMessage() { + return createMessage({ + recipient: address, + value: 42, + transactionHash: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + bridgeAddress: '0x6E4C9178ADc17A0D9b933C0135051536941F5769', + expectedMessageLength: 104 + }) +} From e5bb019d2358947ecc9568d5c61078ccf5e14071 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 11 Sep 2018 14:04:54 -0300 Subject: [PATCH 091/119] Allow running scripts from anywhere --- scripts/sendUserTxToErcForeign.js | 5 ++++- scripts/sendUserTxToErcHome.js | 5 ++++- scripts/sendUserTxToForeign.js | 5 ++++- scripts/sendUserTxToHome.js | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/sendUserTxToErcForeign.js b/scripts/sendUserTxToErcForeign.js index e057fd0..b093b7c 100644 --- a/scripts/sendUserTxToErcForeign.js +++ b/scripts/sendUserTxToErcForeign.js @@ -1,4 +1,7 @@ -require('dotenv').config() +const path = require('path') +require('dotenv').config({ + path: path.join(__dirname, '../.env') +}) const Web3 = require('web3') const Web3Utils = require('web3-utils') const rpcUrlsManager = require('../src/services/getRpcUrlsManager') diff --git a/scripts/sendUserTxToErcHome.js b/scripts/sendUserTxToErcHome.js index 18a729d..1461d99 100644 --- a/scripts/sendUserTxToErcHome.js +++ b/scripts/sendUserTxToErcHome.js @@ -1,4 +1,7 @@ -require('dotenv').config() +const path = require('path') +require('dotenv').config({ + path: path.join(__dirname, '../.env') +}) const Web3 = require('web3') const Web3Utils = require('web3-utils') const rpcUrlsManager = require('../src/services/getRpcUrlsManager') diff --git a/scripts/sendUserTxToForeign.js b/scripts/sendUserTxToForeign.js index 4671c92..2266d90 100644 --- a/scripts/sendUserTxToForeign.js +++ b/scripts/sendUserTxToForeign.js @@ -1,4 +1,7 @@ -require('dotenv').config() +const path = require('path') +require('dotenv').config({ + path: path.join(__dirname, '../.env') +}) const Web3Utils = require('web3-utils') const { web3Foreign } = require('../src/services/web3') const { sendTx, sendRawTx } = require('../src/tx/sendTx') diff --git a/scripts/sendUserTxToHome.js b/scripts/sendUserTxToHome.js index 601c376..cfe5df2 100644 --- a/scripts/sendUserTxToHome.js +++ b/scripts/sendUserTxToHome.js @@ -1,4 +1,7 @@ -require('dotenv').config() +const path = require('path') +require('dotenv').config({ + path: path.join(__dirname, '../.env') +}) const Web3Utils = require('web3-utils') const { web3Home } = require('../src/services/web3') const { sendTx, sendRawTx } = require('../src/tx/sendTx') From 875d1bf66ded35ab417e260ab326e87128bc2e57 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:37:08 -0300 Subject: [PATCH 092/119] Improve error checks for AffirmationRequests --- src/events/processAffirmationRequests.js | 62 ------------ .../processAffirmationRequests/estimateGas.js | 59 +++++++++++ .../processAffirmationRequests/index.js | 98 +++++++++++++++++++ 3 files changed, 157 insertions(+), 62 deletions(-) delete mode 100644 src/events/processAffirmationRequests.js create mode 100644 src/events/processAffirmationRequests/estimateGas.js create mode 100644 src/events/processAffirmationRequests/index.js diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js deleted file mode 100644 index f907bc5..0000000 --- a/src/events/processAffirmationRequests.js +++ /dev/null @@ -1,62 +0,0 @@ -require('dotenv').config() -const logger = require('../services/logger') -const { web3Home } = require('../services/web3') -const promiseLimit = require('promise-limit') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') - -const { VALIDATOR_ADDRESS } = process.env - -const limit = promiseLimit(MAX_CONCURRENT_EVENTS) - -function processAffirmationRequestsBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - - return async function processAffirmationRequests(affirmationRequests) { - const txToSend = [] - - const callbacks = affirmationRequests.map(affirmationRequest => - limit(async () => { - const { recipient, value } = affirmationRequest.returnValues - - logger.info( - { eventTransactionHash: affirmationRequest.transactionHash, sender: recipient, value }, - `Processing affirmationRequest ${affirmationRequest.transactionHash}` - ) - - let gasEstimate - try { - gasEstimate = await homeBridge.methods - .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error( - `RPC Connection Error: executeAffirmation Gas Estimate cannot be obtained.` - ) - } - logger.info( - { eventTransactionHash: affirmationRequest.transactionHash }, - `Already processed affirmationRequest ${affirmationRequest.transactionHash}` - ) - return - } - - const data = await homeBridge.methods - .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: affirmationRequest.transactionHash, - to: config.homeBridgeAddress - }) - }) - ) - - await Promise.all(callbacks) - return txToSend - } -} - -module.exports = processAffirmationRequestsBuilder diff --git a/src/events/processAffirmationRequests/estimateGas.js b/src/events/processAffirmationRequests/estimateGas.js new file mode 100644 index 0000000..d7394af --- /dev/null +++ b/src/events/processAffirmationRequests/estimateGas.js @@ -0,0 +1,59 @@ +const { HttpListProviderError } = require('http-list-provider') +const { + InvalidValidatorError, + AlreadyProcessedError, + AlreadySignedError +} = require('../../utils/errors') + +async function estimateGas({ + web3, + homeBridge, + validatorContract, + recipient, + value, + txHash, + address +}) { + try { + const gasEstimate = await homeBridge.methods + .executeAffirmation(recipient, value, txHash) + .estimateGas({ + from: address + }) + + return gasEstimate + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + const messageHash = web3.utils.soliditySha3(recipient, value, txHash) + const senderHash = web3.utils.soliditySha3(address, messageHash) + + // Check if the message was already signed by this validator + const alreadySigned = await homeBridge.methods.affirmationsSigned(senderHash).call() + + if (alreadySigned) { + throw new AlreadySignedError(e.message) + } + + // Check if minimum number of validations was already reached + const numMessagesSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() + + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) + } + + // Check if address is validator + const isValidator = await validatorContract.methods.isValidator(address).call() + + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) + } + + throw new Error('Unknown error while processing message') + } +} + +module.exports = estimateGas diff --git a/src/events/processAffirmationRequests/index.js b/src/events/processAffirmationRequests/index.js new file mode 100644 index 0000000..6092cc8 --- /dev/null +++ b/src/events/processAffirmationRequests/index.js @@ -0,0 +1,98 @@ +require('dotenv').config() +const logger = require('../../services/logger') +const { web3Home } = require('../../services/web3') +const promiseLimit = require('promise-limit') +const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi') +const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') +const estimateGas = require('./estimateGas') +const { + AlreadyProcessedError, + AlreadySignedError, + InvalidValidatorError +} = require('../../utils/errors') +const { HttpListProviderError } = require('http-list-provider') + +const { VALIDATOR_ADDRESS } = process.env + +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + +let validatorContract = null + +function processAffirmationRequestsBuilder(config) { + const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + + return async function processAffirmationRequests(affirmationRequests) { + const txToSend = [] + + if (validatorContract === null) { + const validatorContractAddress = await homeBridge.methods.validatorContract().call() + validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + } + + const callbacks = affirmationRequests.map(affirmationRequest => + limit(async () => { + const { recipient, value } = affirmationRequest.returnValues + + logger.info( + { eventTransactionHash: affirmationRequest.transactionHash, sender: recipient, value }, + `Processing affirmationRequest ${affirmationRequest.transactionHash}` + ) + + let gasEstimate + try { + gasEstimate = await estimateGas({ + web3: web3Home, + homeBridge, + validatorContract, + recipient, + value, + txHash: affirmationRequest.transactionHash, + address: VALIDATOR_ADDRESS + }) + } catch (e) { + if (e instanceof HttpListProviderError) { + throw new Error( + 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' + ) + } else if (e instanceof InvalidValidatorError) { + logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + process.exit(10) + } else if (e instanceof AlreadySignedError) { + logger.info( + { eventTransactionHash: affirmationRequest.transactionHash }, + `Already signed affirmationRequest ${affirmationRequest.transactionHash}` + ) + return + } else if (e instanceof AlreadyProcessedError) { + logger.info( + { eventTransactionHash: affirmationRequest.transactionHash }, + `affirmationRequest ${ + affirmationRequest.transactionHash + } was already processed by other validators` + ) + return + } else { + logger.error(e, 'Unknown error while processing transaction') + throw e + } + } + + const data = await homeBridge.methods + .executeAffirmation(recipient, value, affirmationRequest.transactionHash) + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: affirmationRequest.transactionHash, + to: config.homeBridgeAddress + }) + }) + ) + + await Promise.all(callbacks) + return txToSend + } +} + +module.exports = processAffirmationRequestsBuilder From 833c37bbc7ff277620c6b85cf4a2a845a98227df Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:47:04 -0300 Subject: [PATCH 093/119] Add test:watch npm script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 904c32c..aa9b978 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "sender:foreign": "./scripts/start-worker.sh sender foreign-sender", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:signature-request' 'npm run watcher:collected-signatures' 'npm run watcher:affirmation-request' 'npm run sender:home' 'npm run sender:foreign'", "test": "NODE_ENV=test mocha", + "test:watch": "NODE_ENV=test mocha --watch --reporter=min", "coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha", "postinstall": "npm install --prefix e2e && mkdir -p logs" }, From c59cd1d3141a93d61693ea5854f7888a5dfda0a2 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:47:16 -0300 Subject: [PATCH 094/119] Add tests for processAffirmationRequests --- test/processAffirmationRequests.test.js | 162 ++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 test/processAffirmationRequests.test.js diff --git a/test/processAffirmationRequests.test.js b/test/processAffirmationRequests.test.js new file mode 100644 index 0000000..092a2f6 --- /dev/null +++ b/test/processAffirmationRequests.test.js @@ -0,0 +1,162 @@ +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +const sinon = require('sinon') +const Web3 = require('web3') +const { HttpListProviderError } = require('http-list-provider') +const estimateGas = require('../src/events/processAffirmationRequests/estimateGas') +const errors = require('../src/utils/errors') + +chai.use(chaiAsPromised) +const { expect } = chai + +const web3 = new Web3() + +describe('processAffirmationRequests', () => { + describe('estimateGas', () => { + const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' + const recipient = '0xCA7693B1c6778f32Db4550D8f7df3C50792D596F' + const value = '0x2A' + const txHash = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' + + it('should return the gas estimate', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.withArgs(sinon.match({ from: address })).resolves(1000) + estimateGasStub.rejects() + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const gasEstimate = await estimateGas({ web3, homeBridge, address }) + + // then + expect(gasEstimate).to.equal(1000) + }) + + it('should rethrow the error if it was a HttpListProviderError', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new HttpListProviderError('connection error')) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address }) + + // then + await expect(result).to.be.rejectedWith(HttpListProviderError) + }) + + it('should throw an AlreadySignedError if the transaction was already signed by this validator', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + // numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + // isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadySignedError) + }) + + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + }) + + it('should throw an InvalidValidatorError if the transaction was not processed nor signed, by the validator is invalid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(false) }) + } + } + + // when + const result = estimateGas({ + web3, + homeBridge, + validatorContract, + address, + recipient, + value, + txHash + }) + + // then + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + }) + + it('should throw an Error if the validator is valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ + web3, + homeBridge, + validatorContract, + address, + recipient, + value, + txHash + }) + + // then + await expect(result).to.be.rejectedWith(Error, 'Unknown error while processing message') + }) + }) +}) From 89e1abf1bfd1306c65234bd18da60072b5b9b882 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:59:59 -0300 Subject: [PATCH 095/119] Check if event is already processed before checking if it's already signed --- .../processAffirmationRequests/estimateGas.js | 18 ++++++++++-------- test/processAffirmationRequests.test.js | 17 ++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/events/processAffirmationRequests/estimateGas.js b/src/events/processAffirmationRequests/estimateGas.js index d7394af..6d8e89c 100644 --- a/src/events/processAffirmationRequests/estimateGas.js +++ b/src/events/processAffirmationRequests/estimateGas.js @@ -30,6 +30,16 @@ async function estimateGas({ const messageHash = web3.utils.soliditySha3(recipient, value, txHash) const senderHash = web3.utils.soliditySha3(address, messageHash) + // Check if minimum number of validations was already reached + const numAffirmationsSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods + .isAlreadyProcessed(numAffirmationsSigned) + .call() + + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) + } + // Check if the message was already signed by this validator const alreadySigned = await homeBridge.methods.affirmationsSigned(senderHash).call() @@ -37,14 +47,6 @@ async function estimateGas({ throw new AlreadySignedError(e.message) } - // Check if minimum number of validations was already reached - const numMessagesSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call() - const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() - - if (alreadyProcessed) { - throw new AlreadyProcessedError(e.message) - } - // Check if address is validator const isValidator = await validatorContract.methods.isValidator(address).call() diff --git a/test/processAffirmationRequests.test.js b/test/processAffirmationRequests.test.js index 092a2f6..deb6681 100644 --- a/test/processAffirmationRequests.test.js +++ b/test/processAffirmationRequests.test.js @@ -53,16 +53,15 @@ describe('processAffirmationRequests', () => { await expect(result).to.be.rejectedWith(HttpListProviderError) }) - it('should throw an AlreadySignedError if the transaction was already signed by this validator', async () => { + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const homeBridge = { methods: { executeAffirmation: () => ({ estimateGas: estimateGasStub }), - // numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), - // isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), - affirmationsSigned: () => ({ call: sinon.stub().resolves(true) }) + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) } } @@ -70,10 +69,10 @@ describe('processAffirmationRequests', () => { const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) // then - await expect(result).to.be.rejectedWith(errors.AlreadySignedError) + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) }) - it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + it('should throw an AlreadySignedError if the transaction was already signed by this validator', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) @@ -81,8 +80,8 @@ describe('processAffirmationRequests', () => { methods: { executeAffirmation: () => ({ estimateGas: estimateGasStub }), numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), - affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), - isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(true) }) } } @@ -90,7 +89,7 @@ describe('processAffirmationRequests', () => { const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) // then - await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + await expect(result).to.be.rejectedWith(errors.AlreadySignedError) }) it('should throw an InvalidValidatorError if the transaction was not processed nor signed, by the validator is invalid', async () => { From c4b2c5aa9a8eb9b4deed9f9491fa01df107a8f6a Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 13 Sep 2018 17:08:30 -0300 Subject: [PATCH 096/119] Improve error checks in processTransfers --- .../processAffirmationRequests/estimateGas.js | 4 +- src/events/processSignatureRequests/index.js | 1 - src/events/processTransfers.js | 48 ---------- src/events/processTransfers/index.js | 91 +++++++++++++++++++ 4 files changed, 93 insertions(+), 51 deletions(-) delete mode 100644 src/events/processTransfers.js create mode 100644 src/events/processTransfers/index.js diff --git a/src/events/processAffirmationRequests/estimateGas.js b/src/events/processAffirmationRequests/estimateGas.js index 6d8e89c..1749ad5 100644 --- a/src/events/processAffirmationRequests/estimateGas.js +++ b/src/events/processAffirmationRequests/estimateGas.js @@ -1,8 +1,8 @@ const { HttpListProviderError } = require('http-list-provider') const { - InvalidValidatorError, AlreadyProcessedError, - AlreadySignedError + AlreadySignedError, + InvalidValidatorError } = require('../../utils/errors') async function estimateGas({ diff --git a/src/events/processSignatureRequests/index.js b/src/events/processSignatureRequests/index.js index 290a51b..93354ca 100644 --- a/src/events/processSignatureRequests/index.js +++ b/src/events/processSignatureRequests/index.js @@ -64,7 +64,6 @@ function processSignatureRequestsBuilder(config) { message, address: VALIDATOR_ADDRESS }) - logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { if (e instanceof HttpListProviderError) { throw new Error( diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js deleted file mode 100644 index e2db251..0000000 --- a/src/events/processTransfers.js +++ /dev/null @@ -1,48 +0,0 @@ -require('dotenv').config() -const { web3Home } = require('../services/web3') -const promiseLimit = require('promise-limit') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') - -const { VALIDATOR_ADDRESS } = process.env - -const limit = promiseLimit(MAX_CONCURRENT_EVENTS) - -function processTransfersBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - - return async function processTransfers(transfers) { - const txToSend = [] - - const callbacks = transfers.map((transfer, index) => - limit(async () => { - const { from, value } = transfer.returnValues - - let gasEstimate - try { - gasEstimate = await homeBridge.methods - .executeAffirmation(from, value, transfer.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - console.log(index + 1, '# already processed Transfer', transfer.transactionHash) - return - } - - const data = await homeBridge.methods - .executeAffirmation(from, value, transfer.transactionHash) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: transfer.transactionHash, - to: config.homeBridgeAddress - }) - }) - ) - - await Promise.all(callbacks) - return txToSend - } -} - -module.exports = processTransfersBuilder diff --git a/src/events/processTransfers/index.js b/src/events/processTransfers/index.js new file mode 100644 index 0000000..9d935b2 --- /dev/null +++ b/src/events/processTransfers/index.js @@ -0,0 +1,91 @@ +require('dotenv').config() +const promiseLimit = require('promise-limit') +const { HttpListProviderError } = require('http-list-provider') +const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi') +const logger = require('../../services/logger') +const { web3Home } = require('../../services/web3') +const { + AlreadyProcessedError, + AlreadySignedError, + InvalidValidatorError +} = require('../../utils/errors') +const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') +const estimateGas = require('../processAffirmationRequests/estimateGas') + +const { VALIDATOR_ADDRESS } = process.env + +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + +let validatorContract = null + +function processTransfersBuilder(config) { + const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + + return async function processTransfers(transfers) { + const txToSend = [] + + if (validatorContract === null) { + const validatorContractAddress = await homeBridge.methods.validatorContract().call() + validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + } + + const callbacks = transfers.map(transfer => + limit(async () => { + const { from, value } = transfer.returnValues + + let gasEstimate + try { + gasEstimate = await estimateGas({ + web3: web3Home, + homeBridge, + validatorContract, + recipient: from, + value, + txHash: transfer.transactionHash, + address: VALIDATOR_ADDRESS + }) + } catch (e) { + if (e instanceof HttpListProviderError) { + throw new Error( + 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' + ) + } else if (e instanceof InvalidValidatorError) { + logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + process.exit(10) + } else if (e instanceof AlreadySignedError) { + logger.info( + { eventTransactionHash: transfer.transactionHash }, + `Already signed transfer ${transfer.transactionHash}` + ) + return + } else if (e instanceof AlreadyProcessedError) { + logger.info( + { eventTransactionHash: transfer.transactionHash }, + `transfer ${transfer.transactionHash} was already processed by other validators` + ) + return + } else { + logger.error(e, 'Unknown error while processing transaction') + throw e + } + } + + const data = await homeBridge.methods + .executeAffirmation(from, value, transfer.transactionHash) + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: transfer.transactionHash, + to: config.homeBridgeAddress + }) + }) + ) + + await Promise.all(callbacks) + return txToSend + } +} + +module.exports = processTransfersBuilder From 08b0cf277846cc647dcda8398ea65c6c315dbaec Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 17 Sep 2018 13:43:09 -0300 Subject: [PATCH 097/119] Update poa-bridge-contracts submodule --- submodules/poa-bridge-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 2609875..4e869c0 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 2609875974d35873de83001aaf76a8fdbbffdf7e +Subproject commit 4e869c0c06dcff4d1d38ca65e9a62e973f5c3e97 From c8b3a6722d8b9e10610eb54fd7fc8808210c86fb Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 18 Sep 2018 16:02:47 -0300 Subject: [PATCH 098/119] Apply pull request comments - Don't check if the transaction was sent from a validator - Don't terminate the process if there is a contract incompatibility - Check that all signatures are valid - Allow having more signatures than needed --- .eslintrc | 1 + .../processCollectedSignatures/estimateGas.js | 37 +++++++------ .../processCollectedSignatures/index.js | 19 +++---- test/processCollectedSignatures.test.js | 54 +++++++++++++------ 4 files changed, 68 insertions(+), 43 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4a3330d..3614120 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,6 +12,7 @@ "no-console": "off", "no-param-reassign": "off", "no-plusplus": "off", + "no-restricted-syntax": "off", "no-shadow": "off", "no-use-before-define": ["error", { "functions": false }], "import/no-dynamic-require": "off" diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js index 08fb458..fc5047a 100644 --- a/src/events/processCollectedSignatures/estimateGas.js +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -1,20 +1,20 @@ +const Web3 = require('web3') const { HttpListProviderError } = require('http-list-provider') -const { - AlreadyProcessedError, - IncompatibleContractError, - InvalidValidatorError -} = require('../../utils/errors') +const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') const { parseMessage } = require('../../utils/message') +const web3 = new Web3() +const { toBN } = Web3.utils + async function estimateGas({ foreignBridge, validatorContract, message, numberOfCollectedSignatures, + signatures, v, r, - s, - address + s }) { try { const gasEstimate = await foreignBridge.methods @@ -26,22 +26,27 @@ async function estimateGas({ throw e } - const requiredSignatures = await foreignBridge.methods.requiredSignatures().call() - if (requiredSignatures.toString() !== numberOfCollectedSignatures.toString()) { - throw new IncompatibleContractError('The number of collected signatures does not match') - } - + // check if the message was already processed const { txHash } = parseMessage(message) const alreadyProcessed = await foreignBridge.methods.relayedMessages(txHash).call() if (alreadyProcessed) { throw new AlreadyProcessedError() } - // Check if address is validator - const isValidator = await validatorContract.methods.isValidator(address).call() + // check if the number of signatures is enough + const requiredSignatures = await validatorContract.methods.requiredSignatures().call() + if (toBN(requiredSignatures).gt(toBN(numberOfCollectedSignatures))) { + throw new IncompatibleContractError('The number of collected signatures does not match') + } + + // check if all the signatures were made by validators + for (const signature of signatures) { + const address = web3.eth.accounts.recover(message, signature) + const isValidator = await validatorContract.methods.isValidator(address).call() - if (!isValidator) { - throw new InvalidValidatorError(`${address} is not a validator`) + if (!isValidator) { + throw new IncompatibleContractError(`Message signed by ${address} that is not a validator`) + } } throw new Error('Unknown error while processing message') diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index cedcd3c..e24c044 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -6,11 +6,7 @@ const logger = require('../../services/logger') const { web3Home, web3Foreign } = require('../../services/web3') const { signatureToVRS } = require('../../utils/message') const estimateGas = require('./estimateGas') -const { - AlreadyProcessedError, - IncompatibleContractError, - InvalidValidatorError -} = require('../../utils/errors') +const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS } = process.env @@ -57,10 +53,12 @@ function processCollectedSignaturesBuilder(config) { requiredSignatures.length = NumberOfCollectedSignatures requiredSignatures.fill(0) + const signatures = [] const [v, r, s] = [[], [], []] const signaturePromises = requiredSignatures.map(async (el, index) => { const signature = await homeBridge.methods.signature(messageHash, index).call() const recover = signatureToVRS(signature) + signatures.push(signature) v.push(recover.v) r.push(recover.r) s.push(recover.s) @@ -73,12 +71,12 @@ function processCollectedSignaturesBuilder(config) { gasEstimate = await estimateGas({ foreignBridge, validatorContract, + signatures, v, r, s, message, - numberOfCollectedSignatures: NumberOfCollectedSignatures, - address: VALIDATOR_ADDRESS + numberOfCollectedSignatures: NumberOfCollectedSignatures }) } catch (e) { if (e instanceof HttpListProviderError) { @@ -92,11 +90,8 @@ function processCollectedSignaturesBuilder(config) { ) return } else if (e instanceof IncompatibleContractError) { - logger.fatal(`The contract is not compatible: ${e.message}`) - process.exit(10) - } else if (e instanceof InvalidValidatorError) { - logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') - process.exit(10) + logger.error(`The contract is not compatible: ${e.message}`) + return } else { logger.error(e, 'Unknown error while processing transaction') throw e diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js index 1a4e904..ed1cae7 100644 --- a/test/processCollectedSignatures.test.js +++ b/test/processCollectedSignatures.test.js @@ -46,33 +46,42 @@ describe('processCollectedSignatures', () => { await expect(result).to.be.rejectedWith(HttpListProviderError) }) - it("should throw an IncompatibleContractError if the number of signatures doesn't match", async () => { + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(2) }) + relayedMessages: () => ({ call: sinon.stub().resolves(true) }) } } // when - const result = estimateGas({ web3, foreignBridge, numberOfCollectedSignatures: 1 }) + const result = estimateGas({ + web3, + foreignBridge, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) // then - await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) }) - it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + it('should throw an IncompatibleContractError if the number of signatures is less than required', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), - relayedMessages: () => ({ call: sinon.stub().resolves(true) }) + relayedMessages: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + requiredSignatures: () => ({ call: sinon.stub().resolves(2) }) } } @@ -80,68 +89,83 @@ describe('processCollectedSignatures', () => { const result = estimateGas({ web3, foreignBridge, + validatorContract, numberOfCollectedSignatures: 1, message: randomMessage() }) // then - await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) }) - it('should throw an InvalidValidatorError if the validator is not valid', async () => { + it('should throw an IncompatibleContractError if the signature is invalid', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), relayedMessages: () => ({ call: sinon.stub().resolves(false) }) } } const validatorContract = { methods: { + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), isValidator: () => ({ call: sinon.stub().resolves(false) }) } } + const message = randomMessage() + const { signature } = web3.eth.accounts.sign( + message, + '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' + ) + // when const result = estimateGas({ web3, foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - message: randomMessage() + signatures: [signature], + message }) // then - await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) }) - it('should throw an Error if the validator is valid', async () => { + it('should throw an Error if the signature is valid', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), relayedMessages: () => ({ call: sinon.stub().resolves(false) }) } } const validatorContract = { methods: { + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), isValidator: () => ({ call: sinon.stub().resolves(true) }) } } + const message = randomMessage() + const { signature } = web3.eth.accounts.sign( + message, + '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' + ) + // when const result = estimateGas({ web3, foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - message: randomMessage() + signatures: [signature], + message }) // then From b8098c844ad8ca9476465c7c3c51400ce9e400f7 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 19 Sep 2018 12:41:16 -0300 Subject: [PATCH 099/119] Change how errors are handled in collected signatures watcher --- .../processCollectedSignatures/estimateGas.js | 8 ++++++-- src/events/processCollectedSignatures/index.js | 16 +++++++++++++--- test/processCollectedSignatures.test.js | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js index fc5047a..dadb421 100644 --- a/src/events/processCollectedSignatures/estimateGas.js +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -1,6 +1,10 @@ const Web3 = require('web3') const { HttpListProviderError } = require('http-list-provider') -const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') const { parseMessage } = require('../../utils/message') const web3 = new Web3() @@ -45,7 +49,7 @@ async function estimateGas({ const isValidator = await validatorContract.methods.isValidator(address).call() if (!isValidator) { - throw new IncompatibleContractError(`Message signed by ${address} that is not a validator`) + throw new InvalidValidatorError(`Message signed by ${address} that is not a validator`) } } diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index e24c044..1f2c717 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -6,7 +6,11 @@ const logger = require('../../services/logger') const { web3Home, web3Foreign } = require('../../services/web3') const { signatureToVRS } = require('../../utils/message') const estimateGas = require('./estimateGas') -const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS } = process.env @@ -89,8 +93,14 @@ function processCollectedSignaturesBuilder(config) { `Already processed CollectedSignatures ${colSignature.transactionHash}` ) return - } else if (e instanceof IncompatibleContractError) { - logger.error(`The contract is not compatible: ${e.message}`) + } else if ( + e instanceof IncompatibleContractError || + e instanceof InvalidValidatorError + ) { + logger.error( + { eventTransactionHash: colSignature.transactionHash }, + `The message couldn't be processed; skipping: ${e.message}` + ) return } else { logger.error(e, 'Unknown error while processing transaction') diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js index ed1cae7..b91124b 100644 --- a/test/processCollectedSignatures.test.js +++ b/test/processCollectedSignatures.test.js @@ -132,7 +132,7 @@ describe('processCollectedSignatures', () => { }) // then - await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) }) it('should throw an Error if the signature is valid', async () => { From 481d556aed15ad7c94470613ea12c8b7b4155537 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 19 Sep 2018 12:51:12 -0300 Subject: [PATCH 100/119] Use v, r, s instead of signature for recovering address --- src/events/processCollectedSignatures/estimateGas.js | 5 ++--- src/events/processCollectedSignatures/index.js | 3 --- test/processCollectedSignatures.test.js | 12 ++++++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js index dadb421..cd49783 100644 --- a/src/events/processCollectedSignatures/estimateGas.js +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -15,7 +15,6 @@ async function estimateGas({ validatorContract, message, numberOfCollectedSignatures, - signatures, v, r, s @@ -44,8 +43,8 @@ async function estimateGas({ } // check if all the signatures were made by validators - for (const signature of signatures) { - const address = web3.eth.accounts.recover(message, signature) + for (let i = 0; i < v.length; i++) { + const address = web3.eth.accounts.recover(message, web3.utils.toHex(v[i]), r[i], s[i]) const isValidator = await validatorContract.methods.isValidator(address).call() if (!isValidator) { diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index 1f2c717..9d3e318 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -57,12 +57,10 @@ function processCollectedSignaturesBuilder(config) { requiredSignatures.length = NumberOfCollectedSignatures requiredSignatures.fill(0) - const signatures = [] const [v, r, s] = [[], [], []] const signaturePromises = requiredSignatures.map(async (el, index) => { const signature = await homeBridge.methods.signature(messageHash, index).call() const recover = signatureToVRS(signature) - signatures.push(signature) v.push(recover.v) r.push(recover.r) s.push(recover.s) @@ -75,7 +73,6 @@ function processCollectedSignaturesBuilder(config) { gasEstimate = await estimateGas({ foreignBridge, validatorContract, - signatures, v, r, s, diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js index b91124b..a319c0d 100644 --- a/test/processCollectedSignatures.test.js +++ b/test/processCollectedSignatures.test.js @@ -116,7 +116,7 @@ describe('processCollectedSignatures', () => { } const message = randomMessage() - const { signature } = web3.eth.accounts.sign( + const { v, r, s } = web3.eth.accounts.sign( message, '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' ) @@ -127,7 +127,9 @@ describe('processCollectedSignatures', () => { foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - signatures: [signature], + v: [v], + r: [r], + s: [s], message }) @@ -153,7 +155,7 @@ describe('processCollectedSignatures', () => { } const message = randomMessage() - const { signature } = web3.eth.accounts.sign( + const { v, r, s } = web3.eth.accounts.sign( message, '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' ) @@ -164,7 +166,9 @@ describe('processCollectedSignatures', () => { foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - signatures: [signature], + v: [v], + r: [r], + s: [s], message }) From cfb6f4534848dac638cbf69300ae2b46553bec1f Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Thu, 27 Sep 2018 15:16:19 +0300 Subject: [PATCH 101/119] Update docker-compose.yml. Added persistent storage --- docker-compose.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index fd636f5..67e3c2c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,12 +2,26 @@ version: "3" services: redis: image: "redis" + command: ["redis-server", "--appendonly", "yes"] + hostname: redis + volumes: + - redis-data:/data rabbit: image: "rabbitmq:3-management" + environment: + - RABBITMQ_NODENAME=node@rabbitmq + volumes: + - rabbit-data:/data/mnesia bridge: build: . environment: - NODE_ENV=production - QUEUE_URL=amqp://rabbit - REDIS_URL=redis://redis + volumes: + - ./env:./env command: "true" + +volumes: + rabbit-data: + redis-data: \ No newline at end of file From 9d2eaaf4a30d77da32e213b79203f99752c55993 Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Thu, 27 Sep 2018 15:42:20 +0300 Subject: [PATCH 102/119] Update env path --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 67e3c2c..7bfb35a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: - QUEUE_URL=amqp://rabbit - REDIS_URL=redis://redis volumes: - - ./env:./env + - ${PWD}/.env:${PWD}/.env command: "true" volumes: From 8649c1bb940980819b9d8dc2e89ecdce254ba3d8 Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Thu, 27 Sep 2018 16:02:22 +0300 Subject: [PATCH 103/119] Change .env file directive --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7bfb35a..11344f3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,8 +18,7 @@ services: - NODE_ENV=production - QUEUE_URL=amqp://rabbit - REDIS_URL=redis://redis - volumes: - - ${PWD}/.env:${PWD}/.env + env_file: ./.env command: "true" volumes: From 02283db1caa259edbd13406e6c1b54e908578547 Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Thu, 27 Sep 2018 17:36:40 +0300 Subject: [PATCH 104/119] update pathes --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 11344f3..f80639b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,13 +5,13 @@ services: command: ["redis-server", "--appendonly", "yes"] hostname: redis volumes: - - redis-data:/data + - ~/bridge_data:/data rabbit: image: "rabbitmq:3-management" environment: - RABBITMQ_NODENAME=node@rabbitmq volumes: - - rabbit-data:/data/mnesia + - ~/bridge_data:/data/mnesia bridge: build: . environment: From 38fc8c29d07901d3c438ef66eb5268b342fe6a8e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 27 Sep 2018 13:37:30 -0300 Subject: [PATCH 105/119] Fetch gas price after setting the interval --- src/services/gasPrice.js | 3 ++- src/utils/utils.js | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/services/gasPrice.js b/src/services/gasPrice.js index 4768122..2c70aa9 100644 --- a/src/services/gasPrice.js +++ b/src/services/gasPrice.js @@ -7,6 +7,7 @@ const ForeignNativeABI = require('../../abis/ForeignBridgeNativeToErc.abi') const HomeErcABI = require('../../abis/HomeBridgeErcToErc.abi') const ForeignErcABI = require('../../abis/ForeignBridgeErcToErc.abi') const logger = require('../services/logger') +const { setIntervalAndRun } = require('../utils/utils') const HomeABI = isErcToErc ? HomeErcABI : HomeNativeABI const ForeignABI = isErcToErc ? ForeignNativeABI : ForeignErcABI @@ -83,7 +84,7 @@ async function start(chainId) { throw new Error(`Unrecognized chainId '${chainId}'`) } - fetchGasPriceInterval = setInterval(async () => { + fetchGasPriceInterval = setIntervalAndRun(async () => { const gasPrice = await fetchGasPrice({ bridgeContract, oracleFn: () => fetchGasPriceFromOracle(oracleUrl, speedType) diff --git a/src/utils/utils.js b/src/utils/utils.js index 40dbff2..f2a66e5 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -46,9 +46,16 @@ function addExtraGas(gas, extraPercentage) { return BigNumber(gasWithExtra) } +function setIntervalAndRun(f, interval) { + const handler = setInterval(f, interval) + f() + return handler +} + module.exports = { syncForEach, checkHTTPS, waitForFunds, - addExtraGas + addExtraGas, + setIntervalAndRun } From 1a199683aed2737fd7f1ef0d24d5fc69a2f4777c Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 28 Sep 2018 10:39:38 -0300 Subject: [PATCH 106/119] Use stable docker image for parity --- e2e/parity/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/parity/Dockerfile b/e2e/parity/Dockerfile index 9150d6a..81113df 100644 --- a/e2e/parity/Dockerfile +++ b/e2e/parity/Dockerfile @@ -1,4 +1,4 @@ -FROM parity/parity +FROM parity/parity:stable WORKDIR /stuff From dcffa8b84983ec6a26d06e34f6fe6faca0859fe6 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 28 Sep 2018 10:43:58 -0300 Subject: [PATCH 107/119] Use explicit tag for redis docker image --- e2e/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index 1e7d8ab..e447ea4 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -9,7 +9,7 @@ services: ports: - "8542:8545" redis: - image: "redis" + image: "redis:4" rabbit: image: "rabbitmq:3-management" ports: From 47636c96cb95ef835af33f39bf81a16c65ca6268 Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Mon, 1 Oct 2018 13:59:45 +0300 Subject: [PATCH 108/119] Added persistence to rabbit and redis containers --- docker-compose.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f80639b..a3e3392 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,22 +5,19 @@ services: command: ["redis-server", "--appendonly", "yes"] hostname: redis volumes: - - ~/bridge_data:/data + - ~/bridge_data/redis:/data rabbit: image: "rabbitmq:3-management" + hostname: rabbit environment: - - RABBITMQ_NODENAME=node@rabbitmq + - RABBITMQ_NODENAME=node@rabbit volumes: - - ~/bridge_data:/data/mnesia + - ~/bridge_data/rabbitmq:/var/lib/rabbitmq bridge: build: . + env_file: ./.env environment: - NODE_ENV=production - QUEUE_URL=amqp://rabbit - REDIS_URL=redis://redis - env_file: ./.env - command: "true" - -volumes: - rabbit-data: - redis-data: \ No newline at end of file + command: "true" \ No newline at end of file From efadd478f04028f629265f2d5719e1817595ecfc Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Mon, 1 Oct 2018 14:23:01 +0300 Subject: [PATCH 109/119] Added "restart:on-failure" policy to docker-compose --- docker-compose.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a3e3392..52a63af 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ services: image: "redis" command: ["redis-server", "--appendonly", "yes"] hostname: redis + restart: on-failure volumes: - ~/bridge_data/redis:/data rabbit: @@ -13,6 +14,7 @@ services: - RABBITMQ_NODENAME=node@rabbit volumes: - ~/bridge_data/rabbitmq:/var/lib/rabbitmq + restart: on-failure bridge: build: . env_file: ./.env @@ -20,4 +22,5 @@ services: - NODE_ENV=production - QUEUE_URL=amqp://rabbit - REDIS_URL=redis://redis - command: "true" \ No newline at end of file + command: "true" + restart: on-failure \ No newline at end of file From 83cfa57116258efad8f9faeef7593cd55ebcc7de Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Mon, 1 Oct 2018 16:28:15 -0600 Subject: [PATCH 110/119] README changes, added CONTRIBUTING and CODE_OF_CONDUCT --- CODE_OF_CONDUCT.md | 73 +++++++++++++++++++++++++++ CONTRIBUTING.md | 28 +++++++++++ README.md | 120 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 196 insertions(+), 25 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ccafccc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ziggy@poa.network. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ca8dcc5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +## Contributing + +Thank your for contributing to this project! We welcome collaborators and expect users to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments. + +1. Fork the repo ( https://github.com/poanetwork/bridge-nodejs/fork ). +2. Create your feature branch (`git checkout -b my-new-feature`). +3. Write tests that cover your work. +4. Commit your changes (`git commit -am 'Add some feature'`). +5. Push to your branch (`git push origin my-new-feature`). +6. Create a new PR (Pull Request). + +### General + +* Commits should be one logical change that still allows all tests to pass. We prefer smaller commits if there could be two levels of logic grouping. The goal is to provide future contributors (including your future self) the reasoning behind your changes and allow them to cherry-pick, patch or port those changes in isolation to other branches or forks. +* If during your PR you reveal a pre-existing bug and know how to fix it: + 1. If you can isolate the bug, fix it in a separate PR. + 2. If the fix depends on your other commits, add it in a separate commit to the same PR. + + In either case, try to write a regression test that fails because of the bug but passes with your fix. + +### Issues +Creating and discussing [Issues](https://github.com/poanetwork/bridge-nodejs/issues) provides significant value to the project. If you find a bug you can report it in an Issue. + +### Pull Requests +All pull requests should include: +* A clear, readable description of the purpose of the PR +* A clear, readable description of changes +* Any additional concerns or comments (optional) \ No newline at end of file diff --git a/README.md b/README.md index 0bdc7de..a05417e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,41 @@ -# Architecture + +# POA Bridge - NodeJS Oracle + +[![Build Status](https://travis-ci.org/poanetwork/bridge-nodejs.svg?branch=develop)](https://travis-ci.org/poanetwork/bridge-nodejs) +[![Gitter](https://badges.gitter.im/poanetwork/poa-bridge.svg)](https://gitter.im/poanetwork/poa-bridge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Bridge Overview + +The POA Bridge allows users to transfer assets between two chains in the Ethereum ecosystem. It is composed of several elements which are located in different POA Network repositories: + +**Bridge Elements** +1. An oracle written in NodeJS, contained in this repository. +2. [Solidity smart contracts](https://github.com/poanetwork/poa-bridge-contracts). Used to manage bridge validators, collect signatures, and confirm asset relay and disposal. +3. [Bridge UI Application](https://github.com/poanetwork/bridge-ui). A DApp interface to transfer tokens and coins between chains. +4. [Bridge Monitor](https://github.com/poanetwork/bridge-monitor). A tool for checking balances and unprocessed events in bridged networks. + +The bridge oracle is deployed on validator nodes in the network (nodes are specified in the smart contracts). The oracle connects to two chains via a Remote Procedure Call (RPC). It is responsible for: +- listening to events related to bridge contracts +- sending transactions to authorize token transfers + +Following is an overview of the NodeJS bridge oracle and [instructions for getting started](#how-to-use) with the POA Bridge. + +## Interoperability + +Interoperability is the ability to share resources between networks. The POA Bridge is an interoperability protocol where users can transfer value (ERC20 compatible tokens and network coins) between chains in the Ethereum ecosystem. This creates opportunities to use different chains for different purposes. For example, smart contracts can allocate resource intensive operations to a sidechain where transactions are fast and inexpensive. + +## Operational Modes + +The POA bridge currently provides two operational modes, with a 3rd mode in development. In this context, **Home** - or Native - refers to a sidechain or private chain network, and **Foreign** generally refers to the Ethereum mainnet. + +**Note:** _In ERC20-to-ERC20 mode Foreign can refer to another sidechain, so that two sidechains can bridge with one another._ + +- [x] `Native-to-ERC20` **Coins** on a Home network can be converted to ERC20-compatible **tokens** on a Foreign network. Coins are locked on the Home side and the corresponding amount of ERC20 tokens are minted on the Foreign side. When the operation is reversed, tokens are burnt on the Foreign side and unlocked in the Home network. +- [x] `ERC20-to-ERC20` ERC20-compatible tokens on the Foreign network are locked and minted as ERC20-compatible tokens (ERC677 tokens) on the Home network. When transferred from Home to Foreign, they are burnt on the Home side and unlocked in the Foreign network. This can be considered a form of atomic swap when a user swaps the token "X" in network "A" to the token "Y" in network "B". +- [ ] `ERC20-to-Native`: Currently in development. Pre-existing tokens in the Foreign network are locked and coins are minted in the `Home` network. + + +## Architecture ![bridge architecture](https://user-images.githubusercontent.com/4614574/42094368-f260f648-7b85-11e8-91d4-e602253a6560.png) @@ -6,53 +43,66 @@ A watcher listens for a certain event and creates proper jobs in the queue. These jobs contain the transaction data (without the nonce) and the transaction hash for the related event. The watcher runs on a given frequency, keeping track of the last processed block. There are three Watchers: -- Signature Request Watcher: Listens to `UserRequestForSignature` events on Home network. -- Collected Signatures Watcher: Listens to `CollectedSignatures` events on Home network. -- Affirmation Request Watcher: Listens to `UserRequestForAffirmation` events on Foreign network. +- **Signature Request Watcher**: Listens to `UserRequestForSignature` events on Home network. +- **Collected Signatures Watcher**: Listens to `CollectedSignatures` events on Home network. +- **Affirmation Request Watcher**: Listens to `UserRequestForAffirmation` events on Foreign network. ### Sender -A sender subscribes to the queue and keeps track of the nonce. It takes jobs from the queue, extract transaction data, adds proper nonce and sends it to the network. +A sender subscribes to the queue and keeps track of the nonce. It takes jobs from the queue, extracts transaction data, adds the proper nonce, and sends it to the network. There are two Senders: -- Home Sender: Sends transaction to Home network. -- Foreign Sender: Sends transaction to Foreign network. +- **Home Sender**: Sends transaction to the `Home` network. +- **Foreign Sender**: Sends transaction to the `Foreign` network. -# How to use +# How to Use -1. Deploy bridge contracts +## Installation and Deployment + +### Requirements + +- [Truffle](https://github.com/trufflesuite/truffle) version `4.0` +- [RabbitMQ](https://www.rabbitmq.com/) version: `3.7` +- [Redis](https://redis.io/) version: `4.0` + +For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues/90) + +### Deploy Bridge Contracts + +1. Deploy the bridge contracts 1. Clone repo: `git clone https://github.com/poanetwork/poa-bridge-contracts` 2. `cd poa-bridge-contracts` - 3. Checkout branch `v2` : `git checkout v2` + 3. Checkout branch `refactor_v1` : `git checkout refactor_v1` 4. Compile contracts: `truffle compile` 5. Go to deploy folder: `cd deploy` 6. create a `.env` file: `cp .env.example .env` (look at `.env.example` to see the variables that need to be present) 7. Execute `node deploy.js` -2. Install [RabbitMQ](https://www.rabbitmq.com/) and [Redis](https://redis.io/) - - RabbitMQ version: `3.7` - - Redis version: `4.0` - -3. Create a `.env` file: `cp .env.example .env` and fill in the information using the output data from previous deploy step. Check the `.env.example` file to see the required variables. +2. Start Redis and RabbitMQ in your local environment. + 1. `redis-server` starts Redis. `redis-cli ping` will return a `pong` if Redis is running. + 2. `rabbitmq-server` starts RabbitMQ. Use `rabbitmqctl status` to check if RabbitMQ is running. + +3. Create a `.env` file: `cp .env.example .env` + 1. fill in the required information using the output data from the bridge contract deployment. Check the `.env.example` file to see the required variables. -## Run the processes +## Run the Processes -### Native to Erc mode +### Native to ERC mode - `npm run watcher:signature-request` - `npm run watcher:collected-signatures` - `npm run watcher:affirmation-request` - `npm run sender:home` - `npm run sender:foreign` -To send deposits to home contract run `node tests/sendUserTxToHome.js` +To send deposits to a home contract run `node scripts/sendUserTxToHome.js` -To send withdrawals to foreign contract run `node tests/sendUserTxToForeign.js` +To send withdrawals to a foreign contract run `node scripts/sendUserTxToForeign.js` -Make sure your `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is same as in your .env deployment contract +Make sure your `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is the same as in your .env deployment contract. -### Erc to Erc mode +### ERC to ERC Mode -On `.env` file set `BRIDGE_MODE=ERC_TO_ERC` +In `.env` file set `BRIDGE_MODE=ERC_TO_ERC` - `npm run watcher:signature-request` - `npm run watcher:collected-signatures` @@ -60,9 +110,9 @@ On `.env` file set `BRIDGE_MODE=ERC_TO_ERC` - `npm run sender:home` - `npm run sender:foreign` -To deposit from Foreign to Home contract run `node scripts/sendUserTxToErcForeign.js 10` where `10` is how many tx you would like to send out +To deposit from Foreign to Home contract run `node scripts/sendUserTxToErcForeign.js 10` where `10` is how many tx you would like to send out. -To withdrawal to Home to Foreign contract run `node scripts/sendUserTxToErcHome.js 10` where `10` is how many tx you would like to send out +To withdrawal to Home to Foreign contract run `node scripts/sendUserTxToErcHome.js 10` where `10` is how many tx you would like to send out. ### Run with Docker @@ -77,7 +127,7 @@ To withdrawal to Home to Foreign contract run `node scripts/sendUserTxToErcHome. To use the bridge UI, clone [the repo](https://github.com/poanetwork/bridge-ui/), create a `.env` using the same values as before, and run `npm start`. -### Useful commands for development +### Useful Commands for Development #### RabbitMQ Command | Description @@ -105,3 +155,23 @@ Command | Description Variable | Description | Values --- | --- | --- `ALLOW_HTTP` | Explicitly allows the usage of `http` connections instead of `https`. | `yes` / `no` + +## Testing + +```bash +npm test +``` + +## Contributing + +See the [CONTRIBUTING](CONTRIBUTING.md) document for contribution, testing and pull request protocol. + +## License + +[![License: LGPL v3.0](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) + +This project is licensed under the GNU Lesser General Public License v3.0. See the [LICENSE](LICENSE) file for details. + +## References + +* [POA Bridge FAQ](https://poanet.zendesk.com/hc/en-us/categories/360000349273-POA-Bridge) From 258b0678278881f35dd3a8fb212830d89d74dcfc Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Tue, 2 Oct 2018 12:10:57 +0300 Subject: [PATCH 111/119] Explicitly specify redis version --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 52a63af..2f9d0d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3" services: redis: - image: "redis" + image: "redis:4" command: ["redis-server", "--appendonly", "yes"] hostname: redis restart: on-failure From 58beed0535fd188db0bac45dd954de0268e2a574 Mon Sep 17 00:00:00 2001 From: ArseniiPetrovich Date: Tue, 2 Oct 2018 12:49:50 +0300 Subject: [PATCH 112/119] Fix edge permission error case --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2f9d0d4..b452a72 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: environment: - RABBITMQ_NODENAME=node@rabbit volumes: - - ~/bridge_data/rabbitmq:/var/lib/rabbitmq + - ~/bridge_data/rabbitmq:/var/lib/rabbitmq/mnesia restart: on-failure bridge: build: . From 35f5966129cfb42a5c76b2db9f4127912dc8b0cf Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 3 Oct 2018 10:04:11 -0300 Subject: [PATCH 113/119] Update checkHTTPS warn message --- src/sender.js | 4 ++-- src/utils/utils.js | 16 ++++++++++------ src/watcher.js | 4 ++-- test/utils.test.js | 8 ++++---- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/sender.js b/src/sender.js index 4e7afc9..55d15ed 100644 --- a/src/sender.js +++ b/src/sender.js @@ -28,8 +28,8 @@ async function initialize() { try { const checkHttps = checkHTTPS(process.env.ALLOW_HTTP) - rpcUrlsManager.homeUrls.forEach(checkHttps) - rpcUrlsManager.foreignUrls.forEach(checkHttps) + rpcUrlsManager.homeUrls.forEach(checkHttps('home')) + rpcUrlsManager.foreignUrls.forEach(checkHttps('foreign')) GasPrice.start(config.id) diff --git a/src/utils/utils.js b/src/utils/utils.js index f2a66e5..6834f75 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -9,12 +9,16 @@ async function syncForEach(array, callback) { } function checkHTTPS(ALLOW_HTTP) { - return function(url) { - if (!/^https.*/.test(url)) { - if (ALLOW_HTTP !== 'yes') { - throw new Error(`http is not allowed: ${url}`) - } else { - logger.warn(`You are using http (${url}). In production https must be used instead.`) + return function(network) { + return function(url) { + if (!/^https.*/.test(url)) { + if (ALLOW_HTTP !== 'yes') { + throw new Error(`http is not allowed: ${url}`) + } else { + logger.warn( + `You are using http (${url}) on ${network} network. In production https must be used instead.` + ) + } } } } diff --git a/src/watcher.js b/src/watcher.js index 3e748c6..7c5a428 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -30,8 +30,8 @@ async function initialize() { try { const checkHttps = checkHTTPS(process.env.ALLOW_HTTP) - rpcUrlsManager.homeUrls.forEach(checkHttps) - rpcUrlsManager.foreignUrls.forEach(checkHttps) + rpcUrlsManager.homeUrls.forEach(checkHttps('home')) + rpcUrlsManager.foreignUrls.forEach(checkHttps('foreign')) await getLastProcessedBlock() connectWatcherToQueue({ diff --git a/test/utils.test.js b/test/utils.test.js index 8327a90..0c1f5ac 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -44,22 +44,22 @@ describe('utils', () => { }) it('should do nothing if HTTP is allowed and the URL is https', () => { - utils.checkHTTPS('yes')('https://www.google.com') + utils.checkHTTPS('yes')('home')('https://www.google.com') expect(logger.warn.called).to.equal(false) }) it('should emit a warning if HTTP is allowed and the URL is http', () => { - utils.checkHTTPS('yes')('http://www.google.com') + utils.checkHTTPS('yes')('home')('http://www.google.com') expect(logger.warn.called).to.equal(true) }) it('should do nothing if HTTP is not allowed and the URL is https', () => { - utils.checkHTTPS('no')('https://www.google.com') + utils.checkHTTPS('no')('home')('https://www.google.com') expect(logger.warn.called).to.equal(false) }) it('should throw an error if HTTP is not allowed and the URL is http', () => { - expect(() => utils.checkHTTPS('no')('http://www.google.com')).to.throw() + expect(() => utils.checkHTTPS('no')('home')('http://www.google.com')).to.throw() }) }) From 5c79fd339253cd430eb0ca45c0837298420edbcb Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Mon, 8 Oct 2018 12:04:43 -0600 Subject: [PATCH 114/119] Changes based on review comments --- ERC-to-ERC.png | Bin 0 -> 30561 bytes Native-to-ERC.png | Bin 0 -> 30806 bytes README.md | 173 +++++++++++++++++++++++++++++++--------------- 3 files changed, 117 insertions(+), 56 deletions(-) create mode 100644 ERC-to-ERC.png create mode 100644 Native-to-ERC.png diff --git a/ERC-to-ERC.png b/ERC-to-ERC.png new file mode 100644 index 0000000000000000000000000000000000000000..ab4ba346a114e739fb8edae3d3a21c989c857745 GIT binary patch literal 30561 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 F{|6F4Jfr{s literal 0 HcmV?d00001 diff --git a/README.md b/README.md index a05417e..a7b4681 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,12 @@ The POA Bridge allows users to transfer assets between two chains in the Ethereu 1. An oracle written in NodeJS, contained in this repository. 2. [Solidity smart contracts](https://github.com/poanetwork/poa-bridge-contracts). Used to manage bridge validators, collect signatures, and confirm asset relay and disposal. 3. [Bridge UI Application](https://github.com/poanetwork/bridge-ui). A DApp interface to transfer tokens and coins between chains. -4. [Bridge Monitor](https://github.com/poanetwork/bridge-monitor). A tool for checking balances and unprocessed events in bridged networks. +4. [Bridge Monitor](https://github.com/poanetwork/bridge-monitor). A tool for checking balances and unprocessed events in bridged networks. +5. [Bridge Deployment Playbooks](https://github.com/poanetwork/deployment-bridge). Manages configuration instructions for remote deployments. -The bridge oracle is deployed on validator nodes in the network (nodes are specified in the smart contracts). The oracle connects to two chains via a Remote Procedure Call (RPC). It is responsible for: +The bridge oracle is deployed on specified validator nodes (only nodes whose private keys correspond to addresses specified in the smart contracts) in the network. The oracle connects to two chains via a Remote Procedure Call (RPC). It is responsible for: - listening to events related to bridge contracts -- sending transactions to authorize token transfers +- sending transactions to authorize asset transfers Following is an overview of the NodeJS bridge oracle and [instructions for getting started](#how-to-use) with the POA Bridge. @@ -24,11 +25,19 @@ Following is an overview of the NodeJS bridge oracle and [instructions for getti Interoperability is the ability to share resources between networks. The POA Bridge is an interoperability protocol where users can transfer value (ERC20 compatible tokens and network coins) between chains in the Ethereum ecosystem. This creates opportunities to use different chains for different purposes. For example, smart contracts can allocate resource intensive operations to a sidechain where transactions are fast and inexpensive. -## Operational Modes +## Network Processes -The POA bridge currently provides two operational modes, with a 3rd mode in development. In this context, **Home** - or Native - refers to a sidechain or private chain network, and **Foreign** generally refers to the Ethereum mainnet. +### Network Definitions -**Note:** _In ERC20-to-ERC20 mode Foreign can refer to another sidechain, so that two sidechains can bridge with one another._ + Bridging occurs between two networks. + + * **Home** - or Native - is a network with fast and inexpensive operations. All bridge operations to collect validator confirmations are performed on this side of the bridge. + +* **Foreign** can be any chain, but generally refers to the Ethereum mainnet. + +### Operational Modes + +The POA bridge currently provides two operational modes, with a 3rd mode in development. - [x] `Native-to-ERC20` **Coins** on a Home network can be converted to ERC20-compatible **tokens** on a Foreign network. Coins are locked on the Home side and the corresponding amount of ERC20 tokens are minted on the Foreign side. When the operation is reversed, tokens are burnt on the Foreign side and unlocked in the Home network. - [x] `ERC20-to-ERC20` ERC20-compatible tokens on the Foreign network are locked and minted as ERC20-compatible tokens (ERC677 tokens) on the Home network. When transferred from Home to Foreign, they are burnt on the Home side and unlocked in the Foreign network. This can be considered a form of atomic swap when a user swaps the token "X" in network "A" to the token "Y" in network "B". @@ -37,15 +46,28 @@ The POA bridge currently provides two operational modes, with a 3rd mode in deve ## Architecture -![bridge architecture](https://user-images.githubusercontent.com/4614574/42094368-f260f648-7b85-11e8-91d4-e602253a6560.png) +### Native-to-ERC + +![Native-to-ERC](Native-to-ERC.png) + +### ERC-to-ERC + +![ERC-to-ERC](ERC-to-ERC.png) + ### Watcher A watcher listens for a certain event and creates proper jobs in the queue. These jobs contain the transaction data (without the nonce) and the transaction hash for the related event. The watcher runs on a given frequency, keeping track of the last processed block. +If the watcher observes that the transaction date cannot be prepared, which generally means that the corresponding method of the bridge contract cannot be invoked, it inspects the contract state to identify the potential reason for failure and records this in the logs. + + There are three Watchers: -- **Signature Request Watcher**: Listens to `UserRequestForSignature` events on Home network. -- **Collected Signatures Watcher**: Listens to `CollectedSignatures` events on Home network. -- **Affirmation Request Watcher**: Listens to `UserRequestForAffirmation` events on Foreign network. +- **Signature Request Watcher**: Listens to `UserRequestForSignature` events on the Home network. +- **Collected Signatures Watcher**: Listens to `CollectedSignatures` events on the Home network. +- **Affirmation Request Watcher**: Depends on the bridge mode. + - Native-to-ERC: Listens to `UserRequestForAffirmation` raised by the bridge contract. + - `ERC-to-ERC` and `ERC-to-Native`: Listens to `Transfer` events raised by the token contract. + ### Sender A sender subscribes to the queue and keeps track of the nonce. It takes jobs from the queue, extracts transaction data, adds the proper nonce, and sends it to the network. @@ -58,51 +80,31 @@ There are two Senders: ## Installation and Deployment -### Requirements - -- [Truffle](https://github.com/trufflesuite/truffle) version `4.0` -- [RabbitMQ](https://www.rabbitmq.com/) version: `3.7` -- [Redis](https://redis.io/) version: `4.0` +The bridge contracts can be installed and deployed using [Docker](https://www.docker.com/products/docker-engine) and [Docker Compose](https://docs.docker.com/compose/install/) or manually with [RabbitMQ](https://www.rabbitmq.com/) version `3.7` and [Redis](https://redis.io/) version `4.0`. For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues/90) -### Deploy Bridge Contracts +#### Deploy the Bridge Contracts -1. Deploy the bridge contracts - 1. Clone repo: `git clone https://github.com/poanetwork/poa-bridge-contracts` - 2. `cd poa-bridge-contracts` - 3. Checkout branch `refactor_v1` : `git checkout refactor_v1` - 4. Compile contracts: `truffle compile` - 5. Go to deploy folder: `cd deploy` - 6. create a `.env` file: `cp .env.example .env` (look at `.env.example` to see the variables that need to be present) - 7. Execute `node deploy.js` +1. [Deploy the bridge contracts](https://github.com/poanetwork/poa-bridge-contracts/blob/master/deploy/README.md) 2. Start Redis and RabbitMQ in your local environment. - 1. `redis-server` starts Redis. `redis-cli ping` will return a `pong` if Redis is running. - 2. `rabbitmq-server` starts RabbitMQ. Use `rabbitmqctl status` to check if RabbitMQ is running. + 1. **Docker**: Start RabbitMQ and Redis: `docker-compose up -d` + 2. **Manual Start**: + `redis-server` starts Redis. `redis-cli ping` will return a `pong` if Redis is running. + `rabbitmq-server` starts RabbitMQ. Use `rabbitmqctl status` to check if RabbitMQ is running. 3. Create a `.env` file: `cp .env.example .env` - 1. fill in the required information using the output data from the bridge contract deployment. Check the `.env.example` file to see the required variables. - -## Run the Processes - -### Native to ERC mode - - `npm run watcher:signature-request` - - `npm run watcher:collected-signatures` - - `npm run watcher:affirmation-request` - - `npm run sender:home` - - `npm run sender:foreign` - -To send deposits to a home contract run `node scripts/sendUserTxToHome.js` + 1. fill in the required information using the output data from the bridge contract deployment in step 1. Check the `.env.example` file to see the required variables. -To send withdrawals to a foreign contract run `node scripts/sendUserTxToForeign.js` -Make sure your `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is the same as in your .env deployment contract. +## Run the Processes +- **Native-to-ERC**: In the `.env` file set `BRIDGE_MODE=NATIVE_TO_ERC` +- **ERC-to-ERC mode**: In the `.env` file set `BRIDGE_MODE=ERC_TO_ERC` -### ERC to ERC Mode -In `.env` file set `BRIDGE_MODE=ERC_TO_ERC` +### RabbitMQ / Redis - `npm run watcher:signature-request` - `npm run watcher:collected-signatures` @@ -110,11 +112,8 @@ In `.env` file set `BRIDGE_MODE=ERC_TO_ERC` - `npm run sender:home` - `npm run sender:foreign` -To deposit from Foreign to Home contract run `node scripts/sendUserTxToErcForeign.js 10` where `10` is how many tx you would like to send out. - -To withdrawal to Home to Foreign contract run `node scripts/sendUserTxToErcHome.js 10` where `10` is how many tx you would like to send out. -### Run with Docker +### Docker - Start RabbitMQ and Redis: `docker-compose up -d` - `docker-compose run bridge npm run watcher:signature-request` @@ -123,9 +122,10 @@ To withdrawal to Home to Foreign contract run `node scripts/sendUserTxToErcHome. - `docker-compose run bridge npm run sender:home` - `docker-compose run bridge npm run sender:foreign` +### Bridge UI + +See the [Bridge UI installation instructions](https://github.com/poanetwork/bridge-ui/) to configure and use the optional Bridge UI. -To use the bridge UI, clone [the repo](https://github.com/poanetwork/bridge-ui/), -create a `.env` using the same values as before, and run `npm start`. ### Useful Commands for Development @@ -144,17 +144,56 @@ Command | Description --- | --- `KEYS *` | Returns all keys `SET signature-request:lastProcessedBlock 1234` | Set key to hold the string value. -`GET signature-request:lastProcessedBlock` | Get the value of key. +`GET signature-request:lastProcessedBlock` | Get the key value. `DEL signature-request:lastProcessedBlock` | Removes the specified key. -`FLUSHALL` | Delete all the keys of all the existing databases -`redis-cli ping` | check if redis is running -`redis-server` | starts redis server +`FLUSHALL` | Delete all the keys in all existing databases. +`redis-cli ping` | check if redis is running. +`redis-server` | start redis server. + +##### Rollback the Last Processed Block in Redis + +If the bridge does not handle an event properly (i.e. a transaction stalls due to a low gas price), the Redis DB can be rolled back. You must identify which watcher needs to re-run. For example, if the validator signatures were collected but the transaction with signatures was not sent to the Foreign network, the `collected-signatures` watcher must look at the block where the corresponding `CollectedSignatures` event was raised. +Execute this command in the bridge root directory: + +```shell +bash ./reset-lastBlock.sh +``` + +where the _watcher_ could be one of: + +- `signature-request` +- `collected-signatures` +- `affirmation-request` + +### Parameters + +| Variable | Description | Values | +|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------| +| `BRIDGE_MODE` | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | `NATIVE_TO_ERC` / `ERC_TO_ERC` | +| `HOME_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Home network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | url(s) | +| `HOME_BRIDGE_ADDRESS` | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x" | +| `HOME_POLLING_INTERVAL` | The interval in milliseconds used to request the RPC node in the Home network for new blocks. The interval should match the average production time for a new block. | integer | +| `FOREIGN_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | url(s) | +| `FOREIGN_BRIDGE_ADDRESS` | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x" | +| `ERC20_TOKEN_ADDRESS` | Used with the `ERC_TO_ERC` bridge mode, this parameter specifies the ERC20-compatible token contract address. The token contract address is used to identify transactions that transfer tokens to the Foreign Bridge account address. Omit this parameter with other bridge modes. | hexidecimal beginning with "0x" | +| `FOREIGN_POLLING_INTERVAL` | The interval in milliseconds used to request the RPC node in the Foreign network for new blocks. The interval should match the average production time for a new block. | integer | +| `HOME_GAS_PRICE_ORACLE_URL` | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | url | +| `HOME_GAS_PRICE_SPEED_TYPE` | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `HOME_GAS_PRICE_ORACLE_URL` is not used. | `instant` / `fast` / `standard` / `slow` | +| `HOME_GAS_PRICE_FALLBACK` | The gas price (in GWei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer | +| `HOME_GAS_PRICE_UPDATE_INTERVAL` | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Home Bridge contract. | integer | +| `FOREIGN_GAS_PRICE_ORACLE_URL` | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. | url | +| `FOREIGN_GAS_PRICE_SPEED_TYPE` | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `FOREIGN_GAS_PRICE_ORACLE_URL`is not used. | `instant` / `fast` / `standard` / `slow` | +| `FOREIGN_GAS_PRICE_FALLBACK` | The gas price (in GWei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer | +| `FOREIGN_GAS_PRICE_UPDATE_INTERVAL` | The interval in milliseconds used to get the updated gas price value either from the oracle or from the Foreign Bridge contract. | integer | +| `VALIDATOR_ADDRESS_PRIVATE_KEY` | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal beginning with "0x" | +| `HOME_START_BLOCK` | The block number in the Home network used to start watching for events when the bridge instance is run for the first time. Usually this is the same block where the Home Bridge contract is deployed. If a new validator instance is being deployed for an existing set of validators, the block number could be the latest block in the chain. | integer | +| `FOREIGN_START_BLOCK` | The block number in the Foreign network used to start watching for events when the bridge instance runs for the first time. Usually this is the same block where the Foreign Bridge contract was deployed to. If a new validator instance is being deployed for an existing set of validators, the block number could be the latest block in the chain. | integer | +| `QUEUE_URL` | RabbitMQ url used by watchers and senders to communicate to the message queue. Typically set to: `amqp://127.0.0.1`. | local url | +| `REDIS_URL` | Redis DB url used by watchers and senders to communicate to the database. Typically set to: `redis://127.0.0.1:6379`. | local url | +| `REDIS_LOCK_TTL` | Threshold in milliseconds for locking a resource in the Redis DB. Until the threshold is exceeded, the resource is unlocked. Usually it is `1000`. | integer | +| `ALLOW_HTTP` | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC urls can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no` | -### Env Variables -Variable | Description | Values ---- | --- | --- -`ALLOW_HTTP` | Explicitly allows the usage of `http` connections instead of `https`. | `yes` / `no` ## Testing @@ -162,6 +201,28 @@ Variable | Description | Values npm test ``` +### E2E tests + +See the [E2E README](/e2e) for instructions. + + +### Native-to-ERC Mode Testing + +When running the processes, the following commands can be used to test functionality. + +- To send deposits to a home contract run `node scripts/sendUserTxToHome.js` + +- To send withdrawals to a foreign contract run `node scripts/sendUserTxToForeign.js` + +Confirm the `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is the same as in the .env deployment contract. + +### ERC-to-ERC-Mode Testing + +- To deposit from a Foreign to a Home contract run `node scripts/sendUserTxToErcForeign.js 10` where `10` is how many tx you would like to send out. + +- To make withdrawal to Home from a Foreign contract run `node scripts/sendUserTxToErcHome.js 10` where `10` is how many tx you would like to withdraw. + + ## Contributing See the [CONTRIBUTING](CONTRIBUTING.md) document for contribution, testing and pull request protocol. From 66016ba659ea212a756c3983c5062a98da95f154 Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Mon, 8 Oct 2018 23:24:35 +0300 Subject: [PATCH 115/119] Final corrections --- README.md | 168 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 110 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index a7b4681..4683621 100644 --- a/README.md +++ b/README.md @@ -41,16 +41,16 @@ The POA bridge currently provides two operational modes, with a 3rd mode in deve - [x] `Native-to-ERC20` **Coins** on a Home network can be converted to ERC20-compatible **tokens** on a Foreign network. Coins are locked on the Home side and the corresponding amount of ERC20 tokens are minted on the Foreign side. When the operation is reversed, tokens are burnt on the Foreign side and unlocked in the Home network. - [x] `ERC20-to-ERC20` ERC20-compatible tokens on the Foreign network are locked and minted as ERC20-compatible tokens (ERC677 tokens) on the Home network. When transferred from Home to Foreign, they are burnt on the Home side and unlocked in the Foreign network. This can be considered a form of atomic swap when a user swaps the token "X" in network "A" to the token "Y" in network "B". -- [ ] `ERC20-to-Native`: Currently in development. Pre-existing tokens in the Foreign network are locked and coins are minted in the `Home` network. +- [ ] `ERC20-to-Native`: Currently in development. Pre-existing tokens in the Foreign network are locked and coins are minted in the `Home` network. The Home network consensus engine in this case should support invocation of Parity's Block Reward contract (https://wiki.parity.io/Block-Reward-Contract.html) to mint coins as per the bridge contract request. ## Architecture -### Native-to-ERC +### Native-to-ERC20 ![Native-to-ERC](Native-to-ERC.png) -### ERC-to-ERC +### ERC20-to-ERC20 ![ERC-to-ERC](ERC-to-ERC.png) @@ -58,15 +58,15 @@ The POA bridge currently provides two operational modes, with a 3rd mode in deve ### Watcher A watcher listens for a certain event and creates proper jobs in the queue. These jobs contain the transaction data (without the nonce) and the transaction hash for the related event. The watcher runs on a given frequency, keeping track of the last processed block. -If the watcher observes that the transaction date cannot be prepared, which generally means that the corresponding method of the bridge contract cannot be invoked, it inspects the contract state to identify the potential reason for failure and records this in the logs. +If the watcher observes that the transaction data cannot be prepared, which generally means that the corresponding method of the bridge contract cannot be invoked, it inspects the contract state to identify the potential reason for failure and records this in the logs. There are three Watchers: - **Signature Request Watcher**: Listens to `UserRequestForSignature` events on the Home network. - **Collected Signatures Watcher**: Listens to `CollectedSignatures` events on the Home network. - **Affirmation Request Watcher**: Depends on the bridge mode. - - Native-to-ERC: Listens to `UserRequestForAffirmation` raised by the bridge contract. - - `ERC-to-ERC` and `ERC-to-Native`: Listens to `Transfer` events raised by the token contract. + - `Native-to-ERC20`: Listens to `UserRequestForAffirmation` raised by the bridge contract. + - `ERC20-to-ERC20` and `ERC20-to-Native`: Listens to `Transfer` events raised by the token contract. ### Sender @@ -76,43 +76,82 @@ There are two Senders: - **Home Sender**: Sends transaction to the `Home` network. - **Foreign Sender**: Sends transaction to the `Foreign` network. -# How to Use +### RabbitMQ -## Installation and Deployment +[RabbitMQ](https://www.rabbitmq.com/) is used to send jobs from watchers to senders. -The bridge contracts can be installed and deployed using [Docker](https://www.docker.com/products/docker-engine) and [Docker Compose](https://docs.docker.com/compose/install/) or manually with [RabbitMQ](https://www.rabbitmq.com/) version `3.7` and [Redis](https://redis.io/) version `4.0`. +### Redis DB + +Raddis is used to store number of blocks that were already inspected by watchers, and the NOnce which was used by the sender last time to send a transaction. For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues/90) -#### Deploy the Bridge Contracts +# How to Use -1. [Deploy the bridge contracts](https://github.com/poanetwork/poa-bridge-contracts/blob/master/deploy/README.md) +## Installation and Deployment -2. Start Redis and RabbitMQ in your local environment. - 1. **Docker**: Start RabbitMQ and Redis: `docker-compose up -d` - 2. **Manual Start**: - `redis-server` starts Redis. `redis-cli ping` will return a `pong` if Redis is running. - `rabbitmq-server` starts RabbitMQ. Use `rabbitmqctl status` to check if RabbitMQ is running. +#### Deploy the Bridge Contracts -3. Create a `.env` file: `cp .env.example .env` - 1. fill in the required information using the output data from the bridge contract deployment in step 1. Check the `.env.example` file to see the required variables. +1. [Deploy the bridge contracts](https://github.com/poanetwork/poa-bridge-contracts/blob/master/deploy/README.md) +2. Open `bridgeDeploymentResults.json` generated by the bridge contract deployment process. + + For `Native-to-ERC20` mode it looks like: + ```json + { + "homeBridge": { + "address": "0xc60daff55ec5b5ce5c3d2105a77e287ff638c35e", + "deployedBlockNumber": 123321 + }, + "foreignBridge": { + "address": "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be", + "deployedBlockNumber": 456654, + "erc677": { + "address": "0x41a29780309dc2582f080f6af89953be3435679a" + } + } + } + ``` + + For `ERC20-to-ERC20` mode it looks like: + ```json + { + "homeBridge": { + "address": "0x765a0d90e5a5773deacbd94b2dc941cbb163bdab", + "deployedBlockNumber": 789987, + "erc677": { + "address": "0x269f57f5ae5421d084686f9e353f5b7ee6af54c2" + } + }, + "foreignBridge": { + "address": "0x7ae703ea88b0545eef1f0bf8f91d5276e39be2f7", + "deployedBlockNumber": 567765 + } + } + ``` + +## Configuration + +1. Create a `.env` file: `cp .env.example .env` + +2. Fill in the required information using the output data from `bridgeDeploymentResults.json`. Check the tables with the set of parameters below to see their explanation. ## Run the Processes -- **Native-to-ERC**: In the `.env` file set `BRIDGE_MODE=NATIVE_TO_ERC` -- **ERC-to-ERC mode**: In the `.env` file set `BRIDGE_MODE=ERC_TO_ERC` - +There are two options to run the nodejs oracle: +1. By using docker containers. This requires [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) installed. If you are on Linux, it's also recommended that you [create a docker group and add your user to it](https://docs.docker.com/install/linux/linux-postinstall/), so that you can use the CLI without sudo. +2. By using NodeJs Package Manager. -### RabbitMQ / Redis +### NPM + - `redis-server` starts Redis. redis-cli ping will return a pong if Redis is running. + - `rabbitmq-server` starts RabbitMQ. Use rabbitmqctl status to check if RabbitMQ is running. - `npm run watcher:signature-request` - `npm run watcher:collected-signatures` - `npm run watcher:affirmation-request` - `npm run sender:home` - `npm run sender:foreign` - ### Docker - Start RabbitMQ and Redis: `docker-compose up -d` @@ -126,31 +165,7 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues See the [Bridge UI installation instructions](https://github.com/poanetwork/bridge-ui/) to configure and use the optional Bridge UI. - -### Useful Commands for Development - -#### RabbitMQ -Command | Description ---- | --- -`rabbitmqctl list_queues` | List all queues -`rabbitmqctl purge_queue home` | Remove all messages from `home` queue -`rabbitmqctl status` | check if rabbitmq server is currently running -`rabbitmq-server` | start rabbitMQ server - -#### Redis -Use `redis-cli` - -Command | Description ---- | --- -`KEYS *` | Returns all keys -`SET signature-request:lastProcessedBlock 1234` | Set key to hold the string value. -`GET signature-request:lastProcessedBlock` | Get the key value. -`DEL signature-request:lastProcessedBlock` | Removes the specified key. -`FLUSHALL` | Delete all the keys in all existing databases. -`redis-cli ping` | check if redis is running. -`redis-server` | start redis server. - -##### Rollback the Last Processed Block in Redis +## Rollback the Last Processed Block in Redis If the bridge does not handle an event properly (i.e. a transaction stalls due to a low gas price), the Redis DB can be rolled back. You must identify which watcher needs to re-run. For example, if the validator signatures were collected but the transaction with signatures was not sent to the Foreign network, the `collected-signatures` watcher must look at the block where the corresponding `CollectedSignatures` event was raised. @@ -159,6 +174,10 @@ Execute this command in the bridge root directory: ```shell bash ./reset-lastBlock.sh ``` +or +```shell +docker-compose run bridge bash ./reset-lastBlock.sh +``` where the _watcher_ could be one of: @@ -166,7 +185,7 @@ where the _watcher_ could be one of: - `collected-signatures` - `affirmation-request` -### Parameters +### Configuration parameters | Variable | Description | Values | |-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------| @@ -186,7 +205,7 @@ where the _watcher_ could be one of: | `FOREIGN_GAS_PRICE_SPEED_TYPE` | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `FOREIGN_GAS_PRICE_ORACLE_URL`is not used. | `instant` / `fast` / `standard` / `slow` | | `FOREIGN_GAS_PRICE_FALLBACK` | The gas price (in GWei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer | | `FOREIGN_GAS_PRICE_UPDATE_INTERVAL` | The interval in milliseconds used to get the updated gas price value either from the oracle or from the Foreign Bridge contract. | integer | -| `VALIDATOR_ADDRESS_PRIVATE_KEY` | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal beginning with "0x" | +| `VALIDATOR_ADDRESS_PRIVATE_KEY` | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal without "0x" | | `HOME_START_BLOCK` | The block number in the Home network used to start watching for events when the bridge instance is run for the first time. Usually this is the same block where the Home Bridge contract is deployed. If a new validator instance is being deployed for an existing set of validators, the block number could be the latest block in the chain. | integer | | `FOREIGN_START_BLOCK` | The block number in the Foreign network used to start watching for events when the bridge instance runs for the first time. Usually this is the same block where the Foreign Bridge contract was deployed to. If a new validator instance is being deployed for an existing set of validators, the block number could be the latest block in the chain. | integer | | `QUEUE_URL` | RabbitMQ url used by watchers and senders to communicate to the message queue. Typically set to: `amqp://127.0.0.1`. | local url | @@ -194,6 +213,28 @@ where the _watcher_ could be one of: | `REDIS_LOCK_TTL` | Threshold in milliseconds for locking a resource in the Redis DB. Until the threshold is exceeded, the resource is unlocked. Usually it is `1000`. | integer | | `ALLOW_HTTP` | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC urls can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no` | +### Useful Commands for Development + +#### RabbitMQ +Command | Description +--- | --- +`rabbitmqctl list_queues` | List all queues +`rabbitmqctl purge_queue home` | Remove all messages from `home` queue +`rabbitmqctl status` | check if rabbitmq server is currently running +`rabbitmq-server` | start rabbitMQ server + +#### Redis +Use `redis-cli` + +Command | Description +--- | --- +`KEYS *` | Returns all keys +`SET signature-request:lastProcessedBlock 1234` | Set key to hold the string value. +`GET signature-request:lastProcessedBlock` | Get the key value. +`DEL signature-request:lastProcessedBlock` | Removes the specified key. +`FLUSHALL` | Delete all the keys in all existing databases. +`redis-cli ping` | check if redis is running. +`redis-server` | start redis server. ## Testing @@ -205,23 +246,34 @@ npm test See the [E2E README](/e2e) for instructions. - -### Native-to-ERC Mode Testing +### Native-to-ERC20 Mode Testing When running the processes, the following commands can be used to test functionality. -- To send deposits to a home contract run `node scripts/sendUserTxToHome.js` +- To send deposits to a home contract run `node scripts/sendUserTxToHome.js ` (or `docker-compose run bridge node scripts/sendUserTxToHome.js `), where `` is how many tx will be sent out to deposit. -- To send withdrawals to a foreign contract run `node scripts/sendUserTxToForeign.js` +- To send withdrawals to a foreign contract run `node scripts/sendUserTxToForeign.js ` (or `docker-compose run bridge node scripts/sendUserTxToForeign.js `), where `` is how many tx will be sent out to withdraw. -Confirm the `HOME_MIN_AMOUNT_PER_TX` and `FOREIGN_MIN_AMOUNT_PER_TX` is the same as in the .env deployment contract. +### ERC20-to-ERC20 Mode Testing -### ERC-to-ERC-Mode Testing +- To deposit from a Foreign to a Home contract run `node scripts/sendUserTxToErcForeign.js `. -- To deposit from a Foreign to a Home contract run `node scripts/sendUserTxToErcForeign.js 10` where `10` is how many tx you would like to send out. +- To make withdrawal to Home from a Foreign contract run `node scripts/sendUserTxToErcHome.js `. -- To make withdrawal to Home from a Foreign contract run `node scripts/sendUserTxToErcHome.js 10` where `10` is how many tx you would like to withdraw. +### Configuration parameters for testing +| Variable | Description | +|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `HOME_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Home network. | +| `FOREIGN_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. | +| `USER_ADDRESS` | An account - the current owner of coins/tokesn | +| `USER_ADDRESS_PRIVATE_KEY` | A private key belonging to the account | +| `HOME_BRIDGE_ADDRESS` | Address of the bridge in the Home network to send transactions | +| `HOME_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Home network | +| `FOREIGN_BRIDGE_ADDRESS` | Address of the bridge in the Foreign network to send transactions | +| `FOREIGN_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Foreign network | +| `ERC20_TOKEN_ADDRESS` | An address of the token deployed on the Home side for `ERC20-to-ERC20` mode | +| `BRIDGEABLE_TOKEN_ADDRESS` | An address of the token deployed on the Home side for `Native-to-ERC20` mode or on the Foreign side for `ERC20-to-ERC20` | ## Contributing From 3c84ef9634d3800a743e9b0d5a02c92b1b5cc236 Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Mon, 8 Oct 2018 15:10:46 -0600 Subject: [PATCH 116/119] fix typos --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4683621..4e0c980 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ The POA bridge currently provides two operational modes, with a 3rd mode in deve - [x] `Native-to-ERC20` **Coins** on a Home network can be converted to ERC20-compatible **tokens** on a Foreign network. Coins are locked on the Home side and the corresponding amount of ERC20 tokens are minted on the Foreign side. When the operation is reversed, tokens are burnt on the Foreign side and unlocked in the Home network. - [x] `ERC20-to-ERC20` ERC20-compatible tokens on the Foreign network are locked and minted as ERC20-compatible tokens (ERC677 tokens) on the Home network. When transferred from Home to Foreign, they are burnt on the Home side and unlocked in the Foreign network. This can be considered a form of atomic swap when a user swaps the token "X" in network "A" to the token "Y" in network "B". -- [ ] `ERC20-to-Native`: Currently in development. Pre-existing tokens in the Foreign network are locked and coins are minted in the `Home` network. The Home network consensus engine in this case should support invocation of Parity's Block Reward contract (https://wiki.parity.io/Block-Reward-Contract.html) to mint coins as per the bridge contract request. +- [ ] `ERC20-to-Native`: Currently in development. Pre-existing tokens in the Foreign network are locked and coins are minted in the `Home` network. The Home network consensus engine in this case should support invocation of Parity's Block Reward contract (https://wiki.parity.io/Block-Reward-Contract.html) to mint coins per the bridge contract request. ## Architecture @@ -82,7 +82,7 @@ There are two Senders: ### Redis DB -Raddis is used to store number of blocks that were already inspected by watchers, and the NOnce which was used by the sender last time to send a transaction. +Redis is used to store the number of blocks that were already inspected by watchers, and the NOnce (Number of Operation) which was used by the sender last time to send a transaction. For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues/90) @@ -96,7 +96,7 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues 2. Open `bridgeDeploymentResults.json` generated by the bridge contract deployment process. - For `Native-to-ERC20` mode it looks like: + `Native-to-ERC20` mode example: ```json { "homeBridge": { @@ -113,7 +113,7 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues } ``` - For `ERC20-to-ERC20` mode it looks like: + `ERC20-to-ERC20` mode example: ```json { "homeBridge": { @@ -134,13 +134,13 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues 1. Create a `.env` file: `cp .env.example .env` -2. Fill in the required information using the output data from `bridgeDeploymentResults.json`. Check the tables with the set of parameters below to see their explanation. +2. Fill in the required information using the output data from `bridgeDeploymentResults.json`. Check the tables with the [set of parameters](#configuration-parameters) below to see their explanation. ## Run the Processes There are two options to run the nodejs oracle: -1. By using docker containers. This requires [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) installed. If you are on Linux, it's also recommended that you [create a docker group and add your user to it](https://docs.docker.com/install/linux/linux-postinstall/), so that you can use the CLI without sudo. -2. By using NodeJs Package Manager. +1. Docker containers. This requires [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) installed. If you are on Linux, it's also recommended that you [create a docker group and add your user to it](https://docs.docker.com/install/linux/linux-postinstall/), so that you can use the CLI without sudo. +2. NodeJs Package Manager. ### NPM @@ -190,28 +190,28 @@ where the _watcher_ could be one of: | Variable | Description | Values | |-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------| | `BRIDGE_MODE` | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | `NATIVE_TO_ERC` / `ERC_TO_ERC` | -| `HOME_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Home network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | url(s) | +| `HOME_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Home network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s) | | `HOME_BRIDGE_ADDRESS` | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x" | | `HOME_POLLING_INTERVAL` | The interval in milliseconds used to request the RPC node in the Home network for new blocks. The interval should match the average production time for a new block. | integer | -| `FOREIGN_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | url(s) | +| `FOREIGN_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s) | | `FOREIGN_BRIDGE_ADDRESS` | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x" | | `ERC20_TOKEN_ADDRESS` | Used with the `ERC_TO_ERC` bridge mode, this parameter specifies the ERC20-compatible token contract address. The token contract address is used to identify transactions that transfer tokens to the Foreign Bridge account address. Omit this parameter with other bridge modes. | hexidecimal beginning with "0x" | | `FOREIGN_POLLING_INTERVAL` | The interval in milliseconds used to request the RPC node in the Foreign network for new blocks. The interval should match the average production time for a new block. | integer | -| `HOME_GAS_PRICE_ORACLE_URL` | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | url | +| `HOME_GAS_PRICE_ORACLE_URL` | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | URL | | `HOME_GAS_PRICE_SPEED_TYPE` | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `HOME_GAS_PRICE_ORACLE_URL` is not used. | `instant` / `fast` / `standard` / `slow` | | `HOME_GAS_PRICE_FALLBACK` | The gas price (in GWei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer | | `HOME_GAS_PRICE_UPDATE_INTERVAL` | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Home Bridge contract. | integer | -| `FOREIGN_GAS_PRICE_ORACLE_URL` | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. | url | +| `FOREIGN_GAS_PRICE_ORACLE_URL` | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. | URL | | `FOREIGN_GAS_PRICE_SPEED_TYPE` | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `FOREIGN_GAS_PRICE_ORACLE_URL`is not used. | `instant` / `fast` / `standard` / `slow` | | `FOREIGN_GAS_PRICE_FALLBACK` | The gas price (in GWei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer | | `FOREIGN_GAS_PRICE_UPDATE_INTERVAL` | The interval in milliseconds used to get the updated gas price value either from the oracle or from the Foreign Bridge contract. | integer | | `VALIDATOR_ADDRESS_PRIVATE_KEY` | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal without "0x" | | `HOME_START_BLOCK` | The block number in the Home network used to start watching for events when the bridge instance is run for the first time. Usually this is the same block where the Home Bridge contract is deployed. If a new validator instance is being deployed for an existing set of validators, the block number could be the latest block in the chain. | integer | | `FOREIGN_START_BLOCK` | The block number in the Foreign network used to start watching for events when the bridge instance runs for the first time. Usually this is the same block where the Foreign Bridge contract was deployed to. If a new validator instance is being deployed for an existing set of validators, the block number could be the latest block in the chain. | integer | -| `QUEUE_URL` | RabbitMQ url used by watchers and senders to communicate to the message queue. Typically set to: `amqp://127.0.0.1`. | local url | -| `REDIS_URL` | Redis DB url used by watchers and senders to communicate to the database. Typically set to: `redis://127.0.0.1:6379`. | local url | +| `QUEUE_URL` | RabbitMQ URL used by watchers and senders to communicate to the message queue. Typically set to: `amqp://127.0.0.1`. | local URL | +| `REDIS_URL` | Redis DB URL used by watchers and senders to communicate to the database. Typically set to: `redis://127.0.0.1:6379`. | local URL | | `REDIS_LOCK_TTL` | Threshold in milliseconds for locking a resource in the Redis DB. Until the threshold is exceeded, the resource is unlocked. Usually it is `1000`. | integer | -| `ALLOW_HTTP` | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC urls can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no` | +| `ALLOW_HTTP` | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC URLs can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no` | ### Useful Commands for Development @@ -266,14 +266,14 @@ When running the processes, the following commands can be used to test functiona |-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `HOME_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Home network. | | `FOREIGN_RPC_URL` | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. | -| `USER_ADDRESS` | An account - the current owner of coins/tokesn | -| `USER_ADDRESS_PRIVATE_KEY` | A private key belonging to the account | -| `HOME_BRIDGE_ADDRESS` | Address of the bridge in the Home network to send transactions | -| `HOME_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Home network | -| `FOREIGN_BRIDGE_ADDRESS` | Address of the bridge in the Foreign network to send transactions | -| `FOREIGN_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Foreign network | -| `ERC20_TOKEN_ADDRESS` | An address of the token deployed on the Home side for `ERC20-to-ERC20` mode | -| `BRIDGEABLE_TOKEN_ADDRESS` | An address of the token deployed on the Home side for `Native-to-ERC20` mode or on the Foreign side for `ERC20-to-ERC20` | +| `USER_ADDRESS` | An account - the current owner of coins/tokens. | +| `USER_ADDRESS_PRIVATE_KEY` | A private key belonging to the account. | +| `HOME_BRIDGE_ADDRESS` | Address of the bridge in the Home network to send transactions. | +| `HOME_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Home network. | +| `FOREIGN_BRIDGE_ADDRESS` | Address of the bridge in the Foreign network to send transactions. | +| `FOREIGN_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Foreign network. | +| `ERC20_TOKEN_ADDRESS` | An address of the token deployed on the Home side for `ERC20-to-ERC20` mode. | +| `BRIDGEABLE_TOKEN_ADDRESS` | An address of the token deployed on the Home side for `Native-to-ERC20` mode or on the Foreign side for `ERC20-to-ERC20`. | ## Contributing From a44dc17d25672d03190b4717a111e28f2f4c1e6b Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 9 Oct 2018 09:57:27 -0300 Subject: [PATCH 117/119] Update contract submodule to 2.0.0 tag --- e2e/Dockerfile | 3 ++- submodules/poa-bridge-contracts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e/Dockerfile b/e2e/Dockerfile index 5507ead..c9f487c 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -11,7 +11,8 @@ RUN git clone https://github.com/poanetwork/poa-bridge-contracts.git RUN mkdir submodules && \ mv poa-bridge-contracts submodules && \ cd submodules/poa-bridge-contracts && \ - git checkout refactor_v1 + git fetch && \ + git checkout 5d9f5946cda6043d7584b6da49693f7d3391b78d RUN npm install --unsafe-perm diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 4e869c0..5d9f594 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 4e869c0c06dcff4d1d38ca65e9a62e973f5c3e97 +Subproject commit 5d9f5946cda6043d7584b6da49693f7d3391b78d From 5da4f2a7cbea422a08c5d421756c1dede93bf655 Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Tue, 9 Oct 2018 16:31:05 +0300 Subject: [PATCH 118/119] README updated to reflect necessity to build docker containers --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e0c980..8d7a7c3 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ There are two options to run the nodejs oracle: ### Docker - - Start RabbitMQ and Redis: `docker-compose up -d` + - Start RabbitMQ and Redis: if the brdige containers are being run for the first time `docker-compose up -d --build` otherwise `docker-compose up -d` - `docker-compose run bridge npm run watcher:signature-request` - `docker-compose run bridge npm run watcher:collected-signatures` - `docker-compose run bridge npm run watcher:affirmation-request` From 0020db79769f1dac865aec485b2a96bfa45aa87b Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Tue, 9 Oct 2018 16:53:36 +0300 Subject: [PATCH 119/119] fix a typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7a7c3..fa4fb0f 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ There are two options to run the nodejs oracle: ### Docker - - Start RabbitMQ and Redis: if the brdige containers are being run for the first time `docker-compose up -d --build` otherwise `docker-compose up -d` + - Start RabbitMQ and Redis: if you are running the bridge containers for the first time use `docker-compose up -d --build` otherwise use `docker-compose up -d` - `docker-compose run bridge npm run watcher:signature-request` - `docker-compose run bridge npm run watcher:collected-signatures` - `docker-compose run bridge npm run watcher:affirmation-request`