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 NewFundsTransfer(uint indexed oldEpoch, uint indexed newEpoch)',
'event EpochStart(uint indexed epoch, uint x, uint y)',
'event EpochClose(uint indexed epoch)',
'event ForceSign()',
'function getThreshold(uint epoch) view returns (uint)',
'function getParties(uint epoch) view returns (uint)',
'function getRangeSize() view returns (uint)',
@ -160,7 +162,6 @@ async function sendSign(event, transactionHash) {
}
async function sendStartSign() {
redisTx.incr(`foreignNonce${epoch}`)
signQueue.send({
epoch,
blockNumber,
@ -169,6 +170,7 @@ async function sendStartSign() {
parties: (await bridge.getParties(epoch)).toNumber()
})
foreignNonce[epoch] += 1
redisTx.incr(`foreignNonce${epoch}`)
}
async function processEpochStart(event) {
@ -186,6 +188,19 @@ async function processEpochStart(event) {
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() {
channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
@ -264,6 +279,8 @@ async function loop() {
}))
for (let curBlockNumber = blockNumber, i = 0; curBlockNumber <= endBlock; curBlockNumber += 1) {
const rangeOffset = (curBlockNumber + 1 - epochStart) % rangeSize
const rangeStart = curBlockNumber - (rangeOffset || rangeSize)
let epochTimeUpdated = false
while (i < bridgeEvents.length && bridgeEvents[i].blockNumber === curBlockNumber) {
const event = bridge.interface.parseLog(bridgeEvents[i])
@ -306,6 +323,19 @@ async function loop() {
epoch
})
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:
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')
if (lastTransactionBlockNumber > curBlockNumber - rangeSize) {
if (isCurrentValidator && lastTransactionBlockNumber > curBlockNumber - rangeSize) {
logger.info('Sending message to start signature generation for the ended range')
await sendStartSign()
}

View File

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

View File

@ -189,6 +189,14 @@ async function confirmFundsTransfer(req, res) {
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) {
try {
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) {
if (ethers.utils.isHexString(req.params.validator, 20)) {
logger.info('Voting for removing validator')
@ -298,14 +314,17 @@ 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
x, y, epoch, rangeSize, nextRangeSize, closeEpoch, nextCloseEpoch, epochStartBlock,
foreignNonce, nextEpoch, threshold, nextThreshold, validators, nextValidators, status,
homeBalance
] = await Promise.all([
bridge.getX().then((value) => new BN(value).toString(16)),
bridge.getY().then((value) => new BN(value).toString(16)),
bridge.epoch().then(boundX),
bridge.getRangeSize().then(boundX),
bridge.getNextRangeSize().then(boundX),
bridge.getCloseEpoch(),
bridge.getNextCloseEpoch(),
bridge.getStartBlock().then(boundX),
bridge.getNonce().then(boundX),
bridge.nextEpoch().then(boundX),
@ -338,6 +357,8 @@ async function info(req, res) {
nextEpoch,
threshold,
nextThreshold,
closeEpoch,
nextCloseEpoch,
homeBridgeAddress: HOME_BRIDGE_ADDRESS,
foreignBridgeAddress: foreignAddress,
foreignNonce,
@ -371,6 +392,7 @@ app.post('/signupsign', signupSign)
app.post('/confirmKeygen', confirmKeygen)
app.post('/confirmFundsTransfer', confirmFundsTransfer)
app.post('/confirmCloseEpoch', confirmCloseEpoch)
app.post('/transfer', transfer)
votesProxyApp.get('/vote/startVoting', voteStartVoting)
@ -379,6 +401,7 @@ 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('/vote/changeCloseEpoch/:closeEpoch', voteChangeCloseEpoch)
votesProxyApp.get('/info', info)
async function main() {

View File

@ -11,10 +11,12 @@ function decodeStatus(status) {
case 0:
return 'ready'
case 1:
return 'voting'
return 'closing_epoch'
case 2:
return 'keygen'
return 'voting'
case 3:
return 'keygen'
case 4:
return 'funds_transfer'
default:
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 httpClient = axios.create({ baseURL: FOREIGN_URL })
const proxyClient = axios.create({ baseURL: PROXY_URL })
const SIGN_OK = 0
const SIGN_NONCE_INTERRUPT = 1
@ -67,8 +68,12 @@ function restart(req, res) {
}
}
function confirmFundsTransfer() {
exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' })
async function confirmFundsTransfer() {
await proxyClient.post('/confirmFundsTransfer')
}
async function confirmCloseEpoch() {
await proxyClient.post('/confirmCloseEpoch')
}
function getAccountFromFile(file) {
@ -216,10 +221,10 @@ async function main() {
logger.info('Consumed sign event: %o', data)
const {
nonce, epoch, newEpoch, parties, threshold
nonce, epoch, newEpoch, parties, threshold, closeEpoch
} = data
const keysFile = `/keys/keys${epoch}.store`
const keysFile = `/keys/keys${epoch || closeEpoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile)
if (from === '') {
logger.info('No keys found, acking message')
@ -236,7 +241,34 @@ async function main() {
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 exchangesData = exchanges.map((exchangeMsg) => JSON.parse(exchangeMsg.content))

View File

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