Possibility to allow/block specific addresses in erc-to-native mode (#442)

This commit is contained in:
Kirill Fedoseev 2020-09-28 14:54:03 +03:00 committed by GitHub
parent 48dd53622c
commit dc377aeb9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 146 additions and 10 deletions

@ -37,6 +37,9 @@ 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`
## UI configuration

@ -19,6 +19,6 @@
"eslint-plugin-jest": "^23.18.0"
},
"engines": {
"node": ">= 8.9"
"node": ">= 10.18"
}
}

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

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

@ -4,11 +4,18 @@ 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
} = process.env
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
let validatorContract = null
@ -46,6 +53,49 @@ 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 sender = (await web3Home.eth.getTransaction(originalTxHash)).from.toLowerCase()
if (allowanceList.indexOf(sender) === -1) {
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,3 +1,4 @@
const fs = require('fs')
const BigNumber = require('bignumber.js')
const promiseRetry = require('promise-retry')
const Web3 = require('web3')
@ -105,6 +106,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 +137,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"
}
}