diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 8a0c884f..ed1c6e28 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -47,6 +47,7 @@ ORACLE_FOREIGN_TX_RESEND_INTERVAL | Interval in milliseconds for automatic resen ORACLE_SHUTDOWN_SERVICE_URL | Optional external URL to some other service/monitor/configuration manager that controls the remote shutdown process. GET request should return `application/json` message with the following schema: `{ shutdown: true/false }`. | URL ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL | Optional interval in milliseconds used to request the side RPC node or external shutdown service. Default is 120000. | integer ORACLE_SIDE_RPC_URL | Optional HTTPS URL(s) for communication with the external shutdown service or side RPC nodes, used for shutdown manager activities. 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) +ORACLE_FOREIGN_ARCHIVE_RPC_URL | Optional HTTPS URL(s) for communication with the archive nodes on the foreign network. Only used in AMB bridge mode for async information request processing. 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) ORACLE_SHUTDOWN_CONTRACT_ADDRESS | Optional contract address in the side chain accessible through `ORACLE_SIDE_RPC_URL`, where the method passed in `ORACLE_SHUTDOWN_CONTRACT_METHOD` is implemented. | `address` ORACLE_SHUTDOWN_CONTRACT_METHOD | Method signature to be used in the side chain to identify the current shutdown status. Method should return boolean. Default value is `isShutdown()`. | `function signature` ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Foreign chain. Infinite, if not provided. | `integer` diff --git a/deployment-e2e/molecule/ultimate-commons/oracle-docker-compose.yml b/deployment-e2e/molecule/ultimate-commons/oracle-docker-compose.yml index b8986f86..5fad0ed6 100644 --- a/deployment-e2e/molecule/ultimate-commons/oracle-docker-compose.yml +++ b/deployment-e2e/molecule/ultimate-commons/oracle-docker-compose.yml @@ -9,6 +9,7 @@ - oracle_net_db_bridge_request - oracle_net_db_bridge_collected - oracle_net_db_bridge_affirmation + - oracle_net_db_bridge_information - oracle_net_db_bridge_transfer - oracle_net_db_bridge_senderhome - oracle_net_db_bridge_senderforeign @@ -16,6 +17,7 @@ - oracle_net_rabbit_bridge_request - oracle_net_rabbit_bridge_collected - oracle_net_rabbit_bridge_affirmation + - oracle_net_rabbit_bridge_information - oracle_net_rabbit_bridge_transfer - oracle_net_rabbit_bridge_senderhome - oracle_net_rabbit_bridge_senderforeign diff --git a/deployment/roles/oracle/tasks/logging.yml b/deployment/roles/oracle/tasks/logging.yml index 9f2cee28..96bdffe0 100644 --- a/deployment/roles/oracle/tasks/logging.yml +++ b/deployment/roles/oracle/tasks/logging.yml @@ -3,6 +3,7 @@ with_items: - docker-compose - docker-compose-transfer + - docker-compose-amb loop_control: loop_var: file diff --git a/deployment/roles/oracle/tasks/post_config.yml b/deployment/roles/oracle/tasks/post_config.yml index 3651dc57..6fe87ceb 100644 --- a/deployment/roles/oracle/tasks/post_config.yml +++ b/deployment/roles/oracle/tasks/post_config.yml @@ -42,6 +42,10 @@ set_fact: composefileoverride="-f docker-compose-transfer.yml" when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE" +- name: Extend docker compose file for amb + set_fact: composefileoverride="-f docker-compose-amb.yml" + when: ORACLE_BRIDGE_MODE == "ARBITRARY_MESSAGE" + - name: Install .key config template: src: key.j2 diff --git a/deployment/roles/oracle/tasks/pre_config.yml b/deployment/roles/oracle/tasks/pre_config.yml index 9b7a32a6..20bdbdfc 100644 --- a/deployment/roles/oracle/tasks/pre_config.yml +++ b/deployment/roles/oracle/tasks/pre_config.yml @@ -20,3 +20,4 @@ with_items: - docker-compose.yml - docker-compose-transfer.yml + - docker-compose-amb.yml diff --git a/e2e-commons/components-envs/oracle-amb.env b/e2e-commons/components-envs/oracle-amb.env index 3de73f3d..b09c20d3 100644 --- a/e2e-commons/components-envs/oracle-amb.env +++ b/e2e-commons/components-envs/oracle-amb.env @@ -24,3 +24,4 @@ ORACLE_ALLOW_HTTP_FOR_RPC=yes ORACLE_HOME_START_BLOCK=1 ORACLE_FOREIGN_START_BLOCK=1 ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=/mono/oracle/access-lists/block_list.txt +ORACLE_FOREIGN_ARCHIVE_RPC_URL=http://parity2:8545 diff --git a/e2e-commons/contracts-envs/amb.env b/e2e-commons/contracts-envs/amb.env index e575f0b5..020c7113 100644 --- a/e2e-commons/contracts-envs/amb.env +++ b/e2e-commons/contracts-envs/amb.env @@ -9,7 +9,7 @@ HOME_RPC_URL=http://parity1:8545 HOME_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_VALIDATORS_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b -HOME_MAX_AMOUNT_PER_TX=8000000 +HOME_MAX_AMOUNT_PER_TX=2000000 HOME_REQUIRED_BLOCK_CONFIRMATIONS=1 HOME_GAS_PRICE=1000000000 @@ -17,7 +17,7 @@ FOREIGN_RPC_URL=http://parity2:8545 FOREIGN_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b FOREIGN_VALIDATORS_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b FOREIGN_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b -FOREIGN_MAX_AMOUNT_PER_TX=8000000 +FOREIGN_MAX_AMOUNT_PER_TX=2000000 FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1 FOREIGN_GAS_PRICE=10000000000 diff --git a/e2e-commons/down.sh b/e2e-commons/down.sh index 0672cced..48b27ccb 100755 --- a/e2e-commons/down.sh +++ b/e2e-commons/down.sh @@ -5,6 +5,7 @@ if [ $CI ]; then exit $rc; fi ps | grep node | grep -v grep | grep -v yarn | awk '{print "kill " $1}' | /bin/bash docker-compose down +docker-compose -p validator1 down docker-compose -p validator2 down docker-compose -p validator3 down docker network rm ultimate || true diff --git a/e2e-commons/up.sh b/e2e-commons/up.sh index b82b5a3e..b3813379 100755 --- a/e2e-commons/up.sh +++ b/e2e-commons/up.sh @@ -15,52 +15,42 @@ docker network create --driver bridge ultimate || true docker-compose up -d parity1 parity2 e2e startValidator () { - docker-compose $1 run -d --name $4 redis - docker-compose $1 run -d --name $5 rabbit + db_env="-e ORACLE_QUEUE_URL=amqp://$4 -e ORACLE_REDIS_URL=redis://$3" + + docker-compose $1 run -d --name $3 redis + docker-compose $1 run -d --name $4 rabbit + if [[ -z "$MODE" || "$MODE" == erc-to-native ]]; then - docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:signature-request - docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:collected-signatures - docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:affirmation-request - docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:transfer + docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:signature-request + docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:collected-signatures + docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:affirmation-request + docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:transfer fi if [[ -z "$MODE" || "$MODE" == amb ]]; then - docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:signature-request - docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:collected-signatures - docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:affirmation-request + docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:signature-request + docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:collected-signatures + docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:affirmation-request + docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:information-request fi - docker-compose $1 run $2 $3 -d oracle-amb yarn sender:home - docker-compose $1 run $2 $3 -d oracle-amb yarn sender:foreign - docker-compose $1 run $2 $3 -d oracle-amb yarn manager:shutdown -} -startAMBValidator () { - docker-compose $1 run -d --name $4 redis - docker-compose $1 run -d --name $5 rabbit - docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:signature-request - docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:collected-signatures - docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:affirmation-request - docker-compose $1 run $2 $3 -d oracle-amb yarn sender:home - docker-compose $1 run $2 $3 -d oracle-amb yarn sender:foreign - docker-compose $1 run $2 $3 -d oracle-amb yarn manager:shutdown + docker-compose $1 run $2 $db_env -d oracle-amb yarn sender:home + docker-compose $1 run $2 $db_env -d oracle-amb yarn sender:foreign + docker-compose $1 run $2 $db_env -d oracle-amb yarn manager:shutdown } while [ "$1" != "" ]; do if [ "$1" == "oracle" ]; then - startValidator "" "" "" "redis" "rabbit" + startValidator "-p validator1" "" redis rabbit fi if [ "$1" == "oracle-validator-2" ]; then - oracle2name="-p validator2" oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513" - oracle2comp="-e ORACLE_QUEUE_URL=amqp://rabbit2 -e ORACLE_REDIS_URL=redis://redis2" - startValidator "$oracle2name" "$oracle2Values" "$oracle2comp" "redis2" "rabbit2" + startValidator "-p validator2" "$oracle2Values" redis2 rabbit2 fi if [ "$1" == "oracle-validator-3" ]; then - oracle3name="-p validator3" oracle3Values="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1" - oracle3comp="-e ORACLE_QUEUE_URL=amqp://rabbit3 -e ORACLE_REDIS_URL=redis://redis3" - startValidator "$oracle3name" "$oracle3Values" "$oracle3comp" "redis3" "rabbit3" + startValidator "-p validator3" "$oracle3Values" redis3 rabbit3 fi if [ "$1" == "alm" ]; then @@ -92,17 +82,15 @@ while [ "$1" != "" ]; do fi if [ "$1" == "alm-e2e" ]; then - startAMBValidator "" "" "" "redis" "rabbit" + MODE=amb + + startValidator "-p validator1" "" redis rabbit - oracle2name="-p validator2" oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513" - oracle2comp="-e ORACLE_QUEUE_URL=amqp://rabbit2 -e ORACLE_REDIS_URL=redis://redis2" - startAMBValidator "$oracle2name" "$oracle2Values" "$oracle2comp" "redis2" "rabbit2" + startValidator "-p validator2" "$oracle2Values" redis2 rabbit2 - oracle3name="-p validator3" oracle3Values="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1" - oracle3comp="-e ORACLE_QUEUE_URL=amqp://rabbit3 -e ORACLE_REDIS_URL=redis://redis3" - startAMBValidator "$oracle3name" "$oracle3Values" "$oracle3comp" "redis3" "rabbit3" + startValidator "-p validator3" "$oracle3Values" redis3 rabbit3 fi shift # Shift all the parameters down by one diff --git a/oracle-e2e/package.json b/oracle-e2e/package.json index 03f0765b..562d64e1 100644 --- a/oracle-e2e/package.json +++ b/oracle-e2e/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "start": "mocha", + "start": "mocha --exit", "lint": "eslint . --ignore-path ../.eslintignore", "amb": "mocha test/amb.js", "erc-to-native": "mocha test/ercToNative.js", diff --git a/oracle-e2e/test/amb.js b/oracle-e2e/test/amb.js index cb362275..cecc1f53 100644 --- a/oracle-e2e/test/amb.js +++ b/oracle-e2e/test/amb.js @@ -10,23 +10,47 @@ const { toBN } = Web3.utils const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL)) const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(foreignRPC.URL)) -const COMMON_HOME_BRIDGE_ADDRESS = amb.home -const COMMON_FOREIGN_BRIDGE_ADDRESS = amb.foreign - homeWeb3.eth.accounts.wallet.add(user.privateKey) homeWeb3.eth.accounts.wallet.add(validator.privateKey) foreignWeb3.eth.accounts.wallet.add(user.privateKey) foreignWeb3.eth.accounts.wallet.add(validator.privateKey) -const homeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.homeBox) -const blockHomeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.blockedHomeBox) -const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox) -const homeBridge = new homeWeb3.eth.Contract(HOME_AMB_ABI, COMMON_HOME_BRIDGE_ADDRESS) -const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS) +const opts = { + from: user.address, + gas: 400000, + gasPrice: '1' +} +const homeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.homeBox, opts) +const blockHomeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.blockedHomeBox, opts) +const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox, opts) +const homeBridge = new homeWeb3.eth.Contract(HOME_AMB_ABI, amb.home, opts) +const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, amb.foreign, opts) describe('arbitrary message bridging', () => { let requiredSignatures = 1 before(async () => { + const allowedMethods = [ + 'eth_call(address,bytes)', + 'eth_call(address,bytes,uint256)', + 'eth_call(address,address,uint256,bytes)', + 'eth_blockNumber()', + 'eth_getBlockByNumber()', + 'eth_getBlockByNumber(uint256)', + 'eth_getBlockByHash(bytes32)', + 'eth_getBalance(address)', + 'eth_getBalance(address,uint256)', + 'eth_getTransactionCount(address)', + 'eth_getTransactionCount(address,uint256)', + 'eth_getTransactionByHash(bytes32)', + 'eth_getTransactionReceipt(bytes32)', + 'eth_getStorageAt(address,bytes32)', + 'eth_getStorageAt(address,bytes32,uint256)' + ] + for (const method of allowedMethods) { + const selector = homeWeb3.utils.soliditySha3(method) + await homeBridge.methods.enableAsyncRequestSelector(selector, true).send({ from: validator.address }) + } + // Only 1 validator is used in ultimate tests if (process.env.ULTIMATE === 'true') { return @@ -66,10 +90,7 @@ describe('arbitrary message bridging', () => { await homeBox.methods .setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox) - .send({ - from: user.address, - gas: '400000' - }) + .send() .catch(e => { console.error(e) }) @@ -98,10 +119,7 @@ describe('arbitrary message bridging', () => { await blockHomeBox.methods .setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox) - .send({ - from: user.address, - gas: '400000' - }) + .send() .catch(e => { console.error(e) }) @@ -137,10 +155,7 @@ describe('arbitrary message bridging', () => { await homeBox.methods .setValueOnOtherNetworkUsingManualLane(newValue, amb.home, amb.foreignBox) - .send({ - from: user.address, - gas: '400000' - }) + .send() .catch(e => { console.error(e) }) @@ -173,10 +188,7 @@ describe('arbitrary message bridging', () => { await foreignBox.methods .setValueOnOtherNetwork(newValue, amb.foreign, amb.homeBox) - .send({ - from: user.address, - gas: '400000' - }) + .send() .catch(e => { console.error(e) }) @@ -191,4 +203,363 @@ describe('arbitrary message bridging', () => { }) }) }) + describe('Home to Foreign Async Call', () => { + async function makeAsyncCall(selector, data) { + const prevMessageId = await homeBox.methods.messageId().call() + + await homeBox.methods + .makeAsyncCall(amb.home, selector, data) + .send() + .catch(e => { + console.error(e) + }) + + // check that value changed and balance decreased + await uniformRetry(async retry => { + const messageId = await homeBox.methods.messageId().call() + if (messageId === prevMessageId) { + retry() + } + }) + } + + it('should make async eth_call', async () => { + const foreignValue = await foreignBox.methods.value().call() + const selector = homeWeb3.utils.soliditySha3('eth_call(address,bytes)') + const data = homeWeb3.eth.abi.encodeParameters( + ['address', 'bytes'], + [amb.foreignBox, foreignBox.methods.value().encodeABI()] + ) + + await makeAsyncCall(selector, data) + + assert(await homeBox.methods.status().call(), 'status is false') + assert.strictEqual( + await homeBox.methods.data().call(), + homeWeb3.eth.abi.encodeParameters(['bytes'], [homeWeb3.eth.abi.encodeParameter('uint256', foreignValue)]), + 'returned data is incorrect' + ) + }) + + it('should make async eth_call with 4 arguments', async () => { + const foreignValue = await foreignBox.methods.value().call() + const selector = homeWeb3.utils.soliditySha3('eth_call(address,address,uint256,bytes)') + const data1 = homeWeb3.eth.abi.encodeParameters( + ['address', 'address', 'uint256', 'bytes'], + [amb.foreignBox, user.address, '100000', foreignBox.methods.value().encodeABI()] + ) + + await makeAsyncCall(selector, data1) + + assert(await homeBox.methods.status().call(), 'status is false') + assert.strictEqual( + await homeBox.methods.data().call(), + homeWeb3.eth.abi.encodeParameters(['bytes'], [homeWeb3.eth.abi.encodeParameter('uint256', foreignValue)]), + 'returned data is incorrect' + ) + + const data2 = homeWeb3.eth.abi.encodeParameters( + ['address', 'address', 'uint256', 'bytes'], + [amb.foreignBox, user.address, '1000', foreignBox.methods.value().encodeABI()] + ) + + await makeAsyncCall(selector, data2) + + assert(!(await homeBox.methods.status().call()), 'status is true') + assert.strictEqual(await homeBox.methods.data().call(), null, 'returned data is incorrect') + + const data3 = homeWeb3.eth.abi.encodeParameters( + ['address', 'address', 'uint256', 'bytes'], + [amb.foreignBox, user.address, '21300', foreignBox.methods.value().encodeABI()] + ) + + await makeAsyncCall(selector, data3) + + assert(!(await homeBox.methods.status().call()), 'status is true') + assert.strictEqual(await homeBox.methods.data().call(), null, 'returned data is incorrect') + }) + + it('should make async eth_call for specific block', async () => { + const foreignValue = await foreignBox.methods.value().call() + const blockNumber = await foreignWeb3.eth.getBlockNumber() + const selector = homeWeb3.utils.soliditySha3('eth_call(address,bytes,uint256)') + const data1 = homeWeb3.eth.abi.encodeParameters( + ['address', 'bytes', 'uint256'], + [amb.foreignBox, foreignBox.methods.value().encodeABI(), 60] + ) + const data2 = homeWeb3.eth.abi.encodeParameters( + ['address', 'bytes', 'uint256'], + [amb.foreignBox, foreignBox.methods.value().encodeABI(), blockNumber - 2] + ) + const data3 = homeWeb3.eth.abi.encodeParameters( + ['address', 'bytes', 'uint256'], + [amb.foreignBox, foreignBox.methods.value().encodeABI(), blockNumber + 20] + ) + + await makeAsyncCall(selector, data1) + + assert(await homeBox.methods.status().call(), 'status is false') + assert.strictEqual( + await homeBox.methods.data().call(), + homeWeb3.eth.abi.encodeParameters(['bytes'], [homeWeb3.eth.abi.encodeParameter('uint256', 0)]), + 'returned data is incorrect' + ) + + await makeAsyncCall(selector, data2) + + assert(await homeBox.methods.status().call(), 'status is false') + assert.strictEqual( + await homeBox.methods.data().call(), + homeWeb3.eth.abi.encodeParameters(['bytes'], [homeWeb3.eth.abi.encodeParameter('uint256', foreignValue)]), + 'returned data is incorrect' + ) + + await makeAsyncCall(selector, data3) + + assert(!(await homeBox.methods.status().call()), 'status is true') + }) + + it('should make async eth_blockNumber', async () => { + const selector = homeWeb3.utils.soliditySha3('eth_blockNumber()') + + await makeAsyncCall(selector, '0x') + + assert(await homeBox.methods.status().call(), 'status is false') + assert.strictEqual((await homeBox.methods.data().call()).length, 66, 'invalid block number') + }) + + it('should make async eth_getBlockByNumber', async () => { + const blockNumber = ((await foreignWeb3.eth.getBlockNumber()) - 5).toString() + const selector = homeWeb3.utils.soliditySha3('eth_getBlockByNumber(uint256)') + + await makeAsyncCall(selector, homeWeb3.eth.abi.encodeParameter('uint256', blockNumber)) + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64 * 3) + const { 0: number, 1: hash, 2: miner } = homeWeb3.eth.abi.decodeParameters( + ['uint256', 'bytes32', 'address'], + data + ) + const block = await foreignWeb3.eth.getBlock(blockNumber) + assert.strictEqual(number, blockNumber, 'wrong block number returned') + assert.strictEqual(hash, block.hash, 'wrong block hash returned') + assert.strictEqual(miner, block.miner, 'wrong block miner returned') + }) + + it('should make async eth_getBlockByNumber and return latest block', async () => { + const selector = homeWeb3.utils.soliditySha3('eth_getBlockByNumber()') + + await makeAsyncCall(selector, '0x') + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64 * 3) + }) + + it('should make async eth_getBlockByHash', async () => { + const blockNumber = ((await foreignWeb3.eth.getBlockNumber()) - 5).toString() + const block = await foreignWeb3.eth.getBlock(blockNumber) + const selector = homeWeb3.utils.soliditySha3('eth_getBlockByHash(bytes32)') + + await makeAsyncCall(selector, block.hash) + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64 * 3) + + const { 0: number, 1: hash, 2: miner } = homeWeb3.eth.abi.decodeParameters( + ['uint256', 'bytes32', 'address'], + data + ) + + assert.strictEqual(number, blockNumber, 'wrong block number returned') + assert.strictEqual(hash, block.hash, 'wrong block hash returned') + assert.strictEqual(miner, block.miner, 'wrong block miner returned') + }) + + it('should make async eth_getBalance', async () => { + const balance = await foreignWeb3.eth.getBalance(user.address) + const selector = homeWeb3.utils.soliditySha3('eth_getBalance(address)') + + await makeAsyncCall(selector, homeWeb3.eth.abi.encodeParameter('address', user.address)) + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64) + + assert.strictEqual(homeWeb3.eth.abi.decodeParameter('uint256', data), balance, 'wrong user balance returned') + }) + + it('should make async eth_getBalance for specific block', async () => { + const balance = await foreignWeb3.eth.getBalance(user.address) + const { blockNumber } = await foreignWeb3.eth.sendTransaction({ + to: user.address, + value: 1, + from: user.address, + gas: 21000 + }) + const selector = homeWeb3.utils.soliditySha3('eth_getBalance(address,uint256)') + + const data1 = homeWeb3.eth.abi.encodeParameters(['address', 'uint256'], [user.address, blockNumber - 1]) + const data2 = homeWeb3.eth.abi.encodeParameters(['address', 'uint256'], [user.address, blockNumber]) + await makeAsyncCall(selector, data1) + + assert(await homeBox.methods.status().call(), 'status is false') + let data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64) + + assert.strictEqual(homeWeb3.eth.abi.decodeParameter('uint256', data), balance, 'wrong user balance returned') + + await makeAsyncCall(selector, data2) + + assert(await homeBox.methods.status().call(), 'status is false') + data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64) + + assert.notStrictEqual(homeWeb3.eth.abi.decodeParameter('uint256', data), balance, 'wrong user balance returned') + }) + + it('should make async eth_getTransactionCount', async () => { + const nonce = (await foreignWeb3.eth.getTransactionCount(user.address)).toString() + const selector = homeWeb3.utils.soliditySha3('eth_getTransactionCount(address)') + + await makeAsyncCall(selector, homeWeb3.eth.abi.encodeParameter('address', user.address)) + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64) + + assert.strictEqual(homeWeb3.eth.abi.decodeParameter('uint256', data), nonce, 'wrong user nonce returned') + }) + + it('should make async eth_getTransactionCount for specific block', async () => { + let nonce = (await foreignWeb3.eth.getTransactionCount(user.address)).toString() + const { blockNumber } = await foreignWeb3.eth.sendTransaction({ + to: user.address, + value: 1, + from: user.address, + gas: 21000 + }) + const selector = homeWeb3.utils.soliditySha3('eth_getTransactionCount(address,uint256)') + + const data1 = homeWeb3.eth.abi.encodeParameters(['address', 'uint256'], [user.address, blockNumber - 1]) + const data2 = homeWeb3.eth.abi.encodeParameters(['address', 'uint256'], [user.address, blockNumber]) + + await makeAsyncCall(selector, data1) + assert(await homeBox.methods.status().call(), 'status is false') + let data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64) + + assert.strictEqual(homeWeb3.eth.abi.decodeParameter('uint256', data), nonce, 'wrong user nonce returned') + + await makeAsyncCall(selector, data2) + assert(await homeBox.methods.status().call(), 'status is false') + data = await homeBox.methods.data().call() + assert.strictEqual(data.length, 2 + 64) + + nonce = (parseInt(nonce, 10) + 1).toString() + assert.strictEqual(homeWeb3.eth.abi.decodeParameter('uint256', data), nonce, 'wrong user nonce returned') + }) + + it('should make async eth_getTransactionByHash', async () => { + const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6' + const tx = await foreignWeb3.eth.getTransaction(txHash) + const selector = homeWeb3.utils.soliditySha3('eth_getTransactionByHash(bytes32)') + + await makeAsyncCall(selector, txHash) + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + const dataTypes = [ + 'bytes32', + 'uint256', + 'address', + 'address', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'bytes' + ] + const values = homeWeb3.eth.abi.decodeParameters(dataTypes, data) + + assert.strictEqual(values[0], txHash, 'wrong txHash returned') + assert.strictEqual(values[1], tx.blockNumber.toString(), 'wrong tx blockNumber returned') + assert.strictEqual(values[2], tx.from, 'wrong tx from returned') + assert.strictEqual(values[3], tx.to, 'wrong tx to returned') + assert.strictEqual(values[4], tx.value, 'wrong tx value returned') + assert.strictEqual(values[5], tx.nonce.toString(), 'wrong tx nonce returned') + assert.strictEqual(values[6], tx.gas.toString(), 'wrong tx gas returned') + assert.strictEqual(values[7], tx.gasPrice, 'wrong tx gasPrice returned') + assert.strictEqual(values[8], tx.input, 'wrong tx data returned') + }) + + it('should make async eth_getTransactionReceipt', async () => { + const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6' + const receipt = await foreignWeb3.eth.getTransactionReceipt(txHash) + const selector = homeWeb3.utils.soliditySha3('eth_getTransactionReceipt(bytes32)') + + await makeAsyncCall(selector, txHash) + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + const dataTypes = ['bytes32', 'uint256', 'bool', '(address,bytes32[],bytes)[]'] + const values = homeWeb3.eth.abi.decodeParameters(dataTypes, data) + + assert.strictEqual(values[0], txHash, 'wrong txHash returned') + assert.strictEqual(values[1], receipt.blockNumber.toString(), 'wrong tx blockNumber returned') + assert.strictEqual(values[2], receipt.status, 'wrong tx status returned') + assert.strictEqual(values[3].length, 1, 'wrong logs length returned') + assert.strictEqual(values[3][0][0], receipt.logs[0].address, 'wrong log address returned') + assert.strictEqual(values[3][0][1].length, 2, 'wrong log topics length returned') + assert.strictEqual(values[3][0][1][0], receipt.logs[0].topics[0], 'wrong event signature returned') + assert.strictEqual(values[3][0][1][1], receipt.logs[0].topics[1], 'wrong message id returned') + assert.strictEqual(values[3][0][2], receipt.logs[0].data, 'wrong log data returned') + }) + + it('should make async eth_getStorageAt', async () => { + // slot for uintStorage[MAX_GAS_PER_TX] + const slot = '0x3d7fe2ee9790702383ef0118b516833ef2542132d3ca4ac6c77f62f1230fa610' + const value = await foreignWeb3.eth.getStorageAt(amb.foreign, slot) + const selector = homeWeb3.utils.soliditySha3('eth_getStorageAt(address,bytes32)') + + await makeAsyncCall(selector, homeWeb3.eth.abi.encodeParameters(['address', 'bytes32'], [amb.foreign, slot])) + + assert(await homeBox.methods.status().call(), 'status is false') + const data = await homeBox.methods.data().call() + + assert.strictEqual(data, value, 'wrong storage value returned') + }) + + it('should make async eth_getStorageAt for specific block', async () => { + // slot for uintStorage[MAX_GAS_PER_TX] + const slot = '0x3d7fe2ee9790702383ef0118b516833ef2542132d3ca4ac6c77f62f1230fa610' + const value = await foreignWeb3.eth.getStorageAt(amb.foreign, slot) + const blockNumber = await foreignWeb3.eth.getBlockNumber() + const selector = homeWeb3.utils.soliditySha3('eth_getStorageAt(address,bytes32,uint256)') + + const data1 = homeWeb3.eth.abi.encodeParameters( + ['address', 'bytes32', 'uint256'], + [amb.foreign, slot, blockNumber] + ) + const data2 = homeWeb3.eth.abi.encodeParameters(['address', 'bytes32', 'uint256'], [amb.foreign, slot, 1]) + + await makeAsyncCall(selector, data1) + assert(await homeBox.methods.status().call(), 'status is false') + let data = await homeBox.methods.data().call() + + assert.strictEqual(data, value, 'wrong storage value returned') + + await makeAsyncCall(selector, data2) + assert(await homeBox.methods.status().call(), 'status is false') + data = await homeBox.methods.data().call() + + assert.strictEqual( + data, + '0x0000000000000000000000000000000000000000000000000000000000000000', + 'wrong storage value returned' + ) + }) + }) }) diff --git a/oracle/.eslintrc b/oracle/.eslintrc index fe78d205..ed9361b1 100644 --- a/oracle/.eslintrc +++ b/oracle/.eslintrc @@ -6,6 +6,7 @@ ], "plugins": ["node"], "rules": { - "node/no-unpublished-require": "off" + "node/no-unpublished-require": "off", + "global-require": "off" } } diff --git a/oracle/config/affirmation-request-watcher.config.js b/oracle/config/affirmation-request-watcher.config.js index 99dbbc07..1a0da400 100644 --- a/oracle/config/affirmation-request-watcher.config.js +++ b/oracle/config/affirmation-request-watcher.config.js @@ -3,8 +3,8 @@ const baseConfig = require('./base.config') const id = `${baseConfig.id}-affirmation-request` module.exports = { - ...baseConfig.bridgeConfig, - ...baseConfig.foreignConfig, + ...baseConfig, + main: baseConfig.foreign, event: 'UserRequestForAffirmation', queue: 'home-prioritized', name: `watcher-${id}`, diff --git a/oracle/config/base.config.js b/oracle/config/base.config.js index ed357e84..54748622 100644 --- a/oracle/config/base.config.js +++ b/oracle/config/base.config.js @@ -1,6 +1,5 @@ require('../env') -const { toBN } = require('web3').utils const { BRIDGE_MODES, HOME_ERC_TO_NATIVE_ABI, @@ -11,13 +10,26 @@ const { const { web3Home, web3Foreign } = require('../src/services/web3') const { privateKeyToAddress } = require('../src/utils/utils') -const { ORACLE_VALIDATOR_ADDRESS, ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env +const { + ORACLE_BRIDGE_MODE, + ORACLE_VALIDATOR_ADDRESS, + ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, + ORACLE_MAX_PROCESSING_TIME, + COMMON_HOME_BRIDGE_ADDRESS, + COMMON_FOREIGN_BRIDGE_ADDRESS, + ORACLE_HOME_RPC_POLLING_INTERVAL, + ORACLE_FOREIGN_RPC_POLLING_INTERVAL, + ORACLE_HOME_START_BLOCK, + ORACLE_FOREIGN_START_BLOCK, + ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, + ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT +} = process.env let homeAbi let foreignAbi let id -switch (process.env.ORACLE_BRIDGE_MODE) { +switch (ORACLE_BRIDGE_MODE) { case BRIDGE_MODES.ERC_TO_NATIVE: homeAbi = HOME_ERC_TO_NATIVE_ABI foreignAbi = FOREIGN_ERC_TO_NATIVE_ABI @@ -30,7 +42,7 @@ switch (process.env.ORACLE_BRIDGE_MODE) { break default: if (process.env.NODE_ENV !== 'test') { - throw new Error(`Bridge Mode: ${process.env.ORACLE_BRIDGE_MODE} not supported.`) + throw new Error(`Bridge Mode: ${ORACLE_BRIDGE_MODE} not supported.`) } else { homeAbi = HOME_ERC_TO_NATIVE_ABI foreignAbi = FOREIGN_ERC_TO_NATIVE_ABI @@ -38,56 +50,41 @@ switch (process.env.ORACLE_BRIDGE_MODE) { } } -let maxProcessingTime = null -if (String(process.env.ORACLE_MAX_PROCESSING_TIME) === '0') { - maxProcessingTime = 0 -} else if (!process.env.ORACLE_MAX_PROCESSING_TIME) { - maxProcessingTime = - 4 * Math.max(process.env.ORACLE_HOME_RPC_POLLING_INTERVAL, process.env.ORACLE_FOREIGN_RPC_POLLING_INTERVAL) -} else { - maxProcessingTime = Number(process.env.ORACLE_MAX_PROCESSING_TIME) +const homeContract = new web3Home.eth.Contract(homeAbi, COMMON_HOME_BRIDGE_ADDRESS) +const homeConfig = { + chain: 'home', + bridgeAddress: COMMON_HOME_BRIDGE_ADDRESS, + bridgeABI: homeAbi, + pollingInterval: parseInt(ORACLE_HOME_RPC_POLLING_INTERVAL, 10), + startBlock: parseInt(ORACLE_HOME_START_BLOCK, 10) || 0, + blockPollingLimit: parseInt(ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, 10), + web3: web3Home, + bridgeContract: homeContract, + eventContract: homeContract } -const bridgeConfig = { - homeBridgeAddress: process.env.COMMON_HOME_BRIDGE_ADDRESS, - homeBridgeAbi: homeAbi, - foreignBridgeAddress: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS, - foreignBridgeAbi: foreignAbi, +const foreignContract = new web3Foreign.eth.Contract(foreignAbi, COMMON_FOREIGN_BRIDGE_ADDRESS) +const foreignConfig = { + chain: 'foreign', + bridgeAddress: COMMON_FOREIGN_BRIDGE_ADDRESS, + bridgeABI: foreignAbi, + pollingInterval: parseInt(ORACLE_FOREIGN_RPC_POLLING_INTERVAL, 10), + startBlock: parseInt(ORACLE_FOREIGN_START_BLOCK, 10) || 0, + blockPollingLimit: parseInt(ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT, 10), + web3: web3Foreign, + bridgeContract: foreignContract, + eventContract: foreignContract +} + +const maxProcessingTime = + parseInt(ORACLE_MAX_PROCESSING_TIME, 10) || 4 * Math.max(homeConfig.pollingInterval, foreignConfig.pollingInterval) + +module.exports = { eventFilter: {}, validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY), maxProcessingTime, - shutdownKey: 'oracle-shutdown' -} - -const toBNOrNull = x => (x ? toBN(x) : null) - -const homeConfig = { - chain: 'home', - eventContractAddress: process.env.COMMON_HOME_BRIDGE_ADDRESS, - eventAbi: homeAbi, - bridgeContractAddress: process.env.COMMON_HOME_BRIDGE_ADDRESS, - bridgeAbi: homeAbi, - pollingInterval: process.env.ORACLE_HOME_RPC_POLLING_INTERVAL, - startBlock: toBN(process.env.ORACLE_HOME_START_BLOCK || 0), - blockPollingLimit: toBNOrNull(process.env.ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT), - web3: web3Home -} - -const foreignConfig = { - chain: 'foreign', - eventContractAddress: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS, - eventAbi: foreignAbi, - bridgeContractAddress: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS, - bridgeAbi: foreignAbi, - pollingInterval: process.env.ORACLE_FOREIGN_RPC_POLLING_INTERVAL, - startBlock: toBN(process.env.ORACLE_FOREIGN_START_BLOCK || 0), - blockPollingLimit: toBNOrNull(process.env.ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT), - web3: web3Foreign -} - -module.exports = { - bridgeConfig, - homeConfig, - foreignConfig, + shutdownKey: 'oracle-shutdown', + home: homeConfig, + foreign: foreignConfig, id } diff --git a/oracle/config/collected-signatures-watcher.config.js b/oracle/config/collected-signatures-watcher.config.js index 9340d13d..1747bef3 100644 --- a/oracle/config/collected-signatures-watcher.config.js +++ b/oracle/config/collected-signatures-watcher.config.js @@ -3,8 +3,8 @@ const baseConfig = require('./base.config') const id = `${baseConfig.id}-collected-signatures` module.exports = { - ...baseConfig.bridgeConfig, - ...baseConfig.homeConfig, + ...baseConfig, + main: baseConfig.home, event: 'CollectedSignatures', queue: 'foreign-prioritized', name: `watcher-${id}`, diff --git a/oracle/config/foreign-sender.config.js b/oracle/config/foreign-sender.config.js index 97b1a154..990ac78d 100644 --- a/oracle/config/foreign-sender.config.js +++ b/oracle/config/foreign-sender.config.js @@ -6,7 +6,7 @@ const { web3Foreign, web3ForeignRedundant, web3ForeignFallback } = require('../s const { ORACLE_FOREIGN_TX_RESEND_INTERVAL } = process.env module.exports = { - ...baseConfig.bridgeConfig, + ...baseConfig, queue: 'foreign-prioritized', oldQueue: 'foreign', id: 'foreign', diff --git a/oracle/config/home-sender.config.js b/oracle/config/home-sender.config.js index d442c03d..0220efd1 100644 --- a/oracle/config/home-sender.config.js +++ b/oracle/config/home-sender.config.js @@ -6,7 +6,7 @@ const { web3Home, web3HomeRedundant, web3HomeFallback } = require('../src/servic const { ORACLE_HOME_TX_RESEND_INTERVAL } = process.env module.exports = { - ...baseConfig.bridgeConfig, + ...baseConfig, queue: 'home-prioritized', oldQueue: 'home', id: 'home', diff --git a/oracle/config/information-request-watcher.config.js b/oracle/config/information-request-watcher.config.js new file mode 100644 index 00000000..0431603f --- /dev/null +++ b/oracle/config/information-request-watcher.config.js @@ -0,0 +1,14 @@ +const baseConfig = require('./base.config') +const { web3ForeignArchive } = require('../src/services/web3') + +const id = `${baseConfig.id}-information-request` + +module.exports = { + ...baseConfig, + web3ForeignArchive: web3ForeignArchive || baseConfig.foreign.web3, + main: baseConfig.home, + event: 'UserRequestForInformation', + queue: 'home-prioritized', + name: `watcher-${id}`, + id +} diff --git a/oracle/config/shutdown-manager.config.js b/oracle/config/shutdown-manager.config.js index cbbe3747..4927094b 100644 --- a/oracle/config/shutdown-manager.config.js +++ b/oracle/config/shutdown-manager.config.js @@ -8,7 +8,7 @@ const { } = process.env module.exports = { - ...baseConfig.bridgeConfig, + ...baseConfig, id: 'shutdown-manager', name: 'shutdown-manager', pollingInterval: ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL || 120000, @@ -16,5 +16,6 @@ module.exports = { checksBeforeStop: 1, shutdownServiceURL: ORACLE_SHUTDOWN_SERVICE_URL, shutdownContractAddress: ORACLE_SHUTDOWN_CONTRACT_ADDRESS, - shutdownMethod: (ORACLE_SHUTDOWN_CONTRACT_METHOD || 'isShutdown()').trim() + shutdownMethod: (ORACLE_SHUTDOWN_CONTRACT_METHOD || 'isShutdown()').trim(), + requestTimeout: 2000 } diff --git a/oracle/config/signature-request-watcher.config.js b/oracle/config/signature-request-watcher.config.js index 60d9a805..58bc832f 100644 --- a/oracle/config/signature-request-watcher.config.js +++ b/oracle/config/signature-request-watcher.config.js @@ -3,8 +3,8 @@ const baseConfig = require('./base.config') const id = `${baseConfig.id}-signature-request` module.exports = { - ...baseConfig.bridgeConfig, - ...baseConfig.homeConfig, + ...baseConfig, + main: baseConfig.home, event: 'UserRequestForSignature', queue: 'home-prioritized', name: `watcher-${id}`, diff --git a/oracle/config/transfer-watcher.config.js b/oracle/config/transfer-watcher.config.js index 23a36779..94891c21 100644 --- a/oracle/config/transfer-watcher.config.js +++ b/oracle/config/transfer-watcher.config.js @@ -23,11 +23,12 @@ if (baseConfig.id !== 'erc-native') { } module.exports = { - ...baseConfig.bridgeConfig, - ...baseConfig.foreignConfig, + ...baseConfig, + main: { + ...baseConfig.foreign, + eventContract: new baseConfig.foreign.web3.eth.Contract(ERC20_ABI, initialChecks.bridgeableTokenAddress) + }, event: 'Transfer', - eventContractAddress: initialChecks.bridgeableTokenAddress, - eventAbi: ERC20_ABI, eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS }, queue: 'home-prioritized', name: `watcher-${id}`, diff --git a/oracle/docker-compose-amb.yml b/oracle/docker-compose-amb.yml new file mode 100644 index 00000000..46760f8b --- /dev/null +++ b/oracle/docker-compose-amb.yml @@ -0,0 +1,97 @@ +--- +version: '2.4' +services: + rabbit: + extends: + file: docker-compose.yml + service: rabbit + networks: + - net_rabbit_bridge_information + redis: + extends: + file: docker-compose.yml + service: redis + networks: + - net_db_bridge_information + bridge_request: + extends: + file: docker-compose.yml + service: bridge_request + networks: + - net_db_bridge_request + - net_rabbit_bridge_request + bridge_collected: + extends: + file: docker-compose.yml + service: bridge_collected + networks: + - net_db_bridge_request + - net_rabbit_bridge_request + bridge_affirmation: + extends: + file: docker-compose.yml + service: bridge_affirmation + networks: + - net_db_bridge_request + - net_rabbit_bridge_request + bridge_information: + cpus: 0.1 + mem_limit: 500m + image: poanetwork/tokenbridge-oracle:latest + env_file: ./.env + environment: + - NODE_ENV=production + - ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS} + restart: unless-stopped + entrypoint: yarn watcher:information-request + networks: + - net_db_bridge_information + - net_rabbit_bridge_information + bridge_senderhome: + extends: + file: docker-compose.yml + service: bridge_senderhome + networks: + - net_db_bridge_request + - net_rabbit_bridge_request + bridge_senderforeign: + extends: + file: docker-compose.yml + service: bridge_senderforeign + networks: + - net_db_bridge_request + - net_rabbit_bridge_request + bridge_shutdown: + extends: + file: docker-compose.yml + service: bridge_shutdown + networks: + - net_db_bridge_shutdown + +networks: + net_db_bridge_request: + driver: bridge + net_db_bridge_collected: + driver: bridge + net_db_bridge_affirmation: + driver: bridge + net_db_bridge_information: + driver: bridge + net_db_bridge_senderhome: + driver: bridge + net_db_bridge_senderforeign: + driver: bridge + net_rabbit_bridge_request: + driver: bridge + net_db_bridge_shutdown: + driver: bridge + net_rabbit_bridge_collected: + driver: bridge + net_rabbit_bridge_affirmation: + driver: bridge + net_rabbit_bridge_information: + driver: bridge + net_rabbit_bridge_senderhome: + driver: bridge + net_rabbit_bridge_senderforeign: + driver: bridge diff --git a/oracle/package.json b/oracle/package.json index 0a8d575e..7f8eb359 100644 --- a/oracle/package.json +++ b/oracle/package.json @@ -8,6 +8,7 @@ "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", + "watcher:information-request": "./scripts/start-worker.sh watcher information-request-watcher", "watcher:transfer": "./scripts/start-worker.sh watcher transfer-watcher", "sender:home": "./scripts/start-worker.sh sender home-sender", "sender:foreign": "./scripts/start-worker.sh sender foreign-sender", diff --git a/oracle/scripts/getValidatorStartBlocks.js b/oracle/scripts/getValidatorStartBlocks.js index 4276e524..4070188d 100644 --- a/oracle/scripts/getValidatorStartBlocks.js +++ b/oracle/scripts/getValidatorStartBlocks.js @@ -1,16 +1,10 @@ require('../env') const { BRIDGE_VALIDATORS_ABI } = require('../../commons') -const { web3Home, web3Foreign } = require('../src/services/web3') -const { bridgeConfig } = require('../config/base.config') +const { home, foreign } = require('../config/base.config') -const homeABI = bridgeConfig.homeBridgeAbi -const foreignABI = bridgeConfig.foreignBridgeAbi - -async function getStartBlock(web3, bridgeAddress, bridgeAbi) { +async function getStartBlock(bridgeContract, web3) { try { - const bridgeContract = new web3.eth.Contract(bridgeAbi, bridgeAddress) - const deployedAtBlock = await bridgeContract.methods.deployedAtBlock().call() const validatorContractAddress = await bridgeContract.methods.validatorContract().call() @@ -30,10 +24,8 @@ async function getStartBlock(web3, bridgeAddress, bridgeAbi) { } async function main() { - const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env - - const homeStartBlock = await getStartBlock(web3Home, COMMON_HOME_BRIDGE_ADDRESS, homeABI) - const foreignStartBlock = await getStartBlock(web3Foreign, COMMON_FOREIGN_BRIDGE_ADDRESS, foreignABI) + const homeStartBlock = await getStartBlock(home.bridgeContract, home.web3) + const foreignStartBlock = await getStartBlock(foreign.bridgeContract, foreign.web3) const result = { homeStartBlock, foreignStartBlock diff --git a/oracle/src/confirmRelay.js b/oracle/src/confirmRelay.js index eef5f52d..0af48fda 100644 --- a/oracle/src/confirmRelay.js +++ b/oracle/src/confirmRelay.js @@ -25,10 +25,9 @@ const processTransfers = require('./events/processTransfers')(config) const processAMBSignatureRequests = require('./events/processAMBSignatureRequests')(config) const processAMBCollectedSignatures = require('./events/processAMBCollectedSignatures')(config) const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config) +const processAMBInformationRequests = require('./events/processAMBInformationRequests')(config) -const web3Instance = config.web3 -const { eventContractAddress } = config -const eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress) +const { web3, eventContract } = config.main let attached @@ -36,7 +35,7 @@ async function initialize() { try { const checkHttps = checkHTTPS(ORACLE_ALLOW_HTTP_FOR_RPC, logger) - web3Instance.currentProvider.urls.forEach(checkHttps(config.chain)) + web3.currentProvider.urls.forEach(checkHttps(config.chain)) attached = await isAttached() if (attached) { @@ -93,6 +92,8 @@ function processEvents(events) { return processAMBCollectedSignatures(events) case 'amb-affirmation-request': return processAMBAffirmationRequests(events) + case 'amb-information-request': + return processAMBInformationRequests(events) default: return [] } @@ -101,7 +102,7 @@ function processEvents(events) { async function main({ sendJob, txHash }) { try { const events = await getEventsFromTx({ - web3: web3Instance, + web3, contract: eventContract, event: config.event, txHash, @@ -128,8 +129,8 @@ async function main({ sendJob, txHash }) { async function sendJobTx(jobs) { const gasPrice = await GasPrice.start(config.chain, true) - const chainId = await getChainId(web3Instance) - let nonce = await getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS) + const chainId = await getChainId(web3) + let nonce = await getNonce(web3, ORACLE_VALIDATOR_ADDRESS) await syncForEach(jobs, async job => { let gasLimit @@ -150,7 +151,7 @@ async function sendJobTx(jobs) { privateKey: ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, to: job.to, chainId, - web3: web3Instance + web3 }) nonce++ @@ -166,7 +167,7 @@ async function sendJobTx(jobs) { ) if (e.message.toLowerCase().includes('insufficient funds')) { - const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS) + const currentBalance = await web3.eth.getBalance(ORACLE_VALIDATOR_ADDRESS) const minimumBalance = gasLimit.multipliedBy(gasPrice) logger.error( `Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.` diff --git a/oracle/src/events/processAMBAffirmationRequests/index.js b/oracle/src/events/processAMBAffirmationRequests/index.js index 94c91c25..bf39cc4d 100644 --- a/oracle/src/events/processAMBAffirmationRequests/index.js +++ b/oracle/src/events/processAMBAffirmationRequests/index.js @@ -2,8 +2,7 @@ require('dotenv').config() const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('../../services/HttpListProvider') const rootLogger = require('../../services/logger') -const { web3Home } = require('../../services/web3') -const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi +const { getValidatorContract } = require('../../tx/web3') const { EXIT_CODES, MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants') const estimateGas = require('./estimateGas') const { parseAMBMessage } = require('../../../../commons') @@ -11,20 +10,16 @@ const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = req const limit = promiseLimit(MAX_CONCURRENT_EVENTS) -let validatorContract = null - function processAffirmationRequestsBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + const { bridgeContract, web3 } = config.home + + let validatorContract = null return async function processAffirmationRequests(affirmationRequests) { const txToSend = [] if (validatorContract === null) { - rootLogger.debug('Getting validator contract address') - const validatorContractAddress = await homeBridge.methods.validatorContract().call() - rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained') - - validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + validatorContract = await getValidatorContract(bridgeContract, web3) } rootLogger.debug(`Processing ${affirmationRequests.length} AffirmationRequest events`) @@ -45,8 +40,8 @@ function processAffirmationRequestsBuilder(config) { try { logger.debug('Estimate gas') gasEstimate = await estimateGas({ - web3: web3Home, - homeBridge, + web3, + homeBridge: bridgeContract, validatorContract, message, address: config.validatorAddress @@ -70,14 +65,13 @@ function processAffirmationRequestsBuilder(config) { } } - const data = await homeBridge.methods.executeAffirmation(message).encodeABI() - + const data = bridgeContract.methods.executeAffirmation(message).encodeABI() txToSend.push({ data, gasEstimate, extraGas: EXTRA_GAS_ABSOLUTE, transactionReference: affirmationRequest.transactionHash, - to: config.homeBridgeAddress + to: config.home.bridgeAddress }) }) .map(promise => limit(promise)) diff --git a/oracle/src/events/processAMBCollectedSignatures/index.js b/oracle/src/events/processAMBCollectedSignatures/index.js index bc719f25..df0439f4 100644 --- a/oracle/src/events/processAMBCollectedSignatures/index.js +++ b/oracle/src/events/processAMBCollectedSignatures/index.js @@ -1,9 +1,8 @@ require('dotenv').config() const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('../../services/HttpListProvider') -const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi +const { getValidatorContract } = require('../../tx/web3') const rootLogger = require('../../services/logger') -const { web3Home, web3Foreign } = require('../../services/web3') const { signatureToVRS, packSignatures } = require('../../utils/message') const { readAccessListFile } = require('../../utils/utils') const { parseAMBMessage } = require('../../../../commons') @@ -19,22 +18,16 @@ const { ORACLE_ALWAYS_RELAY_SIGNATURES } = process.env -let validatorContract = null - function processCollectedSignaturesBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + const { home, foreign } = config - const foreignBridge = new web3Foreign.eth.Contract(config.foreignBridgeAbi, config.foreignBridgeAddress) + let validatorContract = null return async function processCollectedSignatures(signatures) { const txToSend = [] if (validatorContract === null) { - rootLogger.debug('Getting validator contract address') - const validatorContractAddress = await foreignBridge.methods.validatorContract().call() - rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained') - - validatorContract = new web3Foreign.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + validatorContract = await getValidatorContract(foreign.bridgeContract, foreign.web3) } rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`) @@ -48,13 +41,13 @@ function processCollectedSignaturesBuilder(config) { if (ORACLE_ALWAYS_RELAY_SIGNATURES && ORACLE_ALWAYS_RELAY_SIGNATURES === 'true') { logger.debug('Validator handles all CollectedSignature requests') - } else if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) { + } else if (authorityResponsibleForRelay !== home.web3.utils.toChecksumAddress(config.validatorAddress)) { logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`) return } logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`) - const message = await homeBridge.methods.message(messageHash).call() + const message = await home.bridgeContract.methods.message(messageHash).call() const parsedMessage = parseAMBMessage(message) if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST || ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) { @@ -102,7 +95,7 @@ function processCollectedSignaturesBuilder(config) { logger.debug('Getting message signatures') const signaturePromises = requiredSignatures.map(async (el, index) => { logger.debug({ index }, 'Getting message signature') - const signature = await homeBridge.methods.signature(messageHash, index).call() + const signature = await home.bridgeContract.methods.signature(messageHash, index).call() const vrs = signatureToVRS(signature) v.push(vrs.v) r.push(vrs.r) @@ -120,7 +113,7 @@ function processCollectedSignaturesBuilder(config) { try { logger.debug('Estimate gas') gasEstimate = await estimateGas({ - foreignBridge, + foreignBridge: foreign.bridgeContract, validatorContract, v, r, @@ -146,14 +139,13 @@ function processCollectedSignaturesBuilder(config) { throw e } } - const data = await foreignBridge.methods.executeSignatures(message, signatures).encodeABI() - + const data = foreign.bridgeContract.methods.executeSignatures(message, signatures).encodeABI() txToSend.push({ data, gasEstimate, extraGas: EXTRA_GAS_ABSOLUTE, transactionReference: colSignature.transactionHash, - to: config.foreignBridgeAddress + to: config.foreign.bridgeAddress }) }) .map(promise => limit(promise)) diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethBlockNumber.js b/oracle/src/events/processAMBInformationRequests/calls/ethBlockNumber.js new file mode 100644 index 00000000..f1e3756d --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethBlockNumber.js @@ -0,0 +1,3 @@ +module.exports = { + 'eth_blockNumber()': async (web3, _, block) => [true, web3.eth.abi.encodeParameter('uint256', block.number)] +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethCall.js b/oracle/src/events/processAMBInformationRequests/calls/ethCall.js new file mode 100644 index 00000000..50c8d5e7 --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethCall.js @@ -0,0 +1,40 @@ +const { toBN } = require('web3').utils + +const { zipToObject } = require('../../../utils/utils') + +const argTypes = { + to: 'address', + from: 'address', + gas: 'uint256', + data: 'bytes', + blockNumber: 'uint256' +} + +function makeCall(argNames) { + return async function(web3, data, foreignBlock) { + const types = argNames.map(name => argTypes[name]) + const args = web3.eth.abi.decodeParameters(types, data) + const { blockNumber, ...opts } = zipToObject(argNames, args) + + if (blockNumber && toBN(blockNumber).gt(toBN(foreignBlock.number))) { + return [false, '0x'] + } + + const [status, result] = await web3.eth + .call(opts, blockNumber || foreignBlock.number) + .then(result => [true, result], err => [false, err.data]) + + return [status, web3.eth.abi.encodeParameter('bytes', result)] + } +} + +module.exports = { + 'eth_call(address,bytes)': makeCall(['to', 'data']), + 'eth_call(address,bytes,uint256)': makeCall(['to', 'data', 'blockNumber']), + 'eth_call(address,address,bytes)': makeCall(['to', 'from', 'data']), + 'eth_call(address,address,bytes,uint256)': makeCall(['to', 'from', 'data', 'blockNumber']), + 'eth_call(address,uint256,bytes)': makeCall(['to', 'gas', 'data']), + 'eth_call(address,uint256,bytes,uint256)': makeCall(['to', 'gas', 'data', 'blockNumber']), + 'eth_call(address,address,uint256,bytes)': makeCall(['to', 'from', 'gas', 'data']), + 'eth_call(address,address,uint256,bytes,uint256)': makeCall(['to', 'from', 'gas', 'data', 'blockNumber']) +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetBalance.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetBalance.js new file mode 100644 index 00000000..b301696c --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetBalance.js @@ -0,0 +1,26 @@ +const { toBN } = require('web3').utils + +async function call(web3, data, foreignBlock) { + const address = web3.eth.abi.decodeParameter('address', data) + + const balance = await web3.eth.getBalance(address, foreignBlock.number) + + return [true, web3.eth.abi.encodeParameter('uint256', balance)] +} + +async function callArchive(web3, data, foreignBlock) { + const { 0: address, 1: blockNumber } = web3.eth.abi.decodeParameters(['address', 'uint256'], data) + + if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { + return [false, '0x'] + } + + const balance = await web3.eth.getBalance(address, blockNumber) + + return [true, web3.eth.abi.encodeParameter('uint256', balance)] +} + +module.exports = { + 'eth_getBalance(address)': call, + 'eth_getBalance(address,uint256)': callArchive +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByHash.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByHash.js new file mode 100644 index 00000000..523e428c --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByHash.js @@ -0,0 +1,17 @@ +const { serializeBlock } = require('./serializers') + +async function call(web3, data, foreignBlock) { + const blockHash = web3.eth.abi.decodeParameter('bytes32', data) + + const block = await web3.eth.getBlock(blockHash) + + if (block === null || block.number > foreignBlock.number) { + return [false, '0x'] + } + + return [true, serializeBlock(web3, block)] +} + +module.exports = { + 'eth_getBlockByHash(bytes32)': call +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByNumber.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByNumber.js new file mode 100644 index 00000000..030e3333 --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByNumber.js @@ -0,0 +1,20 @@ +const { toBN } = require('web3').utils + +const { serializeBlock } = require('./serializers') + +async function call(web3, data, foreignBlock) { + const blockNumber = web3.eth.abi.decodeParameter('uint256', data) + + if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { + return [false, '0x'] + } + + const block = await web3.eth.getBlock(blockNumber) + + return [true, serializeBlock(web3, block)] +} + +module.exports = { + 'eth_getBlockByNumber()': async (web3, _, block) => [true, serializeBlock(web3, block)], + 'eth_getBlockByNumber(uint256)': call +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetStorageAt.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetStorageAt.js new file mode 100644 index 00000000..7f6c7ddc --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetStorageAt.js @@ -0,0 +1,26 @@ +const { toBN } = require('web3').utils + +async function call(web3, data, foreignBlock) { + const { 0: address, 1: slot } = web3.eth.abi.decodeParameters(['address', 'bytes32'], data) + + const value = await web3.eth.getStorageAt(address, slot, foreignBlock.number) + + return [true, web3.eth.abi.encodeParameter('bytes32', value)] +} + +async function callArchive(web3, data, foreignBlock) { + const { 0: address, 1: slot, 2: blockNumber } = web3.eth.abi.decodeParameters(['address', 'bytes32', 'uint256'], data) + + if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { + return [false, '0x'] + } + + const value = await web3.eth.getStorageAt(address, slot, blockNumber) + + return [true, web3.eth.abi.encodeParameter('bytes32', value)] +} + +module.exports = { + 'eth_getStorageAt(address,bytes32)': call, + 'eth_getStorageAt(address,bytes32,uint256)': callArchive +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionByHash.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionByHash.js new file mode 100644 index 00000000..039db023 --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionByHash.js @@ -0,0 +1,17 @@ +const { serializeTx } = require('./serializers') + +async function call(web3, data, foreignBlock) { + const hash = web3.eth.abi.decodeParameter('bytes32', data) + + const tx = await web3.eth.getTransaction(hash) + + if (tx === null || tx.blockNumber > foreignBlock.number) { + return [false, '0x'] + } + + return [true, serializeTx(web3, tx)] +} + +module.exports = { + 'eth_getTransactionByHash(bytes32)': call +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionCount.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionCount.js new file mode 100644 index 00000000..a20c8cac --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionCount.js @@ -0,0 +1,26 @@ +const { toBN } = require('web3').utils + +async function call(web3, data, foreignBlock) { + const address = web3.eth.abi.decodeParameter('address', data) + + const nonce = await web3.eth.getTransactionCount(address, foreignBlock.number) + + return [true, web3.eth.abi.encodeParameter('uint256', nonce)] +} + +async function callArchive(web3, data, foreignBlock) { + const { 0: address, 1: blockNumber } = web3.eth.abi.decodeParameters(['address', 'uint256'], data) + + if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { + return [false, '0x'] + } + + const nonce = await web3.eth.getTransactionCount(address, blockNumber) + + return [true, web3.eth.abi.encodeParameter('uint256', nonce)] +} + +module.exports = { + 'eth_getTransactionCount(address)': call, + 'eth_getTransactionCount(address,uint256)': callArchive +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionReceipt.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionReceipt.js new file mode 100644 index 00000000..c794e5d2 --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionReceipt.js @@ -0,0 +1,17 @@ +const { serializeReceipt } = require('./serializers') + +async function call(web3, data, foreignBlock) { + const hash = web3.eth.abi.decodeParameter('bytes32', data) + + const receipt = await web3.eth.getTransactionReceipt(hash) + + if (receipt === null || receipt.blockNumber > foreignBlock.number) { + return [false, '0x'] + } + + return [true, serializeReceipt(web3, receipt)] +} + +module.exports = { + 'eth_getTransactionReceipt(bytes32)': call +} diff --git a/oracle/src/events/processAMBInformationRequests/calls/serializers.js b/oracle/src/events/processAMBInformationRequests/calls/serializers.js new file mode 100644 index 00000000..22d701b5 --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/calls/serializers.js @@ -0,0 +1,37 @@ +const { ZERO_ADDRESS } = require('../../../../../commons') + +const serializeBlock = (web3, block) => { + const args = [block.number, block.hash, block.miner] + const types = ['uint256', 'bytes32', 'address'] + return web3.eth.abi.encodeParameters(types, args) +} + +const serializeTx = (web3, tx) => { + const args = [ + tx.hash, + tx.blockNumber, + tx.from, + tx.to || ZERO_ADDRESS, + tx.value, + tx.nonce, + tx.gas, + tx.gasPrice, + tx.input + ] + const types = ['bytes32', 'uint256', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'] + return web3.eth.abi.encodeParameters(types, args) +} + +const normalizeLog = log => [log.address, log.topics, log.data] + +const serializeReceipt = (web3, receipt) => { + const args = [receipt.transactionHash, receipt.blockNumber, receipt.status, receipt.logs.map(normalizeLog)] + const types = ['bytes32', 'uint256', 'bool', '(address,bytes32[],bytes)[]'] + return web3.eth.abi.encodeParameters(types, args) +} + +module.exports = { + serializeBlock, + serializeTx, + serializeReceipt +} diff --git a/oracle/src/events/processAMBInformationRequests/estimateGas.js b/oracle/src/events/processAMBInformationRequests/estimateGas.js new file mode 100644 index 00000000..d163fb16 --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/estimateGas.js @@ -0,0 +1,58 @@ +const { HttpListProviderError } = require('../../services/HttpListProvider') +const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') +const logger = require('../../services/logger').child({ + module: 'processInformationRequests:estimateGas' +}) +const { strip0x } = require('../../../../commons') +const { AMB_AFFIRMATION_REQUEST_EXTRA_GAS_ESTIMATOR: estimateExtraGas } = require('../../utils/constants') + +async function estimateGas({ web3, homeBridge, validatorContract, messageId, status, result, address }) { + try { + const gasEstimate = await homeBridge.methods.confirmInformation(messageId, status, result).estimateGas({ + from: address + }) + + // message length in bytes + const len = strip0x(result).length / 2 + + const callbackGasLimit = parseInt(await homeBridge.methods.maxGasPerTx().call(), 10) + + return gasEstimate + callbackGasLimit + estimateExtraGas(len) + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + const messageHash = web3.utils.soliditySha3(messageId, status, result) + const senderHash = web3.utils.soliditySha3(address, messageHash) + + // Check if minimum number of validations was already reached + logger.debug('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 + logger.debug('Check if the message was already signed') + const alreadySigned = await homeBridge.methods.affirmationsSigned(senderHash).call() + + if (alreadySigned) { + throw new AlreadySignedError(e.message) + } + + // Check if address is validator + logger.debug('Check if address is a 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/oracle/src/events/processAMBInformationRequests/index.js b/oracle/src/events/processAMBInformationRequests/index.js new file mode 100644 index 00000000..5b433c9d --- /dev/null +++ b/oracle/src/events/processAMBInformationRequests/index.js @@ -0,0 +1,137 @@ +require('dotenv').config() +const promiseLimit = require('promise-limit') +const { soliditySha3 } = require('web3').utils +const { HttpListProviderError } = require('../../services/HttpListProvider') +const rootLogger = require('../../services/logger') +const makeBlockFinder = require('../../services/blockFinder') +const { EXIT_CODES, MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants') +const estimateGas = require('./estimateGas') +const { getValidatorContract, getBlock, getBlockNumber, getRequiredBlockConfirmations } = require('../../tx/web3') +const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') + +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + +const asyncCalls = { + ...require('./calls/ethCall'), + ...require('./calls/ethBlockNumber'), + ...require('./calls/ethGetBlockByNumber'), + ...require('./calls/ethGetBlockByHash'), + ...require('./calls/ethGetTransactionByHash'), + ...require('./calls/ethGetTransactionReceipt'), + ...require('./calls/ethGetBalance'), + ...require('./calls/ethGetTransactionCount'), + ...require('./calls/ethGetStorageAt') +} + +const asyncCallsSelectorsMapping = {} +Object.keys(asyncCalls).forEach(method => { + asyncCallsSelectorsMapping[soliditySha3(method)] = method +}) + +function processInformationRequestsBuilder(config) { + const { home, foreign, web3ForeignArchive } = config + + let validatorContract = null + let blockFinder = null + + return async function processInformationRequests(informationRequests) { + const txToSend = [] + + if (validatorContract === null) { + validatorContract = await getValidatorContract(home.bridgeContract, home.web3) + } + + if (blockFinder === null) { + rootLogger.debug('Initializing block finder') + blockFinder = await makeBlockFinder('foreign', foreign.web3) + } + + const foreignBlockNumber = + (await getBlockNumber(foreign.web3)) - (await getRequiredBlockConfirmations(foreign.bridgeContract)) + const homeBlock = await getBlock(home.web3, informationRequests[0].blockNumber) + const lastForeignBlock = await getBlock(foreign.web3, foreignBlockNumber) + + if (homeBlock.timestamp > lastForeignBlock.timestamp) { + rootLogger.debug( + { homeTimestamp: homeBlock.timestamp, foreignTimestamp: lastForeignBlock.timestamp }, + `Waiting for the closest foreign block to be confirmed` + ) + return null + } + const foreignClosestBlock = await blockFinder(homeBlock.timestamp, lastForeignBlock) + + rootLogger.debug(`Processing ${informationRequests.length} UserRequestForInformation events`) + const callbacks = informationRequests + .map(informationRequest => async () => { + const { messageId, requestSelector, data } = informationRequest.returnValues + + const logger = rootLogger.child({ + eventTransactionHash: informationRequest.transactionHash, + eventMessageId: messageId + }) + + const asyncCallMethod = asyncCallsSelectorsMapping[requestSelector] + + if (!asyncCallMethod) { + logger.warn({ requestSelector }, 'Unknown async request selector received') + return + } + logger.info({ requestSelector, method: asyncCallMethod, data }, 'Processing async request') + + const call = asyncCalls[asyncCallMethod] + const [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => { + if (e instanceof HttpListProviderError) { + throw e + } + return [false, '0x'] + }) + logger.info({ requestSelector, method: asyncCallMethod, status, result }, 'Request result obtained') + + let gasEstimate + try { + logger.debug('Estimate gas') + gasEstimate = await estimateGas({ + web3: home.web3, + homeBridge: home.bridgeContract, + validatorContract, + messageId, + status, + result, + address: config.validatorAddress + }) + logger.debug({ gasEstimate }, 'Gas estimated') + } catch (e) { + if (e instanceof HttpListProviderError) { + throw new Error('RPC Connection Error: confirmInformation Gas Estimate cannot be obtained.') + } else if (e instanceof InvalidValidatorError) { + logger.fatal({ address: config.validatorAddress }, 'Invalid validator') + process.exit(EXIT_CODES.INCOMPATIBILITY) + } else if (e instanceof AlreadySignedError) { + logger.info(`Already signed informationRequest ${messageId}`) + return + } else if (e instanceof AlreadyProcessedError) { + logger.info(`informationRequest ${messageId} was already processed by other validators`) + return + } else { + logger.error(e, 'Unknown error while processing transaction') + throw e + } + } + + const confirmationData = home.bridgeContract.methods.confirmInformation(messageId, status, result).encodeABI() + txToSend.push({ + data: confirmationData, + gasEstimate, + extraGas: EXTRA_GAS_ABSOLUTE, + transactionReference: informationRequest.transactionHash, + to: config.home.bridgeAddress + }) + }) + .map(promise => limit(promise)) + + await Promise.all(callbacks) + return txToSend + } +} + +module.exports = processInformationRequestsBuilder diff --git a/oracle/src/events/processAMBSignatureRequests/index.js b/oracle/src/events/processAMBSignatureRequests/index.js index 0840768d..3cf87907 100644 --- a/oracle/src/events/processAMBSignatureRequests/index.js +++ b/oracle/src/events/processAMBSignatureRequests/index.js @@ -1,9 +1,8 @@ require('dotenv').config() const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('../../services/HttpListProvider') -const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi const rootLogger = require('../../services/logger') -const { web3Home } = require('../../services/web3') +const { getValidatorContract } = require('../../tx/web3') const { parseAMBMessage } = require('../../../../commons') const estimateGas = require('../processSignatureRequests/estimateGas') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') @@ -13,20 +12,16 @@ const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const limit = promiseLimit(MAX_CONCURRENT_EVENTS) -let validatorContract = null - function processSignatureRequestsBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + const { bridgeContract, web3 } = config.home + + let validatorContract = null return async function processSignatureRequests(signatureRequests) { const txToSend = [] if (validatorContract === null) { - rootLogger.debug('Getting validator contract address') - const validatorContractAddress = await homeBridge.methods.validatorContract().call() - rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained') - - validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + validatorContract = await getValidatorContract(bridgeContract, web3) } rootLogger.debug(`Processing ${signatureRequests.length} SignatureRequest events`) @@ -42,14 +37,14 @@ function processSignatureRequestsBuilder(config) { const { sender, executor } = parseAMBMessage(message) logger.info({ sender, executor }, `Processing signatureRequest ${messageId}`) - const signature = web3Home.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`) + const signature = web3.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`) let gasEstimate try { logger.debug('Estimate gas') gasEstimate = await estimateGas({ - web3: web3Home, - homeBridge, + web3, + homeBridge: bridgeContract, validatorContract, signature: signature.signature, message, @@ -74,13 +69,12 @@ function processSignatureRequestsBuilder(config) { } } - const data = await homeBridge.methods.submitSignature(signature.signature, message).encodeABI() - + const data = bridgeContract.methods.submitSignature(signature.signature, message).encodeABI() txToSend.push({ data, gasEstimate, transactionReference: signatureRequest.transactionHash, - to: config.homeBridgeAddress + to: config.home.bridgeAddress }) }) .map(promise => limit(promise)) diff --git a/oracle/src/events/processAffirmationRequests/index.js b/oracle/src/events/processAffirmationRequests/index.js index 5f89d631..694cf28b 100644 --- a/oracle/src/events/processAffirmationRequests/index.js +++ b/oracle/src/events/processAffirmationRequests/index.js @@ -2,29 +2,23 @@ require('../../../env') const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('../../services/HttpListProvider') const rootLogger = require('../../services/logger') -const { web3Home } = require('../../services/web3') - -const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons') +const { getValidatorContract } = require('../../tx/web3') const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const estimateGas = require('./estimateGas') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') const limit = promiseLimit(MAX_CONCURRENT_EVENTS) -let validatorContract = null - function processAffirmationRequestsBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + const { bridgeContract, web3 } = config.home + + let validatorContract = null return async function processAffirmationRequests(affirmationRequests) { const txToSend = [] if (validatorContract === null) { - rootLogger.debug('Getting validator contract address') - const validatorContractAddress = await homeBridge.methods.validatorContract().call() - rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained') - - validatorContract = new web3Home.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress) + validatorContract = await getValidatorContract(bridgeContract, web3) } rootLogger.debug(`Processing ${affirmationRequests.length} AffirmationRequest events`) @@ -42,8 +36,8 @@ function processAffirmationRequestsBuilder(config) { try { logger.debug('Estimate gas') gasEstimate = await estimateGas({ - web3: web3Home, - homeBridge, + web3, + homeBridge: bridgeContract, validatorContract, recipient, value, @@ -71,15 +65,14 @@ function processAffirmationRequestsBuilder(config) { } } - const data = await homeBridge.methods + const data = bridgeContract.methods .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .encodeABI({ from: config.validatorAddress }) - + .encodeABI() txToSend.push({ data, gasEstimate, transactionReference: affirmationRequest.transactionHash, - to: config.homeBridgeAddress + to: config.home.bridgeAddress }) }) .map(promise => limit(promise)) diff --git a/oracle/src/events/processCollectedSignatures/index.js b/oracle/src/events/processCollectedSignatures/index.js index 75ecc9de..2127897f 100644 --- a/oracle/src/events/processCollectedSignatures/index.js +++ b/oracle/src/events/processCollectedSignatures/index.js @@ -1,9 +1,8 @@ require('../../../env') const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('../../services/HttpListProvider') -const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons') const rootLogger = require('../../services/logger') -const { web3Home, web3Foreign } = require('../../services/web3') +const { getValidatorContract } = require('../../tx/web3') const { signatureToVRS, packSignatures, parseMessage } = require('../../utils/message') const { readAccessListFile } = require('../../utils/utils') const estimateGas = require('./estimateGas') @@ -19,22 +18,16 @@ const { const limit = promiseLimit(MAX_CONCURRENT_EVENTS) -let validatorContract = null - function processCollectedSignaturesBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + const { home, foreign } = config - const foreignBridge = new web3Foreign.eth.Contract(config.foreignBridgeAbi, config.foreignBridgeAddress) + let validatorContract = null return async function processCollectedSignatures(signatures) { const txToSend = [] if (validatorContract === null) { - rootLogger.debug('Getting validator contract address') - const validatorContractAddress = await foreignBridge.methods.validatorContract().call() - rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained') - - validatorContract = new web3Foreign.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress) + validatorContract = await getValidatorContract(foreign.bridgeContract, foreign.web3) } rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`) @@ -48,13 +41,13 @@ function processCollectedSignaturesBuilder(config) { if (ORACLE_ALWAYS_RELAY_SIGNATURES && ORACLE_ALWAYS_RELAY_SIGNATURES === 'true') { logger.debug('Validator handles all CollectedSignature requests') - } else if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) { + } else if (authorityResponsibleForRelay !== home.web3.utils.toChecksumAddress(config.validatorAddress)) { logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`) return } logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`) - const message = await homeBridge.methods.message(messageHash).call() + const message = await home.bridgeContract.methods.message(messageHash).call() if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST || ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) { const parsedMessage = parseMessage(message) @@ -66,7 +59,7 @@ function processCollectedSignaturesBuilder(config) { if (allowanceList.indexOf(recipient) === -1) { if (ORACLE_HOME_TO_FOREIGN_CHECK_SENDER === 'true') { logger.debug({ txHash: originalTxHash }, 'Requested sender of an original withdrawal transaction') - const sender = (await web3Home.eth.getTransaction(originalTxHash)).from.toLowerCase() + const sender = (await home.web3.eth.getTransaction(originalTxHash)).from.toLowerCase() if (allowanceList.indexOf(sender) === -1) { logger.info( { sender, recipient }, @@ -90,7 +83,7 @@ function processCollectedSignaturesBuilder(config) { } if (ORACLE_HOME_TO_FOREIGN_CHECK_SENDER === 'true') { logger.debug({ txHash: originalTxHash }, 'Requested sender of an original withdrawal transaction') - const sender = (await web3Home.eth.getTransaction(originalTxHash)).from.toLowerCase() + const sender = (await home.bridgeContract.eth.getTransaction(originalTxHash)).from.toLowerCase() if (blockList.indexOf(sender) > -1) { logger.info({ sender }, 'Validator skips a transaction. Sender address is in the block list.') return @@ -110,7 +103,7 @@ function processCollectedSignaturesBuilder(config) { logger.debug('Getting message signatures') const signaturePromises = requiredSignatures.map(async (el, index) => { logger.debug({ index }, 'Getting message signature') - const signature = await homeBridge.methods.signature(messageHash, index).call() + const signature = await home.bridgeContract.methods.signature(messageHash, index).call() const vrs = signatureToVRS(signature) v.push(vrs.v) r.push(vrs.r) @@ -125,7 +118,7 @@ function processCollectedSignaturesBuilder(config) { try { logger.debug('Estimate gas') gasEstimate = await estimateGas({ - foreignBridge, + foreignBridge: foreign.bridgeContract, validatorContract, v, r, @@ -149,12 +142,12 @@ function processCollectedSignaturesBuilder(config) { throw e } } - const data = await foreignBridge.methods.executeSignatures(message, signatures).encodeABI() + const data = foreign.bridgeContract.methods.executeSignatures(message, signatures).encodeABI() txToSend.push({ data, gasEstimate, transactionReference: colSignature.transactionHash, - to: config.foreignBridgeAddress + to: config.foreign.bridgeAddress }) }) .map(promise => limit(promise)) diff --git a/oracle/src/events/processSignatureRequests/index.js b/oracle/src/events/processSignatureRequests/index.js index 81e955e7..00a6e908 100644 --- a/oracle/src/events/processSignatureRequests/index.js +++ b/oracle/src/events/processSignatureRequests/index.js @@ -1,9 +1,8 @@ require('../../../env') const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('../../services/HttpListProvider') -const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons') const rootLogger = require('../../services/logger') -const { web3Home } = require('../../services/web3') +const { getValidatorContract } = require('../../tx/web3') const { createMessage } = require('../../utils/message') const estimateGas = require('./estimateGas') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') @@ -13,25 +12,21 @@ const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const limit = promiseLimit(MAX_CONCURRENT_EVENTS) -let expectedMessageLength = null -let validatorContract = null - function processSignatureRequestsBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + const { bridgeContract, web3 } = config.home + + let expectedMessageLength = null + let validatorContract = null return async function processSignatureRequests(signatureRequests) { const txToSend = [] if (expectedMessageLength === null) { - expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() + expectedMessageLength = await bridgeContract.methods.requiredMessageLength().call() } if (validatorContract === null) { - rootLogger.debug('Getting validator contract address') - const validatorContractAddress = await homeBridge.methods.validatorContract().call() - rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained') - - validatorContract = new web3Home.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress) + validatorContract = await getValidatorContract(bridgeContract, web3) } rootLogger.debug(`Processing ${signatureRequests.length} SignatureRequest events`) @@ -49,18 +44,18 @@ function processSignatureRequestsBuilder(config) { recipient, value, transactionHash: signatureRequest.transactionHash, - bridgeAddress: config.foreignBridgeAddress, + bridgeAddress: config.foreign.bridgeAddress, expectedMessageLength }) - const signature = web3Home.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`) + const signature = web3.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`) let gasEstimate try { logger.debug('Estimate gas') gasEstimate = await estimateGas({ - web3: web3Home, - homeBridge, + web3, + homeBridge: bridgeContract, validatorContract, signature: signature.signature, message, @@ -87,15 +82,12 @@ function processSignatureRequestsBuilder(config) { } } - const data = await homeBridge.methods - .submitSignature(signature.signature, message) - .encodeABI({ from: config.validatorAddress }) - + const data = bridgeContract.methods.submitSignature(signature.signature, message).encodeABI() txToSend.push({ data, gasEstimate, transactionReference: signatureRequest.transactionHash, - to: config.homeBridgeAddress + to: config.home.bridgeAddress }) }) .map(promise => limit(promise)) diff --git a/oracle/src/events/processTransfers/index.js b/oracle/src/events/processTransfers/index.js index 074827c4..420dff8c 100644 --- a/oracle/src/events/processTransfers/index.js +++ b/oracle/src/events/processTransfers/index.js @@ -1,35 +1,32 @@ require('../../../env') const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('../../services/HttpListProvider') -const { BRIDGE_VALIDATORS_ABI, ZERO_ADDRESS } = require('../../../../commons') +const { ZERO_ADDRESS } = require('../../../../commons') const rootLogger = require('../../services/logger') -const { web3Home, web3Foreign } = require('../../services/web3') +const { getValidatorContract } = require('../../tx/web3') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const estimateGas = require('../processAffirmationRequests/estimateGas') const limit = promiseLimit(MAX_CONCURRENT_EVENTS) -let validatorContract = null - function processTransfersBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - const userRequestForAffirmationAbi = config.foreignBridgeAbi.filter( + const { bridgeContract, web3 } = config.home + + const userRequestForAffirmationAbi = config.foreign.bridgeABI.find( e => e.type === 'event' && e.name === 'UserRequestForAffirmation' - )[0] - const tokensSwappedAbi = config.foreignBridgeAbi.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0] - const userRequestForAffirmationHash = web3Home.eth.abi.encodeEventSignature(userRequestForAffirmationAbi) - const tokensSwappedHash = tokensSwappedAbi ? web3Home.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x' + ) + const tokensSwappedAbi = config.foreign.bridgeABI.find(e => e.type === 'event' && e.name === 'TokensSwapped') + const userRequestForAffirmationHash = web3.eth.abi.encodeEventSignature(userRequestForAffirmationAbi) + const tokensSwappedHash = tokensSwappedAbi ? web3.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x' + + let validatorContract = null return async function processTransfers(transfers) { const txToSend = [] if (validatorContract === null) { - rootLogger.debug('Getting validator contract address') - const validatorContractAddress = await homeBridge.methods.validatorContract().call() - rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained') - - validatorContract = new web3Home.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress) + validatorContract = await getValidatorContract(bridgeContract, web3) } rootLogger.debug(`Processing ${transfers.length} Transfer events`) @@ -43,10 +40,10 @@ function processTransfersBuilder(config) { logger.info({ from, value }, `Processing transfer ${transfer.transactionHash}`) - const receipt = await web3Foreign.eth.getTransactionReceipt(transfer.transactionHash) + const receipt = await config.foreign.web3.eth.getTransactionReceipt(transfer.transactionHash) const existsAffirmationEvent = receipt.logs.some( - e => e.address === config.foreignBridgeAddress && e.topics[0] === userRequestForAffirmationHash + e => e.address === config.foreign.bridgeAddress && e.topics[0] === userRequestForAffirmationHash ) if (existsAffirmationEvent) { @@ -59,7 +56,7 @@ function processTransfersBuilder(config) { } const existsTokensSwappedEvent = tokensSwappedAbi - ? receipt.logs.some(e => e.address === config.foreignBridgeAddress && e.topics[0] === tokensSwappedHash) + ? receipt.logs.some(e => e.address === config.foreign.bridgeAddress && e.topics[0] === tokensSwappedHash) : false if (from === ZERO_ADDRESS && existsTokensSwappedEvent) { @@ -73,8 +70,8 @@ function processTransfersBuilder(config) { try { logger.debug('Estimate gas') gasEstimate = await estimateGas({ - web3: web3Home, - homeBridge, + web3, + homeBridge: bridgeContract, validatorContract, recipient: from, value, @@ -100,15 +97,12 @@ function processTransfersBuilder(config) { } } - const data = await homeBridge.methods - .executeAffirmation(from, value, transfer.transactionHash) - .encodeABI({ from: config.validatorAddress }) - + const data = bridgeContract.methods.executeAffirmation(from, value, transfer.transactionHash).encodeABI() txToSend.push({ data, gasEstimate, transactionReference: transfer.transactionHash, - to: config.homeBridgeAddress + to: config.home.bridgeAddress }) }) .map(promise => limit(promise)) diff --git a/oracle/src/sender.js b/oracle/src/sender.js index 2236c797..03845ba7 100644 --- a/oracle/src/sender.js +++ b/oracle/src/sender.js @@ -30,9 +30,8 @@ if (process.argv.length < 3) { const config = require(path.join('../config/', process.argv[2])) -const web3Instance = config.web3 -const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : config.web3 -const { web3Fallback } = config +const { web3, web3Fallback } = config +const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : web3 const nonceKey = `${config.id}:nonce` let chainId = 0 @@ -41,12 +40,11 @@ async function initialize() { try { const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) - web3Instance.currentProvider.urls.forEach(checkHttps(config.chain)) + web3.currentProvider.urls.forEach(checkHttps(config.chain)) GasPrice.start(config.id) - chainId = await getChainId(web3Instance) - + chainId = await getChainId(web3) connectQueue() } catch (e) { logger.error(e.message) @@ -86,7 +84,7 @@ async function readNonce(forceUpdate) { logger.debug('Reading nonce') if (forceUpdate) { logger.debug('Forcing update of nonce') - return getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS) + return getNonce(web3, ORACLE_VALIDATOR_ADDRESS) } const nonce = await redis.get(nonceKey) @@ -95,7 +93,7 @@ async function readNonce(forceUpdate) { return Number(nonce) } else { logger.warn("Nonce wasn't found in the DB") - return getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS) + return getNonce(web3, ORACLE_VALIDATOR_ADDRESS) } } @@ -210,7 +208,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT if (message.includes('insufficient funds')) { insufficientFunds = true - const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS) + const currentBalance = await web3.eth.getBalance(ORACLE_VALIDATOR_ADDRESS) minimumBalance = gasLimit.multipliedBy(gasPrice) logger.error( `Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.` @@ -240,7 +238,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT if (insufficientFunds) { logger.warn('Insufficient funds. Stop sending transactions until the account has the minimum balance') channel.close() - waitForFunds(web3Instance, ORACLE_VALIDATOR_ADDRESS, minimumBalance, resume, logger) + waitForFunds(web3, ORACLE_VALIDATOR_ADDRESS, minimumBalance, resume, logger) } } catch (e) { logger.error(e) diff --git a/oracle/src/services/blockFinder.js b/oracle/src/services/blockFinder.js new file mode 100644 index 00000000..eac25755 --- /dev/null +++ b/oracle/src/services/blockFinder.js @@ -0,0 +1,67 @@ +require('../../env') +const rootLogger = require('./logger') + +async function makeBlockFinder(name, web3) { + const logger = rootLogger.child({ + module: `blockFinder-${name}` + }) + + logger.info('Estimating average block time') + const lastBlock = await web3.eth.getBlock('latest') + const oldBlock = await web3.eth.getBlock(Math.max(lastBlock.number - 10000, 1)) + const blockDiff = lastBlock.number - oldBlock.number + const timeDiff = lastBlock.timestamp - oldBlock.timestamp + const averageBlockTime = timeDiff / blockDiff + logger.info(`Average block time among last ${blockDiff} blocks is ${averageBlockTime} seconds`) + + return async (timestamp, startingBlock) => { + logger.info(`Searching for block with timestamp ${timestamp}, starting from block #${startingBlock.number}`) + let currentBlock = startingBlock + + let requests = 0 + const getBlock = number => { + requests++ + return web3.eth.getBlock(number) + } + + let prevBlockDiff = Infinity + while (true) { + const timeDiff = currentBlock.timestamp - timestamp + const blockDiff = Math.ceil(timeDiff / averageBlockTime) + if (Math.abs(blockDiff) < 3 || Math.abs(blockDiff) >= Math.abs(prevBlockDiff)) { + break + } + prevBlockDiff = blockDiff + const blockNumber = currentBlock.number - blockDiff + logger.debug(`Moving ${-blockDiff} blocks from #${currentBlock.number} to #${blockNumber}`) + currentBlock = await getBlock(blockNumber) + } + + if (currentBlock.timestamp < timestamp) { + while (true) { + logger.debug(`Checking next block #${currentBlock.number + 1}`) + const nextBlock = await getBlock(currentBlock.number + 1) + if (nextBlock.timestamp <= timestamp) { + currentBlock = nextBlock + } else { + break + } + } + } else if (currentBlock.timestamp > timestamp) { + while (currentBlock.timestamp > timestamp) { + logger.debug(`Checking previous block #${currentBlock.number - 1}`) + currentBlock = await getBlock(currentBlock.number - 1) + } + } + + logger.info( + `Found block #${currentBlock.number}, with timestamp ${ + currentBlock.timestamp + }. Made a total of ${requests} eth_getBlockByNumber requests` + ) + + return currentBlock + } +} + +module.exports = makeBlockFinder diff --git a/oracle/src/services/gasPrice.js b/oracle/src/services/gasPrice.js index 1d7075bf..fc5bf355 100644 --- a/oracle/src/services/gasPrice.js +++ b/oracle/src/services/gasPrice.js @@ -1,7 +1,6 @@ require('../../env') const fetch = require('node-fetch') -const { web3Home, web3Foreign } = require('../services/web3') -const { bridgeConfig } = require('../../config/base.config') +const { home, foreign } = require('../../config/base.config') const logger = require('../services/logger').child({ module: 'gasPrice' }) @@ -9,17 +8,12 @@ const { setIntervalAndRun } = require('../utils/utils') const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES, DEFAULT_GAS_PRICE_FACTOR } = require('../utils/constants') const { gasPriceFromSupplier, gasPriceFromContract } = require('../../../commons') -const HomeABI = bridgeConfig.homeBridgeAbi -const ForeignABI = bridgeConfig.foreignBridgeAbi - const { - COMMON_FOREIGN_BRIDGE_ADDRESS, COMMON_FOREIGN_GAS_PRICE_FALLBACK, COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL, COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE, ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL, COMMON_FOREIGN_GAS_PRICE_FACTOR, - COMMON_HOME_BRIDGE_ADDRESS, COMMON_HOME_GAS_PRICE_FALLBACK, COMMON_HOME_GAS_PRICE_SUPPLIER_URL, COMMON_HOME_GAS_PRICE_SPEED_TYPE, @@ -27,10 +21,6 @@ const { COMMON_HOME_GAS_PRICE_FACTOR } = process.env -const homeBridge = new web3Home.eth.Contract(HomeABI, COMMON_HOME_BRIDGE_ADDRESS) - -const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, COMMON_FOREIGN_BRIDGE_ADDRESS) - let cachedGasPrice = null let fetchGasPriceInterval = null @@ -48,13 +38,13 @@ const fetchGasPrice = async (speedType, factor, bridgeContract, gasPriceSupplier async function start(chainId, fetchOnce) { clearInterval(fetchGasPriceInterval) - let bridgeContract = null + let contract = null let gasPriceSupplierUrl = null let speedType = null let updateInterval = null let factor = null if (chainId === 'home') { - bridgeContract = homeBridge + contract = home.bridgeContract gasPriceSupplierUrl = COMMON_HOME_GAS_PRICE_SUPPLIER_URL speedType = COMMON_HOME_GAS_PRICE_SPEED_TYPE updateInterval = ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL || DEFAULT_UPDATE_INTERVAL @@ -62,7 +52,7 @@ async function start(chainId, fetchOnce) { cachedGasPrice = COMMON_HOME_GAS_PRICE_FALLBACK } else if (chainId === 'foreign') { - bridgeContract = foreignBridge + contract = foreign.bridgeContract gasPriceSupplierUrl = COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL speedType = COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE updateInterval = ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL || DEFAULT_UPDATE_INTERVAL @@ -79,14 +69,13 @@ async function start(chainId, fetchOnce) { } if (fetchOnce) { - await fetchGasPrice(speedType, factor, bridgeContract, fetchFn) + await fetchGasPrice(speedType, factor, contract, fetchFn) } else { fetchGasPriceInterval = await setIntervalAndRun( - () => fetchGasPrice(speedType, factor, bridgeContract, fetchFn), + () => fetchGasPrice(speedType, factor, contract, fetchFn), updateInterval ) } - return getPrice() } function getPrice() { diff --git a/oracle/src/services/logger.js b/oracle/src/services/logger.js index 48cc80d6..8eb72bea 100644 --- a/oracle/src/services/logger.js +++ b/oracle/src/services/logger.js @@ -3,6 +3,7 @@ const path = require('path') const { web3Home, web3Foreign, + web3ForeignArchive, web3Side, web3HomeFallback, web3ForeignFallback, @@ -31,6 +32,10 @@ web3ForeignFallback.currentProvider.setLogger(logger) web3HomeRedundant.currentProvider.setLogger(logger) web3ForeignRedundant.currentProvider.setLogger(logger) +if (web3ForeignArchive) { + web3ForeignArchive.currentProvider.setLogger(logger) +} + if (web3Side) { web3Side.currentProvider.setLogger(logger) } diff --git a/oracle/src/services/web3.js b/oracle/src/services/web3.js index 9d1d1458..5006d9f5 100644 --- a/oracle/src/services/web3.js +++ b/oracle/src/services/web3.js @@ -7,6 +7,7 @@ const { COMMON_HOME_RPC_URL, COMMON_FOREIGN_RPC_URL, ORACLE_SIDE_RPC_URL, + ORACLE_FOREIGN_ARCHIVE_RPC_URL, ORACLE_RPC_REQUEST_TIMEOUT, ORACLE_HOME_RPC_POLLING_INTERVAL, ORACLE_FOREIGN_RPC_POLLING_INTERVAL @@ -42,6 +43,18 @@ const web3Home = new Web3(homeProvider) const foreignProvider = new HttpListProvider(foreignUrls, foreignOptions) const web3Foreign = new Web3(foreignProvider) +let web3ForeignArchive = null +if (ORACLE_FOREIGN_ARCHIVE_RPC_URL) { + const archiveUrls = ORACLE_FOREIGN_ARCHIVE_RPC_URL.split(' ').filter(url => url.length > 0) + const options = { + requestTimeout: configuredTimeout || 2000, + retry: RETRY_CONFIG + } + + const archiveProvider = new HttpListProvider(archiveUrls, options) + web3ForeignArchive = new Web3(archiveProvider) +} + let web3Side = null if (ORACLE_SIDE_RPC_URL) { const sideUrls = ORACLE_SIDE_RPC_URL.split(' ').filter(url => url.length > 0) @@ -83,6 +96,7 @@ if (foreignUrls.length > 1) { module.exports = { web3Home, web3Foreign, + web3ForeignArchive, web3Side, web3HomeRedundant, web3ForeignRedundant, diff --git a/oracle/src/tx/web3.js b/oracle/src/tx/web3.js index ce18930e..5ad65ef8 100644 --- a/oracle/src/tx/web3.js +++ b/oracle/src/tx/web3.js @@ -1,6 +1,7 @@ const logger = require('../services/logger').child({ module: 'web3' }) +const { BRIDGE_VALIDATORS_ABI } = require('../../../commons') async function getNonce(web3, address) { try { @@ -9,6 +10,7 @@ async function getNonce(web3, address) { logger.debug({ address, transactionCount }, 'Transaction count obtained') return transactionCount } catch (e) { + logger.error(e.message) throw new Error(`Nonce cannot be obtained`) } } @@ -20,10 +22,23 @@ async function getBlockNumber(web3) { logger.debug({ blockNumber }, 'Block number obtained') return blockNumber } catch (e) { + logger.error(e.message) throw new Error(`Block Number cannot be obtained`) } } +async function getBlock(web3, number) { + try { + logger.debug(`Getting block ${number}`) + const block = await web3.eth.getBlock(number) + logger.debug({ number: block.number, timestamp: block.timestamp, hash: block.hash }, 'Block obtained') + return block + } catch (e) { + logger.error(e.message) + throw new Error(`Block cannot be obtained`) + } +} + async function getChainId(web3) { try { logger.debug('Getting chain id') @@ -31,6 +46,7 @@ async function getChainId(web3) { logger.debug({ chainId }, 'Chain id obtained') return chainId } catch (e) { + logger.error(e.message) throw new Error(`Chain Id cannot be obtained`) } } @@ -39,14 +55,29 @@ async function getRequiredBlockConfirmations(contract) { try { const contractAddress = contract.options.address logger.debug({ contractAddress }, 'Getting required block confirmations') - const requiredBlockConfirmations = await contract.methods.requiredBlockConfirmations().call() + const requiredBlockConfirmations = parseInt(await contract.methods.requiredBlockConfirmations().call(), 10) logger.debug({ contractAddress, requiredBlockConfirmations }, 'Required block confirmations obtained') return requiredBlockConfirmations } catch (e) { + logger.error(e.message) throw new Error(`Required block confirmations cannot be obtained`) } } +async function getValidatorContract(contract, web3) { + try { + const contractAddress = contract.options.address + logger.debug({ contractAddress }, 'Getting validator contract address') + const validatorContractAddress = await contract.methods.validatorContract().call() + logger.debug({ contractAddress, validatorContractAddress }, 'Validator contract address obtained') + + return new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress) + } catch (e) { + logger.error(e.message) + throw new Error(`Validator cannot be obtained`) + } +} + async function getEvents({ contract, event, fromBlock, toBlock, filter }) { try { const contractAddress = contract.options.address @@ -58,6 +89,7 @@ async function getEvents({ contract, event, fromBlock, toBlock, filter }) { logger.debug({ contractAddress, event, count: pastEvents.length }, 'Past events obtained') return pastEvents } catch (e) { + logger.error(e.message) throw new Error(`${event} events cannot be obtained`) } } @@ -81,6 +113,7 @@ async function getEventsFromTx({ web3, contract, event, txHash, filter }) { logger.debug({ contractAddress, event, count: pastEvents.length }, 'Past events obtained') return pastEvents } catch (e) { + logger.error(e.message) throw new Error(`${event} events cannot be obtained`) } } @@ -88,8 +121,10 @@ async function getEventsFromTx({ web3, contract, event, txHash, filter }) { module.exports = { getNonce, getBlockNumber, + getBlock, getChainId, getRequiredBlockConfirmations, + getValidatorContract, getEvents, getEventsFromTx } diff --git a/oracle/src/utils/utils.js b/oracle/src/utils/utils.js index 35c63312..f907f7b7 100644 --- a/oracle/src/utils/utils.js +++ b/oracle/src/utils/utils.js @@ -137,6 +137,14 @@ async function readAccessListFile(fileName, logger) { return readAccessLists[fileName] } +function zipToObject(keys, values) { + const res = {} + keys.forEach((key, i) => { + res[key] = values[i] + }) + return res +} + module.exports = { syncForEach, checkHTTPS, @@ -149,5 +157,6 @@ module.exports = { nonceError, getRetrySequence, promiseAny, - readAccessListFile + readAccessListFile, + zipToObject } diff --git a/oracle/src/watcher.js b/oracle/src/watcher.js index 27bae1a9..5310c6f9 100644 --- a/oracle/src/watcher.js +++ b/oracle/src/watcher.js @@ -1,12 +1,10 @@ require('../env') const path = require('path') -const { BN, toBN } = require('web3').utils const { connectWatcherToQueue, connection } = require('./services/amqpClient') -const { getBlockNumber } = require('./tx/web3') const { redis } = require('./services/redisClient') const logger = require('./services/logger') const { getShutdownFlag } = require('./services/shutdownState') -const { getRequiredBlockConfirmations, getEvents } = require('./tx/web3') +const { getBlockNumber, getRequiredBlockConfirmations, getEvents } = require('./tx/web3') const { checkHTTPS, watchdog } = require('./utils/utils') const { EXIT_CODES } = require('./utils/constants') @@ -24,24 +22,19 @@ const processTransfers = require('./events/processTransfers')(config) const processAMBSignatureRequests = require('./events/processAMBSignatureRequests')(config) const processAMBCollectedSignatures = require('./events/processAMBCollectedSignatures')(config) const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config) +const processAMBInformationRequests = require('./events/processAMBInformationRequests')(config) const { getTokensState } = require('./utils/tokenState') -const ZERO = toBN(0) -const ONE = toBN(1) - -const web3Instance = config.web3 -const bridgeContract = new web3Instance.eth.Contract(config.bridgeAbi, config.bridgeContractAddress) -let { eventContractAddress } = config -let eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress) +const { web3, bridgeContract, eventContract, startBlock, pollingInterval, chain } = config.main const lastBlockRedisKey = `${config.id}:lastProcessedBlock` -let lastProcessedBlock = BN.max(config.startBlock.sub(ONE), ZERO) +let lastProcessedBlock = Math.max(startBlock - 1, 0) async function initialize() { try { const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) - web3Instance.currentProvider.urls.forEach(checkHttps(config.chain)) + web3.currentProvider.urls.forEach(checkHttps(chain)) await getLastProcessedBlock() connectWatcherToQueue({ @@ -72,18 +65,18 @@ async function runMain({ sendToQueue }) { setTimeout(() => { runMain({ sendToQueue }) - }, config.pollingInterval) + }, pollingInterval) } async function getLastProcessedBlock() { const result = await redis.get(lastBlockRedisKey) - logger.debug({ fromRedis: result, fromConfig: lastProcessedBlock.toString() }, 'Last Processed block obtained') - lastProcessedBlock = result ? toBN(result) : lastProcessedBlock + logger.debug({ fromRedis: result, fromConfig: lastProcessedBlock }, 'Last Processed block obtained') + lastProcessedBlock = result ? parseInt(result, 10) : lastProcessedBlock } function updateLastProcessedBlock(lastBlockNumber) { lastProcessedBlock = lastBlockNumber - return redis.set(lastBlockRedisKey, lastProcessedBlock.toString()) + return redis.set(lastBlockRedisKey, lastProcessedBlock) } function processEvents(events) { @@ -113,28 +106,18 @@ async function checkConditions() { case 'erc-native-transfer': logger.debug('Getting token address to listen Transfer events') state = await getTokensState(bridgeContract, logger) - updateEventContract(state.bridgeableTokenAddress) + eventContract.options.address = state.bridgeableTokenAddress break default: } } -function updateEventContract(address) { - if (eventContractAddress !== address) { - eventContractAddress = address - eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress) - } -} - -async function getLastBlockToProcess() { - const lastBlockNumberPromise = getBlockNumber(web3Instance).then(toBN) - const requiredBlockConfirmationsPromise = getRequiredBlockConfirmations(bridgeContract).then(toBN) +async function getLastBlockToProcess(web3, bridgeContract) { const [lastBlockNumber, requiredBlockConfirmations] = await Promise.all([ - lastBlockNumberPromise, - requiredBlockConfirmationsPromise + getBlockNumber(web3), + getRequiredBlockConfirmations(bridgeContract) ]) - - return lastBlockNumber.sub(requiredBlockConfirmations) + return lastBlockNumber - requiredBlockConfirmations } async function main({ sendToQueue }) { @@ -151,28 +134,49 @@ async function main({ sendToQueue }) { await checkConditions() - const lastBlockToProcess = await getLastBlockToProcess() + const lastBlockToProcess = await getLastBlockToProcess(web3, bridgeContract) - if (lastBlockToProcess.lte(lastProcessedBlock)) { + if (lastBlockToProcess <= lastProcessedBlock) { logger.debug('All blocks already processed') return } - const fromBlock = lastProcessedBlock.add(ONE) - const rangeEndBlock = config.blockPollingLimit ? fromBlock.add(config.blockPollingLimit) : lastBlockToProcess - const toBlock = BN.min(lastBlockToProcess, rangeEndBlock) + const fromBlock = lastProcessedBlock + 1 + const rangeEndBlock = config.blockPollingLimit ? fromBlock + config.blockPollingLimit : lastBlockToProcess + let toBlock = Math.min(lastBlockToProcess, rangeEndBlock) - const events = await getEvents({ + const events = (await getEvents({ contract: eventContract, event: config.event, fromBlock, toBlock, filter: config.eventFilter - }) + })).sort((a, b) => a.blockNumber - b.blockNumber) logger.info(`Found ${events.length} ${config.event} events`) if (events.length) { - const job = await processEvents(events) + let job + + // for async information requests, requests are processed in batches only if they are located in the same block + if (config.id === 'amb-information-request') { + // obtain block number and events from the earliest block + const batchBlockNumber = events[0].blockNumber + const batchEvents = events.filter(event => event.blockNumber === batchBlockNumber) + + // if there are some other events in the later blocks, + // adjust lastProcessedBlock so that these events will be processed again on the next iteration + if (batchEvents.length < events.length) { + // pick event outside from the batch + toBlock = events[batchEvents.length].blockNumber - 1 + } + + job = await processAMBInformationRequests(batchEvents) + if (job === null) { + return + } + } else { + job = await processEvents(events) + } logger.info('Transactions to send:', job.length) if (job.length) { diff --git a/oracle/test/blockFinder.test.js b/oracle/test/blockFinder.test.js new file mode 100644 index 00000000..6e20c2fd --- /dev/null +++ b/oracle/test/blockFinder.test.js @@ -0,0 +1,42 @@ +const { expect } = require('chai') +const makeBlockFinder = require('../src/services/blockFinder') + +const mockBlock = n => ({ timestamp: n > 500 ? n * 10 : n + 4500, number: n }) +const latestBlock = mockBlock(1000) +const web3Mock = { + eth: { + async getBlock(n) { + return n === 'latest' ? latestBlock : mockBlock(n) + } + } +} + +describe('blockFinder', () => { + it('get recent blocks', async () => { + const blockFinder = await makeBlockFinder('test', web3Mock) + + expect(await blockFinder(10000, latestBlock)).to.eql(latestBlock) + expect(await blockFinder(9999, latestBlock)).to.eql(mockBlock(999)) + expect(await blockFinder(9995, latestBlock)).to.eql(mockBlock(999)) + expect(await blockFinder(9991, latestBlock)).to.eql(mockBlock(999)) + expect(await blockFinder(9990, latestBlock)).to.eql(mockBlock(999)) + expect(await blockFinder(9989, latestBlock)).to.eql(mockBlock(998)) + expect(await blockFinder(9981, latestBlock)).to.eql(mockBlock(998)) + }) + + it('get older blocks', async () => { + const blockFinder = await makeBlockFinder('test', web3Mock) + + expect(await blockFinder(7500, latestBlock)).to.eql(mockBlock(750)) + expect(await blockFinder(7497, latestBlock)).to.eql(mockBlock(749)) + expect(await blockFinder(7511, latestBlock)).to.eql(mockBlock(751)) + }) + + it('get ancient blocks with different time period', async () => { + const blockFinder = await makeBlockFinder('test', web3Mock) + + expect(await blockFinder(4600, latestBlock)).to.eql(mockBlock(100)) + expect(await blockFinder(4601, latestBlock)).to.eql(mockBlock(101)) + expect(await blockFinder(4602, latestBlock)).to.eql(mockBlock(102)) + }) +}) diff --git a/parity/chain-foreign.json b/parity/chain-foreign.json index cdd358a1..b05924fb 100644 --- a/parity/chain-foreign.json +++ b/parity/chain-foreign.json @@ -34,7 +34,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x7A1200" + "gasLimit": "0xb71b00" }, "accounts": { "0000000000000000000000000000000000000001": { diff --git a/parity/chain.json b/parity/chain.json index 7653c93d..fcca2cbe 100644 --- a/parity/chain.json +++ b/parity/chain.json @@ -34,7 +34,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x7A1200" + "gasLimit": "0xb71b00" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },