Skip to content

Commit

Permalink
Refactored signer and keygen clients
Browse files Browse the repository at this point in the history
  • Loading branch information
k1rill-fedoseev committed Nov 25, 2019
1 parent 799004c commit fd549fe
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 161 deletions.
73 changes: 40 additions & 33 deletions src/oracle/tss-keygen/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const app = express()

const proxyClient = axios.create({ baseURL: PROXY_URL })

let channel
let currentKeygenEpoch = null
let ready = false

Expand All @@ -25,8 +26,45 @@ async function confirmKeygen({ x, y }, epoch) {
})
}

function writeParams(parties, threshold) {
logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({
parties: parties.toString(),
threshold: (threshold - 1).toString()
}))
}

async function keygenConsumer(msg) {
const { epoch, parties, threshold } = JSON.parse(msg.content)
logger.info(`Consumed new epoch event, starting keygen for epoch ${epoch}`)

const keysFile = `/keys/keys${epoch}.store`

logger.info('Running ./keygen-entrypoint.sh')
currentKeygenEpoch = epoch

writeParams(parties, threshold)
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
currentKeygenEpoch = null
if (fs.existsSync(keysFile)) {
logger.info(`Finished keygen for epoch ${epoch}`)
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
logger.warn(`Generated multisig account in binance chain: ${publicKeyToAddress(publicKey)}`)

logger.info('Sending keys confirmation')
await confirmKeygen(publicKey, epoch)
} else {
logger.warn(`Keygen for epoch ${epoch} failed`)
}
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()))
}

async function main() {
const channel = await connectRabbit(RABBITMQ_URL)
channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to epoch events queue')
const keygenQueue = await assertQueue(channel, 'keygenQueue')
const cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue')
Expand All @@ -36,38 +74,7 @@ async function main() {
}

channel.prefetch(1)
keygenQueue.consume((msg) => {
const { epoch, parties, threshold } = JSON.parse(msg.content)
logger.info(`Consumed new epoch event, starting keygen for epoch ${epoch}`)

const keysFile = `/keys/keys${epoch}.store`

logger.info('Running ./keygen-entrypoint.sh')
currentKeygenEpoch = epoch

logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({
parties: parties.toString(),
threshold: (threshold - 1).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}`)
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
logger.warn(`Generated multisig account in binance chain: ${publicKeyToAddress(publicKey)}`)

logger.info('Sending keys confirmation')
await confirmKeygen(publicKey, epoch)
} else {
logger.warn(`Keygen for epoch ${epoch} failed`)
}
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()))
})
keygenQueue.consume(keygenConsumer)

cancelKeygenQueue.consume(async (msg) => {
const { epoch } = JSON.parse(msg.content)
Expand Down
248 changes: 120 additions & 128 deletions src/oracle/tss-sign/signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const SIGN_OK = 0
const SIGN_NONCE_INTERRUPT = 1
const SIGN_FAILED = 2

let attempt
let nextAttempt = null
let cancelled
let ready = false
Expand Down Expand Up @@ -102,7 +101,8 @@ async function getAccount(address) {
async function getFee() {
logger.info('Getting fees')
const response = await retry(() => httpClient.get('/api/v1/fees'))
return response.data.filter((fee) => fee.multi_transfer_fee)[0].multi_transfer_fee * 2
const multiTransferFee = response.data.find((fee) => fee.multi_transfer_fee).multi_transfer_fee
return new BN(multiTransferFee * 2).div(10 ** 8)
}

async function waitForAccountNonce(address, nonce) {
Expand Down Expand Up @@ -145,10 +145,14 @@ async function sendTx(tx) {
}
}

function sign(keysFile, hash, tx, publicKey, signerAddress) {
function sign(keysFile, tx, publicKey, signerAddress) {
let restartTimeoutId
let nonceDaemonIntervalId
let nonceInterrupt = false

const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)

return new Promise((resolve) => {
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async (error) => {
logger.trace('Sign entrypoint exited, %o', error)
Expand Down Expand Up @@ -215,149 +219,137 @@ function sign(keysFile, hash, tx, publicKey, signerAddress) {
})
}

async function main() {
channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to signature events queue')
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
const signQueue = await assertQueue(channel, 'signQueue')
function getAccountBalance(account, asset) {
return account.balances.find((token) => token.symbol === asset).free
}

while (!ready) {
await delay(1000)
async function buildTx(from, account, data, txAttempt) {
const { closeEpoch, newEpoch, nonce } = data

const txOptions = {
from,
accountNumber: account.account_number,
sequence: nonce,
asset: FOREIGN_ASSET,
memo: `Attempt ${txAttempt}`
}
let exchanges

channel.prefetch(1)
signQueue.consume(async (msg) => {
const data = JSON.parse(msg.content)
if (closeEpoch) {
logger.info(`Building corresponding account flags transaction, nonce ${nonce}`)

txOptions.flags = 0x01
} else if (newEpoch) {
const newKeysFile = `/keys/keys${newEpoch}.store`
const to = getAccountFromFile(newKeysFile).address

logger.info('Consumed sign event: %o', data)
const {
nonce, epoch, newEpoch, parties, threshold, closeEpoch
} = data

const keysFile = `/keys/keys${epoch || closeEpoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile)
if (from === '') {
logger.info('No keys found, acking message')
channel.ack(msg)
return
if (to === '') {
return { tx: null }
}
const account = await getAccount(from)

logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({
parties: parties.toString(),
threshold: (threshold - 1).toString()
logger.info(`Building corresponding transaction for transferring all funds, nonce ${nonce}, recipient ${to}`)
const fee = await getFee()

txOptions.recipients = [{
to,
tokens: getAccountBalance(account, FOREIGN_ASSET),
bnbs: new BN(getAccountBalance(account, 'BNB')).minus(fee)
}]
} else {
logger.info(`Building corresponding transfer transaction, nonce ${nonce}`)
exchanges = await getExchangeMessages(nonce)
const exchangesData = exchanges.map((exchangeMsg) => JSON.parse(exchangeMsg.content))

txOptions.recipients = exchangesData.map(({ value, recipient }) => ({
to: recipient,
tokens: value
}))
}

attempt = 1
const tx = new Transaction(txOptions)

if (closeEpoch) {
while (true) {
logger.info(`Building corresponding account flags transaction, nonce ${nonce}`)
return {
tx,
exchanges
}
}

const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
flags: 0x01,
memo: `Attempt ${attempt}`
})
function writeParams(parties, threshold) {
logger.debug('Writing params')
fs.writeFileSync('./params', JSON.stringify({
parties: parties.toString(),
threshold: (threshold - 1).toString()
}))
}

const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const signResult = await sign(keysFile, hash, tx, publicKey, from)
async function consumer(msg) {
const data = JSON.parse(msg.content)

if (signResult === SIGN_OK || signResult === SIGN_NONCE_INTERRUPT) {
await confirmCloseEpoch(closeEpoch)
break
}
logger.info('Consumed sign event: %o', data)
const {
nonce, epoch, newEpoch, parties, threshold, closeEpoch
} = data

// 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))

if (exchanges.length > 0 && account.sequence <= nonce) {
const recipients = exchangesData.map(({ value, recipient }) => ({
to: recipient,
tokens: value
}))

while (true) {
logger.info(`Building corresponding transfer transaction, nonce ${nonce}`)

const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
recipients,
asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}`
})

const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const signResult = await sign(keysFile, hash, tx, publicKey, from)

if (signResult === SIGN_OK || signResult === SIGN_NONCE_INTERRUPT) {
// eslint-disable-next-line no-loop-func
exchanges.forEach((exchangeMsg) => channel.ack(exchangeMsg))
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 (account.sequence <= nonce) {
const newKeysFile = `/keys/keys${newEpoch}.store`
const { address: to } = getAccountFromFile(newKeysFile)

while (to !== '') {
logger.info(`Building corresponding transaction for transferring all funds, nonce ${nonce}, recipient ${to}`)
const fee = await getFee()
const tx = new Transaction({
from,
accountNumber: account.account_number,
sequence: nonce,
recipients: [{
to,
tokens: account.balances.find((token) => token.symbol === FOREIGN_ASSET).free,
bnbs: new BN(account.balances.find((token) => token.symbol === 'BNB').free).minus(new BN(fee).div(10 ** 8))
}],
asset: FOREIGN_ASSET,
memo: `Attempt ${attempt}`
})
const keysFile = `/keys/keys${epoch || closeEpoch}.store`
const { address: from, publicKey } = getAccountFromFile(keysFile)
if (from === '') {
logger.info('No keys found, acking message')
channel.ack(msg)
return
}
const account = await getAccount(from)

const hash = sha256(tx.getSignBytes())
logger.info(`Starting signature generation for transaction hash ${hash}`)
const signResult = await sign(keysFile, hash, tx, publicKey, from)
if (nonce > account.sequence) {
logger.debug('Tx has been already sent')
logger.info('Acking message (skipped nonce)')
channel.ack(msg)
return
}

if (signResult === SIGN_OK || signResult === SIGN_NONCE_INTERRUPT) {
await confirmFundsTransfer(epoch)
break
}
writeParams(parties, threshold)
let attempt = 1

const { tx, exchanges } = buildTx(from, account, data, attempt)

// 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)
while (tx !== null) {
const signResult = await sign(keysFile, tx, publicKey, from)

if (signResult === SIGN_OK || signResult === SIGN_NONCE_INTERRUPT) {
if (closeEpoch) {
await confirmCloseEpoch(closeEpoch)
} else if (newEpoch) {
await confirmFundsTransfer(epoch)
} else {
// eslint-disable-next-line no-loop-func
exchanges.forEach((exchangeMsg) => channel.ack(exchangeMsg))
}
} else {
logger.debug('Tx has been already sent')
break
}
logger.info('Acking message')
channel.ack(msg)
})

// signer either failed, or timed out after parties signup
attempt = nextAttempt || attempt + 1
nextAttempt = null
logger.warn(`Sign failed, starting next attempt ${attempt}`)
tx.tx.memo = `Attempt ${attempt}`
await delay(1000)
}
logger.info('Acking message')
channel.ack(msg)
}


async function main() {
channel = await connectRabbit(RABBITMQ_URL)
logger.info('Connecting to signature events queue')
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
const signQueue = await assertQueue(channel, 'signQueue')

while (!ready) {
await delay(1000)
}

channel.prefetch(1)
signQueue.consume(consumer)
}

app.get('/restart/:attempt', restart)
Expand Down

0 comments on commit fd549fe

Please sign in to comment.