Skip to content

Commit

Permalink
Merge pull request #71 from poanetwork/24-enhance-check-for-duplicate…
Browse files Browse the repository at this point in the history
…d-transactions-handling

Enhance check for duplicated transactions handling
  • Loading branch information
akolotov committed Sep 21, 2018
2 parents ef4e6b4 + 481d556 commit 8359556
Show file tree
Hide file tree
Showing 21 changed files with 1,098 additions and 208 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Expand Up @@ -12,6 +12,7 @@
"no-console": "off",
"no-param-reassign": "off",
"no-plusplus": "off",
"no-restricted-syntax": "off",
"no-shadow": "off",
"no-use-before-define": ["error", { "functions": false }],
"import/no-dynamic-require": "off"
Expand Down
3 changes: 2 additions & 1 deletion e2e/parity/chain.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -12,6 +12,7 @@
"sender:foreign": "./scripts/start-worker.sh sender foreign-sender",
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'npm run watcher:signature-request' 'npm run watcher:collected-signatures' 'npm run watcher:affirmation-request' 'npm run sender:home' 'npm run sender:foreign'",
"test": "NODE_ENV=test mocha",
"test:watch": "NODE_ENV=test mocha --watch --reporter=min",
"coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha",
"postinstall": "npm install --prefix e2e && mkdir -p logs"
},
Expand All @@ -35,6 +36,7 @@
"web3-utils": "^1.0.0-beta.34"
},
"devDependencies": {
"bn-chai": "^1.0.1",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"concurrently": "^3.6.0",
Expand Down
2 changes: 2 additions & 0 deletions scripts/start-worker.sh
@@ -1,5 +1,7 @@
#!/usr/bin/env bash

set -o pipefail

WORKERS_DIR="src/"
LOGS_DIR="logs/"

Expand Down
62 changes: 0 additions & 62 deletions src/events/processAffirmationRequests.js

This file was deleted.

61 changes: 61 additions & 0 deletions src/events/processAffirmationRequests/estimateGas.js
@@ -0,0 +1,61 @@
const { HttpListProviderError } = require('http-list-provider')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')

async function estimateGas({
web3,
homeBridge,
validatorContract,
recipient,
value,
txHash,
address
}) {
try {
const gasEstimate = await homeBridge.methods
.executeAffirmation(recipient, value, txHash)
.estimateGas({
from: address
})

return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e
}

const messageHash = web3.utils.soliditySha3(recipient, value, txHash)
const senderHash = web3.utils.soliditySha3(address, messageHash)

// Check if minimum number of validations was already reached
const numAffirmationsSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call()
const alreadyProcessed = await homeBridge.methods
.isAlreadyProcessed(numAffirmationsSigned)
.call()

if (alreadyProcessed) {
throw new AlreadyProcessedError(e.message)
}

// Check if the message was already signed by this validator
const alreadySigned = await homeBridge.methods.affirmationsSigned(senderHash).call()

if (alreadySigned) {
throw new AlreadySignedError(e.message)
}

// Check if address is validator
const isValidator = await validatorContract.methods.isValidator(address).call()

if (!isValidator) {
throw new InvalidValidatorError(`${address} is not a validator`)
}

throw new Error('Unknown error while processing message')
}
}

module.exports = estimateGas
98 changes: 98 additions & 0 deletions src/events/processAffirmationRequests/index.js
@@ -0,0 +1,98 @@
require('dotenv').config()
const logger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const promiseLimit = require('promise-limit')
const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi')
const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const estimateGas = require('./estimateGas')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const { HttpListProviderError } = require('http-list-provider')

const { VALIDATOR_ADDRESS } = process.env

const limit = promiseLimit(MAX_CONCURRENT_EVENTS)

let validatorContract = null

function processAffirmationRequestsBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)

return async function processAffirmationRequests(affirmationRequests) {
const txToSend = []

if (validatorContract === null) {
const validatorContractAddress = await homeBridge.methods.validatorContract().call()
validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress)
}

const callbacks = affirmationRequests.map(affirmationRequest =>
limit(async () => {
const { recipient, value } = affirmationRequest.returnValues

logger.info(
{ eventTransactionHash: affirmationRequest.transactionHash, sender: recipient, value },
`Processing affirmationRequest ${affirmationRequest.transactionHash}`
)

let gasEstimate
try {
gasEstimate = await estimateGas({
web3: web3Home,
homeBridge,
validatorContract,
recipient,
value,
txHash: affirmationRequest.transactionHash,
address: VALIDATOR_ADDRESS
})
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator')
process.exit(10)
} else if (e instanceof AlreadySignedError) {
logger.info(
{ eventTransactionHash: affirmationRequest.transactionHash },
`Already signed affirmationRequest ${affirmationRequest.transactionHash}`
)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
{ eventTransactionHash: affirmationRequest.transactionHash },
`affirmationRequest ${
affirmationRequest.transactionHash
} was already processed by other validators`
)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}

const data = await homeBridge.methods
.executeAffirmation(recipient, value, affirmationRequest.transactionHash)
.encodeABI({ from: VALIDATOR_ADDRESS })

txToSend.push({
data,
gasEstimate,
transactionReference: affirmationRequest.transactionHash,
to: config.homeBridgeAddress
})
})
)

await Promise.all(callbacks)
return txToSend
}
}

module.exports = processAffirmationRequestsBuilder
59 changes: 59 additions & 0 deletions src/events/processCollectedSignatures/estimateGas.js
@@ -0,0 +1,59 @@
const Web3 = require('web3')
const { HttpListProviderError } = require('http-list-provider')
const {
AlreadyProcessedError,
IncompatibleContractError,
InvalidValidatorError
} = require('../../utils/errors')
const { parseMessage } = require('../../utils/message')

const web3 = new Web3()
const { toBN } = Web3.utils

async function estimateGas({
foreignBridge,
validatorContract,
message,
numberOfCollectedSignatures,
v,
r,
s
}) {
try {
const gasEstimate = await foreignBridge.methods
.executeSignatures(v, r, s, message)
.estimateGas()
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e
}

// check if the message was already processed
const { txHash } = parseMessage(message)
const alreadyProcessed = await foreignBridge.methods.relayedMessages(txHash).call()
if (alreadyProcessed) {
throw new AlreadyProcessedError()
}

// check if the number of signatures is enough
const requiredSignatures = await validatorContract.methods.requiredSignatures().call()
if (toBN(requiredSignatures).gt(toBN(numberOfCollectedSignatures))) {
throw new IncompatibleContractError('The number of collected signatures does not match')
}

// check if all the signatures were made by validators
for (let i = 0; i < v.length; i++) {
const address = web3.eth.accounts.recover(message, web3.utils.toHex(v[i]), r[i], s[i])
const isValidator = await validatorContract.methods.isValidator(address).call()

if (!isValidator) {
throw new InvalidValidatorError(`Message signed by ${address} that is not a validator`)
}
}

throw new Error('Unknown error while processing message')
}
}

module.exports = estimateGas

0 comments on commit 8359556

Please sign in to comment.