From aa40f2fc6d604b6e67cb061619be6dfc0349b9b2 Mon Sep 17 00:00:00 2001 From: Westlad Date: Wed, 2 Mar 2022 11:32:26 +0000 Subject: [PATCH 1/8] fix: pong-down now removes client storage --- test/ping-pong/pong-down | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/ping-pong/pong-down b/test/ping-pong/pong-down index f7983b637..1219ad509 100755 --- a/test/ping-pong/pong-down +++ b/test/ping-pong/pong-down @@ -13,12 +13,8 @@ usage() while [ -n "$1" ]; do case $1 in -v | --volumes ) if [[ $(echo $VOLUME_LIST | grep ping-pong_proving_files) ]]; then - echo -n 'Removing ' - docker volume rm ping-pong_proving_files - fi - if [[ $(echo $VOLUME_LIST | grep ping-pong_mongodb) ]]; then echo -n 'Removing ' - docker volume rm ping-pong_mongodb + docker volume rm ping-pong_proving_files fi if [[ $(echo $VOLUME_LIST | grep ping-pong_build) ]]; then echo -n 'Removing ' @@ -34,6 +30,11 @@ while [ -n "$1" ]; do shift done +if [[ $(echo $VOLUME_LIST | grep ping-pong_mongodb) ]]; then + echo -n 'Removing ' + docker volume rm ping-pong_mongodb +fi + DIR=./common-files/node_modules if [[ -d "$DIR" ]]; then rm -dr common-files/node_modules From ae609ddaa7faa955ee31c83e0d9b5afd4f8ff502 Mon Sep 17 00:00:00 2001 From: Westlad Date: Wed, 2 Mar 2022 11:35:27 +0000 Subject: [PATCH 2/8] fix: pong-down now removes client storage --- doc/contract-update.md | 68 --------------------------------------- doc/contract-upgrade.png | Bin 45549 -> 0 bytes 2 files changed, 68 deletions(-) delete mode 100644 doc/contract-update.md delete mode 100644 doc/contract-upgrade.png diff --git a/doc/contract-update.md b/doc/contract-update.md deleted file mode 100644 index 589ffc5dc..000000000 --- a/doc/contract-update.md +++ /dev/null @@ -1,68 +0,0 @@ -# Upgrading and Migrating contracts - -We consider two approaches: *Upgrading* existing contracts to a new version but retaining -state data as-is, and; *migrating* users to a completely new set of contracts. - -We would only migrate in the event of a key loss, which prevents upgrading contracts. - -## Upgrade contracts approach - -Our contracts are already mostly divided into contracts that contain data and contracts that -contain logic. This is fortuitous for the pattern we are going to use. - -`State.sol` contains most of the state, with some in `Shield.sol`. -This division could be made complete by moving the following functions out of `State.sol` -- `proposeBlock` -- `removeProposer` -- `emitRollback` -- `isBlockReal` -- `rewardChallenger` -- `removeProposer` - -and moving the `Shield.sol` data into `State.sol`. `State.sol` will then only contain storage data -and getters/setters for the same. Note that it also holds nightfall's escrow pool and so needs to host `withdraw`. -It will also need modifying so that the addresses of the logic contracts, which it allows to -set state, can be updated by a registered contract containing an `upgrade` function (see later). - -After that is done, we will not normally upgrade the `State.sol` contract but will upgrade the -stateless logic contracts. - -We will create a set of contracts, which proxy calls to the contracts (one for each logic contract). This avoids users -having to repoint their applications to the new contracts. These contracts must be ERC1967 compliant. - -![contract interaction](./contract-upgrade.png) - -### upgrade function - -This function will, atomically, carry out the following actions: - -1. Repoint `State.sol`'s registered logic contracts to the new logic contracts; -1. Repoint `Proxy.sol` contracts to the new contracts; -1. Change the addresses of the contracts registered with `State.sol` to the new contract addresses; -1. Store the address of the old contracts and the block number at which the swap-over occurred in `State.sol`; this -will be used to help nightfall applications parse historic events and calldata. This will be in the form of -an array, added to each time the contracts are upgraded. - -We will ensure that a unique private key is needed to call `upgrade`. - -Note: The nightfall applications will need to be updated so that they can sync events and calldata on startup, -which may have changing contract addresses, reflecting historic contract upgrades. - -## Migrate contracts approach - -In rare circumstances an upgrade may not be possible; the only use-case is probably compromise of the upgrade -private key. If this happens we would deploy an entirely new set of contracts. Rather than attempt to -migrate storage data (which would be difficult because of the number of mapping types, and the need to -freeze updates for the old contract data, which would require a wait of one finalisation period, ~1 week and may not be -possible if the relevant key is compromised), we will simply advertise the new contract addresses and recommend -that people upgrade by removing their funds from the old contract. - -## other - -We will include a function to pause nightfall, in case we need to copy over finalised state at any time. - -## Implementation - -We will use the [Openzepplin](https://docs.openzeppelin.com/upgrades-plugins/1.x/) Upgrades Truffle plugin to -implement the above approach. This will reduce the possibility of error. We will use the Beacon pattern -because this will allow us to atomically upgrade the contracts. diff --git a/doc/contract-upgrade.png b/doc/contract-upgrade.png deleted file mode 100644 index 69bfc94dc5d60ca61bd01ee9decf29debb2ad4c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45549 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 Date: Wed, 2 Mar 2022 16:43:42 +0000 Subject: [PATCH 3/8] fix: temporary logging added --- .../src/event-handlers/new-current-proposer.mjs | 6 ++++++ nightfall-optimist/src/index.mjs | 1 + nightfall-optimist/src/routes/proposer.mjs | 1 + test/ping-pong/proposer/src/index.mjs | 2 +- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/nightfall-optimist/src/event-handlers/new-current-proposer.mjs b/nightfall-optimist/src/event-handlers/new-current-proposer.mjs index ec3f86714..2fc17bc9b 100644 --- a/nightfall-optimist/src/event-handlers/new-current-proposer.mjs +++ b/nightfall-optimist/src/event-handlers/new-current-proposer.mjs @@ -31,6 +31,12 @@ async function newCurrentProposerEventHandler(data, args) { // !! converts this to a "is not null" check - i.e. false if is null // are we the next proposer? proposer.isMe = !!(await isRegisteredProposerAddressMine(currentProposer)); + console.log( + 'PROPOSER IN EVENT HANDLER', + proposer, + currentProposer, + await isRegisteredProposerAddressMine(currentProposer), + ); } catch (err) { // handle errors logger.error(err); diff --git a/nightfall-optimist/src/index.mjs b/nightfall-optimist/src/index.mjs index 23c54f1be..5b9cfa2ef 100644 --- a/nightfall-optimist/src/index.mjs +++ b/nightfall-optimist/src/index.mjs @@ -31,6 +31,7 @@ const main = async () => { queues[0].on('end', () => { // We do the proposer isMe check here to fail fast instead of re-enqueing. // We check if the queue[2] is empty, this is safe it is manually enqueued/dequeued. + console.log('PROPOSER', proposer, queues[2].length); if (proposer.isMe && queues[2].length === 0) { // logger.info('Queue has emptied. Queueing block assembler.'); return enqueueEvent(conditionalMakeBlock, 0, proposer); diff --git a/nightfall-optimist/src/routes/proposer.mjs b/nightfall-optimist/src/routes/proposer.mjs index d89ba6f48..aa87800a4 100644 --- a/nightfall-optimist/src/routes/proposer.mjs +++ b/nightfall-optimist/src/routes/proposer.mjs @@ -37,6 +37,7 @@ router.post('/register', async (req, res, next) => { logger.silly(`raw transaction is ${JSON.stringify(txDataToSign, null, 2)}`); res.json({ txDataToSign }); setRegisteredProposerAddress(address); // save the registration address + console.log('REGISTERED ADDRESS', address); } catch (err) { logger.error(err); next(err); diff --git a/test/ping-pong/proposer/src/index.mjs b/test/ping-pong/proposer/src/index.mjs index 6e423ef10..babf851bb 100644 --- a/test/ping-pong/proposer/src/index.mjs +++ b/test/ping-pong/proposer/src/index.mjs @@ -32,7 +32,7 @@ async function startProposer() { } else if (!proposers.map(p => p.thisAddress).includes(nf3.ethereumAddress)) { await nf3.registerProposer(); logger.info('Proposer registration complete'); - } else logger.warn('Proposer appears to be registerd already'); + } else logger.warn('Proposer appears to be registered already'); if (PROPOSER_PORT !== '') { logger.debug('Proposer healthcheck up'); app.listen(PROPOSER_PORT); From 85c88eab6e648a2b3e6e7400e97b1077ebd09c3c Mon Sep 17 00:00:00 2001 From: Westlad Date: Fri, 4 Mar 2022 15:11:09 +0000 Subject: [PATCH 4/8] feat: containers restart correctly --- cli/lib/nf3.mjs | 19 ++++++ common-files/utils/contract.mjs | 1 - config/default.js | 3 +- .../src/event-handlers/subscribe.mjs | 3 +- nightfall-client/src/services/database.mjs | 3 +- nightfall-client/src/services/state-sync.mjs | 7 +- nightfall-deployer/src/index.mjs | 11 +++- .../event-handlers/new-current-proposer.mjs | 6 -- .../src/event-handlers/subscribe.mjs | 1 - nightfall-optimist/src/index.mjs | 3 +- nightfall-optimist/src/routes/proposer.mjs | 65 ++++++++++++++++--- nightfall-optimist/src/services/database.mjs | 15 +++-- test/e2e/protocol/challenger.test.mjs | 2 +- test/ping-pong/proposer/src/index.mjs | 7 +- 14 files changed, 107 insertions(+), 39 deletions(-) diff --git a/cli/lib/nf3.mjs b/cli/lib/nf3.mjs index 326fbf5d8..af5072413 100644 --- a/cli/lib/nf3.mjs +++ b/cli/lib/nf3.mjs @@ -570,6 +570,25 @@ class Nf3 { ); } + /** + Registers a proposer locally with the Optimist instance only. This will cause + Optimist to make blocks when this proposer is current but these will revert if + the proposer isn't registered on the blockchain too. This method is useful only + if the proposer is already registered on the blockchain (has paid their bond) and + for some reason the Optimist instance does not know about them, e.g. a new instance + has been created. The method 'registerProposer' will both register the proposer + with the blockchain and register locally with the optimist instance. So, if + that method has been used successfully, there is no need to also call this method + @method + @async + @returns {Promise} A promise that resolves to the Ethereum transaction receipt. + */ + async registerProposerLocally() { + return axios.post(`${this.optimistBaseUrl}/proposer/registerlocally`, { + address: this.ethereumAddress, + }); + } + /** De-registers an existing proposer. It will use the address of the Ethereum Signing key that is holds to de-register diff --git a/common-files/utils/contract.mjs b/common-files/utils/contract.mjs index 0c10447ab..0ae90e207 100644 --- a/common-files/utils/contract.mjs +++ b/common-files/utils/contract.mjs @@ -104,7 +104,6 @@ export async function waitForContract(contractName) { try { error = undefined; const address = await getContractAddress(contractName); // eslint-disable-line no-await-in-loop - logger.debug(`${contractName} contract address is ${address}`); if (address === undefined) throw new Error(`${contractName} contract address was undefined`); instance = getContractInstance(contractName, address); return instance; diff --git a/config/default.js b/config/default.js index 8bad62aa5..f039ee93f 100644 --- a/config/default.js +++ b/config/default.js @@ -3,7 +3,8 @@ const { DOMAIN_NAME = '' } = process.env; module.exports = { COMMITMENTS_DB: 'nightfall_commitments', OPTIMIST_DB: 'optimist_data', - METADATA_COLLECTION: 'metadata', + PROPOSER_COLLECTION: 'proposers', + CHALLENGER_COLLECTION: 'challengers', TRANSACTIONS_COLLECTION: 'transactions', SUBMITTED_BLOCKS_COLLECTION: 'blocks', NULLIFIER_COLLECTION: 'nullifiers', diff --git a/nightfall-client/src/event-handlers/subscribe.mjs b/nightfall-client/src/event-handlers/subscribe.mjs index 9a1d67b39..1e58721e9 100644 --- a/nightfall-client/src/event-handlers/subscribe.mjs +++ b/nightfall-client/src/event-handlers/subscribe.mjs @@ -15,7 +15,7 @@ const { STATE_CONTRACT_NAME, RETRIES } = config; * This is useful in case nightfall-client comes up before the contract * is fully deployed. */ -async function waitForContract(contractName) { +export async function waitForContract(contractName) { let errorCount = 0; let error; let instance; @@ -23,7 +23,6 @@ async function waitForContract(contractName) { try { error = undefined; const address = await getContractAddress(contractName); - logger.debug(`${contractName} contract address is ${address}`); if (address === undefined) throw new Error(`${contractName} contract address was undefined`); instance = getContractInstance(contractName, address); return instance; diff --git a/nightfall-client/src/services/database.mjs b/nightfall-client/src/services/database.mjs index e0dc24b1a..2e0e3233f 100644 --- a/nightfall-client/src/services/database.mjs +++ b/nightfall-client/src/services/database.mjs @@ -109,7 +109,8 @@ export async function saveBlock(_block) { if (!existing || !existing.blockNumber) { return db.collection(SUBMITTED_BLOCKS_COLLECTION).updateOne(query, update, { upsert: true }); } - throw new Error('Attempted to replay existing layer 2 block'); + logger.warn('Attempted to replay existing layer 2 block. This is expected if we are syncing'); + return true; } /** diff --git a/nightfall-client/src/services/state-sync.mjs b/nightfall-client/src/services/state-sync.mjs index a398aa4fb..a05d0c67e 100644 --- a/nightfall-client/src/services/state-sync.mjs +++ b/nightfall-client/src/services/state-sync.mjs @@ -5,21 +5,20 @@ their local commitments databsae. import config from 'config'; import logger from 'common-files/utils/logger.mjs'; -import { getContractInstance } from 'common-files/utils/contract.mjs'; import mongo from 'common-files/utils/mongo.mjs'; +import { waitForContract } from '../event-handlers/subscribe.mjs'; import blockProposedEventHandler from '../event-handlers/block-proposed.mjs'; import rollbackEventHandler from '../event-handlers/rollback.mjs'; const { MONGO_URL, COMMITMENTS_DB, COMMITMENTS_COLLECTION, STATE_CONTRACT_NAME } = config; const syncState = async (fromBlock = 'earliest', toBlock = 'latest', eventFilter = 'allEvents') => { - const stateContractInstance = await getContractInstance(STATE_CONTRACT_NAME); // Rollback, BlockProposed + const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME); // Rollback, BlockProposed const pastStateEvents = await stateContractInstance.getPastEvents(eventFilter, { fromBlock, toBlock, }); - logger.info(`pastStateEvents: ${JSON.stringify(pastStateEvents)}`); for (let i = 0; i < pastStateEvents.length; i++) { switch (pastStateEvents[i].event) { @@ -46,8 +45,6 @@ const genGetCommitments = async (query = {}, proj = {}) => { // eslint-disable-next-line import/prefer-default-export export const initialClientSync = async () => { const allCommitments = await genGetCommitments(); - if (allCommitments.length === 0) return {}; - const commitmentBlockNumbers = allCommitments.map(a => a.blockNumber).filter(n => n >= 0); logger.info(`commitmentBlockNumbers: ${commitmentBlockNumbers}`); const firstSeenBlockNumber = Math.min(...commitmentBlockNumbers); diff --git a/nightfall-deployer/src/index.mjs b/nightfall-deployer/src/index.mjs index f3567324d..615789e80 100644 --- a/nightfall-deployer/src/index.mjs +++ b/nightfall-deployer/src/index.mjs @@ -1,4 +1,5 @@ import Web3 from 'common-files/utils/web3.mjs'; +import logger from 'common-files/utils/logger.mjs'; import circuits from './circuit-setup.mjs'; import setupContracts from './contract-setup.mjs'; @@ -6,7 +7,15 @@ import setupContracts from './contract-setup.mjs'; async function main() { await circuits.waitForZokrates(); await circuits.setupCircuits(); - await setupContracts(); + try { + await setupContracts(); + } catch (err) { + if (err.message.includes('Transaction has been reverted by the EVM')) + logger.warn( + 'Writing contract addresses to the State contract failed. This is probably because they are aready set. Did you already run deployer?', + ); + else throw new Error(err); + } Web3.disconnect(); } diff --git a/nightfall-optimist/src/event-handlers/new-current-proposer.mjs b/nightfall-optimist/src/event-handlers/new-current-proposer.mjs index 2fc17bc9b..ec3f86714 100644 --- a/nightfall-optimist/src/event-handlers/new-current-proposer.mjs +++ b/nightfall-optimist/src/event-handlers/new-current-proposer.mjs @@ -31,12 +31,6 @@ async function newCurrentProposerEventHandler(data, args) { // !! converts this to a "is not null" check - i.e. false if is null // are we the next proposer? proposer.isMe = !!(await isRegisteredProposerAddressMine(currentProposer)); - console.log( - 'PROPOSER IN EVENT HANDLER', - proposer, - currentProposer, - await isRegisteredProposerAddressMine(currentProposer), - ); } catch (err) { // handle errors logger.error(err); diff --git a/nightfall-optimist/src/event-handlers/subscribe.mjs b/nightfall-optimist/src/event-handlers/subscribe.mjs index 631aba659..a88615ea6 100644 --- a/nightfall-optimist/src/event-handlers/subscribe.mjs +++ b/nightfall-optimist/src/event-handlers/subscribe.mjs @@ -32,7 +32,6 @@ export async function waitForContract(contractName) { try { error = undefined; const address = await getContractAddress(contractName); - logger.debug(`${contractName} contract address is ${address}`); if (address === undefined) throw new Error(`${contractName} contract address was undefined`); instance = getContractInstance(contractName, address); return instance; diff --git a/nightfall-optimist/src/index.mjs b/nightfall-optimist/src/index.mjs index 5b9cfa2ef..cfc19b379 100644 --- a/nightfall-optimist/src/index.mjs +++ b/nightfall-optimist/src/index.mjs @@ -16,10 +16,12 @@ import { import { setChallengeWebSocketConnection } from './services/challenges.mjs'; import initialBlockSync from './services/state-sync.mjs'; import { setInstantWithdrawalWebSocketConnection } from './services/instant-withdrawal.mjs'; +import { setProposer } from './routes/proposer.mjs'; const main = async () => { try { const proposer = new Proposer(); + setProposer(proposer); // passes the proposer instance int the proposer routes // subscribe to WebSocket events first await subscribeToBlockAssembledWebSocketConnection(setBlockAssembledWebSocketConnection); await subscribeToChallengeWebSocketConnection(setChallengeWebSocketConnection); @@ -31,7 +33,6 @@ const main = async () => { queues[0].on('end', () => { // We do the proposer isMe check here to fail fast instead of re-enqueing. // We check if the queue[2] is empty, this is safe it is manually enqueued/dequeued. - console.log('PROPOSER', proposer, queues[2].length); if (proposer.isMe && queues[2].length === 0) { // logger.info('Queue has emptied. Queueing block assembler.'); return enqueueEvent(conditionalMakeBlock, 0, proposer); diff --git a/nightfall-optimist/src/routes/proposer.mjs b/nightfall-optimist/src/routes/proposer.mjs index aa87800a4..4797f3107 100644 --- a/nightfall-optimist/src/routes/proposer.mjs +++ b/nightfall-optimist/src/routes/proposer.mjs @@ -8,6 +8,7 @@ import config from 'config'; import Timber from 'common-files/classes/timber.mjs'; import logger from 'common-files/utils/logger.mjs'; import { getContractInstance } from 'common-files/utils/contract.mjs'; +import { enqueueEvent } from 'common-files/utils/event-queue.mjs'; import Block from '../classes/block.mjs'; import { Transaction, TransactionError } from '../classes/index.mjs'; import { @@ -21,6 +22,11 @@ import transactionSubmittedEventHandler from '../event-handlers/transaction-subm const router = express.Router(); const { STATE_CONTRACT_NAME, PROPOSERS_CONTRACT_NAME, SHIELD_CONTRACT_NAME, ZERO } = config; +let proposer; +export function setProposer(p) { + proposer = p; +} + /** * Function to return a raw transaction that registers a proposer. This just * provides the tx data, the user will need to append the registration bond @@ -34,13 +40,52 @@ router.post('/register', async (req, res, next) => { const proposersContractInstance = await getContractInstance(PROPOSERS_CONTRACT_NAME); const txDataToSign = await proposersContractInstance.methods.registerProposer().encodeABI(); logger.debug('returning raw transaction data'); - logger.silly(`raw transaction is ${JSON.stringify(txDataToSign, null, 2)}`); res.json({ txDataToSign }); - setRegisteredProposerAddress(address); // save the registration address - console.log('REGISTERED ADDRESS', address); + await setRegisteredProposerAddress(address); // save the registration address } catch (err) { - logger.error(err); - next(err); + if (err.message.includes('E11000 duplicate key error')) + // if we get a duplicate key error then the EVM will have reverted so we don't need to handle this separately + logger.warn( + 'Duplicate key detected. You are probably trying to to register the proposer twice. Second attempt ignored', + ); + else { + logger.error(err); + next(err); + } + } +}); + +/** + * Function to locally register a proposer with optimist. This call is similar to + * /register but it won't touch the blockchain. It will just cause optimist to make + * blocks when this proposer is the current proposer. It's useful if you are starting + * a new optimist instance but your proposer address is already registed with the blockchain + * It's idempotent: attempting to re-register a proposer will have no effect. + */ +router.post('/registerlocally', async (req, res, next) => { + logger.debug( + `local register proposer endpoint received POST ${JSON.stringify(req.body, null, 2)}`, + ); + const { address } = req.body; + try { + await setRegisteredProposerAddress(address); // save the registration address + // we should also check if we're the current proposer because we're registered on + // the blockchain so we could be + const stateContractInstance = await getContractInstance(STATE_CONTRACT_NAME); + const currentProposer = await stateContractInstance.methods.getCurrentProposer().call(); + if (address === currentProposer.thisAddress) { + proposer.isMe = true; + await enqueueEvent(() => logger.info('Start Queue'), 0); // kickstart the queue + } + res.sendStatus(200); + } catch (err) { + if (err.message.includes('duplicate key')) { + logger.warn(`Proposer ${address} is already registered locally`); + res.sendStatus(200); + } else { + logger.error(err); + next(err); + } } }); @@ -59,9 +104,9 @@ router.get('/proposers', async (req, res, next) => { // Loop through the circular list until we run back into the currentProposer. do { // eslint-disable-next-line no-await-in-loop - const proposer = await proposersContractInstance.methods.proposers(thisPtr).call(); - proposers.push(proposer); - thisPtr = proposer.nextAddress; + const prop = await proposersContractInstance.methods.proposers(thisPtr).call(); + proposers.push(prop); + thisPtr = prop.nextAddress; } while (thisPtr !== currentProposer.thisAddress); logger.debug('returning raw transaction data'); @@ -158,12 +203,12 @@ router.post('/propose', async (req, res, next) => { logger.debug(`propose endpoint received POST`); logger.silly(`With content ${JSON.stringify(req.body, null, 2)}`); try { - const { transactions, proposer, currentLeafCount } = req.body; + const { transactions, proposer: prop, currentLeafCount } = req.body; // use the information we've been POSTED to assemble a block // we use a Builder pattern because an async constructor is bad form const block = await Block.build({ transactions, - proposer, + proposer: prop, currentLeafCount, }); logger.debug(`New block assembled ${JSON.stringify(block, null, 2)}`); diff --git a/nightfall-optimist/src/services/database.mjs b/nightfall-optimist/src/services/database.mjs index 461bf5cdf..4d0bfebd8 100644 --- a/nightfall-optimist/src/services/database.mjs +++ b/nightfall-optimist/src/services/database.mjs @@ -12,7 +12,8 @@ const { MONGO_URL, OPTIMIST_DB, TRANSACTIONS_COLLECTION, - METADATA_COLLECTION, + PROPOSER_COLLECTION, + CHALLENGER_COLLECTION, SUBMITTED_BLOCKS_COLLECTION, NULLIFIER_COLLECTION, COMMIT_COLLECTION, @@ -54,7 +55,7 @@ export async function addChallengerAddress(address) { const db = connection.db(OPTIMIST_DB); logger.debug(`Saving challenger address ${address}`); const data = { challenger: address }; - return db.collection(METADATA_COLLECTION).insertOne(data); + return db.collection(CHALLENGER_COLLECTION).insertOne(data); } /** @@ -67,7 +68,7 @@ export async function removeChallengerAddress(address) { const db = connection.db(OPTIMIST_DB); logger.debug(`Removing challenger address ${address}`); const data = { challenger: address }; - return db.collection(METADATA_COLLECTION).deleteOne(data); + return db.collection(CHALLENGER_COLLECTION).deleteOne(data); } /** @@ -76,7 +77,7 @@ Function to tell us if an address used to commit to a challenge belongs to us export async function isChallengerAddressMine(address) { const connection = await mongo.connection(MONGO_URL); const db = connection.db(OPTIMIST_DB); - const metadata = await db.collection(METADATA_COLLECTION).findOne({ challenger: address }); + const metadata = await db.collection(CHALLENGER_COLLECTION).findOne({ challenger: address }); return metadata !== null; } @@ -218,8 +219,8 @@ export async function setRegisteredProposerAddress(address) { const connection = await mongo.connection(MONGO_URL); const db = connection.db(OPTIMIST_DB); logger.debug(`Saving proposer address ${address}`); - const data = { proposer: address }; - return db.collection(METADATA_COLLECTION).insertOne(data); + const data = { _id: address }; + return db.collection(PROPOSER_COLLECTION).insertOne(data); } /** @@ -229,7 +230,7 @@ thus it should start assembling blocks of transactions. export async function isRegisteredProposerAddressMine(address) { const connection = await mongo.connection(MONGO_URL); const db = connection.db(OPTIMIST_DB); - const metadata = await db.collection(METADATA_COLLECTION).findOne({ proposer: address }); + const metadata = await db.collection(PROPOSER_COLLECTION).findOne({ _id: address }); logger.silly(`found registered proposer ${JSON.stringify(metadata, null, 2)}`); return metadata; } diff --git a/test/e2e/protocol/challenger.test.mjs b/test/e2e/protocol/challenger.test.mjs index 2a3f86cc3..6c217c3f9 100644 --- a/test/e2e/protocol/challenger.test.mjs +++ b/test/e2e/protocol/challenger.test.mjs @@ -21,7 +21,7 @@ describe('Basic Challenger tests', () => { await nf3Challenger.init(mnemonics.challenger); // Challenger registration await nf3Challenger.registerChallenger(); - // Chalenger listening for incoming events + // Challenger listening for incoming events nf3Challenger.startChallenger(); }); diff --git a/test/ping-pong/proposer/src/index.mjs b/test/ping-pong/proposer/src/index.mjs index babf851bb..c34740b86 100644 --- a/test/ping-pong/proposer/src/index.mjs +++ b/test/ping-pong/proposer/src/index.mjs @@ -24,7 +24,7 @@ async function startProposer() { else throw new Error('Healthcheck failed'); logger.info('Attempting to register proposer'); // let's see if the proposer has been registered before - const { proposers } = await nf3.getProposers(); + const { proposers } = await nf3.getProposers(); // read proposer list from the blockchain // if not, let's register them if (proposers.length === 0) { await nf3.registerProposer(); @@ -32,7 +32,10 @@ async function startProposer() { } else if (!proposers.map(p => p.thisAddress).includes(nf3.ethereumAddress)) { await nf3.registerProposer(); logger.info('Proposer registration complete'); - } else logger.warn('Proposer appears to be registered already'); + } else { + logger.warn('Proposer appears to be registered on the blockchain already'); + await nf3.registerProposerLocally(); + } if (PROPOSER_PORT !== '') { logger.debug('Proposer healthcheck up'); app.listen(PROPOSER_PORT); From 4a2a568f568cb22685c73dcf739e505e6984ea89 Mon Sep 17 00:00:00 2001 From: Westlad Date: Thu, 10 Mar 2022 11:48:37 +0000 Subject: [PATCH 5/8] fix: improved registration logic --- cli/lib/nf3.mjs | 1 + nightfall-optimist/src/routes/proposer.mjs | 95 ++++++++------------ nightfall-optimist/src/services/proposer.mjs | 26 ++++++ test/ping-pong/proposer/src/index.mjs | 14 +-- 4 files changed, 63 insertions(+), 73 deletions(-) create mode 100644 nightfall-optimist/src/services/proposer.mjs diff --git a/cli/lib/nf3.mjs b/cli/lib/nf3.mjs index af5072413..b02d97cf7 100644 --- a/cli/lib/nf3.mjs +++ b/cli/lib/nf3.mjs @@ -563,6 +563,7 @@ class Nf3 { const res = await axios.post(`${this.optimistBaseUrl}/proposer/register`, { address: this.ethereumAddress, }); + if (res.data.txDataToSign === '') return false; // already registered return this.submitTransaction( res.data.txDataToSign, this.proposersContractAddress, diff --git a/nightfall-optimist/src/routes/proposer.mjs b/nightfall-optimist/src/routes/proposer.mjs index 4797f3107..3af90166c 100644 --- a/nightfall-optimist/src/routes/proposer.mjs +++ b/nightfall-optimist/src/routes/proposer.mjs @@ -13,11 +13,13 @@ import Block from '../classes/block.mjs'; import { Transaction, TransactionError } from '../classes/index.mjs'; import { setRegisteredProposerAddress, + isRegisteredProposerAddressMine, getMempoolTransactions, getLatestTree, } from '../services/database.mjs'; import { waitForContract } from '../event-handlers/subscribe.mjs'; import transactionSubmittedEventHandler from '../event-handlers/transaction-submitted.mjs'; +import { getProposers } from '../services/proposer.mjs'; const router = express.Router(); const { STATE_CONTRACT_NAME, PROPOSERS_CONTRACT_NAME, SHIELD_CONTRACT_NAME, ZERO } = config; @@ -37,55 +39,42 @@ router.post('/register', async (req, res, next) => { logger.debug(`register proposer endpoint received POST ${JSON.stringify(req.body, null, 2)}`); try { const { address } = req.body; - const proposersContractInstance = await getContractInstance(PROPOSERS_CONTRACT_NAME); - const txDataToSign = await proposersContractInstance.methods.registerProposer().encodeABI(); - logger.debug('returning raw transaction data'); - res.json({ txDataToSign }); - await setRegisteredProposerAddress(address); // save the registration address - } catch (err) { - if (err.message.includes('E11000 duplicate key error')) - // if we get a duplicate key error then the EVM will have reverted so we don't need to handle this separately + const proposersContractInstance = await waitForContract(PROPOSERS_CONTRACT_NAME); + // the first thing to do is to check if the proposer is already registered on the blockchain + const proposers = (await getProposers()).map(p => p.thisAddress); + // if not, let's register it + let txDataToSign = ''; + if (!proposers.includes(address)) { + txDataToSign = await proposersContractInstance.methods.registerProposer().encodeABI(); + } else logger.warn( - 'Duplicate key detected. You are probably trying to to register the proposer twice. Second attempt ignored', + 'Proposer was already registered on the blockchain - registration attempt ignored', ); - else { - logger.error(err); - next(err); - } - } -}); - -/** - * Function to locally register a proposer with optimist. This call is similar to - * /register but it won't touch the blockchain. It will just cause optimist to make - * blocks when this proposer is the current proposer. It's useful if you are starting - * a new optimist instance but your proposer address is already registed with the blockchain - * It's idempotent: attempting to re-register a proposer will have no effect. - */ -router.post('/registerlocally', async (req, res, next) => { - logger.debug( - `local register proposer endpoint received POST ${JSON.stringify(req.body, null, 2)}`, - ); - const { address } = req.body; - try { - await setRegisteredProposerAddress(address); // save the registration address - // we should also check if we're the current proposer because we're registered on - // the blockchain so we could be - const stateContractInstance = await getContractInstance(STATE_CONTRACT_NAME); - const currentProposer = await stateContractInstance.methods.getCurrentProposer().call(); - if (address === currentProposer.thisAddress) { - proposer.isMe = true; - await enqueueEvent(() => logger.info('Start Queue'), 0); // kickstart the queue + // when we get to here, either the proposer was already registered (txDataToSign === '') + // or we're just about to register them. We may or may not be registed locally + // with optimist though. Let's check and fix that if needed. + if (!(await isRegisteredProposerAddressMine(address))) { + logger.debug('Registering proposer locally'); + await setRegisteredProposerAddress(address); // save the registration address + // We've just registered with optimist but if we were already registered on the blockchain, + // we should check if we're the current proposer and, if so, set things up so we start + // making blocks immediately + if (txDataToSign === '') { + logger.warn( + 'Proposer was already registered on the blockchain but not with this Optimist instance - registering locally', + ); + const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME); + const currentProposer = await stateContractInstance.methods.getCurrentProposer().call(); + if (address === currentProposer.thisAddress) { + proposer.isMe = true; + await enqueueEvent(() => logger.info('Start Queue'), 0); // kickstart the queue + } + } } - res.sendStatus(200); + res.json({ txDataToSign }); } catch (err) { - if (err.message.includes('duplicate key')) { - logger.warn(`Proposer ${address} is already registered locally`); - res.sendStatus(200); - } else { - logger.error(err); - next(err); - } + logger.error(err); + next(err); } }); @@ -95,22 +84,8 @@ router.post('/registerlocally', async (req, res, next) => { router.get('/proposers', async (req, res, next) => { logger.debug(`list proposals endpoint received GET`); try { - const proposersContractInstance = await getContractInstance(STATE_CONTRACT_NAME); - // proposers is an on-chain mapping so to get proposers we need to key to start iterating - // the safest to start with is the currentProposer - const currentProposer = await proposersContractInstance.methods.currentProposer().call(); - const proposers = []; - let thisPtr = currentProposer.thisAddress; - // Loop through the circular list until we run back into the currentProposer. - do { - // eslint-disable-next-line no-await-in-loop - const prop = await proposersContractInstance.methods.proposers(thisPtr).call(); - proposers.push(prop); - thisPtr = prop.nextAddress; - } while (thisPtr !== currentProposer.thisAddress); - + const proposers = getProposers(); logger.debug('returning raw transaction data'); - logger.silly(`raw transaction is ${JSON.stringify(proposers, null, 2)}`); res.json({ proposers }); } catch (err) { logger.error(err); diff --git a/nightfall-optimist/src/services/proposer.mjs b/nightfall-optimist/src/services/proposer.mjs new file mode 100644 index 000000000..73b025e6f --- /dev/null +++ b/nightfall-optimist/src/services/proposer.mjs @@ -0,0 +1,26 @@ +/** +Module containing functions relating to proposers and their registration +*/ +import config from 'config'; +import { waitForContract } from 'common-files/utils/contract.mjs'; + +const { STATE_CONTRACT_NAME } = config; + +export async function getProposers() { + const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME); + // proposers is an on-chain mapping so to get proposers we need to key to start iterating + // the safest to start with is the currentProposer + const currentProposer = await stateContractInstance.methods.currentProposer().call(); + const proposers = []; + let thisPtr = currentProposer.thisAddress; + // Loop through the circular list until we run back into the currentProposer. + do { + // eslint-disable-next-line no-await-in-loop + const prop = await stateContractInstance.methods.proposers(thisPtr).call(); + proposers.push(prop); + thisPtr = prop.nextAddress; + } while (thisPtr !== currentProposer.thisAddress); + return proposers; +} + +export default getProposers; diff --git a/test/ping-pong/proposer/src/index.mjs b/test/ping-pong/proposer/src/index.mjs index c34740b86..c721ab838 100644 --- a/test/ping-pong/proposer/src/index.mjs +++ b/test/ping-pong/proposer/src/index.mjs @@ -23,19 +23,7 @@ async function startProposer() { if (await nf3.healthcheck('optimist')) logger.info('Healthcheck passed'); else throw new Error('Healthcheck failed'); logger.info('Attempting to register proposer'); - // let's see if the proposer has been registered before - const { proposers } = await nf3.getProposers(); // read proposer list from the blockchain - // if not, let's register them - if (proposers.length === 0) { - await nf3.registerProposer(); - logger.info('Proposer registration complete'); - } else if (!proposers.map(p => p.thisAddress).includes(nf3.ethereumAddress)) { - await nf3.registerProposer(); - logger.info('Proposer registration complete'); - } else { - logger.warn('Proposer appears to be registered on the blockchain already'); - await nf3.registerProposerLocally(); - } + await nf3.registerProposer(); if (PROPOSER_PORT !== '') { logger.debug('Proposer healthcheck up'); app.listen(PROPOSER_PORT); From f0485c1c6bbc1c991ccc2157e0ce3c49c76499bf Mon Sep 17 00:00:00 2001 From: Westlad Date: Thu, 10 Mar 2022 12:21:00 +0000 Subject: [PATCH 6/8] fix: remove redundant export --- nightfall-optimist/src/routes/proposer.mjs | 2 +- nightfall-optimist/src/services/proposer.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nightfall-optimist/src/routes/proposer.mjs b/nightfall-optimist/src/routes/proposer.mjs index 3af90166c..da011d29e 100644 --- a/nightfall-optimist/src/routes/proposer.mjs +++ b/nightfall-optimist/src/routes/proposer.mjs @@ -19,7 +19,7 @@ import { } from '../services/database.mjs'; import { waitForContract } from '../event-handlers/subscribe.mjs'; import transactionSubmittedEventHandler from '../event-handlers/transaction-submitted.mjs'; -import { getProposers } from '../services/proposer.mjs'; +import getProposers from '../services/proposer.mjs'; const router = express.Router(); const { STATE_CONTRACT_NAME, PROPOSERS_CONTRACT_NAME, SHIELD_CONTRACT_NAME, ZERO } = config; diff --git a/nightfall-optimist/src/services/proposer.mjs b/nightfall-optimist/src/services/proposer.mjs index 73b025e6f..d91ceb709 100644 --- a/nightfall-optimist/src/services/proposer.mjs +++ b/nightfall-optimist/src/services/proposer.mjs @@ -6,7 +6,7 @@ import { waitForContract } from 'common-files/utils/contract.mjs'; const { STATE_CONTRACT_NAME } = config; -export async function getProposers() { +async function getProposers() { const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME); // proposers is an on-chain mapping so to get proposers we need to key to start iterating // the safest to start with is the currentProposer From 3ef1f665409e180f0f7dd0c16bbd7b7f722fef93 Mon Sep 17 00:00:00 2001 From: Westlad Date: Fri, 11 Mar 2022 13:59:30 +0000 Subject: [PATCH 7/8] fix: tests failing --- nightfall-optimist/src/routes/proposer.mjs | 4 ++-- test/e2e/protocol/proposer.test.mjs | 11 +++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/nightfall-optimist/src/routes/proposer.mjs b/nightfall-optimist/src/routes/proposer.mjs index da011d29e..8b6c661b4 100644 --- a/nightfall-optimist/src/routes/proposer.mjs +++ b/nightfall-optimist/src/routes/proposer.mjs @@ -84,8 +84,8 @@ router.post('/register', async (req, res, next) => { router.get('/proposers', async (req, res, next) => { logger.debug(`list proposals endpoint received GET`); try { - const proposers = getProposers(); - logger.debug('returning raw transaction data'); + const proposers = await getProposers(); + logger.debug(`Returning proposer list of length ${proposers.length}`); res.json({ proposers }); } catch (err) { logger.error(err); diff --git a/test/e2e/protocol/proposer.test.mjs b/test/e2e/protocol/proposer.test.mjs index 57e2cc8f0..94e4bd418 100644 --- a/test/e2e/protocol/proposer.test.mjs +++ b/test/e2e/protocol/proposer.test.mjs @@ -73,14 +73,9 @@ describe('Basic Proposer tests', () => { }); it('should fail to register a proposer twice', async () => { - try { - const res = await testProposers[2].registerProposer(); - expectTransaction(res); - - expect.fail('Submitting the same proposer registration should have caused an EVM revert'); - } catch (err) { - expect(err.message).to.include('Transaction has been reverted by the EVM'); - } + const res = await testProposers[2].registerProposer(); + // eslint-disable-next-line @babel/no-unused-expressions + expect(res).to.be.false; }); it('should unregister a proposer', async () => { From 5321ffb994c3f356d42e1918aaf5e534f6f4d075 Mon Sep 17 00:00:00 2001 From: Westlad Date: Fri, 11 Mar 2022 17:46:11 +0000 Subject: [PATCH 8/8] fix: remove unconfirmed transactions counter --- cli/lib/nf3.mjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/cli/lib/nf3.mjs b/cli/lib/nf3.mjs index 0798bf544..41fca0b1c 100644 --- a/cli/lib/nf3.mjs +++ b/cli/lib/nf3.mjs @@ -222,15 +222,12 @@ class Nf3 { // TODO does this still work if there is a chain reorg or do we have to handle that? return new Promise((resolve, reject) => { logger.debug(`Confirming transaction ${signed.transactionHash}`); - this.notConfirmed++; this.web3.eth .sendSignedTransaction(signed.rawTransaction) .on('confirmation', (number, receipt) => { if (number === 12) { - this.notConfirmed--; logger.debug( `Transaction ${receipt.transactionHash} has been confirmed ${number} times.`, - `Number of unconfirmed transactions is ${this.notConfirmed}`, ); resolve(receipt); }