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
} = process.env
module.exports = deployer => {
module.exports = (deployer) => {
deployer.deploy(
Bridge,
THRESHOLD,

View File

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

View File

@ -9,13 +9,72 @@ const { publicKeyToAddress } = require('./crypto')
const { FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME)
const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL)
const FOREIGN_FETCH_BLOCK_TIME_OFFSET = parseInt(process.env.FOREIGN_FETCH_BLOCK_TIME_OFFSET)
const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME, 10)
const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL, 10)
const FOREIGN_FETCH_BLOCK_TIME_OFFSET = parseInt(process.env.FOREIGN_FETCH_BLOCK_TIME_OFFSET, 10)
const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL })
const proxyHttpClient = axios.create({ baseURL: PROXY_URL })
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() {
if (await redis.get('foreignTime') === null) {
logger.info('Set default foreign time')
@ -26,21 +85,23 @@ async function initialize () {
async function main() {
const { transactions, endTime } = await fetchNewTransactions()
if (!transactions || transactions.length === 0) {
logger.debug(`Found 0 new transactions`)
await new Promise(r => setTimeout(r, FOREIGN_FETCH_INTERVAL))
logger.debug('Found 0 new transactions')
await new Promise((r) => setTimeout(r, FOREIGN_FETCH_INTERVAL))
return
}
logger.info(`Found ${transactions.length} new transactions`)
logger.trace('%o', transactions)
for (const tx of transactions.reverse()) {
for (let i = transactions.length - 1; i >= 0; i -= 1) {
const tx = transactions[i]
if (tx.memo !== 'funding') {
const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value
await proxyHttpClient
.post('/transfer', {
to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')),
value: new BN(tx.value).multipliedBy(10 ** 18).integerValue(),
value: new BN(tx.value).multipliedBy(10 ** 18)
.integerValue(),
hash: `0x${tx.txHash}`
})
}
@ -48,60 +109,8 @@ async function main () {
await redis.set('foreignTime', endTime)
}
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')) + 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 () => {
initialize()
.then(async () => {
while (true) {
await main()
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
const Web3 = require('web3')
const utils = require('ethers').utils
const { utils } = require('ethers')
const BN = require('bignumber.js')
const axios = require('axios')
@ -10,19 +10,21 @@ const { publicKeyToAddress } = require('./crypto')
const abiBridge = require('./contracts_data/Bridge.json').abi
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL, HOME_START_BLOCK, VALIDATOR_PRIVATE_KEY } = process.env
const {
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL, HOME_START_BLOCK, VALIDATOR_PRIVATE_KEY
} = process.env
const homeWeb3 = new Web3(HOME_RPC_URL)
const bridge = new homeWeb3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
const foreignNonce = []
let channel
let exchangeQueue
let signQueue
let keygenQueue
let cancelKeygenQueue
let blockNumber
let foreignNonce = []
let epoch
let epochStart
let redisTx
@ -36,10 +38,11 @@ async function resetFutureMessages (queue) {
if (messageCount) {
logger.info(`Filtering ${messageCount} reloaded messages from queue ${queue.name}`)
const backup = await assertQueue(channel, `${queue.name}.backup`)
do {
while (true) {
const message = await queue.get()
if (message === false)
if (message === false) {
break
}
const data = JSON.parse(message.content)
if (data.blockNumber < blockNumber) {
logger.debug('Saving message %o', data)
@ -48,140 +51,42 @@ async function resetFutureMessages (queue) {
logger.debug('Dropping message %o', data)
}
channel.ack(message)
} while (true)
}
logger.debug('Dropped messages came from future')
do {
while (true) {
const message = await backup.get()
if (message === false)
if (message === false) {
break
}
const data = JSON.parse(message.content)
logger.debug('Requeuing message %o', data)
queue.send(data)
channel.ack(message)
} while (true)
}
logger.debug('Redirected messages back to initial queue')
}
}
async function initialize () {
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) {
const newEpoch = event.returnValues.newEpoch.toNumber()
keygenQueue.send({
epoch: newEpoch,
blockNumber,
threshold: (await bridge.methods.getThreshold(newEpoch).call()).toNumber(),
parties: (await bridge.methods.getParties(newEpoch).call()).toNumber()
threshold: (await bridge.methods.getThreshold(newEpoch)
.call()).toNumber(),
parties: (await bridge.methods.getParties(newEpoch)
.call()).toNumber()
})
logger.debug('Sent keygen start event')
}
function sendKeygenCancellation(event) {
const epoch = event.returnValues.epoch.toNumber()
const eventEpoch = event.returnValues.epoch.toNumber()
cancelKeygenQueue.send({
epoch,
epoch: eventEpoch,
blockNumber
})
logger.debug('Sent keygen cancellation event')
@ -195,11 +100,13 @@ async function sendSignFundsTransfer (event) {
blockNumber,
newEpoch,
nonce: foreignNonce[oldEpoch],
threshold: (await bridge.methods.getThreshold(oldEpoch).call()).toNumber(),
parties: (await bridge.methods.getParties(oldEpoch).call()).toNumber()
threshold: (await bridge.methods.getThreshold(oldEpoch)
.call()).toNumber(),
parties: (await bridge.methods.getParties(oldEpoch)
.call()).toNumber()
})
logger.debug('Sent sign funds transfer event')
foreignNonce[oldEpoch]++
foreignNonce[oldEpoch] += 1
redisTx.incr(`foreignNonce${oldEpoch}`)
}
@ -215,7 +122,11 @@ async function sendSign (event) {
chainId: await homeWeb3.eth.net.getId()
})
const hash = homeWeb3.utils.sha3(msg)
const publicKey = utils.recoverPublicKey(hash, { r: tx.r, s: tx.s, v: tx.v })
const publicKey = utils.recoverPublicKey(hash, {
r: tx.r,
s: tx.s,
v: tx.v
})
const msgToQueue = {
epoch,
blockNumber,
@ -223,7 +134,8 @@ async function sendSign (event) {
x: publicKey.substr(4, 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()
}
@ -240,18 +152,23 @@ async function sendStartSign () {
signQueue.send({
epoch,
blockNumber,
nonce: foreignNonce[epoch]++,
threshold: (await bridge.methods.getThreshold(epoch).call()).toNumber(),
parties: (await bridge.methods.getParties(epoch).call()).toNumber()
nonce: foreignNonce[epoch],
threshold: (await bridge.methods.getThreshold(epoch)
.call()).toNumber(),
parties: (await bridge.methods.getParties(epoch)
.call()).toNumber()
})
foreignNonce[epoch] += 1
}
async function processEpochStart(event) {
epoch = event.returnValues.epoch.toNumber()
epochStart = blockNumber
logger.info(`Epoch ${epoch} started`)
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber()
isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress)
rangeSize = (await bridge.methods.getRangeSize()
.call()).toNumber()
isCurrentValidator = (await bridge.methods.getValidators()
.call()).includes(validatorAddress)
if (isCurrentValidator) {
logger.info(`${validatorAddress} is a current validator`)
} else {
@ -260,3 +177,115 @@ async function processEpochStart (event) {
logger.info(`Updated range size to ${rangeSize}`)
foreignNonce[epoch] = 0
}
async function initialize() {
channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
signQueue = await assertQueue(channel, 'signQueue')
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-pretty": "3.2.1",
"axios": "0.19.0"
},
"engines": {
"node": ">=10.6.0"
}
}

View File

@ -4,15 +4,16 @@ function Tokenizer (_buffer) {
const buffer = _buffer
let position = 0
return {
isEmpty: function () {
isEmpty() {
return position === buffer.length
},
parse: function (length = 32, base = 16) {
parse(length = 32, base = 16) {
const res = new BN(buffer.slice(position, position + length)).toString(base)
position += length
return res
},
byte: function () {
byte() {
// eslint-disable-next-line no-plusplus
return buffer[position++]
}
}
@ -21,7 +22,7 @@ function Tokenizer (_buffer) {
const keygenDecoders = [
null,
// round 1
function (tokenizer) {
(tokenizer) => {
const res = {
e: {
n: tokenizer.parse(256, 10)
@ -37,23 +38,21 @@ const keygenDecoders = [
return res
},
// round 2
function (tokenizer) {
return {
(tokenizer) => ({
blind_factor: tokenizer.parse(),
y_i: {
x: tokenizer.parse(),
y: tokenizer.parse()
}
}
},
}),
// round 3
function (tokenizer) {
(tokenizer) => {
const res = {
ciphertext: [],
tag: []
}
const ciphertextLength = tokenizer.byte() // probably 32
for (let i = 0; i < ciphertextLength; i++) {
for (let i = 0; i < ciphertextLength; i += 1) {
res.ciphertext.push(tokenizer.byte())
}
while (!tokenizer.isEmpty()) {
@ -62,7 +61,7 @@ const keygenDecoders = [
return res
},
// round 4
function (tokenizer) {
(tokenizer) => {
const res = {
parameters: {
threshold: tokenizer.byte(),
@ -73,14 +72,55 @@ const keygenDecoders = [
while (!tokenizer.isEmpty()) {
res.commitments.push({
x: tokenizer.parse(),
y: tokenizer.parse(),
y: tokenizer.parse()
})
}
return res
},
// round 5
function (tokenizer) {
return {
(tokenizer) => ({
pk: {
x: tokenizer.parse(),
y: tokenizer.parse()
},
pk_t_rand_commitment: {
x: tokenizer.parse(),
y: tokenizer.parse()
},
challenge_response: tokenizer.parse()
})
]
const signDecoders = [
// round 0
(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: {
x: tokenizer.parse(),
y: tokenizer.parse()
@ -92,79 +132,25 @@ const keygenDecoders = [
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
},
// round 3
function (tokenizer) {
return tokenizer.parse()
},
(tokenizer) => tokenizer.parse(),
// round 4
function (tokenizer) {
return {
(tokenizer) => ({
blind_factor: tokenizer.parse(),
g_gamma_i: {
x: tokenizer.parse(),
y: tokenizer.parse()
}
}
},
}),
// round 5
function (tokenizer) {
return {
(tokenizer) => ({
com: tokenizer.parse()
}
},
}),
// round 6
function (tokenizer) {
return [
(tokenizer) => [
{
V_i: {
x: tokenizer.parse(),
@ -192,17 +178,13 @@ const signDecoders = [
z1: tokenizer.parse(),
z2: tokenizer.parse()
}
]
},
],
// round 7
function (tokenizer) {
return {
(tokenizer) => ({
com: tokenizer.parse()
}
},
}),
// round 8
function (tokenizer) {
return {
(tokenizer) => ({
u_i: {
x: tokenizer.parse(),
y: tokenizer.parse()
@ -212,18 +194,17 @@ const signDecoders = [
y: tokenizer.parse()
},
blind_factor: tokenizer.parse()
}
},
}),
// round 9
function (tokenizer) {
return tokenizer.parse()
},
(tokenizer) => tokenizer.parse()
]
module.exports = function (isKeygen, round, value) {
value = Buffer.from(value.substr(2), 'hex')
const tokenizer = Tokenizer(value)
const roundNumber = parseInt(round[round.length - 1])
function decode(isKeygen, round, value) {
const newValue = Buffer.from(value.substr(2), 'hex')
const tokenizer = Tokenizer(newValue)
const roundNumber = parseInt(round[round.length - 1], 10)
const decoder = (isKeygen ? keygenDecoders : signDecoders)[roundNumber]
return JSON.stringify(decoder(tokenizer))
}
module.exports = decode

View File

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

View File

@ -12,8 +12,8 @@ const logger = require('./logger')
const { publicKeyToAddress } = require('./crypto')
const {
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID,
SIDE_CHAIN_ID, HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY,
HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET
} = process.env
const abiSharedDb = require('./contracts_data/SharedDB.json').abi
const abiBridge = require('./contracts_data/Bridge.json').abi
@ -39,26 +39,7 @@ const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.post('/get', get)
app.post('/set', set)
app.post('/signupkeygen', signupKeygen)
app.post('/signupsign', signupSign)
app.post('/confirmKeygen', confirmKeygen)
app.post('/confirmFundsTransfer', confirmFundsTransfer)
app.post('/transfer', transfer)
const votesProxyApp = express()
votesProxyApp.use(express.json())
votesProxyApp.use(express.urlencoded({ extended: true }))
votesProxyApp.get('/vote/startVoting', voteStartVoting)
votesProxyApp.get('/vote/startKeygen', voteStartKeygen)
votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen)
votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold)
votesProxyApp.get('/info', info)
async function main() {
homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress)
@ -88,27 +69,69 @@ function 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) {
logger.debug('Get call, %o', req.body.key)
const round = req.body.key.second
const uuid = req.body.key.third
let from
if (uuid.startsWith('k'))
from = (await bridge.methods.getNextValidators().call())[parseInt(req.body.key.first) - 1]
else {
const validators = await bridge.methods.getValidators().call()
from = await sharedDb.methods.getSignupAddress(uuid, validators, parseInt(req.body.key.first)).call()
if (uuid.startsWith('k')) {
from = (await bridge.methods.getNextValidators()
.call())[parseInt(req.body.key.first, 10) - 1]
} else {
const validators = await bridge.methods.getValidators()
.call()
from = await sharedDb.methods.getSignupAddress(
uuid,
validators, parseInt(req.body.key.first, 10)
)
.call()
}
const to = Number(req.body.key.fourth) // 0 if empty
const key = homeWeb3.utils.sha3(`${round}_${to}`)
const data = await sharedDb.methods.getData(from, sideWeb3.utils.sha3(uuid), key).call()
const data = await sharedDb.methods.getData(from, sideWeb3.utils.sha3(uuid), key)
.call()
if (data.length > 2) {
logger.trace(`Received encoded data: ${data}`)
const decoded = decode(uuid[0] === 'k', round, data)
logger.trace('Decoded data: %o', decoded)
res.send(Ok({ key: req.body.key, value: decoded }))
res.send(Ok({
key: req.body.key,
value: decoded
}))
} else {
setTimeout(() => res.send(Err(null)), 1000)
}
@ -136,14 +159,19 @@ async function set (req, res) {
async function signupKeygen(req, res) {
logger.debug('SignupKeygen call')
const epoch = (await bridge.methods.nextEpoch().call()).toNumber()
const partyId = (await bridge.methods.getNextPartyId(validatorAddress).call()).toNumber()
const epoch = (await bridge.methods.nextEpoch()
.call()).toNumber()
const partyId = (await bridge.methods.getNextPartyId(validatorAddress)
.call()).toNumber()
if (partyId === 0) {
res.send(Err({ message: 'Not a validator' }))
logger.debug('Not a validator')
} else {
res.send(Ok({ uuid: `k${epoch}`, number: partyId }))
res.send(Ok({
uuid: `k${epoch}`,
number: partyId
}))
logger.debug('SignupKeygen end')
}
}
@ -157,15 +185,23 @@ async function signupSign (req, res) {
// Already have signup
if (receipt.status === false) {
res.send(Ok({ uuid: hash, number: 0 }))
res.send(Ok({
uuid: hash,
number: 0
}))
logger.debug('Already have signup')
return
}
const validators = await bridge.methods.getValidators().call()
const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress).call()).toNumber()
const validators = await bridge.methods.getValidators()
.call()
const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress)
.call()).toNumber()
res.send(Ok({ uuid: hash, number: id }))
res.send(Ok({
uuid: hash,
number: id
}))
logger.debug('SignupSign end')
}
@ -186,48 +222,6 @@ async function confirmFundsTransfer (req, res) {
logger.debug('Confirm funds transfer end')
}
function sideSendQuery (query) {
return lock.acquire('home', async () => {
logger.debug('Sending side query')
const encodedABI = query.encodeABI()
const senderResponse = await sideSender({
data: encodedABI,
to: SIDE_SHARED_DB_ADDRESS,
nonce: sideValidatorNonce
})
if (senderResponse !== true) {
sideValidatorNonce++
}
return senderResponse
})
}
function homeSendQuery (query) {
return lock.acquire('home', async () => {
logger.debug('Sending home query')
const encodedABI = query.encodeABI()
const senderResponse = await homeSender({
data: encodedABI,
to: HOME_BRIDGE_ADDRESS,
nonce: homeValidatorNonce
})
if (senderResponse !== true) {
homeValidatorNonce++
}
return senderResponse
})
}
function parseReason (message) {
const result = /(?<="reason":").*?(?=")/.exec(message)
return result ? result[0] : ''
}
function parseError (message) {
const result = /(?<="error":").*?(?=")/.exec(message)
return result ? result[0] : ''
}
async function sendVote(query, req, res, waitFlag = false) {
try {
const sentQuery = await homeSendQuery(query)
@ -308,6 +302,8 @@ function decodeStatus (status) {
return 'keygen'
case 3:
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) {
logger.debug('Info start')
try {
const [ x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch, threshold, nextThreshold, validators, nextValidators, status, homeBalance ] = await Promise.all([
bridge.methods.getX().call().then(x => new BN(x).toString(16)),
bridge.methods.getY().call().then(x => new BN(x).toString(16)),
bridge.methods.epoch().call().then(x => x.toNumber()),
bridge.methods.getRangeSize().call().then(x => x.toNumber()),
bridge.methods.getNextRangeSize().call().then(x => x.toNumber()),
bridge.methods.getStartBlock().call().then(x => x.toNumber()),
bridge.methods.getNonce().call().then(boundX),
bridge.methods.nextEpoch().call().then(x => x.toNumber()),
bridge.methods.getThreshold().call().then(x => x.toNumber()),
bridge.methods.getNextThreshold().call().then(x => x.toNumber()),
bridge.methods.getValidators().call(),
bridge.methods.getNextValidators().call(),
bridge.methods.status().call(),
token.methods.balanceOf(HOME_BRIDGE_ADDRESS).call().then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3)))
const [
x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch,
threshold, nextThreshold, validators, nextValidators, status, homeBalance
] = await Promise.all([
bridge.methods.getX()
.call()
.then((value) => new BN(value).toString(16)),
bridge.methods.getY()
.call()
.then((value) => new BN(value).toString(16)),
bridge.methods.epoch()
.call()
.then(toNumber),
bridge.methods.getRangeSize()
.call()
.then(toNumber),
bridge.methods.getNextRangeSize()
.call()
.then(toNumber),
bridge.methods.getStartBlock()
.call()
.then(toNumber),
bridge.methods.getNonce()
.call()
.then(boundX),
bridge.methods.nextEpoch()
.call()
.then(toNumber),
bridge.methods.getThreshold()
.call()
.then(toNumber),
bridge.methods.getNextThreshold()
.call()
.then(toNumber),
bridge.methods.getValidators()
.call(),
bridge.methods.getNextValidators()
.call(),
bridge.methods.status()
.call(),
token.methods.balanceOf(HOME_BRIDGE_ADDRESS)
.call()
.then((value) => parseFloat(new BN(value).dividedBy(10 ** 18)
.toFixed(8, 3)))
])
const [ confirmationsForFundsTransfer, votesForVoting, votesForKeygen, votesForCancelKeygen ] = await Promise.all([
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 1, nextEpoch ]))).call().then(boundX),
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 2, nextEpoch ]))).call().then(boundX),
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 7, nextEpoch ]))).call().then(boundX),
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 8, nextEpoch ]))).call().then(boundX)
const [
confirmationsForFundsTransfer, votesForVoting, votesForKeygen, votesForCancelKeygen
] = await Promise.all([
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [1, nextEpoch])))
.call()
.then(boundX),
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [2, nextEpoch])))
.call()
.then(boundX),
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [7, nextEpoch])))
.call()
.then(boundX),
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack(['uint8', 'uint256'], [8, nextEpoch])))
.call()
.then(boundX)
])
const foreignAddress = publicKeyToAddress({ x, y })
const foreignAddress = publicKeyToAddress({
x,
y
})
const balances = await getForeignBalances(foreignAddress)
const msg = {
epoch,
@ -361,7 +426,7 @@ async function info (req, res) {
nextValidators,
homeBalance,
foreignBalanceTokens: parseFloat(balances[FOREIGN_ASSET]) || 0,
foreignBalanceNative: parseFloat(balances['BNB']) || 0,
foreignBalanceNative: parseFloat(balances.BNB) || 0,
bridgeStatus: decodeStatus(status),
votesForVoting,
votesForKeygen,
@ -372,29 +437,27 @@ async function info (req, res) {
res.send(msg)
} catch (e) {
logger.debug('%o', e)
res.send({ message: 'Something went wrong, resend request', error: e })
res.send({
message: 'Something went wrong, resend request',
error: e
})
}
logger.debug('Info end')
}
async function transfer (req, res) {
logger.info('Transfer start')
const { hash, to, value } = req.body
if (homeWeb3.utils.isAddress(to)) {
logger.info(`Calling transfer to ${to}, ${value} tokens`)
const query = bridge.methods.transfer(hash, to, '0x' + (new BN(value).toString(16)))
await homeSendQuery(query)
}
res.send()
logger.info('Transfer end')
}
app.post('/get', get)
app.post('/set', set)
app.post('/signupkeygen', signupKeygen)
app.post('/signupsign', signupSign)
function getForeignBalances (address) {
return httpClient
.get(`/api/v1/account/${address}`)
.then(res => res.data.balances.reduce((prev, cur) => {
prev[cur.symbol] = cur.free
return prev
}, {}))
.catch(err => ({}))
}
app.post('/confirmKeygen', confirmKeygen)
app.post('/confirmFundsTransfer', confirmFundsTransfer)
app.post('/transfer', transfer)
votesProxyApp.get('/vote/startVoting', voteStartVoting)
votesProxyApp.get('/vote/startKeygen', voteStartKeygen)
votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen)
votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold)
votesProxyApp.get('/info', info)

View File

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

View File

@ -14,10 +14,10 @@ function sendRpcRequest (url, method, params) {
params,
id: 1
})
.then(res => res.data)
.catch(async e => {
.then((res) => res.data)
.catch(async () => {
logger.warn(`Request to ${url}, method ${method} failed, retrying`)
await new Promise(res => setTimeout(res, 1000))
await new Promise((res) => setTimeout(res, 1000))
return sendRpcRequest(url, method, params)
})
}
@ -27,8 +27,8 @@ async function createSender (url, privateKey) {
const signer = new ethers.utils.SigningKey(privateKey)
const chainId = await web3.eth.net.getId()
return async function (tx) {
tx = {
return async function send(tx) {
const newTx = {
data: tx.data,
to: tx.to,
nonce: tx.nonce,
@ -38,13 +38,13 @@ async function createSender (url, privateKey) {
}
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', [{
from: signer.address,
to: tx.to,
data: tx.data,
gasPrice: tx.gasPrice,
value: tx.value,
to: newTx.to,
data: newTx.data,
gasPrice: newTx.gasPrice,
value: newTx.value,
gas: `0x${new BN(MAX_GAS_LIMIT).toString(16)}`
}])
@ -52,8 +52,9 @@ async function createSender (url, privateKey) {
logger.debug('Gas estimate failed %o, skipping tx, reverting nonce', estimate.error)
return true
}
const gasLimit = BN.min(new BN(estimate.result, 16).multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT)
tx.gasLimit = `0x${new BN(gasLimit).toString(16)}`
const gasLimit = BN.min(new BN(estimate.result, 16)
.multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT)
newTx.gasLimit = `0x${new BN(gasLimit).toString(16)}`
logger.trace(`Estimated gas to ${gasLimit}`)
const hash = web3.utils.sha3(ethers.utils.serializeTransaction(tx))
@ -68,7 +69,10 @@ async function createSender (url, privateKey) {
return false
}
return { txHash: result, gasLimit: tx.gasLimit }
return {
txHash: result,
gasLimit: tx.gasLimit
}
} catch (e) {
logger.warn('Something failed, %o', e)
return false
@ -81,11 +85,14 @@ async function waitForReceipt (url, txHash) {
const { result, error } = await sendRpcRequest(url, 'eth_getTransactionReceipt', [txHash])
if (result === null || error) {
await new Promise(res => setTimeout(res, 1000))
await new Promise((res) => setTimeout(res, 1000))
} else {
return result
}
}
}
module.exports = { createSender, waitForReceipt }
module.exports = {
createSender,
waitForReceipt
}

View File

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

View File

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

View File

@ -2,30 +2,30 @@ const amqp = require('amqplib')
const logger = require('./logger')
function _connectRabbit (url) {
return amqp.connect(url).catch(() => {
logger.debug('Failed to connect to rabbitmqServer, reconnecting')
return new Promise(resolve =>
setTimeout(() => resolve(_connectRabbit(url)), 2000)
)
})
}
async function connectRabbit(url) {
const connection = await _connectRabbit(url)
return await connection.createChannel()
while (true) {
try {
return (await amqp.connect(url)).createChannel()
} catch (e) {
logger.debug('Failed to connect to rabbitmqServer, reconnecting')
await new Promise((resolve) => setTimeout(resolve, 2000))
}
}
}
async function assertQueue(channel, name) {
const queue = await channel.assertQueue(name)
return {
name: queue.queue,
send: msg => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), {
send: (msg) => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), {
persistent: true
}),
get: consumer => channel.get(queue.queue, consumer),
consume: consumer => channel.consume(queue.queue, consumer)
get: (consumer) => channel.get(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 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 }) {
const compact = (parseInt(y[y.length - 1], 16) % 2 ? '03' : '02') + padZeros(x, 64)
const sha256Hash = sha256(Buffer.from(compact, 'hex'))
@ -9,18 +29,8 @@ function publicKeyToAddress ({ x, y }) {
return bech32.encode('tbnb', words)
}
function padZeros (s, len) {
while (s.length < len)
s = '0' + s
return s
module.exports = {
publicKeyToAddress,
padZeros,
sha256
}
function sha256 (bytes) {
return crypto.createHash('sha256').update(bytes).digest('hex')
}
function ripemd160 (bytes) {
return crypto.createHash('ripemd160').update(bytes).digest('hex')
}
module.exports = { publicKeyToAddress, padZeros, sha256 }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,20 @@
const exec = require('child_process')
const fs = require('fs')
const BN = require('bignumber.js')
const axios = require('axios')
const express = require('express')
const logger = require('./logger')
const { connectRabbit, assertQueue } = require('./amqp')
const { publicKeyToAddress, sha256 } = require('./crypto')
const app = express()
app.get('/restart/:attempt', restart)
app.get('/start', (req, res) => {
logger.info('Ready to start')
ready = true
res.send()
})
app.listen(8001, () => logger.debug('Listening on 8001'))
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
const Transaction = require('./tx')
const axios = require('axios')
const app = express()
const {
RABBITMQ_URL, FOREIGN_URL, PROXY_URL, FOREIGN_ASSET
} = process.env
const httpClient = axios.create({ baseURL: FOREIGN_URL })
@ -29,117 +25,10 @@ let ready = false
let exchangeQueue
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) {
logger.debug('Getting exchange messages')
const messages = []
do {
while (true) {
const msg = await exchangeQueue.get()
if (msg === false) {
break
@ -151,38 +40,11 @@ async function getExchangeMessages (nonce) {
break
}
messages.push(msg)
} while (true)
}
logger.debug(`Found ${messages.length} messages`)
return messages
}
function sign (keysFile, hash, tx, publicKey) {
return new Promise(resolve => {
const cmd = exec.execFile('./sign-entrypoint.sh', [ PROXY_URL, keysFile, hash ], async (error) => {
if (fs.existsSync('signature')) {
logger.info('Finished signature generation')
const signature = JSON.parse(fs.readFileSync('signature'))
logger.debug('%o', signature)
logger.info('Building signed transaction')
const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] })
logger.info('Sending transaction')
logger.debug(signedTx)
await sendTx(signedTx)
resolve(true)
} else if (error === null || error.code === 0) {
resolve(true)
} else {
logger.warn('Sign failed')
resolve(false)
}
})
cmd.stdout.on('data', data => logger.debug(data.toString()))
cmd.stderr.on('data', data => logger.debug(data.toString()))
})
}
function restart(req, res) {
logger.info('Cancelling current sign')
nextAttempt = req.params.attempt
@ -204,48 +66,206 @@ function getAccountFromFile (file) {
const publicKey = JSON.parse(fs.readFileSync(file))[5]
return {
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) {
logger.info(`Getting account ${address} data`)
return httpClient
.get(`/api/v1/account/${address}`)
.then(res => res.data)
.then((res) => res.data)
.catch(() => {
logger.debug('Retrying')
return getAccount(address)
})
}
async function waitForAccountNonce(address, nonce) {
cancelled = false
logger.info(`Waiting for account ${address} to have nonce ${nonce}`)
while (!cancelled) {
const { sequence } = await getAccount(address)
if (sequence >= nonce) {
break
}
await new Promise((resolve) => setTimeout(resolve, 1000))
logger.debug('Waiting for needed account nonce')
}
logger.info('Account nonce is OK')
return !cancelled
}
function sendTx(tx) {
return httpClient
.post(`/api/v1/broadcast?sync=true`, tx, {
.post('/api/v1/broadcast?sync=true', tx, {
headers: {
'Content-Type': 'text/plain'
}
})
.catch(err => {
if (err.response.data.message.includes('Tx already exists in cache'))
.catch((err) => {
if (err.response.data.message.includes('Tx already exists in cache')) {
logger.debug('Tx already exists in cache')
else {
logger.info('Something failed, restarting: %o', err.response)
return new Promise(resolve => setTimeout(() => resolve(sendTx(tx)), 1000))
return true
}
logger.info('Something failed, restarting: %o', err.response)
return new Promise((resolve) => setTimeout(() => resolve(sendTx(tx)), 1000))
})
}
function sign(keysFile, hash, tx, publicKey) {
return new Promise((resolve) => {
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async (error) => {
if (fs.existsSync('signature')) {
logger.info('Finished signature generation')
const signature = JSON.parse(fs.readFileSync('signature'))
logger.debug('%o', signature)
logger.info('Building signed transaction')
const signedTx = tx.addSignature(publicKey, {
r: signature[1],
s: signature[3]
})
logger.info('Sending transaction')
logger.debug(signedTx)
await sendTx(signedTx)
resolve(true)
} else if (error === null || error.code === 0) {
resolve(true)
} else {
logger.warn('Sign failed')
resolve(false)
}
})
cmd.stdout.on('data', (data) => logger.debug(data.toString()))
cmd.stderr.on('data', (data) => logger.debug(data.toString()))
})
}
async function main() {
logger.info('Connecting to RabbitMQ server')
channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to signature events queue')
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 {
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 totalBnbs = recipients.reduce((sum, { bnbs }) => sum.plus(new BN(bnbs || 0)), new BN(0))
const totalTokens = recipients.reduce(
(sum, { tokens }) => sum.plus(new BN(tokens || 0)), new BN(0)
)
const totalBnbs = recipients.reduce(
(sum, { bnbs }) => sum.plus(new BN(bnbs || 0)), new BN(0)
)
const senderCoins = []
if (asset && totalTokens.isGreaterThan(0)) {
senderCoins.push({
denom: asset,
amount: totalTokens.multipliedBy(10 ** 8).toNumber(),
amount: totalTokens.multipliedBy(10 ** 8)
.toNumber()
})
}
if (totalBnbs.isGreaterThan(0)) {
senderCoins.push({
denom: BNB_ASSET,
amount: totalBnbs.multipliedBy(10 ** 8).toNumber(),
amount: totalBnbs.multipliedBy(10 ** 8)
.toNumber()
})
}
senderCoins.sort((a, b) => a.denom > b.denom)
@ -39,13 +47,15 @@ class Transaction {
if (asset && tokens) {
receiverCoins.push({
denom: asset,
amount: new BN(tokens).multipliedBy(10 ** 8).toNumber(),
amount: new BN(tokens).multipliedBy(10 ** 8)
.toNumber()
})
}
if (bnbs) {
receiverCoins.push({
denom: BNB_ASSET,
amount: new BN(bnbs).multipliedBy(10 ** 8).toNumber(),
amount: new BN(bnbs).multipliedBy(10 ** 8)
.toNumber()
})
}
receiverCoins.sort((a, b) => a.denom > b.denom)
@ -56,8 +66,14 @@ class Transaction {
})
const msg = {
inputs: inputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})),
outputs: outputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})),
inputs: inputs.map((x) => ({
...x,
address: crypto.decodeAddress(x.address)
})),
outputs: outputs.map((x) => ({
...x,
address: crypto.decodeAddress(x.address)
})),
msgType: 'MsgSend'
}
@ -72,7 +88,7 @@ class Transaction {
memo,
msg,
sequence,
type: msg.msgType,
type: msg.msgType
})
}
@ -86,14 +102,16 @@ class Transaction {
const s = new BN(signature.s, 16)
if (s.gt(n.div(2))) {
logger.debug('Normalizing s')
signature.s = n.minus(s).toString(16)
// eslint-disable-next-line no-param-reassign
signature.s = n.minus(s)
.toString(16)
}
const publicKeyEncoded = Buffer.from('eb5ae98721' + (yLast % 2 ? '03' : '02') + padZeros(publicKey.x, 64), 'hex')
const publicKeyEncoded = Buffer.from(`eb5ae98721${yLast % 2 ? '03' : '02'}${padZeros(publicKey.x, 64)}`, 'hex')
this.tx.signatures = [{
pub_key: publicKeyEncoded,
signature: Buffer.from(padZeros(signature.r, 64) + padZeros(signature.s, 64), 'hex'),
account_number: this.tx.account_number,
sequence: this.tx.sequence,
sequence: this.tx.sequence
}]
return this.tx.serialize()
}

View File

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

View File

@ -43,10 +43,11 @@ async function main () {
receipt = await client.transfer(from, to, tokens, FOREIGN_ASSET, 'exchange')
}
if (receipt.status === 200)
if (receipt.status === 200) {
console.log(receipt.result[0].hash)
else
} else {
console.log(receipt)
}
}
main()

View File

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

View File

@ -1,7 +1,9 @@
const Web3 = require('web3')
const BN = require('bignumber.js')
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_PRIVATE_KEY, HOME_TOKEN_ADDRESS } = process.env
const {
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_PRIVATE_KEY, HOME_TOKEN_ADDRESS
} = process.env
const abiToken = require('./IERC20').abi
const abiBridge = require('./Bridge').abi
@ -16,19 +18,20 @@ const sender = web3.eth.accounts.privateKeyToAccount(`0x${PRIVATE_KEY}`).address
async function main() {
const HOME_CHAIN_ID = await web3.eth.net.getId()
const blockGasLimit = (await web3.eth.getBlock("latest", false)).gasLimit
const blockGasLimit = (await web3.eth.getBlock('latest', false)).gasLimit
const to = process.argv[2]
const amount = parseInt(process.argv[3])
const amount = parseInt(process.argv[3], 10)
let coins = process.argv[4]
const txCount = await web3.eth.getTransactionCount(sender)
if (to === "bridge" && amount !== 0) {
if (to === 'bridge' && amount !== 0) {
console.log(`Transfer from ${sender} to ${HOME_BRIDGE_ADDRESS}, ${amount} tokens`)
const queryApprove = token.methods.approve(HOME_BRIDGE_ADDRESS, '0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16)))
const queryApprove = token.methods.approve(HOME_BRIDGE_ADDRESS, `0x${new BN(amount).multipliedBy(10 ** 18)
.toString(16)}`)
const txApprove = {
data: queryApprove.encodeABI(),
from: sender,
@ -42,9 +45,10 @@ async function main () {
const signedTxApprove = await web3.eth.accounts.signTransaction(txApprove, PRIVATE_KEY)
const receiptApprove = await web3.eth.sendSignedTransaction(signedTxApprove.rawTransaction)
console.log('txHash approve: ' + receiptApprove.transactionHash)
console.log(`txHash approve: ${receiptApprove.transactionHash}`)
const queryExchange = bridge.methods.exchange('0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16)))
const queryExchange = bridge.methods.exchange(`0x${new BN(amount).multipliedBy(10 ** 18)
.toString(16)}`)
const txExchange = {
data: queryExchange.encodeABI(),
from: sender,
@ -58,11 +62,12 @@ async function main () {
const signedTxExchange = await web3.eth.accounts.signTransaction(txExchange, PRIVATE_KEY)
const receiptExchange = await web3.eth.sendSignedTransaction(signedTxExchange.rawTransaction)
console.log('txHash exchange: ' + receiptExchange.transactionHash)
console.log(`txHash exchange: ${receiptExchange.transactionHash}`)
} else if (amount !== 0) {
console.log(`Transfer from ${sender} to ${to}, ${amount} tokens`)
const query = token.methods.transfer(to, '0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16)))
const query = token.methods.transfer(to, `0x${new BN(amount).multipliedBy(10 ** 18)
.toString(16)}`)
const tx = {
data: query.encodeABI(),
from: sender,
@ -75,7 +80,7 @@ async function main () {
}) * 1.5), blockGasLimit)
const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
console.log('txHash transfer: ' + receipt.transactionHash)
console.log(`txHash transfer: ${receipt.transactionHash}`)
}
if (coins) {
@ -85,7 +90,7 @@ async function main () {
const tx = {
data: '0x',
from: sender,
to: to,
to,
nonce: await web3.eth.getTransactionCount(sender),
chainId: HOME_CHAIN_ID,
value: web3.utils.toWei(new BN(coins).toString(), 'ether'),
@ -94,9 +99,8 @@ async function main () {
const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
console.log('txHash: ' + receipt.transactionHash)
console.log(`txHash: ${receipt.transactionHash}`)
}
}
main()

View File

@ -2,20 +2,6 @@ const { utils } = require('ethers')
const bech32 = require('bech32')
const crypto = require('crypto')
const privateKey = process.argv[2].startsWith('0x') ? process.argv[2] : '0x' + process.argv[2]
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) {
return crypto.createHash('sha256').update(bytes).digest('hex')
}
@ -23,3 +9,21 @@ function sha256 (bytes) {
function ripemd160(bytes) {
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`)
const tx_coins = {
const txCoins = {
data: '0x',
from: sender,
to: to,
to,
nonce: await web3.eth.getTransactionCount(sender),
chainId: SIDE_CHAIN_ID,
value: web3.utils.toWei(new BN(amount).toString(), 'ether'),
gas: 21000
}
const signedTx = await web3.eth.accounts.signTransaction(tx_coins, SIDE_PRIVATE_KEY)
const signedTx = await web3.eth.accounts.signTransaction(txCoins, SIDE_PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
console.log('txHash: ' + receipt.transactionHash)
console.log(`txHash: ${receipt.transactionHash}`)
}
main()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@ module.exports = async function main (privateKey) {
await delay(1000)
return {
transfer: async function (to, tokens, bnbs) {
async transfer(to, tokens, bnbs) {
const outputs = [{
to,
coins: []
@ -35,7 +35,7 @@ module.exports = async function main (privateKey) {
}
await client.multiSend(from, outputs, 'funding')
},
exchange: async function (to, value) {
async exchange(to, value) {
await client.transfer(from, to, value.toString(), FOREIGN_ASSET, 'exchange')
}
}

View File

@ -10,12 +10,12 @@ const bnc = axios.create({
})
module.exports = {
getBalance: async function (address) {
async getBalance(address) {
const response = await retry(5, () => bnc.get(`/api/v1/account/${address}`))
const tokens = response.data.balances.find(x => x.symbol === FOREIGN_ASSET)
const tokens = response.data.balances.find((x) => x.symbol === FOREIGN_ASSET)
return response && tokens ? parseFloat(tokens.free) : 0
},
getSequence: async function (address) {
async getSequence(address) {
const response = await retry(5, () => bnc.get(`/api/v1/account/${address}/sequence`))
return response ? response.data.sequence : 0
}

View File

@ -9,22 +9,22 @@ function createController (validatorId) {
})
return {
getInfo: async function () {
async getInfo() {
return (await proxy.get('/info')).data
},
voteStartVoting: async function () {
async voteStartVoting() {
return (await proxy.get('/vote/startVoting')).data
},
voteStartKeygen: async function () {
async voteStartKeygen() {
return (await proxy.get('/vote/startKeygen')).data
},
voteAddValidator: async function (validatorAddress) {
async voteAddValidator(validatorAddress) {
return (await proxy.get(`/vote/addValidator/${validatorAddress}`)).data
},
voteRemoveValidator: async function (validatorAddress) {
async voteRemoveValidator(validatorAddress) {
return (await proxy.get(`/vote/removeValidator/${validatorAddress}`)).data
},
voteChangeThreshold: async function (threshold) {
async voteChangeThreshold(threshold) {
return (await proxy.get(`/vote/changeThreshold/${threshold}`)).data
}
}

View File

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

View File

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