diff --git a/DEMO.md b/DEMO.md index f225671..0ff1e93 100644 --- a/DEMO.md +++ b/DEMO.md @@ -382,7 +382,15 @@ container for listening GET requests - `ADDRESS` - Ethereum address of a validator. - After enough votes are collected, validator is added into the next validators list for the next epoch. - - http://localhost:5001/vote/addValidator/ADDRESS + - http://localhost:5001/vote/removeValidator/ADDRESS - `ADDRESS` - Ethereum address of a validator. - After enough votes are collected, validator is removed from the next validators list for the next epoch. + - http://localhost:5001/vote/changeThreshold/THRESHOLD + - `THRESHOLD` - Number. New threshold value. + - After enough votes are collected, new threshold is set for next epoch. + - http://localhost:5001/vote/changeCloseEpoch/CLOSE_EPOCH + - `CLOSE_EPOCH` - Boolean. Next epoch close epoch policy + (If true, next validators set will first disable binance account for previous + epoch, before moving onto a new one). + - After enough votes are collected, new close policy is set for the next epoch. diff --git a/src/oracle/bncWatcher/Dockerfile b/src/oracle/bncWatcher/Dockerfile index af9ce69..5d97eb9 100644 --- a/src/oracle/bncWatcher/Dockerfile +++ b/src/oracle/bncWatcher/Dockerfile @@ -3,12 +3,13 @@ FROM node:10.16.0-alpine WORKDIR /watcher RUN apk update && \ - apk add libssl1.1 libressl-dev curl + apk add libressl-dev COPY ./bncWatcher/package.json /watcher/ RUN npm install -COPY ./bncWatcher/bncWatcher.js ./shared/db.js ./shared/logger.js ./shared/crypto.js ./shared/amqp.js ./shared/wait.js /watcher/ +COPY ./bncWatcher/bncWatcher.js /watcher/src/ +COPY ./shared/db.js ./shared/logger.js ./shared/crypto.js ./shared/amqp.js ./shared/wait.js ./shared/binanceClient.js /watcher/shared/ -ENTRYPOINT ["node", "bncWatcher.js"] +ENTRYPOINT ["node", "src/bncWatcher.js"] diff --git a/src/oracle/bncWatcher/bncWatcher.js b/src/oracle/bncWatcher/bncWatcher.js index c66ba36..799fd78 100644 --- a/src/oracle/bncWatcher/bncWatcher.js +++ b/src/oracle/bncWatcher/bncWatcher.js @@ -3,21 +3,21 @@ const BN = require('bignumber.js') const fs = require('fs') const { computeAddress } = require('ethers').utils -const logger = require('./logger') -const redis = require('./db') -const { publicKeyToAddress } = require('./crypto') -const { delay, retry } = require('./wait') -const { connectRabbit, assertQueue } = require('./amqp') +const logger = require('../shared/logger') +const redis = require('../shared/db') +const { publicKeyToAddress } = require('../shared/crypto') +const { delay } = require('../shared/wait') +const { connectRabbit, assertQueue } = require('../shared/amqp') +const { getTx, getBlockTime, fetchNewTransactions } = require('../shared/binanceClient') const { - FOREIGN_URL, PROXY_URL, FOREIGN_ASSET, RABBITMQ_URL + PROXY_URL, RABBITMQ_URL } = process.env 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 FOREIGN_FETCH_MAX_TIME_INTERVAL = parseInt(process.env.FOREIGN_FETCH_MAX_TIME_INTERVAL, 10) -const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL }) const proxyHttpClient = axios.create({ baseURL: PROXY_URL }) let channel @@ -33,45 +33,11 @@ function getForeignAddress(epoch) { } } -async function getTx(hash) { - const response = await retry(() => foreignHttpClient.get( - `/api/v1/tx/${hash}`, - { - params: { - format: 'json' - } - } - )) - return response.data.tx.value -} - -async function getBlockTime() { - const response = await retry(() => foreignHttpClient.get('/api/v1/time')) - return Date.parse(response.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET -} - -async function fetchNewTransactions(address, startTime, endTime) { - logger.debug('Fetching new transactions') - const params = { - address, - side: 'RECEIVE', - txAsset: FOREIGN_ASSET, - txType: 'TRANSFER', - startTime, - endTime - } - - logger.trace('Transactions fetch params %o', params) - return ( - await retry(() => foreignHttpClient.get('/api/v1/transactions', { params })) - ).data.tx -} - async function fetchTimeIntervalsQueue() { let epoch = null let startTime = null let endTime = null - const lastBncBlockTime = await getBlockTime() + const lastBncBlockTime = await getBlockTime() - FOREIGN_FETCH_BLOCK_TIME_OFFSET logger.trace(`Binance last block timestamp ${lastBncBlockTime}`) while (true) { const msg = await epochTimeIntervalsQueue.get() @@ -160,7 +126,8 @@ async function loop() { 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).toString(16), + value: new BN(tx.value).multipliedBy(10 ** 18) + .toString(16), hash: tx.txHash, epoch }) diff --git a/src/oracle/bncWatcher/package.json b/src/oracle/bncWatcher/package.json index 08a6559..5c1a75c 100644 --- a/src/oracle/bncWatcher/package.json +++ b/src/oracle/bncWatcher/package.json @@ -13,7 +13,10 @@ }, "engines": { "node": ">=10.6.0" - } + }, + "files": [ + "../shared" + ] } diff --git a/src/oracle/ethWatcher/Dockerfile b/src/oracle/ethWatcher/Dockerfile index 9ca8e0f..9d97003 100644 --- a/src/oracle/ethWatcher/Dockerfile +++ b/src/oracle/ethWatcher/Dockerfile @@ -3,12 +3,13 @@ FROM node:10.16.0-alpine WORKDIR /watcher RUN apk update && \ - apk add libssl1.1 libressl-dev curl + apk add libressl-dev COPY ./ethWatcher/package.json /watcher/ RUN npm install -COPY ./ethWatcher/ethWatcher.js ./shared/db.js ./shared/logger.js ./shared/amqp.js ./shared/crypto.js ./shared/wait.js /watcher/ +COPY ./ethWatcher/ethWatcher.js /watcher/src/ +COPY ./shared/db.js ./shared/logger.js ./shared/amqp.js ./shared/crypto.js ./shared/wait.js /watcher/shared/ -ENTRYPOINT ["node", "ethWatcher.js"] +ENTRYPOINT ["node", "src/ethWatcher.js"] diff --git a/src/oracle/ethWatcher/ethWatcher.js b/src/oracle/ethWatcher/ethWatcher.js index 82e7632..ae49468 100644 --- a/src/oracle/ethWatcher/ethWatcher.js +++ b/src/oracle/ethWatcher/ethWatcher.js @@ -2,11 +2,11 @@ const ethers = require('ethers') const BN = require('bignumber.js') const axios = require('axios') -const logger = require('./logger') -const redis = require('./db') -const { connectRabbit, assertQueue } = require('./amqp') -const { publicKeyToAddress } = require('./crypto') -const { delay, retry } = require('./wait') +const logger = require('../shared/logger') +const redis = require('../shared/db') +const { connectRabbit, assertQueue } = require('../shared/amqp') +const { publicKeyToAddress } = require('../shared/crypto') +const { delay, retry } = require('../shared/wait') const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL, HOME_START_BLOCK, VALIDATOR_PRIVATE_KEY diff --git a/src/oracle/ethWatcher/package.json b/src/oracle/ethWatcher/package.json index a7517af..8096339 100644 --- a/src/oracle/ethWatcher/package.json +++ b/src/oracle/ethWatcher/package.json @@ -13,5 +13,8 @@ }, "engines": { "node": ">=10.6.0" - } + }, + "files": [ + "../shared" + ] } diff --git a/src/oracle/proxy/Dockerfile b/src/oracle/proxy/Dockerfile index 2069f11..b776276 100644 --- a/src/oracle/proxy/Dockerfile +++ b/src/oracle/proxy/Dockerfile @@ -6,6 +6,7 @@ COPY ./proxy/package.json /proxy/ RUN npm install -COPY ./proxy/index.js ./proxy/encode.js ./proxy/decode.js ./proxy/sendTx.js ./proxy/contractsAbi.js ./proxy/utils.js ./shared/logger.js ./shared/crypto.js ./shared/wait.js /proxy/ +COPY ./proxy/index.js ./proxy/encode.js ./proxy/decode.js ./proxy/sendTx.js ./proxy/contractsAbi.js ./proxy/utils.js ./proxy/expressUtils.js /proxy/src/ +COPY ./shared/logger.js ./shared/crypto.js ./shared/wait.js ./shared/binanceClient.js /proxy/shared/ -ENTRYPOINT ["node", "index.js"] +ENTRYPOINT ["node", "src/index.js"] diff --git a/src/oracle/proxy/encode.js b/src/oracle/proxy/encode.js index 13361f3..526c1f2 100644 --- a/src/oracle/proxy/encode.js +++ b/src/oracle/proxy/encode.js @@ -1,6 +1,6 @@ const BN = require('bignumber.js') -const { padZeros } = require('./crypto') +const { padZeros } = require('../shared/crypto') function makeBuffer(value, length = 32, base = 16) { return Buffer.from(padZeros(new BN(value, base).toString(16), length * 2), 'hex') diff --git a/src/oracle/proxy/expressUtils.js b/src/oracle/proxy/expressUtils.js new file mode 100644 index 0000000..4078563 --- /dev/null +++ b/src/oracle/proxy/expressUtils.js @@ -0,0 +1,63 @@ +const ethers = require('ethers') + +const logger = require('../shared/logger') + +function parseNumber(fromQuery, field, defaultValue = null) { + return (req, res, next) => { + const source = fromQuery ? req.query : req.params + if (/^[0-9]+$/.test(source[field])) { + req[field] = parseInt(source[field], 10) + logger.trace(`Set req.${field} to ${req[field]}`) + next() + } else if (!source[field] && defaultValue !== null) { + req[field] = defaultValue + logger.trace(`Set req.${field} to ${defaultValue}`) + next() + } else { + res.status(400).end() + } + } +} + +function parseAddress(field) { + return (req, res, next) => { + logger.debug(`${field} %o`, req.params) + if (ethers.utils.isHexString(req.params[field], 20)) { + req[field] = req.params[field] + logger.trace(`Set req.${field} to ${req[field]}`) + next() + } else { + res.status(400).end() + } + } +} + +function parseBool(field) { + return (req, res, next) => { + if (req.params[field] === 'true' || req.params[field] === 'false') { + req[field] = req.params[field] === 'true' + logger.trace(`Set req.${field} to ${req[field]}`) + next() + } else { + res.status(400).end() + } + } +} + +function logRequest(req, res, next) { + logger.debug(`${req.method} request to ${req.originalUrl}`) + if (req.query && Object.keys(req.query).length > 0) { + logger.trace('Requst query: %o', req.query) + } + if (req.body && Object.keys(req.body).length > 0) { + logger.trace('Requst body: %o', req.body) + } + next() +} + +module.exports = { + parseNumber, + parseAddress, + parseBool, + logRequest +} diff --git a/src/oracle/proxy/index.js b/src/oracle/proxy/index.js index 6a12894..c91ebc2 100644 --- a/src/oracle/proxy/index.js +++ b/src/oracle/proxy/index.js @@ -1,51 +1,36 @@ const express = require('express') const AsyncLock = require('async-lock') -const axios = require('axios') const BN = require('bignumber.js') const ethers = require('ethers') const { tokenAbi, bridgeAbi, sharedDbAbi } = require('./contractsAbi') const { - Ok, Err, decodeStatus + Ok, Err, decodeStatus, encodeParam, Action } = require('./utils') const encode = require('./encode') const decode = require('./decode') const { createSender, waitForReceipt } = require('./sendTx') -const logger = require('./logger') -const { publicKeyToAddress, padZeros } = require('./crypto') +const logger = require('../shared/logger') +const { publicKeyToAddress, padZeros } = require('../shared/crypto') +const { + parseNumber, parseAddress, parseBool, logRequest +} = require('./expressUtils') +const { getForeignBalances } = require('../shared/binanceClient') const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, - HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET + HOME_TOKEN_ADDRESS, FOREIGN_ASSET } = process.env -const Action = { - CONFIRM_KEYGEN: 0, - CONFIRM_FUNDS_TRANSFER: 1, - CONFIRM_CLOSE_EPOCH: 2, - VOTE_START_VOTING: 3, - VOTE_ADD_VALIDATOR: 4, - VOTE_REMOVE_VALIDATOR: 5, - VOTE_CHANGE_THRESHOLD: 6, - VOTE_CHANGE_RANGE_SIZE: 7, - VOTE_CHANGE_CLOSE_EPOCH: 8, - VOTE_START_KEYGEN: 9, - VOTE_CANCEL_KEYGEN: 10, - TRANSFER: 11 -} - const homeProvider = new ethers.providers.JsonRpcProvider(HOME_RPC_URL) const sideProvider = new ethers.providers.JsonRpcProvider(SIDE_RPC_URL) -const homeWallet = new ethers.Wallet(VALIDATOR_PRIVATE_KEY, homeProvider) const sideWallet = new ethers.Wallet(VALIDATOR_PRIVATE_KEY, sideProvider) -const token = new ethers.Contract(HOME_TOKEN_ADDRESS, tokenAbi, homeWallet) -const bridge = new ethers.Contract(HOME_BRIDGE_ADDRESS, bridgeAbi, homeWallet) +const token = new ethers.Contract(HOME_TOKEN_ADDRESS, tokenAbi, homeProvider) +const bridge = new ethers.Contract(HOME_BRIDGE_ADDRESS, bridgeAbi, homeProvider) const sharedDb = new ethers.Contract(SIDE_SHARED_DB_ADDRESS, sharedDbAbi, sideWallet) -const validatorAddress = homeWallet.address - -const httpClient = axios.create({ baseURL: FOREIGN_URL }) +const validatorAddress = sideWallet.address const lock = new AsyncLock() @@ -74,7 +59,6 @@ function sideSendQuery(query) { } async function status(req, res) { - logger.debug('Status call') const [bridgeEpoch, bridgeStatus] = await Promise.all([ bridge.epoch(), bridge.status() @@ -83,11 +67,9 @@ async function status(req, res) { bridgeEpoch, bridgeStatus }) - logger.debug('Status end') } 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 @@ -117,12 +99,9 @@ async function get(req, res) { } else { setTimeout(() => res.send(Err(null)), 1000) } - - logger.debug('Get end') } async function set(req, res) { - logger.debug('Set call') const round = req.body.key.second const uuid = req.body.key.third const to = Number(req.body.key.fourth) @@ -136,11 +115,9 @@ async function set(req, res) { await sideSendQuery(query) res.send(Ok(null)) - logger.debug('Set end') } async function signupKeygen(req, res) { - logger.debug('SignupKeygen call') const epoch = await bridge.nextEpoch() const partyId = await bridge.getNextPartyId(validatorAddress) @@ -166,12 +143,10 @@ async function signupKeygen(req, res) { uuid, number: partyId })) - logger.debug('SignupKeygen end') } } async function signupSign(req, res) { - logger.debug('SignupSign call') const msgHash = req.body.third logger.debug('Checking previous attempts') @@ -211,34 +186,15 @@ async function signupSign(req, res) { uuid: hash, number: id })) - logger.debug('SignupSign end') } -function encodeParam(param) { - switch (typeof param) { - case 'string': - if (param.startsWith('0x')) { - return Buffer.from(param.slice(2), 'hex') - } - return Buffer.from(param, 'hex') - case 'number': - return Buffer.from(padZeros(param.toString(16), 4), 'hex') - case 'boolean': - return Buffer.from([param ? 1 : 0]) - default: - return null - } -} - -function buildMessage(type, ...params) { - logger.debug(`${type}, %o`, params) - return Buffer.concat([ +async function processMessage(type, ...params) { + logger.debug(`Building message ${type}, %o`, params) + const message = Buffer.concat([ Buffer.from([type]), ...params.map(encodeParam) ]) -} -async function processMessage(message) { const signature = await sideWallet.signMessage(message) logger.debug('Adding signature to shared db contract') const query = sharedDb.interface.functions.addSignature.encode([`0x${message.toString('hex')}`, signature]) @@ -246,137 +202,94 @@ async function processMessage(message) { } async function confirmKeygen(req, res) { - logger.debug('Confirm keygen call') const { x, y, epoch } = req.body - const message = buildMessage(Action.CONFIRM_KEYGEN, epoch, padZeros(x, 64), padZeros(y, 64)) - await processMessage(message) + await processMessage(Action.CONFIRM_KEYGEN, epoch, padZeros(x, 64), padZeros(y, 64)) res.send() - logger.debug('Confirm keygen end') } async function confirmFundsTransfer(req, res) { - logger.debug('Confirm funds transfer call') const { epoch } = req.body - const message = buildMessage(Action.CONFIRM_FUNDS_TRANSFER, epoch) - await processMessage(message) + await processMessage(Action.CONFIRM_FUNDS_TRANSFER, epoch) res.send() - logger.debug('Confirm funds transfer end') } async function confirmCloseEpoch(req, res) { - logger.debug('Confirm close epoch call') const { epoch } = req.body - const message = buildMessage(Action.CONFIRM_CLOSE_EPOCH, epoch) - await processMessage(message) + await processMessage(Action.CONFIRM_CLOSE_EPOCH, epoch) res.send() - logger.debug('Confirm close epoch end') } async function voteStartVoting(req, res) { - logger.info('Voting for starting new epoch voting process') const epoch = await bridge.epoch() - const message = buildMessage(Action.VOTE_START_VOTING, epoch) - await processMessage(message) + await processMessage(Action.VOTE_START_VOTING, epoch) res.send('Voted\n') - logger.info('Voted successfully') } async function voteStartKeygen(req, res) { - logger.info('Voting for starting new epoch keygen') const epoch = await bridge.epoch() - const message = buildMessage(Action.VOTE_START_KEYGEN, epoch) - await processMessage(message) + await processMessage(Action.VOTE_START_KEYGEN, epoch) res.send('Voted\n') - logger.info('Voted successfully') } async function voteCancelKeygen(req, res) { - logger.info('Voting for cancelling new epoch keygen') const epoch = await bridge.nextEpoch() - const message = buildMessage(Action.VOTE_CANCEL_KEYGEN, epoch) - await processMessage(message) + await processMessage(Action.VOTE_CANCEL_KEYGEN, epoch) res.send('Voted\n') - logger.info('Voted successfully') } async function voteAddValidator(req, res) { - if (ethers.utils.isHexString(req.params.validator, 20)) { - logger.info('Voting for adding new validator') - const epoch = await bridge.epoch() - const message = buildMessage( - Action.VOTE_ADD_VALIDATOR, - epoch, - req.params.validator, - padZeros(req.attempt, 18) - ) - await processMessage(message) - res.send('Voted\n') - logger.info('Voted successfully') - } + const epoch = await bridge.epoch() + await processMessage( + Action.VOTE_ADD_VALIDATOR, + epoch, + req.validator, + padZeros(req.attempt.toString(16), 18) + ) + res.send('Voted\n') } async function voteChangeThreshold(req, res) { - if (/^[0-9]+$/.test(req.params.threshold)) { - logger.info('Voting for changing threshold') - const epoch = await bridge.epoch() - const message = buildMessage( - Action.VOTE_CHANGE_THRESHOLD, - epoch, - parseInt(req.params.threshold, 10), - padZeros(req.attempt, 54) - ) - await processMessage(message) - res.send('Voted\n') - logger.info('Voted successfully') - } + const epoch = await bridge.epoch() + await processMessage( + Action.VOTE_CHANGE_THRESHOLD, + epoch, + req.threshold, + padZeros(req.attempt.toString(16), 54) + ) + res.send('Voted\n') } async function voteChangeRangeSize(req, res) { - if (/^[0-9]+$/.test(req.params.rangeSize)) { - logger.info('Voting for changing range size') - const epoch = await bridge.epoch() - const message = buildMessage( - Action.VOTE_CHANGE_RANGE_SIZE, - epoch, - parseInt(req.params.rangeSize, 10), - padZeros(req.attempt, 54) - ) - await processMessage(message) - res.send('Voted\n') - logger.info('Voted successfully') - } + const epoch = await bridge.epoch() + await processMessage( + Action.VOTE_CHANGE_RANGE_SIZE, + epoch, + req.rangeSize, + padZeros(req.attempt.toString(16), 54) + ) + res.send('Voted\n') } async function voteChangeCloseEpoch(req, res) { - if (req.params.closeEpoch === 'true' || req.params.closeEpoch === 'false') { - logger.info('Voting for changing close epoch') - const epoch = await bridge.epoch() - const message = buildMessage( - Action.VOTE_CHANGE_CLOSE_EPOCH, - epoch, - req.params.closeEpoch === 'true', - padZeros(req.attempt, 56) - ) - await processMessage(message) - res.send('Voted\n') - logger.info('Voted successfully') - } + const epoch = await bridge.epoch() + await processMessage( + Action.VOTE_CHANGE_CLOSE_EPOCH, + epoch, + req.closeEpoch, + padZeros(req.attempt.toString(16), 56) + ) + res.send('Voted\n') } async function voteRemoveValidator(req, res) { - if (ethers.utils.isHexString(req.params.validator, 20)) { - logger.info('Voting for removing validator') - const epoch = await bridge.epoch() - const message = buildMessage( - Action.VOTE_REMOVE_VALIDATOR, - epoch, - req.params.validator, - padZeros(req.attempt, 18) - ) - await processMessage(message) - res.send('Voted\n') - logger.info('Voted successfully') - } + const epoch = await bridge.epoch() + await processMessage( + Action.VOTE_REMOVE_VALIDATOR, + epoch, + req.validator, + padZeros(req.attempt.toString(16), 18) + ) + res.send('Voted\n') } async function transfer(req, res) { @@ -386,35 +299,23 @@ async function transfer(req, res) { } = req.body if (ethers.utils.isHexString(to, 20)) { logger.info(`Calling transfer to ${to}, 0x${value} tokens`) - const message = buildMessage(Action.TRANSFER, epoch, hash, to, padZeros(value, 24)) - logger.info(`Message for sign: ${message.toString('hex')}`) - await processMessage(message) + await processMessage(Action.TRANSFER, epoch, hash, to, padZeros(value, 24)) } 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, closeEpoch, nextCloseEpoch, epochStartBlock, foreignNonce, nextEpoch, threshold, nextThreshold, validators, nextValidators, bridgeStatus, homeBalance ] = await Promise.all([ - bridge.getX().then((value) => new BN(value).toString(16)), - bridge.getY().then((value) => new BN(value).toString(16)), + bridge.getX() + .then((value) => new BN(value).toString(16)), + bridge.getY() + .then((value) => new BN(value).toString(16)), bridge.epoch(), bridge.getRangeSize(), bridge.getNextRangeSize(), @@ -429,7 +330,8 @@ async function info(req, res) { bridge.getNextValidators(), bridge.status(), token.balanceOf(HOME_BRIDGE_ADDRESS) - .then((value) => parseFloat(new BN(value).dividedBy(10 ** 18).toFixed(8, 3))) + .then((value) => parseFloat(new BN(value).dividedBy(10 ** 18) + .toFixed(8, 3))) ]) const foreignAddress = publicKeyToAddress({ x, @@ -465,9 +367,10 @@ async function info(req, res) { error: e }) } - logger.debug('Info end') } + +app.use('/', logRequest) app.get('/status', status) app.post('/get', get) @@ -480,27 +383,18 @@ app.post('/confirmFundsTransfer', confirmFundsTransfer) app.post('/confirmCloseEpoch', confirmCloseEpoch) app.post('/transfer', transfer) +votesProxyApp.use('/', logRequest) + votesProxyApp.get('/vote/startVoting', voteStartVoting) votesProxyApp.get('/vote/startKeygen', voteStartKeygen) votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen) -votesProxyApp.use('/vote', (req, res, next) => { - if (/^[0-9]+$/.test(req.query.attempt)) { - req.attempt = parseInt(req.query.attempt, 10).toString(16) - logger.debug(`Vote attempt 0x${req.attempt}`) - next() - } else if (!req.query.attempt) { - req.attempt = '0' - logger.debug('Vote attempt 0x00') - next() - } -}) - -votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator) -votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator) -votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold) -votesProxyApp.get('/vote/changeRangeSize/:rangeSize', voteChangeRangeSize) -votesProxyApp.get('/vote/changeCloseEpoch/:closeEpoch', voteChangeCloseEpoch) +votesProxyApp.use('/vote', parseNumber(true, 'attempt', 0)) +votesProxyApp.get('/vote/addValidator/:validator', parseAddress('validator'), voteAddValidator) +votesProxyApp.get('/vote/removeValidator/:validator', parseAddress('validator'), voteRemoveValidator) +votesProxyApp.get('/vote/changeThreshold/:threshold', parseNumber(false, 'threshold'), voteChangeThreshold) +votesProxyApp.get('/vote/changeRangeSize/:rangeSize', parseNumber(false, 'rangeSize'), voteChangeRangeSize) +votesProxyApp.get('/vote/changeCloseEpoch/:closeEpoch', parseBool('closeEpoch'), voteChangeCloseEpoch) votesProxyApp.get('/info', info) async function main() { diff --git a/src/oracle/proxy/package.json b/src/oracle/proxy/package.json index 33c7e10..86af594 100644 --- a/src/oracle/proxy/package.json +++ b/src/oracle/proxy/package.json @@ -14,5 +14,8 @@ }, "engines": { "node": ">=10.6.0" - } + }, + "files": [ + "../shared" + ] } diff --git a/src/oracle/proxy/sendTx.js b/src/oracle/proxy/sendTx.js index f73adf3..add626d 100644 --- a/src/oracle/proxy/sendTx.js +++ b/src/oracle/proxy/sendTx.js @@ -2,8 +2,8 @@ const axios = require('axios') const ethers = require('ethers') const BN = require('bignumber.js') -const logger = require('./logger') -const { delay, retry } = require('./wait') +const logger = require('../shared/logger') +const { delay, retry } = require('../shared/wait') const { GAS_LIMIT_FACTOR, MAX_GAS_LIMIT } = process.env diff --git a/src/oracle/proxy/utils.js b/src/oracle/proxy/utils.js index 2ae74d1..b8abbc6 100644 --- a/src/oracle/proxy/utils.js +++ b/src/oracle/proxy/utils.js @@ -1,3 +1,20 @@ +const { padZeros } = require('../shared/crypto') + +const Action = { + CONFIRM_KEYGEN: 0, + CONFIRM_FUNDS_TRANSFER: 1, + CONFIRM_CLOSE_EPOCH: 2, + VOTE_START_VOTING: 3, + VOTE_ADD_VALIDATOR: 4, + VOTE_REMOVE_VALIDATOR: 5, + VOTE_CHANGE_THRESHOLD: 6, + VOTE_CHANGE_RANGE_SIZE: 7, + VOTE_CHANGE_CLOSE_EPOCH: 8, + VOTE_START_KEYGEN: 9, + VOTE_CANCEL_KEYGEN: 10, + TRANSFER: 11 +} + function Ok(data) { return { Ok: data } } @@ -23,8 +40,26 @@ function decodeStatus(status) { } } +function encodeParam(param) { + switch (typeof param) { + case 'string': + if (param.startsWith('0x')) { + return Buffer.from(param.slice(2), 'hex') + } + return Buffer.from(param, 'hex') + case 'number': + return Buffer.from(padZeros(param.toString(16), 4), 'hex') + case 'boolean': + return Buffer.from([param ? 1 : 0]) + default: + return null + } +} + module.exports = { Ok, Err, - decodeStatus + decodeStatus, + encodeParam, + Action } diff --git a/src/oracle/shared/binanceClient.js b/src/oracle/shared/binanceClient.js new file mode 100644 index 0000000..89819aa --- /dev/null +++ b/src/oracle/shared/binanceClient.js @@ -0,0 +1,104 @@ +const axios = require('axios') +const BN = require('bignumber.js') + +const logger = require('./logger') +const { delay, retry } = require('./wait') + +const { FOREIGN_URL, FOREIGN_ASSET } = process.env + +const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL }) + +async function getForeignBalances(address) { + try { + const response = await foreignHttpClient.get(`/api/v1/account/${address}`) + return response.data.balances.reduce((prev, cur) => { + // eslint-disable-next-line no-param-reassign + prev[cur.symbol] = cur.free + return prev + }, {}) + } catch (e) { + return {} + } +} + +async function getTx(hash) { + const response = await retry(() => foreignHttpClient.get( + `/api/v1/tx/${hash}`, + { + params: { + format: 'json' + } + } + )) + return response.data.tx.value +} + +async function getBlockTime() { + const response = await retry(() => foreignHttpClient.get('/api/v1/time')) + return Date.parse(response.data.block_time) +} + +async function fetchNewTransactions(address, startTime, endTime) { + logger.debug('Fetching new transactions') + const params = { + address, + side: 'RECEIVE', + txAsset: FOREIGN_ASSET, + txType: 'TRANSFER', + startTime, + endTime + } + + logger.trace('Transactions fetch params %o', params) + return ( + await retry(() => foreignHttpClient.get('/api/v1/transactions', { params })) + ).data.tx +} + +async function getAccount(address) { + logger.info(`Getting account ${address} data`) + const response = await retry(() => foreignHttpClient.get(`/api/v1/account/${address}`)) + return response.data +} + +async function getFee() { + logger.info('Getting fees') + const response = await retry(() => foreignHttpClient.get('/api/v1/fees')) + const multiTransferFee = response.data.find((fee) => fee.multi_transfer_fee).multi_transfer_fee + return new BN(multiTransferFee * 2).div(10 ** 8) +} + +async function sendTx(tx) { + while (true) { + try { + return await foreignHttpClient.post('/api/v1/broadcast?sync=true', tx, { + headers: { + 'Content-Type': 'text/plain' + } + }) + } catch (err) { + logger.trace('Error, response data %o', err.response.data) + if (err.response.data.message.includes('Tx already exists in cache')) { + logger.debug('Tx already exists in cache') + return true + } + if (err.response.data.message.includes(' < ')) { + logger.warn('Insufficient funds, waiting for funds') + await delay(60000) + } else { + logger.info('Something failed, restarting: %o', err.response) + await delay(10000) + } + } + } +} + +module.exports = { + getForeignBalances, + getTx, + getBlockTime, + fetchNewTransactions, + getAccount, + getFee, + sendTx +} diff --git a/src/oracle/tss-keygen/Dockerfile b/src/oracle/tss-keygen/Dockerfile index fd34ce2..cc96bc9 100644 --- a/src/oracle/tss-keygen/Dockerfile +++ b/src/oracle/tss-keygen/Dockerfile @@ -3,16 +3,18 @@ FROM node:10.16.0-slim WORKDIR /tss RUN apt-get update && \ - apt-get install -y libssl1.1 libssl-dev curl procps + apt-get install -y libssl-dev procps COPY ./tss-keygen/package.json /tss/ RUN npm install -COPY ./tss-keygen/keygen-entrypoint.sh ./tss-keygen/keygen.js ./shared/logger.js ./shared/amqp.js ./shared/crypto.js ./shared/wait.js /tss/ - COPY --from=tss /tss/target/release/gg18_keygen_client /tss/ +COPY ./tss-keygen/keygen-entrypoint.sh /tss/ +COPY ./tss-keygen/keygen.js /tss/src/ +COPY ./shared/logger.js ./shared/amqp.js ./shared/crypto.js ./shared/wait.js /tss/shared/ + RUN mkdir /keys -ENTRYPOINT ["node", "keygen.js"] +ENTRYPOINT ["node", "src/keygen.js"] diff --git a/src/oracle/tss-keygen/keygen.js b/src/oracle/tss-keygen/keygen.js index 18fcfc4..7d15000 100644 --- a/src/oracle/tss-keygen/keygen.js +++ b/src/oracle/tss-keygen/keygen.js @@ -3,10 +3,10 @@ const fs = require('fs') const express = require('express') const axios = require('axios') -const logger = require('./logger') -const { connectRabbit, assertQueue } = require('./amqp') -const { publicKeyToAddress } = require('./crypto') -const { delay } = require('./wait') +const logger = require('../shared/logger') +const { connectRabbit, assertQueue } = require('../shared/amqp') +const { publicKeyToAddress } = require('../shared/crypto') +const { delay } = require('../shared/wait') const { RABBITMQ_URL, PROXY_URL } = process.env const KEYGEN_ATTEMPT_TIMEOUT = parseInt(process.env.KEYGEN_ATTEMPT_TIMEOUT, 10) diff --git a/src/oracle/tss-keygen/package.json b/src/oracle/tss-keygen/package.json index fb1d8db..13b5540 100644 --- a/src/oracle/tss-keygen/package.json +++ b/src/oracle/tss-keygen/package.json @@ -11,5 +11,8 @@ }, "engines": { "node": ">=10.6.0" - } + }, + "files": [ + "../shared" + ] } diff --git a/src/oracle/tss-sign/Dockerfile b/src/oracle/tss-sign/Dockerfile index 0b14ac5..7192fe7 100644 --- a/src/oracle/tss-sign/Dockerfile +++ b/src/oracle/tss-sign/Dockerfile @@ -3,15 +3,16 @@ FROM node:10.16.0-slim WORKDIR /tss RUN apt-get update && \ - apt-get install -y libssl1.1 libssl-dev curl python make g++ libudev-dev libusb-dev usbutils procps -#apk packages: libssl1.1 eudev-dev libressl-dev curl build-base python linux-headers libusb-dev + apt-get install -y libssl-dev python make g++ libudev-dev usbutils procps COPY ./tss-sign/package.json /tss/ RUN npm install --no-optional -COPY ./tss-sign/sign-entrypoint.sh ./tss-sign/signer.js ./tss-sign/tx.js ./shared/logger.js ./shared/amqp.js ./shared/crypto.js ./shared/wait.js /tss/ - COPY --from=tss /tss/target/release/gg18_sign_client /tss/ -ENTRYPOINT ["node", "signer.js"] +COPY ./tss-sign/sign-entrypoint.sh /tss/ +COPY ./tss-sign/signer.js ./tss-sign/tx.js /tss/src/ +COPY ./shared/logger.js ./shared/amqp.js ./shared/crypto.js ./shared/wait.js ./shared/binanceClient.js /tss/shared/ + +ENTRYPOINT ["node", "src/signer.js"] diff --git a/src/oracle/tss-sign/package.json b/src/oracle/tss-sign/package.json index 7d0c822..342ae14 100644 --- a/src/oracle/tss-sign/package.json +++ b/src/oracle/tss-sign/package.json @@ -12,5 +12,8 @@ }, "engines": { "node": ">=10.6.0" - } + }, + "files": [ + "../shared" + ] } diff --git a/src/oracle/tss-sign/signer.js b/src/oracle/tss-sign/signer.js index 514b7ba..5acf86c 100644 --- a/src/oracle/tss-sign/signer.js +++ b/src/oracle/tss-sign/signer.js @@ -4,23 +4,23 @@ 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 { delay, retry } = require('./wait') +const logger = require('../shared/logger') +const { connectRabbit, assertQueue } = require('../shared/amqp') +const { publicKeyToAddress, sha256 } = require('../shared/crypto') +const { delay } = require('../shared/wait') +const { getAccount, getFee, sendTx } = require('../shared/binanceClient') const Transaction = require('./tx') const app = express() const { - RABBITMQ_URL, FOREIGN_URL, PROXY_URL, FOREIGN_ASSET + RABBITMQ_URL, PROXY_URL, FOREIGN_ASSET } = process.env const SIGN_ATTEMPT_TIMEOUT = parseInt(process.env.SIGN_ATTEMPT_TIMEOUT, 10) const SIGN_NONCE_CHECK_INTERVAL = parseInt(process.env.SIGN_NONCE_CHECK_INTERVAL, 10) const SEND_TIMEOUT = parseInt(process.env.SEND_TIMEOUT, 10) -const httpClient = axios.create({ baseURL: FOREIGN_URL }) const proxyClient = axios.create({ baseURL: PROXY_URL }) const SIGN_OK = 0 @@ -88,19 +88,6 @@ function getAccountFromFile(file) { } } -async function getAccount(address) { - logger.info(`Getting account ${address} data`) - const response = await retry(() => httpClient.get(`/api/v1/account/${address}`)) - return response.data -} - -async function getFee() { - logger.info('Getting fees') - const response = await retry(() => httpClient.get('/api/v1/fees')) - const multiTransferFee = response.data.find((fee) => fee.multi_transfer_fee).multi_transfer_fee - return new BN(multiTransferFee * 2).div(10 ** 8) -} - async function waitForAccountNonce(address, nonce) { cancelled = false logger.info(`Waiting for account ${address} to have nonce ${nonce}`) @@ -116,31 +103,6 @@ async function waitForAccountNonce(address, nonce) { return !cancelled } -async function sendTx(tx) { - while (true) { - try { - return await httpClient.post('/api/v1/broadcast?sync=true', tx, { - headers: { - 'Content-Type': 'text/plain' - } - }) - } catch (err) { - logger.trace('Error, response data %o', err.response.data) - if (err.response.data.message.includes('Tx already exists in cache')) { - logger.debug('Tx already exists in cache') - return true - } - if (err.response.data.message.includes(' < ')) { - logger.warn('Insufficient funds, waiting for funds') - await delay(60000) - } else { - logger.info('Something failed, restarting: %o', err.response) - await delay(10000) - } - } - } -} - function sign(keysFile, tx, publicKey, signerAddress) { let restartTimeoutId let nonceDaemonIntervalId @@ -302,7 +264,7 @@ async function consumer(msg) { const { tx, exchanges } = await buildTx(from, account, data) - while (tx !== null) { + while (true) { const signResult = await sign(keysFile, tx, publicKey, from) if (signResult === SIGN_OK || signResult === SIGN_NONCE_INTERRUPT) { diff --git a/src/oracle/tss-sign/tx.js b/src/oracle/tss-sign/tx.js index 032d4e8..c6395c1 100644 --- a/src/oracle/tss-sign/tx.js +++ b/src/oracle/tss-sign/tx.js @@ -2,8 +2,8 @@ const TransactionBnc = require('@binance-chain/javascript-sdk/lib/tx').default const { crypto } = require('@binance-chain/javascript-sdk') const BN = require('bignumber.js') -const logger = require('./logger') -const { padZeros } = require('./crypto') +const logger = require('../shared/logger') +const { padZeros } = require('../shared/crypto') const { FOREIGN_CHAIN_ID } = process.env diff --git a/src/tss/Dockerfile b/src/tss/Dockerfile index 9b509e8..4eb92bb 100644 --- a/src/tss/Dockerfile +++ b/src/tss/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:19.10 RUN apt-get update && \ - apt-get install -y curl build-essential git openssl pkg-config libssl-dev libgmp3-dev + apt-get install -y curl build-essential openssl pkg-config libssl-dev libgmp3-dev RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-08-28 -y ENV PATH=/root/.cargo/bin:$PATH