Fixed eslint error/warnings

This commit is contained in:
Kirill Fedoseev 2019-11-01 21:43:25 +03:00
parent 34d6927c0b
commit 62c1dd8064
41 changed files with 1047 additions and 840 deletions

View File

@ -1,20 +1,20 @@
const Bridge = artifacts.require('Bridge') const Bridge = artifacts.require('Bridge')
const addresses = Object.entries(process.env) const addresses = Object.entries(process.env)
.filter(([ key ]) => key.startsWith('VALIDATOR_ADDRESS')) .filter(([key]) => key.startsWith('VALIDATOR_ADDRESS'))
.map(([ , value ]) => value) .map(([, value]) => value)
const { const {
THRESHOLD, HOME_TOKEN_ADDRESS, MIN_TX_LIMIT, MAX_TX_LIMIT, BLOCKS_RANGE_SIZE THRESHOLD, HOME_TOKEN_ADDRESS, MIN_TX_LIMIT, MAX_TX_LIMIT, BLOCKS_RANGE_SIZE
} = process.env } = process.env
module.exports = deployer => { module.exports = (deployer) => {
deployer.deploy( deployer.deploy(
Bridge, Bridge,
THRESHOLD, THRESHOLD,
addresses, addresses,
HOME_TOKEN_ADDRESS, HOME_TOKEN_ADDRESS,
[ MIN_TX_LIMIT, MAX_TX_LIMIT ], [MIN_TX_LIMIT, MAX_TX_LIMIT],
BLOCKS_RANGE_SIZE BLOCKS_RANGE_SIZE
) )
} }

View File

@ -1,5 +1,5 @@
const SharedDB = artifacts.require('SharedDB') const SharedDB = artifacts.require('SharedDB')
module.exports = deployer => { module.exports = (deployer) => {
deployer.deploy(SharedDB) deployer.deploy(SharedDB)
} }

View File

@ -9,70 +9,50 @@ const { publicKeyToAddress } = require('./crypto')
const { FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env const { FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME) const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME, 10)
const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL) 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) const FOREIGN_FETCH_BLOCK_TIME_OFFSET = parseInt(process.env.FOREIGN_FETCH_BLOCK_TIME_OFFSET, 10)
const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL }) const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL })
const proxyHttpClient = axios.create({ baseURL: PROXY_URL }) const proxyHttpClient = axios.create({ baseURL: PROXY_URL })
async function initialize () { function getLastForeignAddress() {
if (await redis.get('foreignTime') === null) { const epoch = Math.max(0, ...fs.readdirSync('/keys')
logger.info('Set default foreign time') .map((x) => parseInt(x.split('.')[0].substr(4), 10)))
await redis.set('foreignTime', FOREIGN_START_TIME) 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 () { function getTx(hash) {
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) {
return foreignHttpClient return foreignHttpClient
.get(`/api/v1/tx/${hash}`, { .get(`/api/v1/tx/${hash}`, {
params: { params: {
format: 'json' format: 'json'
} }
}) })
.then(res => res.data.tx.value) .then((res) => res.data.tx.value)
.catch(() => getTx(hash)) .catch(() => getTx(hash))
} }
function getBlockTime () { function getBlockTime() {
return foreignHttpClient return foreignHttpClient
.get(`/api/v1/time`) .get('/api/v1/time')
.then(res => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET) .then((res) => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET)
.catch(() => getBlockTime()) .catch(() => getBlockTime())
} }
async function fetchNewTransactions () { async function fetchNewTransactions() {
logger.debug('Fetching new transactions') 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 address = getLastForeignAddress()
const endTime = Math.min(startTime + 3 * 30 * 24 * 60 * 60 * 1000, await getBlockTime()) const endTime = Math.min(startTime + 3 * 30 * 24 * 60 * 60 * 1000, await getBlockTime())
if (address === null) if (address === null) {
return {} return {}
}
logger.debug('Sending api transactions request') logger.debug('Sending api transactions request')
const params = { const params = {
address, address,
@ -80,29 +60,58 @@ async function fetchNewTransactions () {
txAsset: FOREIGN_ASSET, txAsset: FOREIGN_ASSET,
txType: 'TRANSFER', txType: 'TRANSFER',
startTime, startTime,
endTime, endTime
} }
try { try {
logger.trace('%o', params) logger.trace('%o', params)
const transactions = (await foreignHttpClient const transactions = (await foreignHttpClient
.get('/api/v1/transactions', { params })).data.tx .get('/api/v1/transactions', { params })).data.tx
return { transactions, endTime } return {
transactions,
endTime
}
} catch (e) { } catch (e) {
return await fetchNewTransactions() return await fetchNewTransactions()
} }
} }
function getLastForeignAddress () { async function initialize() {
const epoch = Math.max(0, ...fs.readdirSync('/keys').map(x => parseInt(x.split('.')[0].substr(4)))) if (await redis.get('foreignTime') === null) {
if (epoch === 0) logger.info('Set default foreign time')
return null await redis.set('foreignTime', FOREIGN_START_TIME)
const keysFile = `/keys/keys${epoch}.store` }
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
return publicKeyToAddress(publicKey)
} }
initialize().then(async () => { async function main() {
while (true) { const { transactions, endTime } = await fetchNewTransactions()
await main() 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()
}
})

View File

@ -9,6 +9,9 @@
"ethers": "4.0.33", "ethers": "4.0.33",
"pino": "5.13.4", "pino": "5.13.4",
"pino-pretty": "3.2.1" "pino-pretty": "3.2.1"
},
"engines": {
"node": ">=10.6.0"
} }
} }

View File

@ -9,10 +9,8 @@ services:
- HOME_RPC_URL - HOME_RPC_URL
- HOME_BRIDGE_ADDRESS - HOME_BRIDGE_ADDRESS
- HOME_TOKEN_ADDRESS - HOME_TOKEN_ADDRESS
- HOME_CHAIN_ID
- SIDE_RPC_URL - SIDE_RPC_URL
- SIDE_SHARED_DB_ADDRESS - SIDE_SHARED_DB_ADDRESS
- SIDE_CHAIN_ID
- VALIDATOR_PRIVATE_KEY - VALIDATOR_PRIVATE_KEY
- FOREIGN_URL - FOREIGN_URL
- FOREIGN_ASSET - FOREIGN_ASSET
@ -83,7 +81,6 @@ services:
- HOME_RPC_URL - HOME_RPC_URL
- HOME_BRIDGE_ADDRESS - HOME_BRIDGE_ADDRESS
- HOME_TOKEN_ADDRESS - HOME_TOKEN_ADDRESS
- HOME_CHAIN_ID
- HOME_START_BLOCK - HOME_START_BLOCK
- BLOCKS_RANGE_SIZE - BLOCKS_RANGE_SIZE
- VALIDATOR_PRIVATE_KEY - VALIDATOR_PRIVATE_KEY

View File

@ -9,10 +9,8 @@ services:
- HOME_RPC_URL - HOME_RPC_URL
- HOME_BRIDGE_ADDRESS - HOME_BRIDGE_ADDRESS
- HOME_TOKEN_ADDRESS - HOME_TOKEN_ADDRESS
- HOME_CHAIN_ID
- SIDE_RPC_URL - SIDE_RPC_URL
- SIDE_SHARED_DB_ADDRESS - SIDE_SHARED_DB_ADDRESS
- SIDE_CHAIN_ID
- VALIDATOR_PRIVATE_KEY - VALIDATOR_PRIVATE_KEY
- FOREIGN_URL - FOREIGN_URL
- FOREIGN_ASSET - FOREIGN_ASSET
@ -93,7 +91,6 @@ services:
- HOME_RPC_URL - HOME_RPC_URL
- HOME_BRIDGE_ADDRESS - HOME_BRIDGE_ADDRESS
- HOME_TOKEN_ADDRESS - HOME_TOKEN_ADDRESS
- HOME_CHAIN_ID
- HOME_START_BLOCK - HOME_START_BLOCK
- VALIDATOR_PRIVATE_KEY - VALIDATOR_PRIVATE_KEY
- 'RABBITMQ_URL=amqp://rabbitmq:5672' - 'RABBITMQ_URL=amqp://rabbitmq:5672'

View File

@ -1,5 +1,5 @@
const Web3 = require('web3') const Web3 = require('web3')
const utils = require('ethers').utils const { utils } = require('ethers')
const BN = require('bignumber.js') const BN = require('bignumber.js')
const axios = require('axios') const axios = require('axios')
@ -10,19 +10,21 @@ const { publicKeyToAddress } = require('./crypto')
const abiBridge = require('./contracts_data/Bridge.json').abi 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 homeWeb3 = new Web3(HOME_RPC_URL)
const bridge = new homeWeb3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS) const bridge = new homeWeb3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
const foreignNonce = []
let channel let channel
let exchangeQueue let exchangeQueue
let signQueue let signQueue
let keygenQueue let keygenQueue
let cancelKeygenQueue let cancelKeygenQueue
let blockNumber let blockNumber
let foreignNonce = []
let epoch let epoch
let epochStart let epochStart
let redisTx let redisTx
@ -30,16 +32,17 @@ let rangeSize
let lastTransactionBlockNumber let lastTransactionBlockNumber
let isCurrentValidator let isCurrentValidator
async function resetFutureMessages (queue) { async function resetFutureMessages(queue) {
logger.debug(`Resetting future messages in queue ${queue.name}`) logger.debug(`Resetting future messages in queue ${queue.name}`)
const { messageCount } = await channel.checkQueue(queue.name) const { messageCount } = await channel.checkQueue(queue.name)
if (messageCount) { if (messageCount) {
logger.info(`Filtering ${messageCount} reloaded messages from queue ${queue.name}`) logger.info(`Filtering ${messageCount} reloaded messages from queue ${queue.name}`)
const backup = await assertQueue(channel, `${queue.name}.backup`) const backup = await assertQueue(channel, `${queue.name}.backup`)
do { while (true) {
const message = await queue.get() const message = await queue.get()
if (message === false) if (message === false) {
break break
}
const data = JSON.parse(message.content) const data = JSON.parse(message.content)
if (data.blockNumber < blockNumber) { if (data.blockNumber < blockNumber) {
logger.debug('Saving message %o', data) logger.debug('Saving message %o', data)
@ -48,25 +51,134 @@ async function resetFutureMessages (queue) {
logger.debug('Dropping message %o', data) logger.debug('Dropping message %o', data)
} }
channel.ack(message) channel.ack(message)
} while (true) }
logger.debug('Dropped messages came from future') logger.debug('Dropped messages came from future')
do { while (true) {
const message = await backup.get() const message = await backup.get()
if (message === false) if (message === false) {
break break
}
const data = JSON.parse(message.content) const data = JSON.parse(message.content)
logger.debug('Requeuing message %o', data) logger.debug('Requeuing message %o', data)
queue.send(data) queue.send(data)
channel.ack(message) channel.ack(message)
} while (true) }
logger.debug('Redirected messages back to initial queue') 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) channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue') exchangeQueue = await assertQueue(channel, 'exchangeQueue')
signQueue = await assertQueue(channel, 'signQueue') signQueue = await assertQueue(channel, 'signQueue')
@ -79,11 +191,12 @@ async function initialize () {
epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0 epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0
logger.info(`Current epoch ${epoch}`) logger.info(`Current epoch ${epoch}`)
epochStart = events.length ? events[events.length - 1].blockNumber : 1 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) { if (epochStart > saved) {
logger.info(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`) logger.info(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`)
blockNumber = epochStart blockNumber = epochStart
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber() rangeSize = (await bridge.methods.getRangeSize()
.call()).toNumber()
await redis.multi() await redis.multi()
.set('homeBlock', blockNumber - 1) .set('homeBlock', blockNumber - 1)
.set(`foreignNonce${epoch}`, 0) .set(`foreignNonce${epoch}`, 0)
@ -92,10 +205,11 @@ async function initialize () {
} else { } else {
logger.info('Restoring epoch and block number from local db') logger.info('Restoring epoch and block number from local db')
blockNumber = saved 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') logger.debug('Checking if current validator')
isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress) isCurrentValidator = (await bridge.methods.getValidators()
.call()).includes(validatorAddress)
if (isCurrentValidator) { if (isCurrentValidator) {
logger.info(`${validatorAddress} is a current validator`) logger.info(`${validatorAddress} is a current validator`)
} else { } else {
@ -106,16 +220,16 @@ async function initialize () {
await resetFutureMessages(cancelKeygenQueue) await resetFutureMessages(cancelKeygenQueue)
await resetFutureMessages(exchangeQueue) await resetFutureMessages(exchangeQueue)
await resetFutureMessages(signQueue) await resetFutureMessages(signQueue)
logger.debug(`Sending start commands`) logger.debug('Sending start commands')
await axios.get('http://keygen:8001/start') await axios.get('http://keygen:8001/start')
await axios.get('http://signer:8001/start') await axios.get('http://signer:8001/start')
} }
async function main () { async function main() {
logger.debug(`Watching events in block #${blockNumber}`) logger.debug(`Watching events in block #${blockNumber}`)
if (await homeWeb3.eth.getBlock(blockNumber) === null) { if (await homeWeb3.eth.getBlock(blockNumber) === null) {
logger.debug('No block') logger.debug('No block')
await new Promise(r => setTimeout(r, 1000)) await new Promise((r) => setTimeout(r, 1000))
return return
} }
@ -126,7 +240,8 @@ async function main () {
toBlock: blockNumber toBlock: blockNumber
}) })
for (const event of bridgeEvents) { for (let i = 0; i < bridgeEvents.length; i += 1) {
const event = bridgeEvents[i]
switch (event.event) { switch (event.event) {
case 'NewEpoch': case 'NewEpoch':
await sendKeygen(event) await sendKeygen(event)
@ -135,14 +250,20 @@ async function main () {
sendKeygenCancellation(event) sendKeygenCancellation(event)
break break
case 'NewFundsTransfer': case 'NewFundsTransfer':
isCurrentValidator && await sendSignFundsTransfer(event) if (isCurrentValidator) {
await sendSignFundsTransfer(event)
}
break break
case 'ExchangeRequest': case 'ExchangeRequest':
isCurrentValidator && await sendSign(event) if (isCurrentValidator) {
await sendSign(event)
}
break break
case 'EpochStart': case 'EpochStart':
await processEpochStart(event) await processEpochStart(event)
break break
default:
logger.warn('Unknown event %o', event)
} }
} }
@ -155,108 +276,16 @@ async function main () {
} }
} }
blockNumber++ blockNumber += 1
// Exec redis tx // Exec redis tx
await redisTx.incr('homeBlock').exec() await redisTx.incr('homeBlock')
.exec()
await redis.save() await redis.save()
} }
initialize().then(async () => { initialize()
while (true) { .then(async () => {
await main() while (true) {
} await main()
}, e => logger.warn('Initialization failed %o', e)) }
}, (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
}

View File

@ -11,5 +11,8 @@
"pino": "5.13.4", "pino": "5.13.4",
"pino-pretty": "3.2.1", "pino-pretty": "3.2.1",
"axios": "0.19.0" "axios": "0.19.0"
},
"engines": {
"node": ">=10.6.0"
} }
} }

View File

@ -1,18 +1,19 @@
const BN = require('bn.js') const BN = require('bn.js')
function Tokenizer (_buffer) { function Tokenizer(_buffer) {
const buffer = _buffer const buffer = _buffer
let position = 0 let position = 0
return { return {
isEmpty: function () { isEmpty() {
return position === buffer.length 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) const res = new BN(buffer.slice(position, position + length)).toString(base)
position += length position += length
return res return res
}, },
byte: function () { byte() {
// eslint-disable-next-line no-plusplus
return buffer[position++] return buffer[position++]
} }
} }
@ -21,7 +22,7 @@ function Tokenizer (_buffer) {
const keygenDecoders = [ const keygenDecoders = [
null, null,
// round 1 // round 1
function (tokenizer) { (tokenizer) => {
const res = { const res = {
e: { e: {
n: tokenizer.parse(256, 10) n: tokenizer.parse(256, 10)
@ -37,23 +38,21 @@ const keygenDecoders = [
return res return res
}, },
// round 2 // round 2
function (tokenizer) { (tokenizer) => ({
return { blind_factor: tokenizer.parse(),
blind_factor: tokenizer.parse(), y_i: {
y_i: { x: tokenizer.parse(),
x: tokenizer.parse(), y: tokenizer.parse()
y: tokenizer.parse()
}
} }
}, }),
// round 3 // round 3
function (tokenizer) { (tokenizer) => {
const res = { const res = {
ciphertext: [], ciphertext: [],
tag: [] tag: []
} }
const ciphertextLength = tokenizer.byte() // probably 32 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()) res.ciphertext.push(tokenizer.byte())
} }
while (!tokenizer.isEmpty()) { while (!tokenizer.isEmpty()) {
@ -62,7 +61,7 @@ const keygenDecoders = [
return res return res
}, },
// round 4 // round 4
function (tokenizer) { (tokenizer) => {
const res = { const res = {
parameters: { parameters: {
threshold: tokenizer.byte(), threshold: tokenizer.byte(),
@ -73,157 +72,139 @@ const keygenDecoders = [
while (!tokenizer.isEmpty()) { while (!tokenizer.isEmpty()) {
res.commitments.push({ res.commitments.push({
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse(), y: tokenizer.parse()
}) })
} }
return res return res
}, },
// round 5 // round 5
function (tokenizer) { (tokenizer) => ({
return { pk: {
pk: { x: tokenizer.parse(),
x: tokenizer.parse(), y: tokenizer.parse()
y: tokenizer.parse() },
}, pk_t_rand_commitment: {
pk_t_rand_commitment: { x: tokenizer.parse(),
x: tokenizer.parse(), y: tokenizer.parse()
y: tokenizer.parse() },
}, challenge_response: tokenizer.parse()
challenge_response: tokenizer.parse() })
}
}
] ]
const signDecoders = [ const signDecoders = [
// round 0 // round 0
function (tokenizer) { (tokenizer) => tokenizer.byte(),
return tokenizer.byte()
},
// round 1 // round 1
function (tokenizer) { (tokenizer) => [
return [ {
{ com: tokenizer.parse()
com: tokenizer.parse() },
}, {
{ c: tokenizer.parse(512)
c: tokenizer.parse(512) }
} ],
]
},
// round 2 // round 2
function (tokenizer) { (tokenizer) => {
const res = [] const res = []
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i += 1) {
res[i] = { res[i] = {
c: tokenizer.parse(512), c: tokenizer.parse(512),
b_proof: { b_proof: {
pk: { pk: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse(), y: tokenizer.parse()
}, },
pk_t_rand_commitment: { pk_t_rand_commitment: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse(), y: tokenizer.parse()
}, },
challenge_response: tokenizer.parse(), challenge_response: tokenizer.parse()
}, },
beta_tag_proof: { beta_tag_proof: {
pk: { pk: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse(), y: tokenizer.parse()
}, },
pk_t_rand_commitment: { pk_t_rand_commitment: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse(), y: tokenizer.parse()
}, },
challenge_response: tokenizer.parse(), challenge_response: tokenizer.parse()
} }
} }
} }
return res return res
}, },
// round 3 // round 3
function (tokenizer) { (tokenizer) => tokenizer.parse(),
return tokenizer.parse()
},
// round 4 // round 4
function (tokenizer) { (tokenizer) => ({
return { blind_factor: tokenizer.parse(),
blind_factor: tokenizer.parse(), g_gamma_i: {
g_gamma_i: { x: tokenizer.parse(),
x: tokenizer.parse(), y: tokenizer.parse()
y: tokenizer.parse()
}
} }
}, }),
// round 5 // round 5
function (tokenizer) { (tokenizer) => ({
return { com: tokenizer.parse()
com: tokenizer.parse() }),
}
},
// round 6 // round 6
function (tokenizer) { (tokenizer) => [
return [ {
{ V_i: {
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: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse() y: tokenizer.parse()
}, },
t_i: { A_i: {
x: tokenizer.parse(),
y: tokenizer.parse()
},
B_i: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse() y: tokenizer.parse()
}, },
blind_factor: 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 // round 9
function (tokenizer) { (tokenizer) => tokenizer.parse()
return tokenizer.parse()
},
] ]
module.exports = function (isKeygen, round, value) { function decode(isKeygen, round, value) {
value = Buffer.from(value.substr(2), 'hex') const newValue = Buffer.from(value.substr(2), 'hex')
const tokenizer = Tokenizer(value) const tokenizer = Tokenizer(newValue)
const roundNumber = parseInt(round[round.length - 1]) const roundNumber = parseInt(round[round.length - 1], 10)
const decoder = (isKeygen ? keygenDecoders : signDecoders)[roundNumber] const decoder = (isKeygen ? keygenDecoders : signDecoders)[roundNumber]
return JSON.stringify(decoder(tokenizer)) return JSON.stringify(decoder(tokenizer))
} }
module.exports = decode

View File

@ -2,43 +2,44 @@ const BN = require('bignumber.js')
const { padZeros } = require('./crypto') 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') return Buffer.from(padZeros(new BN(value, base).toString(16), length * 2), 'hex')
} }
const keygenEncoders = [ const keygenEncoders = [
null, null,
// round 1 // round 1
function * (value) { function* g(value) {
yield makeBuffer(value.e.n, 256, 10) yield makeBuffer(value.e.n, 256, 10)
yield makeBuffer(value.com) yield makeBuffer(value.com)
for (let x of value.correct_key_proof.sigma_vec) { for (let i = 0; i < value.correct_key_proof.sigma_vec.length; i += 1) {
yield makeBuffer(x, 256, 10) yield makeBuffer(value.correct_key_proof.sigma_vecp[i], 256, 10)
} }
}, },
// round 2 // round 2
function * (value) { function* g(value) {
yield makeBuffer(value.blind_factor) yield makeBuffer(value.blind_factor)
yield makeBuffer(value.y_i.x) yield makeBuffer(value.y_i.x)
yield makeBuffer(value.y_i.y) yield makeBuffer(value.y_i.y)
}, },
// round 3 // round 3
function * (value) { function* g(value) {
yield Buffer.from([ value.ciphertext.length ]) yield Buffer.from([value.ciphertext.length])
yield Buffer.from(value.ciphertext) // 32 bytes or less yield Buffer.from(value.ciphertext) // 32 bytes or less
yield Buffer.from(value.tag) // 16 bytes or less yield Buffer.from(value.tag) // 16 bytes or less
}, },
// round 4 // round 4
function * (value) { function* g(value) {
yield Buffer.from([ value.parameters.threshold ]) // 1 byte yield Buffer.from([value.parameters.threshold]) // 1 byte
yield Buffer.from([ value.parameters.share_count ]) // 1 byte yield Buffer.from([value.parameters.share_count]) // 1 byte
for (let x of value.commitments) { 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.x)
yield makeBuffer(x.y) yield makeBuffer(x.y)
} }
}, },
// round 5 // round 5
function * (value) { function* g(value) {
yield makeBuffer(value.pk.x) yield makeBuffer(value.pk.x)
yield makeBuffer(value.pk.y) yield makeBuffer(value.pk.y)
yield makeBuffer(value.pk_t_rand_commitment.x) yield makeBuffer(value.pk_t_rand_commitment.x)
@ -49,17 +50,17 @@ const keygenEncoders = [
const signEncoders = [ const signEncoders = [
// round 0 // round 0
function * (value) { function* g(value) {
yield Buffer.from([ value ]) yield Buffer.from([value])
}, },
// round 1 // round 1
function * (value) { function* g(value) {
yield makeBuffer(value[0].com) yield makeBuffer(value[0].com)
yield makeBuffer(value[1].c, 512) yield makeBuffer(value[1].c, 512)
}, },
// round 2 // round 2
function * (value) { function* g(value) {
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i += 1) {
yield makeBuffer(value[i].c, 512) yield makeBuffer(value[i].c, 512)
yield makeBuffer(value[i].b_proof.pk.x) yield makeBuffer(value[i].b_proof.pk.x)
yield makeBuffer(value[i].b_proof.pk.y) yield makeBuffer(value[i].b_proof.pk.y)
@ -74,21 +75,21 @@ const signEncoders = [
} }
}, },
// round 3 // round 3
function * (value) { function* g(value) {
yield makeBuffer(value) yield makeBuffer(value)
}, },
// round 4 // round 4
function * (value) { function* g(value) {
yield makeBuffer(value.blind_factor) yield makeBuffer(value.blind_factor)
yield makeBuffer(value.g_gamma_i.x) yield makeBuffer(value.g_gamma_i.x)
yield makeBuffer(value.g_gamma_i.y) yield makeBuffer(value.g_gamma_i.y)
}, },
// round 5 // round 5
function * (value) { function* g(value) {
yield makeBuffer(value.com) yield makeBuffer(value.com)
}, },
// round 6 // round 6
function * (value) { function* g(value) {
yield makeBuffer(value[0].V_i.x) yield makeBuffer(value[0].V_i.x)
yield makeBuffer(value[0].V_i.y) yield makeBuffer(value[0].V_i.y)
yield makeBuffer(value[0].A_i.x) yield makeBuffer(value[0].A_i.x)
@ -104,11 +105,11 @@ const signEncoders = [
yield makeBuffer(value[1].z2) yield makeBuffer(value[1].z2)
}, },
// round 7 // round 7
function * (value) { function* g(value) {
yield makeBuffer(value.com) yield makeBuffer(value.com)
}, },
// round 8 // round 8
function * (value) { function* g(value) {
yield makeBuffer(value.u_i.x) yield makeBuffer(value.u_i.x)
yield makeBuffer(value.u_i.y) yield makeBuffer(value.u_i.y)
yield makeBuffer(value.t_i.x) yield makeBuffer(value.t_i.x)
@ -116,23 +117,23 @@ const signEncoders = [
yield makeBuffer(value.blind_factor) yield makeBuffer(value.blind_factor)
}, },
// round 9 // round 9
function * (value) { function* g(value) {
yield makeBuffer(value) yield makeBuffer(value)
}, }
] ]
module.exports = function (isKeygen, round, value) { function encode(isKeygen, round, value) {
const parsedValue = JSON.parse(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 encoder = (isKeygen ? keygenEncoders : signEncoders)[roundNumber]
const generator = encoder(parsedValue) const generator = encoder(parsedValue)
const buffers = [] const buffers = []
let next let next = generator.next()
while (true) { while (!next.done) {
next = generator.next()
if (next.done)
break
buffers.push(next.value) buffers.push(next.value)
next = generator.next()
} }
return Buffer.concat(buffers) return Buffer.concat(buffers)
} }
module.exports = encode

View File

@ -12,8 +12,8 @@ const logger = require('./logger')
const { publicKeyToAddress } = require('./crypto') const { publicKeyToAddress } = require('./crypto')
const { const {
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID, HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY,
SIDE_CHAIN_ID, HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET
} = process.env } = process.env
const abiSharedDb = require('./contracts_data/SharedDB.json').abi const abiSharedDb = require('./contracts_data/SharedDB.json').abi
const abiBridge = require('./contracts_data/Bridge.json').abi const abiBridge = require('./contracts_data/Bridge.json').abi
@ -39,28 +39,9 @@ const app = express()
app.use(express.json()) app.use(express.json())
app.use(express.urlencoded({ extended: true })) 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() const votesProxyApp = express()
votesProxyApp.use(express.json())
votesProxyApp.use(express.urlencoded({ extended: true }))
votesProxyApp.get('/vote/startVoting', voteStartVoting) async function main() {
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 () {
homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress) homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress)
sideValidatorNonce = await sideWeb3.eth.getTransactionCount(validatorAddress) sideValidatorNonce = await sideWeb3.eth.getTransactionCount(validatorAddress)
@ -80,35 +61,77 @@ async function main () {
main() main()
function Ok (data) { function Ok(data) {
return { Ok: data } return { Ok: data }
} }
function Err (data) { function Err(data) {
return { 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) logger.debug('Get call, %o', req.body.key)
const round = req.body.key.second const round = req.body.key.second
const uuid = req.body.key.third const uuid = req.body.key.third
let from let from
if (uuid.startsWith('k')) if (uuid.startsWith('k')) {
from = (await bridge.methods.getNextValidators().call())[parseInt(req.body.key.first) - 1] from = (await bridge.methods.getNextValidators()
else { .call())[parseInt(req.body.key.first, 10) - 1]
const validators = await bridge.methods.getValidators().call() } else {
from = await sharedDb.methods.getSignupAddress(uuid, validators, parseInt(req.body.key.first)).call() 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 to = Number(req.body.key.fourth) // 0 if empty
const key = homeWeb3.utils.sha3(`${round}_${to}`) 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) { if (data.length > 2) {
logger.trace(`Received encoded data: ${data}`) logger.trace(`Received encoded data: ${data}`)
const decoded = decode(uuid[0] === 'k', round, data) const decoded = decode(uuid[0] === 'k', round, data)
logger.trace('Decoded data: %o', decoded) 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 { } else {
setTimeout(() => res.send(Err(null)), 1000) setTimeout(() => res.send(Err(null)), 1000)
} }
@ -116,7 +139,7 @@ async function get (req, res) {
logger.debug('Get end') logger.debug('Get end')
} }
async function set (req, res) { async function set(req, res) {
logger.debug('Set call') logger.debug('Set call')
const round = req.body.key.second const round = req.body.key.second
const uuid = req.body.key.third const uuid = req.body.key.third
@ -134,21 +157,26 @@ async function set (req, res) {
logger.debug('Set end') logger.debug('Set end')
} }
async function signupKeygen (req, res) { async function signupKeygen(req, res) {
logger.debug('SignupKeygen call') logger.debug('SignupKeygen call')
const epoch = (await bridge.methods.nextEpoch().call()).toNumber() const epoch = (await bridge.methods.nextEpoch()
const partyId = (await bridge.methods.getNextPartyId(validatorAddress).call()).toNumber() .call()).toNumber()
const partyId = (await bridge.methods.getNextPartyId(validatorAddress)
.call()).toNumber()
if (partyId === 0) { if (partyId === 0) {
res.send(Err({ message: 'Not a validator' })) res.send(Err({ message: 'Not a validator' }))
logger.debug('Not a validator') logger.debug('Not a validator')
} else { } else {
res.send(Ok({ uuid: `k${epoch}`, number: partyId })) res.send(Ok({
uuid: `k${epoch}`,
number: partyId
}))
logger.debug('SignupKeygen end') logger.debug('SignupKeygen end')
} }
} }
async function signupSign (req, res) { async function signupSign(req, res) {
logger.debug('SignupSign call') logger.debug('SignupSign call')
const hash = sideWeb3.utils.sha3(`0x${req.body.third}`) const hash = sideWeb3.utils.sha3(`0x${req.body.third}`)
const query = sharedDb.methods.signupSign(hash) const query = sharedDb.methods.signupSign(hash)
@ -157,19 +185,27 @@ async function signupSign (req, res) {
// Already have signup // Already have signup
if (receipt.status === false) { if (receipt.status === false) {
res.send(Ok({ uuid: hash, number: 0 })) res.send(Ok({
uuid: hash,
number: 0
}))
logger.debug('Already have signup') logger.debug('Already have signup')
return return
} }
const validators = await bridge.methods.getValidators().call() const validators = await bridge.methods.getValidators()
const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress).call()).toNumber() .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') logger.debug('SignupSign end')
} }
async function confirmKeygen (req, res) { async function confirmKeygen(req, res) {
logger.debug('Confirm keygen call') logger.debug('Confirm keygen call')
const { x, y } = req.body[5] const { x, y } = req.body[5]
const query = bridge.methods.confirmKeygen(`0x${x}`, `0x${y}`) const query = bridge.methods.confirmKeygen(`0x${x}`, `0x${y}`)
@ -178,7 +214,7 @@ async function confirmKeygen (req, res) {
logger.debug('Confirm keygen end') logger.debug('Confirm keygen end')
} }
async function confirmFundsTransfer (req, res) { async function confirmFundsTransfer(req, res) {
logger.debug('Confirm funds transfer call') logger.debug('Confirm funds transfer call')
const query = bridge.methods.confirmFundsTransfer() const query = bridge.methods.confirmFundsTransfer()
await homeSendQuery(query) await homeSendQuery(query)
@ -186,49 +222,7 @@ async function confirmFundsTransfer (req, res) {
logger.debug('Confirm funds transfer end') logger.debug('Confirm funds transfer end')
} }
function sideSendQuery (query) { async function sendVote(query, req, res, waitFlag = false) {
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) {
try { try {
const sentQuery = await homeSendQuery(query) const sentQuery = await homeSendQuery(query)
let { txHash, gasLimit } = sentQuery 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') logger.info('Voting for starting new epoch voting process')
const query = bridge.methods.startVoting() const query = bridge.methods.startVoting()
sendVote(query, req, res, true) sendVote(query, req, res, true)
} }
async function voteStartKeygen (req, res) { async function voteStartKeygen(req, res) {
logger.info('Voting for starting new epoch keygen') logger.info('Voting for starting new epoch keygen')
const query = bridge.methods.voteStartKeygen() const query = bridge.methods.voteStartKeygen()
sendVote(query, req, res) sendVote(query, req, res)
} }
async function voteCancelKeygen (req, res) { async function voteCancelKeygen(req, res) {
logger.info('Voting for cancelling new epoch keygen') logger.info('Voting for cancelling new epoch keygen')
const query = bridge.methods.voteCancelKeygen() const query = bridge.methods.voteCancelKeygen()
sendVote(query, req, res) sendVote(query, req, res)
} }
async function voteAddValidator (req, res) { async function voteAddValidator(req, res) {
logger.info('Voting for adding new validator') logger.info('Voting for adding new validator')
const query = bridge.methods.voteAddValidator(req.params.validator) const query = bridge.methods.voteAddValidator(req.params.validator)
sendVote(query, req, res) sendVote(query, req, res)
} }
async function voteChangeThreshold (req, res) { async function voteChangeThreshold(req, res) {
logger.info('Voting for changing threshold') logger.info('Voting for changing threshold')
const query = bridge.methods.voteChangeThreshold(req.params.threshold) const query = bridge.methods.voteChangeThreshold(req.params.threshold)
sendVote(query, req, res) sendVote(query, req, res)
} }
async function voteRemoveValidator (req, res) { async function voteRemoveValidator(req, res) {
logger.info('Voting for removing validator') logger.info('Voting for removing validator')
const query = bridge.methods.voteRemoveValidator(req.params.validator) const query = bridge.methods.voteRemoveValidator(req.params.validator)
sendVote(query, req, res, true) sendVote(query, req, res, true)
} }
function decodeStatus (status) { function decodeStatus(status) {
switch (status) { switch (status) {
case 0: case 0:
return 'ready' return 'ready'
@ -308,10 +302,12 @@ function decodeStatus (status) {
return 'keygen' return 'keygen'
case 3: case 3:
return 'funds_transfer' return 'funds_transfer'
default:
return 'unknown_state'
} }
} }
function boundX (x) { function boundX(x) {
try { try {
return x.toNumber() return x.toNumber()
} catch (e) { } 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') logger.debug('Info start')
try { try {
const [ x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch, threshold, nextThreshold, validators, nextValidators, status, homeBalance ] = await Promise.all([ const [
bridge.methods.getX().call().then(x => new BN(x).toString(16)), x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch,
bridge.methods.getY().call().then(x => new BN(x).toString(16)), threshold, nextThreshold, validators, nextValidators, status, homeBalance
bridge.methods.epoch().call().then(x => x.toNumber()), ] = await Promise.all([
bridge.methods.getRangeSize().call().then(x => x.toNumber()), bridge.methods.getX()
bridge.methods.getNextRangeSize().call().then(x => x.toNumber()), .call()
bridge.methods.getStartBlock().call().then(x => x.toNumber()), .then((value) => new BN(value).toString(16)),
bridge.methods.getNonce().call().then(boundX), bridge.methods.getY()
bridge.methods.nextEpoch().call().then(x => x.toNumber()), .call()
bridge.methods.getThreshold().call().then(x => x.toNumber()), .then((value) => new BN(value).toString(16)),
bridge.methods.getNextThreshold().call().then(x => x.toNumber()), bridge.methods.epoch()
bridge.methods.getValidators().call(), .call()
bridge.methods.getNextValidators().call(), .then(toNumber),
bridge.methods.status().call(), bridge.methods.getRangeSize()
token.methods.balanceOf(HOME_BRIDGE_ADDRESS).call().then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3))) .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([ const [
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 1, nextEpoch ]))).call().then(boundX), confirmationsForFundsTransfer, votesForVoting, votesForKeygen, votesForCancelKeygen
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 2, nextEpoch ]))).call().then(boundX), ] = await Promise.all([
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'], [1, nextEpoch])))
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 8, nextEpoch ]))).call().then(boundX) .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 balances = await getForeignBalances(foreignAddress)
const msg = { const msg = {
epoch, epoch,
@ -361,7 +426,7 @@ async function info (req, res) {
nextValidators, nextValidators,
homeBalance, homeBalance,
foreignBalanceTokens: parseFloat(balances[FOREIGN_ASSET]) || 0, foreignBalanceTokens: parseFloat(balances[FOREIGN_ASSET]) || 0,
foreignBalanceNative: parseFloat(balances['BNB']) || 0, foreignBalanceNative: parseFloat(balances.BNB) || 0,
bridgeStatus: decodeStatus(status), bridgeStatus: decodeStatus(status),
votesForVoting, votesForVoting,
votesForKeygen, votesForKeygen,
@ -372,29 +437,27 @@ async function info (req, res) {
res.send(msg) res.send(msg)
} catch (e) { } catch (e) {
logger.debug('%o', 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') logger.debug('Info end')
} }
async function transfer (req, res) { app.post('/get', get)
logger.info('Transfer start') app.post('/set', set)
const { hash, to, value } = req.body app.post('/signupkeygen', signupKeygen)
if (homeWeb3.utils.isAddress(to)) { app.post('/signupsign', signupSign)
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) { app.post('/confirmKeygen', confirmKeygen)
return httpClient app.post('/confirmFundsTransfer', confirmFundsTransfer)
.get(`/api/v1/account/${address}`) app.post('/transfer', transfer)
.then(res => res.data.balances.reduce((prev, cur) => {
prev[cur.symbol] = cur.free votesProxyApp.get('/vote/startVoting', voteStartVoting)
return prev votesProxyApp.get('/vote/startKeygen', voteStartKeygen)
}, {})) votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen)
.catch(err => ({})) votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
} votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold)
votesProxyApp.get('/info', info)

View File

@ -12,5 +12,8 @@
"ethers": "4.0.37", "ethers": "4.0.37",
"pino": "5.13.4", "pino": "5.13.4",
"pino-pretty": "3.2.1" "pino-pretty": "3.2.1"
},
"engines": {
"node": ">=10.6.0"
} }
} }

View File

@ -7,28 +7,28 @@ const logger = require('./logger')
const { GAS_LIMIT_FACTOR, MAX_GAS_LIMIT } = process.env const { GAS_LIMIT_FACTOR, MAX_GAS_LIMIT } = process.env
function sendRpcRequest (url, method, params) { function sendRpcRequest(url, method, params) {
return axios.post(url, { return axios.post(url, {
jsonrpc: '2.0', jsonrpc: '2.0',
method, method,
params, params,
id: 1 id: 1
}) })
.then(res => res.data) .then((res) => res.data)
.catch(async e => { .catch(async () => {
logger.warn(`Request to ${url}, method ${method} failed, retrying`) 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) return sendRpcRequest(url, method, params)
}) })
} }
async function createSender (url, privateKey) { async function createSender(url, privateKey) {
const web3 = new Web3(url, null, { transactionConfirmationBlocks: 1 }) const web3 = new Web3(url, null, { transactionConfirmationBlocks: 1 })
const signer = new ethers.utils.SigningKey(privateKey) const signer = new ethers.utils.SigningKey(privateKey)
const chainId = await web3.eth.net.getId() const chainId = await web3.eth.net.getId()
return async function (tx) { return async function send(tx) {
tx = { const newTx = {
data: tx.data, data: tx.data,
to: tx.to, to: tx.to,
nonce: tx.nonce, nonce: tx.nonce,
@ -38,29 +38,30 @@ async function createSender (url, privateKey) {
} }
try { try {
logger.trace(`Preparing and sending transaction %o on ${url}`, tx) logger.trace(`Preparing and sending transaction %o on ${url}`, newTx)
const estimate = await sendRpcRequest(url, 'eth_estimateGas', [ { const estimate = await sendRpcRequest(url, 'eth_estimateGas', [{
from: signer.address, from: signer.address,
to: tx.to, to: newTx.to,
data: tx.data, data: newTx.data,
gasPrice: tx.gasPrice, gasPrice: newTx.gasPrice,
value: tx.value, value: newTx.value,
gas: `0x${new BN(MAX_GAS_LIMIT).toString(16)}` gas: `0x${new BN(MAX_GAS_LIMIT).toString(16)}`
} ]) }])
if (estimate.error) { if (estimate.error) {
logger.debug('Gas estimate failed %o, skipping tx, reverting nonce', estimate.error) logger.debug('Gas estimate failed %o, skipping tx, reverting nonce', estimate.error)
return true return true
} }
const gasLimit = BN.min(new BN(estimate.result, 16).multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT) const gasLimit = BN.min(new BN(estimate.result, 16)
tx.gasLimit = `0x${new BN(gasLimit).toString(16)}` .multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT)
newTx.gasLimit = `0x${new BN(gasLimit).toString(16)}`
logger.trace(`Estimated gas to ${gasLimit}`) logger.trace(`Estimated gas to ${gasLimit}`)
const hash = web3.utils.sha3(ethers.utils.serializeTransaction(tx)) const hash = web3.utils.sha3(ethers.utils.serializeTransaction(tx))
const signature = signer.signDigest(hash) const signature = signer.signDigest(hash)
const signedTx = ethers.utils.serializeTransaction(tx, signature) 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 nonce error
// handle insufficient funds error // handle insufficient funds error
if (error) { if (error) {
@ -68,7 +69,10 @@ async function createSender (url, privateKey) {
return false return false
} }
return { txHash: result, gasLimit: tx.gasLimit } return {
txHash: result,
gasLimit: tx.gasLimit
}
} catch (e) { } catch (e) {
logger.warn('Something failed, %o', e) logger.warn('Something failed, %o', e)
return false return false
@ -76,16 +80,19 @@ async function createSender (url, privateKey) {
} }
} }
async function waitForReceipt (url, txHash) { async function waitForReceipt(url, txHash) {
while (true) { while (true) {
const { result, error } = await sendRpcRequest(url, 'eth_getTransactionReceipt', [ txHash ]) const { result, error } = await sendRpcRequest(url, 'eth_getTransactionReceipt', [txHash])
if (result === null || error) { if (result === null || error) {
await new Promise(res => setTimeout(res, 1000)) await new Promise((res) => setTimeout(res, 1000))
} else { } else {
return result return result
} }
} }
} }
module.exports = { createSender, waitForReceipt } module.exports = {
createSender,
waitForReceipt
}

View File

@ -3,5 +3,8 @@
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"ioredis": "4.14.1" "ioredis": "4.14.1"
},
"engines": {
"node": ">=10.6.0"
} }
} }

View File

@ -12,7 +12,7 @@ redis.on('error', () => {
}) })
redis.on('connect', async () => { redis.on('connect', async () => {
await redis.set('homeBlock', parseInt(process.argv[2])) await redis.set('homeBlock', parseInt(process.argv[2], 10))
await redis.save() await redis.save()
redis.disconnect() redis.disconnect()
}) })

View File

@ -2,30 +2,30 @@ const amqp = require('amqplib')
const logger = require('./logger') 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) { async function connectRabbit(url) {
const connection = await _connectRabbit(url) while (true) {
return await connection.createChannel() try {
} return (await amqp.connect(url)).createChannel()
} catch (e) {
async function assertQueue (channel, name) { logger.debug('Failed to connect to rabbitmqServer, reconnecting')
const queue = await channel.assertQueue(name) await new Promise((resolve) => setTimeout(resolve, 2000))
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 } 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
}

View File

@ -1,7 +1,27 @@
const crypto = require('crypto') const crypto = require('crypto')
const bech32 = require('bech32') 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 compact = (parseInt(y[y.length - 1], 16) % 2 ? '03' : '02') + padZeros(x, 64)
const sha256Hash = sha256(Buffer.from(compact, 'hex')) const sha256Hash = sha256(Buffer.from(compact, 'hex'))
const hash = ripemd160(Buffer.from(sha256Hash, 'hex')) const hash = ripemd160(Buffer.from(sha256Hash, 'hex'))
@ -9,18 +29,8 @@ function publicKeyToAddress ({ x, y }) {
return bech32.encode('tbnb', words) return bech32.encode('tbnb', words)
} }
function padZeros (s, len) { module.exports = {
while (s.length < len) publicKeyToAddress,
s = '0' + s padZeros,
return s 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 }

View File

@ -1,6 +1,7 @@
const Redis = require('ioredis') const Redis = require('ioredis')
const logger = require('./logger') const logger = require('./logger')
logger.info('Connecting to redis') logger.info('Connecting to redis')
const redis = new Redis({ const redis = new Redis({
@ -14,7 +15,7 @@ redis.on('connect', () => {
logger.info('Connected to redis') logger.info('Connected to redis')
}) })
redis.on('error', e => { redis.on('error', (e) => {
logger.warn('Redis error %o', e) logger.warn('Redis error %o', e)
}) })

View File

@ -9,17 +9,15 @@ const { publicKeyToAddress } = require('./crypto')
const { RABBITMQ_URL, PROXY_URL } = process.env const { RABBITMQ_URL, PROXY_URL } = process.env
const app = express() 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 currentKeygenEpoch = null
let ready = false 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') logger.info('Connecting to RabbitMQ server')
const channel = await connectRabbit(RABBITMQ_URL) const channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to epoch events queue') logger.info('Connecting to epoch events queue')
@ -27,11 +25,11 @@ async function main () {
const cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue') const cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue')
while (!ready) { while (!ready) {
await new Promise(res => setTimeout(res, 1000)) await new Promise((res) => setTimeout(res, 1000))
} }
channel.prefetch(1) channel.prefetch(1)
keygenQueue.consume(msg => { keygenQueue.consume((msg) => {
const { epoch, parties, threshold } = JSON.parse(msg.content) const { epoch, parties, threshold } = JSON.parse(msg.content)
logger.info(`Consumed new epoch event, starting keygen for epoch ${epoch}`) logger.info(`Consumed new epoch event, starting keygen for epoch ${epoch}`)
@ -41,8 +39,11 @@ async function main () {
currentKeygenEpoch = epoch currentKeygenEpoch = epoch
logger.debug('Writing params') logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() })) fs.writeFileSync('./params', JSON.stringify({
const cmd = exec.execFile('./keygen-entrypoint.sh', [ PROXY_URL, keysFile ], async () => { parties: parties.toString(),
threshold: threshold.toString()
}))
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
currentKeygenEpoch = null currentKeygenEpoch = null
if (fs.existsSync(keysFile)) { if (fs.existsSync(keysFile)) {
logger.info(`Finished keygen for epoch ${epoch}`) logger.info(`Finished keygen for epoch ${epoch}`)
@ -57,11 +58,11 @@ async function main () {
logger.debug('Ack for keygen message') logger.debug('Ack for keygen message')
channel.ack(msg) channel.ack(msg)
}) })
cmd.stdout.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())) cmd.stderr.on('data', (data) => logger.debug(data.toString()))
}) })
cancelKeygenQueue.consume(async msg => { cancelKeygenQueue.consume(async (msg) => {
const { epoch } = JSON.parse(msg.content) const { epoch } = JSON.parse(msg.content)
logger.info(`Consumed new cancel event for epoch ${epoch} keygen`) logger.info(`Consumed new cancel event for epoch ${epoch} keygen`)
if (currentKeygenEpoch === epoch) { if (currentKeygenEpoch === epoch) {
@ -72,8 +73,12 @@ async function main () {
}) })
} }
main()
async function confirmKeygen (keysFile) { app.get('/start', (req, res) => {
exec.execSync(`curl -X POST -H "Content-Type: application/json" -d @"${keysFile}" "${PROXY_URL}/confirmKeygen"`, { stdio: 'pipe' }) logger.info('Ready to start')
} ready = true
res.send()
})
app.listen(8001, () => logger.debug('Listening on 8001'))
main()

View File

@ -7,5 +7,8 @@
"pino": "5.13.4", "pino": "5.13.4",
"pino-pretty": "3.2.1", "pino-pretty": "3.2.1",
"express": "4.17.1" "express": "4.17.1"
},
"engines": {
"node": ">=10.6.0"
} }
} }

View File

@ -9,5 +9,8 @@
"express": "4.17.1", "express": "4.17.1",
"pino": "5.13.4", "pino": "5.13.4",
"pino-pretty": "3.2.1" "pino-pretty": "3.2.1"
},
"engines": {
"node": ">=10.6.0"
} }
} }

View File

@ -1,24 +1,20 @@
const exec = require('child_process') const exec = require('child_process')
const fs = require('fs') const fs = require('fs')
const BN = require('bignumber.js') const BN = require('bignumber.js')
const axios = require('axios')
const express = require('express') const express = require('express')
const logger = require('./logger') const logger = require('./logger')
const { connectRabbit, assertQueue } = require('./amqp') const { connectRabbit, assertQueue } = require('./amqp')
const { publicKeyToAddress, sha256 } = require('./crypto') 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 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 }) const httpClient = axios.create({ baseURL: FOREIGN_URL })
@ -29,7 +25,126 @@ let ready = false
let exchangeQueue let exchangeQueue
let channel 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') logger.info('Connecting to RabbitMQ server')
channel = await connectRabbit(RABBITMQ_URL) channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to signature events queue') logger.info('Connecting to signature events queue')
@ -37,15 +152,17 @@ async function main () {
const signQueue = await assertQueue(channel, 'signQueue') const signQueue = await assertQueue(channel, 'signQueue')
while (!ready) { while (!ready) {
await new Promise(res => setTimeout(res, 1000)) await new Promise((res) => setTimeout(res, 1000))
} }
channel.prefetch(1) channel.prefetch(1)
signQueue.consume(async msg => { signQueue.consume(async (msg) => {
const data = JSON.parse(msg.content) const data = JSON.parse(msg.content)
logger.info('Consumed sign event: %o', data) 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 keysFile = `/keys/keys${epoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile) const { address: from, publicKey } = getAccountFromFile(keysFile)
@ -57,16 +174,22 @@ async function main () {
const account = await getAccount(from) const account = await getAccount(from)
logger.debug('Writing params') 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 attempt = 1
if (!newEpoch) { if (!newEpoch) {
const exchanges = await getExchangeMessages(nonce) 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) { 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) { while (true) {
logger.info(`Building corresponding transfer transaction, nonce ${nonce}`) logger.info(`Building corresponding transfer transaction, nonce ${nonce}`)
@ -82,16 +205,18 @@ async function main () {
const hash = sha256(tx.getSignBytes()) const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`) 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) { if (done) {
exchanges.forEach(msg => channel.ack(msg)) // eslint-disable-next-line no-loop-func
exchanges.forEach((exchangeMsg) => channel.ack(exchangeMsg))
break break
} }
attempt = nextAttempt ? nextAttempt : attempt + 1 attempt = nextAttempt || attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`) logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null nextAttempt = null
await new Promise(resolve => setTimeout(resolve, 1000)) await new Promise((resolve) => setTimeout(resolve, 1000))
} }
} }
} else if (account.sequence <= nonce) { } else if (account.sequence <= nonce) {
@ -104,27 +229,28 @@ async function main () {
from, from,
accountNumber: account.account_number, accountNumber: account.account_number,
sequence: nonce, sequence: nonce,
recipients: [ { recipients: [{
to, to,
tokens: account.balances.find(x => x.symbol === FOREIGN_ASSET).free, tokens: account.balances.find((token) => token.symbol === FOREIGN_ASSET).free,
bnbs: new BN(account.balances.find(x => x.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8)), bnbs: new BN(account.balances.find((token) => token.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8))
} ], }],
asset: FOREIGN_ASSET, asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}` memo: `Attempt ${attempt}`
}) })
const hash = sha256(tx.getSignBytes()) const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`) 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) { if (done) {
await confirmFundsTransfer() await confirmFundsTransfer()
break break
} }
attempt = nextAttempt ? nextAttempt : attempt + 1 attempt = nextAttempt || attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`) logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null nextAttempt = null
await new Promise(resolve => setTimeout(resolve, 1000)) await new Promise((resolve) => setTimeout(resolve, 1000))
} }
} else { } else {
logger.debug('Tx has been already sent') 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() 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))
}
})
}

View File

@ -10,42 +10,52 @@ const { FOREIGN_CHAIN_ID } = process.env
const BNB_ASSET = 'BNB' const BNB_ASSET = 'BNB'
class Transaction { class Transaction {
constructor (options) { constructor(options) {
const { from, accountNumber, sequence, recipients, asset, memo = '' } = 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 totalTokens = recipients.reduce(
const totalBnbs = recipients.reduce((sum, { bnbs }) => sum.plus(new BN(bnbs || 0)), new BN(0)) (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 = [] const senderCoins = []
if (asset && totalTokens.isGreaterThan(0)) { if (asset && totalTokens.isGreaterThan(0)) {
senderCoins.push({ senderCoins.push({
denom: asset, denom: asset,
amount: totalTokens.multipliedBy(10 ** 8).toNumber(), amount: totalTokens.multipliedBy(10 ** 8)
.toNumber()
}) })
} }
if (totalBnbs.isGreaterThan(0)) { if (totalBnbs.isGreaterThan(0)) {
senderCoins.push({ senderCoins.push({
denom: BNB_ASSET, denom: BNB_ASSET,
amount: totalBnbs.multipliedBy(10 ** 8).toNumber(), amount: totalBnbs.multipliedBy(10 ** 8)
.toNumber()
}) })
} }
senderCoins.sort((a, b) => a.denom > b.denom) senderCoins.sort((a, b) => a.denom > b.denom)
const inputs = [ { const inputs = [{
address: from, address: from,
coins: senderCoins coins: senderCoins
} ] }]
const outputs = recipients.map(({ to, tokens, bnbs }) => { const outputs = recipients.map(({ to, tokens, bnbs }) => {
const receiverCoins = [] const receiverCoins = []
if (asset && tokens) { if (asset && tokens) {
receiverCoins.push({ receiverCoins.push({
denom: asset, denom: asset,
amount: new BN(tokens).multipliedBy(10 ** 8).toNumber(), amount: new BN(tokens).multipliedBy(10 ** 8)
.toNumber()
}) })
} }
if (bnbs) { if (bnbs) {
receiverCoins.push({ receiverCoins.push({
denom: BNB_ASSET, 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) receiverCoins.sort((a, b) => a.denom > b.denom)
@ -56,8 +66,14 @@ class Transaction {
}) })
const msg = { const msg = {
inputs: inputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})), inputs: inputs.map((x) => ({
outputs: outputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})), ...x,
address: crypto.decodeAddress(x.address)
})),
outputs: outputs.map((x) => ({
...x,
address: crypto.decodeAddress(x.address)
})),
msgType: 'MsgSend' msgType: 'MsgSend'
} }
@ -72,29 +88,31 @@ class Transaction {
memo, memo,
msg, msg,
sequence, sequence,
type: msg.msgType, type: msg.msgType
}) })
} }
getSignBytes () { getSignBytes() {
return this.tx.getSignBytes(this.signMsg) return this.tx.getSignBytes(this.signMsg)
} }
addSignature (publicKey, signature) { addSignature(publicKey, signature) {
const yLast = parseInt(publicKey.y[publicKey.y.length - 1], 16) const yLast = parseInt(publicKey.y[publicKey.y.length - 1], 16)
const n = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16) const n = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)
const s = new BN(signature.s, 16) const s = new BN(signature.s, 16)
if (s.gt(n.div(2))) { if (s.gt(n.div(2))) {
logger.debug('Normalizing s') 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') const publicKeyEncoded = Buffer.from(`eb5ae98721${yLast % 2 ? '03' : '02'}${padZeros(publicKey.x, 64)}`, 'hex')
this.tx.signatures = [ { this.tx.signatures = [{
pub_key: publicKeyEncoded, pub_key: publicKeyEncoded,
signature: Buffer.from(padZeros(signature.r, 64) + padZeros(signature.s, 64), 'hex'), signature: Buffer.from(padZeros(signature.r, 64) + padZeros(signature.s, 64), 'hex'),
account_number: this.tx.account_number, account_number: this.tx.account_number,
sequence: this.tx.sequence, sequence: this.tx.sequence
} ] }]
return this.tx.serialize() return this.tx.serialize()
} }
} }

View File

@ -5,10 +5,14 @@ const { FOREIGN_URL, FOREIGN_ASSET } = process.env
const address = process.argv[2] const address = process.argv[2]
const httpClient = axios.create({ baseURL: FOREIGN_URL }) const httpClient = axios.create({ baseURL: FOREIGN_URL })
httpClient function main() {
.get(`/api/v1/account/${address}`) httpClient
.then(res => { .get(`/api/v1/account/${address}`)
console.log(`BNB: ${parseFloat(res.data.balances.find(x => x.symbol === 'BNB').free)}`) .then((res) => {
console.log(`${FOREIGN_ASSET}: ${parseFloat(res.data.balances.find(x => x.symbol === FOREIGN_ASSET).free)}`) 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) })
.catch(console.log)
}
main()

View File

@ -6,7 +6,7 @@ const PRIVATE_KEY = process.env.PRIVATE_KEY || FOREIGN_PRIVATE_KEY
const client = new Bnc(FOREIGN_URL) const client = new Bnc(FOREIGN_URL)
async function main () { async function main() {
client.chooseNetwork('testnet') client.chooseNetwork('testnet')
await client.setPrivateKey(PRIVATE_KEY) await client.setPrivateKey(PRIVATE_KEY)
@ -43,10 +43,11 @@ async function main () {
receipt = await client.transfer(from, to, tokens, FOREIGN_ASSET, 'exchange') receipt = await client.transfer(from, to, tokens, FOREIGN_ASSET, 'exchange')
} }
if (receipt.status === 200) if (receipt.status === 200) {
console.log(receipt.result[0].hash) console.log(receipt.result[0].hash)
else } else {
console.log(receipt) console.log(receipt)
}
} }
main() main()

View File

@ -8,11 +8,18 @@ const abiToken = require('./IERC20').abi
const web3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 }) const web3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 })
const token = new web3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS) 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() token.methods.balanceOf(address)
.then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3))) .call()
.then(x => console.log(`${x.toString()} tokens`)) .then((balance) => parseFloat(new BN(balance).dividedBy(10 ** 18)
.catch(() => console.log('0 tokens')) .toFixed(8, 3)))
.then((balance) => console.log(`${balance.toString()} tokens`))
.catch(() => console.log('0 tokens'))
}
main()

View File

@ -1,7 +1,9 @@
const Web3 = require('web3') const Web3 = require('web3')
const BN = require('bignumber.js') 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 abiToken = require('./IERC20').abi
const abiBridge = require('./Bridge').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 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 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 to = process.argv[2]
const amount = parseInt(process.argv[3]) const amount = parseInt(process.argv[3], 10)
let coins = process.argv[4] let coins = process.argv[4]
const txCount = await web3.eth.getTransactionCount(sender) 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`) 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 = { const txApprove = {
data: queryApprove.encodeABI(), data: queryApprove.encodeABI(),
from: sender, from: sender,
@ -42,9 +45,10 @@ async function main () {
const signedTxApprove = await web3.eth.accounts.signTransaction(txApprove, PRIVATE_KEY) const signedTxApprove = await web3.eth.accounts.signTransaction(txApprove, PRIVATE_KEY)
const receiptApprove = await web3.eth.sendSignedTransaction(signedTxApprove.rawTransaction) 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 = { const txExchange = {
data: queryExchange.encodeABI(), data: queryExchange.encodeABI(),
from: sender, from: sender,
@ -58,11 +62,12 @@ async function main () {
const signedTxExchange = await web3.eth.accounts.signTransaction(txExchange, PRIVATE_KEY) const signedTxExchange = await web3.eth.accounts.signTransaction(txExchange, PRIVATE_KEY)
const receiptExchange = await web3.eth.sendSignedTransaction(signedTxExchange.rawTransaction) const receiptExchange = await web3.eth.sendSignedTransaction(signedTxExchange.rawTransaction)
console.log('txHash exchange: ' + receiptExchange.transactionHash) console.log(`txHash exchange: ${receiptExchange.transactionHash}`)
} else if (amount !== 0) { } else if (amount !== 0) {
console.log(`Transfer from ${sender} to ${to}, ${amount} tokens`) 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 = { const tx = {
data: query.encodeABI(), data: query.encodeABI(),
from: sender, from: sender,
@ -75,7 +80,7 @@ async function main () {
}) * 1.5), blockGasLimit) }) * 1.5), blockGasLimit)
const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY) const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction) const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
console.log('txHash transfer: ' + receipt.transactionHash) console.log(`txHash transfer: ${receipt.transactionHash}`)
} }
if (coins) { if (coins) {
@ -85,7 +90,7 @@ async function main () {
const tx = { const tx = {
data: '0x', data: '0x',
from: sender, from: sender,
to: to, to,
nonce: await web3.eth.getTransactionCount(sender), nonce: await web3.eth.getTransactionCount(sender),
chainId: HOME_CHAIN_ID, chainId: HOME_CHAIN_ID,
value: web3.utils.toWei(new BN(coins).toString(), 'ether'), 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 signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction) const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
console.log('txHash: ' + receipt.transactionHash) console.log(`txHash: ${receipt.transactionHash}`)
} }
} }
main() main()

View File

@ -2,24 +2,28 @@ const { utils } = require('ethers')
const bech32 = require('bech32') const bech32 = require('bech32')
const crypto = require('crypto') 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) function ripemd160(bytes) {
const publicKey = utils.computePublicKey(privateKey, true) 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 sha256Hash = sha256(Buffer.from(publicKey.substr(2), 'hex'))
const hash = ripemd160(Buffer.from(sha256Hash, 'hex')) const hash = ripemd160(Buffer.from(sha256Hash, 'hex'))
const words = bech32.toWords(Buffer.from(hash, 'hex')) const words = bech32.toWords(Buffer.from(hash, 'hex'))
return bech32.encode('tbnb', words) return bech32.encode('tbnb', words)
} }
function sha256 (bytes) { function main() {
return crypto.createHash('sha256').update(bytes).digest('hex') 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) { main()
return crypto.createHash('ripemd160').update(bytes).digest('hex')
}

View File

@ -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 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 SIDE_CHAIN_ID = await web3.eth.net.getId()
const to = process.argv[2] const to = process.argv[2]
@ -15,20 +15,19 @@ async function main () {
console.log(`Transfer from ${sender} to ${to}, ${amount} eth`) console.log(`Transfer from ${sender} to ${to}, ${amount} eth`)
const tx_coins = { const txCoins = {
data: '0x', data: '0x',
from: sender, from: sender,
to: to, to,
nonce: await web3.eth.getTransactionCount(sender), nonce: await web3.eth.getTransactionCount(sender),
chainId: SIDE_CHAIN_ID, chainId: SIDE_CHAIN_ID,
value: web3.utils.toWei(new BN(amount).toString(), 'ether'), value: web3.utils.toWei(new BN(amount).toString(), 'ether'),
gas: 21000 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) const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
console.log('txHash: ' + receipt.transactionHash) console.log(`txHash: ${receipt.transactionHash}`)
} }
main() main()

View File

@ -6,5 +6,8 @@
"axios": "0.19.0", "axios": "0.19.0",
"@binance-chain/javascript-sdk": "2.16.1", "@binance-chain/javascript-sdk": "2.16.1",
"bignumber.js": "9.0.0" "bignumber.js": "9.0.0"
},
"engines": {
"node": ">=10.6.0"
} }
} }

View File

@ -5,15 +5,16 @@ const { getBalance } = require('./utils/bncController')
const { controller1, controller3 } = require('./utils/proxyController') const { controller1, controller3 } = require('./utils/proxyController')
module.exports = newValidator => { module.exports = (newValidator) => {
describe('add validator', function () { describe('add validator', function () {
let info let info
let initialInfo let initialInfo
let nextValidators let nextValidators
before(async function () { before(async function () {
initialInfo = info = await controller1.getInfo() initialInfo = await controller1.getInfo()
nextValidators = [ ...initialInfo.validators, newValidator ] info = initialInfo
nextValidators = [...initialInfo.validators, newValidator]
}) })
it('should start voting process', async function () { 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') assert.strictEqual(info.bridgeStatus, 'ready', 'Should not change state after one vote')
await controller3.voteStartVoting() 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.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.nextEpoch, initialInfo.epoch + 1, 'Next epoch is not set correctly')
assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are 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') assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly')
await controller3.voteAddValidator(newValidator) 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.validators, initialInfo.validators, 'Validators are not set correctly')
assert.deepStrictEqual(info.nextValidators, nextValidators, 'Next 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') assert.strictEqual(info.bridgeStatus, 'voting', 'Should not change state after one vote')
await controller3.voteStartKeygen() 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 controller3.voteStartKeygen()
await delay(5000) await delay(5000)
@ -81,12 +85,15 @@ module.exports = newValidator => {
it('should finish keygen process and start funds transfer', async function () { it('should finish keygen process and start funds transfer', async function () {
this.timeout(120000) 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 () { it('should transfer all funds to new account and start new epoch', async function () {
this.timeout(300000) 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.deepStrictEqual(info.validators, nextValidators, 'Incorrect set of validators in new epoch')
assert.strictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch') assert.strictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch')
assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch') assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch')

View File

@ -2,7 +2,7 @@ const { delay } = require('./utils/wait')
const { controller1 } = require('./utils/proxyController') const { controller1 } = require('./utils/proxyController')
module.exports = usersFunc => { module.exports = (usersFunc) => {
describe('exchange of tokens in bnc => eth direction', function () { describe('exchange of tokens in bnc => eth direction', function () {
let users let users
let info let info
@ -11,21 +11,21 @@ module.exports = usersFunc => {
before(async function () { before(async function () {
users = usersFunc() users = usersFunc()
info = await controller1.getInfo() 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))) await Promise.all(users.map((user, i) => user.exchangeBnc(info.foreignBridgeAddress, 3 + i)))
}) })
it('should make coorect exchange transactions on eth side', async function () { it('should make correct exchange transactions on eth side', async function () {
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i += 1) {
do { while (true) {
const user = users[i] const user = users[i]
const newEthBalance = await user.getEthBalance() const newEthBalance = await user.getEthBalance()
if (newEthBalance === ethBalances[i] + 3 + i) { if (newEthBalance === ethBalances[i] + 3 + i) {
break break
} }
await delay(500) await delay(500)
} while (true) }
} }
}) })
}) })

View File

@ -5,13 +5,14 @@ const { getBalance } = require('./utils/bncController')
const { controller1, controller2, controller3 } = require('./utils/proxyController') const { controller1, controller2, controller3 } = require('./utils/proxyController')
module.exports = newThreshold => { module.exports = (newThreshold) => {
describe('change threshold', function () { describe('change threshold', function () {
let info let info
let initialInfo let initialInfo
before(async function () { before(async function () {
initialInfo = info = await controller1.getInfo() initialInfo = await controller1.getInfo()
info = initialInfo
}) })
it('should start voting process', async function () { 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') assert.strictEqual(info.bridgeStatus, 'ready', 'Should not change state after one vote')
await controller2.voteStartVoting() 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.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.nextEpoch, initialInfo.epoch + 1, 'Next epoch is not set correctly')
assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are 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') assert.deepStrictEqual(info.nextThreshold, initialInfo.threshold, 'Next threshold is not set correctly')
await controller2.voteChangeThreshold(newThreshold) 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.validators, initialInfo.validators, 'Validators are not set correctly')
assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next 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') 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') assert.strictEqual(info.bridgeStatus, 'voting', 'Should not change state after one vote')
await controller2.voteStartKeygen() 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 controller3.voteStartKeygen()
await delay(5000) await delay(5000)
@ -86,12 +90,15 @@ module.exports = newThreshold => {
it('should finish keygen process and start funds transfer', async function () { it('should finish keygen process and start funds transfer', async function () {
this.timeout(120000) 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 () { it('should transfer all funds to new account and start new epoch', async function () {
this.timeout(300000) 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.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.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch')
assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch') assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch')

View File

@ -1,12 +1,12 @@
const assert = require('assert') const assert = require('assert')
const { getSequence } = require('./utils/bncController') 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 { HOME_BRIDGE_ADDRESS } = process.env
const { controller1 } = require('./utils/proxyController') const { controller1 } = require('./utils/proxyController')
module.exports = usersFunc => { module.exports = (usersFunc) => {
describe('exchange of tokens in eth => bnc direction', function () { describe('exchange of tokens in eth => bnc direction', function () {
let info let info
let users let users
@ -17,8 +17,8 @@ module.exports = usersFunc => {
before(async function () { before(async function () {
users = usersFunc() users = usersFunc()
info = await controller1.getInfo() info = await controller1.getInfo()
ethBalances = await Promise.all(users.map(user => user.getEthBalance())) ethBalances = await Promise.all(users.map((user) => user.getEthBalance()))
bncBalances = await users.seqMap(user => user.getBncBalance()) bncBalances = await seqMap(users, (user) => user.getBncBalance())
bncBridgeSequence = await getSequence(info.foreignBridgeAddress) bncBridgeSequence = await getSequence(info.foreignBridgeAddress)
await Promise.all(users.map((user, i) => user.approveEth(HOME_BRIDGE_ADDRESS, 5 + i))) 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 () { it('should accept exchange requests', async function () {
await Promise.all(users.map((user, i) => user.exchangeEth(5 + i))) await Promise.all(users.map((user, i) => user.exchangeEth(5 + i)))
const newEthBalances = await Promise.all(users.map(user => user.getEthBalance())) const newEthBalances = await Promise.all(users.map((user) => user.getEthBalance()))
for (let i = 0; i < 3; i++) { 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`) 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 () { it('should make exchange transaction on bnc side', async function () {
this.timeout(300000) 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 () { it('should make correct exchange transaction', async function () {
await delay(10000) await delay(10000)
const newBncBalances = await Promise.all(users.map(user => user.getBncBalance())) const newBncBalances = await Promise.all(users.map((user) => user.getBncBalance()))
for (let i = 0; i < 3; i++) { 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`) assert.strictEqual(newBncBalances[i], bncBalances[i] + 5 + i, `Balance of ${users[i].bncAddress} did not updated as expected`)
} }
}) })

View File

@ -1,5 +1,5 @@
const createUser = require('./utils/user') const createUser = require('./utils/user')
const { waitPromise } = require('./utils/wait') const { waitPromise, seqMap } = require('./utils/wait')
const testEthToBnc = require('./ethToBnc') const testEthToBnc = require('./ethToBnc')
const testBncToEth = require('./bncToEth') const testBncToEth = require('./bncToEth')
@ -18,7 +18,7 @@ describe('bridge tests', function () {
let users let users
before(async function () { 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 () { describe('generation of initial epoch keys', function () {
@ -33,7 +33,7 @@ describe('bridge tests', function () {
it('should generate keys', async function () { it('should generate keys', async function () {
this.timeout(120000) this.timeout(120000)
info = await waitPromise(controller1.getInfo, info => info.epoch === 1) info = await waitPromise(controller1.getInfo, (newInfo) => newInfo.epoch === 1)
}) })
after(async function () { after(async function () {

View File

@ -5,15 +5,16 @@ const { getBalance } = require('./utils/bncController')
const { controller1, controller2, controller3 } = require('./utils/proxyController') const { controller1, controller2, controller3 } = require('./utils/proxyController')
module.exports = oldValidator => { module.exports = (oldValidator) => {
describe('remove validator', function () { describe('remove validator', function () {
let info let info
let initialInfo let initialInfo
let nextValidators let nextValidators
before(async function () { before(async function () {
initialInfo = info = await controller1.getInfo() initialInfo = await controller1.getInfo()
nextValidators = initialInfo.validators.filter(validator => validator !== oldValidator) info = initialInfo
nextValidators = initialInfo.validators.filter((validator) => validator !== oldValidator)
}) })
it('should start voting process', async function () { 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') assert.strictEqual(info.bridgeStatus, 'ready', 'Should not change state after one vote')
await controller2.voteStartVoting() 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.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.nextEpoch, initialInfo.epoch + 1, 'Next epoch is not set correctly')
assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are 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') assert.deepStrictEqual(info.nextValidators, initialInfo.validators, 'Next validators are not set correctly')
await controller2.voteRemoveValidator(oldValidator) 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.validators, initialInfo.validators, 'Validators are not set correctly')
assert.deepStrictEqual(info.nextValidators, nextValidators, 'Next 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') assert.strictEqual(info.bridgeStatus, 'voting', 'Should not change state after one vote')
await controller2.voteStartKeygen() 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 controller3.voteStartKeygen()
await delay(5000) await delay(5000)
@ -81,12 +85,15 @@ module.exports = oldValidator => {
it('should finish keygen process and start funds transfer', async function () { it('should finish keygen process and start funds transfer', async function () {
this.timeout(120000) 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 () { it('should transfer all funds to new account and start new epoch', async function () {
this.timeout(300000) 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.deepStrictEqual(info.validators, nextValidators, 'Incorrect set of validators in new epoch')
assert.strictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch') assert.strictEqual(info.nextEpoch, initialInfo.epoch + 1, 'Incorrect next epoch')
assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch') assert.strictEqual(info.bridgeStatus, 'ready', 'Incorrect bridge state in new epoch')

View File

@ -4,7 +4,7 @@ const { delay } = require('./wait')
const { FOREIGN_URL, FOREIGN_ASSET } = process.env 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) const client = new Bnc(FOREIGN_URL)
client.chooseNetwork('testnet') client.chooseNetwork('testnet')
@ -16,7 +16,7 @@ module.exports = async function main (privateKey) {
await delay(1000) await delay(1000)
return { return {
transfer: async function (to, tokens, bnbs) { async transfer(to, tokens, bnbs) {
const outputs = [{ const outputs = [{
to, to,
coins: [] coins: []
@ -35,7 +35,7 @@ module.exports = async function main (privateKey) {
} }
await client.multiSend(from, outputs, 'funding') 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') await client.transfer(from, to, value.toString(), FOREIGN_ASSET, 'exchange')
} }
} }

View File

@ -10,12 +10,12 @@ const bnc = axios.create({
}) })
module.exports = { module.exports = {
getBalance: async function (address) { async getBalance(address) {
const response = await retry(5, () => bnc.get(`/api/v1/account/${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 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`)) const response = await retry(5, () => bnc.get(`/api/v1/account/${address}/sequence`))
return response ? response.data.sequence : 0 return response ? response.data.sequence : 0
} }

View File

@ -1,6 +1,6 @@
const axios = require('axios') const axios = require('axios')
function createController (validatorId) { function createController(validatorId) {
const url = `http://validator${validatorId}_proxy_1:8002/` const url = `http://validator${validatorId}_proxy_1:8002/`
const proxy = axios.create({ const proxy = axios.create({
@ -9,22 +9,22 @@ function createController (validatorId) {
}) })
return { return {
getInfo: async function () { async getInfo() {
return (await proxy.get('/info')).data return (await proxy.get('/info')).data
}, },
voteStartVoting: async function () { async voteStartVoting() {
return (await proxy.get('/vote/startVoting')).data return (await proxy.get('/vote/startVoting')).data
}, },
voteStartKeygen: async function () { async voteStartKeygen() {
return (await proxy.get('/vote/startKeygen')).data return (await proxy.get('/vote/startKeygen')).data
}, },
voteAddValidator: async function (validatorAddress) { async voteAddValidator(validatorAddress) {
return (await proxy.get(`/vote/addValidator/${validatorAddress}`)).data return (await proxy.get(`/vote/addValidator/${validatorAddress}`)).data
}, },
voteRemoveValidator: async function (validatorAddress) { async voteRemoveValidator(validatorAddress) {
return (await proxy.get(`/vote/removeValidator/${validatorAddress}`)).data return (await proxy.get(`/vote/removeValidator/${validatorAddress}`)).data
}, },
voteChangeThreshold: async function (threshold) { async voteChangeThreshold(threshold) {
return (await proxy.get(`/vote/changeThreshold/${threshold}`)).data return (await proxy.get(`/vote/changeThreshold/${threshold}`)).data
} }
} }

View File

@ -11,7 +11,7 @@ const txOptions = {
gasLimit: 200000 gasLimit: 200000
} }
module.exports = async function (privateKey) { async function createUser(privateKey) {
const wallet = new ethers.Wallet(privateKey, provider) const wallet = new ethers.Wallet(privateKey, provider)
const ethAddress = wallet.address const ethAddress = wallet.address
const bncAddress = getAddressFromPrivateKey(privateKey) const bncAddress = getAddressFromPrivateKey(privateKey)
@ -23,32 +23,34 @@ module.exports = async function (privateKey) {
return { return {
ethAddress, ethAddress,
bncAddress, bncAddress,
getEthBalance: async function () { async getEthBalance() {
const balance = await token.balanceOf(ethAddress) const balance = await token.balanceOf(ethAddress)
return parseFloat(new BN(balance).dividedBy(10 ** 18).toFixed(8, 3)) return parseFloat(new BN(balance).dividedBy(10 ** 18).toFixed(8, 3))
}, },
transferEth: async function (to, value) { async transferEth(to, value) {
const tx = await token.transfer(to, '0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) const tx = await token.transfer(to, `0x${new BN(value).multipliedBy(10 ** 18).toString(16)}`, txOptions)
await tx.wait() await tx.wait()
}, },
approveEth: async function (to, value) { async approveEth(to, value) {
const tx = await token.approve(to, '0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) const tx = await token.approve(to, `0x${new BN(value).multipliedBy(10 ** 18).toString(16)}`, txOptions)
await tx.wait() await tx.wait()
}, },
exchangeEth: async function (value) { async exchangeEth(value) {
const tx = await bridge.exchange('0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) const tx = await bridge.exchange(`0x${new BN(value).multipliedBy(10 ** 18).toString(16)}`, txOptions)
await tx.wait() await tx.wait()
}, },
getBncBalance: async function () { async getBncBalance() {
const balance = await getBalance(bncAddress) const balance = await getBalance(bncAddress)
await delay(1000) await delay(1000)
return balance return balance
}, },
transferBnc: async function (bridgeAddress, tokens, bnbs) { async transferBnc(bridgeAddress, tokens, bnbs) {
return await bncClient.transfer(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) return await bncClient.exchange(bridgeAddress, value)
} }
} }
} }
module.exports = createUser

View File

@ -1,32 +1,34 @@
async function delay(ms) { async function delay(ms) {
await new Promise(res => setTimeout(res, ms)) await new Promise((res) => setTimeout(res, ms))
} }
async function waitPromise (getPromise, checker) { async function waitPromise(getPromise, checker) {
do { while (true) {
const result = await getPromise() const result = await getPromise()
if (checker(result)) if (checker(result)) {
return result return result
}
await delay(1000) await delay(1000)
} while (true) }
} }
async function retry (n, getPromise) { async function retry(n, getPromise) {
while (n) { while (n) {
try { try {
return await getPromise() return await getPromise()
} catch (e) { } catch (e) {
await delay(3000) await delay(3000)
n-- // eslint-disable-next-line no-param-reassign
n -= 1
} }
} }
return null return null
} }
Array.prototype.seqMap = async function (transition) { async function seqMap(arr, transition) {
const results = [] const results = []
for (let i = 0; i < this.length; i++) { for (let i = 0; i < arr.length; i += 1) {
results[i] = await transition(this[i]) results[i] = await transition(arr[i])
} }
return results return results
} }
@ -34,5 +36,6 @@ Array.prototype.seqMap = async function (transition) {
module.exports = { module.exports = {
waitPromise, waitPromise,
delay, delay,
retry retry,
seqMap
} }