diff --git a/src/deploy/deploy-home/migrations/1_deployment.js b/src/deploy/deploy-home/migrations/1_deployment.js index 5bd4359..fe88868 100644 --- a/src/deploy/deploy-home/migrations/1_deployment.js +++ b/src/deploy/deploy-home/migrations/1_deployment.js @@ -1,20 +1,20 @@ const Bridge = artifacts.require('Bridge') const addresses = Object.entries(process.env) - .filter(([ key ]) => key.startsWith('VALIDATOR_ADDRESS')) - .map(([ , value ]) => value) + .filter(([key]) => key.startsWith('VALIDATOR_ADDRESS')) + .map(([, value]) => value) const { THRESHOLD, HOME_TOKEN_ADDRESS, MIN_TX_LIMIT, MAX_TX_LIMIT, BLOCKS_RANGE_SIZE } = process.env -module.exports = deployer => { +module.exports = (deployer) => { deployer.deploy( Bridge, THRESHOLD, addresses, HOME_TOKEN_ADDRESS, - [ MIN_TX_LIMIT, MAX_TX_LIMIT ], + [MIN_TX_LIMIT, MAX_TX_LIMIT], BLOCKS_RANGE_SIZE ) } diff --git a/src/deploy/deploy-side/migrations/1_deployment.js b/src/deploy/deploy-side/migrations/1_deployment.js index bad14cd..7b2ff31 100644 --- a/src/deploy/deploy-side/migrations/1_deployment.js +++ b/src/deploy/deploy-side/migrations/1_deployment.js @@ -1,5 +1,5 @@ const SharedDB = artifacts.require('SharedDB') -module.exports = deployer => { +module.exports = (deployer) => { deployer.deploy(SharedDB) } diff --git a/src/oracle/bncWatcher/bncWatcher.js b/src/oracle/bncWatcher/bncWatcher.js index b6ac7a8..91df7a1 100644 --- a/src/oracle/bncWatcher/bncWatcher.js +++ b/src/oracle/bncWatcher/bncWatcher.js @@ -9,70 +9,50 @@ const { publicKeyToAddress } = require('./crypto') const { FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env -const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME) -const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL) -const FOREIGN_FETCH_BLOCK_TIME_OFFSET = parseInt(process.env.FOREIGN_FETCH_BLOCK_TIME_OFFSET) +const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME, 10) +const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL, 10) +const FOREIGN_FETCH_BLOCK_TIME_OFFSET = parseInt(process.env.FOREIGN_FETCH_BLOCK_TIME_OFFSET, 10) const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL }) const proxyHttpClient = axios.create({ baseURL: PROXY_URL }) -async function initialize () { - if (await redis.get('foreignTime') === null) { - logger.info('Set default foreign time') - await redis.set('foreignTime', FOREIGN_START_TIME) +function getLastForeignAddress() { + const epoch = Math.max(0, ...fs.readdirSync('/keys') + .map((x) => parseInt(x.split('.')[0].substr(4), 10))) + if (epoch === 0) { + return null } + const keysFile = `/keys/keys${epoch}.store` + const publicKey = JSON.parse(fs.readFileSync(keysFile))[5] + return publicKeyToAddress(publicKey) } -async function main () { - const { transactions, endTime } = await fetchNewTransactions() - if (!transactions || transactions.length === 0) { - logger.debug(`Found 0 new transactions`) - await new Promise(r => setTimeout(r, FOREIGN_FETCH_INTERVAL)) - return - } - - logger.info(`Found ${transactions.length} new transactions`) - logger.trace('%o', transactions) - - for (const tx of transactions.reverse()) { - if (tx.memo !== 'funding') { - const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value - await proxyHttpClient - .post('/transfer', { - to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')), - value: new BN(tx.value).multipliedBy(10 ** 18).integerValue(), - hash: `0x${tx.txHash}` - }) - } - } - await redis.set('foreignTime', endTime) -} - -function getTx (hash) { +function getTx(hash) { return foreignHttpClient .get(`/api/v1/tx/${hash}`, { params: { format: 'json' } }) - .then(res => res.data.tx.value) + .then((res) => res.data.tx.value) .catch(() => getTx(hash)) } -function getBlockTime () { +function getBlockTime() { return foreignHttpClient - .get(`/api/v1/time`) - .then(res => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET) + .get('/api/v1/time') + .then((res) => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET) .catch(() => getBlockTime()) } -async function fetchNewTransactions () { +async function fetchNewTransactions() { logger.debug('Fetching new transactions') - const startTime = parseInt(await redis.get('foreignTime')) + 1 + const startTime = parseInt(await redis.get('foreignTime'), 10) + 1 const address = getLastForeignAddress() const endTime = Math.min(startTime + 3 * 30 * 24 * 60 * 60 * 1000, await getBlockTime()) - if (address === null) + if (address === null) { return {} + } logger.debug('Sending api transactions request') const params = { address, @@ -80,29 +60,58 @@ async function fetchNewTransactions () { txAsset: FOREIGN_ASSET, txType: 'TRANSFER', startTime, - endTime, + endTime } try { logger.trace('%o', params) const transactions = (await foreignHttpClient .get('/api/v1/transactions', { params })).data.tx - return { transactions, endTime } + return { + transactions, + endTime + } } catch (e) { return await fetchNewTransactions() } } -function getLastForeignAddress () { - const epoch = Math.max(0, ...fs.readdirSync('/keys').map(x => parseInt(x.split('.')[0].substr(4)))) - if (epoch === 0) - return null - const keysFile = `/keys/keys${epoch}.store` - const publicKey = JSON.parse(fs.readFileSync(keysFile))[5] - return publicKeyToAddress(publicKey) +async function initialize() { + if (await redis.get('foreignTime') === null) { + logger.info('Set default foreign time') + await redis.set('foreignTime', FOREIGN_START_TIME) + } } -initialize().then(async () => { - while (true) { - await main() +async function main() { + const { transactions, endTime } = await fetchNewTransactions() + if (!transactions || transactions.length === 0) { + logger.debug('Found 0 new transactions') + await new Promise((r) => setTimeout(r, FOREIGN_FETCH_INTERVAL)) + return } -}) + + logger.info(`Found ${transactions.length} new transactions`) + logger.trace('%o', transactions) + + for (let i = transactions.length - 1; i >= 0; i -= 1) { + const tx = transactions[i] + if (tx.memo !== 'funding') { + const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value + await proxyHttpClient + .post('/transfer', { + to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')), + value: new BN(tx.value).multipliedBy(10 ** 18) + .integerValue(), + hash: `0x${tx.txHash}` + }) + } + } + await redis.set('foreignTime', endTime) +} + +initialize() + .then(async () => { + while (true) { + await main() + } + }) diff --git a/src/oracle/bncWatcher/package.json b/src/oracle/bncWatcher/package.json index 445edda..721ea33 100644 --- a/src/oracle/bncWatcher/package.json +++ b/src/oracle/bncWatcher/package.json @@ -9,6 +9,9 @@ "ethers": "4.0.33", "pino": "5.13.4", "pino-pretty": "3.2.1" + }, + "engines": { + "node": ">=10.6.0" } } diff --git a/src/oracle/docker-compose-test.yml b/src/oracle/docker-compose-test.yml index 696d830..2de2b8b 100644 --- a/src/oracle/docker-compose-test.yml +++ b/src/oracle/docker-compose-test.yml @@ -9,10 +9,8 @@ services: - HOME_RPC_URL - HOME_BRIDGE_ADDRESS - HOME_TOKEN_ADDRESS - - HOME_CHAIN_ID - SIDE_RPC_URL - SIDE_SHARED_DB_ADDRESS - - SIDE_CHAIN_ID - VALIDATOR_PRIVATE_KEY - FOREIGN_URL - FOREIGN_ASSET @@ -83,7 +81,6 @@ services: - HOME_RPC_URL - HOME_BRIDGE_ADDRESS - HOME_TOKEN_ADDRESS - - HOME_CHAIN_ID - HOME_START_BLOCK - BLOCKS_RANGE_SIZE - VALIDATOR_PRIVATE_KEY diff --git a/src/oracle/docker-compose.yml b/src/oracle/docker-compose.yml index 0572ed2..994bfa7 100644 --- a/src/oracle/docker-compose.yml +++ b/src/oracle/docker-compose.yml @@ -9,10 +9,8 @@ services: - HOME_RPC_URL - HOME_BRIDGE_ADDRESS - HOME_TOKEN_ADDRESS - - HOME_CHAIN_ID - SIDE_RPC_URL - SIDE_SHARED_DB_ADDRESS - - SIDE_CHAIN_ID - VALIDATOR_PRIVATE_KEY - FOREIGN_URL - FOREIGN_ASSET @@ -93,7 +91,6 @@ services: - HOME_RPC_URL - HOME_BRIDGE_ADDRESS - HOME_TOKEN_ADDRESS - - HOME_CHAIN_ID - HOME_START_BLOCK - VALIDATOR_PRIVATE_KEY - 'RABBITMQ_URL=amqp://rabbitmq:5672' diff --git a/src/oracle/ethWatcher/ethWatcher.js b/src/oracle/ethWatcher/ethWatcher.js index ca9fea6..b59602e 100644 --- a/src/oracle/ethWatcher/ethWatcher.js +++ b/src/oracle/ethWatcher/ethWatcher.js @@ -1,5 +1,5 @@ const Web3 = require('web3') -const utils = require('ethers').utils +const { utils } = require('ethers') const BN = require('bignumber.js') const axios = require('axios') @@ -10,19 +10,21 @@ const { publicKeyToAddress } = require('./crypto') const abiBridge = require('./contracts_data/Bridge.json').abi -const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL, HOME_START_BLOCK, VALIDATOR_PRIVATE_KEY } = process.env +const { + HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL, HOME_START_BLOCK, VALIDATOR_PRIVATE_KEY +} = process.env const homeWeb3 = new Web3(HOME_RPC_URL) const bridge = new homeWeb3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS) const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address +const foreignNonce = [] let channel let exchangeQueue let signQueue let keygenQueue let cancelKeygenQueue let blockNumber -let foreignNonce = [] let epoch let epochStart let redisTx @@ -30,16 +32,17 @@ let rangeSize let lastTransactionBlockNumber let isCurrentValidator -async function resetFutureMessages (queue) { +async function resetFutureMessages(queue) { logger.debug(`Resetting future messages in queue ${queue.name}`) const { messageCount } = await channel.checkQueue(queue.name) if (messageCount) { logger.info(`Filtering ${messageCount} reloaded messages from queue ${queue.name}`) const backup = await assertQueue(channel, `${queue.name}.backup`) - do { + while (true) { const message = await queue.get() - if (message === false) + if (message === false) { break + } const data = JSON.parse(message.content) if (data.blockNumber < blockNumber) { logger.debug('Saving message %o', data) @@ -48,25 +51,134 @@ async function resetFutureMessages (queue) { logger.debug('Dropping message %o', data) } channel.ack(message) - } while (true) + } logger.debug('Dropped messages came from future') - do { + while (true) { const message = await backup.get() - if (message === false) + if (message === false) { break + } const data = JSON.parse(message.content) logger.debug('Requeuing message %o', data) queue.send(data) channel.ack(message) - } while (true) + } logger.debug('Redirected messages back to initial queue') } } -async function initialize () { +async function sendKeygen(event) { + const newEpoch = event.returnValues.newEpoch.toNumber() + keygenQueue.send({ + epoch: newEpoch, + blockNumber, + threshold: (await bridge.methods.getThreshold(newEpoch) + .call()).toNumber(), + parties: (await bridge.methods.getParties(newEpoch) + .call()).toNumber() + }) + logger.debug('Sent keygen start event') +} + +function sendKeygenCancellation(event) { + const eventEpoch = event.returnValues.epoch.toNumber() + cancelKeygenQueue.send({ + epoch: eventEpoch, + blockNumber + }) + logger.debug('Sent keygen cancellation event') +} + +async function sendSignFundsTransfer(event) { + const newEpoch = event.returnValues.newEpoch.toNumber() + const oldEpoch = event.returnValues.oldEpoch.toNumber() + signQueue.send({ + epoch: oldEpoch, + blockNumber, + newEpoch, + nonce: foreignNonce[oldEpoch], + threshold: (await bridge.methods.getThreshold(oldEpoch) + .call()).toNumber(), + parties: (await bridge.methods.getParties(oldEpoch) + .call()).toNumber() + }) + logger.debug('Sent sign funds transfer event') + foreignNonce[oldEpoch] += 1 + redisTx.incr(`foreignNonce${oldEpoch}`) +} + +async function sendSign(event) { + const tx = await homeWeb3.eth.getTransaction(event.transactionHash) + const msg = utils.serializeTransaction({ + nonce: tx.nonce, + gasPrice: `0x${new BN(tx.gasPrice).toString(16)}`, + gasLimit: `0x${new BN(tx.gas).toString(16)}`, + to: tx.to, + value: `0x${new BN(tx.value).toString(16)}`, + data: tx.input, + chainId: await homeWeb3.eth.net.getId() + }) + const hash = homeWeb3.utils.sha3(msg) + const publicKey = utils.recoverPublicKey(hash, { + r: tx.r, + s: tx.s, + v: tx.v + }) + const msgToQueue = { + epoch, + blockNumber, + recipient: publicKeyToAddress({ + x: publicKey.substr(4, 64), + y: publicKey.substr(68, 64) + }), + value: (new BN(event.returnValues.value)).dividedBy(10 ** 18) + .toFixed(8, 3), + nonce: event.returnValues.nonce.toNumber() + } + + exchangeQueue.send(msgToQueue) + logger.debug('Sent new sign event: %o', msgToQueue) + + lastTransactionBlockNumber = blockNumber + redisTx.set('lastTransactionBlockNumber', blockNumber) + logger.debug(`Set lastTransactionBlockNumber to ${blockNumber}`) +} + +async function sendStartSign() { + redisTx.incr(`foreignNonce${epoch}`) + signQueue.send({ + epoch, + blockNumber, + nonce: foreignNonce[epoch], + threshold: (await bridge.methods.getThreshold(epoch) + .call()).toNumber(), + parties: (await bridge.methods.getParties(epoch) + .call()).toNumber() + }) + foreignNonce[epoch] += 1 +} + +async function processEpochStart(event) { + epoch = event.returnValues.epoch.toNumber() + epochStart = blockNumber + logger.info(`Epoch ${epoch} started`) + rangeSize = (await bridge.methods.getRangeSize() + .call()).toNumber() + isCurrentValidator = (await bridge.methods.getValidators() + .call()).includes(validatorAddress) + if (isCurrentValidator) { + logger.info(`${validatorAddress} is a current validator`) + } else { + logger.info(`${validatorAddress} is not a current validator`) + } + logger.info(`Updated range size to ${rangeSize}`) + foreignNonce[epoch] = 0 +} + +async function initialize() { channel = await connectRabbit(RABBITMQ_URL) exchangeQueue = await assertQueue(channel, 'exchangeQueue') signQueue = await assertQueue(channel, 'signQueue') @@ -79,11 +191,12 @@ async function initialize () { epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0 logger.info(`Current epoch ${epoch}`) epochStart = events.length ? events[events.length - 1].blockNumber : 1 - const saved = (parseInt(await redis.get('homeBlock')) + 1) || parseInt(HOME_START_BLOCK) + const saved = (parseInt(await redis.get('homeBlock'), 10) + 1) || parseInt(HOME_START_BLOCK, 10) if (epochStart > saved) { logger.info(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`) blockNumber = epochStart - rangeSize = (await bridge.methods.getRangeSize().call()).toNumber() + rangeSize = (await bridge.methods.getRangeSize() + .call()).toNumber() await redis.multi() .set('homeBlock', blockNumber - 1) .set(`foreignNonce${epoch}`, 0) @@ -92,10 +205,11 @@ async function initialize () { } else { logger.info('Restoring epoch and block number from local db') blockNumber = saved - foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`)) || 0 + foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`), 10) || 0 } logger.debug('Checking if current validator') - isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress) + isCurrentValidator = (await bridge.methods.getValidators() + .call()).includes(validatorAddress) if (isCurrentValidator) { logger.info(`${validatorAddress} is a current validator`) } else { @@ -106,16 +220,16 @@ async function initialize () { await resetFutureMessages(cancelKeygenQueue) await resetFutureMessages(exchangeQueue) await resetFutureMessages(signQueue) - logger.debug(`Sending start commands`) + logger.debug('Sending start commands') await axios.get('http://keygen:8001/start') await axios.get('http://signer:8001/start') } -async function main () { +async function main() { logger.debug(`Watching events in block #${blockNumber}`) if (await homeWeb3.eth.getBlock(blockNumber) === null) { logger.debug('No block') - await new Promise(r => setTimeout(r, 1000)) + await new Promise((r) => setTimeout(r, 1000)) return } @@ -126,7 +240,8 @@ async function main () { toBlock: blockNumber }) - for (const event of bridgeEvents) { + for (let i = 0; i < bridgeEvents.length; i += 1) { + const event = bridgeEvents[i] switch (event.event) { case 'NewEpoch': await sendKeygen(event) @@ -135,14 +250,20 @@ async function main () { sendKeygenCancellation(event) break case 'NewFundsTransfer': - isCurrentValidator && await sendSignFundsTransfer(event) + if (isCurrentValidator) { + await sendSignFundsTransfer(event) + } break case 'ExchangeRequest': - isCurrentValidator && await sendSign(event) + if (isCurrentValidator) { + await sendSign(event) + } break case 'EpochStart': await processEpochStart(event) break + default: + logger.warn('Unknown event %o', event) } } @@ -155,108 +276,16 @@ async function main () { } } - blockNumber++ + blockNumber += 1 // Exec redis tx - await redisTx.incr('homeBlock').exec() + await redisTx.incr('homeBlock') + .exec() await redis.save() } -initialize().then(async () => { - while (true) { - await main() - } -}, e => logger.warn('Initialization failed %o', e)) - -async function sendKeygen (event) { - const newEpoch = event.returnValues.newEpoch.toNumber() - keygenQueue.send({ - epoch: newEpoch, - blockNumber, - threshold: (await bridge.methods.getThreshold(newEpoch).call()).toNumber(), - parties: (await bridge.methods.getParties(newEpoch).call()).toNumber() - }) - logger.debug('Sent keygen start event') -} - -function sendKeygenCancellation (event) { - const epoch = event.returnValues.epoch.toNumber() - cancelKeygenQueue.send({ - epoch, - blockNumber - }) - logger.debug('Sent keygen cancellation event') -} - -async function sendSignFundsTransfer (event) { - const newEpoch = event.returnValues.newEpoch.toNumber() - const oldEpoch = event.returnValues.oldEpoch.toNumber() - signQueue.send({ - epoch: oldEpoch, - blockNumber, - newEpoch, - nonce: foreignNonce[oldEpoch], - threshold: (await bridge.methods.getThreshold(oldEpoch).call()).toNumber(), - parties: (await bridge.methods.getParties(oldEpoch).call()).toNumber() - }) - logger.debug('Sent sign funds transfer event') - foreignNonce[oldEpoch]++ - redisTx.incr(`foreignNonce${oldEpoch}`) -} - -async function sendSign (event) { - const tx = await homeWeb3.eth.getTransaction(event.transactionHash) - const msg = utils.serializeTransaction({ - nonce: tx.nonce, - gasPrice: `0x${new BN(tx.gasPrice).toString(16)}`, - gasLimit: `0x${new BN(tx.gas).toString(16)}`, - to: tx.to, - value: `0x${new BN(tx.value).toString(16)}`, - data: tx.input, - chainId: await homeWeb3.eth.net.getId() - }) - const hash = homeWeb3.utils.sha3(msg) - const publicKey = utils.recoverPublicKey(hash, { r: tx.r, s: tx.s, v: tx.v }) - const msgToQueue = { - epoch, - blockNumber, - recipient: publicKeyToAddress({ - x: publicKey.substr(4, 64), - y: publicKey.substr(68, 64) - }), - value: (new BN(event.returnValues.value)).dividedBy(10 ** 18).toFixed(8, 3), - nonce: event.returnValues.nonce.toNumber() - } - - exchangeQueue.send(msgToQueue) - logger.debug('Sent new sign event: %o', msgToQueue) - - lastTransactionBlockNumber = blockNumber - redisTx.set('lastTransactionBlockNumber', blockNumber) - logger.debug(`Set lastTransactionBlockNumber to ${blockNumber}`) -} - -async function sendStartSign () { - redisTx.incr(`foreignNonce${epoch}`) - signQueue.send({ - epoch, - blockNumber, - nonce: foreignNonce[epoch]++, - threshold: (await bridge.methods.getThreshold(epoch).call()).toNumber(), - parties: (await bridge.methods.getParties(epoch).call()).toNumber() - }) -} - -async function processEpochStart (event) { - epoch = event.returnValues.epoch.toNumber() - epochStart = blockNumber - logger.info(`Epoch ${epoch} started`) - rangeSize = (await bridge.methods.getRangeSize().call()).toNumber() - isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress) - if (isCurrentValidator) { - logger.info(`${validatorAddress} is a current validator`) - } else { - logger.info(`${validatorAddress} is not a current validator`) - } - logger.info(`Updated range size to ${rangeSize}`) - foreignNonce[epoch] = 0 -} +initialize() + .then(async () => { + while (true) { + await main() + } + }, (e) => logger.warn('Initialization failed %o', e)) diff --git a/src/oracle/ethWatcher/package.json b/src/oracle/ethWatcher/package.json index 027e9de..26ec5a6 100644 --- a/src/oracle/ethWatcher/package.json +++ b/src/oracle/ethWatcher/package.json @@ -11,5 +11,8 @@ "pino": "5.13.4", "pino-pretty": "3.2.1", "axios": "0.19.0" + }, + "engines": { + "node": ">=10.6.0" } } diff --git a/src/oracle/proxy/decode.js b/src/oracle/proxy/decode.js index b736749..f1d2e2e 100644 --- a/src/oracle/proxy/decode.js +++ b/src/oracle/proxy/decode.js @@ -1,18 +1,19 @@ const BN = require('bn.js') -function Tokenizer (_buffer) { +function Tokenizer(_buffer) { const buffer = _buffer let position = 0 return { - isEmpty: function () { + isEmpty() { return position === buffer.length }, - parse: function (length = 32, base = 16) { + parse(length = 32, base = 16) { const res = new BN(buffer.slice(position, position + length)).toString(base) position += length return res }, - byte: function () { + byte() { + // eslint-disable-next-line no-plusplus return buffer[position++] } } @@ -21,7 +22,7 @@ function Tokenizer (_buffer) { const keygenDecoders = [ null, // round 1 - function (tokenizer) { + (tokenizer) => { const res = { e: { n: tokenizer.parse(256, 10) @@ -37,23 +38,21 @@ const keygenDecoders = [ return res }, // round 2 - function (tokenizer) { - return { - blind_factor: tokenizer.parse(), - y_i: { - x: tokenizer.parse(), - y: tokenizer.parse() - } + (tokenizer) => ({ + blind_factor: tokenizer.parse(), + y_i: { + x: tokenizer.parse(), + y: tokenizer.parse() } - }, + }), // round 3 - function (tokenizer) { + (tokenizer) => { const res = { ciphertext: [], tag: [] } const ciphertextLength = tokenizer.byte() // probably 32 - for (let i = 0; i < ciphertextLength; i++) { + for (let i = 0; i < ciphertextLength; i += 1) { res.ciphertext.push(tokenizer.byte()) } while (!tokenizer.isEmpty()) { @@ -62,7 +61,7 @@ const keygenDecoders = [ return res }, // round 4 - function (tokenizer) { + (tokenizer) => { const res = { parameters: { threshold: tokenizer.byte(), @@ -73,157 +72,139 @@ const keygenDecoders = [ while (!tokenizer.isEmpty()) { res.commitments.push({ x: tokenizer.parse(), - y: tokenizer.parse(), + y: tokenizer.parse() }) } return res }, // round 5 - function (tokenizer) { - return { - pk: { - x: tokenizer.parse(), - y: tokenizer.parse() - }, - pk_t_rand_commitment: { - x: tokenizer.parse(), - y: tokenizer.parse() - }, - challenge_response: tokenizer.parse() - } - } + (tokenizer) => ({ + pk: { + x: tokenizer.parse(), + y: tokenizer.parse() + }, + pk_t_rand_commitment: { + x: tokenizer.parse(), + y: tokenizer.parse() + }, + challenge_response: tokenizer.parse() + }) ] const signDecoders = [ // round 0 - function (tokenizer) { - return tokenizer.byte() - }, + (tokenizer) => tokenizer.byte(), // round 1 - function (tokenizer) { - return [ - { - com: tokenizer.parse() - }, - { - c: tokenizer.parse(512) - } - ] - }, + (tokenizer) => [ + { + com: tokenizer.parse() + }, + { + c: tokenizer.parse(512) + } + ], // round 2 - function (tokenizer) { + (tokenizer) => { const res = [] - for (let i = 0; i < 2; i++) { + for (let i = 0; i < 2; i += 1) { res[i] = { c: tokenizer.parse(512), b_proof: { pk: { x: tokenizer.parse(), - y: tokenizer.parse(), + y: tokenizer.parse() }, pk_t_rand_commitment: { x: tokenizer.parse(), - y: tokenizer.parse(), + y: tokenizer.parse() }, - challenge_response: tokenizer.parse(), + challenge_response: tokenizer.parse() }, beta_tag_proof: { pk: { x: tokenizer.parse(), - y: tokenizer.parse(), + y: tokenizer.parse() }, pk_t_rand_commitment: { x: tokenizer.parse(), - y: tokenizer.parse(), + y: tokenizer.parse() }, - challenge_response: tokenizer.parse(), + challenge_response: tokenizer.parse() } } } return res }, // round 3 - function (tokenizer) { - return tokenizer.parse() - }, + (tokenizer) => tokenizer.parse(), // round 4 - function (tokenizer) { - return { - blind_factor: tokenizer.parse(), - g_gamma_i: { - x: tokenizer.parse(), - y: tokenizer.parse() - } + (tokenizer) => ({ + blind_factor: tokenizer.parse(), + g_gamma_i: { + x: tokenizer.parse(), + y: tokenizer.parse() } - }, + }), // round 5 - function (tokenizer) { - return { - com: tokenizer.parse() - } - }, + (tokenizer) => ({ + com: tokenizer.parse() + }), // round 6 - function (tokenizer) { - return [ - { - V_i: { - x: tokenizer.parse(), - y: tokenizer.parse() - }, - A_i: { - x: tokenizer.parse(), - y: tokenizer.parse() - }, - B_i: { - x: tokenizer.parse(), - y: tokenizer.parse() - }, - blind_factor: tokenizer.parse() - }, - { - T: { - x: tokenizer.parse(), - y: tokenizer.parse() - }, - A3: { - x: tokenizer.parse(), - y: tokenizer.parse() - }, - z1: tokenizer.parse(), - z2: tokenizer.parse() - } - ] - }, - // round 7 - function (tokenizer) { - return { - com: tokenizer.parse() - } - }, - // round 8 - function (tokenizer) { - return { - u_i: { + (tokenizer) => [ + { + V_i: { x: tokenizer.parse(), y: tokenizer.parse() }, - t_i: { + A_i: { + x: tokenizer.parse(), + y: tokenizer.parse() + }, + B_i: { x: tokenizer.parse(), y: tokenizer.parse() }, blind_factor: tokenizer.parse() + }, + { + T: { + x: tokenizer.parse(), + y: tokenizer.parse() + }, + A3: { + x: tokenizer.parse(), + y: tokenizer.parse() + }, + z1: tokenizer.parse(), + z2: tokenizer.parse() } - }, + ], + // round 7 + (tokenizer) => ({ + com: tokenizer.parse() + }), + // round 8 + (tokenizer) => ({ + u_i: { + x: tokenizer.parse(), + y: tokenizer.parse() + }, + t_i: { + x: tokenizer.parse(), + y: tokenizer.parse() + }, + blind_factor: tokenizer.parse() + }), // round 9 - function (tokenizer) { - return tokenizer.parse() - }, + (tokenizer) => tokenizer.parse() ] -module.exports = function (isKeygen, round, value) { - value = Buffer.from(value.substr(2), 'hex') - const tokenizer = Tokenizer(value) - const roundNumber = parseInt(round[round.length - 1]) +function decode(isKeygen, round, value) { + const newValue = Buffer.from(value.substr(2), 'hex') + const tokenizer = Tokenizer(newValue) + const roundNumber = parseInt(round[round.length - 1], 10) const decoder = (isKeygen ? keygenDecoders : signDecoders)[roundNumber] return JSON.stringify(decoder(tokenizer)) } + +module.exports = decode diff --git a/src/oracle/proxy/encode.js b/src/oracle/proxy/encode.js index 1178306..4a18410 100644 --- a/src/oracle/proxy/encode.js +++ b/src/oracle/proxy/encode.js @@ -2,43 +2,44 @@ const BN = require('bignumber.js') const { padZeros } = require('./crypto') -function makeBuffer (value, length = 32, base = 16) { +function makeBuffer(value, length = 32, base = 16) { return Buffer.from(padZeros(new BN(value, base).toString(16), length * 2), 'hex') } const keygenEncoders = [ null, // round 1 - function * (value) { + function* g(value) { yield makeBuffer(value.e.n, 256, 10) yield makeBuffer(value.com) - for (let x of value.correct_key_proof.sigma_vec) { - yield makeBuffer(x, 256, 10) + for (let i = 0; i < value.correct_key_proof.sigma_vec.length; i += 1) { + yield makeBuffer(value.correct_key_proof.sigma_vecp[i], 256, 10) } }, // round 2 - function * (value) { + function* g(value) { yield makeBuffer(value.blind_factor) yield makeBuffer(value.y_i.x) yield makeBuffer(value.y_i.y) }, // round 3 - function * (value) { - yield Buffer.from([ value.ciphertext.length ]) + function* g(value) { + yield Buffer.from([value.ciphertext.length]) yield Buffer.from(value.ciphertext) // 32 bytes or less yield Buffer.from(value.tag) // 16 bytes or less }, // round 4 - function * (value) { - yield Buffer.from([ value.parameters.threshold ]) // 1 byte - yield Buffer.from([ value.parameters.share_count ]) // 1 byte - for (let x of value.commitments) { + function* g(value) { + yield Buffer.from([value.parameters.threshold]) // 1 byte + yield Buffer.from([value.parameters.share_count]) // 1 byte + for (let i = 0; i < value.commitments.length; i += 1) { + const x = value.correct_key_proof.sigma_vec[i] yield makeBuffer(x.x) yield makeBuffer(x.y) } }, // round 5 - function * (value) { + function* g(value) { yield makeBuffer(value.pk.x) yield makeBuffer(value.pk.y) yield makeBuffer(value.pk_t_rand_commitment.x) @@ -49,17 +50,17 @@ const keygenEncoders = [ const signEncoders = [ // round 0 - function * (value) { - yield Buffer.from([ value ]) + function* g(value) { + yield Buffer.from([value]) }, // round 1 - function * (value) { + function* g(value) { yield makeBuffer(value[0].com) yield makeBuffer(value[1].c, 512) }, // round 2 - function * (value) { - for (let i = 0; i < 2; i++) { + function* g(value) { + for (let i = 0; i < 2; i += 1) { yield makeBuffer(value[i].c, 512) yield makeBuffer(value[i].b_proof.pk.x) yield makeBuffer(value[i].b_proof.pk.y) @@ -74,21 +75,21 @@ const signEncoders = [ } }, // round 3 - function * (value) { + function* g(value) { yield makeBuffer(value) }, // round 4 - function * (value) { + function* g(value) { yield makeBuffer(value.blind_factor) yield makeBuffer(value.g_gamma_i.x) yield makeBuffer(value.g_gamma_i.y) }, // round 5 - function * (value) { + function* g(value) { yield makeBuffer(value.com) }, // round 6 - function * (value) { + function* g(value) { yield makeBuffer(value[0].V_i.x) yield makeBuffer(value[0].V_i.y) yield makeBuffer(value[0].A_i.x) @@ -104,11 +105,11 @@ const signEncoders = [ yield makeBuffer(value[1].z2) }, // round 7 - function * (value) { + function* g(value) { yield makeBuffer(value.com) }, // round 8 - function * (value) { + function* g(value) { yield makeBuffer(value.u_i.x) yield makeBuffer(value.u_i.y) yield makeBuffer(value.t_i.x) @@ -116,23 +117,23 @@ const signEncoders = [ yield makeBuffer(value.blind_factor) }, // round 9 - function * (value) { + function* g(value) { yield makeBuffer(value) - }, + } ] -module.exports = function (isKeygen, round, value) { +function encode(isKeygen, round, value) { const parsedValue = JSON.parse(value) - const roundNumber = parseInt(round[round.length - 1]) + const roundNumber = parseInt(round[round.length - 1], 10) const encoder = (isKeygen ? keygenEncoders : signEncoders)[roundNumber] const generator = encoder(parsedValue) const buffers = [] - let next - while (true) { - next = generator.next() - if (next.done) - break + let next = generator.next() + while (!next.done) { buffers.push(next.value) + next = generator.next() } return Buffer.concat(buffers) } + +module.exports = encode diff --git a/src/oracle/proxy/index.js b/src/oracle/proxy/index.js index 99122a7..ef7e76f 100644 --- a/src/oracle/proxy/index.js +++ b/src/oracle/proxy/index.js @@ -12,8 +12,8 @@ const logger = require('./logger') const { publicKeyToAddress } = require('./crypto') const { - HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID, - SIDE_CHAIN_ID, HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET + HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, + HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET } = process.env const abiSharedDb = require('./contracts_data/SharedDB.json').abi const abiBridge = require('./contracts_data/Bridge.json').abi @@ -39,28 +39,9 @@ const app = express() app.use(express.json()) app.use(express.urlencoded({ extended: true })) -app.post('/get', get) -app.post('/set', set) -app.post('/signupkeygen', signupKeygen) -app.post('/signupsign', signupSign) - -app.post('/confirmKeygen', confirmKeygen) -app.post('/confirmFundsTransfer', confirmFundsTransfer) -app.post('/transfer', transfer) - const votesProxyApp = express() -votesProxyApp.use(express.json()) -votesProxyApp.use(express.urlencoded({ extended: true })) -votesProxyApp.get('/vote/startVoting', voteStartVoting) -votesProxyApp.get('/vote/startKeygen', voteStartKeygen) -votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen) -votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator) -votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator) -votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold) -votesProxyApp.get('/info', info) - -async function main () { +async function main() { homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress) sideValidatorNonce = await sideWeb3.eth.getTransactionCount(validatorAddress) @@ -80,35 +61,77 @@ async function main () { main() -function Ok (data) { +function Ok(data) { return { Ok: data } } -function Err (data) { +function Err(data) { return { Err: data } } -async function get (req, res) { +function sideSendQuery(query) { + return lock.acquire('home', async () => { + logger.debug('Sending side query') + const encodedABI = query.encodeABI() + const senderResponse = await sideSender({ + data: encodedABI, + to: SIDE_SHARED_DB_ADDRESS, + nonce: sideValidatorNonce + }) + if (senderResponse !== true) { + sideValidatorNonce += 1 + } + return senderResponse + }) +} + +function homeSendQuery(query) { + return lock.acquire('home', async () => { + logger.debug('Sending home query') + const encodedABI = query.encodeABI() + const senderResponse = await homeSender({ + data: encodedABI, + to: HOME_BRIDGE_ADDRESS, + nonce: homeValidatorNonce + }) + if (senderResponse !== true) { + homeValidatorNonce += 1 + } + return senderResponse + }) +} + +async function get(req, res) { logger.debug('Get call, %o', req.body.key) const round = req.body.key.second const uuid = req.body.key.third let from - if (uuid.startsWith('k')) - from = (await bridge.methods.getNextValidators().call())[parseInt(req.body.key.first) - 1] - else { - const validators = await bridge.methods.getValidators().call() - from = await sharedDb.methods.getSignupAddress(uuid, validators, parseInt(req.body.key.first)).call() + if (uuid.startsWith('k')) { + from = (await bridge.methods.getNextValidators() + .call())[parseInt(req.body.key.first, 10) - 1] + } else { + const validators = await bridge.methods.getValidators() + .call() + from = await sharedDb.methods.getSignupAddress( + uuid, + validators, parseInt(req.body.key.first, 10) + ) + .call() } const to = Number(req.body.key.fourth) // 0 if empty const key = homeWeb3.utils.sha3(`${round}_${to}`) - const data = await sharedDb.methods.getData(from, sideWeb3.utils.sha3(uuid), key).call() + const data = await sharedDb.methods.getData(from, sideWeb3.utils.sha3(uuid), key) + .call() if (data.length > 2) { logger.trace(`Received encoded data: ${data}`) const decoded = decode(uuid[0] === 'k', round, data) logger.trace('Decoded data: %o', decoded) - res.send(Ok({ key: req.body.key, value: decoded })) + res.send(Ok({ + key: req.body.key, + value: decoded + })) } else { setTimeout(() => res.send(Err(null)), 1000) } @@ -116,7 +139,7 @@ async function get (req, res) { logger.debug('Get end') } -async function set (req, res) { +async function set(req, res) { logger.debug('Set call') const round = req.body.key.second const uuid = req.body.key.third @@ -134,21 +157,26 @@ async function set (req, res) { logger.debug('Set end') } -async function signupKeygen (req, res) { +async function signupKeygen(req, res) { logger.debug('SignupKeygen call') - const epoch = (await bridge.methods.nextEpoch().call()).toNumber() - const partyId = (await bridge.methods.getNextPartyId(validatorAddress).call()).toNumber() + const epoch = (await bridge.methods.nextEpoch() + .call()).toNumber() + const partyId = (await bridge.methods.getNextPartyId(validatorAddress) + .call()).toNumber() if (partyId === 0) { res.send(Err({ message: 'Not a validator' })) logger.debug('Not a validator') } else { - res.send(Ok({ uuid: `k${epoch}`, number: partyId })) + res.send(Ok({ + uuid: `k${epoch}`, + number: partyId + })) logger.debug('SignupKeygen end') } } -async function signupSign (req, res) { +async function signupSign(req, res) { logger.debug('SignupSign call') const hash = sideWeb3.utils.sha3(`0x${req.body.third}`) const query = sharedDb.methods.signupSign(hash) @@ -157,19 +185,27 @@ async function signupSign (req, res) { // Already have signup if (receipt.status === false) { - res.send(Ok({ uuid: hash, number: 0 })) + res.send(Ok({ + uuid: hash, + number: 0 + })) logger.debug('Already have signup') return } - const validators = await bridge.methods.getValidators().call() - const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress).call()).toNumber() + const validators = await bridge.methods.getValidators() + .call() + const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress) + .call()).toNumber() - res.send(Ok({ uuid: hash, number: id })) + res.send(Ok({ + uuid: hash, + number: id + })) logger.debug('SignupSign end') } -async function confirmKeygen (req, res) { +async function confirmKeygen(req, res) { logger.debug('Confirm keygen call') const { x, y } = req.body[5] const query = bridge.methods.confirmKeygen(`0x${x}`, `0x${y}`) @@ -178,7 +214,7 @@ async function confirmKeygen (req, res) { logger.debug('Confirm keygen end') } -async function confirmFundsTransfer (req, res) { +async function confirmFundsTransfer(req, res) { logger.debug('Confirm funds transfer call') const query = bridge.methods.confirmFundsTransfer() await homeSendQuery(query) @@ -186,49 +222,7 @@ async function confirmFundsTransfer (req, res) { logger.debug('Confirm funds transfer end') } -function sideSendQuery (query) { - return lock.acquire('home', async () => { - logger.debug('Sending side query') - const encodedABI = query.encodeABI() - const senderResponse = await sideSender({ - data: encodedABI, - to: SIDE_SHARED_DB_ADDRESS, - nonce: sideValidatorNonce - }) - if (senderResponse !== true) { - sideValidatorNonce++ - } - return senderResponse - }) -} - -function homeSendQuery (query) { - return lock.acquire('home', async () => { - logger.debug('Sending home query') - const encodedABI = query.encodeABI() - const senderResponse = await homeSender({ - data: encodedABI, - to: HOME_BRIDGE_ADDRESS, - nonce: homeValidatorNonce - }) - if (senderResponse !== true) { - homeValidatorNonce++ - } - return senderResponse - }) -} - -function parseReason (message) { - const result = /(?<="reason":").*?(?=")/.exec(message) - return result ? result[0] : '' -} - -function parseError (message) { - const result = /(?<="error":").*?(?=")/.exec(message) - return result ? result[0] : '' -} - -async function sendVote (query, req, res, waitFlag = false) { +async function sendVote(query, req, res, waitFlag = false) { try { const sentQuery = await homeSendQuery(query) let { txHash, gasLimit } = sentQuery @@ -262,43 +256,43 @@ async function sendVote (query, req, res, waitFlag = false) { } } -async function voteStartVoting (req, res) { +async function voteStartVoting(req, res) { logger.info('Voting for starting new epoch voting process') const query = bridge.methods.startVoting() sendVote(query, req, res, true) } -async function voteStartKeygen (req, res) { +async function voteStartKeygen(req, res) { logger.info('Voting for starting new epoch keygen') const query = bridge.methods.voteStartKeygen() sendVote(query, req, res) } -async function voteCancelKeygen (req, res) { +async function voteCancelKeygen(req, res) { logger.info('Voting for cancelling new epoch keygen') const query = bridge.methods.voteCancelKeygen() sendVote(query, req, res) } -async function voteAddValidator (req, res) { +async function voteAddValidator(req, res) { logger.info('Voting for adding new validator') const query = bridge.methods.voteAddValidator(req.params.validator) sendVote(query, req, res) } -async function voteChangeThreshold (req, res) { +async function voteChangeThreshold(req, res) { logger.info('Voting for changing threshold') const query = bridge.methods.voteChangeThreshold(req.params.threshold) sendVote(query, req, res) } -async function voteRemoveValidator (req, res) { +async function voteRemoveValidator(req, res) { logger.info('Voting for removing validator') const query = bridge.methods.voteRemoveValidator(req.params.validator) sendVote(query, req, res, true) } -function decodeStatus (status) { +function decodeStatus(status) { switch (status) { case 0: return 'ready' @@ -308,10 +302,12 @@ function decodeStatus (status) { return 'keygen' case 3: return 'funds_transfer' + default: + return 'unknown_state' } } -function boundX (x) { +function boundX(x) { try { return x.toNumber() } catch (e) { @@ -319,32 +315,101 @@ function boundX (x) { } } -async function info (req, res) { +function toNumber(x) { + return x.toNumber() +} + +async function transfer(req, res) { + logger.info('Transfer start') + const { hash, to, value } = req.body + if (homeWeb3.utils.isAddress(to)) { + logger.info(`Calling transfer to ${to}, ${value} tokens`) + const query = bridge.methods.transfer(hash, to, `0x${new BN(value).toString(16)}`) + await homeSendQuery(query) + } + res.send() + logger.info('Transfer end') +} + +function getForeignBalances(address) { + return httpClient + .get(`/api/v1/account/${address}`) + .then((res) => res.data.balances.reduce((prev, cur) => { + // eslint-disable-next-line no-param-reassign + prev[cur.symbol] = cur.free + return prev + }, {})) + .catch(() => ({})) +} + +async function info(req, res) { logger.debug('Info start') try { - const [ x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch, threshold, nextThreshold, validators, nextValidators, status, homeBalance ] = await Promise.all([ - bridge.methods.getX().call().then(x => new BN(x).toString(16)), - bridge.methods.getY().call().then(x => new BN(x).toString(16)), - bridge.methods.epoch().call().then(x => x.toNumber()), - bridge.methods.getRangeSize().call().then(x => x.toNumber()), - bridge.methods.getNextRangeSize().call().then(x => x.toNumber()), - bridge.methods.getStartBlock().call().then(x => x.toNumber()), - bridge.methods.getNonce().call().then(boundX), - bridge.methods.nextEpoch().call().then(x => x.toNumber()), - bridge.methods.getThreshold().call().then(x => x.toNumber()), - bridge.methods.getNextThreshold().call().then(x => x.toNumber()), - bridge.methods.getValidators().call(), - bridge.methods.getNextValidators().call(), - bridge.methods.status().call(), - token.methods.balanceOf(HOME_BRIDGE_ADDRESS).call().then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3))) + const [ + x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch, + threshold, nextThreshold, validators, nextValidators, status, homeBalance + ] = await Promise.all([ + bridge.methods.getX() + .call() + .then((value) => new BN(value).toString(16)), + bridge.methods.getY() + .call() + .then((value) => new BN(value).toString(16)), + bridge.methods.epoch() + .call() + .then(toNumber), + bridge.methods.getRangeSize() + .call() + .then(toNumber), + bridge.methods.getNextRangeSize() + .call() + .then(toNumber), + bridge.methods.getStartBlock() + .call() + .then(toNumber), + bridge.methods.getNonce() + .call() + .then(boundX), + bridge.methods.nextEpoch() + .call() + .then(toNumber), + bridge.methods.getThreshold() + .call() + .then(toNumber), + bridge.methods.getNextThreshold() + .call() + .then(toNumber), + bridge.methods.getValidators() + .call(), + bridge.methods.getNextValidators() + .call(), + bridge.methods.status() + .call(), + token.methods.balanceOf(HOME_BRIDGE_ADDRESS) + .call() + .then((value) => parseFloat(new BN(value).dividedBy(10 ** 18) + .toFixed(8, 3))) ]) - const [ confirmationsForFundsTransfer, votesForVoting, votesForKeygen, votesForCancelKeygen ] = await Promise.all([ - bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 1, nextEpoch ]))).call().then(boundX), - bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 2, nextEpoch ]))).call().then(boundX), - bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 7, nextEpoch ]))).call().then(boundX), - bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 8, nextEpoch ]))).call().then(boundX) + const [ + confirmationsForFundsTransfer, votesForVoting, votesForKeygen, votesForCancelKeygen + ] = await Promise.all([ + bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [1, nextEpoch]))) + .call() + .then(boundX), + bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [2, nextEpoch]))) + .call() + .then(boundX), + bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [7, nextEpoch]))) + .call() + .then(boundX), + bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [8, nextEpoch]))) + .call() + .then(boundX) ]) - const foreignAddress = publicKeyToAddress({ x, y }) + const foreignAddress = publicKeyToAddress({ + x, + y + }) const balances = await getForeignBalances(foreignAddress) const msg = { epoch, @@ -361,7 +426,7 @@ async function info (req, res) { nextValidators, homeBalance, foreignBalanceTokens: parseFloat(balances[FOREIGN_ASSET]) || 0, - foreignBalanceNative: parseFloat(balances['BNB']) || 0, + foreignBalanceNative: parseFloat(balances.BNB) || 0, bridgeStatus: decodeStatus(status), votesForVoting, votesForKeygen, @@ -372,29 +437,27 @@ async function info (req, res) { res.send(msg) } catch (e) { logger.debug('%o', e) - res.send({ message: 'Something went wrong, resend request', error: e }) + res.send({ + message: 'Something went wrong, resend request', + error: e + }) } logger.debug('Info end') } -async function transfer (req, res) { - logger.info('Transfer start') - const { hash, to, value } = req.body - if (homeWeb3.utils.isAddress(to)) { - logger.info(`Calling transfer to ${to}, ${value} tokens`) - const query = bridge.methods.transfer(hash, to, '0x' + (new BN(value).toString(16))) - await homeSendQuery(query) - } - res.send() - logger.info('Transfer end') -} +app.post('/get', get) +app.post('/set', set) +app.post('/signupkeygen', signupKeygen) +app.post('/signupsign', signupSign) -function getForeignBalances (address) { - return httpClient - .get(`/api/v1/account/${address}`) - .then(res => res.data.balances.reduce((prev, cur) => { - prev[cur.symbol] = cur.free - return prev - }, {})) - .catch(err => ({})) -} +app.post('/confirmKeygen', confirmKeygen) +app.post('/confirmFundsTransfer', confirmFundsTransfer) +app.post('/transfer', transfer) + +votesProxyApp.get('/vote/startVoting', voteStartVoting) +votesProxyApp.get('/vote/startKeygen', voteStartKeygen) +votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen) +votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator) +votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator) +votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold) +votesProxyApp.get('/info', info) diff --git a/src/oracle/proxy/package.json b/src/oracle/proxy/package.json index d941190..6624098 100644 --- a/src/oracle/proxy/package.json +++ b/src/oracle/proxy/package.json @@ -12,5 +12,8 @@ "ethers": "4.0.37", "pino": "5.13.4", "pino-pretty": "3.2.1" + }, + "engines": { + "node": ">=10.6.0" } } diff --git a/src/oracle/proxy/sendTx.js b/src/oracle/proxy/sendTx.js index bba562a..c18ed41 100644 --- a/src/oracle/proxy/sendTx.js +++ b/src/oracle/proxy/sendTx.js @@ -7,28 +7,28 @@ const logger = require('./logger') const { GAS_LIMIT_FACTOR, MAX_GAS_LIMIT } = process.env -function sendRpcRequest (url, method, params) { +function sendRpcRequest(url, method, params) { return axios.post(url, { jsonrpc: '2.0', method, params, id: 1 }) - .then(res => res.data) - .catch(async e => { + .then((res) => res.data) + .catch(async () => { logger.warn(`Request to ${url}, method ${method} failed, retrying`) - await new Promise(res => setTimeout(res, 1000)) + await new Promise((res) => setTimeout(res, 1000)) return sendRpcRequest(url, method, params) }) } -async function createSender (url, privateKey) { +async function createSender(url, privateKey) { const web3 = new Web3(url, null, { transactionConfirmationBlocks: 1 }) const signer = new ethers.utils.SigningKey(privateKey) const chainId = await web3.eth.net.getId() - return async function (tx) { - tx = { + return async function send(tx) { + const newTx = { data: tx.data, to: tx.to, nonce: tx.nonce, @@ -38,29 +38,30 @@ async function createSender (url, privateKey) { } try { - logger.trace(`Preparing and sending transaction %o on ${url}`, tx) - const estimate = await sendRpcRequest(url, 'eth_estimateGas', [ { + logger.trace(`Preparing and sending transaction %o on ${url}`, newTx) + const estimate = await sendRpcRequest(url, 'eth_estimateGas', [{ from: signer.address, - to: tx.to, - data: tx.data, - gasPrice: tx.gasPrice, - value: tx.value, + to: newTx.to, + data: newTx.data, + gasPrice: newTx.gasPrice, + value: newTx.value, gas: `0x${new BN(MAX_GAS_LIMIT).toString(16)}` - } ]) + }]) if (estimate.error) { logger.debug('Gas estimate failed %o, skipping tx, reverting nonce', estimate.error) return true } - const gasLimit = BN.min(new BN(estimate.result, 16).multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT) - tx.gasLimit = `0x${new BN(gasLimit).toString(16)}` + const gasLimit = BN.min(new BN(estimate.result, 16) + .multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT) + newTx.gasLimit = `0x${new BN(gasLimit).toString(16)}` logger.trace(`Estimated gas to ${gasLimit}`) const hash = web3.utils.sha3(ethers.utils.serializeTransaction(tx)) const signature = signer.signDigest(hash) const signedTx = ethers.utils.serializeTransaction(tx, signature) - const { result, error } = await sendRpcRequest(url, 'eth_sendRawTransaction', [ signedTx ]) + const { result, error } = await sendRpcRequest(url, 'eth_sendRawTransaction', [signedTx]) // handle nonce error // handle insufficient funds error if (error) { @@ -68,7 +69,10 @@ async function createSender (url, privateKey) { return false } - return { txHash: result, gasLimit: tx.gasLimit } + return { + txHash: result, + gasLimit: tx.gasLimit + } } catch (e) { logger.warn('Something failed, %o', e) return false @@ -76,16 +80,19 @@ async function createSender (url, privateKey) { } } -async function waitForReceipt (url, txHash) { +async function waitForReceipt(url, txHash) { while (true) { - const { result, error } = await sendRpcRequest(url, 'eth_getTransactionReceipt', [ txHash ]) + const { result, error } = await sendRpcRequest(url, 'eth_getTransactionReceipt', [txHash]) if (result === null || error) { - await new Promise(res => setTimeout(res, 1000)) + await new Promise((res) => setTimeout(res, 1000)) } else { return result } } } -module.exports = { createSender, waitForReceipt } +module.exports = { + createSender, + waitForReceipt +} diff --git a/src/oracle/scripts/resetToBlock/package.json b/src/oracle/scripts/resetToBlock/package.json index f4546c1..3a3d820 100644 --- a/src/oracle/scripts/resetToBlock/package.json +++ b/src/oracle/scripts/resetToBlock/package.json @@ -3,5 +3,8 @@ "version": "0.0.1", "dependencies": { "ioredis": "4.14.1" + }, + "engines": { + "node": ">=10.6.0" } } diff --git a/src/oracle/scripts/resetToBlock/resetToBlock.js b/src/oracle/scripts/resetToBlock/resetToBlock.js index bcbaddf..a7d540d 100644 --- a/src/oracle/scripts/resetToBlock/resetToBlock.js +++ b/src/oracle/scripts/resetToBlock/resetToBlock.js @@ -12,7 +12,7 @@ redis.on('error', () => { }) redis.on('connect', async () => { - await redis.set('homeBlock', parseInt(process.argv[2])) + await redis.set('homeBlock', parseInt(process.argv[2], 10)) await redis.save() redis.disconnect() }) diff --git a/src/oracle/shared/amqp.js b/src/oracle/shared/amqp.js index 8bc751e..e6a88d9 100644 --- a/src/oracle/shared/amqp.js +++ b/src/oracle/shared/amqp.js @@ -2,30 +2,30 @@ const amqp = require('amqplib') const logger = require('./logger') -function _connectRabbit (url) { - return amqp.connect(url).catch(() => { - logger.debug('Failed to connect to rabbitmqServer, reconnecting') - return new Promise(resolve => - setTimeout(() => resolve(_connectRabbit(url)), 2000) - ) - }) -} - async function connectRabbit(url) { - const connection = await _connectRabbit(url) - return await connection.createChannel() -} - -async function assertQueue (channel, name) { - const queue = await channel.assertQueue(name) - return { - name: queue.queue, - send: msg => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), { - persistent: true - }), - get: consumer => channel.get(queue.queue, consumer), - consume: consumer => channel.consume(queue.queue, consumer) + while (true) { + try { + return (await amqp.connect(url)).createChannel() + } catch (e) { + logger.debug('Failed to connect to rabbitmqServer, reconnecting') + await new Promise((resolve) => setTimeout(resolve, 2000)) + } } } -module.exports = { connectRabbit, assertQueue } +async function assertQueue(channel, name) { + const queue = await channel.assertQueue(name) + return { + name: queue.queue, + send: (msg) => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), { + persistent: true + }), + get: (consumer) => channel.get(queue.queue, consumer), + consume: (consumer) => channel.consume(queue.queue, consumer) + } +} + +module.exports = { + connectRabbit, + assertQueue +} diff --git a/src/oracle/shared/crypto.js b/src/oracle/shared/crypto.js index 16c0fd8..358e61c 100644 --- a/src/oracle/shared/crypto.js +++ b/src/oracle/shared/crypto.js @@ -1,7 +1,27 @@ const crypto = require('crypto') const bech32 = require('bech32') -function publicKeyToAddress ({ x, y }) { +function padZeros(s, len) { + while (s.length < len) { + // eslint-disable-next-line no-param-reassign + s = `0${s}` + } + return s +} + +function sha256(bytes) { + return crypto.createHash('sha256') + .update(bytes) + .digest('hex') +} + +function ripemd160(bytes) { + return crypto.createHash('ripemd160') + .update(bytes) + .digest('hex') +} + +function publicKeyToAddress({ x, y }) { const compact = (parseInt(y[y.length - 1], 16) % 2 ? '03' : '02') + padZeros(x, 64) const sha256Hash = sha256(Buffer.from(compact, 'hex')) const hash = ripemd160(Buffer.from(sha256Hash, 'hex')) @@ -9,18 +29,8 @@ function publicKeyToAddress ({ x, y }) { return bech32.encode('tbnb', words) } -function padZeros (s, len) { - while (s.length < len) - s = '0' + s - return s +module.exports = { + publicKeyToAddress, + padZeros, + sha256 } - -function sha256 (bytes) { - return crypto.createHash('sha256').update(bytes).digest('hex') -} - -function ripemd160 (bytes) { - return crypto.createHash('ripemd160').update(bytes).digest('hex') -} - -module.exports = { publicKeyToAddress, padZeros, sha256 } diff --git a/src/oracle/shared/db.js b/src/oracle/shared/db.js index a6732f7..1faf6fc 100644 --- a/src/oracle/shared/db.js +++ b/src/oracle/shared/db.js @@ -1,6 +1,7 @@ const Redis = require('ioredis') const logger = require('./logger') + logger.info('Connecting to redis') const redis = new Redis({ @@ -14,7 +15,7 @@ redis.on('connect', () => { logger.info('Connected to redis') }) -redis.on('error', e => { +redis.on('error', (e) => { logger.warn('Redis error %o', e) }) diff --git a/src/oracle/tss-keygen/keygen.js b/src/oracle/tss-keygen/keygen.js index 77dfc80..77f9a7e 100644 --- a/src/oracle/tss-keygen/keygen.js +++ b/src/oracle/tss-keygen/keygen.js @@ -9,17 +9,15 @@ const { publicKeyToAddress } = require('./crypto') const { RABBITMQ_URL, PROXY_URL } = process.env const app = express() -app.get('/start', (req, res) => { - logger.info('Ready to start') - ready = true - res.send() -}) -app.listen(8001, () => logger.debug('Listening on 8001')) let currentKeygenEpoch = null let ready = false -async function main () { +async function confirmKeygen(keysFile) { + exec.execSync(`curl -X POST -H "Content-Type: application/json" -d @"${keysFile}" "${PROXY_URL}/confirmKeygen"`, { stdio: 'pipe' }) +} + +async function main() { logger.info('Connecting to RabbitMQ server') const channel = await connectRabbit(RABBITMQ_URL) logger.info('Connecting to epoch events queue') @@ -27,11 +25,11 @@ async function main () { const cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue') while (!ready) { - await new Promise(res => setTimeout(res, 1000)) + await new Promise((res) => setTimeout(res, 1000)) } channel.prefetch(1) - keygenQueue.consume(msg => { + keygenQueue.consume((msg) => { const { epoch, parties, threshold } = JSON.parse(msg.content) logger.info(`Consumed new epoch event, starting keygen for epoch ${epoch}`) @@ -41,8 +39,11 @@ async function main () { currentKeygenEpoch = epoch logger.debug('Writing params') - fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() })) - const cmd = exec.execFile('./keygen-entrypoint.sh', [ PROXY_URL, keysFile ], async () => { + fs.writeFileSync('./params', JSON.stringify({ + parties: parties.toString(), + threshold: threshold.toString() + })) + const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => { currentKeygenEpoch = null if (fs.existsSync(keysFile)) { logger.info(`Finished keygen for epoch ${epoch}`) @@ -57,11 +58,11 @@ async function main () { logger.debug('Ack for keygen message') channel.ack(msg) }) - cmd.stdout.on('data', data => logger.debug(data.toString())) - cmd.stderr.on('data', data => logger.debug(data.toString())) + cmd.stdout.on('data', (data) => logger.debug(data.toString())) + cmd.stderr.on('data', (data) => logger.debug(data.toString())) }) - cancelKeygenQueue.consume(async msg => { + cancelKeygenQueue.consume(async (msg) => { const { epoch } = JSON.parse(msg.content) logger.info(`Consumed new cancel event for epoch ${epoch} keygen`) if (currentKeygenEpoch === epoch) { @@ -72,8 +73,12 @@ async function main () { }) } -main() -async function confirmKeygen (keysFile) { - exec.execSync(`curl -X POST -H "Content-Type: application/json" -d @"${keysFile}" "${PROXY_URL}/confirmKeygen"`, { stdio: 'pipe' }) -} +app.get('/start', (req, res) => { + logger.info('Ready to start') + ready = true + res.send() +}) +app.listen(8001, () => logger.debug('Listening on 8001')) + +main() diff --git a/src/oracle/tss-keygen/package.json b/src/oracle/tss-keygen/package.json index 64c6c24..ba4680e 100644 --- a/src/oracle/tss-keygen/package.json +++ b/src/oracle/tss-keygen/package.json @@ -7,5 +7,8 @@ "pino": "5.13.4", "pino-pretty": "3.2.1", "express": "4.17.1" + }, + "engines": { + "node": ">=10.6.0" } } diff --git a/src/oracle/tss-sign/package.json b/src/oracle/tss-sign/package.json index 141a555..7d0c822 100644 --- a/src/oracle/tss-sign/package.json +++ b/src/oracle/tss-sign/package.json @@ -9,5 +9,8 @@ "express": "4.17.1", "pino": "5.13.4", "pino-pretty": "3.2.1" + }, + "engines": { + "node": ">=10.6.0" } } diff --git a/src/oracle/tss-sign/signer.js b/src/oracle/tss-sign/signer.js index 5077e6a..2ffe5cb 100644 --- a/src/oracle/tss-sign/signer.js +++ b/src/oracle/tss-sign/signer.js @@ -1,24 +1,20 @@ const exec = require('child_process') const fs = require('fs') const BN = require('bignumber.js') +const axios = require('axios') const express = require('express') const logger = require('./logger') const { connectRabbit, assertQueue } = require('./amqp') const { publicKeyToAddress, sha256 } = require('./crypto') -const app = express() -app.get('/restart/:attempt', restart) -app.get('/start', (req, res) => { - logger.info('Ready to start') - ready = true - res.send() -}) -app.listen(8001, () => logger.debug('Listening on 8001')) - -const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env const Transaction = require('./tx') -const axios = require('axios') + +const app = express() + +const { + RABBITMQ_URL, FOREIGN_URL, PROXY_URL, FOREIGN_ASSET +} = process.env const httpClient = axios.create({ baseURL: FOREIGN_URL }) @@ -29,7 +25,126 @@ let ready = false let exchangeQueue let channel -async function main () { +async function getExchangeMessages(nonce) { + logger.debug('Getting exchange messages') + const messages = [] + while (true) { + const msg = await exchangeQueue.get() + if (msg === false) { + break + } + const data = JSON.parse(msg.content) + logger.debug('Got message %o', data) + if (data.nonce !== nonce) { + channel.nack(msg, false, true) + break + } + messages.push(msg) + } + logger.debug(`Found ${messages.length} messages`) + return messages +} + +function restart(req, res) { + logger.info('Cancelling current sign') + nextAttempt = req.params.attempt + exec.execSync('pkill gg18_sign || true') + cancelled = true + res.send('Cancelled') +} + +function confirmFundsTransfer() { + exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' }) +} + +function getAccountFromFile(file) { + logger.debug(`Reading ${file}`) + if (!fs.existsSync(file)) { + logger.debug('No keys found, skipping') + return { address: '' } + } + const publicKey = JSON.parse(fs.readFileSync(file))[5] + return { + address: publicKeyToAddress(publicKey), + publicKey + } +} + +function getAccount(address) { + logger.info(`Getting account ${address} data`) + return httpClient + .get(`/api/v1/account/${address}`) + .then((res) => res.data) + .catch(() => { + logger.debug('Retrying') + return getAccount(address) + }) +} + +async function waitForAccountNonce(address, nonce) { + cancelled = false + logger.info(`Waiting for account ${address} to have nonce ${nonce}`) + while (!cancelled) { + const { sequence } = await getAccount(address) + if (sequence >= nonce) { + break + } + await new Promise((resolve) => setTimeout(resolve, 1000)) + logger.debug('Waiting for needed account nonce') + } + logger.info('Account nonce is OK') + return !cancelled +} + + +function sendTx(tx) { + return httpClient + .post('/api/v1/broadcast?sync=true', tx, { + headers: { + 'Content-Type': 'text/plain' + } + }) + .catch((err) => { + if (err.response.data.message.includes('Tx already exists in cache')) { + logger.debug('Tx already exists in cache') + return true + } + logger.info('Something failed, restarting: %o', err.response) + return new Promise((resolve) => setTimeout(() => resolve(sendTx(tx)), 1000)) + }) +} + +function sign(keysFile, hash, tx, publicKey) { + return new Promise((resolve) => { + const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async (error) => { + if (fs.existsSync('signature')) { + logger.info('Finished signature generation') + const signature = JSON.parse(fs.readFileSync('signature')) + logger.debug('%o', signature) + + logger.info('Building signed transaction') + const signedTx = tx.addSignature(publicKey, { + r: signature[1], + s: signature[3] + }) + + logger.info('Sending transaction') + logger.debug(signedTx) + await sendTx(signedTx) + resolve(true) + } else if (error === null || error.code === 0) { + resolve(true) + } else { + logger.warn('Sign failed') + resolve(false) + } + }) + cmd.stdout.on('data', (data) => logger.debug(data.toString())) + cmd.stderr.on('data', (data) => logger.debug(data.toString())) + }) +} + +async function main() { logger.info('Connecting to RabbitMQ server') channel = await connectRabbit(RABBITMQ_URL) logger.info('Connecting to signature events queue') @@ -37,15 +152,17 @@ async function main () { const signQueue = await assertQueue(channel, 'signQueue') while (!ready) { - await new Promise(res => setTimeout(res, 1000)) + await new Promise((res) => setTimeout(res, 1000)) } channel.prefetch(1) - signQueue.consume(async msg => { + signQueue.consume(async (msg) => { const data = JSON.parse(msg.content) logger.info('Consumed sign event: %o', data) - const { nonce, epoch, newEpoch, parties, threshold } = data + const { + nonce, epoch, newEpoch, parties, threshold + } = data const keysFile = `/keys/keys${epoch}.store` const { address: from, publicKey } = getAccountFromFile(keysFile) @@ -57,16 +174,22 @@ async function main () { const account = await getAccount(from) logger.debug('Writing params') - fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() })) + fs.writeFileSync('./params', JSON.stringify({ + parties: parties.toString(), + threshold: threshold.toString() + })) attempt = 1 if (!newEpoch) { const exchanges = await getExchangeMessages(nonce) - const exchangesData = exchanges.map(msg => JSON.parse(msg.content)) + const exchangesData = exchanges.map((exchangeMsg) => JSON.parse(exchangeMsg.content)) if (exchanges.length > 0 && account.sequence <= nonce) { - const recipients = exchangesData.map(({ value, recipient }) => ({ to: recipient, tokens: value })) + const recipients = exchangesData.map(({ value, recipient }) => ({ + to: recipient, + tokens: value + })) while (true) { logger.info(`Building corresponding transfer transaction, nonce ${nonce}`) @@ -82,16 +205,18 @@ async function main () { const hash = sha256(tx.getSignBytes()) logger.info(`Starting signature generation for transaction hash ${hash}`) - const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1) + const done = await sign(keysFile, hash, tx, publicKey) + && await waitForAccountNonce(from, nonce + 1) if (done) { - exchanges.forEach(msg => channel.ack(msg)) + // eslint-disable-next-line no-loop-func + exchanges.forEach((exchangeMsg) => channel.ack(exchangeMsg)) break } - attempt = nextAttempt ? nextAttempt : attempt + 1 + attempt = nextAttempt || attempt + 1 logger.warn(`Sign failed, starting next attempt ${attempt}`) nextAttempt = null - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)) } } } else if (account.sequence <= nonce) { @@ -104,27 +229,28 @@ async function main () { from, accountNumber: account.account_number, sequence: nonce, - recipients: [ { + recipients: [{ to, - tokens: account.balances.find(x => x.symbol === FOREIGN_ASSET).free, - bnbs: new BN(account.balances.find(x => x.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8)), - } ], + tokens: account.balances.find((token) => token.symbol === FOREIGN_ASSET).free, + bnbs: new BN(account.balances.find((token) => token.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8)) + }], asset: FOREIGN_ASSET, memo: `Attempt ${attempt}` }) const hash = sha256(tx.getSignBytes()) logger.info(`Starting signature generation for transaction hash ${hash}`) - const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1) + const done = await sign(keysFile, hash, tx, publicKey) + && await waitForAccountNonce(from, nonce + 1) if (done) { await confirmFundsTransfer() break } - attempt = nextAttempt ? nextAttempt : attempt + 1 + attempt = nextAttempt || attempt + 1 logger.warn(`Sign failed, starting next attempt ${attempt}`) nextAttempt = null - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)) } } else { logger.debug('Tx has been already sent') @@ -134,118 +260,12 @@ async function main () { }) } +app.get('/restart/:attempt', restart) +app.get('/start', (req, res) => { + logger.info('Ready to start') + ready = true + res.send() +}) +app.listen(8001, () => logger.debug('Listening on 8001')) + main() - -async function getExchangeMessages (nonce) { - logger.debug('Getting exchange messages') - const messages = [] - do { - const msg = await exchangeQueue.get() - if (msg === false) { - break - } - const data = JSON.parse(msg.content) - logger.debug('Got message %o', data) - if (data.nonce !== nonce) { - channel.nack(msg, false, true) - break - } - messages.push(msg) - } while (true) - logger.debug(`Found ${messages.length} messages`) - return messages -} - -function sign (keysFile, hash, tx, publicKey) { - return new Promise(resolve => { - const cmd = exec.execFile('./sign-entrypoint.sh', [ PROXY_URL, keysFile, hash ], async (error) => { - if (fs.existsSync('signature')) { - logger.info('Finished signature generation') - const signature = JSON.parse(fs.readFileSync('signature')) - logger.debug('%o', signature) - - logger.info('Building signed transaction') - const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] }) - - logger.info('Sending transaction') - logger.debug(signedTx) - await sendTx(signedTx) - resolve(true) - } else if (error === null || error.code === 0) { - resolve(true) - } else { - logger.warn('Sign failed') - resolve(false) - } - }) - cmd.stdout.on('data', data => logger.debug(data.toString())) - cmd.stderr.on('data', data => logger.debug(data.toString())) - }) -} - -function restart (req, res) { - logger.info('Cancelling current sign') - nextAttempt = req.params.attempt - exec.execSync('pkill gg18_sign || true') - cancelled = true - res.send('Cancelled') -} - -function confirmFundsTransfer () { - exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' }) -} - -function getAccountFromFile (file) { - logger.debug(`Reading ${file}`) - if (!fs.existsSync(file)) { - logger.debug('No keys found, skipping') - return { address: '' } - } - const publicKey = JSON.parse(fs.readFileSync(file))[5] - return { - address: publicKeyToAddress(publicKey), - publicKey: publicKey - } -} - -async function waitForAccountNonce (address, nonce) { - cancelled = false - logger.info(`Waiting for account ${address} to have nonce ${nonce}`) - while (!cancelled) { - const sequence = (await getAccount(address)).sequence - if (sequence >= nonce) - break - await new Promise(resolve => setTimeout(resolve, 1000)) - logger.debug('Waiting for needed account nonce') - } - logger.info('Account nonce is OK') - return !cancelled -} - -function getAccount (address) { - logger.info(`Getting account ${address} data`) - return httpClient - .get(`/api/v1/account/${address}`) - .then(res => res.data) - .catch(() => { - logger.debug('Retrying') - return getAccount(address) - }) -} - -function sendTx (tx) { - return httpClient - .post(`/api/v1/broadcast?sync=true`, tx, { - headers: { - 'Content-Type': 'text/plain' - } - }) - .catch(err => { - if (err.response.data.message.includes('Tx already exists in cache')) - logger.debug('Tx already exists in cache') - else { - logger.info('Something failed, restarting: %o', err.response) - return new Promise(resolve => setTimeout(() => resolve(sendTx(tx)), 1000)) - } - }) -} diff --git a/src/oracle/tss-sign/tx.js b/src/oracle/tss-sign/tx.js index 351e3a6..fa3ec96 100644 --- a/src/oracle/tss-sign/tx.js +++ b/src/oracle/tss-sign/tx.js @@ -10,42 +10,52 @@ const { FOREIGN_CHAIN_ID } = process.env const BNB_ASSET = 'BNB' class Transaction { - constructor (options) { - const { from, accountNumber, sequence, recipients, asset, memo = '' } = options + constructor(options) { + const { + from, accountNumber, sequence, recipients, asset, memo = '' + } = options - const totalTokens = recipients.reduce((sum, { tokens }) => sum.plus(new BN(tokens || 0)), new BN(0)) - const totalBnbs = recipients.reduce((sum, { bnbs }) => sum.plus(new BN(bnbs || 0)), new BN(0)) + const totalTokens = recipients.reduce( + (sum, { tokens }) => sum.plus(new BN(tokens || 0)), new BN(0) + ) + const totalBnbs = recipients.reduce( + (sum, { bnbs }) => sum.plus(new BN(bnbs || 0)), new BN(0) + ) const senderCoins = [] if (asset && totalTokens.isGreaterThan(0)) { senderCoins.push({ denom: asset, - amount: totalTokens.multipliedBy(10 ** 8).toNumber(), + amount: totalTokens.multipliedBy(10 ** 8) + .toNumber() }) } if (totalBnbs.isGreaterThan(0)) { senderCoins.push({ denom: BNB_ASSET, - amount: totalBnbs.multipliedBy(10 ** 8).toNumber(), + amount: totalBnbs.multipliedBy(10 ** 8) + .toNumber() }) } senderCoins.sort((a, b) => a.denom > b.denom) - const inputs = [ { + const inputs = [{ address: from, coins: senderCoins - } ] + }] const outputs = recipients.map(({ to, tokens, bnbs }) => { const receiverCoins = [] if (asset && tokens) { receiverCoins.push({ denom: asset, - amount: new BN(tokens).multipliedBy(10 ** 8).toNumber(), + amount: new BN(tokens).multipliedBy(10 ** 8) + .toNumber() }) } if (bnbs) { receiverCoins.push({ denom: BNB_ASSET, - amount: new BN(bnbs).multipliedBy(10 ** 8).toNumber(), + amount: new BN(bnbs).multipliedBy(10 ** 8) + .toNumber() }) } receiverCoins.sort((a, b) => a.denom > b.denom) @@ -56,8 +66,14 @@ class Transaction { }) const msg = { - inputs: inputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})), - outputs: outputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})), + inputs: inputs.map((x) => ({ + ...x, + address: crypto.decodeAddress(x.address) + })), + outputs: outputs.map((x) => ({ + ...x, + address: crypto.decodeAddress(x.address) + })), msgType: 'MsgSend' } @@ -72,29 +88,31 @@ class Transaction { memo, msg, sequence, - type: msg.msgType, + type: msg.msgType }) } - getSignBytes () { + getSignBytes() { return this.tx.getSignBytes(this.signMsg) } - addSignature (publicKey, signature) { + addSignature(publicKey, signature) { const yLast = parseInt(publicKey.y[publicKey.y.length - 1], 16) const n = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16) const s = new BN(signature.s, 16) if (s.gt(n.div(2))) { logger.debug('Normalizing s') - signature.s = n.minus(s).toString(16) + // eslint-disable-next-line no-param-reassign + signature.s = n.minus(s) + .toString(16) } - const publicKeyEncoded = Buffer.from('eb5ae98721' + (yLast % 2 ? '03' : '02') + padZeros(publicKey.x, 64), 'hex') - this.tx.signatures = [ { + const publicKeyEncoded = Buffer.from(`eb5ae98721${yLast % 2 ? '03' : '02'}${padZeros(publicKey.x, 64)}`, 'hex') + this.tx.signatures = [{ pub_key: publicKeyEncoded, signature: Buffer.from(padZeros(signature.r, 64) + padZeros(signature.s, 64), 'hex'), account_number: this.tx.account_number, - sequence: this.tx.sequence, - } ] + sequence: this.tx.sequence + }] return this.tx.serialize() } } diff --git a/src/test-services/binanceBalance/testGetBinanceBalance.js b/src/test-services/binanceBalance/testGetBinanceBalance.js index 21d6e8d..6d356b0 100644 --- a/src/test-services/binanceBalance/testGetBinanceBalance.js +++ b/src/test-services/binanceBalance/testGetBinanceBalance.js @@ -5,10 +5,14 @@ const { FOREIGN_URL, FOREIGN_ASSET } = process.env const address = process.argv[2] const httpClient = axios.create({ baseURL: FOREIGN_URL }) -httpClient - .get(`/api/v1/account/${address}`) - .then(res => { - console.log(`BNB: ${parseFloat(res.data.balances.find(x => x.symbol === 'BNB').free)}`) - console.log(`${FOREIGN_ASSET}: ${parseFloat(res.data.balances.find(x => x.symbol === FOREIGN_ASSET).free)}`) - }) - .catch(console.log) +function main() { + httpClient + .get(`/api/v1/account/${address}`) + .then((res) => { + console.log(`BNB: ${parseFloat(res.data.balances.find((token) => token.symbol === 'BNB').free)}`) + console.log(`${FOREIGN_ASSET}: ${parseFloat(res.data.balances.find((token) => token.symbol === FOREIGN_ASSET).free)}`) + }) + .catch(console.log) +} + +main() diff --git a/src/test-services/binanceSend/testBinanceSend.js b/src/test-services/binanceSend/testBinanceSend.js index 46678d7..f0aede3 100644 --- a/src/test-services/binanceSend/testBinanceSend.js +++ b/src/test-services/binanceSend/testBinanceSend.js @@ -6,7 +6,7 @@ const PRIVATE_KEY = process.env.PRIVATE_KEY || FOREIGN_PRIVATE_KEY const client = new Bnc(FOREIGN_URL) -async function main () { +async function main() { client.chooseNetwork('testnet') await client.setPrivateKey(PRIVATE_KEY) @@ -43,10 +43,11 @@ async function main () { receipt = await client.transfer(from, to, tokens, FOREIGN_ASSET, 'exchange') } - if (receipt.status === 200) + if (receipt.status === 200) { console.log(receipt.result[0].hash) - else + } else { console.log(receipt) + } } main() diff --git a/src/test-services/ethereumBalance/testGetEthereumBalance.js b/src/test-services/ethereumBalance/testGetEthereumBalance.js index 3d0e9ea..8b9ac8a 100644 --- a/src/test-services/ethereumBalance/testGetEthereumBalance.js +++ b/src/test-services/ethereumBalance/testGetEthereumBalance.js @@ -8,11 +8,18 @@ const abiToken = require('./IERC20').abi const web3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 }) const token = new web3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS) -const address = process.argv[2] +function main() { + const address = process.argv[2] -web3.eth.getBalance(address).then(x => console.log(`${x.toString()} wei`)) + web3.eth.getBalance(address) + .then((balance) => console.log(`${balance.toString()} wei`)) -token.methods.balanceOf(address).call() - .then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3))) - .then(x => console.log(`${x.toString()} tokens`)) - .catch(() => console.log('0 tokens')) + token.methods.balanceOf(address) + .call() + .then((balance) => parseFloat(new BN(balance).dividedBy(10 ** 18) + .toFixed(8, 3))) + .then((balance) => console.log(`${balance.toString()} tokens`)) + .catch(() => console.log('0 tokens')) +} + +main() diff --git a/src/test-services/ethereumSend/testEthereumSend.js b/src/test-services/ethereumSend/testEthereumSend.js index 345658e..03ee123 100644 --- a/src/test-services/ethereumSend/testEthereumSend.js +++ b/src/test-services/ethereumSend/testEthereumSend.js @@ -1,7 +1,9 @@ const Web3 = require('web3') const BN = require('bignumber.js') -const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_PRIVATE_KEY, HOME_TOKEN_ADDRESS } = process.env +const { + HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_PRIVATE_KEY, HOME_TOKEN_ADDRESS +} = process.env const abiToken = require('./IERC20').abi const abiBridge = require('./Bridge').abi @@ -14,21 +16,22 @@ const bridge = new web3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS) const sender = web3.eth.accounts.privateKeyToAccount(`0x${PRIVATE_KEY}`).address -async function main () { +async function main() { const HOME_CHAIN_ID = await web3.eth.net.getId() - const blockGasLimit = (await web3.eth.getBlock("latest", false)).gasLimit + const blockGasLimit = (await web3.eth.getBlock('latest', false)).gasLimit const to = process.argv[2] - const amount = parseInt(process.argv[3]) + const amount = parseInt(process.argv[3], 10) let coins = process.argv[4] const txCount = await web3.eth.getTransactionCount(sender) - if (to === "bridge" && amount !== 0) { + if (to === 'bridge' && amount !== 0) { console.log(`Transfer from ${sender} to ${HOME_BRIDGE_ADDRESS}, ${amount} tokens`) - const queryApprove = token.methods.approve(HOME_BRIDGE_ADDRESS, '0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16))) + const queryApprove = token.methods.approve(HOME_BRIDGE_ADDRESS, `0x${new BN(amount).multipliedBy(10 ** 18) + .toString(16)}`) const txApprove = { data: queryApprove.encodeABI(), from: sender, @@ -42,9 +45,10 @@ async function main () { const signedTxApprove = await web3.eth.accounts.signTransaction(txApprove, PRIVATE_KEY) const receiptApprove = await web3.eth.sendSignedTransaction(signedTxApprove.rawTransaction) - console.log('txHash approve: ' + receiptApprove.transactionHash) + console.log(`txHash approve: ${receiptApprove.transactionHash}`) - const queryExchange = bridge.methods.exchange('0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16))) + const queryExchange = bridge.methods.exchange(`0x${new BN(amount).multipliedBy(10 ** 18) + .toString(16)}`) const txExchange = { data: queryExchange.encodeABI(), from: sender, @@ -58,11 +62,12 @@ async function main () { const signedTxExchange = await web3.eth.accounts.signTransaction(txExchange, PRIVATE_KEY) const receiptExchange = await web3.eth.sendSignedTransaction(signedTxExchange.rawTransaction) - console.log('txHash exchange: ' + receiptExchange.transactionHash) + console.log(`txHash exchange: ${receiptExchange.transactionHash}`) } else if (amount !== 0) { console.log(`Transfer from ${sender} to ${to}, ${amount} tokens`) - const query = token.methods.transfer(to, '0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16))) + const query = token.methods.transfer(to, `0x${new BN(amount).multipliedBy(10 ** 18) + .toString(16)}`) const tx = { data: query.encodeABI(), from: sender, @@ -75,7 +80,7 @@ async function main () { }) * 1.5), blockGasLimit) const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY) const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction) - console.log('txHash transfer: ' + receipt.transactionHash) + console.log(`txHash transfer: ${receipt.transactionHash}`) } if (coins) { @@ -85,7 +90,7 @@ async function main () { const tx = { data: '0x', from: sender, - to: to, + to, nonce: await web3.eth.getTransactionCount(sender), chainId: HOME_CHAIN_ID, value: web3.utils.toWei(new BN(coins).toString(), 'ether'), @@ -94,9 +99,8 @@ async function main () { const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY) const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction) - console.log('txHash: ' + receipt.transactionHash) + console.log(`txHash: ${receipt.transactionHash}`) } - } main() diff --git a/src/test-services/getAddresses/testGetAddresses.js b/src/test-services/getAddresses/testGetAddresses.js index e51c3c6..0c7c01a 100644 --- a/src/test-services/getAddresses/testGetAddresses.js +++ b/src/test-services/getAddresses/testGetAddresses.js @@ -2,24 +2,28 @@ const { utils } = require('ethers') const bech32 = require('bech32') const crypto = require('crypto') -const privateKey = process.argv[2].startsWith('0x') ? process.argv[2] : '0x' + process.argv[2] +function sha256(bytes) { + return crypto.createHash('sha256').update(bytes).digest('hex') +} -const ethAddress = utils.computeAddress(privateKey) -const publicKey = utils.computePublicKey(privateKey, true) +function ripemd160(bytes) { + return crypto.createHash('ripemd160').update(bytes).digest('hex') +} -console.log(`Eth address: ${ethAddress}\nBnc address: ${publicKeyToAddress(publicKey)}`) - -function publicKeyToAddress (publicKey) { +function publicKeyToAddress(publicKey) { const sha256Hash = sha256(Buffer.from(publicKey.substr(2), 'hex')) const hash = ripemd160(Buffer.from(sha256Hash, 'hex')) const words = bech32.toWords(Buffer.from(hash, 'hex')) return bech32.encode('tbnb', words) } -function sha256 (bytes) { - return crypto.createHash('sha256').update(bytes).digest('hex') +function main() { + const privateKey = process.argv[2].startsWith('0x') ? process.argv[2] : `0x${process.argv[2]}` + + const ethAddress = utils.computeAddress(privateKey) + const publicKey = utils.computePublicKey(privateKey, true) + + console.log(`Eth address: ${ethAddress}\nBnc address: ${publicKeyToAddress(publicKey)}`) } -function ripemd160 (bytes) { - return crypto.createHash('ripemd160').update(bytes).digest('hex') -} +main() diff --git a/src/test-services/sidePrefund/testEthereumSend.js b/src/test-services/sidePrefund/testEthereumSend.js index 731af3e..9289e30 100644 --- a/src/test-services/sidePrefund/testEthereumSend.js +++ b/src/test-services/sidePrefund/testEthereumSend.js @@ -7,7 +7,7 @@ const web3 = new Web3(SIDE_RPC_URL, null, { transactionConfirmationBlocks: 1 }) const sender = web3.eth.accounts.privateKeyToAccount(`0x${SIDE_PRIVATE_KEY}`).address -async function main () { +async function main() { const SIDE_CHAIN_ID = await web3.eth.net.getId() const to = process.argv[2] @@ -15,20 +15,19 @@ async function main () { console.log(`Transfer from ${sender} to ${to}, ${amount} eth`) - const tx_coins = { + const txCoins = { data: '0x', from: sender, - to: to, + to, nonce: await web3.eth.getTransactionCount(sender), chainId: SIDE_CHAIN_ID, value: web3.utils.toWei(new BN(amount).toString(), 'ether'), gas: 21000 } - const signedTx = await web3.eth.accounts.signTransaction(tx_coins, SIDE_PRIVATE_KEY) + const signedTx = await web3.eth.accounts.signTransaction(txCoins, SIDE_PRIVATE_KEY) const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction) - console.log('txHash: ' + receipt.transactionHash) - + console.log(`txHash: ${receipt.transactionHash}`) } main() diff --git a/tests/package.json b/tests/package.json index de759d4..ffc0554 100644 --- a/tests/package.json +++ b/tests/package.json @@ -6,5 +6,8 @@ "axios": "0.19.0", "@binance-chain/javascript-sdk": "2.16.1", "bignumber.js": "9.0.0" + }, + "engines": { + "node": ">=10.6.0" } } diff --git a/tests/test/addValidator.js b/tests/test/addValidator.js index 8bfa320..6c1068c 100644 --- a/tests/test/addValidator.js +++ b/tests/test/addValidator.js @@ -5,15 +5,16 @@ const { getBalance } = require('./utils/bncController') const { controller1, controller3 } = require('./utils/proxyController') -module.exports = newValidator => { +module.exports = (newValidator) => { describe('add validator', function () { let info let initialInfo let nextValidators before(async function () { - initialInfo = info = await controller1.getInfo() - nextValidators = [ ...initialInfo.validators, newValidator ] + initialInfo = await controller1.getInfo() + info = initialInfo + nextValidators = [...initialInfo.validators, newValidator] }) it('should start voting process', async function () { @@ -23,7 +24,7 @@ module.exports = newValidator => { assert.strictEqual(info.bridgeStatus, 'ready', 'Should not change state after one vote') await controller3.voteStartVoting() - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'voting') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'voting') assert.deepStrictEqual(info.epoch, initialInfo.epoch, 'Current epoch is not set correctly') assert.deepStrictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Next epoch is not set correctly') assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly') @@ -46,7 +47,10 @@ module.exports = newValidator => { assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly') await controller3.voteAddValidator(newValidator) - info = await waitPromise(controller1.getInfo, info => info.nextValidators.length === nextValidators.length) + info = await waitPromise( + controller1.getInfo, + (newInfo) => newInfo.nextValidators.length === nextValidators.length + ) assert.deepStrictEqual(info.validators, initialInfo.validators, 'Validators are not set correctly') assert.deepStrictEqual(info.nextValidators, nextValidators, 'Next validators are not set correctly') @@ -67,7 +71,7 @@ module.exports = newValidator => { assert.strictEqual(info.bridgeStatus, 'voting', 'Should not change state after one vote') await controller3.voteStartKeygen() - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'keygen') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'keygen') await controller3.voteStartKeygen() await delay(5000) @@ -81,12 +85,15 @@ module.exports = newValidator => { it('should finish keygen process and start funds transfer', async function () { this.timeout(120000) - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'funds_transfer') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'funds_transfer') }) it('should transfer all funds to new account and start new epoch', async function () { this.timeout(300000) - info = await waitPromise(controller1.getInfo, info => info.epoch === initialInfo.epoch + 1) + info = await waitPromise( + controller1.getInfo, + (newInfo) => newInfo.epoch === initialInfo.epoch + 1 + ) assert.deepStrictEqual(info.validators, nextValidators, 'Incorrect set of validators in new epoch') assert.strictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch') assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch') diff --git a/tests/test/bncToEth.js b/tests/test/bncToEth.js index 1587a87..3559435 100644 --- a/tests/test/bncToEth.js +++ b/tests/test/bncToEth.js @@ -2,7 +2,7 @@ const { delay } = require('./utils/wait') const { controller1 } = require('./utils/proxyController') -module.exports = usersFunc => { +module.exports = (usersFunc) => { describe('exchange of tokens in bnc => eth direction', function () { let users let info @@ -11,21 +11,21 @@ module.exports = usersFunc => { before(async function () { users = usersFunc() info = await controller1.getInfo() - ethBalances = await Promise.all(users.map(user => user.getEthBalance())) + ethBalances = await Promise.all(users.map((user) => user.getEthBalance())) await Promise.all(users.map((user, i) => user.exchangeBnc(info.foreignBridgeAddress, 3 + i))) }) - it('should make coorect exchange transactions on eth side', async function () { - for (let i = 0; i < 3; i++) { - do { + it('should make correct exchange transactions on eth side', async function () { + for (let i = 0; i < 3; i += 1) { + while (true) { const user = users[i] const newEthBalance = await user.getEthBalance() if (newEthBalance === ethBalances[i] + 3 + i) { break } await delay(500) - } while (true) + } } }) }) diff --git a/tests/test/changeThreshold.js b/tests/test/changeThreshold.js index eb65ec7..74a6e68 100644 --- a/tests/test/changeThreshold.js +++ b/tests/test/changeThreshold.js @@ -5,13 +5,14 @@ const { getBalance } = require('./utils/bncController') const { controller1, controller2, controller3 } = require('./utils/proxyController') -module.exports = newThreshold => { +module.exports = (newThreshold) => { describe('change threshold', function () { let info let initialInfo before(async function () { - initialInfo = info = await controller1.getInfo() + initialInfo = await controller1.getInfo() + info = initialInfo }) it('should start voting process', async function () { @@ -21,7 +22,7 @@ module.exports = newThreshold => { assert.strictEqual(info.bridgeStatus, 'ready', 'Should not change state after one vote') await controller2.voteStartVoting() - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'voting') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'voting') assert.deepStrictEqual(info.epoch, initialInfo.epoch, 'Current epoch is not set correctly') assert.deepStrictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Next epoch is not set correctly') assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly') @@ -46,7 +47,10 @@ module.exports = newThreshold => { assert.deepStrictEqual(info.nextThreshold, initialInfo.threshold, 'Next threshold is not set correctly') await controller2.voteChangeThreshold(newThreshold) - info = await waitPromise(controller1.getInfo, info => info.nextThreshold === newThreshold) + info = await waitPromise( + controller1.getInfo, + (newInfo) => newInfo.nextThreshold === newThreshold + ) assert.deepStrictEqual(info.validators, initialInfo.validators, 'Validators are not set correctly') assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly') assert.deepStrictEqual(info.threshold, initialInfo.threshold, 'Threshold not set correctly') @@ -70,7 +74,7 @@ module.exports = newThreshold => { assert.strictEqual(info.bridgeStatus, 'voting', 'Should not change state after one vote') await controller2.voteStartKeygen() - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'keygen') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'keygen') await controller3.voteStartKeygen() await delay(5000) @@ -86,12 +90,15 @@ module.exports = newThreshold => { it('should finish keygen process and start funds transfer', async function () { this.timeout(120000) - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'funds_transfer') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'funds_transfer') }) it('should transfer all funds to new account and start new epoch', async function () { this.timeout(300000) - info = await waitPromise(controller1.getInfo, info => info.epoch === initialInfo.epoch + 1) + info = await waitPromise( + controller1.getInfo, + (newInfo) => newInfo.epoch === initialInfo.epoch + 1 + ) assert.deepStrictEqual(info.validators, initialInfo.validators, 'Incorrect set of validators in new epoch') assert.strictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch') assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch') diff --git a/tests/test/ethToBnc.js b/tests/test/ethToBnc.js index 461b92f..4e8afd0 100644 --- a/tests/test/ethToBnc.js +++ b/tests/test/ethToBnc.js @@ -1,12 +1,12 @@ const assert = require('assert') const { getSequence } = require('./utils/bncController') -const { waitPromise, delay } = require('./utils/wait') +const { waitPromise, delay, seqMap } = require('./utils/wait') const { HOME_BRIDGE_ADDRESS } = process.env const { controller1 } = require('./utils/proxyController') -module.exports = usersFunc => { +module.exports = (usersFunc) => { describe('exchange of tokens in eth => bnc direction', function () { let info let users @@ -17,8 +17,8 @@ module.exports = usersFunc => { before(async function () { users = usersFunc() info = await controller1.getInfo() - ethBalances = await Promise.all(users.map(user => user.getEthBalance())) - bncBalances = await users.seqMap(user => user.getBncBalance()) + ethBalances = await Promise.all(users.map((user) => user.getEthBalance())) + bncBalances = await seqMap(users, (user) => user.getBncBalance()) bncBridgeSequence = await getSequence(info.foreignBridgeAddress) await Promise.all(users.map((user, i) => user.approveEth(HOME_BRIDGE_ADDRESS, 5 + i))) @@ -26,21 +26,24 @@ module.exports = usersFunc => { it('should accept exchange requests', async function () { await Promise.all(users.map((user, i) => user.exchangeEth(5 + i))) - const newEthBalances = await Promise.all(users.map(user => user.getEthBalance())) - for (let i = 0; i < 3; i++) { + const newEthBalances = await Promise.all(users.map((user) => user.getEthBalance())) + for (let i = 0; i < 3; i += 1) { assert.strictEqual(newEthBalances[i], ethBalances[i] - 5 - i, `Balance of ${users[i].ethAddress} did not updated as expected`) } }) it('should make exchange transaction on bnc side', async function () { this.timeout(300000) - await waitPromise(() => getSequence(info.foreignBridgeAddress), sequence => sequence === bncBridgeSequence + 1) + await waitPromise( + () => getSequence(info.foreignBridgeAddress), + (sequence) => sequence === bncBridgeSequence + 1 + ) }) it('should make correct exchange transaction', async function () { await delay(10000) - const newBncBalances = await Promise.all(users.map(user => user.getBncBalance())) - for (let i = 0; i < 3; i++) { + const newBncBalances = await Promise.all(users.map((user) => user.getBncBalance())) + for (let i = 0; i < 3; i += 1) { assert.strictEqual(newBncBalances[i], bncBalances[i] + 5 + i, `Balance of ${users[i].bncAddress} did not updated as expected`) } }) diff --git a/tests/test/index.js b/tests/test/index.js index d304050..1293eb8 100644 --- a/tests/test/index.js +++ b/tests/test/index.js @@ -1,5 +1,5 @@ const createUser = require('./utils/user') -const { waitPromise } = require('./utils/wait') +const { waitPromise, seqMap } = require('./utils/wait') const testEthToBnc = require('./ethToBnc') const testBncToEth = require('./bncToEth') @@ -18,7 +18,7 @@ describe('bridge tests', function () { let users before(async function () { - users = await usersConfig.seqMap(user => createUser(user.privateKey)) + users = await seqMap(usersConfig, (user) => createUser(user.privateKey)) }) describe('generation of initial epoch keys', function () { @@ -33,7 +33,7 @@ describe('bridge tests', function () { it('should generate keys', async function () { this.timeout(120000) - info = await waitPromise(controller1.getInfo, info => info.epoch === 1) + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.epoch === 1) }) after(async function () { diff --git a/tests/test/removeValidator.js b/tests/test/removeValidator.js index c5377bb..b33b7e3 100644 --- a/tests/test/removeValidator.js +++ b/tests/test/removeValidator.js @@ -5,15 +5,16 @@ const { getBalance } = require('./utils/bncController') const { controller1, controller2, controller3 } = require('./utils/proxyController') -module.exports = oldValidator => { +module.exports = (oldValidator) => { describe('remove validator', function () { let info let initialInfo let nextValidators before(async function () { - initialInfo = info = await controller1.getInfo() - nextValidators = initialInfo.validators.filter(validator => validator !== oldValidator) + initialInfo = await controller1.getInfo() + info = initialInfo + nextValidators = initialInfo.validators.filter((validator) => validator !== oldValidator) }) it('should start voting process', async function () { @@ -23,7 +24,7 @@ module.exports = oldValidator => { assert.strictEqual(info.bridgeStatus, 'ready', 'Should not change state after one vote') await controller2.voteStartVoting() - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'voting') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'voting') assert.deepStrictEqual(info.epoch, initialInfo.epoch, 'Current epoch is not set correctly') assert.deepStrictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Next epoch is not set correctly') assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly') @@ -46,7 +47,10 @@ module.exports = oldValidator => { assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly') await controller2.voteRemoveValidator(oldValidator) - info = await waitPromise(controller1.getInfo, info => info.nextValidators.length === nextValidators.length) + info = await waitPromise( + controller1.getInfo, + (newInfo) => newInfo.nextValidators.length === nextValidators.length + ) assert.deepStrictEqual(info.validators, initialInfo.validators, 'Validators are not set correctly') assert.deepStrictEqual(info.nextValidators, nextValidators, 'Next validators are not set correctly') @@ -67,7 +71,7 @@ module.exports = oldValidator => { assert.strictEqual(info.bridgeStatus, 'voting', 'Should not change state after one vote') await controller2.voteStartKeygen() - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'keygen') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'keygen') await controller3.voteStartKeygen() await delay(5000) @@ -81,12 +85,15 @@ module.exports = oldValidator => { it('should finish keygen process and start funds transfer', async function () { this.timeout(120000) - info = await waitPromise(controller1.getInfo, info => info.bridgeStatus === 'funds_transfer') + info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.bridgeStatus === 'funds_transfer') }) it('should transfer all funds to new account and start new epoch', async function () { this.timeout(300000) - info = await waitPromise(controller1.getInfo, info => info.epoch === initialInfo.epoch + 1) + info = await waitPromise( + controller1.getInfo, + (newInfo) => newInfo.epoch === initialInfo.epoch + 1 + ) assert.deepStrictEqual(info.validators, nextValidators, 'Incorrect set of validators in new epoch') assert.strictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch') assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch') diff --git a/tests/test/utils/bncClient.js b/tests/test/utils/bncClient.js index 4396b53..cd461fb 100644 --- a/tests/test/utils/bncClient.js +++ b/tests/test/utils/bncClient.js @@ -4,7 +4,7 @@ const { delay } = require('./wait') const { FOREIGN_URL, FOREIGN_ASSET } = process.env -module.exports = async function main (privateKey) { +module.exports = async function main(privateKey) { const client = new Bnc(FOREIGN_URL) client.chooseNetwork('testnet') @@ -16,7 +16,7 @@ module.exports = async function main (privateKey) { await delay(1000) return { - transfer: async function (to, tokens, bnbs) { + async transfer(to, tokens, bnbs) { const outputs = [{ to, coins: [] @@ -35,7 +35,7 @@ module.exports = async function main (privateKey) { } await client.multiSend(from, outputs, 'funding') }, - exchange: async function (to, value) { + async exchange(to, value) { await client.transfer(from, to, value.toString(), FOREIGN_ASSET, 'exchange') } } diff --git a/tests/test/utils/bncController.js b/tests/test/utils/bncController.js index 82fe567..cd4a145 100644 --- a/tests/test/utils/bncController.js +++ b/tests/test/utils/bncController.js @@ -10,12 +10,12 @@ const bnc = axios.create({ }) module.exports = { - getBalance: async function (address) { + async getBalance(address) { const response = await retry(5, () => bnc.get(`/api/v1/account/${address}`)) - const tokens = response.data.balances.find(x => x.symbol === FOREIGN_ASSET) + const tokens = response.data.balances.find((x) => x.symbol === FOREIGN_ASSET) return response && tokens ? parseFloat(tokens.free) : 0 }, - getSequence: async function (address) { + async getSequence(address) { const response = await retry(5, () => bnc.get(`/api/v1/account/${address}/sequence`)) return response ? response.data.sequence : 0 } diff --git a/tests/test/utils/proxyController.js b/tests/test/utils/proxyController.js index b9492c4..6e3fc5c 100644 --- a/tests/test/utils/proxyController.js +++ b/tests/test/utils/proxyController.js @@ -1,6 +1,6 @@ const axios = require('axios') -function createController (validatorId) { +function createController(validatorId) { const url = `http://validator${validatorId}_proxy_1:8002/` const proxy = axios.create({ @@ -9,22 +9,22 @@ function createController (validatorId) { }) return { - getInfo: async function () { + async getInfo() { return (await proxy.get('/info')).data }, - voteStartVoting: async function () { + async voteStartVoting() { return (await proxy.get('/vote/startVoting')).data }, - voteStartKeygen: async function () { + async voteStartKeygen() { return (await proxy.get('/vote/startKeygen')).data }, - voteAddValidator: async function (validatorAddress) { + async voteAddValidator(validatorAddress) { return (await proxy.get(`/vote/addValidator/${validatorAddress}`)).data }, - voteRemoveValidator: async function (validatorAddress) { + async voteRemoveValidator(validatorAddress) { return (await proxy.get(`/vote/removeValidator/${validatorAddress}`)).data }, - voteChangeThreshold: async function (threshold) { + async voteChangeThreshold(threshold) { return (await proxy.get(`/vote/changeThreshold/${threshold}`)).data } } diff --git a/tests/test/utils/user.js b/tests/test/utils/user.js index 8294581..3562bbe 100644 --- a/tests/test/utils/user.js +++ b/tests/test/utils/user.js @@ -11,7 +11,7 @@ const txOptions = { gasLimit: 200000 } -module.exports = async function (privateKey) { +async function createUser(privateKey) { const wallet = new ethers.Wallet(privateKey, provider) const ethAddress = wallet.address const bncAddress = getAddressFromPrivateKey(privateKey) @@ -23,32 +23,34 @@ module.exports = async function (privateKey) { return { ethAddress, bncAddress, - getEthBalance: async function () { + async getEthBalance() { const balance = await token.balanceOf(ethAddress) return parseFloat(new BN(balance).dividedBy(10 ** 18).toFixed(8, 3)) }, - transferEth: async function (to, value) { - const tx = await token.transfer(to, '0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) + async transferEth(to, value) { + const tx = await token.transfer(to, `0x${new BN(value).multipliedBy(10 ** 18).toString(16)}`, txOptions) await tx.wait() }, - approveEth: async function (to, value) { - const tx = await token.approve(to, '0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) + async approveEth(to, value) { + const tx = await token.approve(to, `0x${new BN(value).multipliedBy(10 ** 18).toString(16)}`, txOptions) await tx.wait() }, - exchangeEth: async function (value) { - const tx = await bridge.exchange('0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) + async exchangeEth(value) { + const tx = await bridge.exchange(`0x${new BN(value).multipliedBy(10 ** 18).toString(16)}`, txOptions) await tx.wait() }, - getBncBalance: async function () { + async getBncBalance() { const balance = await getBalance(bncAddress) await delay(1000) return balance }, - transferBnc: async function (bridgeAddress, tokens, bnbs) { + async transferBnc(bridgeAddress, tokens, bnbs) { return await bncClient.transfer(bridgeAddress, tokens, bnbs) }, - exchangeBnc: async function (bridgeAddress, value) { + async exchangeBnc(bridgeAddress, value) { return await bncClient.exchange(bridgeAddress, value) } } } + +module.exports = createUser diff --git a/tests/test/utils/wait.js b/tests/test/utils/wait.js index ae0a145..7002df4 100644 --- a/tests/test/utils/wait.js +++ b/tests/test/utils/wait.js @@ -1,32 +1,34 @@ async function delay(ms) { - await new Promise(res => setTimeout(res, ms)) + await new Promise((res) => setTimeout(res, ms)) } -async function waitPromise (getPromise, checker) { - do { +async function waitPromise(getPromise, checker) { + while (true) { const result = await getPromise() - if (checker(result)) + if (checker(result)) { return result + } await delay(1000) - } while (true) + } } -async function retry (n, getPromise) { +async function retry(n, getPromise) { while (n) { try { return await getPromise() } catch (e) { await delay(3000) - n-- + // eslint-disable-next-line no-param-reassign + n -= 1 } } return null } -Array.prototype.seqMap = async function (transition) { +async function seqMap(arr, transition) { const results = [] - for (let i = 0; i < this.length; i++) { - results[i] = await transition(this[i]) + for (let i = 0; i < arr.length; i += 1) { + results[i] = await transition(arr[i]) } return results } @@ -34,5 +36,6 @@ Array.prototype.seqMap = async function (transition) { module.exports = { waitPromise, delay, - retry + retry, + seqMap }