Oracle watcher for AMB async calls (#509)

This commit is contained in:
Kirill Fedoseev 2021-05-09 16:34:19 +03:00 committed by GitHub
parent 38f1bae8f5
commit ffbca8b941
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 1364 additions and 350 deletions

@ -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`

@ -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

@ -3,6 +3,7 @@
with_items:
- docker-compose
- docker-compose-transfer
- docker-compose-amb
loop_control:
loop_var: file

@ -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

@ -20,3 +20,4 @@
with_items:
- docker-compose.yml
- docker-compose-transfer.yml
- docker-compose-amb.yml

@ -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

@ -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

@ -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

@ -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

@ -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",

@ -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'
)
})
})
})

@ -6,6 +6,7 @@
],
"plugins": ["node"],
"rules": {
"node/no-unpublished-require": "off"
"node/no-unpublished-require": "off",
"global-require": "off"
}
}

@ -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}`,

@ -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
}

@ -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}`,

@ -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',

@ -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',

@ -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
}

@ -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
}

@ -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}`,

@ -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}`,

@ -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

@ -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",

@ -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

@ -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}.`

@ -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))

@ -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))

@ -0,0 +1,3 @@
module.exports = {
'eth_blockNumber()': async (web3, _, block) => [true, web3.eth.abi.encodeParameter('uint256', block.number)]
}

@ -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'])
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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

@ -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

@ -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))

@ -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))

@ -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))

@ -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))

@ -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))

@ -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)

@ -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

@ -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() {

@ -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)
}

@ -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,

@ -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
}

@ -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
}

@ -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) {

@ -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))
})
})

@ -34,7 +34,7 @@
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x7A1200"
"gasLimit": "0xb71b00"
},
"accounts": {
"0000000000000000000000000000000000000001": {

@ -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 } } } },