Support of closing epoch in oracle scripts
This commit is contained in:
parent
38e96b5552
commit
7f575238e1
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue