From 0f2e792c340dae0b1374431efc29d22ca9aa0b1f Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:50:37 -0300 Subject: [PATCH 01/24] Add bridgeable token parameters to env files --- e2e/envs/contracts-deploy.env | 4 ++++ e2e/envs/erc-contracts-deploy.env | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/e2e/envs/contracts-deploy.env b/e2e/envs/contracts-deploy.env index ed7f46a..b5c0370 100644 --- a/e2e/envs/contracts-deploy.env +++ b/e2e/envs/contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b diff --git a/e2e/envs/erc-contracts-deploy.env b/e2e/envs/erc-contracts-deploy.env index 35705b5..5aa00e2 100644 --- a/e2e/envs/erc-contracts-deploy.env +++ b/e2e/envs/erc-contracts-deploy.env @@ -5,6 +5,10 @@ DEPLOYMENT_GAS_LIMIT=4000000 DEPLOYMENT_GAS_PRICE=10 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + HOME_RPC_URL=http://parity1:8545 HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b From 8ac0d9e7bf17dec361e0f5bda10f5158b2651d40 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:50:56 -0300 Subject: [PATCH 02/24] Upgrade http-list-provider version --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 962e70f..c9759de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2021,9 +2021,9 @@ "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" }, "http-list-provider": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.3.tgz", - "integrity": "sha512-+l3UPTtL8F5yjRstgZS/XbJ4ez7hirveDyhxoU5zfeiebNpJPgoslF0Owd1rGTQhic2/QuoM9AnWYby8H5NnOw==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/http-list-provider/-/http-list-provider-0.0.4.tgz", + "integrity": "sha512-UjGwD8+xNTyx1JUwlukwBnicias7lLVWobQewHKLvfpDfAPAYeb9dt03aWGcZuvR+NtbirPWtPkHoZFkalqrVg==", "requires": { "node-fetch": "^2.2.0" }, diff --git a/package.json b/package.json index 2e525f1..765ff97 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "amqplib": "^0.5.2", "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", - "http-list-provider": "0.0.3", + "http-list-provider": "0.0.4", "ioredis": "^3.2.2", "lodash": "^4.17.10", "node-fetch": "^2.1.2", From 342ebd809f18fbfefe0d39926dcf71f09c726bf3 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:51:33 -0300 Subject: [PATCH 03/24] Update poa-bridge-contracts submodule --- submodules/poa-bridge-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 57d16f2..4e869c0 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 57d16f2b8140e2bf237dd960972127590589dc4a +Subproject commit 4e869c0c06dcff4d1d38ca65e9a62e973f5c3e97 From a9098c8e326dd0588c611708412dd2b3935c5002 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:51:48 -0300 Subject: [PATCH 04/24] Add module with custom errors --- src/utils/errors.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/utils/errors.js diff --git a/src/utils/errors.js b/src/utils/errors.js new file mode 100644 index 0000000..24252b1 --- /dev/null +++ b/src/utils/errors.js @@ -0,0 +1,7 @@ +class AlreadyProcessedError extends Error {} +class InvalidValidatorError extends Error {} + +module.exports = { + AlreadyProcessedError, + InvalidValidatorError +} From 7acd0852f1469610b7b6725fd9b66d729719653e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 22 Aug 2018 11:52:21 -0300 Subject: [PATCH 05/24] Check if validator is correct when transaction fails --- src/events/processSignatureRequests.js | 57 ++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index c0f7a35..1ec3580 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -1,11 +1,36 @@ require('dotenv').config() const Web3 = require('web3') const HttpListProvider = require('http-list-provider') +const bridgeValidatorsABI = require('../../abis/BridgeValidators.abi') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') +const { AlreadyProcessedError, InvalidValidatorError } = require('../utils/errors') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env +const { HttpListProviderError } = HttpListProvider + +async function estimateGas(web3, bridgeContract, address, method, options) { + try { + const gasEstimate = await method.estimateGas(options) + return gasEstimate + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + const validatorContractAddress = await bridgeContract.methods.validatorContract().call() + const validatorContract = new web3.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + + const isValidator = await validatorContract.methods.isValidator(address).call() + + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) + } + + throw new AlreadyProcessedError(e.message) + } +} function processSignatureRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) @@ -34,18 +59,32 @@ function processSignatureRequestsBuilder(config) { let gasEstimate try { - gasEstimate = await homeBridge.methods - .submitSignature(signature.signature, message) - .estimateGas({ from: VALIDATOR_ADDRESS }) + gasEstimate = await estimateGas( + web3Home, + homeBridge, + VALIDATOR_ADDRESS, + homeBridge.methods.submitSignature(signature.signature, message), + { + from: VALIDATOR_ADDRESS + } + ) + logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { + if (e instanceof HttpListProviderError) { throw new Error(`RPC Connection Error: submitSignature Gas Estimate cannot be obtained.`) + } else if (e instanceof InvalidValidatorError) { + logger.warn({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + throw new Error('Current address does not correspond to a validator') + } else if (e instanceof AlreadyProcessedError) { + logger.info( + { eventTransactionHash: signatureRequest.transactionHash }, + `Already processed signatureRequest ${signatureRequest.transactionHash}` + ) + return + } else { + logger.error(e, 'Unknown error while processing transaction') + return } - logger.info( - { eventTransactionHash: signatureRequest.transactionHash }, - `Already processed signatureRequest ${signatureRequest.transactionHash}` - ) - return } const data = await homeBridge.methods From 88e7a0b4e68301b9fc708503a89f93e164d8a6da Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 3 Sep 2018 08:55:28 -0300 Subject: [PATCH 06/24] Add another account to docker test chain --- e2e/parity/chain.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/parity/chain.json b/e2e/parity/chain.json index 294c7e8..322a415 100644 --- a/e2e/parity/chain.json +++ b/e2e/parity/chain.json @@ -47,6 +47,7 @@ "0000000000000000000000000000000000000008": { "balance": "1", "builtin": { "name": "alt_bn128_pairing", "activate_at": 0, "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } } }, "0000000000000000000000000000000000001337": { "balance": "1", "constructor": "0x606060405233600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550670de0b6b3a764000060035534610000575b612904806100666000396000f3006060604052361561013c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306b2ff471461014157806313af40351461018c57806319362a28146101bf5780633f3935d114610248578063432ced04146102b75780634f39ca59146102eb5780636795dbcd1461032457806369fe0e2d146103c857806379ce9fac146103fd5780638da5cb5b1461045557806390b97fc1146104a457806392698814146105245780639890220b1461055d578063ac4e73f914610584578063ac72c12014610612578063c3a358251461064b578063ddca3f43146106c3578063deb931a2146106e6578063df57b74214610747578063e30bd740146107a8578063eadf976014610862578063ef5454d6146108e7578063f25eb5c114610975578063f6d339e414610984575b610000565b3461000057610172600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a1f565b604051808215151515815260200191505060405180910390f35b34610000576101bd600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a81565b005b346100005761022e60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803560001916906020019091905050610ba2565b604051808215151515815260200191505060405180910390f35b346100005761029d600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610dc9565b604051808215151515815260200191505060405180910390f35b6102d1600480803560001916906020019091905050611035565b604051808215151515815260200191505060405180910390f35b346100005761030a60048080356000191690602001909190505061115f565b604051808215151515815260200191505060405180910390f35b346100005761038660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611378565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576103e3600480803590602001909190505061140d565b604051808215151515815260200191505060405180910390f35b346100005761043b60048080356000191690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506114b4565b604051808215151515815260200191505060405180910390f35b34610000576104626115fb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b346100005761050660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611621565b60405180826000191660001916815260200191505060405180910390f35b34610000576105436004808035600019169060200190919050506116b2565b604051808215151515815260200191505060405180910390f35b346100005761056a611715565b604051808215151515815260200191505060405180910390f35b34610000576105f8600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611824565b604051808215151515815260200191505060405180910390f35b3461000057610631600480803560001916906020019091905050611d8b565b604051808215151515815260200191505060405180910390f35b34610000576106ad60048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611dee565b6040518082815260200191505060405180910390f35b34610000576106d0611e83565b6040518082815260200191505060405180910390f35b3461000057610705600480803560001916906020019091905050611e89565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610766600480803560001916906020019091905050611ed2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576107d9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611f1b565b6040518080602001828103825283818151815260200191508051906020019080838360008314610828575b80518252602083111561082857602082019150602081019050602083039250610804565b505050905090810190601f1680156108545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576108cd60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001909190505061200c565b604051808215151515815260200191505060405180910390f35b346100005761095b600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612236565b604051808215151515815260200191505060405180910390f35b3461000057610982612425565b005b3461000057610a0560048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612698565b604051808215151515815260200191505060405180910390f35b60006000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290049050141590505b919050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610add57610b9f565b8073ffffffffffffffffffffffffffffffffffffffff16600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236460405180905060405180910390a380600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5b50565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610c1d57610dc1565b82600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b60208310610c705780518252602082019150602081019050602083039250610c4d565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b60208310610cdf5780518252602082019150602081019050602083039250610cbc565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314610d82575b805182526020831115610d8257602082019150602081019050602083039250610d5e565b505050905090810190601f168015610dae5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836040518082805190602001908083835b60208310610e1b5780518252602082019150602081019050602083039250610df8565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390206000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610ea45761102f565b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610f2d57805160ff1916838001178555610f5b565b82800160010185558215610f5b579182015b82811115610f5a578251825591602001919060010190610f3f565b5b509050610f8091905b80821115610f7c576000816000905550600101610f64565b5090565b50503373ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b60208310610fcd5780518252602082019150602081019050602083039250610faa565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600191505b5b50919050565b600081600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561109b57611159565b6003543410156110aa57611158565b3360016000856000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503373ffffffffffffffffffffffffffffffffffffffff1683600019167f4963513eca575aba66fdcd25f267aae85958fe6fb97e75fa25d783f1a091a22160405180905060405180910390a3600191505b5b5b50919050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156111da57611372565b6002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061127c57506112b3565b601f0160209004906000526020600020908101906112b291905b808211156112ae576000816000905550600101611296565b5090565b5b5060016000846000191660001916815260200190815260200160002060006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550503373ffffffffffffffffffffffffffffffffffffffff1683600019167fef1961b4d2909dc23643b309bfe5c3e5646842d98c3a58517037ef3871185af360405180905060405180910390a3600191505b5b50919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106113cc57805182526020820191506020810190506020830392506113a9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561146b576114af565b816003819055507f6bbc57480a46553fa4d156ce702beef5f3ad66303b0ed1a5d4cb44966c6584c3826040518082815260200191505060405180910390a1600190505b5b919050565b6000823373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561152f576115f4565b8260016000866000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1685600019167f7b97c62130aa09acbbcbf7482630e756592496f1759eaf702f469cf64dfb779460405180905060405180910390a4600191505b5b5092915050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106116755780518252602082019150602081019050602083039250611652565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390205490505b92915050565b6000600060016000846000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561177357611821565b7fdef931299fe61d176f949118058530c1f3f539dcb6950b4e372c9b835c33ca073073ffffffffffffffffffffffffffffffffffffffff16316040518082815260200191505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051809050600060405180830381858888f19350505050151561181b57610000565b600190505b5b90565b60006000836040518082805190602001908083835b6020831061185c5780518252602082019150602081019050602083039250611839565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390203373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561190157611d83565b846040518082805190602001908083835b602083106119355780518252602082019150602081019050602083039250611912565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390209150600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015611ab4575081600019166002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518082805460018160011615610100020316600290048015611aa15780601f10611a7f576101008083540402835291820191611aa1565b820191906000526020600020905b815481529060010190602001808311611a8d575b5050915050604051809103902060001916145b15611c79576002600060016000856000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f10611b5b5750611b92565b601f016020900490600052602060002090810190611b9191905b80821115611b8d576000816000905550600101611b75565b5090565b5b5060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611c1c5780518252602082019150602081019050602083039250611bf9565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a35b8360016000846000191660001916815260200190815260200160002060010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508373ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611d215780518252602082019150602081019050602083039250611cfe565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f728435a0031f6a04538fcdd24922a7e06bc7bc945db03e83d22122d1bc5f28df60405180905060405180910390a3600192505b5b505092915050565b6000600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b60208310611e425780518252602082019150602081019050602083039250611e1f565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b60035481565b600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b600060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b6020604051908101604052806000815250600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611fff5780601f10611fd457610100808354040283529160200191611fff565b820191906000526020600020905b815481529060010190602001808311611fe257829003601f168201915b505050505090505b919050565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156120875761222e565b82600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b602083106120dd57805182526020820191506020810190506020830392506120ba565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b6020831061214c5780518252602082019150602081019050602083039250612129565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea8660405180806020018281038252838181518152602001915080519060200190808383600083146121ef575b8051825260208311156121ef576020820191506020810190506020830392506121cb565b505050905090810190601f16801561221b5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156122945761241f565b82600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061231d57805160ff191683800117855561234b565b8280016001018555821561234b579182015b8281111561234a57825182559160200191906001019061232f565b5b50905061237091905b8082111561236c576000816000905550600101612354565b5090565b50508173ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b602083106123bd578051825260208201915060208101905060208303925061239a565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600190505b5b92915050565b3373ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156124d65780601f106124b45761010080835404028352918201916124d6565b820191906000526020600020905b8154815290600101906020018083116124c2575b505091505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a360016000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156125b05780601f1061258e5761010080835404028352918201916125b0565b820191906000526020600020905b81548152906001019060200180831161259c575b505091505060405180910390206000191660001916815260200190815260200160002060010160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061265d5750612694565b601f01602090049060005260206000209081019061269391905b8082111561268f576000816000905550600101612677565b5090565b5b505b565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515612713576128d0565b8273ffffffffffffffffffffffffffffffffffffffff16600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b6020831061277f578051825260208201915060208101905060208303925061275c565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b602083106127ee57805182526020820191506020810190506020830392506127cb565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314612891575b8051825260208311156128915760208201915060208101905060208303925061286d565b505050905090810190601f1680156128bd5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b5093925050505600a165627a7a7230582066b2da4773a0f1d81efe071c66b51c46868a871661efd18c0f629353ff4c1f9b0029" }, "aaB52d66283F7A1D5978bcFcB55721ACB467384b": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }, - "bb140FbA6242a1c3887A7823F7750a73101383e3": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } + "bb140FbA6242a1c3887A7823F7750a73101383e3": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }, + "cc6A89217193773Bf97DA4c32C23C33e944B12C8": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } } } From 164295fb454e053a97fab98b1063e727f7cc63b6 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 3 Sep 2018 08:56:09 -0300 Subject: [PATCH 07/24] Improve error checks in processSignatureRequests --- src/events/processSignatureRequests.js | 58 ++++++++++++++++++++------ src/utils/errors.js | 2 + 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 9a074ad..253e737 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -6,21 +6,30 @@ const bridgeValidatorsABI = require('../../abis/BridgeValidators.abi') const logger = require('../services/logger') const rpcUrlsManager = require('../services/getRpcUrlsManager') const { createMessage } = require('../utils/message') -const { AlreadyProcessedError, InvalidValidatorError } = require('../utils/errors') +const { + AlreadyProcessedError, + AlreadySignedError, + InvalidValidatorError +} = require('../utils/errors') const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const { HttpListProviderError } = HttpListProvider -async function estimateGas(web3, bridgeContract, address, method, options) { +async function estimateGas(web3, bridgeContract, signature, message, address) { try { - const gasEstimate = await method.estimateGas(options) + const gasEstimate = await bridgeContract.methods + .submitSignature(signature, message) + .estimateGas({ + from: address + }) return gasEstimate } catch (e) { if (e instanceof HttpListProviderError) { throw e } + // Check if address is validator const validatorContractAddress = await bridgeContract.methods.validatorContract().call() const validatorContract = new web3.eth.Contract(bridgeValidatorsABI, validatorContractAddress) @@ -30,7 +39,26 @@ async function estimateGas(web3, bridgeContract, address, method, options) { throw new InvalidValidatorError(`${address} is not a validator`) } - throw new AlreadyProcessedError(e.message) + // Check if transaction was already signed by this validator + const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) + const alreadySigned = await bridgeContract.methods.messagesSigned(validatorMessageHash).call() + + if (alreadySigned) { + throw new AlreadySignedError(e.message) + } + + // Check if minimum number of validations was already reached + const messageHash = web3.utils.soliditySha3(message) + const numMessagesSigned = await bridgeContract.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await bridgeContract.methods + .isAlreadyProcessed(numMessagesSigned) + .call() + + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) + } + + throw new Error('Unknown error while processing message') } } @@ -74,30 +102,36 @@ function processSignatureRequestsBuilder(config) { gasEstimate = await estimateGas( web3Home, homeBridge, - VALIDATOR_ADDRESS, - homeBridge.methods.submitSignature(signature.signature, message), - { - from: VALIDATOR_ADDRESS - } + signature.signature, + message, + VALIDATOR_ADDRESS ) logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { if (e instanceof HttpListProviderError) { throw new Error( - `RPC Connection Error: submitSignature Gas Estimate cannot be obtained.` + 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' ) } else if (e instanceof InvalidValidatorError) { logger.warn({ address: VALIDATOR_ADDRESS }, 'Invalid validator') throw new Error('Current address does not correspond to a validator') + } else if (e instanceof AlreadySignedError) { + logger.info( + { eventTransactionHash: signatureRequest.transactionHash }, + `Already signed signatureRequest ${signatureRequest.transactionHash}` + ) + return } else if (e instanceof AlreadyProcessedError) { logger.info( { eventTransactionHash: signatureRequest.transactionHash }, - `Already processed signatureRequest ${signatureRequest.transactionHash}` + `signatureRequest ${ + signatureRequest.transactionHash + } was already processed by other validators` ) return } else { logger.error(e, 'Unknown error while processing transaction') - return + throw e } } diff --git a/src/utils/errors.js b/src/utils/errors.js index 24252b1..a9aa54a 100644 --- a/src/utils/errors.js +++ b/src/utils/errors.js @@ -1,7 +1,9 @@ class AlreadyProcessedError extends Error {} +class AlreadySignedError extends Error {} class InvalidValidatorError extends Error {} module.exports = { AlreadyProcessedError, + AlreadySignedError, InvalidValidatorError } From e9d9378979b97a5afb86307413df7ea77edc1fa3 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 3 Sep 2018 12:49:01 -0300 Subject: [PATCH 08/24] Receive validator contract as argument in gasEstimate --- src/events/processSignatureRequests.js | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests.js index 253e737..25ec833 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests.js @@ -16,13 +16,11 @@ const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const { HttpListProviderError } = HttpListProvider -async function estimateGas(web3, bridgeContract, signature, message, address) { +async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) { try { - const gasEstimate = await bridgeContract.methods - .submitSignature(signature, message) - .estimateGas({ - from: address - }) + const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({ + from: address + }) return gasEstimate } catch (e) { if (e instanceof HttpListProviderError) { @@ -30,9 +28,6 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { } // Check if address is validator - const validatorContractAddress = await bridgeContract.methods.validatorContract().call() - const validatorContract = new web3.eth.Contract(bridgeValidatorsABI, validatorContractAddress) - const isValidator = await validatorContract.methods.isValidator(address).call() if (!isValidator) { @@ -41,7 +36,7 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { // Check if transaction was already signed by this validator const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) - const alreadySigned = await bridgeContract.methods.messagesSigned(validatorMessageHash).call() + const alreadySigned = await homeBridge.methods.messagesSigned(validatorMessageHash).call() if (alreadySigned) { throw new AlreadySignedError(e.message) @@ -49,10 +44,8 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { // Check if minimum number of validations was already reached const messageHash = web3.utils.soliditySha3(message) - const numMessagesSigned = await bridgeContract.methods.numMessagesSigned(messageHash).call() - const alreadyProcessed = await bridgeContract.methods - .isAlreadyProcessed(numMessagesSigned) - .call() + const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() if (alreadyProcessed) { throw new AlreadyProcessedError(e.message) @@ -65,6 +58,7 @@ async function estimateGas(web3, bridgeContract, signature, message, address) { const limit = promiseLimit(MAX_CONCURRENT_EVENTS) let expectedMessageLength = null +let validatorContract = null function processSignatureRequestsBuilder(config) { const homeProvider = new HttpListProvider(rpcUrlsManager.homeUrls) @@ -78,6 +72,11 @@ function processSignatureRequestsBuilder(config) { expectedMessageLength = await homeBridge.methods.requiredMessageLength().call() } + if (validatorContract === null) { + const validatorContractAddress = await homeBridge.methods.validatorContract().call() + validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + } + const callbacks = signatureRequests.map(signatureRequest => limit(async () => { const { recipient, value } = signatureRequest.returnValues @@ -99,13 +98,14 @@ function processSignatureRequestsBuilder(config) { let gasEstimate try { - gasEstimate = await estimateGas( - web3Home, + gasEstimate = await estimateGas({ + web3: web3Home, homeBridge, - signature.signature, + validatorContract, + signature: signature.signature, message, - VALIDATOR_ADDRESS - ) + address: VALIDATOR_ADDRESS + }) logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { if (e instanceof HttpListProviderError) { From e715fd8a88a46d8da0bc9ac2e5d6551adbc5613e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 5 Sep 2018 12:53:00 -0300 Subject: [PATCH 09/24] Split signature requests watcher in two files --- .../processSignatureRequests/estimateGas.js | 47 +++++++++++++++++ .../index.js} | 52 +++---------------- 2 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 src/events/processSignatureRequests/estimateGas.js rename src/events/{processSignatureRequests.js => processSignatureRequests/index.js} (68%) diff --git a/src/events/processSignatureRequests/estimateGas.js b/src/events/processSignatureRequests/estimateGas.js new file mode 100644 index 0000000..d1f85bc --- /dev/null +++ b/src/events/processSignatureRequests/estimateGas.js @@ -0,0 +1,47 @@ +const { HttpListProviderError } = require('http-list-provider') +const { + AlreadyProcessedError, + AlreadySignedError, + InvalidValidatorError +} = require('../../utils/errors') + +async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) { + try { + const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({ + from: address + }) + return gasEstimate + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + // Check if address is validator + const isValidator = await validatorContract.methods.isValidator(address).call() + + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) + } + + // Check if transaction was already signed by this validator + const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) + const alreadySigned = await homeBridge.methods.messagesSigned(validatorMessageHash).call() + + if (alreadySigned) { + throw new AlreadySignedError(e.message) + } + + // Check if minimum number of validations was already reached + const messageHash = web3.utils.soliditySha3(message) + const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() + + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) + } + + throw new Error('Unknown error while processing message') + } +} + +module.exports = estimateGas diff --git a/src/events/processSignatureRequests.js b/src/events/processSignatureRequests/index.js similarity index 68% rename from src/events/processSignatureRequests.js rename to src/events/processSignatureRequests/index.js index fa61c34..17a9513 100644 --- a/src/events/processSignatureRequests.js +++ b/src/events/processSignatureRequests/index.js @@ -1,58 +1,20 @@ require('dotenv').config() const promiseLimit = require('promise-limit') const { HttpListProviderError } = require('http-list-provider') -const bridgeValidatorsABI = require('../../abis/BridgeValidators.abi') -const logger = require('../services/logger') -const { web3Home } = require('../services/web3') -const { createMessage } = require('../utils/message') +const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi') +const logger = require('../../services/logger') +const { web3Home } = require('../../services/web3') +const { createMessage } = require('../../utils/message') +const estimateGas = require('./estimateGas') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError -} = require('../utils/errors') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') +} = require('../../utils/errors') +const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env -async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) { - try { - const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({ - from: address - }) - return gasEstimate - } catch (e) { - if (e instanceof HttpListProviderError) { - throw e - } - - // Check if address is validator - const isValidator = await validatorContract.methods.isValidator(address).call() - - if (!isValidator) { - throw new InvalidValidatorError(`${address} is not a validator`) - } - - // Check if transaction was already signed by this validator - const validatorMessageHash = web3.utils.soliditySha3(address, web3.utils.soliditySha3(message)) - const alreadySigned = await homeBridge.methods.messagesSigned(validatorMessageHash).call() - - if (alreadySigned) { - throw new AlreadySignedError(e.message) - } - - // Check if minimum number of validations was already reached - const messageHash = web3.utils.soliditySha3(message) - const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() - const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() - - if (alreadyProcessed) { - throw new AlreadyProcessedError(e.message) - } - - throw new Error('Unknown error while processing message') - } -} - const limit = promiseLimit(MAX_CONCURRENT_EVENTS) let expectedMessageLength = null From 1f7cca6caebe9a7105081b1bb1d4cd925cdaea84 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 5 Sep 2018 16:49:40 -0300 Subject: [PATCH 10/24] Change order of checks when processSignatureRequests fails --- .../processSignatureRequests/estimateGas.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/events/processSignatureRequests/estimateGas.js b/src/events/processSignatureRequests/estimateGas.js index d1f85bc..666f43f 100644 --- a/src/events/processSignatureRequests/estimateGas.js +++ b/src/events/processSignatureRequests/estimateGas.js @@ -16,11 +16,13 @@ async function estimateGas({ web3, homeBridge, validatorContract, signature, mes throw e } - // Check if address is validator - const isValidator = await validatorContract.methods.isValidator(address).call() + // Check if minimum number of validations was already reached + const messageHash = web3.utils.soliditySha3(message) + const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() - if (!isValidator) { - throw new InvalidValidatorError(`${address} is not a validator`) + if (alreadyProcessed) { + throw new AlreadyProcessedError(e.message) } // Check if transaction was already signed by this validator @@ -31,13 +33,11 @@ async function estimateGas({ web3, homeBridge, validatorContract, signature, mes throw new AlreadySignedError(e.message) } - // Check if minimum number of validations was already reached - const messageHash = web3.utils.soliditySha3(message) - const numMessagesSigned = await homeBridge.methods.numMessagesSigned(messageHash).call() - const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() + // Check if address is validator + const isValidator = await validatorContract.methods.isValidator(address).call() - if (alreadyProcessed) { - throw new AlreadyProcessedError(e.message) + if (!isValidator) { + throw new InvalidValidatorError(`${address} is not a validator`) } throw new Error('Unknown error while processing message') From e50cbde9203ea08bf34bcb79f600c217e94ab922 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 6 Sep 2018 15:42:04 -0300 Subject: [PATCH 11/24] Add tests for estimateGas of processSignatureRequests --- test/processSignatureRequests.test.js | 142 ++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 test/processSignatureRequests.test.js diff --git a/test/processSignatureRequests.test.js b/test/processSignatureRequests.test.js new file mode 100644 index 0000000..ae5eee1 --- /dev/null +++ b/test/processSignatureRequests.test.js @@ -0,0 +1,142 @@ +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +const sinon = require('sinon') +const Web3 = require('web3') +const { HttpListProviderError } = require('http-list-provider') +const estimateGas = require('../src/events/processSignatureRequests/estimateGas') +const errors = require('../src/utils/errors') + +chai.use(chaiAsPromised) +const { expect } = chai + +const web3 = new Web3() + +describe.only('processSignatureRequests', () => { + describe('estimateGas', () => { + const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' + + it('should return the gas estimate', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.withArgs(sinon.match({ from: address })).resolves(1000) + estimateGasStub.rejects() + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const gasEstimate = await estimateGas({ web3, homeBridge, address }) + + // then + expect(gasEstimate).to.equal(1000) + }) + + it('should rethrow the error if it was a HttpListProviderError', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new HttpListProviderError('connection error')) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address }) + + // then + await expect(result).to.be.rejectedWith(HttpListProviderError) + }) + + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + }) + + it('should throw an AlreadySignedError if the transaction was not processed but signed by this validator', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + messagesSigned: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadySignedError) + }) + + it('should throw an InvalidValidatorError if the transaction was not processed nor signed, by the validator is invalid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + messagesSigned: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(false) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, validatorContract, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + }) + + it('should throw an Error if the validator is valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + submitSignature: () => ({ estimateGas: estimateGasStub }), + numMessagesSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + messagesSigned: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, validatorContract, address, message: 'foo' }) + + // then + await expect(result).to.be.rejectedWith(Error, 'Unknown error while processing message') + }) + }) +}) From a77fa2ad6141efc86e678671e77e329513e77dca Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 6 Sep 2018 15:59:46 -0300 Subject: [PATCH 12/24] Exit when the current address is not a valid validator --- scripts/start-worker.sh | 2 ++ src/events/processSignatureRequests/index.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/start-worker.sh b/scripts/start-worker.sh index 6d0641b..d0c7dec 100755 --- a/scripts/start-worker.sh +++ b/scripts/start-worker.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -o pipefail + WORKERS_DIR="src/" LOGS_DIR="logs/" diff --git a/src/events/processSignatureRequests/index.js b/src/events/processSignatureRequests/index.js index 17a9513..290a51b 100644 --- a/src/events/processSignatureRequests/index.js +++ b/src/events/processSignatureRequests/index.js @@ -71,8 +71,8 @@ function processSignatureRequestsBuilder(config) { 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' ) } else if (e instanceof InvalidValidatorError) { - logger.warn({ address: VALIDATOR_ADDRESS }, 'Invalid validator') - throw new Error('Current address does not correspond to a validator') + logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + process.exit(10) } else if (e instanceof AlreadySignedError) { logger.info( { eventTransactionHash: signatureRequest.transactionHash }, From ac864522b13290d655628136bcf49e7c8f2f348e Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 6 Sep 2018 16:54:28 -0300 Subject: [PATCH 13/24] Remove .only in test --- test/processSignatureRequests.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/processSignatureRequests.test.js b/test/processSignatureRequests.test.js index ae5eee1..6393396 100644 --- a/test/processSignatureRequests.test.js +++ b/test/processSignatureRequests.test.js @@ -11,7 +11,7 @@ const { expect } = chai const web3 = new Web3() -describe.only('processSignatureRequests', () => { +describe('processSignatureRequests', () => { describe('estimateGas', () => { const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' From d2d900a13101592cdea5a9b97cb4d81967514b1b Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 10 Sep 2018 17:24:24 -0300 Subject: [PATCH 14/24] Improve error checks for CollectedSignatures --- .../processCollectedSignatures/estimateGas.js | 51 ++++++++++++++++ .../index.js} | 60 ++++++++++++++----- src/utils/errors.js | 2 + src/utils/message.js | 17 ++++++ 4 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 src/events/processCollectedSignatures/estimateGas.js rename src/events/{processCollectedSignatures.js => processCollectedSignatures/index.js} (55%) diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js new file mode 100644 index 0000000..08fb458 --- /dev/null +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -0,0 +1,51 @@ +const { HttpListProviderError } = require('http-list-provider') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') +const { parseMessage } = require('../../utils/message') + +async function estimateGas({ + foreignBridge, + validatorContract, + message, + numberOfCollectedSignatures, + v, + r, + s, + address +}) { + try { + const gasEstimate = await foreignBridge.methods + .executeSignatures(v, r, s, message) + .estimateGas() + return gasEstimate + } catch (e) { + if (e instanceof HttpListProviderError) { + throw e + } + + const requiredSignatures = await foreignBridge.methods.requiredSignatures().call() + if (requiredSignatures.toString() !== numberOfCollectedSignatures.toString()) { + throw new IncompatibleContractError('The number of collected signatures does not match') + } + + const { txHash } = parseMessage(message) + const alreadyProcessed = await foreignBridge.methods.relayedMessages(txHash).call() + if (alreadyProcessed) { + throw new AlreadyProcessedError() + } + + // 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 diff --git a/src/events/processCollectedSignatures.js b/src/events/processCollectedSignatures/index.js similarity index 55% rename from src/events/processCollectedSignatures.js rename to src/events/processCollectedSignatures/index.js index 96e52ba..9407465 100644 --- a/src/events/processCollectedSignatures.js +++ b/src/events/processCollectedSignatures/index.js @@ -1,14 +1,24 @@ require('dotenv').config() const promiseLimit = require('promise-limit') -const logger = require('../services/logger') -const { web3Home, web3Foreign } = require('../services/web3') -const { signatureToVRS } = require('../utils/message') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') +const { HttpListProviderError } = require('http-list-provider') +const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi') +const logger = require('../../services/logger') +const { web3Home, web3Foreign } = require('../../services/web3') +const { signatureToVRS } = require('../../utils/message') +const estimateGas = require('./estimateGas') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') +const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS } = process.env const limit = promiseLimit(MAX_CONCURRENT_EVENTS) +let validatorContract = null + function processCollectedSignaturesBuilder(config) { const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) @@ -20,6 +30,11 @@ function processCollectedSignaturesBuilder(config) { return async function processCollectedSignatures(signatures) { const txToSend = [] + if (validatorContract === null) { + const validatorContractAddress = await foreignBridge.methods.validatorContract().call() + validatorContract = new web3Foreign.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + } + const callbacks = signatures.map(colSignature => limit(async () => { const { @@ -52,20 +67,37 @@ function processCollectedSignaturesBuilder(config) { let gasEstimate try { - gasEstimate = await foreignBridge.methods - .executeSignatures(v, r, s, message) - .estimateGas() + gasEstimate = await estimateGas({ + foreignBridge, + validatorContract, + v, + r, + s, + message, + numberOfCollectedSignatures: NumberOfCollectedSignatures, + address: VALIDATOR_ADDRESS + }) } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { + if (e instanceof HttpListProviderError) { throw new Error( - `RPC Connection Error: executeSignatures Gas Estimate cannot be obtained.` + 'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.' + ) + } else if (e instanceof AlreadyProcessedError) { + logger.info( + { eventTransactionHash: colSignature.transactionHash }, + `Already processed CollectedSignatures ${colSignature.transactionHash}` ) + return + } else if (e instanceof IncompatibleContractError) { + logger.fatal(`The contract is not compatible: ${e.message}`) + process.exit(10) + } else if (e instanceof InvalidValidatorError) { + logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') + process.exit(10) + } else { + logger.error(e, 'Unknown error while processing transaction') + throw e } - logger.info( - { eventTransactionHash: colSignature.transactionHash }, - `Already processed CollectedSignatures ${colSignature.transactionHash}` - ) - return } const data = await foreignBridge.methods.executeSignatures(v, r, s, message).encodeABI() txToSend.push({ diff --git a/src/utils/errors.js b/src/utils/errors.js index a9aa54a..4ad1545 100644 --- a/src/utils/errors.js +++ b/src/utils/errors.js @@ -1,9 +1,11 @@ class AlreadyProcessedError extends Error {} class AlreadySignedError extends Error {} +class IncompatibleContractError extends Error {} class InvalidValidatorError extends Error {} module.exports = { AlreadyProcessedError, AlreadySignedError, + IncompatibleContractError, InvalidValidatorError } diff --git a/src/utils/message.js b/src/utils/message.js index 24be74c..d66d043 100644 --- a/src/utils/message.js +++ b/src/utils/message.js @@ -33,6 +33,22 @@ function createMessage({ return message } +function parseMessage(message) { + message = strip0x(message) + + const recipient = '0x' + message.slice(0, 40) + const amount = '0x' + message.slice(40, 40 + 32 * 2) + const txHash = '0x' + message.slice(40 + 32 * 2, 40 + 32 * 2 + 32 * 2) + const contractAddress = '0x' + message.slice(40 + 32 * 2 + 32 * 2, message.length) + + return { + recipient, + amount, + txHash, + contractAddress + } +} + function signatureToVRS(signature) { assert.equal(signature.length, 2 + 32 * 2 + 32 * 2 + 2) signature = strip0x(signature) @@ -44,5 +60,6 @@ function signatureToVRS(signature) { module.exports = { createMessage, + parseMessage, signatureToVRS } From 34990bfe9b614e8c49ab096beacd1184b0f327ee Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 11 Sep 2018 12:39:56 -0300 Subject: [PATCH 15/24] Add test for parseMessage and refactor --- package-lock.json | 6 ++++ package.json | 1 + .../processCollectedSignatures/index.js | 5 ++- src/utils/message.js | 22 +++++++++--- test/message.test.js | 35 ++++++++++++++++--- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f483de5..a7109b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -429,6 +429,12 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, + "bn-chai": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bn-chai/-/bn-chai-1.0.1.tgz", + "integrity": "sha512-7rJXt21DwYiLLpvzLaACixBBoUGkRV1iuFD3wElEhw8Ji9IiY/QsJRtvW+c7ChRgEOyLQkGaSGFUUqBKm21SNA==", + "dev": true + }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", diff --git a/package.json b/package.json index 6812ca9..904c32c 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,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", diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index 9407465..cedcd3c 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -32,7 +32,10 @@ function processCollectedSignaturesBuilder(config) { if (validatorContract === null) { const validatorContractAddress = await foreignBridge.methods.validatorContract().call() - validatorContract = new web3Foreign.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + validatorContract = new web3Foreign.eth.Contract( + bridgeValidatorsABI, + validatorContractAddress + ) } const callbacks = signatures.map(colSignature => diff --git a/src/utils/message.js b/src/utils/message.js index d66d043..2b0dbdd 100644 --- a/src/utils/message.js +++ b/src/utils/message.js @@ -36,10 +36,24 @@ function createMessage({ function parseMessage(message) { message = strip0x(message) - const recipient = '0x' + message.slice(0, 40) - const amount = '0x' + message.slice(40, 40 + 32 * 2) - const txHash = '0x' + message.slice(40 + 32 * 2, 40 + 32 * 2 + 32 * 2) - const contractAddress = '0x' + message.slice(40 + 32 * 2 + 32 * 2, message.length) + const recipientStart = 0 + const recipientLength = 40 + const recipient = `0x${message.slice(recipientStart, recipientStart + recipientLength)}` + + const amountStart = recipientStart + recipientLength + const amountLength = 32 * 2 + const amount = `0x${message.slice(amountStart, amountStart + amountLength)}` + + const txHashStart = amountStart + amountLength + const txHashLength = 32 * 2 + const txHash = `0x${message.slice(txHashStart, txHashStart + txHashLength)}` + + const contractAddressStart = txHashStart + txHashLength + const contractAddressLength = 32 * 2 + const contractAddress = `0x${message.slice( + contractAddressStart, + contractAddressStart + contractAddressLength + )}` return { recipient, diff --git a/test/message.test.js b/test/message.test.js index 7640824..1953b81 100644 --- a/test/message.test.js +++ b/test/message.test.js @@ -1,10 +1,11 @@ -const { expect } = require('chai') -const { createMessage, signatureToVRS } = require('../src/utils/message') +const { BN, toBN } = require('web3').utils +const { expect } = require('chai').use(require('bn-chai')(BN)) +const { createMessage, parseMessage, signatureToVRS } = require('../src/utils/message') describe('message utils', () => { - describe('createMessage', () => { - const expectedMessageLength = 104 + const expectedMessageLength = 104 + describe('createMessage', () => { it('should create a message when receiving valid values', () => { // given const recipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' @@ -226,6 +227,32 @@ describe('message utils', () => { expect(messageThunk).to.throw() }) }) + describe('parseMessage', () => { + it('should return the same values that were used to create the message', () => { + // given + const originalRecipient = '0xe3D952Ad4B96A756D65790393128FA359a7CD888' + const originalValue = '0x2a' + const originalTransactionHash = + '0x4a298455c1ccb17de77718fc045a876e1b4e063afaad361dcdef142a8ee48d5a' + const originalBridgeAddress = '0xfA79875FB0828c1FBD438583ED23fF5a956D80a1' + + // when + const message = createMessage({ + recipient: originalRecipient, + value: originalValue, + transactionHash: originalTransactionHash, + bridgeAddress: originalBridgeAddress, + expectedMessageLength + }) + const { recipient, amount, txHash, contractAddress } = parseMessage(message) + + // then + expect(recipient).to.equal(originalRecipient) + expect(toBN(amount)).to.eq.BN(toBN(originalValue)) + expect(txHash).to.equal(originalTransactionHash) + expect(contractAddress).to.equal(originalBridgeAddress) + }) + }) describe('signatureToVRS', () => { it('should return the v, r, s values', () => { // given From d39c67c202c4ff94d46b087b3ab1fd356e5734bb Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 11 Sep 2018 13:40:06 -0300 Subject: [PATCH 16/24] Add tests for processCollectedSignatures --- test/processCollectedSignatures.test.js | 161 ++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 test/processCollectedSignatures.test.js diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js new file mode 100644 index 0000000..1a4e904 --- /dev/null +++ b/test/processCollectedSignatures.test.js @@ -0,0 +1,161 @@ +const { expect } = require('chai').use(require('chai-as-promised')) +const sinon = require('sinon') +const Web3 = require('web3') +const { HttpListProviderError } = require('http-list-provider') +const { createMessage } = require('../src/utils/message') +const estimateGas = require('../src/events/processCollectedSignatures/estimateGas') +const errors = require('../src/utils/errors') + +const web3 = new Web3() + +const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' + +describe('processCollectedSignatures', () => { + describe('estimateGas', () => { + it('should return the gas estimate', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.resolves(1000) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const gasEstimate = await estimateGas({ web3, foreignBridge }) + + // then + expect(gasEstimate).to.equal(1000) + }) + + it('should rethrow the error if it was a HttpListProviderError', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new HttpListProviderError('connection error')) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const result = estimateGas({ web3, foreignBridge }) + + // then + await expect(result).to.be.rejectedWith(HttpListProviderError) + }) + + it("should throw an IncompatibleContractError if the number of signatures doesn't match", async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(2) }) + } + } + + // when + const result = estimateGas({ web3, foreignBridge, numberOfCollectedSignatures: 1 }) + + // then + await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) + }) + + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), + relayedMessages: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ + web3, + foreignBridge, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + }) + + it('should throw an InvalidValidatorError if the validator is not valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), + relayedMessages: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(false) }) + } + } + + // when + const result = estimateGas({ + web3, + foreignBridge, + validatorContract, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) + + // then + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + }) + + it('should throw an Error if the validator is valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const foreignBridge = { + methods: { + executeSignatures: () => ({ estimateGas: estimateGasStub }), + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), + relayedMessages: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ + web3, + foreignBridge, + validatorContract, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) + + // then + await expect(result).to.be.rejectedWith(Error, 'Unknown error while processing message') + }) + }) +}) + +function randomMessage() { + return createMessage({ + recipient: address, + value: 42, + transactionHash: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + bridgeAddress: '0x6E4C9178ADc17A0D9b933C0135051536941F5769', + expectedMessageLength: 104 + }) +} From 875d1bf66ded35ab417e260ab326e87128bc2e57 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:37:08 -0300 Subject: [PATCH 17/24] Improve error checks for AffirmationRequests --- src/events/processAffirmationRequests.js | 62 ------------ .../processAffirmationRequests/estimateGas.js | 59 +++++++++++ .../processAffirmationRequests/index.js | 98 +++++++++++++++++++ 3 files changed, 157 insertions(+), 62 deletions(-) delete mode 100644 src/events/processAffirmationRequests.js create mode 100644 src/events/processAffirmationRequests/estimateGas.js create mode 100644 src/events/processAffirmationRequests/index.js diff --git a/src/events/processAffirmationRequests.js b/src/events/processAffirmationRequests.js deleted file mode 100644 index f907bc5..0000000 --- a/src/events/processAffirmationRequests.js +++ /dev/null @@ -1,62 +0,0 @@ -require('dotenv').config() -const logger = require('../services/logger') -const { web3Home } = require('../services/web3') -const promiseLimit = require('promise-limit') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') - -const { VALIDATOR_ADDRESS } = process.env - -const limit = promiseLimit(MAX_CONCURRENT_EVENTS) - -function processAffirmationRequestsBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - - return async function processAffirmationRequests(affirmationRequests) { - const txToSend = [] - - 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 homeBridge.methods - .executeAffirmation(recipient, value, affirmationRequest.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - if (e.message.includes('Invalid JSON RPC response')) { - throw new Error( - `RPC Connection Error: executeAffirmation Gas Estimate cannot be obtained.` - ) - } - logger.info( - { eventTransactionHash: affirmationRequest.transactionHash }, - `Already processed affirmationRequest ${affirmationRequest.transactionHash}` - ) - return - } - - 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 diff --git a/src/events/processAffirmationRequests/estimateGas.js b/src/events/processAffirmationRequests/estimateGas.js new file mode 100644 index 0000000..d7394af --- /dev/null +++ b/src/events/processAffirmationRequests/estimateGas.js @@ -0,0 +1,59 @@ +const { HttpListProviderError } = require('http-list-provider') +const { + InvalidValidatorError, + AlreadyProcessedError, + AlreadySignedError +} = 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 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 minimum number of validations was already reached + const numMessagesSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call() + const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() + + if (alreadyProcessed) { + throw new AlreadyProcessedError(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 diff --git a/src/events/processAffirmationRequests/index.js b/src/events/processAffirmationRequests/index.js new file mode 100644 index 0000000..6092cc8 --- /dev/null +++ b/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 From 833c37bbc7ff277620c6b85cf4a2a845a98227df Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:47:04 -0300 Subject: [PATCH 18/24] Add test:watch npm script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 904c32c..aa9b978 100644 --- a/package.json +++ b/package.json @@ -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" }, From c59cd1d3141a93d61693ea5854f7888a5dfda0a2 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:47:16 -0300 Subject: [PATCH 19/24] Add tests for processAffirmationRequests --- test/processAffirmationRequests.test.js | 162 ++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 test/processAffirmationRequests.test.js diff --git a/test/processAffirmationRequests.test.js b/test/processAffirmationRequests.test.js new file mode 100644 index 0000000..092a2f6 --- /dev/null +++ b/test/processAffirmationRequests.test.js @@ -0,0 +1,162 @@ +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +const sinon = require('sinon') +const Web3 = require('web3') +const { HttpListProviderError } = require('http-list-provider') +const estimateGas = require('../src/events/processAffirmationRequests/estimateGas') +const errors = require('../src/utils/errors') + +chai.use(chaiAsPromised) +const { expect } = chai + +const web3 = new Web3() + +describe('processAffirmationRequests', () => { + describe('estimateGas', () => { + const address = '0x02B18eF56cE0FE39F3bEEc8becA8bF2c78579596' + const recipient = '0xCA7693B1c6778f32Db4550D8f7df3C50792D596F' + const value = '0x2A' + const txHash = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' + + it('should return the gas estimate', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.withArgs(sinon.match({ from: address })).resolves(1000) + estimateGasStub.rejects() + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const gasEstimate = await estimateGas({ web3, homeBridge, address }) + + // then + expect(gasEstimate).to.equal(1000) + }) + + it('should rethrow the error if it was a HttpListProviderError', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new HttpListProviderError('connection error')) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address }) + + // then + await expect(result).to.be.rejectedWith(HttpListProviderError) + }) + + it('should throw an AlreadySignedError if the transaction was already signed by this validator', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + // numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + // isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadySignedError) + }) + + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) + + // then + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + }) + + it('should throw an InvalidValidatorError if the transaction was not processed nor signed, by the validator is invalid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(false) }) + } + } + + // when + const result = estimateGas({ + web3, + homeBridge, + validatorContract, + address, + recipient, + value, + txHash + }) + + // then + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + }) + + it('should throw an Error if the validator is valid', async () => { + // given + const estimateGasStub = sinon.stub() + estimateGasStub.rejects(new Error()) + const homeBridge = { + methods: { + executeAffirmation: () => ({ estimateGas: estimateGasStub }), + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + isValidator: () => ({ call: sinon.stub().resolves(true) }) + } + } + + // when + const result = estimateGas({ + web3, + homeBridge, + validatorContract, + address, + recipient, + value, + txHash + }) + + // then + await expect(result).to.be.rejectedWith(Error, 'Unknown error while processing message') + }) + }) +}) From 89e1abf1bfd1306c65234bd18da60072b5b9b882 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 12 Sep 2018 10:59:59 -0300 Subject: [PATCH 20/24] Check if event is already processed before checking if it's already signed --- .../processAffirmationRequests/estimateGas.js | 18 ++++++++++-------- test/processAffirmationRequests.test.js | 17 ++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/events/processAffirmationRequests/estimateGas.js b/src/events/processAffirmationRequests/estimateGas.js index d7394af..6d8e89c 100644 --- a/src/events/processAffirmationRequests/estimateGas.js +++ b/src/events/processAffirmationRequests/estimateGas.js @@ -30,6 +30,16 @@ async function estimateGas({ 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() @@ -37,14 +47,6 @@ async function estimateGas({ throw new AlreadySignedError(e.message) } - // Check if minimum number of validations was already reached - const numMessagesSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call() - const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numMessagesSigned).call() - - if (alreadyProcessed) { - throw new AlreadyProcessedError(e.message) - } - // Check if address is validator const isValidator = await validatorContract.methods.isValidator(address).call() diff --git a/test/processAffirmationRequests.test.js b/test/processAffirmationRequests.test.js index 092a2f6..deb6681 100644 --- a/test/processAffirmationRequests.test.js +++ b/test/processAffirmationRequests.test.js @@ -53,16 +53,15 @@ describe('processAffirmationRequests', () => { await expect(result).to.be.rejectedWith(HttpListProviderError) }) - it('should throw an AlreadySignedError if the transaction was already signed by this validator', async () => { + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const homeBridge = { methods: { executeAffirmation: () => ({ estimateGas: estimateGasStub }), - // numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), - // isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), - affirmationsSigned: () => ({ call: sinon.stub().resolves(true) }) + numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) } } @@ -70,10 +69,10 @@ describe('processAffirmationRequests', () => { const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) // then - await expect(result).to.be.rejectedWith(errors.AlreadySignedError) + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) }) - it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + it('should throw an AlreadySignedError if the transaction was already signed by this validator', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) @@ -81,8 +80,8 @@ describe('processAffirmationRequests', () => { methods: { executeAffirmation: () => ({ estimateGas: estimateGasStub }), numAffirmationsSigned: () => ({ call: sinon.stub().resolves(1) }), - affirmationsSigned: () => ({ call: sinon.stub().resolves(false) }), - isAlreadyProcessed: () => ({ call: sinon.stub().resolves(true) }) + isAlreadyProcessed: () => ({ call: sinon.stub().resolves(false) }), + affirmationsSigned: () => ({ call: sinon.stub().resolves(true) }) } } @@ -90,7 +89,7 @@ describe('processAffirmationRequests', () => { const result = estimateGas({ web3, homeBridge, address, recipient, value, txHash }) // then - await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + await expect(result).to.be.rejectedWith(errors.AlreadySignedError) }) it('should throw an InvalidValidatorError if the transaction was not processed nor signed, by the validator is invalid', async () => { From c4b2c5aa9a8eb9b4deed9f9491fa01df107a8f6a Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 13 Sep 2018 17:08:30 -0300 Subject: [PATCH 21/24] Improve error checks in processTransfers --- .../processAffirmationRequests/estimateGas.js | 4 +- src/events/processSignatureRequests/index.js | 1 - src/events/processTransfers.js | 48 ---------- src/events/processTransfers/index.js | 91 +++++++++++++++++++ 4 files changed, 93 insertions(+), 51 deletions(-) delete mode 100644 src/events/processTransfers.js create mode 100644 src/events/processTransfers/index.js diff --git a/src/events/processAffirmationRequests/estimateGas.js b/src/events/processAffirmationRequests/estimateGas.js index 6d8e89c..1749ad5 100644 --- a/src/events/processAffirmationRequests/estimateGas.js +++ b/src/events/processAffirmationRequests/estimateGas.js @@ -1,8 +1,8 @@ const { HttpListProviderError } = require('http-list-provider') const { - InvalidValidatorError, AlreadyProcessedError, - AlreadySignedError + AlreadySignedError, + InvalidValidatorError } = require('../../utils/errors') async function estimateGas({ diff --git a/src/events/processSignatureRequests/index.js b/src/events/processSignatureRequests/index.js index 290a51b..93354ca 100644 --- a/src/events/processSignatureRequests/index.js +++ b/src/events/processSignatureRequests/index.js @@ -64,7 +64,6 @@ function processSignatureRequestsBuilder(config) { message, address: VALIDATOR_ADDRESS }) - logger.info(`gasEstimate: ${gasEstimate}`) } catch (e) { if (e instanceof HttpListProviderError) { throw new Error( diff --git a/src/events/processTransfers.js b/src/events/processTransfers.js deleted file mode 100644 index e2db251..0000000 --- a/src/events/processTransfers.js +++ /dev/null @@ -1,48 +0,0 @@ -require('dotenv').config() -const { web3Home } = require('../services/web3') -const promiseLimit = require('promise-limit') -const { MAX_CONCURRENT_EVENTS } = require('../utils/constants') - -const { VALIDATOR_ADDRESS } = process.env - -const limit = promiseLimit(MAX_CONCURRENT_EVENTS) - -function processTransfersBuilder(config) { - const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) - - return async function processTransfers(transfers) { - const txToSend = [] - - const callbacks = transfers.map((transfer, index) => - limit(async () => { - const { from, value } = transfer.returnValues - - let gasEstimate - try { - gasEstimate = await homeBridge.methods - .executeAffirmation(from, value, transfer.transactionHash) - .estimateGas({ from: VALIDATOR_ADDRESS }) - } catch (e) { - console.log(index + 1, '# already processed Transfer', transfer.transactionHash) - return - } - - const data = await homeBridge.methods - .executeAffirmation(from, value, transfer.transactionHash) - .encodeABI({ from: VALIDATOR_ADDRESS }) - - txToSend.push({ - data, - gasEstimate, - transactionReference: transfer.transactionHash, - to: config.homeBridgeAddress - }) - }) - ) - - await Promise.all(callbacks) - return txToSend - } -} - -module.exports = processTransfersBuilder diff --git a/src/events/processTransfers/index.js b/src/events/processTransfers/index.js new file mode 100644 index 0000000..9d935b2 --- /dev/null +++ b/src/events/processTransfers/index.js @@ -0,0 +1,91 @@ +require('dotenv').config() +const promiseLimit = require('promise-limit') +const { HttpListProviderError } = require('http-list-provider') +const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi') +const logger = require('../../services/logger') +const { web3Home } = require('../../services/web3') +const { + AlreadyProcessedError, + AlreadySignedError, + InvalidValidatorError +} = require('../../utils/errors') +const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') +const estimateGas = require('../processAffirmationRequests/estimateGas') + +const { VALIDATOR_ADDRESS } = process.env + +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + +let validatorContract = null + +function processTransfersBuilder(config) { + const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress) + + return async function processTransfers(transfers) { + const txToSend = [] + + if (validatorContract === null) { + const validatorContractAddress = await homeBridge.methods.validatorContract().call() + validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress) + } + + const callbacks = transfers.map(transfer => + limit(async () => { + const { from, value } = transfer.returnValues + + let gasEstimate + try { + gasEstimate = await estimateGas({ + web3: web3Home, + homeBridge, + validatorContract, + recipient: from, + value, + txHash: transfer.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: transfer.transactionHash }, + `Already signed transfer ${transfer.transactionHash}` + ) + return + } else if (e instanceof AlreadyProcessedError) { + logger.info( + { eventTransactionHash: transfer.transactionHash }, + `transfer ${transfer.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(from, value, transfer.transactionHash) + .encodeABI({ from: VALIDATOR_ADDRESS }) + + txToSend.push({ + data, + gasEstimate, + transactionReference: transfer.transactionHash, + to: config.homeBridgeAddress + }) + }) + ) + + await Promise.all(callbacks) + return txToSend + } +} + +module.exports = processTransfersBuilder From c8b3a6722d8b9e10610eb54fd7fc8808210c86fb Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 18 Sep 2018 16:02:47 -0300 Subject: [PATCH 22/24] Apply pull request comments - Don't check if the transaction was sent from a validator - Don't terminate the process if there is a contract incompatibility - Check that all signatures are valid - Allow having more signatures than needed --- .eslintrc | 1 + .../processCollectedSignatures/estimateGas.js | 37 +++++++------ .../processCollectedSignatures/index.js | 19 +++---- test/processCollectedSignatures.test.js | 54 +++++++++++++------ 4 files changed, 68 insertions(+), 43 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4a3330d..3614120 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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" diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js index 08fb458..fc5047a 100644 --- a/src/events/processCollectedSignatures/estimateGas.js +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -1,20 +1,20 @@ +const Web3 = require('web3') const { HttpListProviderError } = require('http-list-provider') -const { - AlreadyProcessedError, - IncompatibleContractError, - InvalidValidatorError -} = require('../../utils/errors') +const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') const { parseMessage } = require('../../utils/message') +const web3 = new Web3() +const { toBN } = Web3.utils + async function estimateGas({ foreignBridge, validatorContract, message, numberOfCollectedSignatures, + signatures, v, r, - s, - address + s }) { try { const gasEstimate = await foreignBridge.methods @@ -26,22 +26,27 @@ async function estimateGas({ throw e } - const requiredSignatures = await foreignBridge.methods.requiredSignatures().call() - if (requiredSignatures.toString() !== numberOfCollectedSignatures.toString()) { - throw new IncompatibleContractError('The number of collected signatures does not match') - } - + // 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 address is validator - const isValidator = await validatorContract.methods.isValidator(address).call() + // 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 (const signature of signatures) { + const address = web3.eth.accounts.recover(message, signature) + const isValidator = await validatorContract.methods.isValidator(address).call() - if (!isValidator) { - throw new InvalidValidatorError(`${address} is not a validator`) + if (!isValidator) { + throw new IncompatibleContractError(`Message signed by ${address} that is not a validator`) + } } throw new Error('Unknown error while processing message') diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index cedcd3c..e24c044 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -6,11 +6,7 @@ const logger = require('../../services/logger') const { web3Home, web3Foreign } = require('../../services/web3') const { signatureToVRS } = require('../../utils/message') const estimateGas = require('./estimateGas') -const { - AlreadyProcessedError, - IncompatibleContractError, - InvalidValidatorError -} = require('../../utils/errors') +const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS } = process.env @@ -57,10 +53,12 @@ function processCollectedSignaturesBuilder(config) { requiredSignatures.length = NumberOfCollectedSignatures requiredSignatures.fill(0) + const signatures = [] const [v, r, s] = [[], [], []] const signaturePromises = requiredSignatures.map(async (el, index) => { const signature = await homeBridge.methods.signature(messageHash, index).call() const recover = signatureToVRS(signature) + signatures.push(signature) v.push(recover.v) r.push(recover.r) s.push(recover.s) @@ -73,12 +71,12 @@ function processCollectedSignaturesBuilder(config) { gasEstimate = await estimateGas({ foreignBridge, validatorContract, + signatures, v, r, s, message, - numberOfCollectedSignatures: NumberOfCollectedSignatures, - address: VALIDATOR_ADDRESS + numberOfCollectedSignatures: NumberOfCollectedSignatures }) } catch (e) { if (e instanceof HttpListProviderError) { @@ -92,11 +90,8 @@ function processCollectedSignaturesBuilder(config) { ) return } else if (e instanceof IncompatibleContractError) { - logger.fatal(`The contract is not compatible: ${e.message}`) - process.exit(10) - } else if (e instanceof InvalidValidatorError) { - logger.fatal({ address: VALIDATOR_ADDRESS }, 'Invalid validator') - process.exit(10) + logger.error(`The contract is not compatible: ${e.message}`) + return } else { logger.error(e, 'Unknown error while processing transaction') throw e diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js index 1a4e904..ed1cae7 100644 --- a/test/processCollectedSignatures.test.js +++ b/test/processCollectedSignatures.test.js @@ -46,33 +46,42 @@ describe('processCollectedSignatures', () => { await expect(result).to.be.rejectedWith(HttpListProviderError) }) - it("should throw an IncompatibleContractError if the number of signatures doesn't match", async () => { + it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(2) }) + relayedMessages: () => ({ call: sinon.stub().resolves(true) }) } } // when - const result = estimateGas({ web3, foreignBridge, numberOfCollectedSignatures: 1 }) + const result = estimateGas({ + web3, + foreignBridge, + numberOfCollectedSignatures: 1, + message: randomMessage() + }) // then - await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) + await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) }) - it('should throw an AlreadyProcessedError if the transaction was already procesed', async () => { + it('should throw an IncompatibleContractError if the number of signatures is less than required', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), - relayedMessages: () => ({ call: sinon.stub().resolves(true) }) + relayedMessages: () => ({ call: sinon.stub().resolves(false) }) + } + } + const validatorContract = { + methods: { + requiredSignatures: () => ({ call: sinon.stub().resolves(2) }) } } @@ -80,68 +89,83 @@ describe('processCollectedSignatures', () => { const result = estimateGas({ web3, foreignBridge, + validatorContract, numberOfCollectedSignatures: 1, message: randomMessage() }) // then - await expect(result).to.be.rejectedWith(errors.AlreadyProcessedError) + await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) }) - it('should throw an InvalidValidatorError if the validator is not valid', async () => { + it('should throw an IncompatibleContractError if the signature is invalid', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), relayedMessages: () => ({ call: sinon.stub().resolves(false) }) } } const validatorContract = { methods: { + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), isValidator: () => ({ call: sinon.stub().resolves(false) }) } } + const message = randomMessage() + const { signature } = web3.eth.accounts.sign( + message, + '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' + ) + // when const result = estimateGas({ web3, foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - message: randomMessage() + signatures: [signature], + message }) // then - await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) + await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) }) - it('should throw an Error if the validator is valid', async () => { + it('should throw an Error if the signature is valid', async () => { // given const estimateGasStub = sinon.stub() estimateGasStub.rejects(new Error()) const foreignBridge = { methods: { executeSignatures: () => ({ estimateGas: estimateGasStub }), - requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), relayedMessages: () => ({ call: sinon.stub().resolves(false) }) } } const validatorContract = { methods: { + requiredSignatures: () => ({ call: sinon.stub().resolves(1) }), isValidator: () => ({ call: sinon.stub().resolves(true) }) } } + const message = randomMessage() + const { signature } = web3.eth.accounts.sign( + message, + '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' + ) + // when const result = estimateGas({ web3, foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - message: randomMessage() + signatures: [signature], + message }) // then From b8098c844ad8ca9476465c7c3c51400ce9e400f7 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 19 Sep 2018 12:41:16 -0300 Subject: [PATCH 23/24] Change how errors are handled in collected signatures watcher --- .../processCollectedSignatures/estimateGas.js | 8 ++++++-- src/events/processCollectedSignatures/index.js | 16 +++++++++++++--- test/processCollectedSignatures.test.js | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js index fc5047a..dadb421 100644 --- a/src/events/processCollectedSignatures/estimateGas.js +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -1,6 +1,10 @@ const Web3 = require('web3') const { HttpListProviderError } = require('http-list-provider') -const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') const { parseMessage } = require('../../utils/message') const web3 = new Web3() @@ -45,7 +49,7 @@ async function estimateGas({ const isValidator = await validatorContract.methods.isValidator(address).call() if (!isValidator) { - throw new IncompatibleContractError(`Message signed by ${address} that is not a validator`) + throw new InvalidValidatorError(`Message signed by ${address} that is not a validator`) } } diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index e24c044..1f2c717 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -6,7 +6,11 @@ const logger = require('../../services/logger') const { web3Home, web3Foreign } = require('../../services/web3') const { signatureToVRS } = require('../../utils/message') const estimateGas = require('./estimateGas') -const { AlreadyProcessedError, IncompatibleContractError } = require('../../utils/errors') +const { + AlreadyProcessedError, + IncompatibleContractError, + InvalidValidatorError +} = require('../../utils/errors') const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants') const { VALIDATOR_ADDRESS } = process.env @@ -89,8 +93,14 @@ function processCollectedSignaturesBuilder(config) { `Already processed CollectedSignatures ${colSignature.transactionHash}` ) return - } else if (e instanceof IncompatibleContractError) { - logger.error(`The contract is not compatible: ${e.message}`) + } else if ( + e instanceof IncompatibleContractError || + e instanceof InvalidValidatorError + ) { + logger.error( + { eventTransactionHash: colSignature.transactionHash }, + `The message couldn't be processed; skipping: ${e.message}` + ) return } else { logger.error(e, 'Unknown error while processing transaction') diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js index ed1cae7..b91124b 100644 --- a/test/processCollectedSignatures.test.js +++ b/test/processCollectedSignatures.test.js @@ -132,7 +132,7 @@ describe('processCollectedSignatures', () => { }) // then - await expect(result).to.be.rejectedWith(errors.IncompatibleContractError) + await expect(result).to.be.rejectedWith(errors.InvalidValidatorError) }) it('should throw an Error if the signature is valid', async () => { From 481d556aed15ad7c94470613ea12c8b7b4155537 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 19 Sep 2018 12:51:12 -0300 Subject: [PATCH 24/24] Use v, r, s instead of signature for recovering address --- src/events/processCollectedSignatures/estimateGas.js | 5 ++--- src/events/processCollectedSignatures/index.js | 3 --- test/processCollectedSignatures.test.js | 12 ++++++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/events/processCollectedSignatures/estimateGas.js b/src/events/processCollectedSignatures/estimateGas.js index dadb421..cd49783 100644 --- a/src/events/processCollectedSignatures/estimateGas.js +++ b/src/events/processCollectedSignatures/estimateGas.js @@ -15,7 +15,6 @@ async function estimateGas({ validatorContract, message, numberOfCollectedSignatures, - signatures, v, r, s @@ -44,8 +43,8 @@ async function estimateGas({ } // check if all the signatures were made by validators - for (const signature of signatures) { - const address = web3.eth.accounts.recover(message, signature) + 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) { diff --git a/src/events/processCollectedSignatures/index.js b/src/events/processCollectedSignatures/index.js index 1f2c717..9d3e318 100644 --- a/src/events/processCollectedSignatures/index.js +++ b/src/events/processCollectedSignatures/index.js @@ -57,12 +57,10 @@ function processCollectedSignaturesBuilder(config) { requiredSignatures.length = NumberOfCollectedSignatures requiredSignatures.fill(0) - const signatures = [] const [v, r, s] = [[], [], []] const signaturePromises = requiredSignatures.map(async (el, index) => { const signature = await homeBridge.methods.signature(messageHash, index).call() const recover = signatureToVRS(signature) - signatures.push(signature) v.push(recover.v) r.push(recover.r) s.push(recover.s) @@ -75,7 +73,6 @@ function processCollectedSignaturesBuilder(config) { gasEstimate = await estimateGas({ foreignBridge, validatorContract, - signatures, v, r, s, diff --git a/test/processCollectedSignatures.test.js b/test/processCollectedSignatures.test.js index b91124b..a319c0d 100644 --- a/test/processCollectedSignatures.test.js +++ b/test/processCollectedSignatures.test.js @@ -116,7 +116,7 @@ describe('processCollectedSignatures', () => { } const message = randomMessage() - const { signature } = web3.eth.accounts.sign( + const { v, r, s } = web3.eth.accounts.sign( message, '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' ) @@ -127,7 +127,9 @@ describe('processCollectedSignatures', () => { foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - signatures: [signature], + v: [v], + r: [r], + s: [s], message }) @@ -153,7 +155,7 @@ describe('processCollectedSignatures', () => { } const message = randomMessage() - const { signature } = web3.eth.accounts.sign( + const { v, r, s } = web3.eth.accounts.sign( message, '0xf41510ea3e58c22cbabe881c9c87e60078dac25b23f93319e355c9ae0562987a' ) @@ -164,7 +166,9 @@ describe('processCollectedSignatures', () => { foreignBridge, validatorContract, numberOfCollectedSignatures: 1, - signatures: [signature], + v: [v], + r: [r], + s: [s], message })