Compare commits
21 Commits
2.5.0
...
allow-rela
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60b45faa6c | ||
|
|
48752e8575 | ||
|
|
86a47f4a0b | ||
|
|
7af04fa0e8 | ||
|
|
bc871a6844 | ||
|
|
4198b092a6 | ||
|
|
30208b4f8e | ||
|
|
d5946f7df1 | ||
|
|
4efda98f2b | ||
|
|
9e309db876 | ||
|
|
aff8b777c5 | ||
|
|
74293959f3 | ||
|
|
46daeb6815 | ||
|
|
44ca0d71ce | ||
|
|
fbeb878cdb | ||
|
|
d17ea2ad2b | ||
|
|
4cc87ef61a | ||
|
|
125b66b86d | ||
|
|
7a0ed3f699 | ||
|
|
4e04f2ae1f | ||
|
|
dc377aeb9b |
@@ -22,7 +22,7 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of th
|
||||
|
||||
name | description | value
|
||||
--- | --- | ---
|
||||
ORACLE_BRIDGE_MODE | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | NATIVE_TO_ERC / ERC_TO_ERC / ERC_TO_NATIVE
|
||||
ORACLE_BRIDGE_MODE | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | NATIVE_TO_ERC / ERC_TO_ERC / ERC_TO_NATIVE / ARBITRARY_MESSAGE
|
||||
ORACLE_ALLOW_HTTP_FOR_RPC | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC URLs can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no`
|
||||
ORACLE_HOME_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Home network for new blocks. The interval should match the average production time for a new block. | integer
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Foreign network for new blocks. The interval should match the average production time for a new block. | integer
|
||||
@@ -37,6 +37,10 @@ ORACLE_MAX_PROCESSING_TIME | The workers processes will be killed if this amount
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal without "0x"
|
||||
ORACLE_VALIDATOR_ADDRESS | The public address of the bridge validator | hexidecimal with "0x"
|
||||
ORACLE_TX_REDUNDANCY | If set to `true`, instructs oracle to send `eth_sendRawTransaction` requests through all available RPC urls defined in `COMMON_HOME_RPC_URL` and `COMMON_FOREIGN_RPC_URL` variables instead of using first available one
|
||||
ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST | Filename with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests will be automatically processed by the CollectedSignatures watcher. | string
|
||||
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST | Filename with a list of addresses, separated by newlines. If set, determines the blocked set of accounts whose requests will not be automatically processed by the CollectedSignatures watcher. Has a lower priority than the `ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST` | string
|
||||
ORACLE_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
|
||||
ORACLE_ALWAYS_RELAY_SIGNATURES | If set to `true`, the oracle will always relay signatures even if it was not the last who finilized the signatures collecting process. The default is `false`. | `true` / `false`
|
||||
|
||||
|
||||
## UI configuration
|
||||
@@ -73,4 +77,4 @@ MONITOR_VALIDATOR_FOREIGN_TX_LIMIT | Average gas usage of a transaction sent by
|
||||
MONITOR_TX_NUMBER_THRESHOLD | If estimated number of transaction is equal to or below this value, the monitor will report that the validator has less funds than it is required. | integer
|
||||
MONITOR_PORT | The port for the Monitor. | integer
|
||||
MONITOR_BRIDGE_NAME | The name to be used in the url path for the bridge | string
|
||||
MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for other workers runs
|
||||
MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for other workers runs | `true` / `false`
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
"eslint-plugin-jest": "^23.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9"
|
||||
"node": ">= 10.18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export const CONFIRMATIONS_STATUS_DESCRIPTION_HOME: { [key: string]: string } =
|
||||
EXECUTION_PENDING:
|
||||
'The specified transaction was included in a block\nand the validators collected signatures. The\nvalidator’s transaction with collected signatures was\nsent but is not yet added to a block.',
|
||||
EXECUTION_WAITING:
|
||||
'The specified transaction was included in a block\nand the validators collected signatures. Either\n1. One of the validators is waiting for chain finalization.\n2. A validator skipped its duty to relay signatures.\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
|
||||
'The specified transaction was included in a block\nand the validators collected signatures. Either\n1. One of the validators is waiting for chain finalization.\n2. A validator skipped its duty to relay signatures.\n3. The execution transaction is still pending (e.g. due to the gas price spike).\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
|
||||
FAILED:
|
||||
'The specified transaction was included in a block,\nbut transactions with signatures sent by a majority of\nvalidators failed. The cross-chain relay request will\nnot be processed. Contact to the validators by\nmessaging on %linkhttps://forum.poa.network/c/support',
|
||||
PENDING:
|
||||
|
||||
@@ -169,13 +169,13 @@ const getPastEvents = async (
|
||||
const middlePlusOne = middle.add(toBN(1))
|
||||
|
||||
const firstHalfEvents = await getPastEvents(contract, {
|
||||
...options,
|
||||
options,
|
||||
event,
|
||||
fromBlock,
|
||||
toBlock: middle
|
||||
})
|
||||
const secondHalfEvents = await getPastEvents(contract, {
|
||||
...options,
|
||||
options,
|
||||
event,
|
||||
fromBlock: middlePlusOne,
|
||||
toBlock
|
||||
|
||||
@@ -65,6 +65,20 @@ const homeV1Abi = [
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredBlockConfirmations',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -154,6 +168,20 @@ const foreignViAbi = [
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredBlockConfirmations',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
0
e2e-commons/access-lists/allowance_list.txt
Normal file
0
e2e-commons/access-lists/allowance_list.txt
Normal file
1
e2e-commons/access-lists/block_list.txt
Normal file
1
e2e-commons/access-lists/block_list.txt
Normal file
@@ -0,0 +1 @@
|
||||
0xc9e38bfdB9c635F0796ad83CC8705dc379F41c04
|
||||
@@ -23,3 +23,4 @@ ORACLE_FOREIGN_RPC_POLLING_INTERVAL=500
|
||||
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
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"address": "0x3CC5baAB679eC0732C175760079Bf48F564ad26B",
|
||||
"privateKey": "0xedb53ee050631b7914d5f1a66c2f0d2df3ec85a9ed2a9616b16a7b1b7a10b8d1"
|
||||
},
|
||||
"blockedUser": {
|
||||
"address": "0xc9e38bfdB9c635F0796ad83CC8705dc379F41c04",
|
||||
"privateKey": "0x65df4ea787916f6ed9660f0b0fe36858a65735ad0dcd34527497f4ce32e53883"
|
||||
},
|
||||
"validator": {
|
||||
"address": "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b",
|
||||
"privateKey": "0x8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
|
||||
@@ -50,6 +50,9 @@ services:
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
command: "true"
|
||||
volumes:
|
||||
- '../e2e-commons/access-lists/block_list.txt:/mono/oracle/access-lists/block_list.txt'
|
||||
- '../e2e-commons/access-lists/allowance_list.txt:/mono/oracle/access-lists/allowance_list.txt'
|
||||
networks:
|
||||
- ultimate
|
||||
oracle-amb:
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"axios": "0.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9"
|
||||
"node": ">= 10.18"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"web3": "1.0.0-beta.34"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9"
|
||||
"node": ">= 10.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0"
|
||||
|
||||
@@ -74,6 +74,11 @@ async function main(mode) {
|
||||
|
||||
logger.debug('getting last block numbers')
|
||||
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
|
||||
const homeConfirmations = toBN(await homeBridge.methods.requiredBlockConfirmations().call())
|
||||
const foreignConfirmations = toBN(await foreignBridge.methods.requiredBlockConfirmations().call())
|
||||
const homeDelayedBlockNumber = homeBlockNumber.sub(homeConfirmations)
|
||||
const foreignDelayedBlockNumber = foreignBlockNumber.sub(foreignConfirmations)
|
||||
|
||||
let homeToForeignRequests = []
|
||||
let foreignToHomeRequests = []
|
||||
let homeMigrationBlock = MONITOR_HOME_START_BLOCK
|
||||
@@ -90,7 +95,7 @@ async function main(mode) {
|
||||
homeToForeignRequests = (await getPastEvents(oldHomeBridge, {
|
||||
event: 'UserRequestForSignature',
|
||||
fromBlock: MONITOR_HOME_START_BLOCK,
|
||||
toBlock: homeBlockNumber
|
||||
toBlock: homeDelayedBlockNumber
|
||||
})).map(normalizeEvent)
|
||||
logger.debug(`found ${homeToForeignRequests.length} events`)
|
||||
if (homeToForeignRequests.length > 0) {
|
||||
@@ -101,7 +106,7 @@ async function main(mode) {
|
||||
foreignToHomeRequests = (await getPastEvents(oldForeignBridge, {
|
||||
event: 'UserRequestForAffirmation',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber
|
||||
toBlock: foreignDelayedBlockNumber
|
||||
})).map(normalizeEvent)
|
||||
logger.debug(`found ${foreignToHomeRequests.length} events`)
|
||||
if (foreignToHomeRequests.length > 0) {
|
||||
@@ -113,7 +118,7 @@ async function main(mode) {
|
||||
const homeToForeignRequestsNew = (await getPastEvents(homeBridge, {
|
||||
event: v1Bridge ? 'Deposit' : 'UserRequestForSignature',
|
||||
fromBlock: homeMigrationBlock,
|
||||
toBlock: homeBlockNumber
|
||||
toBlock: homeDelayedBlockNumber
|
||||
})).map(normalizeEvent)
|
||||
homeToForeignRequests = [...homeToForeignRequests, ...homeToForeignRequestsNew]
|
||||
|
||||
@@ -135,7 +140,7 @@ async function main(mode) {
|
||||
const foreignToHomeRequestsNew = (await getPastEvents(foreignBridge, {
|
||||
event: v1Bridge ? 'Withdraw' : 'UserRequestForAffirmation',
|
||||
fromBlock: foreignMigrationBlock,
|
||||
toBlock: foreignBlockNumber
|
||||
toBlock: foreignDelayedBlockNumber
|
||||
})).map(normalizeEvent)
|
||||
foreignToHomeRequests = [...foreignToHomeRequests, ...foreignToHomeRequestsNew]
|
||||
|
||||
@@ -144,7 +149,7 @@ async function main(mode) {
|
||||
let transferEvents = (await getPastEvents(erc20Contract, {
|
||||
event: 'Transfer',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber,
|
||||
toBlock: foreignDelayedBlockNumber,
|
||||
options: {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
}
|
||||
@@ -194,7 +199,7 @@ async function main(mode) {
|
||||
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
|
||||
event: 'Transfer',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber,
|
||||
toBlock: foreignDelayedBlockNumber,
|
||||
options: {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
const Web3 = require('web3')
|
||||
const { BRIDGE_MODES, getBridgeMode, HOME_ERC_TO_ERC_ABI } = require('../../commons')
|
||||
|
||||
const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_HOME_RPC_URL } = process.env
|
||||
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
|
||||
const web3Home = new Web3(homeProvider)
|
||||
|
||||
async function isV1Bridge() {
|
||||
const homeBridge = new web3Home.eth.Contract(HOME_ERC_TO_ERC_ABI, COMMON_HOME_BRIDGE_ADDRESS)
|
||||
const bridgeMode = await getBridgeMode(homeBridge)
|
||||
return bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isV1Bridge
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
"websocket": "^1.0.28"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9"
|
||||
"node": ">= 10.18"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ const promiseRetry = require('promise-retry')
|
||||
const {
|
||||
user,
|
||||
secondUser,
|
||||
blockedUser,
|
||||
validator,
|
||||
ercToNativeBridge,
|
||||
homeRPC,
|
||||
foreignRPC
|
||||
} = require('../../e2e-commons/constants.json')
|
||||
const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI, HOME_ERC_TO_NATIVE_ABI } = require('../../commons')
|
||||
const { uniformRetry } = require('../../e2e-commons/utils')
|
||||
const { uniformRetry, sleep } = require('../../e2e-commons/utils')
|
||||
const { setRequiredSignatures } = require('./utils')
|
||||
|
||||
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
|
||||
@@ -22,6 +23,7 @@ const COMMON_FOREIGN_BRIDGE_ADDRESS = ercToNativeBridge.foreign
|
||||
const { toBN } = foreignWeb3.utils
|
||||
|
||||
homeWeb3.eth.accounts.wallet.add(user.privateKey)
|
||||
homeWeb3.eth.accounts.wallet.add(blockedUser.privateKey)
|
||||
homeWeb3.eth.accounts.wallet.add(validator.privateKey)
|
||||
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
|
||||
foreignWeb3.eth.accounts.wallet.add(validator.privateKey)
|
||||
@@ -142,6 +144,47 @@ describe('erc to native', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
it('should not process transaction from blocked users', async () => {
|
||||
const originalBalance1 = await erc20Token.methods.balanceOf(user.address).call()
|
||||
const originalBalance2 = await erc20Token.methods.balanceOf(blockedUser.address).call()
|
||||
|
||||
// check that account has tokens in home chain
|
||||
const balance1 = await homeWeb3.eth.getBalance(user.address)
|
||||
const balance2 = await homeWeb3.eth.getBalance(blockedUser.address)
|
||||
assert(!toBN(balance1).isZero(), 'Account should have tokens')
|
||||
assert(!toBN(balance2).isZero(), 'Account should have tokens')
|
||||
|
||||
// send transaction to home bridge
|
||||
await homeWeb3.eth.sendTransaction({
|
||||
from: user.address,
|
||||
to: COMMON_HOME_BRIDGE_ADDRESS,
|
||||
gasPrice: '1',
|
||||
gas: '1000000',
|
||||
value: homeWeb3.utils.toWei('0.01')
|
||||
})
|
||||
|
||||
// send transaction to home bridge
|
||||
await homeWeb3.eth.sendTransaction({
|
||||
from: blockedUser.address,
|
||||
to: COMMON_HOME_BRIDGE_ADDRESS,
|
||||
gasPrice: '1',
|
||||
gas: '1000000',
|
||||
value: homeWeb3.utils.toWei('0.01')
|
||||
})
|
||||
|
||||
// check that balance increases
|
||||
await uniformRetry(async retry => {
|
||||
const balance = await erc20Token.methods.balanceOf(user.address).call()
|
||||
if (toBN(balance).lte(toBN(originalBalance1))) {
|
||||
retry()
|
||||
}
|
||||
})
|
||||
|
||||
await sleep(3000)
|
||||
|
||||
const balance = await erc20Token.methods.balanceOf(blockedUser.address).call()
|
||||
assert(toBN(balance).eq(toBN(originalBalance2)), 'Bridge should not process collected signatures from blocked user')
|
||||
})
|
||||
it('should not invest dai when chai token is disabled', async () => {
|
||||
const bridgeDaiTokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ ORACLE_FOREIGN_START_BLOCK=
|
||||
ORACLE_LOG_LEVEL=debug
|
||||
ORACLE_MAX_PROCESSING_TIME=20000
|
||||
|
||||
ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST=access-lists/allowance_list.txt
|
||||
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=access-lists/block_list.txt
|
||||
ORACLE_HOME_TO_FOREIGN_CHECK_SENDER=false
|
||||
|
||||
#Uncomment these lines only if you are going to send transaction by testing scripts
|
||||
#USER_ADDRESS=0x59c4474184579b9c31b5e51445b6eef91cebf370
|
||||
#USER_ADDRESS_PRIVATE_KEY=
|
||||
|
||||
@@ -25,6 +25,9 @@ services:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_collected
|
||||
volumes:
|
||||
- '~/bridge_data/access-lists/block_list.txt:/mono/oracle/access-lists/block_list.txt'
|
||||
- '~/bridge_data/access-lists/allowance_list.txt:/mono/oracle/access-lists/allowance_list.txt'
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
|
||||
@@ -48,6 +48,6 @@
|
||||
"sinon": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9"
|
||||
"node": ">= 10.18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ async function sendJobTx(jobs) {
|
||||
e.message
|
||||
)
|
||||
|
||||
if (e.message.includes('Insufficient funds')) {
|
||||
if (e.message.toLowerCase().includes('insufficient funds')) {
|
||||
const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
|
||||
const minimumBalance = gasLimit.multipliedBy(gasPrice)
|
||||
logger.error(
|
||||
|
||||
@@ -12,6 +12,8 @@ const { MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/const
|
||||
|
||||
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
|
||||
|
||||
const { ORACLE_ALWAYS_RELAY_SIGNATURES } = process.env
|
||||
|
||||
let validatorContract = null
|
||||
|
||||
function processCollectedSignaturesBuilder(config) {
|
||||
@@ -39,7 +41,9 @@ function processCollectedSignaturesBuilder(config) {
|
||||
eventTransactionHash: colSignature.transactionHash
|
||||
})
|
||||
|
||||
if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) {
|
||||
if (ORACLE_ALWAYS_RELAY_SIGNATURES && ORACLE_ALWAYS_RELAY_SIGNATURES === 'true') {
|
||||
logger.debug('Validator handles all CollectedSignature requests')
|
||||
} else if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) {
|
||||
logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,11 +4,19 @@ const { HttpListProviderError } = require('http-list-provider')
|
||||
const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons')
|
||||
const rootLogger = require('../../services/logger')
|
||||
const { web3Home, web3Foreign } = require('../../services/web3')
|
||||
const { signatureToVRS, packSignatures } = require('../../utils/message')
|
||||
const { signatureToVRS, packSignatures, parseMessage } = require('../../utils/message')
|
||||
const { readAccessListFile } = require('../../utils/utils')
|
||||
const estimateGas = require('./estimateGas')
|
||||
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
|
||||
const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
|
||||
|
||||
const {
|
||||
ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST,
|
||||
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST,
|
||||
ORACLE_HOME_TO_FOREIGN_CHECK_SENDER,
|
||||
ORACLE_ALWAYS_RELAY_SIGNATURES
|
||||
} = process.env
|
||||
|
||||
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
|
||||
|
||||
let validatorContract = null
|
||||
@@ -38,7 +46,9 @@ function processCollectedSignaturesBuilder(config) {
|
||||
eventTransactionHash: colSignature.transactionHash
|
||||
})
|
||||
|
||||
if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) {
|
||||
if (ORACLE_ALWAYS_RELAY_SIGNATURES && ORACLE_ALWAYS_RELAY_SIGNATURES === 'true') {
|
||||
logger.debug('Validator handles all CollectedSignature requests')
|
||||
} else if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) {
|
||||
logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`)
|
||||
return
|
||||
}
|
||||
@@ -46,6 +56,53 @@ function processCollectedSignaturesBuilder(config) {
|
||||
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
|
||||
const message = await homeBridge.methods.message(messageHash).call()
|
||||
|
||||
if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST || ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) {
|
||||
const parsedMessage = parseMessage(message)
|
||||
const recipient = parsedMessage.recipient.toLowerCase()
|
||||
const originalTxHash = parsedMessage.txHash
|
||||
|
||||
if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
|
||||
const allowanceList = await readAccessListFile(ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST, logger)
|
||||
if (allowanceList.indexOf(recipient) === -1) {
|
||||
if (ORACLE_HOME_TO_FOREIGN_CHECK_SENDER === 'true') {
|
||||
logger.debug({ txHash: originalTxHash }, 'Requested sender of an original withdrawal transaction')
|
||||
const originalTx = await web3Home.eth.getTransaction(originalTxHash)
|
||||
logger.debug(`Tx data of an original withdrawal transaction ${originalTx.input}`)
|
||||
const isRelayTokens = originalTx.input.slice(2, 10) === '5d1e9307'
|
||||
logger.info(`Is the relayTokens method invoked: ${isRelayTokens}`)
|
||||
const sender = originalTx.from.toLowerCase()
|
||||
if (allowanceList.indexOf(sender) === -1 && !isRelayTokens) {
|
||||
logger.info(
|
||||
{ sender, recipient },
|
||||
'Validator skips a transaction. Neither sender nor recipient addresses are in the allowance list.'
|
||||
)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
{ recipient },
|
||||
'Validator skips a transaction. Recipient address is not 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.indexOf(recipient) > -1) {
|
||||
logger.info({ recipient }, 'Validator skips a transaction. Recipient address is in the block list.')
|
||||
return
|
||||
}
|
||||
if (ORACLE_HOME_TO_FOREIGN_CHECK_SENDER === 'true') {
|
||||
logger.debug({ txHash: originalTxHash }, 'Requested sender of an original withdrawal transaction')
|
||||
const sender = (await web3Home.eth.getTransaction(originalTxHash)).from.toLowerCase()
|
||||
if (blockList.indexOf(sender) > -1) {
|
||||
logger.info({ sender }, 'Validator skips a transaction. Sender address is in the block list.')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug({ NumberOfCollectedSignatures }, 'Number of signatures to get')
|
||||
|
||||
const requiredSignatures = []
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
require('../env')
|
||||
const path = require('path')
|
||||
const { toBN } = require('web3-utils')
|
||||
const { connectSenderToQueue } = require('./services/amqpClient')
|
||||
const { redis } = require('./services/redisClient')
|
||||
const GasPrice = require('./services/gasPrice')
|
||||
@@ -81,13 +80,17 @@ async function readNonce(forceUpdate) {
|
||||
logger.debug({ nonce }, 'Nonce found in the DB')
|
||||
return Number(nonce)
|
||||
} else {
|
||||
logger.debug("Nonce wasn't found in the DB")
|
||||
logger.warn("Nonce wasn't found in the DB")
|
||||
return getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS)
|
||||
}
|
||||
}
|
||||
|
||||
function updateNonce(nonce) {
|
||||
return redis.set(nonceKey, nonce)
|
||||
if (typeof nonce !== 'number') {
|
||||
logger.warn('Given nonce value is not a valid number. Nothing will be updated in the DB.')
|
||||
} else {
|
||||
redis.set(nonceKey, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleTransactionResend }) {
|
||||
@@ -98,21 +101,23 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
|
||||
}
|
||||
|
||||
const txArray = JSON.parse(msg.content)
|
||||
logger.info(`Msg received with ${txArray.length} Tx to send`)
|
||||
const gasPrice = GasPrice.getPrice()
|
||||
logger.debug(`Msg received with ${txArray.length} Tx to send`)
|
||||
const gasPrice = GasPrice.getPrice().toString(10)
|
||||
|
||||
let nonce = await readNonce()
|
||||
let nonce
|
||||
let insufficientFunds = false
|
||||
let minimumBalance = null
|
||||
const failedTx = []
|
||||
const sentTx = []
|
||||
const resendJobs = []
|
||||
|
||||
const isResend = txArray.length > 0 && !!txArray[0].txHash
|
||||
|
||||
if (isResend) {
|
||||
logger.debug(`Checking status of ${txArray.length} transactions`)
|
||||
logger.info(`Checking status of ${txArray.length} transactions`)
|
||||
nonce = null
|
||||
} else {
|
||||
logger.debug(`Sending ${txArray.length} transactions`)
|
||||
logger.info(`Sending ${txArray.length} transactions`)
|
||||
nonce = await readNonce()
|
||||
}
|
||||
await syncForEach(txArray, async job => {
|
||||
let gasLimit
|
||||
@@ -123,38 +128,26 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
|
||||
}
|
||||
|
||||
try {
|
||||
let txNonce
|
||||
if (isResend) {
|
||||
const tx = await web3Instance.eth.getTransaction(job.txHash)
|
||||
|
||||
if (tx === null) {
|
||||
logger.info(`Transaction ${job.txHash} was not found, dropping it`)
|
||||
return
|
||||
}
|
||||
if (tx.blockNumber !== null) {
|
||||
logger.info(`Transaction ${job.txHash} was successfully mined`)
|
||||
if (tx && tx.blockNumber !== null) {
|
||||
logger.debug(`Transaction ${job.txHash} was successfully mined`)
|
||||
return
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Previously sent transaction is stuck, updating gasPrice: ${tx.gasPrice} -> ${gasPrice.toString(10)}`
|
||||
)
|
||||
if (toBN(tx.gasPrice).gte(toBN(gasPrice))) {
|
||||
logger.info("Gas price returned from the oracle didn't increase, will reinspect this transaction later")
|
||||
sentTx.push(job)
|
||||
return
|
||||
if (nonce === null) {
|
||||
nonce = await readNonce(true)
|
||||
}
|
||||
|
||||
txNonce = tx.nonce
|
||||
} else {
|
||||
txNonce = nonce++
|
||||
logger.info(`Transaction ${job.txHash} was not mined, updating gasPrice: ${job.gasPrice} -> ${gasPrice}`)
|
||||
}
|
||||
logger.info(`Sending transaction with nonce ${txNonce}`)
|
||||
logger.info(`Sending transaction with nonce ${nonce}`)
|
||||
const txHash = await sendTx({
|
||||
chain: config.id,
|
||||
data: job.data,
|
||||
nonce: txNonce,
|
||||
gasPrice: gasPrice.toString(10),
|
||||
nonce,
|
||||
gasPrice,
|
||||
amount: '0',
|
||||
gasLimit,
|
||||
privateKey: ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
|
||||
@@ -162,11 +155,14 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
|
||||
chainId,
|
||||
web3: web3Instance
|
||||
})
|
||||
sentTx.push({
|
||||
const resendJob = {
|
||||
...job,
|
||||
txHash
|
||||
})
|
||||
txHash,
|
||||
gasPrice
|
||||
}
|
||||
resendJobs.push(resendJob)
|
||||
|
||||
nonce++
|
||||
logger.info(
|
||||
{ eventTransactionHash: job.transactionReference, generatedTransactionHash: txHash },
|
||||
`Tx generated ${txHash} for event Tx ${job.transactionReference}`
|
||||
@@ -177,11 +173,15 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
|
||||
`Tx Failed for event Tx ${job.transactionReference}.`,
|
||||
e.message
|
||||
)
|
||||
if (!e.message.includes('Transaction with the same hash was already imported')) {
|
||||
if (!e.message.toLowerCase().includes('transaction with the same hash was already imported')) {
|
||||
if (isResend) {
|
||||
resendJobs.push(job)
|
||||
} else {
|
||||
failedTx.push(job)
|
||||
}
|
||||
}
|
||||
|
||||
if (e.message.includes('Insufficient funds')) {
|
||||
if (e.message.toLowerCase().includes('insufficient funds')) {
|
||||
insufficientFunds = true
|
||||
const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
|
||||
minimumBalance = gasLimit.multipliedBy(gasPrice)
|
||||
@@ -194,16 +194,18 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
|
||||
}
|
||||
})
|
||||
|
||||
if (typeof nonce === 'number') {
|
||||
logger.debug('Updating nonce')
|
||||
await updateNonce(nonce)
|
||||
}
|
||||
|
||||
if (failedTx.length) {
|
||||
logger.info(`Sending ${failedTx.length} Failed Tx to Queue`)
|
||||
await scheduleForRetry(failedTx, msg.properties.headers['x-retries'])
|
||||
}
|
||||
if (sentTx.length) {
|
||||
logger.info(`Sending ${sentTx.length} Tx Delayed Resend Requests to Queue`)
|
||||
await scheduleTransactionResend(sentTx)
|
||||
if (resendJobs.length) {
|
||||
logger.info(`Sending ${resendJobs.length} Tx Delayed Resend Requests to Queue`)
|
||||
await scheduleTransactionResend(resendJobs)
|
||||
}
|
||||
ackMsg(msg)
|
||||
logger.debug(`Finished processing msg`)
|
||||
|
||||
@@ -21,7 +21,7 @@ module.exports = {
|
||||
},
|
||||
GAS_PRICE_BOUNDARIES: {
|
||||
MIN: 1,
|
||||
MAX: 250
|
||||
MAX: 1000
|
||||
},
|
||||
TRANSACTION_RESEND_TIMEOUT: 20 * 60 * 1000,
|
||||
SENDER_QUEUE_MAX_PRIORITY: 10,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const fs = require('fs')
|
||||
const BigNumber = require('bignumber.js')
|
||||
const promiseRetry = require('promise-retry')
|
||||
const Web3 = require('web3')
|
||||
@@ -93,10 +94,11 @@ function privateKeyToAddress(privateKey) {
|
||||
}
|
||||
|
||||
function nonceError(e) {
|
||||
const message = e.message.toLowerCase()
|
||||
return (
|
||||
e.message.includes('Transaction nonce is too low') ||
|
||||
e.message.includes('nonce too low') ||
|
||||
e.message.includes('transaction with same nonce in the queue')
|
||||
message.includes('transaction nonce is too low') ||
|
||||
message.includes('nonce too low') ||
|
||||
message.includes('transaction with same nonce in the queue')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,6 +107,27 @@ function nonceError(e) {
|
||||
const invert = p => new Promise((res, rej) => p.then(rej, res))
|
||||
const promiseAny = ps => invert(Promise.all(ps.map(invert)))
|
||||
|
||||
const readAccessLists = {}
|
||||
async function readAccessListFile(fileName, logger) {
|
||||
if (!readAccessLists[fileName]) {
|
||||
logger.debug({ fileName }, 'Access list file read requested')
|
||||
try {
|
||||
const data = await fs.promises.readFile(fileName)
|
||||
readAccessLists[fileName] = data
|
||||
.toString()
|
||||
.split('\n')
|
||||
.map(addr => addr.trim().toLowerCase())
|
||||
.filter(addr => addr.length === 42)
|
||||
logger.info({ fileName }, `Access list was read successfully, ${data.length} addresses found`)
|
||||
logger.debug({ addresses: readAccessLists[fileName] }, `Read addresses from the file`)
|
||||
} catch (e) {
|
||||
readAccessLists[fileName] = []
|
||||
logger.error({ fileName, error: e }, `Failed to read access list from the file`)
|
||||
}
|
||||
}
|
||||
return readAccessLists[fileName]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
syncForEach,
|
||||
checkHTTPS,
|
||||
@@ -115,5 +138,6 @@ module.exports = {
|
||||
privateKeyToAddress,
|
||||
nonceError,
|
||||
getRetrySequence,
|
||||
promiseAny
|
||||
promiseAny,
|
||||
readAccessListFile
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"Dcef88209a20D52165230104B245803C3269454d": { "balance": "1606938044258990275541962092341162602522202993782792835301376" },
|
||||
"bb140FbA6242a1c3887A7823F7750a73101383e3": { "balance": "1606938044258990275541962092341162602522202993782792835301376" },
|
||||
"7FC1442AB55Da569940Eb750AaD2BAA63DA4010E": { "balance": "500000000000000000000" },
|
||||
"B4579fd5AfEaB60B03Db3F408AAdD315035943f7": { "balance": "500000000000000000000" }
|
||||
"B4579fd5AfEaB60B03Db3F408AAdD315035943f7": { "balance": "500000000000000000000" },
|
||||
"c9e38bfdB9c635F0796ad83CC8705dc379F41c04": { "balance": "500000000000000000000" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"lint": "eslint . --ignore-path ../.eslintignore"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9"
|
||||
"node": ">= 10.18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ class ForeignStore {
|
||||
@action
|
||||
async getCurrentLimit() {
|
||||
try {
|
||||
const result = await getCurrentLimit(this.foreignBridge, this.tokenDecimals)
|
||||
const result = await getCurrentLimit(this.foreignBridge, this.homeStore.homeBridge, this.tokenDecimals)
|
||||
this.maxCurrentDeposit = result.maxCurrentDeposit
|
||||
this.dailyLimit = result.dailyLimit
|
||||
this.totalSpentPerDay = result.totalSpentPerDay
|
||||
|
||||
@@ -429,7 +429,11 @@ class HomeStore {
|
||||
@action
|
||||
async getCurrentLimit() {
|
||||
try {
|
||||
const result = await getCurrentLimit(this.homeBridge, this.tokenDecimals)
|
||||
const result = await getCurrentLimit(
|
||||
this.homeBridge,
|
||||
this.rootStore.foreignStore.foreignBridge,
|
||||
this.tokenDecimals
|
||||
)
|
||||
this.maxCurrentDeposit = result.maxCurrentDeposit
|
||||
this.dailyLimit = result.dailyLimit
|
||||
this.totalSpentPerDay = result.totalSpentPerDay
|
||||
|
||||
@@ -21,10 +21,10 @@ export const getMinPerTxLimit = async (contract, decimals) => {
|
||||
return fromDecimals(minPerTx, decimals)
|
||||
}
|
||||
|
||||
export const getCurrentLimit = async (contract, decimals) => {
|
||||
export const getCurrentLimit = async (contract, otherContract, decimals) => {
|
||||
const currentDay = await contract.methods.getCurrentDay().call()
|
||||
const dailyLimit = await contract.methods.dailyLimit().call()
|
||||
const totalSpentPerDay = await contract.methods.totalSpentPerDay(currentDay).call()
|
||||
const totalSpentPerDay = await otherContract.methods.totalExecutedPerDay(currentDay).call()
|
||||
const maxCurrentDeposit = new BN(dailyLimit).minus(new BN(totalSpentPerDay)).toString(10)
|
||||
return {
|
||||
maxCurrentDeposit: fromDecimals(maxCurrentDeposit, decimals),
|
||||
|
||||
Reference in New Issue
Block a user