Compare commits

..

3 Commits

Author SHA1 Message Date
Alexander Kolotov
dc24bf0984 more logs are added to troubleshoots the issues with snapshot preparation 2020-08-11 20:17:50 +03:00
Alexander Kolotov
f3f226afdf Correction of the message for a transaction without the bridge requests (#416) 2020-08-06 23:43:13 +03:00
Kirill Fedoseev
1eb8a8b1dc Use requestGasLimit parameter in AMB oracle in estimateGas (#415) 2020-08-06 16:15:51 +03:00
14 changed files with 136 additions and 27 deletions

View File

@@ -28,16 +28,24 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress) const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress)
console.log('Getting the block to start looking for the bridge events for', side)
let deployedAtBlock = await bridgeContract.methods.deployedAtBlock().call()
console.log('Getting events to track changes of required block confirmations for', side)
// Save RequiredBlockConfirmationChanged events // Save RequiredBlockConfirmationChanged events
let requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('RequiredBlockConfirmationChanged', { let requiredBlockConfirmationChangedEvents = []
fromBlock: 0, requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('RequiredBlockConfirmationChanged', {
fromBlock: deployedAtBlock,
toBlock: currentBlockNumber toBlock: currentBlockNumber
}).catch(error => {
console.log('Cannot get required block confirmations for', side)
}) })
// In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB // In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
// manually generate an event for this. Example Sokol - Kovan bridge // manually generate an event for this. Example Sokol - Kovan bridge
if (requiredBlockConfirmationChangedEvents.length === 0) { if (requiredBlockConfirmationChangedEvents.length === 0) {
const deployedAtBlock = await bridgeContract.methods.deployedAtBlock().call() console.log('Getting required block confirmations from the contract storage for', side)
const blockConfirmations = await bridgeContract.methods.requiredBlockConfirmations().call() const blockConfirmations = await bridgeContract.methods.requiredBlockConfirmations().call()
requiredBlockConfirmationChangedEvents.push({ requiredBlockConfirmationChangedEvents.push({
@@ -58,9 +66,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const validatorAddress = await bridgeContract.methods.validatorContract().call() const validatorAddress = await bridgeContract.methods.validatorContract().call()
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress) const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress)
console.log('Getting the block to start looking for the validators events for', side)
deployedAtBlock = await validatorContract.methods.deployedAtBlock().call()
console.log('Getting events to track changes of required signatures for', side)
// Save RequiredSignaturesChanged events // Save RequiredSignaturesChanged events
const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', { const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', {
fromBlock: 0, fromBlock: deployedAtBlock,
toBlock: currentBlockNumber toBlock: currentBlockNumber
}) })
snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({ snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({
@@ -70,9 +83,10 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
} }
})) }))
console.log('Getting events to track changes of validators added for', side)
// Save ValidatorAdded events // Save ValidatorAdded events
const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', { const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', {
fromBlock: 0, fromBlock: deployedAtBlock,
toBlock: currentBlockNumber toBlock: currentBlockNumber
}) })
@@ -84,9 +98,10 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
event: 'ValidatorAdded' event: 'ValidatorAdded'
})) }))
console.log('Getting events to track changes of validators removed for', side)
// Save ValidatorRemoved events // Save ValidatorRemoved events
const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', { const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', {
fromBlock: 0, fromBlock: deployedAtBlock,
toBlock: currentBlockNumber toBlock: currentBlockNumber
}) })
@@ -114,5 +129,5 @@ main()
.catch(error => { .catch(error => {
console.log('Error while creating snapshots') console.log('Error while creating snapshots')
console.error(error) console.error(error)
process.exit(0) process.exit(1)
}) })

View File

@@ -64,10 +64,6 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash
const formattedMessageId = formatTxHash(displayReference) const formattedMessageId = formatTxHash(displayReference)
const displayedDescription = multiMessageSelected
? getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE, timestamp)
: description
const isHome = chainId === home.chainId.toString() const isHome = chainId === home.chainId.toString()
const txExplorerLink = getExplorerTxUrl(txHash, isHome) const txExplorerLink = getExplorerTxUrl(txHash, isHome)
const displayExplorerLink = status !== TRANSACTION_STATUS.NOT_FOUND const displayExplorerLink = status !== TRANSACTION_STATUS.NOT_FOUND
@@ -75,17 +71,32 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
const displayConfirmations = status === TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE || multiMessageSelected const displayConfirmations = status === TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE || multiMessageSelected
const messageToConfirm = const messageToConfirm =
messages.length > 1 ? messages[selectedMessageId] : messages.length > 0 ? messages[0] : { id: '', data: '' } messages.length > 1 ? messages[selectedMessageId] : messages.length > 0 ? messages[0] : { id: '', data: '' }
let displayedDescription: string = multiMessageSelected
? getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE, timestamp)
: description
let link
const descArray = displayedDescription.split('%link')
if (descArray.length > 1) {
displayedDescription = descArray[0]
link = (
<ExplorerTxLink href={descArray[1]} target="_blank" rel="noopener noreferrer">
{descArray[1]}
</ExplorerTxLink>
)
}
return ( return (
<div> <div>
{status && ( {status && (
<p> <p>
The request{' '} The transaction{' '}
{displayExplorerLink && ( {displayExplorerLink && (
<ExplorerTxLink href={txExplorerLink} target="_blank"> <ExplorerTxLink href={txExplorerLink} target="_blank">
{formattedMessageId} {formattedMessageId}
</ExplorerTxLink> </ExplorerTxLink>
)} )}
{!displayExplorerLink && <label>{formattedMessageId}</label>} {displayedDescription} {!displayExplorerLink && <label>{formattedMessageId}</label>} {displayedDescription} {link}
</p> </p>
)} )}
{displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />} {displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />}

View File

@@ -2,7 +2,8 @@
export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = { export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = {
SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:', SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:',
SUCCESS_ONE_MESSAGE: 'was initiated %t', SUCCESS_ONE_MESSAGE: 'was initiated %t',
SUCCESS_NO_MESSAGES: 'execution succeeded %t but it does not contain any bridge messages', SUCCESS_NO_MESSAGES:
'successfully mined %t but it does not seem to contain any request to the bridge, \nso nothing needs to be confirmed by the validators. \nIf you are sure that the transaction should contain a request to the bridge,\ncontact to the validators by \nmessaging on %linkhttps://forum.poa.network/c/support',
FAILED: 'failed %t', FAILED: 'failed %t',
NOT_FOUND: NOT_FOUND:
'Transaction not found. \n1. Check that the transaction hash is correct. \n2. Wait several blocks for the transaction to be\nmined, gas price affects mining speed.' 'Transaction not found. \n1. Check that the transaction hash is correct. \n2. Wait several blocks for the transaction to be\nmined, gas price affects mining speed.'

View File

@@ -7,7 +7,7 @@ const rpcUrlsManager = require('./services/getRpcUrlsManager')
const { getNonce, getChainId, getEventsFromTx } = require('./tx/web3') const { getNonce, getChainId, getEventsFromTx } = require('./tx/web3')
const { sendTx } = require('./tx/sendTx') const { sendTx } = require('./tx/sendTx')
const { checkHTTPS, watchdog, syncForEach, addExtraGas } = require('./utils/utils') const { checkHTTPS, watchdog, syncForEach, addExtraGas } = require('./utils/utils')
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE } = require('./utils/constants') const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants')
const { ORACLE_VALIDATOR_ADDRESS, ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, ORACLE_ALLOW_HTTP_FOR_RPC } = process.env const { ORACLE_VALIDATOR_ADDRESS, ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, ORACLE_ALLOW_HTTP_FOR_RPC } = process.env
@@ -143,7 +143,12 @@ async function sendJobTx(jobs) {
let nonce = await getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS) let nonce = await getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS)
await syncForEach(jobs, async job => { await syncForEach(jobs, async job => {
const gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE) let gasLimit
if (typeof job.extraGas === 'number') {
gasLimit = addExtraGas(job.gasEstimate + job.extraGas, 0, MAX_GAS_LIMIT)
} else {
gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT)
}
try { try {
logger.info(`Sending transaction with nonce ${nonce}`) logger.info(`Sending transaction with nonce ${nonce}`)

View File

@@ -3,14 +3,23 @@ const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = req
const logger = require('../../services/logger').child({ const logger = require('../../services/logger').child({
module: 'processAffirmationRequests:estimateGas' module: 'processAffirmationRequests:estimateGas'
}) })
const { parseAMBHeader } = require('../../utils/message')
const { strip0x } = require('../../../../commons')
const {
AMB_AFFIRMATION_REQUEST_EXTRA_GAS_ESTIMATOR: estimateExtraGas,
MIN_AMB_HEADER_LENGTH
} = require('../../utils/constants')
async function estimateGas({ web3, homeBridge, validatorContract, message, address }) { async function estimateGas({ web3, homeBridge, validatorContract, message, address }) {
try { try {
const gasEstimate = await homeBridge.methods.executeAffirmation(message).estimateGas({ const gasEstimate = await homeBridge.methods.executeAffirmation(message).estimateGas({
from: address from: address
}) })
const msgGasLimit = parseAMBHeader(message).gasLimit
// message length in bytes
const len = strip0x(message).length / 2 - MIN_AMB_HEADER_LENGTH
return gasEstimate return gasEstimate + msgGasLimit + estimateExtraGas(len)
} catch (e) { } catch (e) {
if (e instanceof HttpListProviderError) { if (e instanceof HttpListProviderError) {
throw e throw e

View File

@@ -4,7 +4,7 @@ const promiseLimit = require('promise-limit')
const rootLogger = require('../../services/logger') const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3') const { web3Home } = require('../../services/web3')
const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { EXIT_CODES, MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants')
const estimateGas = require('./estimateGas') const estimateGas = require('./estimateGas')
const { parseAMBMessage } = require('../../../../commons') const { parseAMBMessage } = require('../../../../commons')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
@@ -75,6 +75,7 @@ function processAffirmationRequestsBuilder(config) {
txToSend.push({ txToSend.push({
data, data,
gasEstimate, gasEstimate,
extraGas: EXTRA_GAS_ABSOLUTE,
transactionReference: affirmationRequest.transactionHash, transactionReference: affirmationRequest.transactionHash,
to: config.homeBridgeAddress to: config.homeBridgeAddress
}) })

View File

@@ -4,6 +4,7 @@ const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError
const logger = require('../../services/logger').child({ const logger = require('../../services/logger').child({
module: 'processCollectedSignatures:estimateGas' module: 'processCollectedSignatures:estimateGas'
}) })
const { parseAMBHeader } = require('../../utils/message')
const web3 = new Web3() const web3 = new Web3()
const { toBN } = Web3.utils const { toBN } = Web3.utils
@@ -24,7 +25,12 @@ async function estimateGas({
const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({ const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({
from: address from: address
}) })
return gasEstimate const msgGasLimit = parseAMBHeader(message).gasLimit
// + estimateExtraGas(len)
// is not needed here, since estimateGas will already take into account gas
// needed for memory expansion, message processing, etc.
return gasEstimate + msgGasLimit
} catch (e) { } catch (e) {
if (e instanceof HttpListProviderError) { if (e instanceof HttpListProviderError) {
throw e throw e

View File

@@ -8,7 +8,7 @@ const { signatureToVRS, packSignatures } = require('../../utils/message')
const { parseAMBMessage } = require('../../../../commons') const { parseAMBMessage } = require('../../../../commons')
const estimateGas = require('./estimateGas') const estimateGas = require('./estimateGas')
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors') const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS) const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
@@ -107,6 +107,7 @@ function processCollectedSignaturesBuilder(config) {
txToSend.push({ txToSend.push({
data, data,
gasEstimate, gasEstimate,
extraGas: EXTRA_GAS_ABSOLUTE,
transactionReference: colSignature.transactionHash, transactionReference: colSignature.transactionHash,
to: config.foreignBridgeAddress to: config.foreignBridgeAddress
}) })

View File

@@ -16,7 +16,7 @@ const {
watchdog, watchdog,
nonceError nonceError
} = require('./utils/utils') } = require('./utils/utils')
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE } = require('./utils/constants') const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants')
const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
@@ -106,7 +106,12 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry }) {
logger.debug(`Sending ${txArray.length} transactions`) logger.debug(`Sending ${txArray.length} transactions`)
await syncForEach(txArray, async job => { await syncForEach(txArray, async job => {
const gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE) let gasLimit
if (typeof job.extraGas === 'number') {
gasLimit = addExtraGas(job.gasEstimate + job.extraGas, 0, MAX_GAS_LIMIT)
} else {
gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT)
}
try { try {
logger.info(`Sending transaction with nonce ${nonce}`) logger.info(`Sending transaction with nonce ${nonce}`)

View File

@@ -1,5 +1,9 @@
module.exports = { module.exports = {
EXTRA_GAS_PERCENTAGE: 4, EXTRA_GAS_PERCENTAGE: 4,
EXTRA_GAS_ABSOLUTE: 200000,
AMB_AFFIRMATION_REQUEST_EXTRA_GAS_ESTIMATOR: len => Math.floor(0.0035 * len ** 2 + 40 * len),
MIN_AMB_HEADER_LENGTH: 32 + 20 + 20 + 4 + 2 + 1 + 2,
MAX_GAS_LIMIT: 10000000,
MAX_CONCURRENT_EVENTS: 50, MAX_CONCURRENT_EVENTS: 50,
RETRY_CONFIG: { RETRY_CONFIG: {
retries: 20, retries: 20,

View File

@@ -73,9 +73,37 @@ function packSignatures(array) {
return `0x${msgLength}${v}${r}${s}` return `0x${msgLength}${v}${r}${s}`
} }
function parseAMBHeader(message) {
message = strip0x(message)
const messageIdStart = 0
const messageIdLength = 32 * 2
const messageId = `0x${message.slice(messageIdStart, messageIdStart + messageIdLength)}`
const senderStart = messageIdStart + messageIdLength
const senderLength = 20 * 2
const sender = `0x${message.slice(senderStart, senderStart + senderLength)}`
const executorStart = senderStart + senderLength
const executorLength = 20 * 2
const executor = `0x${message.slice(executorStart, executorStart + executorLength)}`
const gasLimitStart = executorStart + executorLength
const gasLimitLength = 4 * 2
const gasLimit = parseInt(message.slice(gasLimitStart, gasLimitStart + gasLimitLength), 16)
return {
messageId,
sender,
executor,
gasLimit
}
}
module.exports = { module.exports = {
createMessage, createMessage,
parseMessage, parseMessage,
signatureToVRS, signatureToVRS,
packSignatures packSignatures,
parseAMBHeader
} }

View File

@@ -48,13 +48,13 @@ async function waitForFunds(web3, address, minimumBalance, cb, logger) {
) )
} }
function addExtraGas(gas, extraPercentage) { function addExtraGas(gas, extraPercentage, maxGasLimit = Infinity) {
gas = BigNumber(gas) gas = BigNumber(gas)
extraPercentage = BigNumber(1 + extraPercentage) extraPercentage = BigNumber(1 + extraPercentage)
const gasWithExtra = gas.multipliedBy(extraPercentage).toFixed(0) const gasWithExtra = gas.multipliedBy(extraPercentage).toFixed(0)
return BigNumber(gasWithExtra) return BigNumber.min(maxGasLimit, gasWithExtra)
} }
function setIntervalAndRun(f, interval) { function setIntervalAndRun(f, interval) {

View File

@@ -1,6 +1,6 @@
const { BN, toBN } = require('web3').utils const { BN, toBN } = require('web3').utils
const { expect } = require('chai').use(require('bn-chai')(BN)) const { expect } = require('chai').use(require('bn-chai')(BN))
const { createMessage, parseMessage, signatureToVRS } = require('../src/utils/message') const { createMessage, parseMessage, signatureToVRS, parseAMBHeader } = require('../src/utils/message')
describe('message utils', () => { describe('message utils', () => {
const expectedMessageLength = 104 const expectedMessageLength = 104
@@ -288,4 +288,19 @@ describe('message utils', () => {
expect(signatureThunk).to.throw() expect(signatureThunk).to.throw()
}) })
}) })
describe('parseAMBHeader', () => {
it('should return correct values for parsed headers', () => {
// given
const message =
'0x000500009a6ff99b356dd998260582be7d95a4d08b2132600000000000000061339d0e6f308a410f18888932bdf661636a0f538f34718200957aeadd6bece186e61b95618e73a6dc000f42400101002a4d2fb102cf00000000000000000000000081c770bbe8f5f41b4642ed575e630c911c94e4070000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002e516d543162324178466b6643456a6f715861547148734370666f724a4c66765667434853516e513847575a347662000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e516d5a7543586f693378487338486e4c325a423539316674466f4c454471516b473655746e47324d6d513147614e000000000000000000000000000000000000'
const { messageId, sender, executor, gasLimit } = parseAMBHeader(message)
// then
expect(messageId).to.equal('0x000500009a6ff99b356dd998260582be7d95a4d08b2132600000000000000061')
expect(sender).to.equal('0x339d0e6f308a410f18888932bdf661636a0f538f')
expect(executor).to.equal('0x34718200957aeadd6bece186e61b95618e73a6dc')
expect(gasLimit).to.equal(1000000)
})
})
}) })

View File

@@ -34,6 +34,14 @@ describe('utils', () => {
expect(result.toString()).to.equal('225') expect(result.toString()).to.equal('225')
}) })
it('should handle maxGasLimit', () => {
const result1 = addExtraGas(new BigNumber(100), 0.25, 110)
const result2 = addExtraGas(new BigNumber(100), 0.25, 150)
expect(result1.toString()).to.equal('110')
expect(result2.toString()).to.equal('125')
})
}) })
describe('checkHTTPS', () => { describe('checkHTTPS', () => {