Fixed eslint error/warnings

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

View File

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

View File

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

View File

@ -9,70 +9,50 @@ const { publicKeyToAddress } = require('./crypto')
const { FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME)
const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL)
const FOREIGN_FETCH_BLOCK_TIME_OFFSET = parseInt(process.env.FOREIGN_FETCH_BLOCK_TIME_OFFSET)
const FOREIGN_START_TIME = parseInt(process.env.FOREIGN_START_TIME, 10)
const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL, 10)
const FOREIGN_FETCH_BLOCK_TIME_OFFSET = parseInt(process.env.FOREIGN_FETCH_BLOCK_TIME_OFFSET, 10)
const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL })
const proxyHttpClient = axios.create({ baseURL: PROXY_URL })
async function initialize () {
if (await redis.get('foreignTime') === null) {
logger.info('Set default foreign time')
await redis.set('foreignTime', FOREIGN_START_TIME)
function getLastForeignAddress() {
const epoch = Math.max(0, ...fs.readdirSync('/keys')
.map((x) => parseInt(x.split('.')[0].substr(4), 10)))
if (epoch === 0) {
return null
}
const keysFile = `/keys/keys${epoch}.store`
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
return publicKeyToAddress(publicKey)
}
async function main () {
const { transactions, endTime } = await fetchNewTransactions()
if (!transactions || transactions.length === 0) {
logger.debug(`Found 0 new transactions`)
await new Promise(r => setTimeout(r, FOREIGN_FETCH_INTERVAL))
return
}
logger.info(`Found ${transactions.length} new transactions`)
logger.trace('%o', transactions)
for (const tx of transactions.reverse()) {
if (tx.memo !== 'funding') {
const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value
await proxyHttpClient
.post('/transfer', {
to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')),
value: new BN(tx.value).multipliedBy(10 ** 18).integerValue(),
hash: `0x${tx.txHash}`
})
}
}
await redis.set('foreignTime', endTime)
}
function getTx (hash) {
function getTx(hash) {
return foreignHttpClient
.get(`/api/v1/tx/${hash}`, {
params: {
format: 'json'
}
})
.then(res => res.data.tx.value)
.then((res) => res.data.tx.value)
.catch(() => getTx(hash))
}
function getBlockTime () {
function getBlockTime() {
return foreignHttpClient
.get(`/api/v1/time`)
.then(res => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET)
.get('/api/v1/time')
.then((res) => Date.parse(res.data.block_time) - FOREIGN_FETCH_BLOCK_TIME_OFFSET)
.catch(() => getBlockTime())
}
async function fetchNewTransactions () {
async function fetchNewTransactions() {
logger.debug('Fetching new transactions')
const startTime = parseInt(await redis.get('foreignTime')) + 1
const startTime = parseInt(await redis.get('foreignTime'), 10) + 1
const address = getLastForeignAddress()
const endTime = Math.min(startTime + 3 * 30 * 24 * 60 * 60 * 1000, await getBlockTime())
if (address === null)
if (address === null) {
return {}
}
logger.debug('Sending api transactions request')
const params = {
address,
@ -80,29 +60,58 @@ async function fetchNewTransactions () {
txAsset: FOREIGN_ASSET,
txType: 'TRANSFER',
startTime,
endTime,
endTime
}
try {
logger.trace('%o', params)
const transactions = (await foreignHttpClient
.get('/api/v1/transactions', { params })).data.tx
return { transactions, endTime }
return {
transactions,
endTime
}
} catch (e) {
return await fetchNewTransactions()
}
}
function getLastForeignAddress () {
const epoch = Math.max(0, ...fs.readdirSync('/keys').map(x => parseInt(x.split('.')[0].substr(4))))
if (epoch === 0)
return null
const keysFile = `/keys/keys${epoch}.store`
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
return publicKeyToAddress(publicKey)
async function initialize() {
if (await redis.get('foreignTime') === null) {
logger.info('Set default foreign time')
await redis.set('foreignTime', FOREIGN_START_TIME)
}
}
initialize().then(async () => {
while (true) {
await main()
async function main() {
const { transactions, endTime } = await fetchNewTransactions()
if (!transactions || transactions.length === 0) {
logger.debug('Found 0 new transactions')
await new Promise((r) => setTimeout(r, FOREIGN_FETCH_INTERVAL))
return
}
})
logger.info(`Found ${transactions.length} new transactions`)
logger.trace('%o', transactions)
for (let i = transactions.length - 1; i >= 0; i -= 1) {
const tx = transactions[i]
if (tx.memo !== 'funding') {
const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value
await proxyHttpClient
.post('/transfer', {
to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')),
value: new BN(tx.value).multipliedBy(10 ** 18)
.integerValue(),
hash: `0x${tx.txHash}`
})
}
}
await redis.set('foreignTime', endTime)
}
initialize()
.then(async () => {
while (true) {
await main()
}
})

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
@ -30,16 +32,17 @@ let rangeSize
let lastTransactionBlockNumber
let isCurrentValidator
async function resetFutureMessages (queue) {
async function resetFutureMessages(queue) {
logger.debug(`Resetting future messages in queue ${queue.name}`)
const { messageCount } = await channel.checkQueue(queue.name)
if (messageCount) {
logger.info(`Filtering ${messageCount} reloaded messages from queue ${queue.name}`)
const backup = await assertQueue(channel, `${queue.name}.backup`)
do {
while (true) {
const message = await queue.get()
if (message === false)
if (message === false) {
break
}
const data = JSON.parse(message.content)
if (data.blockNumber < blockNumber) {
logger.debug('Saving message %o', data)
@ -48,25 +51,134 @@ async function resetFutureMessages (queue) {
logger.debug('Dropping message %o', data)
}
channel.ack(message)
} while (true)
}
logger.debug('Dropped messages came from future')
do {
while (true) {
const message = await backup.get()
if (message === false)
if (message === false) {
break
}
const data = JSON.parse(message.content)
logger.debug('Requeuing message %o', data)
queue.send(data)
channel.ack(message)
} while (true)
}
logger.debug('Redirected messages back to initial queue')
}
}
async function initialize () {
async function sendKeygen(event) {
const newEpoch = event.returnValues.newEpoch.toNumber()
keygenQueue.send({
epoch: newEpoch,
blockNumber,
threshold: (await bridge.methods.getThreshold(newEpoch)
.call()).toNumber(),
parties: (await bridge.methods.getParties(newEpoch)
.call()).toNumber()
})
logger.debug('Sent keygen start event')
}
function sendKeygenCancellation(event) {
const eventEpoch = event.returnValues.epoch.toNumber()
cancelKeygenQueue.send({
epoch: eventEpoch,
blockNumber
})
logger.debug('Sent keygen cancellation event')
}
async function sendSignFundsTransfer(event) {
const newEpoch = event.returnValues.newEpoch.toNumber()
const oldEpoch = event.returnValues.oldEpoch.toNumber()
signQueue.send({
epoch: oldEpoch,
blockNumber,
newEpoch,
nonce: foreignNonce[oldEpoch],
threshold: (await bridge.methods.getThreshold(oldEpoch)
.call()).toNumber(),
parties: (await bridge.methods.getParties(oldEpoch)
.call()).toNumber()
})
logger.debug('Sent sign funds transfer event')
foreignNonce[oldEpoch] += 1
redisTx.incr(`foreignNonce${oldEpoch}`)
}
async function sendSign(event) {
const tx = await homeWeb3.eth.getTransaction(event.transactionHash)
const msg = utils.serializeTransaction({
nonce: tx.nonce,
gasPrice: `0x${new BN(tx.gasPrice).toString(16)}`,
gasLimit: `0x${new BN(tx.gas).toString(16)}`,
to: tx.to,
value: `0x${new BN(tx.value).toString(16)}`,
data: tx.input,
chainId: await homeWeb3.eth.net.getId()
})
const hash = homeWeb3.utils.sha3(msg)
const publicKey = utils.recoverPublicKey(hash, {
r: tx.r,
s: tx.s,
v: tx.v
})
const msgToQueue = {
epoch,
blockNumber,
recipient: publicKeyToAddress({
x: publicKey.substr(4, 64),
y: publicKey.substr(68, 64)
}),
value: (new BN(event.returnValues.value)).dividedBy(10 ** 18)
.toFixed(8, 3),
nonce: event.returnValues.nonce.toNumber()
}
exchangeQueue.send(msgToQueue)
logger.debug('Sent new sign event: %o', msgToQueue)
lastTransactionBlockNumber = blockNumber
redisTx.set('lastTransactionBlockNumber', blockNumber)
logger.debug(`Set lastTransactionBlockNumber to ${blockNumber}`)
}
async function sendStartSign() {
redisTx.incr(`foreignNonce${epoch}`)
signQueue.send({
epoch,
blockNumber,
nonce: foreignNonce[epoch],
threshold: (await bridge.methods.getThreshold(epoch)
.call()).toNumber(),
parties: (await bridge.methods.getParties(epoch)
.call()).toNumber()
})
foreignNonce[epoch] += 1
}
async function processEpochStart(event) {
epoch = event.returnValues.epoch.toNumber()
epochStart = blockNumber
logger.info(`Epoch ${epoch} started`)
rangeSize = (await bridge.methods.getRangeSize()
.call()).toNumber()
isCurrentValidator = (await bridge.methods.getValidators()
.call()).includes(validatorAddress)
if (isCurrentValidator) {
logger.info(`${validatorAddress} is a current validator`)
} else {
logger.info(`${validatorAddress} is not a current validator`)
}
logger.info(`Updated range size to ${rangeSize}`)
foreignNonce[epoch] = 0
}
async function initialize() {
channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
signQueue = await assertQueue(channel, 'signQueue')
@ -79,11 +191,12 @@ async function initialize () {
epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0
logger.info(`Current epoch ${epoch}`)
epochStart = events.length ? events[events.length - 1].blockNumber : 1
const saved = (parseInt(await redis.get('homeBlock')) + 1) || parseInt(HOME_START_BLOCK)
const saved = (parseInt(await redis.get('homeBlock'), 10) + 1) || parseInt(HOME_START_BLOCK, 10)
if (epochStart > saved) {
logger.info(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`)
blockNumber = epochStart
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber()
rangeSize = (await bridge.methods.getRangeSize()
.call()).toNumber()
await redis.multi()
.set('homeBlock', blockNumber - 1)
.set(`foreignNonce${epoch}`, 0)
@ -92,10 +205,11 @@ async function initialize () {
} else {
logger.info('Restoring epoch and block number from local db')
blockNumber = saved
foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`)) || 0
foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`), 10) || 0
}
logger.debug('Checking if current validator')
isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress)
isCurrentValidator = (await bridge.methods.getValidators()
.call()).includes(validatorAddress)
if (isCurrentValidator) {
logger.info(`${validatorAddress} is a current validator`)
} else {
@ -106,16 +220,16 @@ async function initialize () {
await resetFutureMessages(cancelKeygenQueue)
await resetFutureMessages(exchangeQueue)
await resetFutureMessages(signQueue)
logger.debug(`Sending start commands`)
logger.debug('Sending start commands')
await axios.get('http://keygen:8001/start')
await axios.get('http://signer:8001/start')
}
async function main () {
async function main() {
logger.debug(`Watching events in block #${blockNumber}`)
if (await homeWeb3.eth.getBlock(blockNumber) === null) {
logger.debug('No block')
await new Promise(r => setTimeout(r, 1000))
await new Promise((r) => setTimeout(r, 1000))
return
}
@ -126,7 +240,8 @@ async function main () {
toBlock: blockNumber
})
for (const event of bridgeEvents) {
for (let i = 0; i < bridgeEvents.length; i += 1) {
const event = bridgeEvents[i]
switch (event.event) {
case 'NewEpoch':
await sendKeygen(event)
@ -135,14 +250,20 @@ async function main () {
sendKeygenCancellation(event)
break
case 'NewFundsTransfer':
isCurrentValidator && await sendSignFundsTransfer(event)
if (isCurrentValidator) {
await sendSignFundsTransfer(event)
}
break
case 'ExchangeRequest':
isCurrentValidator && await sendSign(event)
if (isCurrentValidator) {
await sendSign(event)
}
break
case 'EpochStart':
await processEpochStart(event)
break
default:
logger.warn('Unknown event %o', event)
}
}
@ -155,108 +276,16 @@ async function main () {
}
}
blockNumber++
blockNumber += 1
// Exec redis tx
await redisTx.incr('homeBlock').exec()
await redisTx.incr('homeBlock')
.exec()
await redis.save()
}
initialize().then(async () => {
while (true) {
await main()
}
}, e => logger.warn('Initialization failed %o', e))
async function sendKeygen (event) {
const newEpoch = event.returnValues.newEpoch.toNumber()
keygenQueue.send({
epoch: newEpoch,
blockNumber,
threshold: (await bridge.methods.getThreshold(newEpoch).call()).toNumber(),
parties: (await bridge.methods.getParties(newEpoch).call()).toNumber()
})
logger.debug('Sent keygen start event')
}
function sendKeygenCancellation (event) {
const epoch = event.returnValues.epoch.toNumber()
cancelKeygenQueue.send({
epoch,
blockNumber
})
logger.debug('Sent keygen cancellation event')
}
async function sendSignFundsTransfer (event) {
const newEpoch = event.returnValues.newEpoch.toNumber()
const oldEpoch = event.returnValues.oldEpoch.toNumber()
signQueue.send({
epoch: oldEpoch,
blockNumber,
newEpoch,
nonce: foreignNonce[oldEpoch],
threshold: (await bridge.methods.getThreshold(oldEpoch).call()).toNumber(),
parties: (await bridge.methods.getParties(oldEpoch).call()).toNumber()
})
logger.debug('Sent sign funds transfer event')
foreignNonce[oldEpoch]++
redisTx.incr(`foreignNonce${oldEpoch}`)
}
async function sendSign (event) {
const tx = await homeWeb3.eth.getTransaction(event.transactionHash)
const msg = utils.serializeTransaction({
nonce: tx.nonce,
gasPrice: `0x${new BN(tx.gasPrice).toString(16)}`,
gasLimit: `0x${new BN(tx.gas).toString(16)}`,
to: tx.to,
value: `0x${new BN(tx.value).toString(16)}`,
data: tx.input,
chainId: await homeWeb3.eth.net.getId()
})
const hash = homeWeb3.utils.sha3(msg)
const publicKey = utils.recoverPublicKey(hash, { r: tx.r, s: tx.s, v: tx.v })
const msgToQueue = {
epoch,
blockNumber,
recipient: publicKeyToAddress({
x: publicKey.substr(4, 64),
y: publicKey.substr(68, 64)
}),
value: (new BN(event.returnValues.value)).dividedBy(10 ** 18).toFixed(8, 3),
nonce: event.returnValues.nonce.toNumber()
}
exchangeQueue.send(msgToQueue)
logger.debug('Sent new sign event: %o', msgToQueue)
lastTransactionBlockNumber = blockNumber
redisTx.set('lastTransactionBlockNumber', blockNumber)
logger.debug(`Set lastTransactionBlockNumber to ${blockNumber}`)
}
async function sendStartSign () {
redisTx.incr(`foreignNonce${epoch}`)
signQueue.send({
epoch,
blockNumber,
nonce: foreignNonce[epoch]++,
threshold: (await bridge.methods.getThreshold(epoch).call()).toNumber(),
parties: (await bridge.methods.getParties(epoch).call()).toNumber()
})
}
async function processEpochStart (event) {
epoch = event.returnValues.epoch.toNumber()
epochStart = blockNumber
logger.info(`Epoch ${epoch} started`)
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber()
isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress)
if (isCurrentValidator) {
logger.info(`${validatorAddress} is a current validator`)
} else {
logger.info(`${validatorAddress} is not a current validator`)
}
logger.info(`Updated range size to ${rangeSize}`)
foreignNonce[epoch] = 0
}
initialize()
.then(async () => {
while (true) {
await main()
}
}, (e) => logger.warn('Initialization failed %o', e))

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

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

View File

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

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

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

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

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()
}
async function assertQueue (channel, name) {
const queue = await channel.assertQueue(name)
return {
name: queue.queue,
send: msg => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), {
persistent: true
}),
get: consumer => channel.get(queue.queue, consumer),
consume: consumer => channel.consume(queue.queue, consumer)
while (true) {
try {
return (await amqp.connect(url)).createChannel()
} catch (e) {
logger.debug('Failed to connect to rabbitmqServer, reconnecting')
await new Promise((resolve) => setTimeout(resolve, 2000))
}
}
}
module.exports = { connectRabbit, assertQueue }
async function assertQueue(channel, name) {
const queue = await channel.assertQueue(name)
return {
name: queue.queue,
send: (msg) => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), {
persistent: true
}),
get: (consumer) => channel.get(queue.queue, consumer),
consume: (consumer) => channel.consume(queue.queue, consumer)
}
}
module.exports = {
connectRabbit,
assertQueue
}

View File

@ -1,7 +1,27 @@
const crypto = require('crypto')
const bech32 = require('bech32')
function publicKeyToAddress ({ x, y }) {
function padZeros(s, len) {
while (s.length < len) {
// eslint-disable-next-line no-param-reassign
s = `0${s}`
}
return s
}
function sha256(bytes) {
return crypto.createHash('sha256')
.update(bytes)
.digest('hex')
}
function ripemd160(bytes) {
return crypto.createHash('ripemd160')
.update(bytes)
.digest('hex')
}
function publicKeyToAddress({ x, y }) {
const compact = (parseInt(y[y.length - 1], 16) % 2 ? '03' : '02') + padZeros(x, 64)
const sha256Hash = sha256(Buffer.from(compact, 'hex'))
const hash = ripemd160(Buffer.from(sha256Hash, 'hex'))
@ -9,18 +29,8 @@ function publicKeyToAddress ({ x, y }) {
return bech32.encode('tbnb', words)
}
function padZeros (s, len) {
while (s.length < len)
s = '0' + s
return s
module.exports = {
publicKeyToAddress,
padZeros,
sha256
}
function sha256 (bytes) {
return crypto.createHash('sha256').update(bytes).digest('hex')
}
function ripemd160 (bytes) {
return crypto.createHash('ripemd160').update(bytes).digest('hex')
}
module.exports = { publicKeyToAddress, padZeros, sha256 }

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

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,7 +25,126 @@ let ready = false
let exchangeQueue
let channel
async function main () {
async function getExchangeMessages(nonce) {
logger.debug('Getting exchange messages')
const messages = []
while (true) {
const msg = await exchangeQueue.get()
if (msg === false) {
break
}
const data = JSON.parse(msg.content)
logger.debug('Got message %o', data)
if (data.nonce !== nonce) {
channel.nack(msg, false, true)
break
}
messages.push(msg)
}
logger.debug(`Found ${messages.length} messages`)
return messages
}
function restart(req, res) {
logger.info('Cancelling current sign')
nextAttempt = req.params.attempt
exec.execSync('pkill gg18_sign || true')
cancelled = true
res.send('Cancelled')
}
function confirmFundsTransfer() {
exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' })
}
function getAccountFromFile(file) {
logger.debug(`Reading ${file}`)
if (!fs.existsSync(file)) {
logger.debug('No keys found, skipping')
return { address: '' }
}
const publicKey = JSON.parse(fs.readFileSync(file))[5]
return {
address: publicKeyToAddress(publicKey),
publicKey
}
}
function getAccount(address) {
logger.info(`Getting account ${address} data`)
return httpClient
.get(`/api/v1/account/${address}`)
.then((res) => res.data)
.catch(() => {
logger.debug('Retrying')
return getAccount(address)
})
}
async function waitForAccountNonce(address, nonce) {
cancelled = false
logger.info(`Waiting for account ${address} to have nonce ${nonce}`)
while (!cancelled) {
const { sequence } = await getAccount(address)
if (sequence >= nonce) {
break
}
await new Promise((resolve) => setTimeout(resolve, 1000))
logger.debug('Waiting for needed account nonce')
}
logger.info('Account nonce is OK')
return !cancelled
}
function sendTx(tx) {
return httpClient
.post('/api/v1/broadcast?sync=true', tx, {
headers: {
'Content-Type': 'text/plain'
}
})
.catch((err) => {
if (err.response.data.message.includes('Tx already exists in cache')) {
logger.debug('Tx already exists in cache')
return true
}
logger.info('Something failed, restarting: %o', err.response)
return new Promise((resolve) => setTimeout(() => resolve(sendTx(tx)), 1000))
})
}
function sign(keysFile, hash, tx, publicKey) {
return new Promise((resolve) => {
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async (error) => {
if (fs.existsSync('signature')) {
logger.info('Finished signature generation')
const signature = JSON.parse(fs.readFileSync('signature'))
logger.debug('%o', signature)
logger.info('Building signed transaction')
const signedTx = tx.addSignature(publicKey, {
r: signature[1],
s: signature[3]
})
logger.info('Sending transaction')
logger.debug(signedTx)
await sendTx(signedTx)
resolve(true)
} else if (error === null || error.code === 0) {
resolve(true)
} else {
logger.warn('Sign failed')
resolve(false)
}
})
cmd.stdout.on('data', (data) => logger.debug(data.toString()))
cmd.stderr.on('data', (data) => logger.debug(data.toString()))
})
}
async function main() {
logger.info('Connecting to RabbitMQ server')
channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to signature events queue')
@ -37,15 +152,17 @@ async function main () {
const signQueue = await assertQueue(channel, 'signQueue')
while (!ready) {
await new Promise(res => setTimeout(res, 1000))
await new Promise((res) => setTimeout(res, 1000))
}
channel.prefetch(1)
signQueue.consume(async msg => {
signQueue.consume(async (msg) => {
const data = JSON.parse(msg.content)
logger.info('Consumed sign event: %o', data)
const { nonce, epoch, newEpoch, parties, threshold } = data
const {
nonce, epoch, newEpoch, parties, threshold
} = data
const keysFile = `/keys/keys${epoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile)
@ -57,16 +174,22 @@ async function main () {
const account = await getAccount(from)
logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() }))
fs.writeFileSync('./params', JSON.stringify({
parties: parties.toString(),
threshold: threshold.toString()
}))
attempt = 1
if (!newEpoch) {
const exchanges = await getExchangeMessages(nonce)
const exchangesData = exchanges.map(msg => JSON.parse(msg.content))
const exchangesData = exchanges.map((exchangeMsg) => JSON.parse(exchangeMsg.content))
if (exchanges.length > 0 && account.sequence <= nonce) {
const recipients = exchangesData.map(({ value, recipient }) => ({ to: recipient, tokens: value }))
const recipients = exchangesData.map(({ value, recipient }) => ({
to: recipient,
tokens: value
}))
while (true) {
logger.info(`Building corresponding transfer transaction, nonce ${nonce}`)
@ -82,16 +205,18 @@ async function main () {
const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
const done = await sign(keysFile, hash, tx, publicKey)
&& await waitForAccountNonce(from, nonce + 1)
if (done) {
exchanges.forEach(msg => channel.ack(msg))
// eslint-disable-next-line no-loop-func
exchanges.forEach((exchangeMsg) => channel.ack(exchangeMsg))
break
}
attempt = nextAttempt ? nextAttempt : attempt + 1
attempt = nextAttempt || attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null
await new Promise(resolve => setTimeout(resolve, 1000))
await new Promise((resolve) => setTimeout(resolve, 1000))
}
}
} else if (account.sequence <= nonce) {
@ -104,27 +229,28 @@ async function main () {
from,
accountNumber: account.account_number,
sequence: nonce,
recipients: [ {
recipients: [{
to,
tokens: account.balances.find(x => x.symbol === FOREIGN_ASSET).free,
bnbs: new BN(account.balances.find(x => x.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8)),
} ],
tokens: account.balances.find((token) => token.symbol === FOREIGN_ASSET).free,
bnbs: new BN(account.balances.find((token) => token.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8))
}],
asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}`
})
const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
const done = await sign(keysFile, hash, tx, publicKey)
&& await waitForAccountNonce(from, nonce + 1)
if (done) {
await confirmFundsTransfer()
break
}
attempt = nextAttempt ? nextAttempt : attempt + 1
attempt = nextAttempt || attempt + 1
logger.warn(`Sign failed, starting next attempt ${attempt}`)
nextAttempt = null
await new Promise(resolve => setTimeout(resolve, 1000))
await new Promise((resolve) => setTimeout(resolve, 1000))
}
} else {
logger.debug('Tx has been already sent')
@ -134,118 +260,12 @@ async function main () {
})
}
app.get('/restart/:attempt', restart)
app.get('/start', (req, res) => {
logger.info('Ready to start')
ready = true
res.send()
})
app.listen(8001, () => logger.debug('Listening on 8001'))
main()
async function getExchangeMessages (nonce) {
logger.debug('Getting exchange messages')
const messages = []
do {
const msg = await exchangeQueue.get()
if (msg === false) {
break
}
const data = JSON.parse(msg.content)
logger.debug('Got message %o', data)
if (data.nonce !== nonce) {
channel.nack(msg, false, true)
break
}
messages.push(msg)
} while (true)
logger.debug(`Found ${messages.length} messages`)
return messages
}
function sign (keysFile, hash, tx, publicKey) {
return new Promise(resolve => {
const cmd = exec.execFile('./sign-entrypoint.sh', [ PROXY_URL, keysFile, hash ], async (error) => {
if (fs.existsSync('signature')) {
logger.info('Finished signature generation')
const signature = JSON.parse(fs.readFileSync('signature'))
logger.debug('%o', signature)
logger.info('Building signed transaction')
const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] })
logger.info('Sending transaction')
logger.debug(signedTx)
await sendTx(signedTx)
resolve(true)
} else if (error === null || error.code === 0) {
resolve(true)
} else {
logger.warn('Sign failed')
resolve(false)
}
})
cmd.stdout.on('data', data => logger.debug(data.toString()))
cmd.stderr.on('data', data => logger.debug(data.toString()))
})
}
function restart (req, res) {
logger.info('Cancelling current sign')
nextAttempt = req.params.attempt
exec.execSync('pkill gg18_sign || true')
cancelled = true
res.send('Cancelled')
}
function confirmFundsTransfer () {
exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' })
}
function getAccountFromFile (file) {
logger.debug(`Reading ${file}`)
if (!fs.existsSync(file)) {
logger.debug('No keys found, skipping')
return { address: '' }
}
const publicKey = JSON.parse(fs.readFileSync(file))[5]
return {
address: publicKeyToAddress(publicKey),
publicKey: publicKey
}
}
async function waitForAccountNonce (address, nonce) {
cancelled = false
logger.info(`Waiting for account ${address} to have nonce ${nonce}`)
while (!cancelled) {
const sequence = (await getAccount(address)).sequence
if (sequence >= nonce)
break
await new Promise(resolve => setTimeout(resolve, 1000))
logger.debug('Waiting for needed account nonce')
}
logger.info('Account nonce is OK')
return !cancelled
}
function getAccount (address) {
logger.info(`Getting account ${address} data`)
return httpClient
.get(`/api/v1/account/${address}`)
.then(res => res.data)
.catch(() => {
logger.debug('Retrying')
return getAccount(address)
})
}
function sendTx (tx) {
return httpClient
.post(`/api/v1/broadcast?sync=true`, tx, {
headers: {
'Content-Type': 'text/plain'
}
})
.catch(err => {
if (err.response.data.message.includes('Tx already exists in cache'))
logger.debug('Tx already exists in cache')
else {
logger.info('Something failed, restarting: %o', err.response)
return new Promise(resolve => setTimeout(() => resolve(sendTx(tx)), 1000))
}
})
}

View File

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

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 })
httpClient
.get(`/api/v1/account/${address}`)
.then(res => {
console.log(`BNB: ${parseFloat(res.data.balances.find(x => x.symbol === 'BNB').free)}`)
console.log(`${FOREIGN_ASSET}: ${parseFloat(res.data.balances.find(x => x.symbol === FOREIGN_ASSET).free)}`)
})
.catch(console.log)
function main() {
httpClient
.get(`/api/v1/account/${address}`)
.then((res) => {
console.log(`BNB: ${parseFloat(res.data.balances.find((token) => token.symbol === 'BNB').free)}`)
console.log(`${FOREIGN_ASSET}: ${parseFloat(res.data.balances.find((token) => token.symbol === FOREIGN_ASSET).free)}`)
})
.catch(console.log)
}
main()

View File

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

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)
const address = process.argv[2]
function main() {
const address = process.argv[2]
web3.eth.getBalance(address).then(x => console.log(`${x.toString()} wei`))
web3.eth.getBalance(address)
.then((balance) => console.log(`${balance.toString()} wei`))
token.methods.balanceOf(address).call()
.then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3)))
.then(x => console.log(`${x.toString()} tokens`))
.catch(() => console.log('0 tokens'))
token.methods.balanceOf(address)
.call()
.then((balance) => parseFloat(new BN(balance).dividedBy(10 ** 18)
.toFixed(8, 3)))
.then((balance) => console.log(`${balance.toString()} tokens`))
.catch(() => console.log('0 tokens'))
}
main()

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

View File

@ -2,24 +2,28 @@ const { utils } = require('ethers')
const bech32 = require('bech32')
const crypto = require('crypto')
const privateKey = process.argv[2].startsWith('0x') ? process.argv[2] : '0x' + process.argv[2]
function sha256(bytes) {
return crypto.createHash('sha256').update(bytes).digest('hex')
}
const ethAddress = utils.computeAddress(privateKey)
const publicKey = utils.computePublicKey(privateKey, true)
function ripemd160(bytes) {
return crypto.createHash('ripemd160').update(bytes).digest('hex')
}
console.log(`Eth address: ${ethAddress}\nBnc address: ${publicKeyToAddress(publicKey)}`)
function publicKeyToAddress (publicKey) {
function publicKeyToAddress(publicKey) {
const sha256Hash = sha256(Buffer.from(publicKey.substr(2), 'hex'))
const hash = ripemd160(Buffer.from(sha256Hash, 'hex'))
const words = bech32.toWords(Buffer.from(hash, 'hex'))
return bech32.encode('tbnb', words)
}
function sha256 (bytes) {
return crypto.createHash('sha256').update(bytes).digest('hex')
function main() {
const privateKey = process.argv[2].startsWith('0x') ? process.argv[2] : `0x${process.argv[2]}`
const ethAddress = utils.computeAddress(privateKey)
const publicKey = utils.computePublicKey(privateKey, true)
console.log(`Eth address: ${ethAddress}\nBnc address: ${publicKeyToAddress(publicKey)}`)
}
function ripemd160 (bytes) {
return crypto.createHash('ripemd160').update(bytes).digest('hex')
}
main()

View File

@ -7,7 +7,7 @@ const web3 = new Web3(SIDE_RPC_URL, null, { transactionConfirmationBlocks: 1 })
const sender = web3.eth.accounts.privateKeyToAccount(`0x${SIDE_PRIVATE_KEY}`).address
async function main () {
async function main() {
const SIDE_CHAIN_ID = await web3.eth.net.getId()
const to = process.argv[2]
@ -15,20 +15,19 @@ async function main () {
console.log(`Transfer from ${sender} to ${to}, ${amount} eth`)
const tx_coins = {
const txCoins = {
data: '0x',
from: sender,
to: to,
to,
nonce: await web3.eth.getTransactionCount(sender),
chainId: SIDE_CHAIN_ID,
value: web3.utils.toWei(new BN(amount).toString(), 'ether'),
gas: 21000
}
const signedTx = await web3.eth.accounts.signTransaction(tx_coins, SIDE_PRIVATE_KEY)
const signedTx = await web3.eth.accounts.signTransaction(txCoins, SIDE_PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
console.log('txHash: ' + receipt.transactionHash)
console.log(`txHash: ${receipt.transactionHash}`)
}
main()

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

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

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

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

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

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