Compare commits
49 Commits
feature/am
...
master
Author | SHA1 | Date | |
---|---|---|---|
ce515a8635 | |||
|
961b12b9f3 | ||
|
ff9f3fb7d6 | ||
|
297cb67895 | ||
|
bcf16144c1 | ||
|
16f3e9add6 | ||
|
4af882b0ff | ||
|
dbaf7feca7 | ||
|
735aa75f81 | ||
|
910c3759c1 | ||
|
9c2d2f404c | ||
|
2b51d4c209 | ||
|
981231fb47 | ||
|
5bc562e810 | ||
|
72f0d30b52 | ||
|
8ec11d0476 | ||
|
8d732adba1 | ||
|
296e5c5a22 | ||
|
b17fff2b56 | ||
|
4eba91ef7e | ||
|
1e3aa53ab3 | ||
|
a05ff51555 | ||
|
52e1c88b58 | ||
|
d36dcadd34 | ||
|
d543dbb339 | ||
|
bd21cc163e | ||
|
4a0fc936a1 | ||
|
78564afabd | ||
|
70a2c30b4c | ||
|
06a9586148 | ||
|
7379fe4190 | ||
|
e59766c5df | ||
|
fdb18a1a17 | ||
|
4412046f66 | ||
|
0ff224ccd3 | ||
|
5cedacafe5 | ||
|
2e6179f974 | ||
|
4f5e3c47be | ||
|
4c06329153 | ||
|
d53452675e | ||
|
c92f80c484 | ||
|
5e95b5d8c5 | ||
|
6e1f57493a | ||
|
8ed6550635 | ||
|
c8eb0f1ed8 | ||
|
3e5e50c06e | ||
|
92e1b597c4 | ||
|
8b1a97e673 | ||
|
3cf184c391 |
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@ -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"
|
||||
@ -205,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
|
||||
|
@ -8,11 +8,11 @@ COMMON_HOME_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in t
|
||||
COMMON_FOREIGN_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s)
|
||||
COMMON_HOME_BRIDGE_ADDRESS | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x"
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | URL
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
|
||||
COMMON_HOME_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_HOME_GAS_PRICE_SUPPLIER_URL` is not used. | `instant` / `fast` / `standard` / `slow`
|
||||
COMMON_HOME_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer
|
||||
COMMON_HOME_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. | URL
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
|
||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL`is not used. | `instant` / `fast` / `standard` / `slow`
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK | The gas price (in Wei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
||||
@ -52,6 +52,15 @@ 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_HOME_EVENTS_REPROCESSING | If set to `true`, home events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the home chain. Defaults to `1000` | `integer`
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the home chain. Defaults to `500` | `integer`
|
||||
ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `integer`
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING | If set to `true`, foreign events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the foreign chain. Defaults to `500` | `integer`
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the foreign chain. Defaults to `250` | `integer`
|
||||
ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `integer`
|
||||
|
||||
|
||||
## Monitor configuration
|
||||
|
@ -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
|
||||
|
@ -19,7 +19,7 @@ Sub-repositories maintained within this monorepo are listed below.
|
||||
|
||||
| Sub-repository | Description |
|
||||
| --- | --- |
|
||||
| [Oracle](oracle/README.md) | Oracle responsible for listening to bridge related events and authorizing asset transfers. |
|
||||
| [Oracle](oracle/README.md) | Responsible for listening to bridge related events and authorizing asset transfers. |
|
||||
| [Monitor](monitor/README.md) | Tool for checking balances and unprocessed events in bridged networks. |
|
||||
| [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. |
|
||||
| [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle |
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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: [
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { useMessageConfirmations } from '../hooks/useMessageConfirmations'
|
||||
import { MessageObject } from '../utils/web3'
|
||||
import styled from 'styled-components'
|
||||
import { CONFIRMATIONS_STATUS } from '../config/constants'
|
||||
import { CONFIRMATIONS_STATUS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import { ValidatorsConfirmations } from './ValidatorsConfirmations'
|
||||
@ -54,7 +54,9 @@ export const ConfirmationsContainer = ({
|
||||
home: { name: homeName },
|
||||
foreign: { name: foreignName }
|
||||
} = useStateProvider()
|
||||
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
|
||||
const src = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0)
|
||||
const [executionBlockNumber, setExecutionBlockNumber] = useState(0)
|
||||
const dst = useValidatorContract(!fromHome, executionBlockNumber || 'latest')
|
||||
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
|
||||
const {
|
||||
confirmations,
|
||||
@ -71,11 +73,21 @@ export const ConfirmationsContainer = ({
|
||||
fromHome,
|
||||
homeStartBlock,
|
||||
foreignStartBlock,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
requiredSignatures: src.requiredSignatures,
|
||||
validatorList: src.validatorList,
|
||||
targetValidatorList: dst.validatorList,
|
||||
blockConfirmations
|
||||
})
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (executionBlockNumber || executionData.status !== VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS) return
|
||||
|
||||
setExecutionBlockNumber(executionData.blockNumber)
|
||||
},
|
||||
[executionData.status, executionBlockNumber, executionData.blockNumber]
|
||||
)
|
||||
|
||||
const statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL
|
||||
|
||||
const parseDescription = () => {
|
||||
@ -114,20 +126,22 @@ export const ConfirmationsContainer = ({
|
||||
</MultiLine>
|
||||
</StatusDescription>
|
||||
<ValidatorsConfirmations
|
||||
confirmations={confirmations}
|
||||
requiredSignatures={requiredSignatures}
|
||||
validatorList={validatorList}
|
||||
confirmations={fromHome ? confirmations.filter(c => dst.validatorList.includes(c.validator)) : confirmations}
|
||||
requiredSignatures={dst.requiredSignatures}
|
||||
validatorList={dst.validatorList}
|
||||
waitingBlocksResolved={waitingBlocksResolved}
|
||||
/>
|
||||
{signatureCollected && (
|
||||
<ExecutionConfirmation
|
||||
messageData={message.data}
|
||||
message={message}
|
||||
executionData={executionData}
|
||||
isHome={!fromHome}
|
||||
signatureCollected={signatureCollected}
|
||||
confirmations={confirmations}
|
||||
setExecutionData={setExecutionData}
|
||||
executionEventsFetched={executionEventsFetched}
|
||||
setPendingExecution={setPendingExecution}
|
||||
dstRequiredSignatures={dst.requiredSignatures}
|
||||
dstValidatorList={dst.validatorList}
|
||||
/>
|
||||
)}
|
||||
</StyledConfirmationContainer>
|
||||
|
@ -1,38 +1,50 @@
|
||||
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'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import styled from 'styled-components'
|
||||
import { ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
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'
|
||||
import { matchesRule, MessageObject, WarnRule } from '../utils/web3'
|
||||
import { WarningAlert } from './commons/WarningAlert'
|
||||
import { ErrorAlert } from './commons/ErrorAlert'
|
||||
|
||||
const StyledExecutionConfirmation = styled.div`
|
||||
margin-top: 30px;
|
||||
`
|
||||
|
||||
export interface ExecutionConfirmationParams {
|
||||
messageData: string
|
||||
message: MessageObject
|
||||
executionData: ExecutionData
|
||||
setExecutionData: Function
|
||||
signatureCollected: boolean | string[]
|
||||
confirmations: ConfirmationParam[]
|
||||
isHome: boolean
|
||||
executionEventsFetched: boolean
|
||||
setPendingExecution: Function
|
||||
dstRequiredSignatures: number
|
||||
dstValidatorList: string[]
|
||||
}
|
||||
|
||||
export const ExecutionConfirmation = ({
|
||||
messageData,
|
||||
message,
|
||||
executionData,
|
||||
setExecutionData,
|
||||
signatureCollected,
|
||||
confirmations,
|
||||
isHome,
|
||||
executionEventsFetched,
|
||||
setPendingExecution
|
||||
setPendingExecution,
|
||||
dstRequiredSignatures,
|
||||
dstValidatorList
|
||||
}: ExecutionConfirmationParams) => {
|
||||
const { foreign } = useStateProvider()
|
||||
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [warning, setWarning] = useState('')
|
||||
const availableManualExecution =
|
||||
!isHome &&
|
||||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
|
||||
@ -48,9 +60,43 @@ 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]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!message.data || !executionData || !availableManualExecution) return
|
||||
|
||||
try {
|
||||
const fileName = 'warnRules'
|
||||
const rules: WarnRule[] = require(`../snapshots/${fileName}.json`)
|
||||
for (let rule of rules) {
|
||||
if (matchesRule(rule, message)) {
|
||||
setWarning(rule.message)
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
[availableManualExecution, executionData, message, message.data, setWarning]
|
||||
)
|
||||
|
||||
const getExecutionStatusElement = (validatorStatus = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS:
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
@ -68,6 +114,8 @@ export const ExecutionConfirmation = ({
|
||||
|
||||
return (
|
||||
<StyledExecutionConfirmation>
|
||||
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
|
||||
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
|
||||
<table>
|
||||
<Thead>
|
||||
<tr>
|
||||
@ -105,10 +153,14 @@ export const ExecutionConfirmation = ({
|
||||
{availableManualExecution && (
|
||||
<td>
|
||||
<ManualExecutionButton
|
||||
messageData={messageData}
|
||||
safeExecutionAvailable={safeExecutionAvailable}
|
||||
messageData={message.data}
|
||||
setExecutionData={setExecutionData}
|
||||
signatureCollected={signatureCollected as string[]}
|
||||
confirmations={confirmations}
|
||||
setPendingExecution={setPendingExecution}
|
||||
setError={setError}
|
||||
requiredSignatures={dstRequiredSignatures}
|
||||
validatorList={dstValidatorList}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
|
@ -8,7 +8,6 @@ import { TransactionReceipt } from 'web3-eth'
|
||||
import { InfoAlert } from './commons/InfoAlert'
|
||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
||||
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
|
||||
import { ErrorAlert } from './commons/ErrorAlert'
|
||||
|
||||
const StyledMainPage = styled.div`
|
||||
text-align: center;
|
||||
@ -52,7 +51,7 @@ export interface FormSubmitParams {
|
||||
|
||||
export const MainPage = () => {
|
||||
const history = useHistory()
|
||||
const { home, foreign, error, setError } = useStateProvider()
|
||||
const { home, foreign } = useStateProvider()
|
||||
const [networkName, setNetworkName] = useState('')
|
||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
||||
const [showInfoAlert, setShowInfoAlert] = useState(false)
|
||||
@ -132,7 +131,6 @@ export const MainPage = () => {
|
||||
</AlertP>
|
||||
</InfoAlert>
|
||||
)}
|
||||
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
|
||||
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
|
||||
<Route
|
||||
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
|
||||
|
@ -14,32 +14,92 @@ import { useStateProvider } from '../state/StateProvider'
|
||||
import { signatureToVRS, packSignatures } from '../utils/signatures'
|
||||
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
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[]
|
||||
confirmations: ConfirmationParam[]
|
||||
setPendingExecution: Function
|
||||
setError: Function
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
}
|
||||
|
||||
export const ManualExecutionButton = ({
|
||||
safeExecutionAvailable,
|
||||
messageData,
|
||||
setExecutionData,
|
||||
signatureCollected,
|
||||
setPendingExecution
|
||||
confirmations,
|
||||
setPendingExecution,
|
||||
setError,
|
||||
requiredSignatures,
|
||||
validatorList
|
||||
}: ManualExecutionButtonParams) => {
|
||||
const { foreign, setError } = useStateProvider()
|
||||
const { foreign } = useStateProvider()
|
||||
const { library, activate, account, active } = useWeb3React()
|
||||
const [manualExecution, setManualExecution] = useState(false)
|
||||
const [allowFailures, setAllowFailures] = useState(false)
|
||||
const [ready, setReady] = useState(false)
|
||||
const [title, setTitle] = useState('Loading')
|
||||
const [validSignatures, setValidSignatures] = useState<string[]>([])
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (
|
||||
!foreign.bridgeContract ||
|
||||
!foreign.web3 ||
|
||||
!confirmations ||
|
||||
!confirmations.length ||
|
||||
!requiredSignatures ||
|
||||
!validatorList ||
|
||||
!validatorList.length
|
||||
)
|
||||
return
|
||||
|
||||
const signatures = []
|
||||
for (let i = 0; i < confirmations.length && signatures.length < requiredSignatures; i++) {
|
||||
const sig = confirmations[i].signature
|
||||
if (!sig) {
|
||||
continue
|
||||
}
|
||||
const { v, r, s } = signatureToVRS(sig)
|
||||
const signer = foreign.web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`)
|
||||
if (validatorList.includes(signer)) {
|
||||
signatures.push(sig)
|
||||
}
|
||||
}
|
||||
|
||||
if (signatures.length >= requiredSignatures) {
|
||||
setValidSignatures(signatures.slice(0, requiredSignatures))
|
||||
setTitle('Execute')
|
||||
setReady(true)
|
||||
} else {
|
||||
setTitle('Unavailable')
|
||||
}
|
||||
},
|
||||
[
|
||||
foreign.bridgeContract,
|
||||
foreign.web3,
|
||||
validatorList,
|
||||
requiredSignatures,
|
||||
messageData,
|
||||
setValidSignatures,
|
||||
confirmations
|
||||
]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@ -67,12 +127,16 @@ export const ManualExecutionButton = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
|
||||
if (!library || !foreign.bridgeContract || !foreign.web3 || !validSignatures || !validSignatures.length) return
|
||||
|
||||
const signatures = packSignatures(signatureCollected.map(signatureToVRS))
|
||||
const signatures = packSignatures(validSignatures.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
|
||||
@ -130,17 +194,38 @@ export const ManualExecutionButton = ({
|
||||
foreign.bridgeContract,
|
||||
setError,
|
||||
messageData,
|
||||
signatureCollected,
|
||||
setExecutionData,
|
||||
setPendingExecution
|
||||
setPendingExecution,
|
||||
safeExecutionAvailable,
|
||||
allowFailures,
|
||||
foreign.web3,
|
||||
validSignatures
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="is-center">
|
||||
<StyledButton className="button outline" onClick={() => setManualExecution(true)}>
|
||||
Execute
|
||||
</StyledButton>
|
||||
<div>
|
||||
<div className="is-center">
|
||||
<ActionButton disabled={!ready} className="button outline" onClick={() => setManualExecution(true)}>
|
||||
{title}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
||||
import { useWindowWidth } from '@react-hook/window-size'
|
||||
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { RECENT_AGE, SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import styled from 'styled-components'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
@ -31,7 +31,9 @@ export const ValidatorsConfirmations = ({
|
||||
const getValidatorStatusElement = (validatorStatus = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID:
|
||||
return <SuccessLabel>{VALIDATOR_CONFIRMATION_STATUS.SUCCESS}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
||||
@ -58,26 +60,28 @@ export const ValidatorsConfirmations = ({
|
||||
</tr>
|
||||
</Thead>
|
||||
<tbody>
|
||||
{validatorList.map((validator, i) => {
|
||||
const filteredConfirmation = confirmations.filter(c => c.validator === validator)
|
||||
const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null
|
||||
const displayedStatus = confirmation && confirmation.status ? confirmation.status : ''
|
||||
const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : ''
|
||||
const elementIfNoTimestamp =
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING &&
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? (
|
||||
(displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') &&
|
||||
waitingBlocksResolved ? (
|
||||
SEARCHING_TX
|
||||
) : (
|
||||
<SimpleLoading />
|
||||
)
|
||||
) : (
|
||||
''
|
||||
)
|
||||
{confirmations.map((confirmation, i) => {
|
||||
const displayedStatus = confirmation.status
|
||||
const explorerLink = getExplorerTxUrl(confirmation.txHash, true)
|
||||
let elementIfNoTimestamp: any = <SimpleLoading />
|
||||
switch (displayedStatus) {
|
||||
case '':
|
||||
case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED:
|
||||
if (waitingBlocksResolved) {
|
||||
elementIfNoTimestamp = SEARCHING_TX
|
||||
}
|
||||
break
|
||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
|
||||
elementIfNoTimestamp = ''
|
||||
break
|
||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
||||
elementIfNoTimestamp = RECENT_AGE
|
||||
break
|
||||
}
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td>{windowWidth < 850 ? formatTxHash(validator) : validator}</td>
|
||||
<td>{windowWidth < 850 ? formatTxHash(confirmation.validator) : confirmation.validator}</td>
|
||||
<StatusTd className="text-center">{getValidatorStatusElement(displayedStatus)}</StatusTd>
|
||||
<AgeTd className="text-center">
|
||||
{confirmation && confirmation.timestamp > 0 ? (
|
||||
@ -94,7 +98,7 @@ export const ValidatorsConfirmations = ({
|
||||
</tbody>
|
||||
</table>
|
||||
<RequiredConfirmations>
|
||||
{requiredSignatures} of {validatorList.length} confirmations required
|
||||
At least <strong>{requiredSignatures}</strong> of <strong>{validatorList.length}</strong> confirmations required
|
||||
</RequiredConfirmations>
|
||||
</div>
|
||||
)
|
||||
|
@ -33,7 +33,7 @@ export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: str
|
||||
}
|
||||
return (
|
||||
<div className="row is-center">
|
||||
<StyledErrorAlert className="col-10 is-vertical-align row">
|
||||
<StyledErrorAlert className="col-12 is-vertical-align row">
|
||||
<InfoIcon color="var(--failed-color)" />
|
||||
<TextContainer className="col-10">
|
||||
{text}
|
||||
|
34
alm/src/components/commons/WarningAlert.tsx
Normal file
34
alm/src/components/commons/WarningAlert.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { InfoIcon } from './InfoIcon'
|
||||
import { CloseIcon } from './CloseIcon'
|
||||
|
||||
const StyledErrorAlert = styled.div`
|
||||
border: 1px solid var(--warning-color);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
padding-top: 10px;
|
||||
`
|
||||
|
||||
const CloseIconContainer = styled.div`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
white-space: pre-wrap;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
export const WarningAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
|
||||
return (
|
||||
<div className="row is-center">
|
||||
<StyledErrorAlert className="col-12 is-vertical-align row">
|
||||
<InfoIcon color="var(--warning-color)" />
|
||||
<TextContainer className="col-10">{error}</TextContainer>
|
||||
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
|
||||
<CloseIcon color="var(--warning-color)" />
|
||||
</CloseIconContainer>
|
||||
</StyledErrorAlert>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -54,14 +54,19 @@ export const CONFIRMATIONS_STATUS = {
|
||||
}
|
||||
|
||||
export const VALIDATOR_CONFIRMATION_STATUS = {
|
||||
SUCCESS: 'Success',
|
||||
SUCCESS: 'Confirmed',
|
||||
MANUAL: 'Manual',
|
||||
EXECUTION_SUCCESS: 'Executed',
|
||||
FAILED: 'Failed',
|
||||
FAILED_VALID: 'Failed valid',
|
||||
PENDING: 'Pending',
|
||||
WAITING: 'Waiting',
|
||||
NOT_REQUIRED: 'Not required',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
export const RECENT_AGE = 'Recent'
|
||||
|
||||
export const SEARCHING_TX = 'Searching Transaction...'
|
||||
|
||||
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`
|
||||
|
@ -29,17 +29,16 @@ export interface useMessageConfirmationsParams {
|
||||
foreignStartBlock: Maybe<number>
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
targetValidatorList: string[]
|
||||
blockConfirmations: number
|
||||
}
|
||||
|
||||
export interface BasicConfirmationParam {
|
||||
export interface ConfirmationParam {
|
||||
validator: string
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface ConfirmationParam extends BasicConfirmationParam {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ExecutionData {
|
||||
@ -48,6 +47,7 @@ export interface ExecutionData {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
executionResult: boolean
|
||||
blockNumber: number
|
||||
}
|
||||
|
||||
export const useMessageConfirmations = ({
|
||||
@ -58,6 +58,7 @@ export const useMessageConfirmations = ({
|
||||
foreignStartBlock,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
targetValidatorList,
|
||||
blockConfirmations
|
||||
}: useMessageConfirmationsParams) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
@ -65,7 +66,7 @@ export const useMessageConfirmations = ({
|
||||
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
const [waitingBlocks, setWaitingBlocks] = useState(false)
|
||||
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
|
||||
const [signatureCollected, setSignatureCollected] = useState<boolean | string[]>(false)
|
||||
const [signatureCollected, setSignatureCollected] = useState(false)
|
||||
const [executionEventsFetched, setExecutionEventsFetched] = useState(false)
|
||||
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
|
||||
const [executionData, setExecutionData] = useState<ExecutionData>({
|
||||
@ -73,7 +74,8 @@ export const useMessageConfirmations = ({
|
||||
validator: '',
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
})
|
||||
const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false)
|
||||
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false)
|
||||
@ -140,10 +142,9 @@ export const useMessageConfirmations = ({
|
||||
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
|
||||
// the execution tx on the foreign network is waiting for block confirmations
|
||||
// This is executed if the message is in Home to Foreign direction only
|
||||
const hasCollectedSignatures = !!signatureCollected // true or string[]
|
||||
useEffect(
|
||||
() => {
|
||||
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !hasCollectedSignatures) return
|
||||
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !signatureCollected) return
|
||||
|
||||
let timeoutId: number
|
||||
let isCancelled = false
|
||||
@ -179,7 +180,7 @@ export const useMessageConfirmations = ({
|
||||
isCancelled = true
|
||||
}
|
||||
},
|
||||
[fromHome, home.bridgeContract, home.web3, message.data, receipt, hasCollectedSignatures]
|
||||
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected]
|
||||
)
|
||||
|
||||
// Check if the responsible validator is waiting for block confirmations to execute the message on foreign network
|
||||
@ -252,6 +253,35 @@ export const useMessageConfirmations = ({
|
||||
let timeoutId: number
|
||||
let isCancelled = false
|
||||
|
||||
if (fromHome) {
|
||||
if (!targetValidatorList || !targetValidatorList.length) return
|
||||
const msgHash = home.web3.utils.sha3(message.data)!
|
||||
const allValidators = [...validatorList, ...targetValidatorList].filter((v, i, s) => s.indexOf(v) === i)
|
||||
const manualConfirmations = []
|
||||
for (let i = 0; i < allValidators.length; i++) {
|
||||
try {
|
||||
const overrideSignatures: {
|
||||
[key: string]: string
|
||||
} = require(`../snapshots/signatures_${allValidators[i]}.json`)
|
||||
if (overrideSignatures[msgHash]) {
|
||||
console.log(`Adding manual signature from ${allValidators[i]}`)
|
||||
manualConfirmations.push({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.MANUAL,
|
||||
validator: allValidators[i],
|
||||
timestamp: 0,
|
||||
txHash: '',
|
||||
signature: overrideSignatures[msgHash]
|
||||
})
|
||||
} else {
|
||||
console.log(`No manual signature from ${allValidators[i]} was found`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Signatures overrides are not present for ${allValidators[i]}`)
|
||||
}
|
||||
}
|
||||
setConfirmations(manualConfirmations)
|
||||
}
|
||||
|
||||
getConfirmationsForTx(
|
||||
message.data,
|
||||
home.web3,
|
||||
@ -284,7 +314,8 @@ export const useMessageConfirmations = ({
|
||||
home.bridgeContract,
|
||||
requiredSignatures,
|
||||
waitingBlocksResolved,
|
||||
homeStartBlock
|
||||
homeStartBlock,
|
||||
targetValidatorList
|
||||
]
|
||||
)
|
||||
|
||||
@ -343,7 +374,10 @@ export const useMessageConfirmations = ({
|
||||
// Sets the message status based in the collected information
|
||||
useEffect(
|
||||
() => {
|
||||
if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) {
|
||||
if (
|
||||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS &&
|
||||
existsConfirmation(confirmations)
|
||||
) {
|
||||
const newStatus = executionData.executionResult
|
||||
? CONFIRMATIONS_STATUS.SUCCESS
|
||||
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
|
||||
|
@ -4,19 +4,13 @@ import Web3 from 'web3'
|
||||
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
|
||||
import { BRIDGE_VALIDATORS_ABI } from '../abis'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
|
||||
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
|
||||
|
||||
export interface useValidatorContractParams {
|
||||
fromHome: boolean
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
}
|
||||
|
||||
export const useValidatorContract = ({ receipt, fromHome }: useValidatorContractParams) => {
|
||||
export const useValidatorContract = (isHome: boolean, blockNumber: number | 'latest') => {
|
||||
const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null)
|
||||
const [requiredSignatures, setRequiredSignatures] = useState(0)
|
||||
const [validatorList, setValidatorList] = useState([])
|
||||
const [validatorList, setValidatorList] = useState<string[]>([])
|
||||
|
||||
const { home, foreign } = useStateProvider()
|
||||
|
||||
@ -29,34 +23,34 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract
|
||||
|
||||
const callRequiredSignatures = async (
|
||||
contract: Maybe<Contract>,
|
||||
receipt: TransactionReceipt,
|
||||
blockNumber: number | 'latest',
|
||||
setResult: Function,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3,
|
||||
api: string
|
||||
) => {
|
||||
if (!contract) return
|
||||
const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider, web3, api)
|
||||
const result = await getRequiredSignatures(contract, blockNumber, snapshotProvider, web3, api)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
const callValidatorList = async (
|
||||
contract: Maybe<Contract>,
|
||||
receipt: TransactionReceipt,
|
||||
blockNumber: number | 'latest',
|
||||
setResult: Function,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3,
|
||||
api: string
|
||||
) => {
|
||||
if (!contract) return
|
||||
const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider, web3, api)
|
||||
const result = await getValidatorList(contract, blockNumber, snapshotProvider, web3, api)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
const web3 = fromHome ? home.web3 : foreign.web3
|
||||
const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
||||
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
|
||||
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
|
||||
const web3 = isHome ? home.web3 : foreign.web3
|
||||
const api = isHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
||||
const bridgeContract = isHome ? home.bridgeContract : foreign.bridgeContract
|
||||
const snapshotProvider = isHome ? homeSnapshotProvider : foreignSnapshotProvider
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@ -68,11 +62,11 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!web3 || !receipt) return
|
||||
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider, web3, api)
|
||||
callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider, web3, api)
|
||||
if (!web3 || !blockNumber) return
|
||||
callRequiredSignatures(validatorContract, blockNumber, setRequiredSignatures, snapshotProvider, web3, api)
|
||||
callValidatorList(validatorContract, blockNumber, setValidatorList, snapshotProvider, web3, api)
|
||||
},
|
||||
[validatorContract, receipt, web3, snapshotProvider, api]
|
||||
[validatorContract, blockNumber, web3, snapshotProvider, api]
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { createContext, ReactNode, useState } from 'react'
|
||||
import React, { createContext, ReactNode } from 'react'
|
||||
import { useNetwork } from '../hooks/useNetwork'
|
||||
import {
|
||||
HOME_RPC_URL,
|
||||
@ -25,8 +25,6 @@ export interface StateContext {
|
||||
home: BaseNetworkParams
|
||||
foreign: BaseNetworkParams
|
||||
loading: boolean
|
||||
error: string
|
||||
setError: Function
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
@ -44,9 +42,7 @@ const initialState = {
|
||||
bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
|
||||
bridgeContract: null
|
||||
},
|
||||
loading: true,
|
||||
error: '',
|
||||
setError: () => {}
|
||||
loading: true
|
||||
}
|
||||
|
||||
const StateContext = createContext<StateContext>(initialState)
|
||||
@ -58,7 +54,6 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
homeWeb3: homeNetwork.web3,
|
||||
foreignWeb3: foreignNetwork.web3
|
||||
})
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const value = {
|
||||
home: {
|
||||
@ -73,9 +68,7 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
bridgeContract: foreignBridge,
|
||||
...foreignNetwork
|
||||
},
|
||||
loading: homeNetwork.loading || foreignNetwork.loading,
|
||||
error,
|
||||
setError
|
||||
loading: homeNetwork.loading || foreignNetwork.loading
|
||||
}
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>
|
||||
|
@ -28,5 +28,7 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
|
||||
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
|
||||
--failed-color: ${props => props.theme.failed.textColor};
|
||||
--failed-bg-color: ${props => props.theme.failed.backgroundColor};
|
||||
--warning-color: ${props => props.theme.warning.textColor};
|
||||
--warning-bg-color: ${props => props.theme.warning.backgroundColor};
|
||||
}
|
||||
`
|
||||
|
@ -17,6 +17,10 @@ const theme = {
|
||||
failed: {
|
||||
textColor: '#de4437',
|
||||
backgroundColor: 'rgba(222,68,55,.1)'
|
||||
},
|
||||
warning: {
|
||||
textColor: '#ffa758',
|
||||
backgroundColor: 'rgba(222,68,55,.1)'
|
||||
}
|
||||
}
|
||||
export default theme
|
||||
|
@ -14,37 +14,40 @@ const messageData = '0x123456'
|
||||
const OTHER_HASH = 'aabbccdd'
|
||||
const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560'
|
||||
const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
|
||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
||||
|
||||
describe('getFailedTransactions', () => {
|
||||
test('should only return failed transactions', async () => {
|
||||
const to = otherAddress
|
||||
const transactions = [
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '1', to }
|
||||
{ isError: '0', to, from: validator1 },
|
||||
{ isError: '1', to, from: validator1 },
|
||||
{ isError: '0', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator3 }
|
||||
]
|
||||
|
||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
||||
const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(3)
|
||||
const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
describe('getSuccessTransactions', () => {
|
||||
test('should only return success transactions', async () => {
|
||||
const to = otherAddress
|
||||
const transactions = [
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '1', to }
|
||||
{ isError: '0', to, from: validator1 },
|
||||
{ isError: '1', to, from: validator1 },
|
||||
{ isError: '0', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator3 }
|
||||
]
|
||||
|
||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
||||
const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(2)
|
||||
const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
describe('filterValidatorSignatureTransaction', () => {
|
||||
|
@ -5,7 +5,7 @@ import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { APIPendingTransaction, APITransaction } from '../explorer'
|
||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
|
||||
import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations'
|
||||
import { ConfirmationParam } from '../../hooks/useMessageConfirmations'
|
||||
|
||||
jest.mock('../validatorConfirmationHelpers')
|
||||
|
||||
@ -18,6 +18,9 @@ const messageData = '0x111111111'
|
||||
const web3 = {
|
||||
utils: {
|
||||
soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}`
|
||||
},
|
||||
eth: {
|
||||
accounts: new Web3().eth.accounts
|
||||
}
|
||||
} as Web3
|
||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
||||
@ -25,7 +28,7 @@ const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
||||
const validatorList = [validator1, validator2, validator3]
|
||||
const signature =
|
||||
'0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4'
|
||||
'0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b'
|
||||
const bridgeContract = {
|
||||
methods: {
|
||||
signature: () => ({
|
||||
@ -61,19 +64,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -110,9 +113,8 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@ -135,7 +137,7 @@ describe('getConfirmationsForTx', () => {
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -144,19 +146,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -208,19 +210,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator !== validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator3 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -257,9 +259,8 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(3)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@ -281,16 +282,16 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -304,22 +305,22 @@ describe('getConfirmationsForTx', () => {
|
||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -356,9 +357,8 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@ -382,26 +382,26 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -414,22 +414,22 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
@ -492,22 +492,22 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
@ -521,13 +521,13 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator !== validator1
|
||||
@ -536,7 +536,7 @@ describe('getConfirmationsForTx', () => {
|
||||
txHash: validatorData.validator !== validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -596,9 +596,9 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
@ -610,9 +610,13 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
})
|
||||
test('should remove pending state after transaction mined', async () => {
|
||||
// Validator1 success
|
||||
// Validator2 failed
|
||||
// Validator3 Pending
|
||||
const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3'
|
||||
const validatorList = [validator1, validator2, validator3, validator4]
|
||||
|
||||
// Validator1 success (ts=100)
|
||||
// Validator2 failed (ts=200)
|
||||
// Validator3 Pending (ts=300)
|
||||
// Validator4 Excess confirmation (Failed) (ts=400)
|
||||
|
||||
getValidatorConfirmation
|
||||
.mockImplementationOnce(() => async (validator: string) => ({
|
||||
@ -623,41 +627,57 @@ describe('getConfirmationsForTx', () => {
|
||||
.mockImplementation(() => async (validator: string) => ({
|
||||
validator,
|
||||
status:
|
||||
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
validator === validator1 || validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
txHash: validatorData.validator === validator1 ? '0x100' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 100 : 0
|
||||
}))
|
||||
.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator !== validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator2 ? 123 : 0
|
||||
txHash:
|
||||
validatorData.validator === validator1 ? '0x100' : validatorData.validator === validator3 ? '0x300' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 100 : validatorData.validator === validator3 ? 300 : ''
|
||||
}))
|
||||
getValidatorFailedTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator2 ? '0x200' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 200 : 0
|
||||
}))
|
||||
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2 || validatorData.validator === validator4
|
||||
? validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash:
|
||||
validatorData.validator === validator2 ? '0x200' : validatorData.validator === validator4 ? '0x400' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 200 : validatorData.validator === validator4 ? 400 : ''
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
||||
txHash: validatorData.validator === validator3 ? '0x300' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 300 : 0
|
||||
}))
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -698,7 +718,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
|
||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
@ -712,28 +732,32 @@ describe('getConfirmationsForTx', () => {
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
|
||||
@ -761,14 +785,13 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(7)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(2)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected).toBeCalledTimes(3)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[2][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(2)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(2)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false)
|
||||
|
||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
||||
@ -781,23 +804,26 @@ describe('getConfirmationsForTx', () => {
|
||||
const res7 = setResult.mock.calls[6][0](res6)
|
||||
expect(res5).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res6).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res7).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 }
|
||||
])
|
||||
)
|
||||
})
|
||||
|
@ -84,10 +84,11 @@ describe('getFinalizationEvent', () => {
|
||||
expect(setResult).toBeCalledTimes(1)
|
||||
expect(setResult.mock.calls[0][0]).toEqual({
|
||||
validator: validator1,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
||||
txHash,
|
||||
timestamp,
|
||||
executionResult: true
|
||||
executionResult: true,
|
||||
blockNumber: 5523145
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(0)
|
||||
@ -237,7 +238,8 @@ describe('getFinalizationEvent', () => {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
||||
txHash,
|
||||
timestamp: expect.any(Number),
|
||||
executionResult: false
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(0)
|
||||
@ -294,7 +296,8 @@ describe('getFinalizationEvent', () => {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
txHash,
|
||||
timestamp: expect.any(Number),
|
||||
executionResult: false
|
||||
executionResult: false,
|
||||
blockNumber: expect.any(Number)
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(1)
|
||||
|
@ -22,6 +22,16 @@ export const getRequiredBlockConfirmations = async (
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
) => {
|
||||
let blockConfirmations
|
||||
|
||||
try {
|
||||
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
|
||||
} catch {}
|
||||
|
||||
if (blockConfirmations) {
|
||||
return parseInt(blockConfirmations)
|
||||
}
|
||||
|
||||
const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
||||
@ -35,16 +45,10 @@ export const getRequiredBlockConfirmations = async (
|
||||
|
||||
const events = [...eventsFromSnapshot, ...contractEvents]
|
||||
|
||||
let blockConfirmations
|
||||
if (events.length > 0) {
|
||||
// Use the value from last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
blockConfirmations = event.returnValues.requiredBlockConfirmations
|
||||
} else {
|
||||
// This is a special case where RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
|
||||
// of Sokol - Kovan. In this case the current value is used.
|
||||
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
|
||||
}
|
||||
// Use the value from last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
blockConfirmations = event.returnValues.requiredBlockConfirmations
|
||||
|
||||
return parseInt(blockConfirmations)
|
||||
}
|
||||
|
||||
@ -52,11 +56,25 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
|
||||
|
||||
export const getRequiredSignatures = async (
|
||||
contract: Contract,
|
||||
blockNumber: number,
|
||||
blockNumber: number | 'latest',
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
) => {
|
||||
let requiredSignatures
|
||||
|
||||
try {
|
||||
requiredSignatures = await contract.methods.requiredSignatures().call()
|
||||
} catch {}
|
||||
|
||||
if (requiredSignatures) {
|
||||
return parseInt(requiredSignatures)
|
||||
}
|
||||
|
||||
if (blockNumber === 'latest') {
|
||||
return contract.methods.requiredSignatures().call()
|
||||
}
|
||||
|
||||
const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
||||
@ -72,17 +90,25 @@ export const getRequiredSignatures = async (
|
||||
|
||||
// Use the value form last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
const { requiredSignatures } = event.returnValues
|
||||
;({ requiredSignatures } = event.returnValues)
|
||||
return parseInt(requiredSignatures)
|
||||
}
|
||||
|
||||
export const getValidatorList = async (
|
||||
contract: Contract,
|
||||
blockNumber: number,
|
||||
blockNumber: number | 'latest',
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
) => {
|
||||
try {
|
||||
const currentList = await contract.methods.validatorList().call()
|
||||
|
||||
if (currentList) {
|
||||
return currentList
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
|
||||
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
@ -12,6 +12,7 @@ import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
|
||||
export interface APITransaction {
|
||||
from: string
|
||||
timeStamp: string
|
||||
isError: string
|
||||
input: string
|
||||
@ -54,7 +55,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock,
|
||||
url.searchParams.append('module', 'account')
|
||||
url.searchParams.append('action', 'txlist')
|
||||
url.searchParams.append('address', account)
|
||||
url.searchParams.append('filterby', 'from')
|
||||
url.searchParams.append('filterby', 'to')
|
||||
url.searchParams.append('startblock', startBlock.toString())
|
||||
url.searchParams.append('endblock', endBlock.toString())
|
||||
|
||||
@ -64,7 +65,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock,
|
||||
return []
|
||||
}
|
||||
|
||||
return result.result
|
||||
return result.result || []
|
||||
}
|
||||
|
||||
export const fetchPendingTransactions = async ({
|
||||
@ -180,7 +181,9 @@ export const getLogs = async (
|
||||
if (topics[i] !== null) {
|
||||
url.searchParams.append(`topic${i}`, topics[i] as string)
|
||||
for (let j = 0; j < i; j++) {
|
||||
url.searchParams.append(`topic${j}_${i}_opr`, 'and')
|
||||
if (topics[j] !== null) {
|
||||
url.searchParams.append(`topic${j}_${i}_opr`, 'and')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,7 +197,7 @@ export const getLogs = async (
|
||||
}))
|
||||
}
|
||||
|
||||
const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase()
|
||||
const filterSender = (from: string) => (tx: APITransaction) => tx.from.toLowerCase() === from.toLowerCase()
|
||||
|
||||
export const getFailedTransactions = async (
|
||||
account: string,
|
||||
@ -204,9 +207,9 @@ export const getFailedTransactions = async (
|
||||
api: string,
|
||||
getAccountTransactionsMethod = getAccountTransactions
|
||||
): Promise<APITransaction[]> => {
|
||||
const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
|
||||
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
|
||||
|
||||
return transactions.filter(t => t.isError !== '0').filter(filterReceiver(to))
|
||||
return transactions.filter(t => t.isError !== '0').filter(filterSender(account))
|
||||
}
|
||||
|
||||
export const getSuccessTransactions = async (
|
||||
@ -217,9 +220,9 @@ export const getSuccessTransactions = async (
|
||||
api: string,
|
||||
getAccountTransactionsMethod = getAccountTransactions
|
||||
): Promise<APITransaction[]> => {
|
||||
const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
|
||||
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
|
||||
|
||||
return transactions.filter(t => t.isError === '0').filter(filterReceiver(to))
|
||||
return transactions.filter(t => t.isError === '0').filter(filterSender(account))
|
||||
}
|
||||
|
||||
export const filterValidatorSignatureTransaction = (
|
||||
|
@ -2,26 +2,37 @@ import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
|
||||
import { getAffirmationsSigned, getMessagesSigned } from './contract'
|
||||
import {
|
||||
getValidatorConfirmation,
|
||||
getValidatorFailedTransaction,
|
||||
getValidatorPendingTransaction,
|
||||
getSuccessExecutionTransaction
|
||||
} from './validatorConfirmationHelpers'
|
||||
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { signatureToVRS } from './signatures'
|
||||
|
||||
const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => {
|
||||
const mergeConfirmations = (oldConfirmations: ConfirmationParam[], newConfirmations: ConfirmationParam[]) => {
|
||||
const confirmations = [...oldConfirmations]
|
||||
newConfirmations.forEach(validatorData => {
|
||||
const index = confirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
if (index === -1) {
|
||||
confirmations.push(validatorData)
|
||||
return
|
||||
}
|
||||
const currentStatus = confirmations[index].status
|
||||
const newStatus = validatorData.status
|
||||
if (
|
||||
(validatorData as ConfirmationParam).txHash ||
|
||||
validatorData.txHash ||
|
||||
!!validatorData.signature ||
|
||||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
|
||||
) {
|
||||
confirmations[index] = validatorData
|
||||
confirmations[index] = {
|
||||
status: validatorData.status,
|
||||
validator: validatorData.validator,
|
||||
timestamp: confirmations[index].timestamp || validatorData.timestamp,
|
||||
txHash: confirmations[index].txHash || validatorData.txHash,
|
||||
signature: confirmations[index].signature || validatorData.signature
|
||||
}
|
||||
}
|
||||
})
|
||||
return confirmations
|
||||
@ -45,19 +56,17 @@ export const getConfirmationsForTx = async (
|
||||
setPendingConfirmations: Function,
|
||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => {
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
|
||||
const hashMsg = web3.utils.soliditySha3Raw(messageData)
|
||||
let validatorConfirmations = await Promise.all(
|
||||
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod))
|
||||
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, fromHome))
|
||||
)
|
||||
|
||||
const updateConfirmations = (confirmations: BasicConfirmationParam[]) => {
|
||||
const updateConfirmations = (confirmations: ConfirmationParam[]) => {
|
||||
if (confirmations.length === 0) {
|
||||
return
|
||||
}
|
||||
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
|
||||
setResult((currentConfirmations: BasicConfirmationParam[]) => {
|
||||
setResult((currentConfirmations: ConfirmationParam[]) => {
|
||||
if (currentConfirmations && currentConfirmations.length) {
|
||||
return mergeConfirmations(currentConfirmations, confirmations)
|
||||
}
|
||||
@ -67,11 +76,37 @@ export const getConfirmationsForTx = async (
|
||||
|
||||
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const hasEnoughSignatures = successConfirmations.length === requiredSignatures
|
||||
const hasEnoughSignatures = successConfirmations.length >= requiredSignatures
|
||||
|
||||
updateConfirmations(validatorConfirmations)
|
||||
setSignatureCollected(hasEnoughSignatures)
|
||||
|
||||
if (hasEnoughSignatures) {
|
||||
setPendingConfirmations(false)
|
||||
if (fromHome) {
|
||||
// fetch collected signatures for possible manual processing
|
||||
const signatures = await Promise.all(
|
||||
Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
|
||||
)
|
||||
const confirmations = signatures.flatMap(sig => {
|
||||
const { v, r, s } = signatureToVRS(sig)
|
||||
const address = web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`)
|
||||
return successConfirmations.filter(c => c.validator === address).map(c => ({ ...c, signature: sig }))
|
||||
})
|
||||
updateConfirmations(confirmations)
|
||||
}
|
||||
}
|
||||
|
||||
// get transactions from success signatures
|
||||
const successConfirmationWithData = await Promise.all(
|
||||
successConfirmations.map(
|
||||
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
|
||||
)
|
||||
)
|
||||
|
||||
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
|
||||
updateConfirmations(successConfirmationWithTxFound)
|
||||
|
||||
// If signatures not collected, look for pending transactions
|
||||
if (!hasEnoughSignatures) {
|
||||
// Check if confirmation is pending
|
||||
@ -84,8 +119,6 @@ export const getConfirmationsForTx = async (
|
||||
)
|
||||
updateConfirmations(validatorPendingConfirmations)
|
||||
setPendingConfirmations(validatorPendingConfirmations.length > 0)
|
||||
} else {
|
||||
setPendingConfirmations(false)
|
||||
}
|
||||
|
||||
const undefinedConfirmations = validatorConfirmations.filter(
|
||||
@ -95,13 +128,27 @@ export const getConfirmationsForTx = async (
|
||||
// Check if confirmation failed
|
||||
const validatorFailedConfirmationsChecks = await Promise.all(
|
||||
undefinedConfirmations.map(
|
||||
getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions)
|
||||
getValidatorFailedTransaction(web3, bridgeContract, messageData, startBlock, getFailedTransactions)
|
||||
)
|
||||
)
|
||||
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
let validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED || c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
)
|
||||
if (hasEnoughSignatures && !fromHome) {
|
||||
const lastTS = Math.max(...successConfirmationWithTxFound.map(c => c.timestamp || 0))
|
||||
validatorFailedConfirmations = validatorFailedConfirmations.map(
|
||||
c =>
|
||||
c.timestamp < lastTS
|
||||
? c
|
||||
: {
|
||||
...c,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
}
|
||||
)
|
||||
}
|
||||
setFailedConfirmations(
|
||||
!hasEnoughSignatures && validatorFailedConfirmations.some(c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED)
|
||||
)
|
||||
setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures)
|
||||
updateConfirmations(validatorFailedConfirmations)
|
||||
|
||||
const missingConfirmations = validatorConfirmations.filter(
|
||||
@ -112,30 +159,13 @@ export const getConfirmationsForTx = async (
|
||||
// If signatures collected, it should set other signatures not found as not required
|
||||
const notRequiredConfirmations = missingConfirmations.map(c => ({
|
||||
validator: c.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
}))
|
||||
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
|
||||
const successConfirmationWithData = await Promise.all(
|
||||
successConfirmations.map(
|
||||
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
|
||||
)
|
||||
)
|
||||
|
||||
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
|
||||
updateConfirmations(successConfirmationWithTxFound)
|
||||
|
||||
// retry if not all signatures are collected and some confirmations are still missing
|
||||
// or some success transactions were not fetched successfully
|
||||
if (
|
||||
|
@ -59,11 +59,12 @@ export const getSuccessExecutionData = async (
|
||||
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
||||
validator: validatorAddress,
|
||||
txHash: event.transactionHash,
|
||||
timestamp: blockTimestamp,
|
||||
executionResult: event.returnValues.status
|
||||
executionResult: event.returnValues.status,
|
||||
blockNumber: event.blockNumber
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -115,7 +116,8 @@ export const getFinalizationEvent = async (
|
||||
validator: validator,
|
||||
txHash: pendingTx.hash,
|
||||
timestamp: nowTimestamp,
|
||||
executionResult: false
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
})
|
||||
setPendingExecution(true)
|
||||
} else {
|
||||
@ -144,7 +146,8 @@ export const getFinalizationEvent = async (
|
||||
validator: validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp,
|
||||
executionResult: false
|
||||
executionResult: false,
|
||||
blockNumber: parseInt(failedTx.blockNumber)
|
||||
})
|
||||
setFailedExecution(true)
|
||||
}
|
||||
|
@ -1,38 +1,45 @@
|
||||
import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import validatorsCache from '../services/ValidatorsCache'
|
||||
import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
|
||||
import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
|
||||
import { getAffirmationsSigned, getMessagesSigned } from './contract'
|
||||
|
||||
export const getValidatorConfirmation = (
|
||||
web3: Web3,
|
||||
hashMsg: string,
|
||||
bridgeContract: Contract,
|
||||
confirmationContractMethod: Function
|
||||
) => async (validator: string): Promise<BasicConfirmationParam> => {
|
||||
fromHome: boolean
|
||||
) => async (validator: string): Promise<ConfirmationParam> => {
|
||||
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
|
||||
|
||||
const signatureFromCache = validatorsCache.get(hashSenderMsg)
|
||||
if (signatureFromCache) {
|
||||
return {
|
||||
validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
}
|
||||
const fromCache = validatorsCache.getData(hashSenderMsg)
|
||||
if (fromCache) {
|
||||
return fromCache
|
||||
}
|
||||
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg)
|
||||
const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
// If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (confirmed) {
|
||||
validatorsCache.set(hashSenderMsg, confirmed)
|
||||
const confirmation: ConfirmationParam = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
validator,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
}
|
||||
validatorsCache.setData(hashSenderMsg, confirmation)
|
||||
return confirmation
|
||||
}
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator,
|
||||
status
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +50,7 @@ export const getSuccessExecutionTransaction = (
|
||||
messageData: string,
|
||||
startBlock: number,
|
||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const { validator } = validatorData
|
||||
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
|
||||
const fromCache = validatorsCache.getData(validatorCacheKey)
|
||||
@ -87,11 +94,12 @@ export const getSuccessExecutionTransaction = (
|
||||
}
|
||||
|
||||
export const getValidatorFailedTransaction = (
|
||||
web3: Web3,
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
startBlock: number,
|
||||
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
|
||||
const failedFromCache = validatorsCache.getData(validatorCacheKey)
|
||||
|
||||
@ -106,30 +114,33 @@ export const getValidatorFailedTransaction = (
|
||||
startBlock,
|
||||
endBlock: homeBlockNumberProvider.get() || 0
|
||||
})
|
||||
const newStatus =
|
||||
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
let txHashTimestamp = 0
|
||||
let txHash = ''
|
||||
// If validator signature failed, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
txHashTimestamp = parseInt(failedTx.timeStamp)
|
||||
txHash = failedTx.hash
|
||||
|
||||
validatorsCache.setData(validatorCacheKey, {
|
||||
const confirmation: ConfirmationParam = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
validator: validatorData.validator,
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
})
|
||||
txHash: failedTx.hash,
|
||||
timestamp: parseInt(failedTx.timeStamp)
|
||||
}
|
||||
|
||||
if (failedTx.input && failedTx.input.length > 10) {
|
||||
try {
|
||||
const res = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${failedTx.input.slice(10)}`)
|
||||
confirmation.signature = res[0]
|
||||
confirmation.status = VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
console.log(`Adding manual signature from failed message from ${validatorData.validator}`)
|
||||
} catch {}
|
||||
}
|
||||
validatorsCache.setData(validatorCacheKey, confirmation)
|
||||
return confirmation
|
||||
}
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator: validatorData.validator,
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +148,7 @@ export const getValidatorPendingTransaction = (
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const failedTransactions = await getPendingTransactions({
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
|
@ -10,6 +10,37 @@ import { SnapshotProvider } from '../services/SnapshotProvider'
|
||||
export interface MessageObject {
|
||||
id: string
|
||||
data: string
|
||||
sender?: string
|
||||
executor?: string
|
||||
obToken?: string
|
||||
obReceiver?: string
|
||||
}
|
||||
|
||||
export interface WarnRule {
|
||||
message: string
|
||||
sender?: string
|
||||
executor?: string
|
||||
obToken?: string
|
||||
obReceiver?: string
|
||||
}
|
||||
|
||||
export const matchesRule = (rule: WarnRule, msg: MessageObject) => {
|
||||
if (!msg.executor || !msg.sender) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.executor && rule.executor.toLowerCase() !== msg.executor.toLowerCase()) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.sender && rule.sender.toLowerCase() !== msg.sender.toLowerCase()) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.obToken && (!msg.obToken || rule.obToken.toLowerCase() !== msg.obToken.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.obReceiver && (!msg.obReceiver || rule.obReceiver.toLowerCase() !== msg.obReceiver.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url))
|
||||
@ -26,15 +57,33 @@ export const filterEventsByAbi = (
|
||||
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
|
||||
const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash)
|
||||
|
||||
if (!eventAbi || !eventAbi.inputs || !eventAbi.inputs.length) {
|
||||
return []
|
||||
}
|
||||
const inputs = eventAbi.inputs
|
||||
return events.map(e => {
|
||||
let decodedLogs: { [p: string]: string } = {
|
||||
messageId: '',
|
||||
encodedData: ''
|
||||
const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]])
|
||||
let sender, executor, obToken, obReceiver
|
||||
if (encodedData.length >= 160) {
|
||||
sender = `0x${encodedData.slice(66, 106)}`
|
||||
executor = `0x${encodedData.slice(106, 146)}`
|
||||
const dataOffset =
|
||||
160 + (parseInt(encodedData.slice(154, 156), 16) + parseInt(encodedData.slice(156, 158), 16)) * 2 + 8
|
||||
if (encodedData.length >= dataOffset + 64) {
|
||||
obToken = `0x${encodedData.slice(dataOffset + 24, dataOffset + 64)}`
|
||||
}
|
||||
if (encodedData.length >= dataOffset + 128) {
|
||||
obReceiver = `0x${encodedData.slice(dataOffset + 88, dataOffset + 128)}`
|
||||
}
|
||||
}
|
||||
if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) {
|
||||
decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]])
|
||||
return {
|
||||
id: messageId || '',
|
||||
data: encodedData || '',
|
||||
sender,
|
||||
executor,
|
||||
obToken,
|
||||
obReceiver
|
||||
}
|
||||
return { id: decodedLogs.messageId, data: decodedLogs.encodedData }
|
||||
})
|
||||
}
|
||||
|
||||
|
BIN
audit/chainsecurity/FT-AMB-6.0.0-and-OmniBridge-1.1.0-contracts-security-assessment-report.pdf
Normal file
BIN
audit/chainsecurity/FT-AMB-6.0.0-and-OmniBridge-1.1.0-contracts-security-assessment-report.pdf
Normal file
Binary file not shown.
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -8,8 +8,10 @@
|
||||
"test": "NODE_ENV=test mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mycrypto/gas-estimation": "^1.1.0",
|
||||
"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",
|
||||
|
@ -1,5 +1,7 @@
|
||||
const { toWei, toBN, BN } = require('web3-utils')
|
||||
const { GasPriceOracle } = require('gas-price-oracle')
|
||||
const { estimateFees } = require('@mycrypto/gas-estimation')
|
||||
const fetch = require('node-fetch')
|
||||
const { BRIDGE_MODES } = require('./constants')
|
||||
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
|
||||
|
||||
@ -175,20 +177,27 @@ const gasPriceWithinLimits = (gasPrice, limits) => {
|
||||
const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
|
||||
let gasPrice = oracleGasPrice * factor
|
||||
gasPrice = gasPriceWithinLimits(gasPrice, limits)
|
||||
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei'))
|
||||
return 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 (web3, url, options = {}) => {
|
||||
try {
|
||||
let json
|
||||
if (fetchFn) {
|
||||
const response = await fetchFn()
|
||||
if (url === 'eip1559-gas-estimation') {
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await estimateFees(web3)
|
||||
const res = { maxFeePerGas: maxFeePerGas.toString(10), maxPriorityFeePerGas: maxPriorityFeePerGas.toString(10) }
|
||||
options.logger &&
|
||||
options.logger.debug &&
|
||||
options.logger.debug(res, 'Gas price updated using eip1559-gas-estimation')
|
||||
return res
|
||||
}
|
||||
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]
|
||||
|
||||
@ -205,7 +214,7 @@ const gasPriceFromSupplier = async (fetchFn, options = {}) => {
|
||||
options.logger.debug &&
|
||||
options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API')
|
||||
|
||||
return normalizedGasPrice
|
||||
return { gasPrice: normalizedGasPrice }
|
||||
} catch (e) {
|
||||
options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`)
|
||||
}
|
||||
@ -214,11 +223,11 @@ const gasPriceFromSupplier = async (fetchFn, options = {}) => {
|
||||
|
||||
const gasPriceFromContract = async (bridgeContract, options = {}) => {
|
||||
try {
|
||||
const gasPrice = await bridgeContract.methods.gasPrice().call()
|
||||
const gasPrice = (await bridgeContract.methods.gasPrice().call()).toString()
|
||||
options.logger &&
|
||||
options.logger.debug &&
|
||||
options.logger.debug({ gasPrice }, 'Gas price updated using the contracts')
|
||||
return gasPrice
|
||||
return { gasPrice }
|
||||
} catch (e) {
|
||||
options.logger &&
|
||||
options.logger.error &&
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 004d466a3d593e6304e52c74e6c3e801b6a33b32
|
||||
Subproject commit 908a48107919d4ab127f9af07d44d47eac91547e
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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('') != '' %}
|
||||
|
11
deployment/roles/oracle/templates/32-redis-docker.conf.j2
Normal file
11
deployment/roles/oracle/templates/32-redis-docker.conf.j2
Normal 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
|
11
deployment/roles/oracle/templates/33-rabbit-docker.conf.j2
Normal file
11
deployment/roles/oracle/templates/33-rabbit-docker.conf.j2
Normal 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
|
@ -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
|
||||
@ -25,3 +23,9 @@ ORACLE_HOME_START_BLOCK=1
|
||||
ORACLE_FOREIGN_START_BLOCK=1
|
||||
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=/mono/oracle/access-lists/block_list.txt
|
||||
ORACLE_FOREIGN_ARCHIVE_RPC_URL=http://parity2:8545
|
||||
ORACLE_HOME_EVENTS_REPROCESSING=false
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE=10
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY=10
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING=true
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE=10
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY=10
|
||||
|
@ -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
|
||||
@ -24,3 +22,9 @@ ORACLE_ALLOW_HTTP_FOR_RPC=yes
|
||||
ORACLE_HOME_START_BLOCK=1
|
||||
ORACLE_FOREIGN_START_BLOCK=1
|
||||
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=/mono/oracle/access-lists/block_list.txt
|
||||
ORACLE_HOME_EVENTS_REPROCESSING=true
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE=10
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY=10
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING=true
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE=10
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY=10
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
@ -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))
|
||||
|
@ -5,6 +5,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"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",
|
||||
|
@ -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=$?
|
||||
|
29
oracle-e2e/scripts/generate-amb-tx.js
Normal file
29
oracle-e2e/scripts/generate-amb-tx.js
Normal 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()
|
@ -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 () => {
|
||||
|
@ -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 => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
# POA TokenBridge / Oracle
|
||||
Oracle responsible for listening to bridge related events and authorizing asset transfers.
|
||||
An oracle responsible for listening to bridge related events and authorizing asset transfers.
|
||||
|
||||
## Overview
|
||||
Please refer to the [POA TokenBridge](../README.md) overview first of all.
|
||||
|
||||
The Oracle is deployed on specified validator nodes (only nodes whose private keys correspond to addresses specified in the smart contracts) in the network. It connects to two chains via a Remote Procedure Call (RPC) and is responsible for:
|
||||
The oracle is deployed on specified validator nodes (only nodes whose private keys correspond to addresses specified in the smart contracts) in the network. It connects to two chains via a Remote Procedure Call (RPC) and is responsible for:
|
||||
- listening to events related to bridge contracts
|
||||
- sending transactions to authorize asset transfers
|
||||
|
||||
@ -69,7 +69,7 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues
|
||||
}
|
||||
```
|
||||
|
||||
## Install and configure the Oracle
|
||||
## Install and configure the oracle
|
||||
|
||||
1. [Initialize](../README.md#initializing-the-monorepository) the monorepository.
|
||||
|
||||
|
@ -6,6 +6,7 @@ module.exports = {
|
||||
...baseConfig,
|
||||
main: baseConfig.foreign,
|
||||
event: 'UserRequestForAffirmation',
|
||||
sender: 'home',
|
||||
queue: 'home-prioritized',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
|
@ -7,8 +7,17 @@ const {
|
||||
HOME_AMB_ABI,
|
||||
FOREIGN_AMB_ABI
|
||||
} = require('../../commons')
|
||||
const { web3Home, web3Foreign } = require('../src/services/web3')
|
||||
const { privateKeyToAddress } = require('../src/utils/utils')
|
||||
const {
|
||||
web3Home,
|
||||
web3Foreign,
|
||||
web3HomeRedundant,
|
||||
web3HomeFallback,
|
||||
web3ForeignRedundant,
|
||||
web3ForeignFallback,
|
||||
web3ForeignArchive
|
||||
} = require('../src/services/web3')
|
||||
const { add0xPrefix, privateKeyToAddress } = require('../src/utils/utils')
|
||||
const { EXIT_CODES } = require('../src/utils/constants')
|
||||
|
||||
const {
|
||||
ORACLE_BRIDGE_MODE,
|
||||
@ -22,7 +31,15 @@ const {
|
||||
ORACLE_HOME_START_BLOCK,
|
||||
ORACLE_FOREIGN_START_BLOCK,
|
||||
ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT,
|
||||
ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT
|
||||
ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT,
|
||||
ORACLE_HOME_EVENTS_REPROCESSING,
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE,
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY,
|
||||
ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL,
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING,
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE,
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY,
|
||||
ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL
|
||||
} = process.env
|
||||
|
||||
let homeAbi
|
||||
@ -56,11 +73,19 @@ const homeConfig = {
|
||||
bridgeAddress: COMMON_HOME_BRIDGE_ADDRESS,
|
||||
bridgeABI: homeAbi,
|
||||
pollingInterval: parseInt(ORACLE_HOME_RPC_POLLING_INTERVAL, 10),
|
||||
syncCheckInterval: parseInt(ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL, 10) || 60000,
|
||||
startBlock: parseInt(ORACLE_HOME_START_BLOCK, 10) || 0,
|
||||
blockPollingLimit: parseInt(ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, 10),
|
||||
web3: web3Home,
|
||||
web3Redundant: web3HomeRedundant,
|
||||
web3Fallback: web3HomeFallback,
|
||||
bridgeContract: homeContract,
|
||||
eventContract: homeContract
|
||||
eventContract: homeContract,
|
||||
reprocessingOptions: {
|
||||
enabled: ORACLE_HOME_EVENTS_REPROCESSING === 'true',
|
||||
batchSize: parseInt(ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE, 10) || 1000,
|
||||
blockDelay: parseInt(ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY, 10) || 500
|
||||
}
|
||||
}
|
||||
|
||||
const foreignContract = new web3Foreign.eth.Contract(foreignAbi, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
@ -69,19 +94,41 @@ const foreignConfig = {
|
||||
bridgeAddress: COMMON_FOREIGN_BRIDGE_ADDRESS,
|
||||
bridgeABI: foreignAbi,
|
||||
pollingInterval: parseInt(ORACLE_FOREIGN_RPC_POLLING_INTERVAL, 10),
|
||||
syncCheckInterval: parseInt(ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL, 10) || 60000,
|
||||
startBlock: parseInt(ORACLE_FOREIGN_START_BLOCK, 10) || 0,
|
||||
blockPollingLimit: parseInt(ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT, 10),
|
||||
web3: web3Foreign,
|
||||
web3Redundant: web3ForeignRedundant,
|
||||
web3Fallback: web3ForeignFallback,
|
||||
web3Archive: web3ForeignArchive || web3Foreign,
|
||||
bridgeContract: foreignContract,
|
||||
eventContract: foreignContract
|
||||
eventContract: foreignContract,
|
||||
reprocessingOptions: {
|
||||
enabled: ORACLE_FOREIGN_EVENTS_REPROCESSING === 'true',
|
||||
batchSize: parseInt(ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE, 10) || 500,
|
||||
blockDelay: parseInt(ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY, 10) || 250
|
||||
}
|
||||
}
|
||||
|
||||
const maxProcessingTime =
|
||||
parseInt(ORACLE_MAX_PROCESSING_TIME, 10) || 4 * Math.max(homeConfig.pollingInterval, foreignConfig.pollingInterval)
|
||||
|
||||
let validatorPrivateKey
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
eventFilter: {},
|
||||
validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY),
|
||||
validatorPrivateKey,
|
||||
validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(validatorPrivateKey),
|
||||
maxProcessingTime,
|
||||
shutdownKey: 'oracle-shutdown',
|
||||
home: homeConfig,
|
||||
|
@ -6,6 +6,7 @@ module.exports = {
|
||||
...baseConfig,
|
||||
main: baseConfig.home,
|
||||
event: 'CollectedSignatures',
|
||||
sender: 'foreign',
|
||||
queue: 'foreign-prioritized',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
|
37
oracle/config/foreign-mev-sender.config.js
Normal file
37
oracle/config/foreign-mev-sender.config.js
Normal file
@ -0,0 +1,37 @@
|
||||
const baseConfig = require('./base.config')
|
||||
|
||||
const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
|
||||
const { MEV_HELPER_ABI } = require('../src/utils/mev')
|
||||
const { web3Foreign, getFlashbotsProvider } = require('../src/services/web3')
|
||||
|
||||
const {
|
||||
ORACLE_FOREIGN_TX_RESEND_INTERVAL,
|
||||
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
|
||||
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
|
||||
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
|
||||
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
|
||||
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS,
|
||||
ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE
|
||||
} = process.env
|
||||
|
||||
const contract = new baseConfig.foreign.web3.eth.Contract(MEV_HELPER_ABI, ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS)
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
pollingInterval: baseConfig.foreign.pollingInterval,
|
||||
mevForeign: {
|
||||
contractAddress: ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
|
||||
contract,
|
||||
minGasPrice: ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
|
||||
flatMinerFee: ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
|
||||
maxPriorityFeePerGas: ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
|
||||
maxFeePerGas: ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS,
|
||||
bundlesPerIteration: Math.max(parseInt(ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE, 10) || 5, 1),
|
||||
getFlashbotsProvider
|
||||
},
|
||||
mevJobsRedisKey: `${baseConfig.id}-collected-signatures-mev:mevJobs`,
|
||||
id: 'mev-sender-foreign',
|
||||
name: 'mev-sender-foreign',
|
||||
web3: web3Foreign,
|
||||
resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
const baseConfig = require('./base.config')
|
||||
|
||||
const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
|
||||
const { web3Foreign, web3ForeignRedundant, web3ForeignFallback } = require('../src/services/web3')
|
||||
|
||||
const { ORACLE_FOREIGN_TX_RESEND_INTERVAL } = process.env
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
main: baseConfig.foreign,
|
||||
queue: 'foreign-prioritized',
|
||||
oldQueue: 'foreign',
|
||||
id: 'foreign',
|
||||
name: 'sender-foreign',
|
||||
web3: web3Foreign,
|
||||
web3Redundant: web3ForeignRedundant,
|
||||
web3Fallback: web3ForeignFallback,
|
||||
resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
const baseConfig = require('./base.config')
|
||||
|
||||
const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
|
||||
const { web3Home, web3HomeRedundant, web3HomeFallback } = require('../src/services/web3')
|
||||
|
||||
const { ORACLE_HOME_TX_RESEND_INTERVAL } = process.env
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
main: baseConfig.home,
|
||||
queue: 'home-prioritized',
|
||||
oldQueue: 'home',
|
||||
id: 'home',
|
||||
name: 'sender-home',
|
||||
web3: web3Home,
|
||||
web3Redundant: web3HomeRedundant,
|
||||
web3Fallback: web3HomeFallback,
|
||||
resendInterval: parseInt(ORACLE_HOME_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
const baseConfig = require('./base.config')
|
||||
const { web3ForeignArchive } = require('../src/services/web3')
|
||||
|
||||
const id = `${baseConfig.id}-information-request`
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
web3ForeignArchive: web3ForeignArchive || baseConfig.foreign.web3,
|
||||
main: baseConfig.home,
|
||||
event: 'UserRequestForInformation',
|
||||
sender: 'home',
|
||||
queue: 'home-prioritized',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
|
30
oracle/config/mev-collected-signatures-watcher.config.js
Normal file
30
oracle/config/mev-collected-signatures-watcher.config.js
Normal file
@ -0,0 +1,30 @@
|
||||
const baseConfig = require('./base.config')
|
||||
const { MEV_HELPER_ABI } = require('../src/utils/mev')
|
||||
|
||||
const {
|
||||
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
|
||||
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
|
||||
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
|
||||
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
|
||||
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS
|
||||
} = process.env
|
||||
|
||||
const id = `${baseConfig.id}-collected-signatures-mev`
|
||||
|
||||
const contract = new baseConfig.foreign.web3.eth.Contract(MEV_HELPER_ABI, ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS)
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
mevForeign: {
|
||||
contractAddress: ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
|
||||
contract,
|
||||
minGasPrice: ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
|
||||
flatMinerFee: ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
|
||||
maxPriorityFeePerGas: ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
|
||||
maxFeePerGas: ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS
|
||||
},
|
||||
main: baseConfig.home,
|
||||
event: 'CollectedSignatures',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
}
|
@ -6,6 +6,7 @@ module.exports = {
|
||||
...baseConfig,
|
||||
main: baseConfig.home,
|
||||
event: 'UserRequestForSignature',
|
||||
sender: 'home',
|
||||
queue: 'home-prioritized',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
|
@ -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
|
||||
|
61
oracle/docker-compose-helpers.yml
Normal file
61
oracle/docker-compose-helpers.yml
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
version: '2.4'
|
||||
services:
|
||||
redis:
|
||||
cpus: 0.1
|
||||
mem_limit: 500m
|
||||
command: [ redis-server, --appendonly, 'yes' ]
|
||||
hostname: redis
|
||||
image: redis:4
|
||||
restart: unless-stopped
|
||||
volumes: [ '~/bridge_data/helpers/redis:/data' ]
|
||||
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
|
||||
mevWatcher:
|
||||
cpus: 0.1
|
||||
mem_limit: 500m
|
||||
image: poanetwork/tokenbridge-oracle:latest
|
||||
env_file: ./.env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
ORACLE_VALIDATOR_ADDRESS: ${ORACLE_VALIDATOR_ADDRESS}
|
||||
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS: 'TBD'
|
||||
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE: '50000000000' # 50 gwei
|
||||
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE: '1500000000000000' # 0.0015 eth = 300k gas * 5 gwei
|
||||
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS: '0' # 0 gwei
|
||||
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS: '1000000000000' # 1000 gwei
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: '15000' # CollectedSignatures event polling interval
|
||||
ORACLE_HOME_START_BLOCK: 'TBD'
|
||||
ORACLE_HOME_SKIP_MANUAL_LANE: 'true'
|
||||
restart: unless-stopped
|
||||
entrypoint: yarn mev:watcher:collected-signatures
|
||||
mevSender:
|
||||
cpus: 0.1
|
||||
mem_limit: 500m
|
||||
image: poanetwork/tokenbridge-oracle:latest
|
||||
env_file: ./.env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
ORACLE_VALIDATOR_ADDRESS: ${ORACLE_VALIDATOR_ADDRESS}
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: ${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}
|
||||
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS: 'TBD'
|
||||
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE: '50000000000' # 50 gwei
|
||||
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE: '1500000000000000' # 0.0015 eth = 300k gas * 5 gwei
|
||||
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS: '0' # 0 gwei
|
||||
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS: '1000000000000' # 1000 gwei
|
||||
ORACLE_MEV_FOREIGN_FLASHBOTS_RPC_URL: 'https://relay-goerli.flashbots.net'
|
||||
ORACLE_MEV_FOREIGN_FLASHBOTS_AUTH_SIGNING_KEY: 82db7175932f4e6c8e45283b78b54fd5f195149378ec90d95b8fd0ec8bdadf1d
|
||||
ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE: '5'
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: '70000' # time between sending different batches of MEV bundles (~= 5 blocks * 14 seconds)
|
||||
restart: unless-stopped
|
||||
entrypoint: yarn mev:sender:foreign
|
@ -13,7 +13,15 @@
|
||||
"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",
|
||||
"helper:signPendingMessages": "node ./scripts/signPendingMessages.js",
|
||||
"mev:watcher:collected-signatures": "./scripts/start-worker.sh mevWatcher mev-collected-signatures-watcher",
|
||||
"mev:sender:foreign": "./scripts/start-worker.sh mevSender foreign-mev-sender",
|
||||
"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",
|
||||
@ -23,17 +31,19 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@flashbots/ethers-provider-bundle": "^0.4.3",
|
||||
"amqp-connection-manager": "^2.0.0",
|
||||
"amqplib": "^0.5.2",
|
||||
"bignumber.js": "^7.2.1",
|
||||
"dotenv": "^5.0.1",
|
||||
"ethers": "^5.5.3",
|
||||
"ioredis": "^3.2.2",
|
||||
"node-fetch": "^2.1.2",
|
||||
"pino": "^4.17.3",
|
||||
"pino-pretty": "^2.0.1",
|
||||
"promise-limit": "^2.7.0",
|
||||
"promise-retry": "^1.1.1",
|
||||
"web3": "^1.3.0"
|
||||
"web3": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bn-chai": "^1.0.1",
|
||||
|
@ -36,7 +36,7 @@ async function main() {
|
||||
data,
|
||||
nonce,
|
||||
gasPrice: FOREIGN_TEST_TX_GAS_PRICE,
|
||||
amount: '0',
|
||||
value: '0',
|
||||
gasLimit,
|
||||
to: bridgeableTokenAddress,
|
||||
web3: web3Foreign,
|
||||
|
@ -29,7 +29,7 @@ async function main() {
|
||||
data: '0x',
|
||||
nonce,
|
||||
gasPrice: HOME_TEST_TX_GAS_PRICE,
|
||||
amount: HOME_MIN_AMOUNT_PER_TX,
|
||||
value: web3Home.utils.toWei(HOME_MIN_AMOUNT_PER_TX),
|
||||
gasLimit: 100000,
|
||||
to: COMMON_HOME_BRIDGE_ADDRESS,
|
||||
web3: web3Home,
|
||||
|
@ -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
|
65
oracle/scripts/interestFetcher.js
Normal file
65
oracle/scripts/interestFetcher.js
Normal 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),
|
||||
value: '0',
|
||||
chainId,
|
||||
web3: web3Home
|
||||
})
|
||||
console.log('Sent transaction with fetch interest', txHash, new Date())
|
||||
}, interval)
|
||||
}
|
||||
|
||||
main()
|
83
oracle/scripts/signPendingMessages.js
Normal file
83
oracle/scripts/signPendingMessages.js
Normal file
@ -0,0 +1,83 @@
|
||||
require('dotenv').config()
|
||||
|
||||
const {
|
||||
COMMON_HOME_BRIDGE_ADDRESS,
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS,
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
|
||||
ORACLE_HOME_START_BLOCK,
|
||||
ORACLE_HOME_END_BLOCK,
|
||||
ORACLE_BRIDGE_MODE
|
||||
} = process.env
|
||||
|
||||
const fs = require('fs')
|
||||
const promiseLimit = require('promise-limit')
|
||||
|
||||
const { web3Home, web3Foreign } = require('../src/services/web3')
|
||||
const { getBridgeABIs, getPastEvents, parseAMBMessage, BRIDGE_MODES } = require('../../commons')
|
||||
const { setLogger } = require('../src/services/injectedLogger')
|
||||
|
||||
const mockLogger = { debug: () => {}, info: () => {}, error: () => {}, child: () => mockLogger }
|
||||
setLogger(mockLogger)
|
||||
|
||||
const limit = promiseLimit(50)
|
||||
|
||||
const output = process.argv[2]
|
||||
|
||||
async function main() {
|
||||
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(ORACLE_BRIDGE_MODE)
|
||||
const wallet = web3Home.eth.accounts.wallet.add(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY)
|
||||
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
|
||||
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
const fromBlock = parseInt(ORACLE_HOME_START_BLOCK, 10) || 0
|
||||
let toBlock = parseInt(ORACLE_HOME_END_BLOCK, 10)
|
||||
if (!toBlock) {
|
||||
toBlock = await web3Home.eth.getBlockNumber()
|
||||
}
|
||||
console.log(`Getting CollectedSignatures events from block ${fromBlock} to block ${toBlock}`)
|
||||
const events = await getPastEvents(homeBridge, { event: 'CollectedSignatures', fromBlock, toBlock })
|
||||
console.log(`Found ${events.length} CollectedSignatures events`)
|
||||
console.log('Getting messages')
|
||||
let messages = await Promise.all(
|
||||
events.map((event, i) => () => getMessage(ORACLE_BRIDGE_MODE, homeBridge, foreignBridge, event, i)).map(limit)
|
||||
)
|
||||
messages = messages.filter(x => x)
|
||||
console.log(`Filtered ${messages.length} pending messages`)
|
||||
const result = {}
|
||||
messages.forEach(msg => {
|
||||
result[msg.msgHash] = wallet.sign(msg.message).signature
|
||||
})
|
||||
|
||||
console.log('Writing results')
|
||||
if (output === '-') {
|
||||
console.log(JSON.stringify(result))
|
||||
} else {
|
||||
fs.writeFileSync(output, JSON.stringify(result))
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessage(bridgeMode, homeBridge, foreignBridge, event, i) {
|
||||
if (i % 50 === 0) {
|
||||
console.log(`Processing event #${i}`)
|
||||
}
|
||||
const msgHash = event.returnValues.messageHash
|
||||
const message = await homeBridge.methods.message(msgHash).call()
|
||||
|
||||
let msgId
|
||||
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
|
||||
msgId = parseAMBMessage(message).messageId
|
||||
} else {
|
||||
msgId = `0x${message.slice(106, 170)}`
|
||||
}
|
||||
const alreadyProcessed = await foreignBridge.methods.relayedMessages(msgId).call()
|
||||
|
||||
if (alreadyProcessed) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
msgHash,
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
@ -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
|
||||
|
@ -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)
|
||||
const { web3 } = config.sender === 'foreign' ? config.foreign : config.home
|
||||
|
||||
await GasPrice.start(chain, web3, true)
|
||||
const gasPriceOptions = GasPrice.gasPriceOptions()
|
||||
|
||||
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,13 +174,13 @@ async function sendJobTx(jobs) {
|
||||
const txHash = await sendTx({
|
||||
data: job.data,
|
||||
nonce,
|
||||
gasPrice: gasPrice.toString(10),
|
||||
amount: '0',
|
||||
value: '0',
|
||||
gasLimit,
|
||||
privateKey: ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
|
||||
privateKey: config.validatorPrivateKey,
|
||||
to: job.to,
|
||||
chainId,
|
||||
web3
|
||||
web3,
|
||||
gasPriceOptions
|
||||
})
|
||||
|
||||
nonce++
|
||||
@ -167,8 +196,8 @@ async function sendJobTx(jobs) {
|
||||
)
|
||||
|
||||
if (e.message.toLowerCase().includes('insufficient funds')) {
|
||||
const currentBalance = await web3.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
|
||||
const minimumBalance = gasLimit.multipliedBy(gasPrice)
|
||||
const currentBalance = await web3.eth.getBalance(config.validatorAddress)
|
||||
const minimumBalance = gasLimit.multipliedBy(gasPriceOptions.gasPrice || gasPriceOptions.maxFeePerGas)
|
||||
logger.error(
|
||||
`Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.`
|
||||
)
|
||||
|
@ -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
|
||||
|
||||
|
@ -4,7 +4,6 @@ const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError
|
||||
const logger = require('../../services/logger').child({
|
||||
module: 'processCollectedSignatures:estimateGas'
|
||||
})
|
||||
const { parseAMBHeader } = require('../../utils/message')
|
||||
|
||||
const web3 = new Web3()
|
||||
const { toBN } = Web3.utils
|
||||
@ -22,15 +21,9 @@ async function estimateGas({
|
||||
address
|
||||
}) {
|
||||
try {
|
||||
const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({
|
||||
return await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({
|
||||
from: address
|
||||
})
|
||||
const msgGasLimit = parseAMBHeader(message).gasLimit
|
||||
|
||||
// + estimateExtraGas(len)
|
||||
// is not needed here, since estimateGas will already take into account gas
|
||||
// needed for memory expansion, message processing, etc.
|
||||
return gasEstimate + msgGasLimit
|
||||
} catch (e) {
|
||||
if (e instanceof HttpListProviderError) {
|
||||
throw e
|
||||
|
183
oracle/src/events/processAMBCollectedSignaturesMEV/index.js
Normal file
183
oracle/src/events/processAMBCollectedSignaturesMEV/index.js
Normal file
@ -0,0 +1,183 @@
|
||||
require('dotenv').config()
|
||||
const promiseLimit = require('promise-limit')
|
||||
const { HttpListProviderError } = require('../../services/HttpListProvider')
|
||||
const { getValidatorContract } = require('../../tx/web3')
|
||||
const rootLogger = require('../../services/logger')
|
||||
const { signatureToVRS, packSignatures } = require('../../utils/message')
|
||||
const { readAccessListFile, isRevertError } = require('../../utils/utils')
|
||||
const { parseAMBMessage } = require('../../../../commons')
|
||||
const estimateGas = require('../processAMBCollectedSignatures/estimateGas')
|
||||
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
|
||||
const { MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants')
|
||||
|
||||
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
|
||||
|
||||
const { ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST, ORACLE_HOME_TO_FOREIGN_BLOCK_LIST } = process.env
|
||||
const ORACLE_HOME_SKIP_MANUAL_LANE = process.env.ORACLE_HOME_SKIP_MANUAL_LANE === 'true'
|
||||
|
||||
function processCollectedSignaturesBuilder(config) {
|
||||
const { home, foreign, mevForeign } = config
|
||||
|
||||
let validatorContract = null
|
||||
|
||||
return async function processCollectedSignatures(signatures) {
|
||||
const txToSend = []
|
||||
|
||||
if (validatorContract === null) {
|
||||
validatorContract = await getValidatorContract(foreign.bridgeContract, foreign.web3)
|
||||
}
|
||||
|
||||
rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`)
|
||||
const callbacks = signatures
|
||||
.map(colSignature => async () => {
|
||||
const { messageHash, NumberOfCollectedSignatures } = colSignature.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
eventTransactionHash: colSignature.transactionHash
|
||||
})
|
||||
|
||||
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
|
||||
const message = await home.bridgeContract.methods.message(messageHash).call()
|
||||
const parsedMessage = parseAMBMessage(message)
|
||||
|
||||
if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST || ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) {
|
||||
const sender = parsedMessage.sender.toLowerCase()
|
||||
const executor = parsedMessage.executor.toLowerCase()
|
||||
|
||||
if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
|
||||
const allowanceList = await readAccessListFile(ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST, logger)
|
||||
if (!allowanceList.includes(executor) && !allowanceList.includes(sender)) {
|
||||
logger.info(
|
||||
{ sender, executor },
|
||||
'Validator skips a message. Neither sender nor executor addresses are in the allowance list.'
|
||||
)
|
||||
return
|
||||
}
|
||||
} else if (ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) {
|
||||
const blockList = await readAccessListFile(ORACLE_HOME_TO_FOREIGN_BLOCK_LIST, logger)
|
||||
if (blockList.includes(executor)) {
|
||||
logger.info({ executor }, 'Validator skips a message. Executor address is in the block list.')
|
||||
return
|
||||
}
|
||||
if (blockList.includes(sender)) {
|
||||
logger.info({ sender }, 'Validator skips a message. Sender address is in the block list.')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ORACLE_HOME_SKIP_MANUAL_LANE && parsedMessage.decodedDataType.manualLane) {
|
||||
logger.info(
|
||||
{ dataType: parsedMessage.dataType },
|
||||
'Validator skips a message. Message was forwarded to the manual lane by the extension'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug({ NumberOfCollectedSignatures }, 'Number of signatures to get')
|
||||
|
||||
const requiredSignatures = []
|
||||
requiredSignatures.length = NumberOfCollectedSignatures
|
||||
requiredSignatures.fill(0)
|
||||
|
||||
const signaturesArray = []
|
||||
const [v, r, s] = [[], [], []]
|
||||
logger.debug('Getting message signatures')
|
||||
const signaturePromises = requiredSignatures.map(async (el, index) => {
|
||||
logger.debug({ index }, 'Getting message signature')
|
||||
const signature = await home.bridgeContract.methods.signature(messageHash, index).call()
|
||||
const vrs = signatureToVRS(signature)
|
||||
v.push(vrs.v)
|
||||
r.push(vrs.r)
|
||||
s.push(vrs.s)
|
||||
signaturesArray.push(vrs)
|
||||
})
|
||||
|
||||
await Promise.all(signaturePromises)
|
||||
const signatures = packSignatures(signaturesArray)
|
||||
logger.info(`Processing messageId: ${parsedMessage.messageId}`)
|
||||
|
||||
let gasEstimate
|
||||
try {
|
||||
logger.debug('Estimate gas')
|
||||
gasEstimate = await estimateGas({
|
||||
foreignBridge: foreign.bridgeContract,
|
||||
validatorContract,
|
||||
v,
|
||||
r,
|
||||
s,
|
||||
signatures,
|
||||
message,
|
||||
numberOfCollectedSignatures: NumberOfCollectedSignatures,
|
||||
messageId: parsedMessage.messageId,
|
||||
address: config.validatorAddress
|
||||
})
|
||||
logger.debug({ gasEstimate }, 'Gas estimated')
|
||||
} catch (e) {
|
||||
if (e instanceof HttpListProviderError) {
|
||||
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
|
||||
} else if (e instanceof AlreadyProcessedError) {
|
||||
logger.info(`Already processed CollectedSignatures ${colSignature.transactionHash}`)
|
||||
return
|
||||
} else if (e instanceof IncompatibleContractError || e instanceof InvalidValidatorError) {
|
||||
logger.error(`The message couldn't be processed; skipping: ${e.message}`)
|
||||
return
|
||||
} else {
|
||||
logger.error(e, 'Unknown error while processing transaction')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
const executeData = foreign.bridgeContract.methods.executeSignatures(message, signatures).encodeABI()
|
||||
const profit = await estimateProfit(
|
||||
mevForeign.contract,
|
||||
mevForeign.minGasPrice,
|
||||
executeData,
|
||||
mevForeign.flatMinerFee
|
||||
)
|
||||
if (profit === '0') {
|
||||
logger.error('No MEV opportunity found when testing with min gas price, skipping job')
|
||||
return
|
||||
}
|
||||
logger.info(`Estimated profit of ${profit} when simulating with ${mevForeign.minGasPrice} gas price`)
|
||||
|
||||
txToSend.push({
|
||||
profit,
|
||||
executeData,
|
||||
data: mevForeign.contract.methods.execute(executeData).encodeABI(),
|
||||
gasEstimate,
|
||||
extraGas: EXTRA_GAS_ABSOLUTE,
|
||||
maxFeePerGas: mevForeign.maxFeePerGas,
|
||||
maxPriorityFeePerGas: mevForeign.maxPriorityFeePerGas,
|
||||
transactionReference: colSignature.transactionHash,
|
||||
to: mevForeign.contractAddress,
|
||||
value: mevForeign.flatMinerFee
|
||||
})
|
||||
})
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
|
||||
return txToSend
|
||||
}
|
||||
}
|
||||
|
||||
async function estimateProfit(contract, gasPrice, data, minerFee) {
|
||||
return contract.methods
|
||||
.estimateProfit(gasPrice, data)
|
||||
.call({ value: minerFee })
|
||||
.then(
|
||||
res => res.toString(),
|
||||
e => {
|
||||
if (isRevertError(e)) {
|
||||
return '0'
|
||||
}
|
||||
throw e
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
processCollectedSignaturesBuilder,
|
||||
estimateProfit
|
||||
}
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
|
@ -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)]
|
||||
|
@ -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 = {
|
||||
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
@ -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')
|
||||
@ -29,11 +35,13 @@ Object.keys(asyncCalls).forEach(method => {
|
||||
})
|
||||
|
||||
function processInformationRequestsBuilder(config) {
|
||||
const { home, foreign, web3ForeignArchive } = config
|
||||
const { home, foreign } = config
|
||||
|
||||
let validatorContract = null
|
||||
let blockFinder = null
|
||||
|
||||
foreign.web3Archive.currentProvider.startSyncStateChecker(foreign.syncCheckInterval)
|
||||
|
||||
return async function processInformationRequests(informationRequests) {
|
||||
const txToSend = []
|
||||
|
||||
@ -43,13 +51,15 @@ function processInformationRequestsBuilder(config) {
|
||||
|
||||
if (blockFinder === null) {
|
||||
rootLogger.debug('Initializing block finder')
|
||||
blockFinder = await makeBlockFinder('foreign', foreign.web3)
|
||||
blockFinder = await makeBlockFinder('foreign', foreign.web3Archive)
|
||||
}
|
||||
|
||||
// latest foreign block is requested from an archive RPC, to ensure that it is synced with the network
|
||||
// block confirmations can be requested from the regular JSON RPC
|
||||
const foreignBlockNumber =
|
||||
(await getBlockNumber(foreign.web3)) - (await getRequiredBlockConfirmations(foreign.bridgeContract))
|
||||
(await getBlockNumber(foreign.web3Archive)) - (await getRequiredBlockConfirmations(foreign.bridgeContract))
|
||||
const homeBlock = await getBlock(home.web3, informationRequests[0].blockNumber)
|
||||
const lastForeignBlock = await getBlock(foreign.web3, foreignBlockNumber)
|
||||
const lastForeignBlock = await getBlock(foreign.web3Archive, foreignBlockNumber)
|
||||
|
||||
if (homeBlock.timestamp > lastForeignBlock.timestamp) {
|
||||
rootLogger.debug(
|
||||
@ -79,12 +89,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(foreign.web3Archive, 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 +112,8 @@ function processInformationRequestsBuilder(config) {
|
||||
messageId,
|
||||
status,
|
||||
result,
|
||||
address: config.validatorAddress
|
||||
address: config.validatorAddress,
|
||||
homeBlockNumber: homeBlock.number
|
||||
})
|
||||
logger.debug({ gasEstimate }, 'Gas estimated')
|
||||
} catch (e) {
|
||||
|
@ -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 {
|
||||
|
@ -6,11 +6,9 @@ const logger = require('../../services/logger').child({
|
||||
|
||||
async function estimateGas({ web3, homeBridge, validatorContract, recipient, value, txHash, address }) {
|
||||
try {
|
||||
const gasEstimate = await homeBridge.methods.executeAffirmation(recipient, value, txHash).estimateGas({
|
||||
return await homeBridge.methods.executeAffirmation(recipient, value, txHash).estimateGas({
|
||||
from: address
|
||||
})
|
||||
|
||||
return gasEstimate
|
||||
} catch (e) {
|
||||
if (e instanceof HttpListProviderError) {
|
||||
throw e
|
||||
|
@ -20,8 +20,7 @@ async function estimateGas({
|
||||
signatures
|
||||
}) {
|
||||
try {
|
||||
const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas()
|
||||
return gasEstimate
|
||||
return await foreignBridge.methods.executeSignatures(message, signatures).estimateGas()
|
||||
} catch (e) {
|
||||
if (e instanceof HttpListProviderError) {
|
||||
throw e
|
||||
|
@ -6,10 +6,9 @@ const logger = require('../../services/logger').child({
|
||||
|
||||
async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) {
|
||||
try {
|
||||
const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({
|
||||
return await homeBridge.methods.submitSignature(signature, message).estimateGas({
|
||||
from: address
|
||||
})
|
||||
return gasEstimate
|
||||
} catch (e) {
|
||||
if (e instanceof HttpListProviderError) {
|
||||
throw e
|
||||
|
@ -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 {
|
||||
|
@ -13,12 +13,25 @@ const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
|
||||
function processTransfersBuilder(config) {
|
||||
const { bridgeContract, web3 } = config.home
|
||||
|
||||
const userRequestForAffirmationAbi = config.foreign.bridgeABI.find(
|
||||
e => e.type === 'event' && e.name === 'UserRequestForAffirmation'
|
||||
)
|
||||
const tokensSwappedAbi = config.foreign.bridgeABI.find(e => e.type === 'event' && e.name === 'TokensSwapped')
|
||||
const userRequestForAffirmationHash = web3.eth.abi.encodeEventSignature(userRequestForAffirmationAbi)
|
||||
const tokensSwappedHash = tokensSwappedAbi ? web3.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x'
|
||||
const userRequestForAffirmationHash = web3.eth.abi.encodeEventSignature('UserRequestForAffirmation(address,uint256)')
|
||||
const redeemHash = web3.eth.abi.encodeEventSignature('Redeem(address,uint256,uint256)')
|
||||
const transferHash = web3.eth.abi.encodeEventSignature('Transfer(address,address,uint256)')
|
||||
|
||||
const foreignBridgeAddress = config.foreign.bridgeAddress
|
||||
|
||||
const decodeAddress = data => web3.eth.abi.decodeParameter('address', data)
|
||||
|
||||
const isUserRequestForAffirmation = e =>
|
||||
e.address.toLowerCase() === foreignBridgeAddress.toLowerCase() && e.topics[0] === userRequestForAffirmationHash
|
||||
const isRedeem = cTokenAddress => e =>
|
||||
e.address.toLowerCase() === cTokenAddress.toLowerCase() &&
|
||||
e.topics[0] === redeemHash &&
|
||||
decodeAddress(e.data.slice(0, 66)).toLowerCase() === foreignBridgeAddress.toLowerCase()
|
||||
const isCTokenTransfer = cTokenAddress => e =>
|
||||
e.address.toLowerCase() === cTokenAddress.toLowerCase() &&
|
||||
e.topics[0] === transferHash &&
|
||||
decodeAddress(e.topics[1]).toLowerCase() === foreignBridgeAddress.toLowerCase() &&
|
||||
decodeAddress(e.topics[2]).toLowerCase() === cTokenAddress.toLowerCase()
|
||||
|
||||
let validatorContract = null
|
||||
|
||||
@ -32,37 +45,35 @@ function processTransfersBuilder(config) {
|
||||
rootLogger.debug(`Processing ${transfers.length} Transfer events`)
|
||||
const callbacks = transfers
|
||||
.map(transfer => async () => {
|
||||
const { from, value } = transfer.returnValues
|
||||
const { from, to, value } = transfer.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
eventTransactionHash: transfer.transactionHash
|
||||
eventTransactionHash: transfer.transactionHash,
|
||||
from,
|
||||
to,
|
||||
value
|
||||
})
|
||||
|
||||
logger.info({ from, value }, `Processing transfer ${transfer.transactionHash}`)
|
||||
logger.info('Processing transfer')
|
||||
|
||||
const receipt = await config.foreign.web3.eth.getTransactionReceipt(transfer.transactionHash)
|
||||
|
||||
const existsAffirmationEvent = receipt.logs.some(
|
||||
e => e.address === config.foreign.bridgeAddress && e.topics[0] === userRequestForAffirmationHash
|
||||
)
|
||||
|
||||
if (existsAffirmationEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because a transaction with alternative receiver detected in transaction ${
|
||||
transfer.transactionHash
|
||||
}`
|
||||
)
|
||||
if (receipt.logs.some(isUserRequestForAffirmation)) {
|
||||
logger.info('Transfer event discarded because affirmation is detected in the same transaction')
|
||||
return
|
||||
}
|
||||
|
||||
const existsTokensSwappedEvent = tokensSwappedAbi
|
||||
? receipt.logs.some(e => e.address === config.foreign.bridgeAddress && e.topics[0] === tokensSwappedHash)
|
||||
: false
|
||||
if (from === ZERO_ADDRESS) {
|
||||
logger.info('Mint-like transfers from zero address are not processed')
|
||||
return
|
||||
}
|
||||
|
||||
if (from === ZERO_ADDRESS && existsTokensSwappedEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because token swap is detected in transaction ${transfer.transactionHash}`
|
||||
)
|
||||
// when bridge performs a withdrawal from Compound, the following three events occur
|
||||
// * token.Transfer(from=cToken, to=bridge, amount=X)
|
||||
// * cToken.Redeem(redeemer=bridge, amount=X, tokens=Y)
|
||||
// * cToken.Transfer(from=bridge, to=cToken, amount=Y)
|
||||
if (receipt.logs.some(isRedeem(from)) && receipt.logs.some(isCTokenTransfer(from))) {
|
||||
logger.info('Transfer event discarded because cToken redeem is detected in the same transaction')
|
||||
return
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user