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

@ -8,7 +8,7 @@ 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,

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,13 +9,72 @@ 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 })
function getLastForeignAddress() {
const epoch = Math.max(0, ...fs.readdirSync('/keys')
.map((x) => parseInt(x.split('.')[0].substr(4), 10)))
if (epoch === 0) {
return null
}
const keysFile = `/keys/keys${epoch}.store`
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
return publicKeyToAddress(publicKey)
}
function getTx(hash) {
return foreignHttpClient
.get(`/api/v1/tx/${hash}`, {
params: {
format: 'json'
}
})
.then((res) => res.data.tx.value)
.catch(() => getTx(hash))
}
function getBlockTime() {
return foreignHttpClient
.get('/api/v1/time')
.then((res) => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET)
.catch(() => getBlockTime())
}
async function fetchNewTransactions() {
logger.debug('Fetching new transactions')
const startTime = parseInt(await redis.get('foreignTime'), 10) + 1
const address = getLastForeignAddress()
const endTime = Math.min(startTime + 3 * 30 * 24 * 60 * 60 * 1000, await getBlockTime())
if (address === null) {
return {}
}
logger.debug('Sending api transactions request')
const params = {
address,
side: 'RECEIVE',
txAsset: FOREIGN_ASSET,
txType: 'TRANSFER',
startTime,
endTime
}
try {
logger.trace('%o', params)
const transactions = (await foreignHttpClient
.get('/api/v1/transactions', { params })).data.tx
return {
transactions,
endTime
}
} catch (e) {
return await fetchNewTransactions()
}
}
async function initialize() { async function initialize() {
if (await redis.get('foreignTime') === null) { if (await redis.get('foreignTime') === null) {
logger.info('Set default foreign time') logger.info('Set default foreign time')
@ -26,21 +85,23 @@ async function initialize () {
async function main() { async function main() {
const { transactions, endTime } = await fetchNewTransactions() const { transactions, endTime } = await fetchNewTransactions()
if (!transactions || transactions.length === 0) { if (!transactions || transactions.length === 0) {
logger.debug(`Found 0 new transactions`) logger.debug('Found 0 new transactions')
await new Promise(r => setTimeout(r, FOREIGN_FETCH_INTERVAL)) await new Promise((r) => setTimeout(r, FOREIGN_FETCH_INTERVAL))
return return
} }
logger.info(`Found ${transactions.length} new transactions`) logger.info(`Found ${transactions.length} new transactions`)
logger.trace('%o', transactions) logger.trace('%o', transactions)
for (const tx of transactions.reverse()) { for (let i = transactions.length - 1; i >= 0; i -= 1) {
const tx = transactions[i]
if (tx.memo !== 'funding') { if (tx.memo !== 'funding') {
const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value
await proxyHttpClient await proxyHttpClient
.post('/transfer', { .post('/transfer', {
to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')), to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')),
value: new BN(tx.value).multipliedBy(10 ** 18).integerValue(), value: new BN(tx.value).multipliedBy(10 ** 18)
.integerValue(),
hash: `0x${tx.txHash}` hash: `0x${tx.txHash}`
}) })
} }
@ -48,60 +109,8 @@ async function main () {
await redis.set('foreignTime', endTime) await redis.set('foreignTime', endTime)
} }
function getTx (hash) { initialize()
return foreignHttpClient .then(async () => {
.get(`/api/v1/tx/${hash}`, {
params: {
format: 'json'
}
})
.then(res => res.data.tx.value)
.catch(() => getTx(hash))
}
function getBlockTime () {
return foreignHttpClient
.get(`/api/v1/time`)
.then(res => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET)
.catch(() => getBlockTime())
}
async function fetchNewTransactions () {
logger.debug('Fetching new transactions')
const startTime = parseInt(await redis.get('foreignTime')) + 1
const address = getLastForeignAddress()
const endTime = Math.min(startTime + 3 * 30 * 24 * 60 * 60 * 1000, await getBlockTime())
if (address === null)
return {}
logger.debug('Sending api transactions request')
const params = {
address,
side: 'RECEIVE',
txAsset: FOREIGN_ASSET,
txType: 'TRANSFER',
startTime,
endTime,
}
try {
logger.trace('%o', params)
const transactions = (await foreignHttpClient
.get('/api/v1/transactions', { params })).data.tx
return { transactions, endTime }
} catch (e) {
return await fetchNewTransactions()
}
}
function getLastForeignAddress () {
const epoch = Math.max(0, ...fs.readdirSync('/keys').map(x => parseInt(x.split('.')[0].substr(4))))
if (epoch === 0)
return null
const keysFile = `/keys/keys${epoch}.store`
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
return publicKeyToAddress(publicKey)
}
initialize().then(async () => {
while (true) { while (true) {
await main() 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
@ -36,10 +38,11 @@ async function resetFutureMessages (queue) {
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,140 +51,42 @@ 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 () {
channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
signQueue = await assertQueue(channel, 'signQueue')
keygenQueue = await assertQueue(channel, 'keygenQueue')
cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue')
const events = await bridge.getPastEvents('EpochStart', {
fromBlock: 1
})
epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0
logger.info(`Current epoch ${epoch}`)
epochStart = events.length ? events[events.length - 1].blockNumber : 1
const saved = (parseInt(await redis.get('homeBlock')) + 1) || parseInt(HOME_START_BLOCK)
if (epochStart > saved) {
logger.info(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`)
blockNumber = epochStart
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber()
await redis.multi()
.set('homeBlock', blockNumber - 1)
.set(`foreignNonce${epoch}`, 0)
.exec()
foreignNonce[epoch] = 0
} else {
logger.info('Restoring epoch and block number from local db')
blockNumber = saved
foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`)) || 0
}
logger.debug('Checking if current validator')
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`)
}
await resetFutureMessages(keygenQueue)
await resetFutureMessages(cancelKeygenQueue)
await resetFutureMessages(exchangeQueue)
await resetFutureMessages(signQueue)
logger.debug(`Sending start commands`)
await axios.get('http://keygen:8001/start')
await axios.get('http://signer:8001/start')
}
async function main () {
logger.debug(`Watching events in block #${blockNumber}`)
if (await homeWeb3.eth.getBlock(blockNumber) === null) {
logger.debug('No block')
await new Promise(r => setTimeout(r, 1000))
return
}
redisTx = redis.multi()
const bridgeEvents = await bridge.getPastEvents('allEvents', {
fromBlock: blockNumber,
toBlock: blockNumber
})
for (const event of bridgeEvents) {
switch (event.event) {
case 'NewEpoch':
await sendKeygen(event)
break
case 'NewEpochCancelled':
sendKeygenCancellation(event)
break
case 'NewFundsTransfer':
isCurrentValidator && await sendSignFundsTransfer(event)
break
case 'ExchangeRequest':
isCurrentValidator && await sendSign(event)
break
case 'EpochStart':
await processEpochStart(event)
break
}
}
if ((blockNumber + 1 - epochStart) % rangeSize === 0) {
logger.info('Reached end of the current block range')
if (lastTransactionBlockNumber > blockNumber - rangeSize) {
logger.info('Sending message to start signature generation for the ended range')
await sendStartSign()
}
}
blockNumber++
// Exec redis tx
await redisTx.incr('homeBlock').exec()
await redis.save()
}
initialize().then(async () => {
while (true) {
await main()
}
}, e => logger.warn('Initialization failed %o', e))
async function sendKeygen(event) { async function sendKeygen(event) {
const newEpoch = event.returnValues.newEpoch.toNumber() const newEpoch = event.returnValues.newEpoch.toNumber()
keygenQueue.send({ keygenQueue.send({
epoch: newEpoch, epoch: newEpoch,
blockNumber, blockNumber,
threshold: (await bridge.methods.getThreshold(newEpoch).call()).toNumber(), threshold: (await bridge.methods.getThreshold(newEpoch)
parties: (await bridge.methods.getParties(newEpoch).call()).toNumber() .call()).toNumber(),
parties: (await bridge.methods.getParties(newEpoch)
.call()).toNumber()
}) })
logger.debug('Sent keygen start event') logger.debug('Sent keygen start event')
} }
function sendKeygenCancellation(event) { function sendKeygenCancellation(event) {
const epoch = event.returnValues.epoch.toNumber() const eventEpoch = event.returnValues.epoch.toNumber()
cancelKeygenQueue.send({ cancelKeygenQueue.send({
epoch, epoch: eventEpoch,
blockNumber blockNumber
}) })
logger.debug('Sent keygen cancellation event') logger.debug('Sent keygen cancellation event')
@ -195,11 +100,13 @@ async function sendSignFundsTransfer (event) {
blockNumber, blockNumber,
newEpoch, newEpoch,
nonce: foreignNonce[oldEpoch], nonce: foreignNonce[oldEpoch],
threshold: (await bridge.methods.getThreshold(oldEpoch).call()).toNumber(), threshold: (await bridge.methods.getThreshold(oldEpoch)
parties: (await bridge.methods.getParties(oldEpoch).call()).toNumber() .call()).toNumber(),
parties: (await bridge.methods.getParties(oldEpoch)
.call()).toNumber()
}) })
logger.debug('Sent sign funds transfer event') logger.debug('Sent sign funds transfer event')
foreignNonce[oldEpoch]++ foreignNonce[oldEpoch] += 1
redisTx.incr(`foreignNonce${oldEpoch}`) redisTx.incr(`foreignNonce${oldEpoch}`)
} }
@ -215,7 +122,11 @@ async function sendSign (event) {
chainId: await homeWeb3.eth.net.getId() chainId: await homeWeb3.eth.net.getId()
}) })
const hash = homeWeb3.utils.sha3(msg) const hash = homeWeb3.utils.sha3(msg)
const publicKey = utils.recoverPublicKey(hash, { r: tx.r, s: tx.s, v: tx.v }) const publicKey = utils.recoverPublicKey(hash, {
r: tx.r,
s: tx.s,
v: tx.v
})
const msgToQueue = { const msgToQueue = {
epoch, epoch,
blockNumber, blockNumber,
@ -223,7 +134,8 @@ async function sendSign (event) {
x: publicKey.substr(4, 64), x: publicKey.substr(4, 64),
y: publicKey.substr(68, 64) y: publicKey.substr(68, 64)
}), }),
value: (new BN(event.returnValues.value)).dividedBy(10 ** 18).toFixed(8, 3), value: (new BN(event.returnValues.value)).dividedBy(10 ** 18)
.toFixed(8, 3),
nonce: event.returnValues.nonce.toNumber() nonce: event.returnValues.nonce.toNumber()
} }
@ -240,18 +152,23 @@ async function sendStartSign () {
signQueue.send({ signQueue.send({
epoch, epoch,
blockNumber, blockNumber,
nonce: foreignNonce[epoch]++, nonce: foreignNonce[epoch],
threshold: (await bridge.methods.getThreshold(epoch).call()).toNumber(), threshold: (await bridge.methods.getThreshold(epoch)
parties: (await bridge.methods.getParties(epoch).call()).toNumber() .call()).toNumber(),
parties: (await bridge.methods.getParties(epoch)
.call()).toNumber()
}) })
foreignNonce[epoch] += 1
} }
async function processEpochStart(event) { async function processEpochStart(event) {
epoch = event.returnValues.epoch.toNumber() epoch = event.returnValues.epoch.toNumber()
epochStart = blockNumber epochStart = blockNumber
logger.info(`Epoch ${epoch} started`) logger.info(`Epoch ${epoch} started`)
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber() rangeSize = (await bridge.methods.getRangeSize()
isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress) .call()).toNumber()
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 {
@ -260,3 +177,115 @@ async function processEpochStart (event) {
logger.info(`Updated range size to ${rangeSize}`) logger.info(`Updated range size to ${rangeSize}`)
foreignNonce[epoch] = 0 foreignNonce[epoch] = 0
} }
async function initialize() {
channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
signQueue = await assertQueue(channel, 'signQueue')
keygenQueue = await assertQueue(channel, 'keygenQueue')
cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue')
const events = await bridge.getPastEvents('EpochStart', {
fromBlock: 1
})
epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0
logger.info(`Current epoch ${epoch}`)
epochStart = events.length ? events[events.length - 1].blockNumber : 1
const saved = (parseInt(await redis.get('homeBlock'), 10) + 1) || parseInt(HOME_START_BLOCK, 10)
if (epochStart > saved) {
logger.info(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`)
blockNumber = epochStart
rangeSize = (await bridge.methods.getRangeSize()
.call()).toNumber()
await redis.multi()
.set('homeBlock', blockNumber - 1)
.set(`foreignNonce${epoch}`, 0)
.exec()
foreignNonce[epoch] = 0
} else {
logger.info('Restoring epoch and block number from local db')
blockNumber = saved
foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`), 10) || 0
}
logger.debug('Checking if current validator')
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`)
}
await resetFutureMessages(keygenQueue)
await resetFutureMessages(cancelKeygenQueue)
await resetFutureMessages(exchangeQueue)
await resetFutureMessages(signQueue)
logger.debug('Sending start commands')
await axios.get('http://keygen:8001/start')
await axios.get('http://signer:8001/start')
}
async function main() {
logger.debug(`Watching events in block #${blockNumber}`)
if (await homeWeb3.eth.getBlock(blockNumber) === null) {
logger.debug('No block')
await new Promise((r) => setTimeout(r, 1000))
return
}
redisTx = redis.multi()
const bridgeEvents = await bridge.getPastEvents('allEvents', {
fromBlock: blockNumber,
toBlock: blockNumber
})
for (let i = 0; i < bridgeEvents.length; i += 1) {
const event = bridgeEvents[i]
switch (event.event) {
case 'NewEpoch':
await sendKeygen(event)
break
case 'NewEpochCancelled':
sendKeygenCancellation(event)
break
case 'NewFundsTransfer':
if (isCurrentValidator) {
await sendSignFundsTransfer(event)
}
break
case 'ExchangeRequest':
if (isCurrentValidator) {
await sendSign(event)
}
break
case 'EpochStart':
await processEpochStart(event)
break
default:
logger.warn('Unknown event %o', event)
}
}
if ((blockNumber + 1 - epochStart) % rangeSize === 0) {
logger.info('Reached end of the current block range')
if (lastTransactionBlockNumber > blockNumber - rangeSize) {
logger.info('Sending message to start signature generation for the ended range')
await sendStartSign()
}
}
blockNumber += 1
// Exec redis tx
await redisTx.incr('homeBlock')
.exec()
await redis.save()
}
initialize()
.then(async () => {
while (true) {
await main()
}
}, (e) => logger.warn('Initialization failed %o', e))

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

@ -4,15 +4,16 @@ 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,14 +72,55 @@ 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: {
x: tokenizer.parse(),
y: tokenizer.parse()
},
pk_t_rand_commitment: {
x: tokenizer.parse(),
y: tokenizer.parse()
},
challenge_response: tokenizer.parse()
})
]
const signDecoders = [
// round 0
(tokenizer) => tokenizer.byte(),
// round 1
(tokenizer) => [
{
com: tokenizer.parse()
},
{
c: tokenizer.parse(512)
}
],
// round 2
(tokenizer) => {
const res = []
for (let i = 0; i < 2; i += 1) {
res[i] = {
c: tokenizer.parse(512),
b_proof: {
pk: {
x: tokenizer.parse(),
y: tokenizer.parse()
},
pk_t_rand_commitment: {
x: tokenizer.parse(),
y: tokenizer.parse()
},
challenge_response: tokenizer.parse()
},
beta_tag_proof: {
pk: { pk: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse() y: tokenizer.parse()
@ -92,79 +132,25 @@ const keygenDecoders = [
challenge_response: tokenizer.parse() challenge_response: tokenizer.parse()
} }
} }
]
const signDecoders = [
// round 0
function (tokenizer) {
return tokenizer.byte()
},
// round 1
function (tokenizer) {
return [
{
com: tokenizer.parse()
},
{
c: tokenizer.parse(512)
}
]
},
// round 2
function (tokenizer) {
const res = []
for (let i = 0; i < 2; i++) {
res[i] = {
c: tokenizer.parse(512),
b_proof: {
pk: {
x: tokenizer.parse(),
y: tokenizer.parse(),
},
pk_t_rand_commitment: {
x: tokenizer.parse(),
y: tokenizer.parse(),
},
challenge_response: tokenizer.parse(),
},
beta_tag_proof: {
pk: {
x: tokenizer.parse(),
y: tokenizer.parse(),
},
pk_t_rand_commitment: {
x: tokenizer.parse(),
y: 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(), x: tokenizer.parse(),
@ -192,17 +178,13 @@ const signDecoders = [
z1: tokenizer.parse(), z1: tokenizer.parse(),
z2: tokenizer.parse() z2: tokenizer.parse()
} }
] ],
},
// round 7 // round 7
function (tokenizer) { (tokenizer) => ({
return {
com: tokenizer.parse() com: tokenizer.parse()
} }),
},
// round 8 // round 8
function (tokenizer) { (tokenizer) => ({
return {
u_i: { u_i: {
x: tokenizer.parse(), x: tokenizer.parse(),
y: tokenizer.parse() y: tokenizer.parse()
@ -212,18 +194,17 @@ const signDecoders = [
y: tokenizer.parse() y: tokenizer.parse()
}, },
blind_factor: 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

@ -9,36 +9,37 @@ function makeBuffer (value, length = 32, base = 16) {
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,26 +39,7 @@ 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)
votesProxyApp.get('/vote/startKeygen', voteStartKeygen)
votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen)
votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold)
votesProxyApp.get('/info', info)
async function main() { async function main() {
homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress) homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress)
@ -88,27 +69,69 @@ function Err (data) {
return { Err: data } return { Err: data }
} }
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) { 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)
} }
@ -136,14 +159,19 @@ async function set (req, res) {
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')
} }
} }
@ -157,15 +185,23 @@ 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')
} }
@ -186,48 +222,6 @@ async function confirmFundsTransfer (req, res) {
logger.debug('Confirm funds transfer end') logger.debug('Confirm funds transfer end')
} }
function sideSendQuery (query) {
return lock.acquire('home', async () => {
logger.debug('Sending side query')
const encodedABI = query.encodeABI()
const senderResponse = await sideSender({
data: encodedABI,
to: SIDE_SHARED_DB_ADDRESS,
nonce: sideValidatorNonce
})
if (senderResponse !== true) {
sideValidatorNonce++
}
return senderResponse
})
}
function homeSendQuery (query) {
return lock.acquire('home', async () => {
logger.debug('Sending home query')
const encodedABI = query.encodeABI()
const senderResponse = await homeSender({
data: encodedABI,
to: HOME_BRIDGE_ADDRESS,
nonce: homeValidatorNonce
})
if (senderResponse !== true) {
homeValidatorNonce++
}
return senderResponse
})
}
function parseReason (message) {
const result = /(?<="reason":").*?(?=")/.exec(message)
return result ? result[0] : ''
}
function parseError (message) {
const result = /(?<="error":").*?(?=")/.exec(message)
return result ? result[0] : ''
}
async function sendVote(query, req, res, waitFlag = false) { async function sendVote(query, req, res, waitFlag = false) {
try { try {
const sentQuery = await homeSendQuery(query) const sentQuery = await homeSendQuery(query)
@ -308,6 +302,8 @@ function decodeStatus (status) {
return 'keygen' return 'keygen'
case 3: case 3:
return 'funds_transfer' return 'funds_transfer'
default:
return 'unknown_state'
} }
} }
@ -319,32 +315,101 @@ function boundX (x) {
} }
} }
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) { 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

@ -14,10 +14,10 @@ function sendRpcRequest (url, method, params) {
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)
}) })
} }
@ -27,8 +27,8 @@ async function createSender (url, privateKey) {
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,13 +38,13 @@ 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)}`
}]) }])
@ -52,8 +52,9 @@ async function createSender (url, privateKey) {
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))
@ -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
@ -81,11 +85,14 @@ async function waitForReceipt (url, txHash) {
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) {
logger.debug('Failed to connect to rabbitmqServer, reconnecting')
await new Promise((resolve) => setTimeout(resolve, 2000))
}
}
} }
async function assertQueue(channel, name) { async function assertQueue(channel, name) {
const queue = await channel.assertQueue(name) const queue = await channel.assertQueue(name)
return { return {
name: queue.queue, name: queue.queue,
send: msg => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), { send: (msg) => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), {
persistent: true persistent: true
}), }),
get: consumer => channel.get(queue.queue, consumer), get: (consumer) => channel.get(queue.queue, consumer),
consume: consumer => channel.consume(queue.queue, consumer) consume: (consumer) => channel.consume(queue.queue, consumer)
} }
} }
module.exports = { connectRabbit, assertQueue } module.exports = {
connectRabbit,
assertQueue
}

View File

@ -1,6 +1,26 @@
const crypto = require('crypto') const crypto = require('crypto')
const bech32 = require('bech32') const bech32 = require('bech32')
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 }) { 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'))
@ -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,16 +9,14 @@ 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 confirmKeygen(keysFile) {
exec.execSync(`curl -X POST -H "Content-Type: application/json" -d @"${keysFile}" "${PROXY_URL}/confirmKeygen"`, { stdio: 'pipe' })
}
async function main() { 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)
@ -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,7 +39,10 @@ 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({
parties: parties.toString(),
threshold: threshold.toString()
}))
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => { const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
currentKeygenEpoch = null currentKeygenEpoch = null
if (fs.existsSync(keysFile)) { if (fs.existsSync(keysFile)) {
@ -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,117 +25,10 @@ let ready = false
let exchangeQueue let exchangeQueue
let channel let channel
async function main () {
logger.info('Connecting to RabbitMQ server')
channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to signature events queue')
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
const signQueue = await assertQueue(channel, 'signQueue')
while (!ready) {
await new Promise(res => setTimeout(res, 1000))
}
channel.prefetch(1)
signQueue.consume(async msg => {
const data = JSON.parse(msg.content)
logger.info('Consumed sign event: %o', data)
const { nonce, epoch, newEpoch, parties, threshold } = data
const keysFile = `/keys/keys${epoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile)
if (from === '') {
logger.info('No keys found, acking message')
channel.ack(msg)
return
}
const account = await getAccount(from)
logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() }))
attempt = 1
if (!newEpoch) {
const exchanges = await getExchangeMessages(nonce)
const exchangesData = exchanges.map(msg => JSON.parse(msg.content))
if (exchanges.length > 0 && account.sequence <= nonce) {
const recipients = exchangesData.map(({ value, recipient }) => ({ to: recipient, tokens: value }))
while (true) {
logger.info(`Building corresponding transfer transaction, nonce ${nonce}`)
const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
recipients,
asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}`
})
const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
if (done) {
exchanges.forEach(msg => channel.ack(msg))
break
}
attempt = nextAttempt ? nextAttempt : attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
} else if (account.sequence <= nonce) {
const newKeysFile = `/keys/keys${newEpoch}.store`
const { address: to } = getAccountFromFile(newKeysFile)
while (to !== '') {
logger.info(`Building corresponding transaction for transferring all funds, nonce ${nonce}, recipient ${to}`)
const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
recipients: [ {
to,
tokens: account.balances.find(x => x.symbol === FOREIGN_ASSET).free,
bnbs: new BN(account.balances.find(x => x.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8)),
} ],
asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}`
})
const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
if (done) {
await confirmFundsTransfer()
break
}
attempt = nextAttempt ? nextAttempt : attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null
await new Promise(resolve => setTimeout(resolve, 1000))
}
} else {
logger.debug('Tx has been already sent')
}
logger.info('Acking message')
channel.ack(msg)
})
}
main()
async function getExchangeMessages(nonce) { async function getExchangeMessages(nonce) {
logger.debug('Getting exchange messages') logger.debug('Getting exchange messages')
const messages = [] const messages = []
do { while (true) {
const msg = await exchangeQueue.get() const msg = await exchangeQueue.get()
if (msg === false) { if (msg === false) {
break break
@ -151,38 +40,11 @@ async function getExchangeMessages (nonce) {
break break
} }
messages.push(msg) messages.push(msg)
} while (true) }
logger.debug(`Found ${messages.length} messages`) logger.debug(`Found ${messages.length} messages`)
return 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) { function restart(req, res) {
logger.info('Cancelling current sign') logger.info('Cancelling current sign')
nextAttempt = req.params.attempt nextAttempt = req.params.attempt
@ -204,48 +66,206 @@ function getAccountFromFile (file) {
const publicKey = JSON.parse(fs.readFileSync(file))[5] const publicKey = JSON.parse(fs.readFileSync(file))[5]
return { return {
address: publicKeyToAddress(publicKey), address: publicKeyToAddress(publicKey),
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) { function getAccount(address) {
logger.info(`Getting account ${address} data`) logger.info(`Getting account ${address} data`)
return httpClient return httpClient
.get(`/api/v1/account/${address}`) .get(`/api/v1/account/${address}`)
.then(res => res.data) .then((res) => res.data)
.catch(() => { .catch(() => {
logger.debug('Retrying') logger.debug('Retrying')
return getAccount(address) 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) { function sendTx(tx) {
return httpClient return httpClient
.post(`/api/v1/broadcast?sync=true`, tx, { .post('/api/v1/broadcast?sync=true', tx, {
headers: { headers: {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
} }
}) })
.catch(err => { .catch((err) => {
if (err.response.data.message.includes('Tx already exists in cache')) if (err.response.data.message.includes('Tx already exists in cache')) {
logger.debug('Tx already exists in cache') logger.debug('Tx already exists in cache')
else { return true
logger.info('Something failed, restarting: %o', err.response)
return new Promise(resolve => setTimeout(() => resolve(sendTx(tx)), 1000))
} }
logger.info('Something failed, restarting: %o', err.response)
return new Promise((resolve) => setTimeout(() => resolve(sendTx(tx)), 1000))
}) })
} }
function sign(keysFile, hash, tx, publicKey) {
return new Promise((resolve) => {
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async (error) => {
if (fs.existsSync('signature')) {
logger.info('Finished signature generation')
const signature = JSON.parse(fs.readFileSync('signature'))
logger.debug('%o', signature)
logger.info('Building signed transaction')
const signedTx = tx.addSignature(publicKey, {
r: signature[1],
s: signature[3]
})
logger.info('Sending transaction')
logger.debug(signedTx)
await sendTx(signedTx)
resolve(true)
} else if (error === null || error.code === 0) {
resolve(true)
} else {
logger.warn('Sign failed')
resolve(false)
}
})
cmd.stdout.on('data', (data) => logger.debug(data.toString()))
cmd.stderr.on('data', (data) => logger.debug(data.toString()))
})
}
async function main() {
logger.info('Connecting to RabbitMQ server')
channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to signature events queue')
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
const signQueue = await assertQueue(channel, 'signQueue')
while (!ready) {
await new Promise((res) => setTimeout(res, 1000))
}
channel.prefetch(1)
signQueue.consume(async (msg) => {
const data = JSON.parse(msg.content)
logger.info('Consumed sign event: %o', data)
const {
nonce, epoch, newEpoch, parties, threshold
} = data
const keysFile = `/keys/keys${epoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile)
if (from === '') {
logger.info('No keys found, acking message')
channel.ack(msg)
return
}
const account = await getAccount(from)
logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({
parties: parties.toString(),
threshold: threshold.toString()
}))
attempt = 1
if (!newEpoch) {
const exchanges = await getExchangeMessages(nonce)
const exchangesData = exchanges.map((exchangeMsg) => JSON.parse(exchangeMsg.content))
if (exchanges.length > 0 && account.sequence <= nonce) {
const recipients = exchangesData.map(({ value, recipient }) => ({
to: recipient,
tokens: value
}))
while (true) {
logger.info(`Building corresponding transfer transaction, nonce ${nonce}`)
const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
recipients,
asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}`
})
const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const done = await sign(keysFile, hash, tx, publicKey)
&& await waitForAccountNonce(from, nonce + 1)
if (done) {
// eslint-disable-next-line no-loop-func
exchanges.forEach((exchangeMsg) => channel.ack(exchangeMsg))
break
}
attempt = nextAttempt || attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null
await new Promise((resolve) => setTimeout(resolve, 1000))
}
}
} else if (account.sequence <= nonce) {
const newKeysFile = `/keys/keys${newEpoch}.store`
const { address: to } = getAccountFromFile(newKeysFile)
while (to !== '') {
logger.info(`Building corresponding transaction for transferring all funds, nonce ${nonce}, recipient ${to}`)
const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
recipients: [{
to,
tokens: account.balances.find((token) => token.symbol === FOREIGN_ASSET).free,
bnbs: new BN(account.balances.find((token) => token.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8))
}],
asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}`
})
const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const done = await sign(keysFile, hash, tx, publicKey)
&& await waitForAccountNonce(from, nonce + 1)
if (done) {
await confirmFundsTransfer()
break
}
attempt = nextAttempt || attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null
await new Promise((resolve) => setTimeout(resolve, 1000))
}
} else {
logger.debug('Tx has been already sent')
}
logger.info('Acking message')
channel.ack(msg)
})
}
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()

View File

@ -11,21 +11,29 @@ 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)
@ -39,13 +47,15 @@ class Transaction {
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,7 +88,7 @@ class Transaction {
memo, memo,
msg, msg,
sequence, sequence,
type: msg.msgType, type: msg.msgType
}) })
} }
@ -86,14 +102,16 @@ class Transaction {
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 })
function main() {
httpClient httpClient
.get(`/api/v1/account/${address}`) .get(`/api/v1/account/${address}`)
.then(res => { .then((res) => {
console.log(`BNB: ${parseFloat(res.data.balances.find(x => x.symbol === 'BNB').free)}`) console.log(`BNB: ${parseFloat(res.data.balances.find((token) => token.symbol === 'BNB').free)}`)
console.log(`${FOREIGN_ASSET}: ${parseFloat(res.data.balances.find(x => x.symbol === FOREIGN_ASSET).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

@ -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)
function main() {
const address = process.argv[2] 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)
.toFixed(8, 3)))
.then((balance) => console.log(`${balance.toString()} tokens`))
.catch(() => console.log('0 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
@ -16,19 +18,20 @@ 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,20 +2,6 @@ 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]
const ethAddress = utils.computeAddress(privateKey)
const publicKey = utils.computePublicKey(privateKey, true)
console.log(`Eth address: ${ethAddress}\nBnc address: ${publicKeyToAddress(publicKey)}`)
function publicKeyToAddress (publicKey) {
const sha256Hash = sha256(Buffer.from(publicKey.substr(2), 'hex'))
const hash = ripemd160(Buffer.from(sha256Hash, 'hex'))
const words = bech32.toWords(Buffer.from(hash, 'hex'))
return bech32.encode('tbnb', words)
}
function sha256(bytes) { function sha256(bytes) {
return crypto.createHash('sha256').update(bytes).digest('hex') return crypto.createHash('sha256').update(bytes).digest('hex')
} }
@ -23,3 +9,21 @@ function sha256 (bytes) {
function ripemd160(bytes) { function ripemd160(bytes) {
return crypto.createHash('ripemd160').update(bytes).digest('hex') return crypto.createHash('ripemd160').update(bytes).digest('hex')
} }
function publicKeyToAddress(publicKey) {
const sha256Hash = sha256(Buffer.from(publicKey.substr(2), 'hex'))
const hash = ripemd160(Buffer.from(sha256Hash, 'hex'))
const words = bech32.toWords(Buffer.from(hash, 'hex'))
return bech32.encode('tbnb', words)
}
function main() {
const privateKey = process.argv[2].startsWith('0x') ? process.argv[2] : `0x${process.argv[2]}`
const ethAddress = utils.computeAddress(privateKey)
const publicKey = utils.computePublicKey(privateKey, true)
console.log(`Eth address: ${ethAddress}\nBnc address: ${publicKeyToAddress(publicKey)}`)
}
main()

View File

@ -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,14 +5,15 @@ 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()
info = initialInfo
nextValidators = [...initialInfo.validators, newValidator] nextValidators = [...initialInfo.validators, newValidator]
}) })
@ -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

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

@ -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,14 +1,15 @@
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) {
@ -17,16 +18,17 @@ async function retry (n, getPromise) {
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
} }