Send tx using raw RPC method call for efficiency

This commit is contained in:
Kirill Fedoseev 2019-10-16 22:42:40 +03:00
parent f4150a3324
commit dc668f452b
5 changed files with 113 additions and 70 deletions

View File

@ -17,6 +17,8 @@ services:
- FOREIGN_URL
- FOREIGN_ASSET
- LOG_LEVEL
- "GAS_LIMIT_FACTOR=3"
- "MAX_GAS_LIMIT=6000000"
volumes:
- '../deploy/deploy-test/build/contracts/IERC20.json:/proxy/contracts_data/IERC20.json'
- '../deploy/deploy-home/build/contracts/Bridge.json:/proxy/contracts_data/Bridge.json'

View File

@ -17,6 +17,8 @@ services:
- FOREIGN_URL
- FOREIGN_ASSET
- LOG_LEVEL
- "GAS_LIMIT_FACTOR=3"
- "MAX_GAS_LIMIT=6000000"
volumes:
- '../deploy/deploy-test/build/contracts/IERC20.json:/proxy/contracts_data/IERC20.json'
- '../deploy/deploy-home/build/contracts/Bridge.json:/proxy/contracts_data/Bridge.json'

View File

@ -6,6 +6,6 @@ COPY ./proxy/package.json /proxy/
RUN npm install
COPY ./proxy/index.js ./proxy/encode.js ./proxy/decode.js ./shared/logger.js ./shared/crypto.js /proxy/
COPY ./proxy/index.js ./proxy/encode.js ./proxy/decode.js ./proxy/sendTx.js ./shared/logger.js ./shared/crypto.js /proxy/
ENTRYPOINT ["node", "index.js"]

View File

@ -7,6 +7,7 @@ const { utils } = require('ethers')
const encode = require('./encode')
const decode = require('./decode')
const { createSender, waitForReceipt } = require('./sendTx')
const logger = require('./logger')
const { publicKeyToAddress } = require('./crypto')
@ -31,8 +32,8 @@ const lock = new AsyncLock()
let homeValidatorNonce
let sideValidatorNonce
let homeBlockGasLimit
let sideBlockGasLimit
let homeSender
let sideSender
const app = express()
app.use(express.json())
@ -63,8 +64,8 @@ async function main () {
homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress)
sideValidatorNonce = await sideWeb3.eth.getTransactionCount(validatorAddress)
homeBlockGasLimit = (await homeWeb3.eth.getBlock('latest', false)).gasLimit
sideBlockGasLimit = (await sideWeb3.eth.getBlock('latest', false)).gasLimit
homeSender = await createSender(HOME_RPC_URL, VALIDATOR_PRIVATE_KEY)
sideSender = await createSender(SIDE_RPC_URL, VALIDATOR_PRIVATE_KEY)
logger.warn(`My validator address in home and side networks is ${validatorAddress}`)
@ -151,10 +152,11 @@ async function signupSign (req, res) {
logger.debug('SignupSign call')
const hash = sideWeb3.utils.sha3(`0x${req.body.third}`)
const query = sharedDb.methods.signupSign(hash)
const receipt = await sideSendQuery(query)
const txHash = await sideSendQuery(query)
const receipt = await waitForReceipt(SIDE_RPC_URL, txHash)
// Already have signup
if (receipt === false) {
if (receipt.status === false) {
res.send(Ok({ uuid: hash, number: 0 }))
logger.debug('Already have signup')
return
@ -185,81 +187,27 @@ async function confirmFundsTransfer (req, res) {
}
function sideSendQuery (query) {
return lock.acquire('side', async () => {
logger.debug('Sending query')
return lock.acquire('home', async () => {
logger.debug('Sending side query')
const encodedABI = query.encodeABI()
const tx = {
return await sideSender({
data: encodedABI,
from: validatorAddress,
to: SIDE_SHARED_DB_ADDRESS,
nonce: sideValidatorNonce++,
chainId: await sideWeb3.eth.net.getId()
}
tx.gas = Math.min(Math.ceil(await query.estimateGas({
from: validatorAddress
}) * 1.5), sideBlockGasLimit)
const signedTx = await sideWeb3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
return sideWeb3.eth.sendSignedTransaction(signedTx.rawTransaction)
.catch(e => {
const error = parseError(e.message)
const reason = parseReason(e.message)
if (error === 'revert' && reason.length) {
logger.debug(reason)
return false
} else if (error === 'out of gas') {
logger.debug('Out of gas, retrying')
return true
} else {
logger.debug('Side tx failed, retrying, %o', e.message)
return true
}
})
})
.then(result => {
if (result === true)
return sideSendQuery(query)
return result !== false
nonce: sideValidatorNonce++
})
})
}
function homeSendQuery (query) {
return lock.acquire('home', async () => {
logger.debug('Sending query')
logger.debug('Sending home query')
const encodedABI = query.encodeABI()
const tx = {
return await homeSender({
data: encodedABI,
from: validatorAddress,
to: HOME_BRIDGE_ADDRESS,
nonce: homeValidatorNonce++,
chainId: await homeWeb3.eth.net.getId()
}
tx.gas = Math.min(Math.ceil(await query.estimateGas({
from: validatorAddress
}) * 1.5), homeBlockGasLimit)
const signedTx = await homeWeb3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
return homeWeb3.eth.sendSignedTransaction(signedTx.rawTransaction)
.catch(e => {
const error = parseError(e.message)
const reason = parseReason(e.message)
if (error === 'revert' && reason.length) {
logger.debug(reason)
return false
} else if (error === 'out of gas') {
logger.debug('Out of gas, retrying')
return true
} else {
logger.debug('Home tx failed, retrying, %o', e.message)
return true
}
})
})
.then(result => {
if (result === true)
return homeSendQuery(query)
return result !== false
nonce: homeValidatorNonce++
})
})
}
function parseReason (message) {

View File

@ -0,0 +1,91 @@
const Web3 = require('web3')
const axios = require('axios')
const ethers = require('ethers')
const BN = require('bignumber.js')
const logger = require('./logger')
const { GAS_LIMIT_FACTOR, MAX_GAS_LIMIT } = process.env
function sendRpcRequest (url, method, params) {
return axios.post(url, {
jsonrpc: '2.0',
method,
params,
id: 1
})
.then(res => res.data)
.catch(async e => {
logger.warn(`Request to ${url}, method ${method} failed, retrying`)
await new Promise(res => setTimeout(res, 1000))
return sendRpcRequest(url, method, params)
})
}
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 = {
data: tx.data,
to: tx.to,
nonce: tx.nonce,
chainId,
value: `0x${new BN(tx.value || 0).toString(16)}`,
gasPrice: `0x${new BN(tx.gasPrice || 1000000000).toString(16)}`
}
try {
logger.trace(`Preparing and sending transaction %o on ${url}`, tx)
const estimate = await sendRpcRequest(url, 'eth_estimateGas', [ {
from: signer.address,
to: tx.to,
data: tx.data,
gasPrice: tx.gasPrice,
value: tx.value,
gas: `0x${new BN(MAX_GAS_LIMIT).toString(16)}`
} ])
if (estimate.error) {
logger.debug('Gas estimate failed %o', estimate.error)
return false
}
const gasLimit = BN.min(new BN(estimate.result, 16).multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT)
tx.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 ])
// handle nonce error
// handle insufficient funds error
if (error) {
logger.debug('Sending signed tx %o failed, %o', tx, error)
return false
}
return result
} catch (e) {
logger.warn('Something failed, %o', e)
return false
}
}
}
async function waitForReceipt (url, txHash) {
while (true) {
const { result, error } = await sendRpcRequest(url, 'eth_getTransactionReceipt', [ txHash ])
if(result === null || error) {
await new Promise(res => setTimeout(res, 1000))
} else {
return result
}
}
}
module.exports = { createSender, waitForReceipt }