Support of closing epoch in oracle scripts

This commit is contained in:
Kirill Fedoseev 2019-11-14 13:29:58 +03:00
parent 38e96b5552
commit 7f575238e1
6 changed files with 171 additions and 65 deletions

View File

@ -21,6 +21,8 @@ const bridgeAbi = [
'event NewEpochCancelled(uint indexed epoch)', 'event NewEpochCancelled(uint indexed epoch)',
'event NewFundsTransfer(uint indexed oldEpoch, uint indexed newEpoch)', 'event NewFundsTransfer(uint indexed oldEpoch, uint indexed newEpoch)',
'event EpochStart(uint indexed epoch, uint x, uint y)', 'event EpochStart(uint indexed epoch, uint x, uint y)',
'event EpochClose(uint indexed epoch)',
'event ForceSign()',
'function getThreshold(uint epoch) view returns (uint)', 'function getThreshold(uint epoch) view returns (uint)',
'function getParties(uint epoch) view returns (uint)', 'function getParties(uint epoch) view returns (uint)',
'function getRangeSize() view returns (uint)', 'function getRangeSize() view returns (uint)',
@ -160,7 +162,6 @@ async function sendSign(event, transactionHash) {
} }
async function sendStartSign() { async function sendStartSign() {
redisTx.incr(`foreignNonce${epoch}`)
signQueue.send({ signQueue.send({
epoch, epoch,
blockNumber, blockNumber,
@ -169,6 +170,7 @@ async function sendStartSign() {
parties: (await bridge.getParties(epoch)).toNumber() parties: (await bridge.getParties(epoch)).toNumber()
}) })
foreignNonce[epoch] += 1 foreignNonce[epoch] += 1
redisTx.incr(`foreignNonce${epoch}`)
} }
async function processEpochStart(event) { async function processEpochStart(event) {
@ -186,6 +188,19 @@ async function processEpochStart(event) {
foreignNonce[epoch] = 0 foreignNonce[epoch] = 0
} }
async function sendEpochClose() {
logger.debug(`Consumed epoch ${epoch} close event`)
signQueue.send({
closeEpoch: epoch,
blockNumber,
nonce: foreignNonce[epoch],
threshold: (await bridge.getThreshold(epoch)).toNumber(),
parties: (await bridge.getParties(epoch)).toNumber()
})
foreignNonce[epoch] += 1
redisTx.incr(`foreignNonce${epoch}`)
}
async function initialize() { async function initialize() {
channel = await connectRabbit(RABBITMQ_URL) channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue') exchangeQueue = await assertQueue(channel, 'exchangeQueue')
@ -264,6 +279,8 @@ async function loop() {
})) }))
for (let curBlockNumber = blockNumber, i = 0; curBlockNumber <= endBlock; curBlockNumber += 1) { for (let curBlockNumber = blockNumber, i = 0; curBlockNumber <= endBlock; curBlockNumber += 1) {
const rangeOffset = (curBlockNumber + 1 - epochStart) % rangeSize
const rangeStart = curBlockNumber - (rangeOffset || rangeSize)
let epochTimeUpdated = false let epochTimeUpdated = false
while (i < bridgeEvents.length && bridgeEvents[i].blockNumber === curBlockNumber) { while (i < bridgeEvents.length && bridgeEvents[i].blockNumber === curBlockNumber) {
const event = bridge.interface.parseLog(bridgeEvents[i]) const event = bridge.interface.parseLog(bridgeEvents[i])
@ -306,6 +323,19 @@ async function loop() {
epoch epoch
}) })
break break
case 'EpochClose':
if (isCurrentValidator) {
await sendEpochClose()
}
break
case 'ForceSign':
if (isCurrentValidator && lastTransactionBlockNumber > rangeStart) {
logger.debug('Consumed force sign event')
lastTransactionBlockNumber = 0
redisTx.set('lastTransactionBlockNumber', 0)
await sendStartSign()
}
break
default: default:
logger.warn('Unknown event %o', event) logger.warn('Unknown event %o', event)
} }
@ -320,10 +350,10 @@ async function loop() {
}) })
} }
if ((curBlockNumber + 1 - epochStart) % rangeSize === 0) { if (rangeOffset === 0) {
logger.info('Reached end of the current block range') logger.info('Reached end of the current block range')
if (lastTransactionBlockNumber > curBlockNumber - rangeSize) { if (isCurrentValidator && lastTransactionBlockNumber > curBlockNumber - rangeSize) {
logger.info('Sending message to start signature generation for the ended range') logger.info('Sending message to start signature generation for the ended range')
await sendStartSign() await sendStartSign()
} }

View File

@ -14,17 +14,21 @@ const bridgeAbi = [
'function getNextThreshold() view returns (uint)', 'function getNextThreshold() view returns (uint)',
'function getValidators() view returns (address[])', 'function getValidators() view returns (address[])',
'function getNextValidators() view returns (address[])', 'function getNextValidators() view returns (address[])',
'function getCloseEpoch() view returns (bool)',
'function getNextCloseEpoch() view returns (bool)',
'function status() view returns (uint)', 'function status() view returns (uint)',
'function votesCount(bytes32) view returns (uint)', 'function votesCount(bytes32) view returns (uint)',
'function getNextPartyId(address a) view returns (uint)', 'function getNextPartyId(address a) view returns (uint)',
'function confirmKeygen(uint x, uint y)', 'function confirmKeygen(uint x, uint y)',
'function confirmFundsTransfer()', 'function confirmFundsTransfer()',
'function confirmCloseEpoch()',
'function startVoting()', 'function startVoting()',
'function voteStartKeygen()', 'function voteStartKeygen()',
'function voteCancelKeygen()', 'function voteCancelKeygen()',
'function voteAddValidator(address validator)', 'function voteAddValidator(address validator)',
'function voteRemoveValidator(address validator)', 'function voteRemoveValidator(address validator)',
'function voteChangeThreshold(uint threshold)', 'function voteChangeThreshold(uint threshold)',
'function voteChangeCloseEpoch(bool closeEpoch)',
'function transfer(bytes32 hash, address to, uint value)' 'function transfer(bytes32 hash, address to, uint value)'
] ]
const sharedDbAbi = [ const sharedDbAbi = [

View File

@ -189,6 +189,14 @@ async function confirmFundsTransfer(req, res) {
logger.debug('Confirm funds transfer end') logger.debug('Confirm funds transfer end')
} }
async function confirmCloseEpoch(req, res) {
logger.debug('Confirm close epoch call')
const query = bridge.interface.functions.confirmCloseEpoch.encode([])
await homeSendQuery(query)
res.send()
logger.debug('Confirm close epoch end')
}
async function sendVote(query, req, res, waitFlag = false) { async function sendVote(query, req, res, waitFlag = false) {
try { try {
const sentQuery = await homeSendQuery(query) const sentQuery = await homeSendQuery(query)
@ -257,6 +265,14 @@ async function voteChangeThreshold(req, res) {
} }
} }
async function voteChangeCloseEpoch(req, res) {
if (req.params.closeEpoch === 'true' || req.params.closeEpoch === 'false') {
logger.info('Voting for changing close epoch')
const query = bridge.interface.functions.voteChangeCloseEpoch.encode([req.params.closeEpoch === 'true'])
await sendVote(query, req, res)
}
}
async function voteRemoveValidator(req, res) { async function voteRemoveValidator(req, res) {
if (ethers.utils.isHexString(req.params.validator, 20)) { if (ethers.utils.isHexString(req.params.validator, 20)) {
logger.info('Voting for removing validator') logger.info('Voting for removing validator')
@ -298,14 +314,17 @@ async function info(req, res) {
logger.debug('Info start') logger.debug('Info start')
try { try {
const [ const [
x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch, x, y, epoch, rangeSize, nextRangeSize, closeEpoch, nextCloseEpoch, epochStartBlock,
threshold, nextThreshold, validators, nextValidators, status, homeBalance foreignNonce, nextEpoch, threshold, nextThreshold, validators, nextValidators, status,
homeBalance
] = await Promise.all([ ] = await Promise.all([
bridge.getX().then((value) => new BN(value).toString(16)), bridge.getX().then((value) => new BN(value).toString(16)),
bridge.getY().then((value) => new BN(value).toString(16)), bridge.getY().then((value) => new BN(value).toString(16)),
bridge.epoch().then(boundX), bridge.epoch().then(boundX),
bridge.getRangeSize().then(boundX), bridge.getRangeSize().then(boundX),
bridge.getNextRangeSize().then(boundX), bridge.getNextRangeSize().then(boundX),
bridge.getCloseEpoch(),
bridge.getNextCloseEpoch(),
bridge.getStartBlock().then(boundX), bridge.getStartBlock().then(boundX),
bridge.getNonce().then(boundX), bridge.getNonce().then(boundX),
bridge.nextEpoch().then(boundX), bridge.nextEpoch().then(boundX),
@ -338,6 +357,8 @@ async function info(req, res) {
nextEpoch, nextEpoch,
threshold, threshold,
nextThreshold, nextThreshold,
closeEpoch,
nextCloseEpoch,
homeBridgeAddress: HOME_BRIDGE_ADDRESS, homeBridgeAddress: HOME_BRIDGE_ADDRESS,
foreignBridgeAddress: foreignAddress, foreignBridgeAddress: foreignAddress,
foreignNonce, foreignNonce,
@ -371,6 +392,7 @@ app.post('/signupsign', signupSign)
app.post('/confirmKeygen', confirmKeygen) app.post('/confirmKeygen', confirmKeygen)
app.post('/confirmFundsTransfer', confirmFundsTransfer) app.post('/confirmFundsTransfer', confirmFundsTransfer)
app.post('/confirmCloseEpoch', confirmCloseEpoch)
app.post('/transfer', transfer) app.post('/transfer', transfer)
votesProxyApp.get('/vote/startVoting', voteStartVoting) votesProxyApp.get('/vote/startVoting', voteStartVoting)
@ -379,6 +401,7 @@ votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen)
votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator) votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator) votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold) votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold)
votesProxyApp.get('/vote/changeCloseEpoch/:closeEpoch', voteChangeCloseEpoch)
votesProxyApp.get('/info', info) votesProxyApp.get('/info', info)
async function main() { async function main() {

View File

@ -11,10 +11,12 @@ function decodeStatus(status) {
case 0: case 0:
return 'ready' return 'ready'
case 1: case 1:
return 'voting' return 'closing_epoch'
case 2: case 2:
return 'keygen' return 'voting'
case 3: case 3:
return 'keygen'
case 4:
return 'funds_transfer' return 'funds_transfer'
default: default:
return 'unknown_state' return 'unknown_state'

View File

@ -21,6 +21,7 @@ const SIGN_NONCE_CHECK_INTERVAL = parseInt(process.env.SIGN_NONCE_CHECK_INTERVAL
const SEND_TIMEOUT = parseInt(process.env.SEND_TIMEOUT, 10) const SEND_TIMEOUT = parseInt(process.env.SEND_TIMEOUT, 10)
const httpClient = axios.create({ baseURL: FOREIGN_URL }) const httpClient = axios.create({ baseURL: FOREIGN_URL })
const proxyClient = axios.create({ baseURL: PROXY_URL })
const SIGN_OK = 0 const SIGN_OK = 0
const SIGN_NONCE_INTERRUPT = 1 const SIGN_NONCE_INTERRUPT = 1
@ -67,8 +68,12 @@ function restart(req, res) {
} }
} }
function confirmFundsTransfer() { async function confirmFundsTransfer() {
exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' }) await proxyClient.post('/confirmFundsTransfer')
}
async function confirmCloseEpoch() {
await proxyClient.post('/confirmCloseEpoch')
} }
function getAccountFromFile(file) { function getAccountFromFile(file) {
@ -216,10 +221,10 @@ async function main() {
logger.info('Consumed sign event: %o', data) logger.info('Consumed sign event: %o', data)
const { const {
nonce, epoch, newEpoch, parties, threshold nonce, epoch, newEpoch, parties, threshold, closeEpoch
} = data } = data
const keysFile = `/keys/keys${epoch}.store` const keysFile = `/keys/keys${epoch || closeEpoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile) const { address: from, publicKey } = getAccountFromFile(keysFile)
if (from === '') { if (from === '') {
logger.info('No keys found, acking message') logger.info('No keys found, acking message')
@ -236,7 +241,34 @@ async function main() {
attempt = 1 attempt = 1
if (!newEpoch) { if (closeEpoch) {
while (true) {
logger.info(`Building corresponding account flags transaction, nonce ${nonce}`)
const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
flags: 0x01,
memo: `Attempt ${attempt}`
})
const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const signResult = await sign(keysFile, hash, tx, publicKey, from)
if (signResult === SIGN_OK || signResult === SIGN_NONCE_INTERRUPT) {
await confirmCloseEpoch()
break
}
// signer either failed, or timed out after parties signup
attempt = nextAttempt || attempt + 1
nextAttempt = null
logger.warn(`Sign failed, starting next attempt ${attempt}`)
await delay(1000)
}
} else if (!newEpoch) {
const exchanges = await getExchangeMessages(nonce) const exchanges = await getExchangeMessages(nonce)
const exchangesData = exchanges.map((exchangeMsg) => JSON.parse(exchangeMsg.content)) const exchangesData = exchanges.map((exchangeMsg) => JSON.parse(exchangeMsg.content))

View File

@ -12,70 +12,84 @@ const BNB_ASSET = 'BNB'
class Transaction { class Transaction {
constructor(options) { constructor(options) {
const { const {
from, accountNumber, sequence, recipients, asset, memo = '' from, accountNumber, sequence, recipients, asset, memo = '', flags
} = options } = options
const totalTokens = recipients.reduce( let msg
(sum, { tokens }) => sum.plus(new BN(tokens || 0)), new BN(0) if (flags) {
) msg = {
const totalBnbs = recipients.reduce( from: crypto.decodeAddress(from),
(sum, { bnbs }) => sum.plus(new BN(bnbs || 0)), new BN(0) flags,
) msgType: 'NewOrderMsg' // until 'SetAccountFlagsMsg' is not available
const senderCoins = [] }
if (asset && totalTokens.isGreaterThan(0)) {
senderCoins.push({
denom: asset,
amount: totalTokens.multipliedBy(10 ** 8).toNumber()
})
}
if (totalBnbs.isGreaterThan(0)) {
senderCoins.push({
denom: BNB_ASSET,
amount: totalBnbs.multipliedBy(10 ** 8).toNumber()
})
}
senderCoins.sort((a, b) => a.denom > b.denom)
const inputs = [{ this.signMsg = {
address: from, flags,
coins: senderCoins from
}] }
const outputs = recipients.map(({ to, tokens, bnbs }) => { } else {
const receiverCoins = [] const totalTokens = recipients.reduce(
if (asset && tokens) { (sum, { tokens }) => sum.plus(new BN(tokens || 0)), new BN(0)
receiverCoins.push({ )
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, denom: asset,
amount: new BN(tokens).multipliedBy(10 ** 8).toNumber() amount: totalTokens.multipliedBy(10 ** 8).toNumber()
}) })
} }
if (bnbs) { if (totalBnbs.isGreaterThan(0)) {
receiverCoins.push({ senderCoins.push({
denom: BNB_ASSET, denom: BNB_ASSET,
amount: new BN(bnbs).multipliedBy(10 ** 8).toNumber() amount: totalBnbs.multipliedBy(10 ** 8).toNumber()
}) })
} }
receiverCoins.sort((a, b) => a.denom > b.denom) senderCoins.sort((a, b) => a.denom > b.denom)
return {
address: to, const inputs = [{
coins: receiverCoins 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()
})
}
if (bnbs) {
receiverCoins.push({
denom: BNB_ASSET,
amount: new BN(bnbs).multipliedBy(10 ** 8).toNumber()
})
}
receiverCoins.sort((a, b) => a.denom > b.denom)
return {
address: to,
coins: receiverCoins
}
})
msg = {
inputs: inputs.map((x) => ({
...x,
address: crypto.decodeAddress(x.address)
})),
outputs: outputs.map((x) => ({
...x,
address: crypto.decodeAddress(x.address)
})),
msgType: 'MsgSend'
} }
})
const msg = { this.signMsg = {
inputs: inputs.map((x) => ({ inputs,
...x, outputs
address: crypto.decodeAddress(x.address) }
})),
outputs: outputs.map((x) => ({
...x,
address: crypto.decodeAddress(x.address)
})),
msgType: 'MsgSend'
}
this.signMsg = {
inputs,
outputs
} }
this.tx = new TransactionBnc({ this.tx = new TransactionBnc({
@ -109,6 +123,7 @@ class Transaction {
sequence: this.tx.sequence sequence: this.tx.sequence
}] }]
return this.tx.serialize() return this.tx.serialize()
.replace(/ce6dc043/, 'bea6e301') // until 'SetAccountFlagsMsg' is not available
} }
} }