Compare commits

...

36 Commits

Author SHA1 Message Date
Kirill Fedoseev
052c0738c2 Bump molecule version 2021-11-08 11:49:25 +03:00
Kirill Fedoseev
03dfaa9bb5 Playbook improvements 2021-11-07 19:57:18 +03:00
Kirill Fedoseev
e899b15808 Allow to store validator keystore file in the docker swarm secret 2021-11-07 12:48:12 +03:00
Kirill Fedoseev
4eba91ef7e Fetch AMB signatures a bit earlier (#620) 2021-11-03 19:21:35 +03:00
Kirill Fedoseev
1e3aa53ab3 Add oracle helper script for fetching interest amounts via async calls (#615) 2021-10-21 13:33:12 +03:00
Alexander Kolotov
a05ff51555 Added logging prior the investedAmount call (#614) 2021-10-12 18:06:50 +03:00
Kirill Fedoseev
52e1c88b58 Include cDAI balance in balanceDiff (#613) 2021-10-11 12:14:55 +03:00
Alexander Kolotov
d36dcadd34 Merge the develop branch to the master branch, preparation to v3.2.0
This merge contains the following set of changes:
  * [Oracle, Fix] Use more accurate gas estimates for very high message gas limits (#611)
  * [Common, Other] Final version of AMB and OB contracts security audit report by ChainSecurity (#608)
2021-10-07 21:45:51 +03:00
Kirill Fedoseev
d543dbb339 Use more accurate gas estimates for very high message gas limits (#611) 2021-10-07 20:44:14 +03:00
Kirill Fedoseev
bd21cc163e Merge pull request #608 from poanetwork/reports/chainsecurity-amb-6.0.0-ob-1.1.0-contracts
AMB and OB contracts security audit report by ChainSecurity
2021-09-28 21:12:37 +03:00
Alexander Kolotov
4a0fc936a1 Final version of AMB and OB contracts security audit report by ChainSecurity 2021-09-28 20:39:04 +03:00
Alexander Kolotov
78564afabd Merge the develop branch to the master branch, preparation to v3.1.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Try to detect unsynced node state (#592)
  * [Oracle, Improvement] Allow to override JSON RPC error codes (#603)
  * [Oracle, Fix] Fix handling of Compound related Transfer events (#595)
  * [Oracle, Fix] Add new nonce-related error messages (#599)
  * [Deployment, Fix] .env template includes latest changes related to the oracle configuration (#601)
  * [Deployment, Fix] Improvements for the local logs configuration (#602)
  * [Common, Other] Update the contract's submodule to the release 6.0.0 (#600)
2021-09-20 23:56:37 +03:00
Kirill Fedoseev
70a2c30b4c Fix imported ABI 2021-09-20 13:31:37 +03:00
Kirill Fedoseev
06a9586148 Update yarn.lock 2021-09-20 12:50:07 +03:00
Kirill Fedoseev
7379fe4190 Allow to override JSON RPC error codes (#603) 2021-09-20 12:07:49 +03:00
Alexander Kolotov
e59766c5df Update the contract's submodule to the release 6.0.0 (#600) 2021-09-18 13:44:48 +03:00
Alexander Kolotov
fdb18a1a17 Improvements for the local logs configuration (#602) 2021-09-18 13:42:29 +03:00
Alexander Kolotov
4412046f66 .env template includes latest changes related to the oracle configuration (#601) 2021-09-18 13:41:56 +03:00
Kirill Fedoseev
0ff224ccd3 Add new nonce-related error messages (#599) 2021-09-15 23:42:32 +03:00
Kirill Fedoseev
5cedacafe5 Fix handling of Compound related Transfer events (#595) 2021-08-24 11:31:15 +03:00
Kirill Fedoseev
2e6179f974 Try to detect unsynced node state (#592) 2021-08-03 23:36:50 +03:00
Alexander Kolotov
4f5e3c47be Merge the develop branch to the master branch, preparation to v3.0.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Async calls error codes (#587)
  * [Oracle, Improvement] Refactor/async call serializers (#588)
2021-07-16 17:58:21 +03:00
Kirill Fedoseev
4c06329153 Refactor/async call serializers (#588) 2021-07-13 12:25:05 +03:00
Kirill Fedoseev
d53452675e Async calls error codes (#587) 2021-07-13 11:57:08 +03:00
Alexander Kolotov
c92f80c484 Merge the develop branch to the master branch, preparation to v3.0.0-rc1
This merge contains the following set of changes:
  * [Monitor, Improvement] Add statistics about used AMB information requests (#577)
  * [ALM, Improvement] Add safe-execute button in ALM (#580), closes #573, closes #551
  * [Oracle, Improvement] Improve confirm-relay feature (#582), closes #569
  * [Monitor, Fix] Prune print of long error messages about missing file (#579), closes #578
  * [Oracle, Fix] Use safe approach for eth_getLogs requests (#581)
  * [Oracle, Fix] Fix logging in gas price service (#583), closes #552
  * [Oracle, Fix] Fix oracle error patterns and oracle e2e tests (#585)
  * [Common, Other] Fix dependencies versions and update example.yml (#566)
  * [Common, Other] Upload services logs in e2e and ultimate tests (#568)
  * [Oracle, Other] added example of emergency shutdown controller contract (#572)
  * [Oracle, Other] Refactor oracle configuration (#584)
2021-07-10 10:10:22 +03:00
Kirill Fedoseev
5e95b5d8c5 Fix oracle error patterns and oracle e2e tests (#585) 2021-07-08 16:42:56 +03:00
Kirill Fedoseev
6e1f57493a Refactor oracle configuration (#584) 2021-07-08 09:38:55 +03:00
Kirill Fedoseev
8ed6550635 Improve confirm-relay feature (#582) 2021-07-07 16:21:01 +03:00
Kirill Fedoseev
c8eb0f1ed8 Fix logging in gas price service (#583) 2021-07-05 13:50:18 +03:00
Kirill Fedoseev
3e5e50c06e Use safe approach for eth_getLogs requests (#581) 2021-06-30 12:28:03 +03:00
Kirill Fedoseev
92e1b597c4 Add safe-execute button in ALM (#580) 2021-06-18 09:43:18 +03:00
Kirill Fedoseev
8b1a97e673 Prune print of long error messages about missing file (#579) 2021-06-12 00:24:20 +03:00
Kirill Fedoseev
3cf184c391 Add statistics about used AMB information requests (#577) 2021-05-26 08:58:20 -06:00
Alexander Kolotov
8f72516374 added example of emergency shutdown controller contract (#572) 2021-05-16 15:42:36 -06:00
Kirill Fedoseev
98155e3075 Upload services logs in e2e and ultimate tests (#568) 2021-05-15 10:29:20 -06:00
Kirill Fedoseev
0d724147bd Fix dependencies versions and update example.yml (#566) 2021-05-10 14:36:57 -06:00
118 changed files with 2772 additions and 4134 deletions

View File

@@ -77,7 +77,7 @@ jobs:
- name: Rebuild and push updated images
run: |
function check_if_image_exists() {
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
}
updated=()
if ! check_if_image_exists e2e ${E2E_TAG}; then updated+=("e2e"); fi
@@ -104,7 +104,7 @@ jobs:
- name: Rebuild and push molecule runner e2e image
run: |
function check_if_image_exists() {
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
}
if check_if_image_exists molecule_runner ${MOLECULE_RUNNER_TAG}; then
echo "Image already exists"
@@ -149,6 +149,12 @@ jobs:
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: yarn run ${{ matrix.task }}
run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
- name: Upload logs
if: always()
uses: actions/upload-artifact@v2
with:
name: logs-${{ matrix.task }}
path: e2e-commons/logs
deployment:
runs-on: ubuntu-latest
needs:
@@ -199,7 +205,7 @@ jobs:
- name: Login to docker registry
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: Deploy contracts
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy generate-amb-tx blocks
- name: Pull e2e oracle image
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle-amb
@@ -210,3 +216,12 @@ jobs:
run: sudo chown -R $USER:docker /var/run/docker.sock
- name: Run oracle e2e tests
run: docker-compose -f ./e2e-commons/docker-compose.yml run -e ULTIMATE=true e2e yarn workspace oracle-e2e run ${{ matrix.task }}
- name: Save logs
if: always()
run: e2e-commons/down.sh
- name: Upload logs
if: always()
uses: actions/upload-artifact@v2
with:
name: logs-ultimate-${{ matrix.task }}
path: e2e-commons/logs

View File

@@ -52,6 +52,9 @@ ORACLE_SHUTDOWN_CONTRACT_ADDRESS | Optional contract address in the side chain a
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`
ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Home chain. Infinite, if not provided. | `integer`
ORACLE_JSONRPC_ERROR_CODES | Override default JSON rpc error codes that can trigger RPC fallback to the next URL from the list (or a retry in case of a single RPC URL). Default is `-32603,-32002,-32005`. Should be a comma-separated list of negative integers. | `string`
ORACLE_VALIDATOR_KEYSTORE_PATH | Path to the keystore v3 json file with the encrypted validator key. | `string`
ORACLE_VALIDATOR_KEYSTORE_PASSWORD | Password from the provided keystore file, oracle won't startup properly, if the provided password is invalid | `string`
## Monitor configuration

View File

@@ -19,6 +19,7 @@ COPY --from=contracts /mono/contracts/build ./contracts/build
COPY commons/package.json ./commons/
COPY oracle-e2e/package.json ./oracle-e2e/
COPY monitor-e2e/package.json ./monitor-e2e/
COPY oracle/src/utils/constants.js ./oracle/src/utils/constants.js
COPY yarn.lock .
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
cd $(dirname $0)
../e2e-commons/up.sh deploy blocks alm alm-e2e
../e2e-commons/up.sh deploy generate-amb-tx blocks alm alm-e2e
# run oracle amb e2e tests to generate transactions for alm
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run alm

View File

@@ -6,8 +6,8 @@ jest.setTimeout(60000)
const statusText = 'Success'
const statusSelector = 'label[data-id="status"]'
const homeToForeignTxURL = 'http://localhost:3004/77/0xbc83d43bdc675a615a2b820e43e52d25857aa5fdd77acf2dd92cd247af2c693c'
const foreignToHomeTxURL = 'http://localhost:3004/42/0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6'
const homeToForeignTxURL = 'http://localhost:3004/77/0x295efbe6ae98937ef35d939376c9bd752b4dc6f6899a9d5ddd6a57cea3d76c89'
const foreignToHomeTxURL = 'http://localhost:3004/42/0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
describe('ALM', () => {
let browser

View File

@@ -123,6 +123,24 @@ const abi: AbiItem[] = [
stateMutability: 'nonpayable',
type: 'function'
},
{
constant: false,
inputs: [
{
name: '_data',
type: 'bytes'
},
{
name: '_signatures',
type: 'bytes'
}
],
name: 'safeExecuteSignaturesWithAutoGasLimit',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function'
},
{
constant: true,
inputs: [

View File

@@ -1,4 +1,4 @@
import React from 'react'
import React, { useEffect, useState } from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
@@ -9,6 +9,7 @@ import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton'
import { useStateProvider } from '../state/StateProvider'
const StyledExecutionConfirmation = styled.div`
margin-top: 30px;
@@ -33,6 +34,8 @@ export const ExecutionConfirmation = ({
executionEventsFetched,
setPendingExecution
}: ExecutionConfirmationParams) => {
const { foreign } = useStateProvider()
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
const availableManualExecution =
!isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
@@ -48,6 +51,22 @@ export const ExecutionConfirmation = ({
const formattedValidator =
windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator
useEffect(
() => {
if (!availableManualExecution || !foreign.bridgeContract) return
const p = foreign.bridgeContract.methods.getBridgeInterfacesVersion().call()
p.then(({ major, minor }: any) => {
major = parseInt(major, 10)
minor = parseInt(minor, 10)
if (major < 5 || (major === 5 && minor < 7)) return
setSafeExecutionAvailable(true)
})
},
[availableManualExecution, foreign.bridgeContract]
)
const getExecutionStatusElement = (validatorStatus = '') => {
switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
@@ -105,6 +124,7 @@ export const ExecutionConfirmation = ({
{availableManualExecution && (
<td>
<ManualExecutionButton
safeExecutionAvailable={safeExecutionAvailable}
messageData={messageData}
setExecutionData={setExecutionData}
signatureCollected={signatureCollected as string[]}

View File

@@ -15,16 +15,19 @@ import { signatureToVRS, packSignatures } from '../utils/signatures'
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
import { TransactionReceipt } from 'web3-eth'
const StyledButton = styled.button`
const ActionButton = styled.button`
color: var(--button-color);
border-color: var(--font-color);
margin-top: 10px;
min-width: 120px;
padding: 1rem;
&:focus {
outline: var(--button-color);
}
`
interface ManualExecutionButtonParams {
safeExecutionAvailable: boolean
messageData: string
setExecutionData: Function
signatureCollected: string[]
@@ -32,6 +35,7 @@ interface ManualExecutionButtonParams {
}
export const ManualExecutionButton = ({
safeExecutionAvailable,
messageData,
setExecutionData,
signatureCollected,
@@ -40,6 +44,8 @@ export const ManualExecutionButton = ({
const { foreign, setError } = useStateProvider()
const { library, activate, account, active } = useWeb3React()
const [manualExecution, setManualExecution] = useState(false)
const [allowFailures, setAllowFailures] = useState(false)
const notReady = !foreign.bridgeContract || !signatureCollected || !signatureCollected.length
useEffect(
() => {
@@ -72,7 +78,11 @@ export const ManualExecutionButton = ({
const signatures = packSignatures(signatureCollected.map(signatureToVRS))
const messageId = messageData.slice(0, 66)
const bridge = foreign.bridgeContract
const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI()
const executeMethod =
safeExecutionAvailable && !allowFailures
? bridge.methods.safeExecuteSignaturesWithAutoGasLimit
: bridge.methods.executeSignatures
const data = executeMethod(messageData, signatures).encodeABI()
setManualExecution(false)
library.eth
@@ -132,15 +142,35 @@ export const ManualExecutionButton = ({
messageData,
signatureCollected,
setExecutionData,
setPendingExecution
setPendingExecution,
safeExecutionAvailable,
allowFailures
]
)
return (
<div className="is-center">
<StyledButton className="button outline" onClick={() => setManualExecution(true)}>
Execute
</StyledButton>
<div>
<div className="is-center">
<ActionButton disabled={notReady} className="button outline" onClick={() => setManualExecution(true)}>
Execute
</ActionButton>
</div>
{safeExecutionAvailable && (
<div
title="Allow executed message to fail and record its failure on-chain without reverting the whole transaction.
Use fixed gas limit for execution."
className="is-center"
style={{ paddingTop: 10 }}
>
<input
type="checkbox"
id="allow-failures"
checked={allowFailures}
onChange={e => setAllowFailures(e.target.checked)}
/>
<label htmlFor="allow-failures">Unsafe mode</label>
</div>
)}
</div>
)
}

View File

@@ -86,6 +86,14 @@ export const getConfirmationsForTx = async (
setPendingConfirmations(validatorPendingConfirmations.length > 0)
} else {
setPendingConfirmations(false)
if (fromHome) {
// fetch collected signatures for possible manual processing
setSignatureCollected(
await Promise.all(
Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
)
)
}
}
const undefinedConfirmations = validatorConfirmations.filter(
@@ -115,15 +123,6 @@ export const getConfirmationsForTx = async (
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
}))
updateConfirmations(notRequiredConfirmations)
if (fromHome) {
// fetch collected signatures for possible manual processing
setSignatureCollected(
await Promise.all(
Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
)
)
}
}
// get transactions from success signatures

View File

@@ -1,5 +1,5 @@
const HOME_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/HomeBridgeErcToNative').abi
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignBridgeErcToNative').abi
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/XDaiForeignBridge.json').abi
const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi
const BLOCK_REWARD_ABI = require('../contracts/build/contracts/BlockRewardMock').abi
const BRIDGE_VALIDATORS_ABI = require('../contracts/build/contracts/BridgeValidators').abi

View File

@@ -1,3 +1,5 @@
const { soliditySha3 } = require('web3-utils')
function strip0x(input) {
return input.replace(/^0x/, '')
}
@@ -39,8 +41,35 @@ const normalizeAMBMessageEvent = e => {
return parseAMBMessage(msgData)
}
const ambInformationSignatures = [
'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)'
]
const ambInformationSelectors = Object.fromEntries(ambInformationSignatures.map(sig => [soliditySha3(sig), sig]))
const normalizeAMBInfoRequest = e => ({
messageId: e.returnValues.messageId,
sender: e.returnValues.sender,
requestSelector: ambInformationSelectors[e.returnValues.requestSelector] || 'unknown',
data: e.returnValues.data
})
module.exports = {
strip0x,
parseAMBMessage,
normalizeAMBMessageEvent
normalizeAMBMessageEvent,
ambInformationSignatures,
normalizeAMBInfoRequest
}

View File

@@ -9,7 +9,8 @@
},
"dependencies": {
"gas-price-oracle": "^0.1.5",
"web3-utils": "^1.3.0"
"web3-utils": "^1.3.0",
"node-fetch": "^2.1.2"
},
"devDependencies": {
"bn-chai": "^1.0.1",

View File

@@ -1,5 +1,6 @@
const { toWei, toBN, BN } = require('web3-utils')
const { GasPriceOracle } = require('gas-price-oracle')
const fetch = require('node-fetch')
const { BRIDGE_MODES } = require('./constants')
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
@@ -178,17 +179,16 @@ const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei'))
}
// fetchFn has to be supplied (instead of just url to oracle),
// because this utility function is shared between Browser and Node,
// we use built-in 'fetch' on browser side, and `node-fetch` package in Node.
const gasPriceFromSupplier = async (fetchFn, options = {}) => {
const gasPriceFromSupplier = async (url, options = {}) => {
try {
let json
if (fetchFn) {
const response = await fetchFn()
if (url === 'gas-price-oracle') {
json = await gasPriceOracle.fetchGasPricesOffChain()
} else if (url) {
const response = await fetch(url, { timeout: 2000 })
json = await response.json()
} else {
json = await gasPriceOracle.fetchGasPricesOffChain()
return null
}
const oracleGasPrice = json[options.speedType]

View File

@@ -1,4 +1,4 @@
FROM python:3.7-stretch
FROM python:3.7
RUN curl -fsSL https://get.docker.com | sh
RUN pip3 install docker molecule==2.22rc1 molecule[docker] flake8
RUN pip3 install docker molecule[docker,ansible] pytest pytest-testinfra flake8
WORKDIR mono/deployment-e2e

View File

@@ -3,12 +3,6 @@ dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
enabled: True
options:
config-data:
ignore: ../../hosts.yml
platforms:
- name: monitor-host
groups:
@@ -22,11 +16,6 @@ platforms:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
lint:
name: ansible-lint
enabled: True
options:
r: ["bug"]
playbooks:
prepare: ../prepare.yml
converge: ./converge.yml
@@ -37,14 +26,11 @@ provisioner:
syslog_server_port: "udp://127.0.0.1:514"
verifier:
name: testinfra
lint:
name: flake8
additional_files_or_dirs:
- ../../tests/*
scenario:
name: monitor
test_sequence:
- lint
- cleanup
- destroy
- dependency

View File

@@ -3,12 +3,6 @@ dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
enabled: True
options:
config-data:
ignore: ../../hosts.yml
platforms:
- name: multiple-host
groups:
@@ -23,30 +17,22 @@ platforms:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
lint:
name: ansible-lint
enabled: True
options:
r: ["bug"]
playbooks:
prepare: ../prepare.yml
converge: ../monitor/converge.yml
inventory:
host_vars:
multiple-host:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "6c48435bd464a53ed66ed62127c4dba8af75cf1a99a8ebe2680599948fbfbc6d"
MONITOR_PORT: 3003
syslog_server_port: "udp://127.0.0.1:514"
verifier:
name: testinfra
lint:
name: flake8
additional_files_or_dirs:
- ../../tests/*
scenario:
name: multiple
test_sequence:
- lint
- cleanup
- destroy
- dependency

View File

@@ -3,18 +3,12 @@ dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
enabled: True
options:
config-data:
ignore: ../../hosts.yml
platforms:
- name: oracle-host
groups:
- example
children:
- oracle
- oracle_swarm
image: ubuntu:16.04
privileged: true
network_mode: host
@@ -22,29 +16,22 @@ platforms:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
lint:
name: ansible-lint
enabled: True
options:
r: ["bug"]
playbooks:
prepare: ../prepare.yml
converge: ../../../deployment/site.yml
inventory:
host_vars:
oracle-host:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ORACLE_VALIDATOR_KEYSTORE_PATH: "../../../e2e-commons/keystore.json"
ORACLE_VALIDATOR_KEYSTORE_PASSWORD: "12345678"
syslog_server_port: "udp://127.0.0.1:514"
verifier:
name: testinfra
lint:
name: flake8
additional_files_or_dirs:
- ../../tests/*
scenario:
name: oracle
test_sequence:
- lint
- cleanup
- destroy
- dependency

View File

@@ -3,22 +3,21 @@ import pytest
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('oracle')
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('oracle_swarm')
@pytest.mark.parametrize("name", [
("oracle_rabbit_1"),
("oracle_redis_1"),
("oracle_bridge_request_1"),
("oracle_bridge_collected_1"),
("oracle_bridge_affirmation_1"),
("oracle_bridge_senderhome_1"),
("oracle_bridge_senderforeign_1"),
("oracle_bridge_shutdown_1"),
("oracle_rabbit"),
("oracle_redis"),
("oracle_bridge_request"),
("oracle_bridge_collected"),
("oracle_bridge_affirmation"),
("oracle_bridge_senderhome"),
("oracle_bridge_senderforeign"),
("oracle_bridge_shutdown"),
])
def test_docker_containers(host, name):
container = host.docker(name)
assert container.is_running
assert host.docker(name) is not None
@pytest.mark.parametrize("service", [

View File

@@ -25,8 +25,6 @@ provisioner:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
verifier:
name: testinfra
lint:
name: flake8
scenario:
name: ultimate-amb
test_sequence:

View File

@@ -27,8 +27,6 @@ provisioner:
ORACLE_FOREIGN_START_BLOCK: 1
verifier:
name: testinfra
lint:
name: flake8
scenario:
name: ultimate-erc-to-native
test_sequence:

View File

@@ -5,13 +5,13 @@ ORACLE_LOG_LEVEL: debug
## Home contract
COMMON_HOME_RPC_URL: "https://sokol.poa.network"
COMMON_HOME_BRIDGE_ADDRESS: "0x98aFdE294f1C46aA0a27Cc4049ED337F879d8976"
COMMON_HOME_BRIDGE_ADDRESS: "0x59ba90A588ce732AB33FD32Aab1b58c21400A0f6"
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://sokol.poa.network"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x5a584f4C30B36f282848dAc9a2b20E7BEF481981"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
COMMON_FOREIGN_RPC_URL: "https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0xdA4a49a00F4fF4A5988b9AceE95f99e3b2c208b6"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 5000
## Home Gasprice
COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/"
@@ -31,8 +31,8 @@ ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
MONITOR_BRIDGE_NAME: "bridge"
MONITOR_PORT: 3003
MONITOR_CACHE_EVENTS: "false"
MONITOR_HOME_START_BLOCK: 0
MONITOR_FOREIGN_START_BLOCK: 0
MONITOR_HOME_START_BLOCK: 20821049
MONITOR_FOREIGN_START_BLOCK: 24773297
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_TX_NUMBER_THRESHOLD: 100

View File

@@ -7,6 +7,13 @@ sokol-kovan:
ansible_user: ubuntu
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
#syslog_server_port: "udp://127.0.0.1:514"
oracle_swarm:
hosts:
127.0.0.1:
ansible_user: ubuntu
ORACLE_VALIDATOR_KEYSTORE_PATH: "/path/to/keystore.json"
ORACLE_VALIDATOR_KEYSTORE_PASSWORD: "12345678"
#syslog_server_port: "udp://127.0.0.1:514"
monitor:
hosts:
127.0.0.1:

View File

@@ -1,4 +0,0 @@
# pre-release because it contains "CI Fixes for ansible 2.8"
molecule==2.22rc1
docker
flake8

View File

@@ -1,4 +1,4 @@
{
"live-restore": true,
"live-restore": false,
"no-new-privileges": true
}

View File

@@ -31,6 +31,7 @@
owner: "root"
group: "root"
mode: "0755"
when: skip_compose is undefined
- name: Upgrade pip version
shell: pip3 install --upgrade pip==19.3.1
@@ -45,6 +46,9 @@
group: docker
createhome: yes
- name: reset ssh connection to allow user changes to affect ansible user
meta: reset_connection
- name: Install auditd
apt:
name: auditd

View File

@@ -1,6 +1,6 @@
/var/log/docker/*/docker.log {
rotate 5
size 1G
size 100M
compress
missingok
delaycompress
@@ -8,7 +8,7 @@
}
/var/log/docker/*.log {
rotate 5
size 1G
size 100M
compress
missingok
delaycompress

View File

@@ -7,7 +7,7 @@
loop_control:
loop_var: file
- name: Set the local container logs configuration file
- name: Set the oracle's containers local logs configuration file
template:
src: 31-oracle-docker.conf.j2
dest: /etc/rsyslog.d/31-oracle-docker.conf
@@ -15,6 +15,22 @@
group: root
mode: 0644
- name: Set the redis container local logs configuration file
template:
src: 32-redis-docker.conf.j2
dest: /etc/rsyslog.d/32-redis-docker.conf
owner: root
group: root
mode: 0644
- name: Set the rabbit MQ container local logs configuration file
template:
src: 33-rabbit-docker.conf.j2
dest: /etc/rsyslog.d/33-rabbit-docker.conf
owner: root
group: root
mode: 0644
- name: Set the log configuration file to send container logs to remote server
template:
src: 36-oracle-remote-logging.conf.j2

View File

@@ -27,17 +27,6 @@
set_fact:
ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}"
- name: Get foreign erc type
become_user: "{{ compose_service_user }}"
shell: docker-compose run --rm --entrypoint "node scripts/initialChecks.js" bridge_affirmation
args:
chdir: "{{ bridge_path }}/oracle"
register: ERCTYPE
- name: Set FOREIGN_ERC_TYPE variable
set_fact:
FOREIGN_ERC_TYPE: "{{ (ERCTYPE.stdout).foreignERC | default('') }}"
- name: Extend docker compose file for erc to native
set_fact: composefileoverride="-f docker-compose-transfer.yml"
when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE"

View File

@@ -11,9 +11,16 @@ ORACLE_HOME_RPC_POLLING_INTERVAL={{ ORACLE_HOME_RPC_POLLING_INTERVAL }}
## Foreign contract
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }}
{% if ORACLE_FOREIGN_ARCHIVE_RPC_URL | default('') != '' %}
ORACLE_FOREIGN_ARCHIVE_RPC_URL={{ ORACLE_FOREIGN_ARCHIVE_RPC_URL }}
{% endif %}
COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
ORACLE_FOREIGN_RPC_POLLING_INTERVAL={{ ORACLE_FOREIGN_RPC_POLLING_INTERVAL }}
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
{% endif %}
## Gasprice
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
@@ -47,8 +54,28 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
ORACLE_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }}
ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }}
ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }}
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
{% if ORACLE_FOREIGN_TX_RESEND_INTERVAL | default('') != '' %}
ORACLE_FOREIGN_TX_RESEND_INTERVAL={{ ORACLE_FOREIGN_TX_RESEND_INTERVAL }}
{% endif %}
{% if ORACLE_HOME_TX_RESEND_INTERVAL | default('') != '' %}
ORACLE_HOME_TX_RESEND_INTERVAL={{ ORACLE_HOME_TX_RESEND_INTERVAL }}
{% endif %}
## Emergency shutdown configuration
{% if ORACLE_SHUTDOWN_SERVICE_URL | default('') != '' %}
ORACLE_SHUTDOWN_SERVICE_URL={{ ORACLE_SHUTDOWN_SERVICE_URL }}
{% endif %}
{% if ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL | default('') != '' %}
ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL={{ ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL }}
{% endif %}
{% if ORACLE_SIDE_RPC_URL | default('') != '' %}
ORACLE_SIDE_RPC_URL={{ ORACLE_SIDE_RPC_URL }}
{% endif %}
{% if ORACLE_SHUTDOWN_CONTRACT_ADDRESS | default('') != '' %}
ORACLE_SHUTDOWN_CONTRACT_ADDRESS={{ ORACLE_SHUTDOWN_CONTRACT_ADDRESS }}
{% endif %}
{% if ORACLE_SHUTDOWN_CONTRACT_METHOD | default('') != '' %}
ORACLE_SHUTDOWN_CONTRACT_METHOD={{ ORACLE_SHUTDOWN_CONTRACT_METHOD }}
{% endif %}
{% if ORACLE_HOME_START_BLOCK | default('') != '' %}

View File

@@ -0,0 +1,11 @@
$FileCreateMode 0644
template(name="DockerLogFileName_Redis" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*redis.*)\\/[a-zA-Z0-9]+\\[")
constant(value="/docker.log")
}
if $programname contains 'oracle' and $programname contains 'redis' then \
?DockerLogFileName_Redis
$FileCreateMode 0600

View File

@@ -0,0 +1,11 @@
$FileCreateMode 0644
template(name="DockerLogFileName_Rabbit" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*rabbit.*)\\/[a-zA-Z0-9]+\\[")
constant(value="/docker.log")
}
if $programname contains 'oracle' and $programname contains 'rabbit' then \
?DockerLogFileName_Rabbit
$FileCreateMode 0600

View File

@@ -0,0 +1,9 @@
---
bridge_path: "/home/{{ compose_service_user }}/bridge"
bridge_data_path: "/home/{{ compose_service_user }}/bridge_data"
ORACLE_ALLOW_HTTP_FOR_RPC: no
ORACLE_QUEUE_URL: amqp://rabbit
ORACLE_REDIS_URL: redis://redis
keyfile_path: "/root/.key"
keystore_path: "/root/.keystore.json"
oracle_image: poanetwork/tokenbridge-oracle:latest

View File

@@ -0,0 +1,3 @@
---
dependencies:
- { role: common, skip_repo: true, skip_compose: true }

View File

@@ -0,0 +1,8 @@
---
- name: Pull the containers images
community.docker.docker_image:
name: "{{ oracle_image }}"
source: pull
when: skip_pull is undefined
vars:
ansible_python_interpreter: /usr/bin/python3

View File

@@ -0,0 +1,41 @@
---
- name: Set the oracle's containers local logs configuration file
template:
src: 31-oracle-docker.conf.j2
dest: /etc/rsyslog.d/31-oracle-docker.conf
owner: root
group: root
mode: 0644
- name: Set the redis container local logs configuration file
template:
src: 32-redis-docker.conf.j2
dest: /etc/rsyslog.d/32-redis-docker.conf
owner: root
group: root
mode: 0644
- name: Set the rabbit MQ container local logs configuration file
template:
src: 33-rabbit-docker.conf.j2
dest: /etc/rsyslog.d/33-rabbit-docker.conf
owner: root
group: root
mode: 0644
- name: Set the log configuration file to send container logs to remote server
template:
src: 36-oracle-remote-logging.conf.j2
dest: /etc/rsyslog.d/36-oracle-remote-logging.conf
owner: root
group: root
mode: 0644
when: syslog_server_port is defined
- name: Discarding unwanted messages in rsyslog
blockinfile:
path: /etc/rsyslog.conf
insertbefore: "# Where to place spool and state files"
marker: "#{mark} add string to discarding unwanted messages"
content: ':msg, contains, "ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY" ~'
notify: restart rsyslog

View File

@@ -0,0 +1,6 @@
---
- include_tasks: pre_config.yml
- include_tasks: logging.yml
- include_tasks: jumpbox.yml
- include_tasks: post_config.yml
- include_tasks: servinstall.yml

View File

@@ -0,0 +1,46 @@
---
- name: Get blocks
become_user: "{{ compose_service_user }}"
shell: docker run --env-file .env --rm {{ oracle_image }} scripts/getValidatorStartBlocks.js
args:
chdir: "{{ bridge_path }}/oracle"
register: BLOCKS
when: (ORACLE_HOME_START_BLOCK is not defined) or (ORACLE_FOREIGN_START_BLOCK is not defined)
- name: Write blocks
blockinfile:
path: "{{ bridge_path }}/oracle/.env"
marker: "## {mark} Calculated by scripts/getValidatorStartBlocks.js"
block: |
ORACLE_HOME_START_BLOCK={{ (BLOCKS.stdout | from_json).homeStartBlock }}
ORACLE_FOREIGN_START_BLOCK={{ (BLOCKS.stdout | from_json).foreignStartBlock }}
when: (ORACLE_HOME_START_BLOCK is not defined) or (ORACLE_FOREIGN_START_BLOCK is not defined)
- name: Copy keystore file
copy:
src: "{{ ORACLE_VALIDATOR_KEYSTORE_PATH }}"
dest: "{{ keystore_path }}"
owner: root
group: root
mode: 0600
- name: Create swarm secret
community.docker.docker_secret:
name: oracle_keystore
state: present
data_src: "{{ keystore_path }}"
vars:
ansible_python_interpreter: /usr/bin/python3
- name: Remove unencrypted keystore file
file:
path: "{{ keystore_path }}"
state: absent
- name: Install .key config
template:
src: key.j2
dest: "{{ keyfile_path }}"
owner: root
group: root
mode: 0600

View File

@@ -0,0 +1,47 @@
---
- name: Init docker swarm
community.docker.docker_swarm:
state: present
autolock_managers: yes
listen_addr: 127.0.0.1:2377
vars:
ansible_python_interpreter: /usr/bin/python3
- name: Get unlock token
community.docker.docker_swarm_info:
unlock_key: yes
register: result
vars:
ansible_python_interpreter: /usr/bin/python3
- name: Print unlock token
debug:
var: result.swarm_unlock_key
- name: Create oracle directory
file:
path: "{{ bridge_path }}/oracle"
state: directory
mode: '0755'
- name: Create rabbitmq directory
file:
path: "{{ bridge_data_path }}/{{ item }}"
state: directory
mode: '0775'
loop:
- rabbitmq
- redis
- name: Install .env config
template:
src: .env.j2
dest: "{{ bridge_path }}/oracle/.env"
owner: "{{ compose_service_user }}"
mode: '0640'
- name: Install docker-compose file
template:
src: docker-compose.yml.j2
dest: "{{ bridge_path }}/oracle/docker-compose.yml"
mode: '0755'

View File

@@ -0,0 +1,19 @@
# This role creates a poabridge service which is designed to manage docker-compose bridge deployment.
# /etc/init.d/poabridge start, status, stop, restart - does what the services usually do in such cases.
---
- name: "Set poabridge service"
template:
src: poabridge.j2
dest: "/etc/init.d/poabridge"
owner: root
mode: 755
- name: "Enable the service"
service:
name: "poabridge"
state: started
enabled: yes
use: service
- name: Start the service
shell: service poabridge start

View File

@@ -0,0 +1,86 @@
## General settings
ORACLE_BRIDGE_MODE={{ ORACLE_BRIDGE_MODE }}
{% if ORACLE_LOG_LEVEL | default('') != '' %}
ORACLE_LOG_LEVEL={{ ORACLE_LOG_LEVEL }}
{% endif %}
## Home contract
COMMON_HOME_RPC_URL={{ COMMON_HOME_RPC_URL }}
COMMON_HOME_BRIDGE_ADDRESS={{ COMMON_HOME_BRIDGE_ADDRESS }}
ORACLE_HOME_RPC_POLLING_INTERVAL={{ ORACLE_HOME_RPC_POLLING_INTERVAL }}
## Foreign contract
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }}
{% if ORACLE_FOREIGN_ARCHIVE_RPC_URL | default('') != '' %}
ORACLE_FOREIGN_ARCHIVE_RPC_URL={{ ORACLE_FOREIGN_ARCHIVE_RPC_URL }}
{% endif %}
COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
ORACLE_FOREIGN_RPC_POLLING_INTERVAL={{ ORACLE_FOREIGN_RPC_POLLING_INTERVAL }}
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
{% endif %}
## Gasprice
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
{% endif %}
{% if COMMON_HOME_GAS_PRICE_SPEED_TYPE | default('') != '' %}
COMMON_HOME_GAS_PRICE_SPEED_TYPE={{ COMMON_HOME_GAS_PRICE_SPEED_TYPE }}
{% endif %}
COMMON_HOME_GAS_PRICE_FALLBACK={{ COMMON_HOME_GAS_PRICE_FALLBACK }}
{% if ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL | default('') != '' %}
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL={{ ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL }}
{% endif %}
{% if COMMON_HOME_GAS_PRICE_FACTOR | default('') != '' %}
COMMON_HOME_GAS_PRICE_FACTOR={{ COMMON_HOME_GAS_PRICE_FACTOR }}
{% endif %}
{% if COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL={{ COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL }}
{% endif %}
{% if COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | default('') != '' %}
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE={{ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE }}
{% endif %}
COMMON_FOREIGN_GAS_PRICE_FALLBACK={{ COMMON_FOREIGN_GAS_PRICE_FALLBACK }}
{% if ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL | default('') != '' %}
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL={{ ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL }}
{% endif %}
{% if COMMON_FOREIGN_GAS_PRICE_FACTOR | default('') != '' %}
COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
{% endif %}
## Transport configuration
ORACLE_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }}
ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }}
ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }}
{% if ORACLE_FOREIGN_TX_RESEND_INTERVAL | default('') != '' %}
ORACLE_FOREIGN_TX_RESEND_INTERVAL={{ ORACLE_FOREIGN_TX_RESEND_INTERVAL }}
{% endif %}
{% if ORACLE_HOME_TX_RESEND_INTERVAL | default('') != '' %}
ORACLE_HOME_TX_RESEND_INTERVAL={{ ORACLE_HOME_TX_RESEND_INTERVAL }}
{% endif %}
## Emergency shutdown configuration
{% if ORACLE_SHUTDOWN_SERVICE_URL | default('') != '' %}
ORACLE_SHUTDOWN_SERVICE_URL={{ ORACLE_SHUTDOWN_SERVICE_URL }}
{% endif %}
{% if ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL | default('') != '' %}
ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL={{ ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL }}
{% endif %}
{% if ORACLE_SIDE_RPC_URL | default('') != '' %}
ORACLE_SIDE_RPC_URL={{ ORACLE_SIDE_RPC_URL }}
{% endif %}
{% if ORACLE_SHUTDOWN_CONTRACT_ADDRESS | default('') != '' %}
ORACLE_SHUTDOWN_CONTRACT_ADDRESS={{ ORACLE_SHUTDOWN_CONTRACT_ADDRESS }}
{% endif %}
{% if ORACLE_SHUTDOWN_CONTRACT_METHOD | default('') != '' %}
ORACLE_SHUTDOWN_CONTRACT_METHOD={{ ORACLE_SHUTDOWN_CONTRACT_METHOD }}
{% endif %}
{% if ORACLE_HOME_START_BLOCK | default('') != '' %}
ORACLE_HOME_START_BLOCK={{ ORACLE_HOME_START_BLOCK }}
{% endif %}
{% if ORACLE_FOREIGN_START_BLOCK | default('') != '' %}
ORACLE_FOREIGN_START_BLOCK={{ ORACLE_FOREIGN_START_BLOCK }}
{% endif %}

View File

@@ -0,0 +1,11 @@
$FileCreateMode 0644
template(name="DockerLogFileName_Oracle" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="bridge_(.*)\\/[a-zA-Z0-9]+\\[")
constant(value="/docker.log")
}
if $programname startswith 'oracle_bridge_' then \
?DockerLogFileName_Oracle
$FileCreateMode 0600

View File

@@ -0,0 +1,11 @@
$FileCreateMode 0644
template(name="DockerLogFileName_Redis" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*redis.*)\\/[a-zA-Z0-9]+\\[")
constant(value="/docker.log")
}
if $programname contains 'oracle' and $programname contains 'redis' then \
?DockerLogFileName_Redis
$FileCreateMode 0600

View File

@@ -0,0 +1,11 @@
$FileCreateMode 0644
template(name="DockerLogFileName_Rabbit" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*rabbit.*)\\/[a-zA-Z0-9]+\\[")
constant(value="/docker.log")
}
if $programname contains 'oracle' and $programname contains 'rabbit' then \
?DockerLogFileName_Rabbit
$FileCreateMode 0600

View File

@@ -0,0 +1,15 @@
if $programname startswith 'oracle_bridge_' then {
action(
type="omfwd"
protocol="{{ syslog_server_port.split(":")[0] }}"
target="{{ (syslog_server_port.split(":")[1])[2:] }}"
port="{{ syslog_server_port.split(":")[2] }}"
template="RemoteForwardFormat"
queue.SpoolDirectory="/var/spool/rsyslog"
queue.FileName="remote"
queue.MaxDiskSpace="1g"
queue.SaveOnShutdown="on"
queue.Type="LinkedList"
ResendLastMSGOnReconnect="on"
)
}

View File

@@ -0,0 +1,144 @@
version: '3.9'
x-deploy: &x-deploy
resources:
limits:
cpus: '0.3'
memory: 500M
reservations:
memory: 100M
x-keystore-access: &x-keystore-access
environment:
ORACLE_VALIDATOR_KEYSTORE_PATH: /run/secrets/oracle_keystore
ORACLE_VALIDATOR_KEYSTORE_PASSWORD:
secrets:
- oracle_keystore
x-logging: &x-logging
driver: 'syslog'
options: {tag: '{{ '{{.Name}}/{{.ID}}' }}' }
services:
rabbit:
image: rabbitmq:3
hostname: rabbit
deploy: *x-deploy
logging: *x-logging
environment: [ 'RABBITMQ_NODENAME=node@rabbit' ]
networks:
- net_rabbit_bridge_request
- net_rabbit_bridge_collected
- net_rabbit_bridge_affirmation
- net_rabbit_bridge_senderhome
- net_rabbit_bridge_senderforeign
volumes: [ '{{ bridge_data_path }}/rabbitmq:/var/lib/rabbitmq/mnesia' ]
redis:
image: redis:4
hostname: redis
deploy: *x-deploy
logging: *x-logging
command: [ redis-server, --appendonly, 'yes' ]
networks:
- net_db_bridge_request
- net_db_bridge_collected
- net_db_bridge_affirmation
- net_db_bridge_senderhome
- net_db_bridge_senderforeign
- net_db_bridge_shutdown
volumes: [ '{{ bridge_data_path }}/redis:/data' ]
bridge_request:
image: {{ oracle_image }}
deploy: *x-deploy
logging: *x-logging
env_file: ./.env
<<: *x-keystore-access
entrypoint: yarn watcher:signature-request
networks:
- net_db_bridge_request
- net_rabbit_bridge_request
bridge_collected:
image: {{ oracle_image }}
deploy: *x-deploy
env_file: ./.env
entrypoint: yarn watcher:collected-signatures
networks:
- net_db_bridge_collected
- net_rabbit_bridge_collected
bridge_affirmation:
image: {{ oracle_image }}
deploy: *x-deploy
logging: *x-logging
env_file: ./.env
entrypoint: yarn watcher:affirmation-request
networks:
- net_db_bridge_affirmation
- net_rabbit_bridge_affirmation
bridge_senderhome:
image: {{ oracle_image }}
deploy: *x-deploy
env_file: ./.env
<<: *x-keystore-access
entrypoint: yarn sender:home
networks:
- net_db_bridge_senderhome
- net_rabbit_bridge_senderhome
bridge_senderforeign:
image: {{ oracle_image }}
deploy: *x-deploy
logging: *x-logging
env_file: ./.env
<<: *x-keystore-access
entrypoint: yarn sender:foreign
networks:
- net_db_bridge_senderforeign
- net_rabbit_bridge_senderforeign
bridge_shutdown:
image: {{ oracle_image }}
deploy: *x-deploy
env_file: ./.env
entrypoint: yarn manager:shutdown
networks:
- net_db_bridge_shutdown
{% if ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE" %}
bridge_transfer:
image: {{ oracle_image }}
deploy: *x-deploy
logging: *x-logging
env_file: ./.env
entrypoint: yarn watcher:transfer
networks:
- net_db_bridge_transfer
- net_rabbit_bridge_transfer
{% endif %}
{% if ORACLE_BRIDGE_MODE == "ARBITRARY_MESSAGE" %}
bridge_information:
image: {{ oracle_image }}
deploy: *x-deploy
logging: *x-logging
env_file: ./.env
entrypoint: yarn watcher:information-request
networks:
- net_db_bridge_information
- net_rabbit_bridge_information
{% endif %}
networks:
net_db_bridge_request:
net_db_bridge_collected:
net_db_bridge_affirmation:
net_db_bridge_senderhome:
net_db_bridge_senderforeign:
net_db_bridge_shutdown:
{% if ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE" %}
net_db_bridge_transfer:
net_rabbit_bridge_transfer:
{% endif %}
{% if ORACLE_BRIDGE_MODE == "ARBITRARY_MESSAGE" %}
net_db_bridge_information:
net_rabbit_bridge_information:
{% endif %}
net_rabbit_bridge_request:
net_rabbit_bridge_collected:
net_rabbit_bridge_affirmation:
net_rabbit_bridge_senderhome:
net_rabbit_bridge_senderforeign:
secrets:
oracle_keystore:
external: true

View File

@@ -0,0 +1,2 @@
## Validator-specific options
ORACLE_VALIDATOR_KEYSTORE_PASSWORD={{ ORACLE_VALIDATOR_KEYSTORE_PASSWORD }}

View File

@@ -0,0 +1,66 @@
#! /bin/bash
### BEGIN INIT INFO
# Provides: poabridge
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start daemon at boot time
# Description: Enable service provided by daemon.
### END INIT INFO
WORKDIR="{{ '/home/' + compose_service_user | default('poadocker') + '/' + bridge_path + '/oracle' if bridge_path[:1] != "/" else bridge_path + '/oracle' }}"
#Getting path to private key file and variable name for parsing key file
source {{ keyfile_path }}
start(){
echo "Starting bridge.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" docker stack rm oracle
sudo -u "{{ compose_service_user }}" "ORACLE_VALIDATOR_KEYSTORE_PASSWORD=$ORACLE_VALIDATOR_KEYSTORE_PASSWORD" docker stack deploy oracle -c docker-compose.yml
}
stop(){
echo "Stopping bridge.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" docker stack rm oracle
sleep 2
}
status(){
echo "Bridge status:"
cd $WORKDIR
sudo -u "{{ compose_service_user }}" docker service ls
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
echo "Restarting bridge.."
stop
start
;;
*)
echo $"Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0

View File

@@ -4,6 +4,11 @@
become: true
roles:
- { role: oracle }
- name: Install Oracle as a Docker Swarm service
hosts: oracle_swarm
become: true
roles:
- { role: oracle_swarm }
- name: Install Monitor
hosts: monitor
become: true

View File

@@ -6,14 +6,12 @@ COMMON_HOME_RPC_URL=http://parity1:8545
COMMON_FOREIGN_RPC_URL=http://parity2:8545
COMMON_HOME_BRIDGE_ADDRESS=0x8397be90BCF57b0B71219f555Fe121b22e5a994C
COMMON_FOREIGN_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E
ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000
COMMON_HOME_GAS_PRICE_FACTOR=1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=10000000000
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000

View File

@@ -6,8 +6,6 @@ COMMON_HOME_RPC_URL=http://parity1:8545
COMMON_FOREIGN_RPC_URL=http://parity2:8545
COMMON_HOME_BRIDGE_ADDRESS=0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c
COMMON_FOREIGN_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624
ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1

View File

@@ -38,7 +38,7 @@
"ercToNativeBridge": {
"home": "0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c",
"foreign": "0x32198D570fffC7033641F8A9094FFDCaAEF42624",
"foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9",
"foreignToken": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"monitor": "http://monitor-erc20-native:3012/bridge"
},
"amb": {
@@ -49,6 +49,12 @@
"blockedHomeBox": "0xF9698Eb93702dfdd0e2d802088d4c21822a8A977",
"monitor": "http://monitor-amb:3013/bridge"
},
"amb2": {
"home": "0x5A42E119990c3F3A80Fea20aAF4c3Ff4DB240Cc9",
"foreign": "0x897527391ad3837604973d78D3514f44c36AB9FC",
"homeBox": "0xb008E9076fCbDB2C3AF84225Bc07Eb35B2bE5ECD",
"foreignBox": "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127"
},
"homeRPC": {
"URL": "http://parity1:8545",
"ID": "77"

View File

@@ -32,7 +32,7 @@ FOREIGN_GAS_PRICE=10000000000
FOREIGN_REWARDABLE=false
BLOCK_REWARD_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B
ERC20_TOKEN_ADDRESS=0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9
ERC20_TOKEN_ADDRESS=0x6B175474E89094C44Da98b954EedeAC495271d0F
REQUIRED_NUMBER_OF_VALIDATORS=1
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b 0xdCC784657C78054aa61FbcFFd2605F32374816A4 0xDcef88209a20D52165230104B245803C3269454d"

View File

@@ -1,7 +1,33 @@
#!/usr/bin/env bash
cd $(dirname $0)
if [ $CI ]; then exit $rc; fi
if [ $CI ]; then
rm -rf logs || true
mkdir ./logs
for project in "" validator{1,2,3}; do
for container in $(docker-compose -p "$project" ps | tail -n +3 | awk '{print $1}') ; do
if [[ -z "$project" ]]; then
path="./logs/$container.log"
else
mkdir -p "./logs/$project"
path="./logs/$project/$container.log"
fi
docker logs "$container" > "$path" 2>&1
done
done
touch ../oracle/.env
for file in ../oracle/docker-compose-{amb,transfer}.yml; do
for container in $(docker-compose -f "$file" ps | tail -n +3 | awk '{print $1}') ; do
mkdir -p "./logs/oracle"
docker logs "$container" > "./logs/oracle/$container.log" 2>&1
done
done
exit $rc;
fi
ps | grep node | grep -v grep | grep -v yarn | awk '{print "kill " $1}' | /bin/bash
docker-compose down

View File

@@ -0,0 +1 @@
[{"version":3,"id":"e7e64a1b-5e61-4c17-a473-963d2bbb59e5","address":"d138a69eb2da1c3518e792737c820b23cce62e4b","crypto":{"ciphertext":"f6ddf0b2638fb9fd5777de2aa07937b5ee9bc17acc74c8e6e6580e2dfd0d3de6","cipherparams":{"iv":"bcdbc5af4582887e5cdcf264e8d5b80d"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"f9e621918990e64e278e0fb8cf0343219e1cceaea8547d50fae452ad8f42f231","n":8192,"r":8,"p":1},"mac":"34149cd0b3ddea52588825d403fb75cfb8b864b616d455f75f2de001cc2601ed"}}]

View File

@@ -35,3 +35,9 @@ echo -e "\n\n############ Deploying one more test contract for amb ############\
cd "$DEPLOY_PATH"
node src/utils/deployTestBox.js
cd - > /dev/null
echo -e "\n\n############ Deploying one more amb without oracle for confirm relay tests ############\n"
cp "$ENVS_PATH/amb.env" "$DEPLOY_PATH/.env"
cd "$DEPLOY_PATH"
node deploy.js
cd - > /dev/null

View File

@@ -15,42 +15,46 @@ docker network create --driver bridge ultimate || true
docker-compose up -d parity1 parity2 e2e
startValidator () {
db_env="-e ORACLE_QUEUE_URL=amqp://$4 -e ORACLE_REDIS_URL=redis://$3"
db_env="-e ORACLE_QUEUE_URL=amqp://$3 -e ORACLE_REDIS_URL=redis://$2"
docker-compose $1 run -d --name $3 redis
docker-compose $1 run -d --name $4 rabbit
docker-compose $1 run -d --name $2 redis
docker-compose $1 run -d --name $3 rabbit
if [[ -z "$MODE" || "$MODE" == erc-to-native ]]; then
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
docker-compose $1 run $oraclePK $db_env -d oracle-erc20-native yarn watcher:signature-request
docker-compose $1 run $oracleAddr $db_env -d oracle-erc20-native yarn watcher:collected-signatures
docker-compose $1 run $oracleAddr $db_env -d oracle-erc20-native yarn watcher:affirmation-request
docker-compose $1 run $oracleAddr $db_env -d oracle-erc20-native yarn watcher:transfer
fi
if [[ -z "$MODE" || "$MODE" == amb ]]; then
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
docker-compose $1 run $oraclePK $db_env -d oracle-amb yarn watcher:signature-request
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn watcher:collected-signatures
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn watcher:affirmation-request
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn watcher:information-request
fi
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
docker-compose $1 run $oraclePK $db_env -d oracle-amb yarn sender:home
docker-compose $1 run $oraclePK $db_env -d oracle-amb yarn sender:foreign
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn manager:shutdown
}
while [ "$1" != "" ]; do
if [ "$1" == "oracle" ]; then
startValidator "-p validator1" "" redis rabbit
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
startValidator "-p validator1" redis rabbit
fi
if [ "$1" == "oracle-validator-2" ]; then
oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" "$oracle2Values" redis2 rabbit2
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" redis2 rabbit2
fi
if [ "$1" == "oracle-validator-3" ]; then
oracle3Values="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" "$oracle3Values" redis3 rabbit3
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" redis3 rabbit3
fi
if [ "$1" == "alm" ]; then
@@ -84,13 +88,38 @@ while [ "$1" != "" ]; do
if [ "$1" == "alm-e2e" ]; then
MODE=amb
startValidator "-p validator1" "" redis rabbit
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
startValidator "-p validator1" redis rabbit
oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" "$oracle2Values" redis2 rabbit2
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" redis2 rabbit2
oracle3Values="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" "$oracle3Values" redis3 rabbit3
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" redis3 rabbit3
fi
if [ "$1" == "generate-amb-tx" ]; then
docker-compose run e2e yarn workspace oracle-e2e run generate-amb-tx
fi
if [ "$1" == "manual-amb-relay" ]; then
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
env="-e COMMON_HOME_BRIDGE_ADDRESS=0x5A42E119990c3F3A80Fea20aAF4c3Ff4DB240Cc9 -e COMMON_FOREIGN_BRIDGE_ADDRESS=0x897527391ad3837604973d78D3514f44c36AB9FC"
# these tx hash are hardcoded and need to be updated manually
# once e2e environment setup process is changed
echo '0xea625a823bc5018dc3a4efe349f623e5ebb8c987b55f44d50d6556f42af9a400' > txHashes.txt
docker-compose -p validator1 run -v $(pwd)/txHashes.txt:/tmp/txHashes.txt $oraclePK $env oracle-amb yarn confirm:affirmation-request \
/tmp/txHashes.txt \
0x031c42e44485002c9215a5b1b75e9516131485ce29884a58765bf7a0038538f9
docker-compose -p validator1 run $oraclePK $env oracle-amb yarn confirm:signature-request \
0x1506a18af91afe732167ccbc178b55fc2547da4a814d13c015b6f496cf171754 | tee .tmp.log
tx_hash=$(cat .tmp.log | grep generatedTransactionHash | jq -r .generatedTransactionHash)
rm .tmp.log
rm txHashes.txt
docker-compose -p validator1 run $oraclePK $env oracle-amb yarn confirm:collected-signatures $tx_hash
fi
shift # Shift all the parameters down by one

View File

@@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "mocha --timeout 120000",
"start": "mocha --timeout 120000 --exit",
"lint": "eslint . --ignore-path ../.eslintignore"
},
"author": "",

View File

@@ -1,10 +1,9 @@
require('dotenv').config()
const logger = require('./logger')('detectMediators.js')
const { isHomeContract, isForeignContract } = require('./utils/web3Cache')
const eventsInfo = require('./utils/events')
const { getHomeTxSender, getForeignTxSender } = require('./utils/web3Cache')
const { addExecutionStatus } = require('./utils/message')
const { normalizeAMBMessageEvent } = require('../commons')
const { getHomeTxSender, getForeignTxSender, isHomeContract, isForeignContract } = require('./utils/web3Cache')
const { addExecutionStatus, addRetrievalStatus } = require('./utils/message')
const { normalizeAMBMessageEvent, normalizeAMBInfoRequest } = require('../commons')
function countInteractions(requests) {
const stats = {}
@@ -30,6 +29,41 @@ function countInteractions(requests) {
return stats
}
function countInfoRequests(requests) {
const stats = {}
requests.forEach(msg => {
if (!stats[msg.sender]) {
stats[msg.sender] = {}
}
if (!stats[msg.sender][msg.requestSelector]) {
stats[msg.sender][msg.requestSelector] = {
callSucceeded: {
callbackSucceeded: 0,
callbackFailed: 0
},
callFailed: {
callbackSucceeded: 0,
callbackFailed: 0
},
pending: 0
}
}
const stat = stats[msg.sender][msg.requestSelector]
if (msg.callStatus === true && msg.callbackStatus === true) {
stat.callSucceeded.callbackSucceeded += 1
} else if (msg.callStatus === true && msg.callbackStatus === false) {
stat.callSucceeded.callbackFailed += 1
} else if (msg.callStatus === false && msg.callbackStatus === true) {
stat.callFailed.callbackSucceeded += 1
} else if (msg.callStatus === false && msg.callbackStatus === false) {
stat.callFailed.callbackFailed += 1
} else {
stat.pending += 1
}
})
return stats
}
const normalize = event => ({
...normalizeAMBMessageEvent(event),
txHash: event.transactionHash,
@@ -88,10 +122,13 @@ async function main(mode) {
homeToForeignRequests,
foreignToHomeRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations
foreignToHomeConfirmations,
informationRequests,
informationResponses
} = await eventsInfo(mode)
const homeToForeign = homeToForeignRequests.map(normalize).map(addExecutionStatus(homeToForeignConfirmations))
const foreignToHome = foreignToHomeRequests.map(normalize).map(addExecutionStatus(foreignToHomeConfirmations))
const infoRequests = informationRequests.map(normalizeAMBInfoRequest).map(addRetrievalStatus(informationResponses))
for (const event of homeToForeign) {
// AMB contract emits a single UserRequestForSignature event for every home->foreign request.
@@ -146,6 +183,7 @@ async function main(mode) {
floatingMediators,
remotelyControlledMediators,
unknown,
informationReceivers: countInfoRequests(infoRequests),
lastChecked: Math.floor(Date.now() / 1000)
}
}

View File

@@ -46,29 +46,19 @@ async function main(bridgeMode, eventsInfo) {
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc20token().call()
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
let investedAmountInDai = 0
let bridgeDsrBalance = 0
let displayChaiToken = false
try {
logger.debug('calling foreignBridge.methods.isChaiTokenEnabled')
if (await foreignBridge.methods.isChaiTokenEnabled().call()) {
displayChaiToken = true
logger.debug('calling foreignBridge.methods.investedAmountInDai')
investedAmountInDai = await foreignBridge.methods.investedAmountInDai().call()
logger.debug('calling foreignBridge.methods.dsrBalance')
bridgeDsrBalance = await foreignBridge.methods.dsrBalance().call()
} else {
logger.debug('Chai token is currently disabled')
}
} catch (e) {
logger.debug('Methods for chai token are not present')
}
logger.debug('calling erc20Contract.methods.balanceOf')
const foreignErc20Balance = await erc20Contract.methods
.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS)
.call({}, foreignDelayedBlockNumber)
let foreignErc20BalanceBN = new BN(foreignErc20Balance).plus(lateForeignConfirmationsTotalValue)
try {
logger.debug('calling foreignBridge.methods.investedAmount')
const invested = await foreignBridge.methods.investedAmount(erc20Address).call({}, foreignDelayedBlockNumber)
foreignErc20BalanceBN = foreignErc20BalanceBN.plus(invested)
} catch (_) {
logger.debug('compounding related methods are not present in the foreign bridge')
}
const homeBridge = new web3Home.eth.Contract(HOME_ERC_TO_NATIVE_ABI, COMMON_HOME_BRIDGE_ADDRESS)
logger.debug('calling homeBridge.methods.blockRewardContract')
@@ -84,30 +74,16 @@ async function main(bridgeMode, eventsInfo) {
const mintedCoinsBN = new BN(mintedCoins)
const burntCoinsBN = new BN(burntCoins)
const totalSupplyBN = mintedCoinsBN.minus(burntCoinsBN)
const foreignErc20BalanceBN = new BN(foreignErc20Balance).plus(lateForeignConfirmationsTotalValue)
const investedAmountInDaiBN = new BN(investedAmountInDai)
const bridgeDsrBalanceBN = new BN(bridgeDsrBalance)
const diff = foreignErc20BalanceBN
.plus(investedAmountInDaiBN)
.minus(totalSupplyBN)
.toFixed()
const foreign = {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
}
if (displayChaiToken) {
foreign.investedErc20Balance = Web3Utils.fromWei(investedAmountInDai)
foreign.accumulatedInterest = Web3Utils.fromWei(bridgeDsrBalanceBN.minus(investedAmountInDaiBN).toString(10))
}
const diff = foreignErc20BalanceBN.minus(totalSupplyBN).toFixed()
logger.debug('Done')
return {
home: {
totalSupply: Web3Utils.fromWei(totalSupplyBN.toFixed())
},
foreign,
foreign: {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
},
balanceDiff: Number(Web3Utils.fromWei(diff)),
...blockRanges,
lastChecked: Math.floor(Date.now() / 1000)

View File

@@ -21,7 +21,9 @@ async function main(bridgeMode, eventsInfo) {
homeToForeignConfirmations,
homeToForeignRequests,
foreignToHomeConfirmations,
foreignToHomeRequests
foreignToHomeRequests,
informationRequests,
informationResponses
} = eventsInfo
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
@@ -34,7 +36,9 @@ async function main(bridgeMode, eventsInfo) {
fromForeignToHomeDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length,
home: {
toForeign: homeToForeignRequests.length,
fromForeign: foreignToHomeConfirmations.length
fromForeign: foreignToHomeConfirmations.length,
informationRequests: informationRequests.length,
informationResponses: informationResponses.length
},
foreign: {
fromHome: homeToForeignConfirmations.length,

View File

@@ -11,7 +11,6 @@ const {
OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI
} = require('../../commons')
const { normalizeEventInformation } = require('./message')
const { filterTransferBeforeES } = require('./tokenUtils')
const { writeFile, readCacheFile } = require('./file')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./web3')
const { getPastEvents } = require('./web3Cache')
@@ -129,6 +128,27 @@ async function main(mode) {
})).map(normalizeEvent)
foreignToHomeRequests = [...foreignToHomeRequests, ...foreignToHomeRequestsNew]
let informationRequests
let informationResponses
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
logger.debug("calling homeBridge.getPastEvents('UserRequestForInformation')")
informationRequests = (await getPastEvents(homeBridge, {
event: 'UserRequestForInformation',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
logger.debug("calling foreignBridge.getPastEvents('InformationRetrieved')")
informationResponses = (await getPastEvents(homeBridge, {
event: 'InformationRetrieved',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeBlockNumber,
safeToBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
}
if (isExternalErc20) {
logger.debug("calling erc20Contract.getPastEvents('Transfer')")
let transferEvents = (await getPastEvents(erc20Contract, {
@@ -139,80 +159,32 @@ async function main(mode) {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
},
chain: 'foreign'
}))
.map(normalizeEvent)
.filter(e => e.recipient !== ZERO_ADDRESS) // filter mint operation during SCD-to-MCD swaps
.filter(e => e.recipient.toLowerCase() !== '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643') // filter cDai withdraws during compounding
// Get transfer events for each previously used Sai token
const saiTokenAddress = '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, saiTokenAddress)
logger.debug('Half duplex token:', saiTokenAddress)
logger.debug("calling halfDuplexTokenContract.getPastEvents('Transfer')")
// https://etherscan.io/tx/0xd0c3c92c94e05bc71256055ce8c4c993e047f04e04f3283a04e4cb077b71f6c6
const blockNumberHalfDuplexDisabled = 9884448
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: Math.min(blockNumberHalfDuplexDisabled, foreignDelayedBlockNumber),
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
},
chain: 'foreign'
})).map(normalizeEvent)
let directTransfers = transferEvents
const tokensSwappedAbiExists = FOREIGN_ABI.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
if (tokensSwappedAbiExists) {
logger.debug('collecting half duplex tokens participated in the bridge balance')
logger.debug("calling foreignBridge.getPastEvents('TokensSwapped')")
const tokensSwappedEvents = await getPastEvents(foreignBridge, {
event: 'TokensSwapped',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber,
chain: 'foreign',
safeToBlock: foreignDelayedBlockNumber
})
// Get token swap events emitted by foreign bridge
const bridgeTokensSwappedEvents = tokensSwappedEvents.filter(e => e.address === COMMON_FOREIGN_BRIDGE_ADDRESS)
// Get transfer events for each previous erc20
const uniqueTokenAddressesSet = new Set(bridgeTokensSwappedEvents.map(e => e.returnValues.from))
// Exclude chai token from previous erc20
try {
logger.debug('calling foreignBridge.chaiToken() to remove it from half duplex tokens list')
const chaiToken = await foreignBridge.methods.chaiToken().call()
uniqueTokenAddressesSet.delete(chaiToken)
} catch (e) {
logger.debug('call to foreignBridge.chaiToken() failed')
}
// Exclude dai token from previous erc20
try {
logger.debug('calling foreignBridge.erc20token() to remove it from half duplex tokens list')
const daiToken = await foreignBridge.methods.erc20token().call()
uniqueTokenAddressesSet.delete(daiToken)
} catch (e) {
logger.debug('call to foreignBridge.erc20token() failed')
}
const uniqueTokenAddresses = [...uniqueTokenAddressesSet]
await Promise.all(
uniqueTokenAddresses.map(async tokenAddress => {
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, tokenAddress)
logger.debug('Half duplex token:', tokenAddress)
logger.debug("calling halfDuplexTokenContract.getPastEvents('Transfer')")
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignDelayedBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
},
chain: 'foreign'
})).map(normalizeEvent)
// Remove events after the ES
logger.debug('filtering half duplex transfers happened before ES')
const validHalfDuplexTransfers = await filterTransferBeforeES(halfDuplexTransferEvents)
transferEvents = [...validHalfDuplexTransfers, ...transferEvents]
})
)
// filter transfer that is part of a token swap
directTransfers = transferEvents.filter(
e =>
bridgeTokensSwappedEvents.findIndex(
t => t.transactionHash === e.referenceTx && e.recipient === ZERO_ADDRESS
) === -1
)
}
transferEvents = [...halfDuplexTransferEvents, ...transferEvents]
// Get transfer events that didn't have a UserRequestForAffirmation event in the same transaction
directTransfers = directTransfers.filter(
const directTransfers = transferEvents.filter(
e => foreignToHomeRequests.findIndex(t => t.referenceTx === e.referenceTx) === -1
)
@@ -225,6 +197,8 @@ async function main(mode) {
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests,
informationRequests,
informationResponses,
isExternalErc20,
bridgeMode,
homeBlockNumber,

View File

@@ -8,8 +8,8 @@ function readFile(filePath, parseJson = true) {
const json = JSON.parse(content)
const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked
return Object.assign({}, json, { timeDiff })
} catch (e) {
console.error('readFlle', e)
} catch (_) {
console.error(`File ${filePath} does not exist`)
return {
error: 'the bridge statistics are not available'
}

View File

@@ -28,6 +28,21 @@ function addExecutionStatus(processedList) {
}
}
function addRetrievalStatus(retrievedInfoList) {
const statuses = {}
retrievedInfoList.forEach(e => {
statuses[e.returnValues.messageId] = {
callStatus: e.returnValues.status,
callbackStatus: e.returnValues.callbackStatus
}
})
return deliveredMsg => {
deliveredMsg.callStatus = statuses[deliveredMsg.messageId].callStatus
deliveredMsg.callbackStatus = statuses[deliveredMsg.messageId].callbackStatus
return deliveredMsg
}
}
/**
* Normalizes the different event objects to facilitate data processing
* @param {Object} event
@@ -89,6 +104,7 @@ module.exports = {
deliveredMsgNotProcessed,
processedMsgNotDelivered,
addExecutionStatus,
addRetrievalStatus,
normalizeEventInformation,
eventWithoutReference,
unclaimedHomeToForeignRequests,

View File

@@ -1,27 +0,0 @@
// https://etherscan.io/tx/0xd0c3c92c94e05bc71256055ce8c4c993e047f04e04f3283a04e4cb077b71f6c6
const blockNumberHalfDuplexDisabled = 9884448
/**
* Returns true if the event was before the bridge stopped supporting half duplex transfers.
*/
async function transferBeforeES(event) {
return event.blockNumber < blockNumberHalfDuplexDisabled
}
async function filterTransferBeforeES(array) {
const newArray = []
// Iterate events from newer to older
for (let i = array.length - 1; i >= 0; i--) {
const beforeES = await transferBeforeES(array[i])
if (beforeES) {
// add element to first position so the new array will have the same order
newArray.unshift(array[i])
}
}
return newArray
}
module.exports = {
filterTransferBeforeES,
blockNumberHalfDuplexDisabled
}

View File

@@ -1,6 +1,5 @@
require('dotenv').config()
const Web3Utils = require('web3').utils
const fetch = require('node-fetch')
const logger = require('./logger')('validators')
const { getBridgeABIs, BRIDGE_VALIDATORS_ABI, gasPriceFromSupplier } = require('../commons')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./utils/web3')
@@ -81,7 +80,7 @@ async function main(bridgeMode) {
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
logger.debug('calling home getGasPrices')
homeGasPrice =
(await gasPriceFromSupplier(() => fetch(COMMON_HOME_GAS_PRICE_SUPPLIER_URL), homeGasPriceSupplierOpts)) ||
(await gasPriceFromSupplier(COMMON_HOME_GAS_PRICE_SUPPLIER_URL, homeGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_HOME_GAS_PRICE_FALLBACK)
homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
homeTxCost = homeGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_HOME_TX_LIMIT))
@@ -93,13 +92,9 @@ async function main(bridgeMode) {
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
logger.debug('calling foreign getGasPrices')
const fetchFn =
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL === 'gas-price-oracle'
? null
: () => fetch(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL)
foreignGasPrice =
(await gasPriceFromSupplier(fetchFn, foreignGasPriceSupplierOpts)) ||
(await gasPriceFromSupplier(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL, foreignGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_FOREIGN_GAS_PRICE_FALLBACK)
foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_FOREIGN_TX_LIMIT))

View File

@@ -4,7 +4,8 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "mocha --exit",
"start": "mocha",
"generate-amb-tx": "node ./scripts/generate-amb-tx.js",
"lint": "eslint . --ignore-path ../.eslintignore",
"amb": "mocha test/amb.js",
"erc-to-native": "mocha test/ercToNative.js",

View File

@@ -10,7 +10,7 @@ case "$mode" in
;;
esac
MODE="$mode" ../e2e-commons/up.sh deploy blocks oracle oracle-validator-2 oracle-validator-3
MODE="$mode" ../e2e-commons/up.sh deploy generate-amb-tx manual-amb-relay blocks oracle oracle-validator-2 oracle-validator-3
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run start $script
rc=$?

View File

@@ -0,0 +1,29 @@
const Web3 = require('web3')
const { user, homeRPC, foreignRPC, amb2: amb } = require('../../e2e-commons/constants.json')
const { BOX_ABI } = require('../../commons')
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(foreignRPC.URL))
homeWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
const opts = {
from: user.address,
gas: 400000,
gasPrice: '1'
}
const homeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.homeBox, opts)
const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox, opts)
async function main() {
const res1 = await homeBox.methods.setValueOnOtherNetwork(123, amb.home, amb.foreignBox).send()
const res2 = await foreignBox.methods.setValueOnOtherNetwork(456, amb.foreign, amb.homeBox).send()
const res3 = await foreignBox.methods.setValueOnOtherNetwork(789, amb.foreign, amb.homeBox).send()
console.log(res1.transactionHash)
console.log(res2.transactionHash)
console.log(res3.transactionHash)
}
main()

View File

@@ -1,8 +1,9 @@
const Web3 = require('web3')
const assert = require('assert')
const { ASYNC_CALL_ERRORS } = require('../../oracle/src/utils/constants')
const { user, homeRPC, foreignRPC, amb, validator } = require('../../e2e-commons/constants.json')
const { uniformRetry } = require('../../e2e-commons/utils')
const { BOX_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI } = require('../../commons')
const { BOX_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI, ambInformationSignatures } = require('../../commons')
const { delay, setRequiredSignatures } = require('./utils')
const { toBN } = Web3.utils
@@ -26,27 +27,49 @@ 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)
function validateBlock(web3, serialized, block) {
assert.strictEqual(serialized.length, 2 + 64 * 12)
const values = web3.eth.abi.decodeParameter(
'(uint256,bytes32,address,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256)',
serialized
)
assert.strictEqual(values[0], block.number.toString(), 'wrong block number returned')
assert.strictEqual(values[1], block.hash, 'wrong block hash returned')
assert.strictEqual(values[2], block.miner, 'wrong block miner returned')
assert.strictEqual(values[3], block.gasUsed.toString(), 'wrong block gasUsed returned')
assert.strictEqual(values[4], block.gasLimit.toString(), 'wrong block gasLimit returned')
assert.strictEqual(values[5], block.parentHash, 'wrong block parentHash returned')
assert.strictEqual(values[6], block.receiptsRoot, 'wrong block receiptsRoot returned')
assert.strictEqual(values[7], block.stateRoot, 'wrong block stateRoot returned')
assert.strictEqual(values[8], block.transactionsRoot, 'wrong block transactionsRoot returned')
assert.strictEqual(values[9], block.timestamp.toString(), 'wrong block timestamp returned')
assert.strictEqual(values[10], block.difficulty, 'wrong block difficulty returned')
assert.strictEqual(values[11], block.totalDifficulty, 'wrong block totalDifficulty returned')
}
function validateTransaction(web3, serialized, tx) {
assert.strictEqual(serialized.length, 64 * 13 + tx.input.length + 56)
const values = web3.eth.abi.decodeParameter(
'(bytes32,uint256,bytes32,uint256,address,address,uint256,uint256,uint256,uint256,bytes)',
serialized
)
assert.strictEqual(values[0], tx.hash, 'wrong txHash returned')
assert.strictEqual(values[1], tx.blockNumber.toString(), 'wrong tx blockNumber returned')
assert.strictEqual(values[2], tx.blockHash.toString(), 'wrong tx blockHash returned')
assert.strictEqual(values[3], tx.transactionIndex.toString(), 'wrong tx transactionIndex returned')
assert.strictEqual(values[4], tx.from, 'wrong tx from returned')
assert.strictEqual(values[5], tx.to, 'wrong tx to returned')
assert.strictEqual(values[6], tx.value, 'wrong tx value returned')
assert.strictEqual(values[7], tx.nonce.toString(), 'wrong tx nonce returned')
assert.strictEqual(values[8], tx.gas.toString(), 'wrong tx gas returned')
assert.strictEqual(values[9], tx.gasPrice, 'wrong tx gasPrice returned')
assert.strictEqual(values[10], tx.input, 'wrong tx data returned')
}
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) {
for (const method of ambInformationSignatures) {
const selector = homeWeb3.utils.soliditySha3(method)
await homeBridge.methods.enableAsyncRequestSelector(selector, true).send({ from: validator.address })
}
@@ -80,6 +103,19 @@ describe('arbitrary message bridging', () => {
}
})
})
if (process.env.ULTIMATE !== 'true') {
describe('Confirm Relay', () => {
it('should process lost affirmation-request via confirm relay', async () => {
const value = await homeBox.methods.value().call()
assert(value === '789', 'incorrect value')
})
it('should process lost signature-request & collected-signatures via confirm relay', async () => {
const value = await foreignBox.methods.value().call()
assert(value === '123', 'incorrect value')
})
})
}
describe('Home to Foreign', () => {
describe('Subsidized Mode', () => {
it('should bridge message', async () => {
@@ -88,12 +124,13 @@ describe('arbitrary message bridging', () => {
const initialValue = await foreignBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
await homeBox.methods
const res = await homeBox.methods
.setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox)
.send()
.catch(e => {
console.error(e)
})
console.log(res.transactionHash)
// check that value changed and balance decreased
await uniformRetry(async retry => {
@@ -186,12 +223,13 @@ describe('arbitrary message bridging', () => {
const initialValue = await homeBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
await foreignBox.methods
const res = await foreignBox.methods
.setValueOnOtherNetwork(newValue, amb.foreign, amb.homeBox)
.send()
.catch(e => {
console.error(e)
})
console.log(res.transactionHash)
// check that value changed and balance decreased
await uniformRetry(async retry => {
@@ -266,7 +304,7 @@ describe('arbitrary message bridging', () => {
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')
assert.strictEqual(await homeBox.methods.data().call(), ASYNC_CALL_ERRORS.REVERT, 'returned data is incorrect')
const data3 = homeWeb3.eth.abi.encodeParameters(
['address', 'address', 'uint256', 'bytes'],
@@ -276,7 +314,7 @@ describe('arbitrary message bridging', () => {
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')
assert.strictEqual(await homeBox.methods.data().call(), ASYNC_CALL_ERRORS.REVERT, 'returned data is incorrect')
})
it('should make async eth_call for specific block', async () => {
@@ -285,7 +323,7 @@ describe('arbitrary message bridging', () => {
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]
[amb.foreignBox, foreignBox.methods.value().encodeABI(), 25]
)
const data2 = homeWeb3.eth.abi.encodeParameters(
['address', 'bytes', 'uint256'],
@@ -317,6 +355,11 @@ describe('arbitrary message bridging', () => {
await makeAsyncCall(selector, data3)
assert(!(await homeBox.methods.status().call()), 'status is true')
assert.strictEqual(
await homeBox.methods.data().call(),
ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE,
'returned data is incorrect'
)
})
it('should make async eth_blockNumber', async () => {
@@ -336,15 +379,8 @@ describe('arbitrary message bridging', () => {
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')
validateBlock(homeWeb3, data, block)
})
it('should make async eth_getBlockByNumber and return latest block', async () => {
@@ -354,7 +390,7 @@ describe('arbitrary message bridging', () => {
assert(await homeBox.methods.status().call(), 'status is false')
const data = await homeBox.methods.data().call()
assert.strictEqual(data.length, 2 + 64 * 3)
assert.strictEqual(data.length, 2 + 64 * 12)
})
it('should make async eth_getBlockByHash', async () => {
@@ -366,16 +402,7 @@ describe('arbitrary message bridging', () => {
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')
validateBlock(homeWeb3, data, block)
})
it('should make async eth_getBalance', async () => {
@@ -463,7 +490,7 @@ describe('arbitrary message bridging', () => {
})
it('should make async eth_getTransactionByHash', async () => {
const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6'
const txHash = '0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
const tx = await foreignWeb3.eth.getTransaction(txHash)
const selector = homeWeb3.utils.soliditySha3('eth_getTransactionByHash(bytes32)')
@@ -471,32 +498,11 @@ describe('arbitrary message bridging', () => {
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')
validateTransaction(homeWeb3, data, tx)
})
it('should make async eth_getTransactionReceipt', async () => {
const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6'
const txHash = '0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
const receipt = await foreignWeb3.eth.getTransactionReceipt(txHash)
const selector = homeWeb3.utils.soliditySha3('eth_getTransactionReceipt(bytes32)')
@@ -504,18 +510,25 @@ describe('arbitrary message bridging', () => {
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)
const values = homeWeb3.eth.abi.decodeParameter(
'(bytes32,uint256,bytes32,uint256,address,address,uint256,bool,(address,bytes32[],bytes)[])',
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')
assert.strictEqual(values[2], receipt.blockHash, 'wrong tx blockHash returned')
assert.strictEqual(values[3], receipt.transactionIndex.toString(), 'wrong tx transactionIndex returned')
assert.strictEqual(values[4].toLowerCase(), receipt.from, 'wrong tx from returned')
assert.strictEqual(values[5].toLowerCase(), receipt.to, 'wrong tx to returned')
assert.strictEqual(values[6], receipt.gasUsed.toString(), 'wrong gasUsed to returned')
assert.strictEqual(values[7], receipt.status, 'wrong tx status returned')
assert.strictEqual(values[8].length, 1, 'wrong logs length returned')
assert.strictEqual(values[8][0][0], receipt.logs[0].address, 'wrong log address returned')
assert.strictEqual(values[8][0][1].length, 2, 'wrong log topics length returned')
assert.strictEqual(values[8][0][1][0], receipt.logs[0].topics[0], 'wrong event signature returned')
assert.strictEqual(values[8][0][1][1], receipt.logs[0].topics[1], 'wrong message id returned')
assert.strictEqual(values[8][0][2], receipt.logs[0].data, 'wrong log data returned')
})
it('should make async eth_getStorageAt', async () => {

View File

@@ -33,6 +33,10 @@ const homeBridge = new homeWeb3.eth.Contract(HOME_ERC_TO_NATIVE_ABI, COMMON_HOME
describe('erc to native', () => {
before(async () => {
console.log('Initializing interest')
await foreignBridge.methods
.initializeInterest(ercToNativeBridge.foreignToken, 1, 1, validator.address)
.send({ from: validator.address, gas: '4000000' })
if (process.env.ULTIMATE === 'true') {
return
}
@@ -100,6 +104,8 @@ describe('erc to native', () => {
const transferValue = homeWeb3.utils.toWei('0.05')
// transfer that should not be processed by the filter
await erc20Token.methods.transfer(secondUser.address, transferValue).send({ from: user.address, gas: 100000 })
// send tokens to foreign bridge
await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
@@ -110,6 +116,7 @@ describe('erc to native', () => {
.catch(e => {
console.error(e)
})
await foreignBridge.methods.investDai().send({ from: validator.address, gas: '4000000' })
// check that balance increases
await uniformRetry(async retry => {

View File

@@ -1 +1 @@
--timeout 120000
--timeout 120000 --exit

View File

@@ -6,6 +6,7 @@ module.exports = {
...baseConfig,
main: baseConfig.foreign,
event: 'UserRequestForAffirmation',
sender: 'home',
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@@ -8,12 +8,15 @@ const {
FOREIGN_AMB_ABI
} = require('../../commons')
const { web3Home, web3Foreign } = require('../src/services/web3')
const { privateKeyToAddress } = require('../src/utils/utils')
const { add0xPrefix, privateKeyToAddress, loadKeystore } = require('../src/utils/utils')
const { EXIT_CODES } = require('../src/utils/constants')
const {
ORACLE_BRIDGE_MODE,
ORACLE_VALIDATOR_ADDRESS,
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
ORACLE_VALIDATOR_KEYSTORE_PATH,
ORACLE_VALIDATOR_KEYSTORE_PASSWORD,
ORACLE_MAX_PROCESSING_TIME,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_BRIDGE_ADDRESS,
@@ -79,9 +82,33 @@ const foreignConfig = {
const maxProcessingTime =
parseInt(ORACLE_MAX_PROCESSING_TIME, 10) || 4 * Math.max(homeConfig.pollingInterval, foreignConfig.pollingInterval)
let validatorPrivateKey
let validatorAddress = ORACLE_VALIDATOR_ADDRESS
if (ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY) {
validatorPrivateKey = add0xPrefix(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY)
const derived = privateKeyToAddress(validatorPrivateKey)
if (ORACLE_VALIDATOR_ADDRESS && derived.toLowerCase() !== ORACLE_VALIDATOR_ADDRESS.toLowerCase()) {
console.error(
`Derived address from private key - ${derived} is different from ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}`
)
process.exit(EXIT_CODES.INCOMPATIBILITY)
}
validatorAddress = derived
} else if (ORACLE_VALIDATOR_KEYSTORE_PATH) {
try {
const keystore = loadKeystore(ORACLE_VALIDATOR_KEYSTORE_PATH, ORACLE_VALIDATOR_KEYSTORE_PASSWORD)
validatorPrivateKey = keystore.privateKey
validatorAddress = keystore.address
} catch (e) {
console.error(`Can't load keystore file: ${e.message}`)
process.exit(EXIT_CODES.INCOMPATIBILITY)
}
}
module.exports = {
eventFilter: {},
validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY),
validatorPrivateKey,
validatorAddress,
maxProcessingTime,
shutdownKey: 'oracle-shutdown',
home: homeConfig,

View File

@@ -6,6 +6,7 @@ module.exports = {
...baseConfig,
main: baseConfig.home,
event: 'CollectedSignatures',
sender: 'foreign',
queue: 'foreign-prioritized',
name: `watcher-${id}`,
id

View File

@@ -8,6 +8,7 @@ module.exports = {
web3ForeignArchive: web3ForeignArchive || baseConfig.foreign.web3,
main: baseConfig.home,
event: 'UserRequestForInformation',
sender: 'home',
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@@ -6,6 +6,7 @@ module.exports = {
...baseConfig,
main: baseConfig.home,
event: 'UserRequestForSignature',
sender: 'home',
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@@ -1,20 +1,7 @@
const baseConfig = require('./base.config')
const { ERC20_ABI } = require('../../commons')
const { ERC20_ABI, ZERO_ADDRESS } = require('../../commons')
const { EXIT_CODES } = require('../src/utils/constants')
const initialChecksJson = process.argv[3]
if (!initialChecksJson) {
throw new Error('initial check parameter was not provided.')
}
let initialChecks
try {
initialChecks = JSON.parse(initialChecksJson)
} catch (e) {
throw new Error('Error on decoding values from initial checks.')
}
const id = `${baseConfig.id}-transfer`
if (baseConfig.id !== 'erc-native') {
@@ -22,14 +9,15 @@ if (baseConfig.id !== 'erc-native') {
process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED)
}
// exact address of the token contract is set in the watcher.js checkConditions() function
baseConfig.foreign.eventContract = new baseConfig.foreign.web3.eth.Contract(ERC20_ABI, ZERO_ADDRESS)
module.exports = {
...baseConfig,
main: {
...baseConfig.foreign,
eventContract: new baseConfig.foreign.web3.eth.Contract(ERC20_ABI, initialChecks.bridgeableTokenAddress)
},
main: baseConfig.foreign,
event: 'Transfer',
eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS },
eventFilter: { to: baseConfig.foreign.bridgeAddress },
sender: 'home',
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@@ -0,0 +1,15 @@
---
version: '2.4'
services:
interestFetcher:
cpus: 0.1
mem_limit: 500m
image: poanetwork/tokenbridge-oracle:latest
env_file: ./.env
environment:
NODE_ENV: production
INTEREST_FETCHER_PRIVATE_KEY: ${INTEREST_FETCHER_PRIVATE_KEY}
INTEREST_FETCH_CONTRACT_ADDRESS: '0xCd152c7Bd4189Ddee97EaBb783FC5cD93CF2D230'
INTERVAL: 300000
restart: unless-stopped
entrypoint: yarn helper:interestFether

52
oracle/esController.sol Normal file
View File

@@ -0,0 +1,52 @@
//SPDX-License-Identifier: GPL-3.0-only
/*
This contract can be used together with the emergency shutdown
functionality of the TokenBridge oracles.
*/
pragma solidity 0.7.6;
contract PauseController {
address public manager;
bool internal paused;
bytes32 private immutable ID;
constructor (string memory _id, address _manager) {
require(bytes(_id).length <= 32);
bytes32 id;
assembly {
id := mload(add(_id, 32))
}
ID = id;
manager = _manager;
}
modifier onlyManager() {
require(msg.sender == manager);
_;
}
function changeManager(address _newmanager) external onlyManager {
require(_newmanager != address(0));
manager = _newmanager;
}
function pause() external onlyManager {
paused = true;
}
function play() external onlyManager {
paused = false;
}
function isPaused() external view returns(bool) {
return paused;
}
function id() external view returns(string memory) {
return string(abi.encodePacked(ID));
}
}

View File

@@ -13,7 +13,12 @@
"sender:home": "./scripts/start-worker.sh sender home-sender",
"sender:foreign": "./scripts/start-worker.sh sender foreign-sender",
"confirm:transfer": "./scripts/start-worker.sh confirmRelay transfer-watcher",
"confirm:affirmation-request": "./scripts/start-worker.sh confirmRelay affirmation-request-watcher",
"confirm:signature-request": "./scripts/start-worker.sh confirmRelay signature-request-watcher",
"confirm:collected-signatures": "./scripts/start-worker.sh confirmRelay collected-signatures-watcher",
"confirm:information-request": "./scripts/start-worker.sh confirmRelay information-request-watcher",
"manager:shutdown": "./scripts/start-worker.sh shutdownManager shutdown-manager",
"helper:interestFether": "node ./scripts/interestFetcher.js",
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer, sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn sender:home' 'yarn sender:foreign'",
"test": "NODE_ENV=test mocha",
"test:watch": "NODE_ENV=test mocha --watch --reporter=min",

View File

@@ -1,26 +0,0 @@
require('../env')
const { getTokensState } = require('../src/utils/tokenState')
const { FOREIGN_ERC_TO_NATIVE_ABI } = require('../../commons')
const { web3Foreign } = require('../src/services/web3')
const emptyLogger = {
debug: () => {},
info: () => {}
}
async function initialChecks() {
const { ORACLE_BRIDGE_MODE, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
let result = {}
if (ORACLE_BRIDGE_MODE === 'ERC_TO_NATIVE') {
const bridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
result = await getTokensState(bridge, emptyLogger)
}
console.log(JSON.stringify(result))
return result
}
const result = initialChecks()
module.exports = result

View File

@@ -0,0 +1,65 @@
require('../env')
const { isAddress } = require('web3').utils
const { privateKeyToAddress, setIntervalAndRun } = require('../src/utils/utils')
const { EXIT_CODES } = require('../src/utils/constants')
const { web3Home } = require('../src/services/web3')
const { sendTx } = require('../src/tx/sendTx')
const privateKey = process.env.INTEREST_FETCHER_PRIVATE_KEY
const interval = process.env.INTERVAL ? parseInt(process.env.INTERVAL, 10) : 3600 * 1000
const contractAddress = process.env.INTEREST_FETCH_CONTRACT_ADDRESS
if (!privateKey) {
console.error('Environment variable INTEREST_FETCHER_PRIVATE_KEY is not set')
process.exit(EXIT_CODES.GENERAL_ERROR)
}
if (interval < 300 * 1000) {
console.error('Interval is to small, should be at least 5 minutes')
process.exit(EXIT_CODES.GENERAL_ERROR)
}
if (!isAddress(contractAddress)) {
console.error('Invalid contract address provided', contractAddress)
process.exit(EXIT_CODES.GENERAL_ERROR)
}
const gasPrice = process.env.COMMON_HOME_GAS_PRICE_FALLBACK || '1000000000'
async function main() {
// assuming that we are using this contract - https://github.com/omni/interest-fetcher-contract
const contract = new web3Home.eth.Contract([{ name: 'fetchInterest', type: 'function', inputs: [] }], contractAddress)
const chainId = await web3Home.eth.getChainId()
const data = contract.methods.fetchInterest().encodeABI()
const senderAddress = privateKeyToAddress(privateKey)
console.log(
`Initialized, chainId=${chainId}, data=${data}, contract=${contractAddress}, interval=${interval / 1000}s`
)
await setIntervalAndRun(async () => {
let gasLimit
try {
gasLimit = await contract.methods.fetchInterest().estimateGas()
} catch (e) {
console.log('Gas limit estimation failed, will retry later', new Date())
return
}
const nonce = await web3Home.eth.getTransactionCount(senderAddress)
const txHash = await sendTx({
privateKey,
to: contractAddress,
data,
nonce,
gasPrice,
gasLimit: Math.round(gasLimit * 1.5),
amount: '0',
chainId,
web3: web3Home
})
console.log('Sent transaction with fetch interest', txHash, new Date())
}, interval)
}
main()

View File

@@ -8,12 +8,10 @@ LOGS_DIR="logs/"
WORKER="${WORKERS_DIR}${1}.js"
CONFIG="${2}.config.js"
LOG="${LOGS_DIR}${2}.txt"
TX_HASH="${3}"
CHECKS=$(node scripts/initialChecks.js)
TX_HASH=${@:3}
if [ "${NODE_ENV}" = "production" ]; then
exec node "${WORKER}" "${CONFIG}" "$CHECKS" "$TX_HASH"
exec node "${WORKER}" "${CONFIG}" $TX_HASH
else
node "${WORKER}" "${CONFIG}" "$CHECKS" "$TX_HASH" | tee -a "${LOG}" | pino-pretty
node "${WORKER}" "${CONFIG}" $TX_HASH | tee -a "${LOG}" | pino-pretty
fi

View File

@@ -1,22 +1,39 @@
require('../env')
const path = require('path')
const fs = require('fs')
const { isAttached, connectWatcherToQueue, connection } = require('./services/amqpClient')
const logger = require('./services/logger')
const GasPrice = require('./services/gasPrice')
const { getNonce, getChainId, getEventsFromTx } = require('./tx/web3')
const { sendTx } = require('./tx/sendTx')
const { getTokensState } = require('./utils/tokenState')
const { checkHTTPS, watchdog, syncForEach, addExtraGas } = require('./utils/utils')
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants')
const { ORACLE_VALIDATOR_ADDRESS, ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, ORACLE_ALLOW_HTTP_FOR_RPC } = process.env
const { ORACLE_ALLOW_HTTP_FOR_RPC } = process.env
if (process.argv.length < 5) {
if (process.argv.length < 4) {
logger.error('Please check the number of arguments, transaction hash is not present')
process.exit(EXIT_CODES.GENERAL_ERROR)
}
const config = require(path.join('../config/', process.argv[2]))
const txHash = process.argv[4]
const { web3, eventContract, chain, bridgeContract } = config.main
const isTxHash = txHash => txHash.length === 66 && web3.utils.isHexStrict(txHash)
function readTxHashes(filePath) {
return fs
.readFileSync(filePath)
.toString()
.split('\n')
.map(v => v.trim())
.filter(isTxHash)
}
const txHashesArgs = process.argv.slice(3)
const rawTxHashes = txHashesArgs.filter(isTxHash)
const txHashesFiles = txHashesArgs.filter(path => fs.existsSync(path)).flatMap(readTxHashes)
const txHashes = [...rawTxHashes, ...txHashesFiles]
const processSignatureRequests = require('./events/processSignatureRequests')(config)
const processCollectedSignatures = require('./events/processCollectedSignatures')(config)
@@ -27,15 +44,13 @@ const processAMBCollectedSignatures = require('./events/processAMBCollectedSigna
const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config)
const processAMBInformationRequests = require('./events/processAMBInformationRequests')(config)
const { web3, eventContract } = config.main
let attached
async function initialize() {
try {
const checkHttps = checkHTTPS(ORACLE_ALLOW_HTTP_FOR_RPC, logger)
web3.currentProvider.urls.forEach(checkHttps(config.chain))
web3.currentProvider.urls.forEach(checkHttps(chain))
attached = await isAttached()
if (attached) {
@@ -59,12 +74,12 @@ async function runMain({ sendToQueue }) {
const sendJob = attached ? sendToQueue : sendJobTx
if (!attached || connection.isConnected()) {
if (config.maxProcessingTime) {
await watchdog(() => main({ sendJob, txHash }), config.maxProcessingTime, () => {
await watchdog(() => main({ sendJob, txHashes }), config.maxProcessingTime, () => {
logger.fatal('Max processing time reached')
process.exit(EXIT_CODES.MAX_TIME_REACHED)
})
} else {
await main({ sendJob, txHash })
await main({ sendJob, txHashes })
}
} else {
setTimeout(() => {
@@ -99,27 +114,37 @@ function processEvents(events) {
}
}
async function main({ sendJob, txHash }) {
try {
const events = await getEventsFromTx({
web3,
contract: eventContract,
event: config.event,
txHash,
filter: config.eventFilter
})
logger.info(`Found ${events.length} ${config.event} events`)
async function main({ sendJob, txHashes }) {
if (config.id === 'erc-native-transfer') {
logger.debug('Getting token address to listen Transfer events')
const state = await getTokensState(bridgeContract, logger)
eventContract.options.address = state.bridgeableTokenAddress
}
if (events.length) {
const job = await processEvents(events)
logger.info('Transactions to send:', job.length)
logger.info(`Processing ${txHashes.length} input transactions`)
for (const txHash of txHashes) {
try {
logger.info({ txHash }, `Processing transaction`)
const events = await getEventsFromTx({
web3,
contract: eventContract,
event: config.event,
txHash,
filter: config.eventFilter
})
logger.info({ txHash }, `Found ${events.length} ${config.event} events`)
if (job.length) {
await sendJob(job)
if (events.length) {
const job = await processEvents(events)
logger.info({ txHash }, 'Transactions to send:', job.length)
if (job.length) {
await sendJob(job)
}
}
} catch (e) {
logger.error(e)
}
} catch (e) {
logger.error(e)
}
await connection.close()
@@ -128,9 +153,13 @@ async function main({ sendJob, txHash }) {
}
async function sendJobTx(jobs) {
const gasPrice = await GasPrice.start(config.chain, true)
await GasPrice.start(chain, true)
const gasPrice = GasPrice.getPrice().toString(10)
const { web3 } = config.sender === 'foreign' ? config.foreign : config.home
const chainId = await getChainId(web3)
let nonce = await getNonce(web3, ORACLE_VALIDATOR_ADDRESS)
let nonce = await getNonce(web3, config.validatorAddress)
await syncForEach(jobs, async job => {
let gasLimit
@@ -145,10 +174,10 @@ async function sendJobTx(jobs) {
const txHash = await sendTx({
data: job.data,
nonce,
gasPrice: gasPrice.toString(10),
gasPrice,
amount: '0',
gasLimit,
privateKey: ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
privateKey: config.validatorPrivateKey,
to: job.to,
chainId,
web3
@@ -167,7 +196,7 @@ async function sendJobTx(jobs) {
)
if (e.message.toLowerCase().includes('insufficient funds')) {
const currentBalance = await web3.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
const currentBalance = await web3.eth.getBalance(config.validatorAddress)
const minimumBalance = gasLimit.multipliedBy(gasPrice)
logger.error(
`Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.`

View File

@@ -15,7 +15,7 @@ async function estimateGas({ web3, homeBridge, validatorContract, message, addre
const gasEstimate = await homeBridge.methods.executeAffirmation(message).estimateGas({
from: address
})
const msgGasLimit = parseAMBHeader(message).gasLimit
const msgGasLimit = Math.ceil((parseAMBHeader(message).gasLimit * 64) / 63)
// message length in bytes
const len = strip0x(message).length / 2 - MIN_AMB_HEADER_LENGTH

View File

@@ -1,6 +1,7 @@
const { toBN } = require('web3').utils
const { zipToObject } = require('../../../utils/utils')
const { ASYNC_CALL_ERRORS, ASYNC_ETH_CALL_MAX_GAS_LIMIT } = require('../../../utils/constants')
const { zipToObject, isRevertError } = require('../../../utils/utils')
const argTypes = {
to: 'address',
@@ -17,14 +18,23 @@ function makeCall(argNames) {
const { blockNumber, ...opts } = zipToObject(argNames, args)
if (blockNumber && toBN(blockNumber).gt(toBN(foreignBlock.number))) {
return [false, '0x']
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
}
const [status, result] = await web3.eth
.call(opts, blockNumber || foreignBlock.number)
.then(result => [true, result], err => [false, err.data])
// different clients might use different default gas limits, so it makes sense to limit it by some large number
if (!opts.gas || toBN(opts.gas).gt(toBN(ASYNC_ETH_CALL_MAX_GAS_LIMIT))) {
opts.gas = ASYNC_ETH_CALL_MAX_GAS_LIMIT
}
return [status, web3.eth.abi.encodeParameter('bytes', result)]
return web3.eth
.call(opts, blockNumber || foreignBlock.number)
.then(result => [true, web3.eth.abi.encodeParameter('bytes', result)])
.catch(e => {
if (isRevertError(e)) {
return [false, ASYNC_CALL_ERRORS.REVERT]
}
throw e
})
}
}

View File

@@ -1,5 +1,7 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
async function call(web3, data, foreignBlock) {
const address = web3.eth.abi.decodeParameter('address', data)
@@ -12,7 +14,7 @@ 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']
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
}
const balance = await web3.eth.getBalance(address, blockNumber)

View File

@@ -1,3 +1,4 @@
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
const { serializeBlock } = require('./serializers')
async function call(web3, data, foreignBlock) {
@@ -6,7 +7,7 @@ async function call(web3, data, foreignBlock) {
const block = await web3.eth.getBlock(blockHash)
if (block === null || block.number > foreignBlock.number) {
return [false, '0x']
return [false, ASYNC_CALL_ERRORS.NOT_FOUND]
}
return [true, serializeBlock(web3, block)]

View File

@@ -1,12 +1,13 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
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']
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
}
const block = await web3.eth.getBlock(blockNumber)

View File

@@ -1,5 +1,7 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
async function call(web3, data, foreignBlock) {
const { 0: address, 1: slot } = web3.eth.abi.decodeParameters(['address', 'bytes32'], data)
@@ -12,7 +14,7 @@ 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']
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
}
const value = await web3.eth.getStorageAt(address, slot, blockNumber)

View File

@@ -1,3 +1,4 @@
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
const { serializeTx } = require('./serializers')
async function call(web3, data, foreignBlock) {
@@ -6,7 +7,7 @@ async function call(web3, data, foreignBlock) {
const tx = await web3.eth.getTransaction(hash)
if (tx === null || tx.blockNumber > foreignBlock.number) {
return [false, '0x']
return [false, ASYNC_CALL_ERRORS.NOT_FOUND]
}
return [true, serializeTx(web3, tx)]

View File

@@ -1,5 +1,7 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
async function call(web3, data, foreignBlock) {
const address = web3.eth.abi.decodeParameter('address', data)
@@ -12,7 +14,7 @@ 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']
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
}
const nonce = await web3.eth.getTransactionCount(address, blockNumber)

View File

@@ -1,3 +1,4 @@
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
const { serializeReceipt } = require('./serializers')
async function call(web3, data, foreignBlock) {
@@ -6,7 +7,7 @@ async function call(web3, data, foreignBlock) {
const receipt = await web3.eth.getTransactionReceipt(hash)
if (receipt === null || receipt.blockNumber > foreignBlock.number) {
return [false, '0x']
return [false, ASYNC_CALL_ERRORS.NOT_FOUND]
}
return [true, serializeReceipt(web3, receipt)]

View File

@@ -1,15 +1,30 @@
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 args = [
block.number,
block.hash,
block.miner,
block.gasUsed,
block.gasLimit,
block.parentHash,
block.receiptsRoot,
block.stateRoot,
block.transactionsRoot,
block.timestamp,
block.difficulty,
block.totalDifficulty
]
const type = '(uint256,bytes32,address,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256)'
return web3.eth.abi.encodeParameter(type, args)
}
const serializeTx = (web3, tx) => {
const args = [
tx.hash,
tx.blockNumber,
tx.blockHash,
tx.transactionIndex,
tx.from,
tx.to || ZERO_ADDRESS,
tx.value,
@@ -18,16 +33,26 @@ const serializeTx = (web3, tx) => {
tx.gasPrice,
tx.input
]
const types = ['bytes32', 'uint256', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes']
return web3.eth.abi.encodeParameters(types, args)
const type = '(bytes32,uint256,bytes32,uint256,address,address,uint256,uint256,uint256,uint256,bytes)'
return web3.eth.abi.encodeParameter(type, 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)
const args = [
receipt.transactionHash,
receipt.blockNumber,
receipt.blockHash,
receipt.transactionIndex,
receipt.from,
receipt.to || ZERO_ADDRESS,
receipt.gasUsed,
receipt.status,
receipt.logs.map(normalizeLog)
]
const type = '(bytes32,uint256,bytes32,uint256,address,address,uint256,bool,(address,bytes32[],bytes)[])'
return web3.eth.abi.encodeParameter(type, args)
}
module.exports = {

View File

@@ -6,7 +6,16 @@ const logger = require('../../services/logger').child({
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 }) {
async function estimateGas({
web3,
homeBridge,
validatorContract,
messageId,
status,
result,
address,
homeBlockNumber
}) {
try {
const gasEstimate = await homeBridge.methods.confirmInformation(messageId, status, result).estimateGas({
from: address
@@ -15,7 +24,8 @@ async function estimateGas({ web3, homeBridge, validatorContract, messageId, sta
// message length in bytes
const len = strip0x(result).length / 2
const callbackGasLimit = parseInt(await homeBridge.methods.maxGasPerTx().call(), 10)
let callbackGasLimit = parseInt(await homeBridge.methods.maxGasPerTx().call(), 10)
callbackGasLimit = Math.ceil((callbackGasLimit * 64) / 63)
return gasEstimate + callbackGasLimit + estimateExtraGas(len)
} catch (e) {
@@ -51,6 +61,20 @@ async function estimateGas({ web3, homeBridge, validatorContract, messageId, sta
throw new InvalidValidatorError(`${address} is not a validator`)
}
logger.debug('Check if InformationRetrieved event for this message already exists')
const logs = await homeBridge.getPastEvents('InformationRetrieved', {
fromBlock: homeBlockNumber,
toBlock: 'latest',
filter: { messageId }
})
if (logs.length > 0) {
logger.warn(
'This particular message was already signed and processed by other validators.' +
'However, evaluated async call result is different from the one recorded on-chain.'
)
throw new AlreadyProcessedError(e.message)
}
throw new Error('Unknown error while processing message')
}
}

View File

@@ -4,7 +4,13 @@ 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 {
EXIT_CODES,
MAX_CONCURRENT_EVENTS,
EXTRA_GAS_ABSOLUTE,
ASYNC_CALL_ERRORS,
MAX_ASYNC_CALL_RESULT_LENGTH
} = require('../../utils/constants')
const estimateGas = require('./estimateGas')
const { getValidatorContract, getBlock, getBlockNumber, getRequiredBlockConfirmations } = require('../../tx/web3')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
@@ -79,12 +85,17 @@ function processInformationRequestsBuilder(config) {
logger.info({ requestSelector, method: asyncCallMethod, data }, 'Processing async request')
const call = asyncCalls[asyncCallMethod]
const [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => {
let [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => {
if (e instanceof HttpListProviderError) {
throw e
}
return [false, '0x']
logger.error({ error: e.message }, 'Unknown error during async call execution')
throw e
})
if (result.length > 2 + MAX_ASYNC_CALL_RESULT_LENGTH * 2) {
status = false
result = ASYNC_CALL_ERRORS.RESULT_IS_TOO_LONG
}
logger.info({ requestSelector, method: asyncCallMethod, status, result }, 'Request result obtained')
let gasEstimate
@@ -97,7 +108,8 @@ function processInformationRequestsBuilder(config) {
messageId,
status,
result,
address: config.validatorAddress
address: config.validatorAddress,
homeBlockNumber: homeBlock.number
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {

View File

@@ -8,8 +8,6 @@ const estimateGas = require('../processSignatureRequests/estimateGas')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
function processSignatureRequestsBuilder(config) {
@@ -37,7 +35,7 @@ function processSignatureRequestsBuilder(config) {
const { sender, executor } = parseAMBMessage(message)
logger.info({ sender, executor }, `Processing signatureRequest ${messageId}`)
const signature = web3.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`)
const signature = web3.eth.accounts.sign(message, config.validatorPrivateKey)
let gasEstimate
try {

View File

@@ -8,8 +8,6 @@ const estimateGas = require('./estimateGas')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
function processSignatureRequestsBuilder(config) {
@@ -48,7 +46,7 @@ function processSignatureRequestsBuilder(config) {
expectedMessageLength
})
const signature = web3.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`)
const signature = web3.eth.accounts.sign(message, config.validatorPrivateKey)
let gasEstimate
try {

Some files were not shown because too many files have changed in this diff Show More