diff --git a/.github/workflows/check-PRs.yml b/.github/workflows/check-PRs.yml index 112ba5da3..bec3595ec 100644 --- a/.github/workflows/check-PRs.yml +++ b/.github/workflows/check-PRs.yml @@ -132,6 +132,52 @@ jobs: name: ganache-test-logs path: ./ganache-test.log + optimist-sync-test: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@master + - uses: actions/setup-node@v1 + with: + node-version: '14.17.0' + + - name: Start Containers + run: | + ./setup-nightfall + ./start-nightfall -g -d &> optimist-sync-test.log &disown + + - name: Wait for images to be ready + uses: Wandalen/wretry.action@v1.0.11 + with: + command: | + docker wait nightfall_3_deployer_1 + attempt_limit: 100 + attempt_delay: 20000 + + - name: Debug logs - after image builds + if: always() + run: cat optimist-sync-test.log + + - name: Run optimist sync test + run: | + npm ci + docker wait nightfall_3_deployer_1 + npm run test-optimist-sync + + - name: Debug logs - after optimist sync test run + if: always() + run: cat optimist-sync-test.log + + - name: If optimist sync test failed, shutdown the Containers + if: failure() + run: docker-compose -f docker-compose.yml -f docker-compose.ganache.yml down -v + + - name: If optimist sync test failed, upload logs files as artifacts + if: failure() + uses: actions/upload-artifact@master + with: + name: optimist-sync-test-logs + path: ./optimist-sync-test.log + adversary-test: runs-on: ubuntu-20.04 env: @@ -149,7 +195,7 @@ jobs: run: | ./setup-nightfall ./geth-standalone -s - sleep 300 + sleep 300 ./start-nightfall -l -d -a &> adversary-test.log &disown - name: Wait for images to be ready @@ -175,7 +221,7 @@ jobs: - name: If integration test failed, shutdown the Containers if: failure() run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ganache.yml -f docker-compose.adversary.yml down -v + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ganache.yml -f docker-compose.adversary.yml down -v ./geth-standalone -d - name: If integration test failed, upload logs files as artifacts diff --git a/cli/lib/environment.mjs b/cli/lib/environment.mjs index eafa921c2..19a26364c 100644 --- a/cli/lib/environment.mjs +++ b/cli/lib/environment.mjs @@ -11,9 +11,9 @@ const SUPPORTED_ENVIRONMENTS = { ropsten: { name: 'Ropsten', chainId: 3, - clientApiUrl: 'https://client1.testnet.nightfall3.com', - optimistApiUrl: 'https://optimist1.testnet.nightfall3.com', - optimistWsUrl: 'wss://optimist1-ws.testnet.nightfall3.com', + clientApiUrl: 'https://client.testnet.nightfall3.com', + optimistApiUrl: 'https://optimist.testnet.nightfall3.com', + optimistWsUrl: 'wss://optimist-ws.testnet.nightfall3.com', web3WsUrl: 'wss://ropsten1-ws.testnet.nightfall3.com', }, rinkeby: { @@ -35,10 +35,10 @@ const SUPPORTED_ENVIRONMENTS = { docker: { name: 'Docker', chainId: 1337, - clientApiUrl: 'http://client1', - optimistApiUrl: 'http://optimist1', - optimistWsUrl: 'ws://optimist1:8080', - web3WsUrl: 'ws://blockchain1:8546', + clientApiUrl: 'http://client', + optimistApiUrl: 'http://optimist', + optimistWsUrl: 'ws://optimist:8080', + web3WsUrl: 'ws://blockchain:8546', }, }; diff --git a/cli/lib/nf3.mjs b/cli/lib/nf3.mjs index fec0ad183..75e56599c 100644 --- a/cli/lib/nf3.mjs +++ b/cli/lib/nf3.mjs @@ -219,7 +219,7 @@ class Nf3 { data: unsignedTransaction, }); } catch (error) { - logger.warn(`estimateGas failed. Falling back to constant value`); + // logger.warn(`estimateGas failed. Falling back to constant value`); gasLimit = GAS; // backup if estimateGas failed } return Math.ceil(Number(gasLimit) * GAS_MULTIPLIER); // 50% seems a more than reasonable buffer. @@ -232,11 +232,11 @@ class Nf3 { const res = (await axios.get(GAS_ESTIMATE_ENDPOINT)).data.result; proposedGasPrice = Number(res?.ProposeGasPrice) * 10 ** 9; } catch (error) { - logger.warn('Gas Estimation Failed, using previous block gasPrice'); + // logger.warn('Gas Estimation Failed, using previous block gasPrice'); try { proposedGasPrice = Number(await this.web3.eth.getGasPrice()); } catch (err) { - logger.warn('Failed to get previous block gasprice. Falling back to default'); + // logger.warn('Failed to get previous block gasprice. Falling back to default'); proposedGasPrice = GAS_PRICE; } } @@ -259,11 +259,11 @@ class Nf3 { const gasPrice = await this.estimateGasPrice(); // Estimate the gasLimit const gas = await this.estimateGas(contractAddress, unsignedTransaction); - logger.debug( - `Transaction gasPrice was set at ${Math.ceil( - gasPrice / 10 ** 9, - )} GWei, gas limit was set at ${gas}`, - ); + // logger.debug( + // `Transaction gasPrice was set at ${Math.ceil( + // gasPrice / 10 ** 9, + // )} GWei, gas limit was set at ${gas}`, + // ); const tx = { from: this.ethereumAddress, to: contractAddress, @@ -874,7 +874,7 @@ class Nf3 { @async */ async startProposer() { - const blockProposeEmitter = new EventEmitter(); + const proposeEmitter = new EventEmitter(); const connection = new ReconnectingWebSocket(this.optimistWsUrl, [], { WebSocket }); this.websockets.push(connection); // save so we can close it properly later // we can't setup up a ping until the connection is made because the ping function @@ -895,7 +895,7 @@ class Nf3 { }; connection.onmessage = async message => { const msg = JSON.parse(message.data); - const { type, txDataToSign, block, transactions } = msg; + const { type, txDataToSign, block, transactions, data } = msg; logger.debug(`Proposer received websocket message of type ${type}`); if (type === 'block') { proposerQueue.push(async () => { @@ -905,20 +905,21 @@ class Nf3 { this.stateContractAddress, this.BLOCK_STAKE, ); - blockProposeEmitter.emit('receipt', receipt, block, transactions); + proposeEmitter.emit('receipt', receipt, block, transactions); } catch (err) { // block proposed is reverted. Send transactions back to mempool - blockProposeEmitter.emit('error', err, block, transactions); + proposeEmitter.emit('error', err, block, transactions); await axios.get(`${this.optimistBaseUrl}/block/reset-localblock`); } }); } + if (type === 'rollback') proposeEmitter.emit('rollback', data); return null; }; connection.onerror = () => logger.error('Proposer websocket connection error'); connection.onclosed = () => logger.warn('Proposer websocket connection closed'); // add this proposer to the list of peers that can accept direct transfers and withdraws - return blockProposeEmitter; + return proposeEmitter; } /** @@ -937,67 +938,6 @@ class Nf3 { return res.status; } - /** - Returns an emitter, whose 'data' event fires whenever a block is - detected, passing out the transaction needed to propose the block. This - is a lower level method than `Nf3.startProposer` because it does not sign and - send the transaction to the blockchain. If required, `Nf3.submitTransaction` - can be used to do that. - @method - @async - @returns {Promise} A Promise that resolves into an event emitter. - */ - async getNewBlockEmitter() { - const newBlockEmitter = new EventEmitter(); - const connection = new ReconnectingWebSocket(this.optimistWsUrl, [], { WebSocket }); - this.websockets.push(connection); // save so we can close it properly later - connection.onopen = () => { - // setup a ping every 15s - this.intervalIDs.push( - setInterval(() => { - connection._ws.ping(); - // logger.debug('sent websocket ping'); - }, WEBSOCKET_PING_TIME), - ); - // and a listener for the pong - // connection._ws.on('pong', () => logger.debug('websocket received pong')); - logger.debug('Proposer websocket connection opened'); - connection.send('blocks'); - }; - connection.onmessage = async message => { - const msg = JSON.parse(message.data); - const { type, txDataToSign } = msg; - if (type === 'block') { - newBlockEmitter.emit('data', txDataToSign); - } - }; - return newBlockEmitter; - } - - /** - Registers our address as a challenger address with the optimist container. - This is so that the optimist container can tell when a challenge that we have - committed to has appeared on chain. - @method - @async - @return {Promise} A promise that resolves to an axios response. - */ - async registerChallenger() { - return axios.post(`${this.optimistBaseUrl}/challenger/add`, { address: this.ethereumAddress }); - } - - /** - De-registers our address as a challenger address with the optimist container. - @method - @async - @return {Promise} A promise that resolves to an axios response. - */ - async deregisterChallenger() { - return axios.post(`${this.optimistBaseUrl}/challenger/remove`, { - address: this.ethereumAddress, - }); - } - /** Starts a Challenger that listens for challengable blocks and submits challenge transactions to the blockchain to challenge the block. @@ -1013,18 +953,20 @@ class Nf3 { this.intervalIDs.push( setInterval(() => { connection._ws.ping(); - // logger.debug('sent websocket ping'); + // logger.debug('sent challenge websocket ping'); }, WEBSOCKET_PING_TIME), ); // and a listener for the pong - // connection._ws.on('pong', () => logger.debug('websocket received pong')); - logger.debug('Challenger websocket connection opened'); + // connection._ws.on('pong', () => logger.debug('Challenge websocket received pong')); + logger.debug('Challenge websocket connection opened'); connection.send('challenge'); }; connection.onmessage = async message => { const msg = JSON.parse(message.data); - const { type, txDataToSign } = msg; + const { type, txDataToSign, sender } = msg; logger.debug(`Challenger received websocket message of type ${type}`); + // if we're about to challenge, check it's actually our challenge, so as not to waste gas + if (type === 'challenge' && sender !== this.ethereumAddress) return null; if (type === 'commit' || type === 'challenge') { challengerQueue.push(async () => { try { @@ -1038,14 +980,20 @@ class Nf3 { challengeEmitter.emit('error', err, type); } }); + logger.debug(`queued ${type} ${txDataToSign}`); } return null; }; - connection.onerror = () => logger.error('Challenger websocket connection error'); - connection.onclosed = () => logger.warn('Challenger websocket connection closed'); + connection.onerror = () => logger.error('websocket connection error'); + connection.onclosed = () => logger.warn('websocket connection closed'); return challengeEmitter; } + // method to turn challenges off and on. Note, this does not affect the queue + challengeEnable(enable) { + return axios.post(`${this.optimistBaseUrl}/challenger/enable`, { enable }); + } + // eslint-disable-next-line class-methods-use-this pauseQueueChallenger() { return new Promise(resolve => { @@ -1069,43 +1017,6 @@ class Nf3 { challengerQueue.unshift(async () => logger.info(`queue challengerQueue has been unpaused`)); } - /** - Returns an emitter, whose 'data' event fires whenever a challengeable block is - detected, passing out the transaction needed to raise the challenge. This - is a lower level method than `Nf3.startChallenger` because it does not sign and - send the transaction to the blockchain. If required, `Nf3.submitTransaction` - can be used to do that. - @method - @async - @returns {Promise} A Promise that resolves into an event emitter. - */ - async getChallengeEmitter() { - const newChallengeEmitter = new EventEmitter(); - const connection = new ReconnectingWebSocket(this.optimistWsUrl, [], { WebSocket }); - this.websockets.push(connection); // save so we can close it properly later - connection.onopen = () => { - // setup a ping every 15s - this.intervalIDs.push( - setInterval(() => { - connection._ws.ping(); - // logger.debug('sent websocket ping'); - }, WEBSOCKET_PING_TIME), - ); - // and a listener for the pong - // connection._ws.on('pong', () => logger.debug('websocket received pong')); - logger.debug('Challenger websocket connection opened'); - connection.send('challenge'); - }; - connection.onmessage = async message => { - const msg = JSON.parse(message.data); - const { type, txDataToSign } = msg; - if (type === 'challenge') { - newChallengeEmitter.emit('data', txDataToSign); - } - }; - return newChallengeEmitter; - } - /** Returns the balance of tokens held in layer 2 @method diff --git a/common-files/classes/transaction.mjs b/common-files/classes/transaction.mjs index d67e07436..ecb8956eb 100644 --- a/common-files/classes/transaction.mjs +++ b/common-files/classes/transaction.mjs @@ -5,7 +5,7 @@ An optimistic Transaction class */ import gen from 'general-number'; -import Web3 from '../utils/web3.mjs'; +import Web3 from 'web3'; import { compressProof } from '../utils/curve-maths/curves.mjs'; import constants from '../constants/index.mjs'; @@ -23,7 +23,7 @@ const arrayEquality = (as, bs) => { // function to compute the keccak hash of a transaction function keccak(preimage) { - const web3 = Web3.connection(); + const web3 = new Web3(); const { value, fee, diff --git a/common-files/utils/event-queue.mjs b/common-files/utils/event-queue.mjs index d0c2f9ccd..a490ce06f 100644 --- a/common-files/utils/event-queue.mjs +++ b/common-files/utils/event-queue.mjs @@ -52,6 +52,15 @@ async function enqueueEvent(callback, priority, args) { }); } +/** +This function immediately and unceremoniously empties the queue. It should probably +be used with extreme care on a running queue because the exact state on emptying, and thus +the last job that ran, will be unclear. It will cause the end event to fire. +*/ +function emptyQueue(priority) { + return queues[priority].end(); +} + /** These functions pause the queue once the current process at the head of the queue has completed. It will then wait until we tell it to start again via unpause. @@ -119,7 +128,7 @@ function waitForConfirmation(eventObject) { } async function dequeueEvent(priority) { - queues[priority].shift(); + return queues[priority].shift(); } async function queueManager(eventObject, eventArgs) { @@ -169,4 +178,5 @@ export { waitForConfirmation, pauseQueue, unpauseQueue, + emptyQueue, }; diff --git a/config/default.js b/config/default.js index b47ff3e59..9284b9656 100644 --- a/config/default.js +++ b/config/default.js @@ -75,7 +75,7 @@ module.exports = { // Keep keepalive interval small so that socket doesn't die keepaliveInterval: 1500, }, - timeout: 3600000, + timeout: 0, reconnect: { auto: true, delay: 5000, // ms @@ -136,9 +136,9 @@ module.exports = { ropsten: { name: 'Ropsten', chainId: 3, - clientApiUrl: 'https://client1.testnet.nightfall3.com', - optimistApiUrl: 'https://optimist1.testnet.nightfall3.com', - optimistWsUrl: 'wss://optimist1-ws.testnet.nightfall3.com', + clientApiUrl: 'https://client.testnet.nightfall3.com', + optimistApiUrl: 'https://optimist.testnet.nightfall3.com', + optimistWsUrl: 'wss://optimist-ws.testnet.nightfall3.com', web3WsUrl: `${process.env.ROPSTEN_NODE}`, }, rinkeby: { @@ -281,7 +281,7 @@ module.exports = { process.env.BOOT_CHALLENGER_ADDRESS || '0xfCb059A4dB5B961d3e48706fAC91a55Bad0035C9', }, tokens: { - blockchain1: [ + blockchain: [ { name: 'ERC20Mock', address: '0x9b7bD670D87C3Dd5C808ba627c75ba7E88aD066f', diff --git a/docker-compose.client.dev.yml b/docker-compose.client.dev.yml index 91cf7d234..49093663b 100644 --- a/docker-compose.client.dev.yml +++ b/docker-compose.client.dev.yml @@ -2,7 +2,7 @@ version: '3.5' # Use this script for running up nightfall_3 client in 'developer' mode with local # bindings. See the readme for more information. services: - client1: + client: build: dockerfile: client.Dockerfile context: . diff --git a/docker-compose.client.yml b/docker-compose.client.yml index 4f532cf82..ab40e6f55 100644 --- a/docker-compose.client.yml +++ b/docker-compose.client.yml @@ -2,14 +2,14 @@ version: '3.5' # Use this script for running up nightfall_3 client in 'developer' mode with local # bindings. See the readme for more information. services: - client1: + client: image: ghcr.io/eyblockchain/nightfall3-client:latest volumes: - type: volume source: build target: /app/build - type: volume - source: mongodb1 + source: mongodb target: /app/mongodb networks: - nightfall_network @@ -18,13 +18,13 @@ services: - 8080:80 depends_on: - worker - - rabbitmq1 + - rabbitmq environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin LOG_LEVEL: debug ZOKRATES_WORKER_HOST: worker - RABBITMQ_HOST: amqp://rabbitmq1 + RABBITMQ_HOST: amqp://rabbitmq RABBITMQ_PORT: 5672 ENABLE_QUEUE: 1 USE_STUBS: 'false' # make sure this flag is the same as in deployer service @@ -35,7 +35,7 @@ services: CONTRACT_FILES_URL: ${CONTRACT_FILES_URL} command: ['npm', 'run', 'dev'] - rabbitmq1: + rabbitmq: image: rabbitmq ports: - '15674:15674' @@ -58,7 +58,7 @@ services: CIRCUIT_FILES_URL: ${CIRCUIT_FILES_URL} volumes: - mongodb1: + mongodb: proving_files: build: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 7f62a2fe7..37ec15d29 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -20,22 +20,7 @@ services: source: ./config/default.js target: /app/admin/config/default.js - client1: - build: - dockerfile: client.Dockerfile - context: . - volumes: - - type: bind - source: ./nightfall-client/src - target: /app/src - - type: bind - source: ./common-files - target: /common-files - - type: bind - source: ./config/default.js - target: /app/config/default.js - - client2: + client: build: dockerfile: client.Dockerfile context: . @@ -81,7 +66,7 @@ services: source: ./nightfall-deployer/entrypoint.sh target: /app/entrypoint.sh - optimist1: + optimist: build: dockerfile: optimist.Dockerfile context: . @@ -97,21 +82,6 @@ services: source: ./config/default.js target: /app/config/default.js - optimist2: - build: - dockerfile: optimist.Dockerfile - context: . - volumes: - - type: bind - source: ./nightfall-optimist/src - target: /app/src - - type: bind - source: ./common-files - target: /common-files - - type: bind - source: ./config/default.js - target: /app/config/default.js - worker: # image: 3800decac71d build: @@ -134,4 +104,3 @@ services: # to use with postman and etc - 8091:80 entrypoint: [ "npm", "run", "start" ] - \ No newline at end of file diff --git a/docker-compose.ganache.yml b/docker-compose.ganache.yml index f81df399b..6968e5540 100644 --- a/docker-compose.ganache.yml +++ b/docker-compose.ganache.yml @@ -3,7 +3,7 @@ version: '3.5' # bindings and using a Ganache private blockchain. See the readme for more information. # It acts as an override file for docker-compose.yml services: - blockchain1: + blockchain: image: trufflesuite/ganache-cli:v6.12.1 ports: - 8546:8546 diff --git a/docker-compose.host.docker.internal.yml b/docker-compose.host.docker.internal.yml index ca1a0e554..4aa856f5a 100644 --- a/docker-compose.host.docker.internal.yml +++ b/docker-compose.host.docker.internal.yml @@ -10,15 +10,8 @@ services: environment: BLOCKCHAIN_WS_HOST: host.docker.internal ETH_NETWORK: development - - client1: - extra_hosts: - - 'host.docker.internal:host-gateway' - environment: - BLOCKCHAIN_WS_HOST: host.docker.internal - AUTOSTART_RETRIES: 100 - client2: + client: extra_hosts: - 'host.docker.internal:host-gateway' environment: @@ -35,14 +28,7 @@ services: ETH_NETWORK: development BLOCKCHAIN_WS_HOST: host.docker.internal - optimist1: - extra_hosts: - - 'host.docker.internal:host-gateway' - environment: - BLOCKCHAIN_WS_HOST: host.docker.internal - AUTOSTART_RETRIES: 100 - - optimist2: + optimist: extra_hosts: - 'host.docker.internal:host-gateway' environment: diff --git a/docker-compose.images.example.yml b/docker-compose.images.example.yml index 9904e1c28..8a2175594 100644 --- a/docker-compose.images.example.yml +++ b/docker-compose.images.example.yml @@ -2,14 +2,14 @@ version: '3.5' # Use this script for running up nightfall_3 with images # If you replace the main docker-compose with this file, it should work! services: - client1: + client: image: ghcr.io/eyblockchain/nightfall3-client:latest volumes: - type: volume source: build target: /app/build - type: volume - source: mongodb1 + source: mongodb target: /app/mongodb networks: - nightfall_network @@ -19,48 +19,18 @@ services: depends_on: - deployer - worker - - rabbitmq1 + - rabbitmq environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin LOG_LEVEL: debug - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 ZOKRATES_WORKER_HOST: worker - RABBITMQ_HOST: amqp://rabbitmq1 + RABBITMQ_HOST: amqp://rabbitmq RABBITMQ_PORT: 5672 ENABLE_QUEUE: 1 - OPTIMIST_HOST: optimist1 - OPTIMIST_PORT: 80 - USE_STUBS: 'false' # make sure this flag is the same as in deployer service - command: ['npm', 'run', 'dev'] - - client2: - image: ghcr.io/eyblockchain/nightfall3-client:latest - volumes: - - type: volume - source: build - target: /app/build - networks: - - nightfall_network - ports: - - 27018:27017 - - 8084:80 - depends_on: - - deployer - - worker - - rabbitmq2 - environment: - MONGO_INITDB_ROOT_USERNAME: admin - MONGO_INITDB_ROOT_PASSWORD: admin - LOG_LEVEL: error - BLOCKCHAIN_WS_HOST: blockchain1 - BLOCKCHAIN_PORT: 8546 - ZOKRATES_WORKER_HOST: worker - RABBITMQ_HOST: amqp://rabbitmq2 - RABBITMQ_PORT: 5672 - ENABLE_QUEUE: 1 - OPTIMIST_HOST: optimist2 + OPTIMIST_HOST: optimist OPTIMIST_PORT: 80 USE_STUBS: 'false' # make sure this flag is the same as in deployer service command: ['npm', 'run', 'dev'] @@ -81,13 +51,13 @@ services: LOG_LEVEL: debug # ETH_NETWORK sets the network selected by Truffle from truffle-config.js # startup routines will wait for a blockchain client to be reachable on this network - ETH_NETWORK: blockchain1 - BLOCKCHAIN_WS_HOST: blockchain1 + ETH_NETWORK: blockchain + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 ZOKRATES_WORKER_HOST: worker USE_STUBS: 'false' - optimist1: + optimist: image: ghcr.io/eyblockchain/nightfall3-optimist:latest depends_on: - deployer @@ -103,7 +73,7 @@ services: target: /app/build/ environment: WEBSOCKET_PORT: 8080 - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 HASH_TYPE: poseidon LOG_LEVEL: debug @@ -111,32 +81,7 @@ services: TRANSACTIONS_PER_BLOCK: ${TRANSACTIONS_PER_BLOCK:-2} command: ['npm', 'run', 'dev'] - optimist2: - image: ghcr.io/eyblockchain/nightfall3-optimist:latest - depends_on: - - deployer - networks: - - nightfall_network - ports: - - 8085:80 - # websocket port for Optimist is on localhost:8086 - - 8086:8080 - volumes: - - type: volume - source: build - target: /app/build/ - # - type: bind - environment: - WEBSOCKET_PORT: 8080 - BLOCKCHAIN_WS_HOST: blockchain1 - BLOCKCHAIN_PORT: 8546 - HASH_TYPE: poseidon - LOG_LEVEL: error - IS_CHALLENGER: 'true' - TRANSACTIONS_PER_BLOCK: ${TRANSACTIONS_PER_BLOCK:-2} - command: ['npm', 'run', 'dev'] - - rabbitmq1: + rabbitmq: image: rabbitmq ports: - '15674:15674' @@ -144,14 +89,6 @@ services: networks: - nightfall_network - rabbitmq2: - image: rabbitmq - ports: - - '15675:15674' - - '5673:5672' - networks: - - nightfall_network - worker: image: ghcr.io/eyblockchain/nightfall3-worker volumes: @@ -166,8 +103,7 @@ services: LOG_LEVEL: info volumes: - mongodb1: - mongodb2: + mongodb: proving_files: build: diff --git a/docker-compose.ropsten.yml b/docker-compose.ropsten.yml index 73c6473a8..5bcac99ad 100644 --- a/docker-compose.ropsten.yml +++ b/docker-compose.ropsten.yml @@ -1,12 +1,6 @@ version: '3.5' services: - client1: - environment: - BLOCKCHAIN_URL: $ROPSTEN_NODE - USE_EXTERNAL_NODE: 'true' - AUTOSTART_RETRIES: 600 - - client2: + client: environment: BLOCKCHAIN_URL: $ROPSTEN_NODE USE_EXTERNAL_NODE: 'true' @@ -22,13 +16,7 @@ services: FROM_ADDRESS: ${FROM_ADDRESS:-0x29100E7E3dA6654BF63d9E7804ADe518aCc5AaA5} ETH_PRIVATE_KEY: $ETH_PRIVATE_KEY - optimist1: - environment: - BLOCKCHAIN_URL: $ROPSTEN_NODE - USE_EXTERNAL_NODE: 'true' - AUTOSTART_RETRIES: 600 - - optimist2: + optimist: environment: BLOCKCHAIN_URL: $ROPSTEN_NODE USE_EXTERNAL_NODE: 'true' diff --git a/docker-compose.standalone.geth.yml b/docker-compose.standalone.geth.yml index 732d1ba0d..d75f2f77f 100644 --- a/docker-compose.standalone.geth.yml +++ b/docker-compose.standalone.geth.yml @@ -14,7 +14,7 @@ services: ipv4_address: 172.16.239.10 command: bootnode --nodekey=setup/node.key - blockchain1: + blockchain: image: ethereum/client-go:stable ports: - 8546:8546 diff --git a/docker-compose.stubs.yml b/docker-compose.stubs.yml index 013523eaf..1665955c6 100644 --- a/docker-compose.stubs.yml +++ b/docker-compose.stubs.yml @@ -1,11 +1,7 @@ version: '3.5' # Use this script for making nightfall_3 use stubs. services: - client1: - environment: - USE_STUBS: 'true' # make sure this flag is the same as in deployer service - - client2: + client: environment: USE_STUBS: 'true' # make sure this flag is the same as in deployer service diff --git a/docker-compose.yml b/docker-compose.yml index 96f8a3e12..22ba48973 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,19 +11,19 @@ services: source: build target: /app/build/ environment: - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 LOG_LEVEL: error - ETH_NETWORK: blockchain1 + ETH_NETWORK: blockchain - client1: + client: image: ghcr.io/eyblockchain/nightfall3-client:latest volumes: - type: volume source: build target: /app/build - type: volume - source: mongodb1 + source: mongodb target: /app/mongodb networks: - nightfall_network @@ -33,51 +33,18 @@ services: depends_on: - deployer - worker - - rabbitmq1 + - rabbitmq environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin LOG_LEVEL: debug - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 ZOKRATES_WORKER_HOST: worker - RABBITMQ_HOST: amqp://rabbitmq1 + RABBITMQ_HOST: amqp://rabbitmq RABBITMQ_PORT: 5672 ENABLE_QUEUE: 1 - OPTIMIST_HOST: optimist1 - OPTIMIST_PORT: 80 - USE_STUBS: 'false' # make sure this flag is the same as in deployer service - command: ['npm', 'run', 'dev'] - - client2: - image: ghcr.io/eyblockchain/nightfall3-client:latest - volumes: - - type: volume - source: build - target: /app/build - - type: volume - source: mongodb2 - target: /app/mongodb - networks: - - nightfall_network - ports: - - 27018:27017 - - 8084:80 - depends_on: - - deployer - - worker - - rabbitmq2 - environment: - MONGO_INITDB_ROOT_USERNAME: admin - MONGO_INITDB_ROOT_PASSWORD: admin - LOG_LEVEL: error - BLOCKCHAIN_WS_HOST: blockchain1 - BLOCKCHAIN_PORT: 8546 - ZOKRATES_WORKER_HOST: worker - RABBITMQ_HOST: amqp://rabbitmq2 - RABBITMQ_PORT: 5672 - ENABLE_QUEUE: 1 - OPTIMIST_HOST: optimist2 + OPTIMIST_HOST: optimist OPTIMIST_PORT: 80 USE_STUBS: 'false' # make sure this flag is the same as in deployer service command: ['npm', 'run', 'dev'] @@ -96,8 +63,8 @@ services: LOG_LEVEL: debug # ETH_NETWORK sets the network selected by Truffle from truffle-config.js # startup routines will wait for a blockchain client to be reachable on this network - ETH_NETWORK: blockchain1 - BLOCKCHAIN_WS_HOST: blockchain1 + ETH_NETWORK: blockchain + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 ZOKRATES_WORKER_HOST: worker USE_STUBS: 'false' @@ -114,10 +81,10 @@ services: source: proving_files target: /app/public/ - optimist1: + optimist: image: ghcr.io/eyblockchain/nightfall3-optimist:latest - depends_on: - - deployer + # depends_on: + # - deployer networks: - nightfall_network ports: @@ -130,7 +97,7 @@ services: target: /app/build/ environment: WEBSOCKET_PORT: 8080 - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 HASH_TYPE: poseidon LOG_LEVEL: debug @@ -138,31 +105,7 @@ services: TRANSACTIONS_PER_BLOCK: ${TRANSACTIONS_PER_BLOCK:-2} command: ['npm', 'run', 'dev'] - optimist2: - image: ghcr.io/eyblockchain/nightfall3-optimist:latest - depends_on: - - deployer - networks: - - nightfall_network - ports: - - 8085:80 - # websocket port for Optimist is on localhost:8086 - - 8086:8080 - volumes: - - type: volume - source: build - target: /app/build/ - environment: - WEBSOCKET_PORT: 8080 - BLOCKCHAIN_WS_HOST: blockchain1 - BLOCKCHAIN_PORT: 8546 - HASH_TYPE: poseidon - LOG_LEVEL: error - IS_CHALLENGER: 'false' - TRANSACTIONS_PER_BLOCK: ${TRANSACTIONS_PER_BLOCK:-2} - command: ['npm', 'run', 'dev'] - - rabbitmq1: + rabbitmq: image: rabbitmq ports: - '15674:15674' @@ -170,14 +113,6 @@ services: networks: - nightfall_network - rabbitmq2: - image: rabbitmq - ports: - - '15675:15674' - - '5673:5672' - networks: - - nightfall_network - worker: # image: 3800decac71d image: ghcr.io/eyblockchain/nightfall3-worker:latest @@ -195,8 +130,7 @@ services: MPC: ${MPC} volumes: - mongodb1: null - mongodb2: null + mongodb: null proving_files: null build: null networks: diff --git a/nightfall-client/dropAll.sh b/nightfall-client/dropAll.sh index a83234b79..5dfc109dc 100755 --- a/nightfall-client/dropAll.sh +++ b/nightfall-client/dropAll.sh @@ -1,6 +1,6 @@ #! /bin/bash -docker exec -t nightfall_3_optimist1_1 bash -c \ +docker exec -t nightfall_3_optimist_1 bash -c \ 'mongo --quiet --eval "db = db.getSiblingDB(\"optimist_data\"); db.blocks.drop( { writeConcern: { w: \"majority\" } } ); db.nullifiers.drop( { writeConcern: { w: \"majority\" } } ); diff --git a/nightfall-client/start-client b/nightfall-client/start-client index d35920d7a..9945d19a9 100755 --- a/nightfall-client/start-client +++ b/nightfall-client/start-client @@ -37,9 +37,9 @@ fi if [ -n "$REMOVE_VOLUMES" ]; then # if-else block checks - volume exist and then removes it. - if [[ $(echo $VOLUME_LIST | grep nightfall_3_mongodb1) ]]; then + if [[ $(echo $VOLUME_LIST | grep nightfall_3_mongodb) ]]; then echo -n 'Removing ' - docker volume rm nightfall_3_mongodb1 + docker volume rm nightfall_3_mongodb fi if [[ $(echo $VOLUME_LIST | grep nightfall_3_build) ]]; then diff --git a/nightfall-deployer/truffle-config.js b/nightfall-deployer/truffle-config.js index 7bf292c35..e8f3c5951 100644 --- a/nightfall-deployer/truffle-config.js +++ b/nightfall-deployer/truffle-config.js @@ -45,10 +45,10 @@ module.exports = { // options below to some value. // - blockchain1: { + blockchain: { // host: 'blockchain', // Localhost (default: none) // port: 8546, // Standard Ethereum port (default: none) - url: 'ws://blockchain1:8546', + url: 'ws://blockchain:8546', network_id: 1337, // Any network (default: none) gas: 1000000000, websockets: true, diff --git a/nightfall-optimist/package-lock.json b/nightfall-optimist/package-lock.json index 8068401f8..e8d863890 100644 --- a/nightfall-optimist/package-lock.json +++ b/nightfall-optimist/package-lock.json @@ -1460,6 +1460,7 @@ }, "big-integer": { "version": "1.6.51", + "resolved": false, "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" }, "bignumber.js": { @@ -4022,7 +4023,7 @@ "buffer": "^5.0.5", "eth-lib": "^0.1.26", "fs-extra": "^4.0.2", - "got": "11.8.5", + "got": "^7.1.0", "mime-types": "^2.1.16", "mkdirp-promise": "^5.0.1", "mock-fs": "^4.1.0", @@ -5049,11 +5050,6 @@ "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, - "duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -5833,24 +5829,11 @@ "get-intrinsic": "^1.1.1" } }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" - }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, "has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", @@ -6130,16 +6113,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==" - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==" - }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -6149,11 +6122,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" - }, "is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", @@ -6224,15 +6192,6 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, "joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -6889,11 +6848,6 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6912,14 +6866,6 @@ "p-limit": "^3.0.2" } }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA==", - "requires": { - "p-finally": "^1.0.0" - } - }, "parse-asn1": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", @@ -7060,11 +7006,6 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", "integrity": "sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==" }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==" - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -7681,7 +7622,7 @@ "buffer": "^5.0.5", "eth-lib": "^0.1.26", "fs-extra": "^4.0.2", - "got": "11.8.5", + "got": "^7.1.0", "mime-types": "^2.1.16", "mkdirp-promise": "^5.0.1", "mock-fs": "^4.1.0", @@ -7960,24 +7901,11 @@ "punycode": "^2.1.0" } }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", - "requires": { - "prepend-http": "^1.0.1" - } - }, "url-set-query": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", "integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==" }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==" - }, "utf-8-validate": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", diff --git a/nightfall-optimist/src/classes/block.mjs b/nightfall-optimist/src/classes/block.mjs index 35f1467e0..303389f9a 100644 --- a/nightfall-optimist/src/classes/block.mjs +++ b/nightfall-optimist/src/classes/block.mjs @@ -3,12 +3,12 @@ An optimistic layer 2 Block class */ import config from 'config'; import Timber from 'common-files/classes/timber.mjs'; -import Web3 from 'common-files/utils/web3.mjs'; import constants from 'common-files/constants/index.mjs'; import { getLatestBlockInfo, getTreeByBlockNumberL2 } from '../services/database.mjs'; +import { buildBlockSolidityStruct, calcBlockHash } from '../services/block-utils.mjs'; const { TIMBER_HEIGHT, TXHASH_TREE_HEIGHT, HASH_TYPE, TXHASH_TREE_HASH_TYPE } = config; -const { ZERO, BLOCK_TYPES } = constants; +const { ZERO } = constants; /** This Block class does not have the Block components that are computed on-chain. @@ -173,34 +173,13 @@ class Block { } static calcHash(block) { - const web3 = Web3.connection(); - const { proposer, root, leafCount, blockNumberL2, previousBlockHash, transactionHashesRoot } = - block; - const blockArray = [ - leafCount, - proposer, - root, - blockNumberL2, - previousBlockHash, - transactionHashesRoot, - ]; - const encoded = web3.eth.abi.encodeParameters([BLOCK_TYPES], [blockArray]); - return web3.utils.soliditySha3({ t: 'bytes', v: encoded }); + return calcBlockHash(block); } // remove properties that do not get sent to the blockchain returning // a new object (don't mutate the original) static buildSolidityStruct(block) { - const { proposer, root, leafCount, blockNumberL2, previousBlockHash, transactionHashesRoot } = - block; - return { - leafCount: Number(leafCount), - proposer, - root, - blockNumberL2: Number(blockNumberL2), - previousBlockHash, - transactionHashesRoot, - }; + return buildBlockSolidityStruct(block); } } diff --git a/nightfall-optimist/src/event-handlers/block-proposed.mjs b/nightfall-optimist/src/event-handlers/block-proposed.mjs index e8c9f1ef9..f846f42e0 100644 --- a/nightfall-optimist/src/event-handlers/block-proposed.mjs +++ b/nightfall-optimist/src/event-handlers/block-proposed.mjs @@ -7,7 +7,7 @@ import { enqueueEvent } from 'common-files/utils/event-queue.mjs'; import constants from 'common-files/constants/index.mjs'; import { checkBlock } from '../services/check-block.mjs'; import BlockError from '../classes/block-error.mjs'; -import { createChallenge } from '../services/challenges.mjs'; +import { createChallenge, commitToChallenge } from '../services/challenges.mjs'; import { removeTransactionsFromMemPool, removeCommitmentsFromMemPool, @@ -129,8 +129,14 @@ async function blockProposedEventHandler(data) { transactionHashL1, ...block, }); - await enqueueEvent(() => logger.info('Stop Until Rollback'), 2); - await createChallenge(block, transactions, err); + const txDataToSign = await createChallenge(block, transactions, err); + // push the challenge into the stop queue. This will stop blocks being + // made until the challenge has run and a rollback has happened. We could + // push anything into the queue and that would work but it's useful to + // have the actual challenge to support syncing + logger.debug('enqueuing event to stop queue'); + await enqueueEvent(commitToChallenge, 2, txDataToSign); + await commitToChallenge(txDataToSign); } else { logger.error(err.stack); throw new Error(err); diff --git a/nightfall-optimist/src/event-handlers/challenge-commit.mjs b/nightfall-optimist/src/event-handlers/challenge-commit.mjs index 6d733accf..a8bef5a1d 100644 --- a/nightfall-optimist/src/event-handlers/challenge-commit.mjs +++ b/nightfall-optimist/src/event-handlers/challenge-commit.mjs @@ -1,22 +1,24 @@ import logger from 'common-files/utils/logger.mjs'; import { revealChallenge } from '../services/challenges.mjs'; -import { getCommit, isChallengerAddressMine } from '../services/database.mjs'; +import { getCommit } from '../services/database.mjs'; async function committedToChallengeEventHandler(data) { const { commitHash, sender } = data.returnValues; logger.debug( `Received commmitted to challenge event, with hash ${commitHash} and sender ${sender}`, ); - const challengerIsMe = await isChallengerAddressMine(sender); - if (!challengerIsMe) return; // it's not us - nothing to do - logger.info(`Our challenge commitment has been mined, sending reveal`); + logger.info('A challenge commitment has been mined'); const { txDataToSign, retrieved } = await getCommit(commitHash); - if (txDataToSign === null) throw new Error('Commit hash not found in database'); + // We may not find the commitment. In this case, it's probably not ours so we take no action + if (txDataToSign === null) { + logger.debug('Commit hash not found in database'); + return; + } // if retrieved is true, then we've looked up this commit before and we assume // we have already revealed it - thus we don't reveal it again because that would // just waste gas. This could happen if a chain reorg were to re-emit the // CommittedToChallenge event. - if (!retrieved) revealChallenge(txDataToSign); + if (!retrieved) revealChallenge(txDataToSign, sender); } export default committedToChallengeEventHandler; diff --git a/nightfall-optimist/src/event-handlers/rollback.mjs b/nightfall-optimist/src/event-handlers/rollback.mjs index 5b01a6852..82c420914 100644 --- a/nightfall-optimist/src/event-handlers/rollback.mjs +++ b/nightfall-optimist/src/event-handlers/rollback.mjs @@ -20,6 +20,7 @@ import { } from '../services/check-block.mjs'; import Block from '../classes/block.mjs'; import checkTransaction from '../services/transaction-checker.mjs'; +import { signalRollbackCompleted } from '../services/block-assembler.mjs'; async function rollbackEventHandler(data) { const { blockNumberL2 } = data.returnValues; @@ -82,7 +83,7 @@ async function rollbackEventHandler(data) { await dequeueEvent(2); // Remove an event from the stopQueue. // A Rollback triggers a NewCurrentProposer event which shoudl trigger queue[0].end() // But to be safe we enqueue a helper event to guarantee queue[0].end() runs. - await enqueueEvent(() => logger.info(`Rollback Completed`), 0); + await enqueueEvent(() => signalRollbackCompleted(), 0); } export default rollbackEventHandler; diff --git a/nightfall-optimist/src/index.mjs b/nightfall-optimist/src/index.mjs index 84d9fabfc..b5414f780 100644 --- a/nightfall-optimist/src/index.mjs +++ b/nightfall-optimist/src/index.mjs @@ -35,8 +35,9 @@ 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. + // logger.debug(`proposer is, ${proposer.address}, stop queue length is ${queues[2].length}`); if (proposer.isMe && queues[2].length === 0) { - // logger.info('Queue has emptied. Queueing block assembler.'); + // logger.debug('Queue has emptied. Queueing block assembler.'); return enqueueEvent(conditionalMakeBlock, 0, proposer); } // eslint-disable-next-line no-void, no-useless-return diff --git a/nightfall-optimist/src/routes/challenger.mjs b/nightfall-optimist/src/routes/challenger.mjs index 73e73935c..802161757 100644 --- a/nightfall-optimist/src/routes/challenger.mjs +++ b/nightfall-optimist/src/routes/challenger.mjs @@ -3,29 +3,21 @@ Routes for setting and removing valid challenger addresses. */ import express from 'express'; import logger from 'common-files/utils/logger.mjs'; -import { addChallengerAddress, removeChallengerAddress } from '../services/database.mjs'; +import { emptyQueue } from 'common-files/utils/event-queue.mjs'; +import { startMakingChallenges, stopMakingChallenges } from '../services/challenges.mjs'; const router = express.Router(); -router.post('/add', async (req, res, next) => { - logger.debug('add endpoint received POST'); +router.post('/enable', async (req, res, next) => { + logger.debug('challenge endpoint received POST'); try { - const { address } = req.body; - const result = await addChallengerAddress(address); + const { enable } = req.body; + const result = + enable === true ? (emptyQueue(2), startMakingChallenges()) : stopMakingChallenges(); res.json(result); } catch (err) { next(err); } }); -router.post('/remove', async (req, res, next) => { - logger.debug('remove endpoint received post'); - try { - const { address } = req.body; - res.json(await removeChallengerAddress(address)); - } catch (err) { - next(err); - } -}); - export default router; diff --git a/nightfall-optimist/src/services/block-assembler.mjs b/nightfall-optimist/src/services/block-assembler.mjs index d89eb06a7..f0d73103e 100644 --- a/nightfall-optimist/src/services/block-assembler.mjs +++ b/nightfall-optimist/src/services/block-assembler.mjs @@ -36,6 +36,27 @@ export function setMakeNow(_makeNow = true) { makeNow = _makeNow; } +/** +Function to indicate to a listening proposer that a rollback has been completed. This +is of little use at the moment but will enable the proposer to take actions such as +checking they haven't been removed. This function may be a little out of place here but +we need to use the proposer's websocket! +*/ +export async function signalRollbackCompleted(data) { + // check that the websocket exists (it should) and its readyState is OPEN + // before sending. If not wait until the challenger reconnects + let tryCount = 0; + while (!ws || ws.readyState !== WebSocket.OPEN) { + await new Promise(resolve => setTimeout(resolve, 3000)); // eslint-disable-line no-await-in-loop + logger.warn( + `Websocket to proposer is closed for rollback complete. Waiting for challenger to reconnect`, + ); + if (tryCount++ > 100) throw new Error(`Websocket to proposer has failed`); + } + logger.debug('Rollback completed'); + ws.send(JSON.stringify({ type: 'rollback', data })); +} + async function makeBlock(proposer, number = TRANSACTIONS_PER_BLOCK) { logger.debug('Block Assembler - about to make a new block'); // we retrieve un-processed transactions from our local database, relying on diff --git a/nightfall-optimist/src/services/block-utils.mjs b/nightfall-optimist/src/services/block-utils.mjs new file mode 100644 index 000000000..dd9830b27 --- /dev/null +++ b/nightfall-optimist/src/services/block-utils.mjs @@ -0,0 +1,39 @@ +import constants from 'common-files/constants/index.mjs'; +import Web3 from 'web3'; + +// These functions are called by static methods in the Block class but are sometimes needed when the rest +// of the block object isn't. They can thus be called directly when instantiating the Block class +// would be problematic because of its reliance on the Optimist database. + +const { BLOCK_TYPES } = constants; + +export function calcBlockHash(block) { + const web3 = new Web3(); + const { proposer, root, leafCount, blockNumberL2, previousBlockHash, transactionHashesRoot } = + block; + const blockArray = [ + leafCount, + proposer, + root, + blockNumberL2, + previousBlockHash, + transactionHashesRoot, + ]; + const encoded = web3.eth.abi.encodeParameters([BLOCK_TYPES], [blockArray]); + return web3.utils.soliditySha3({ t: 'bytes', v: encoded }); +} + +// remove properties that do not get sent to the blockchain returning +// a new object (don't mutate the original) +export function buildBlockSolidityStruct(block) { + const { proposer, root, leafCount, blockNumberL2, previousBlockHash, transactionHashesRoot } = + block; + return { + leafCount: Number(leafCount), + proposer, + root, + blockNumberL2: Number(blockNumberL2), + previousBlockHash, + transactionHashesRoot, + }; +} diff --git a/nightfall-optimist/src/services/challenges.mjs b/nightfall-optimist/src/services/challenges.mjs index 5a683bde4..429137726 100644 --- a/nightfall-optimist/src/services/challenges.mjs +++ b/nightfall-optimist/src/services/challenges.mjs @@ -33,7 +33,11 @@ export function stopMakingChallenges() { makeChallenges = false; } -async function commitToChallenge(txDataToSign) { +export async function commitToChallenge(txDataToSign) { + if (!makeChallenges) { + logger.debug('makeChallenges is off, no challenge commitment was sent'); + return; + } const web3 = Web3.connection(); const commitHash = web3.utils.soliditySha3({ t: 'bytes', v: txDataToSign }); const challengeContractInstance = await getContractInstance(CHALLENGES_CONTRACT_NAME); @@ -61,8 +65,8 @@ async function commitToChallenge(txDataToSign) { ); } -export async function revealChallenge(txDataToSign) { - logger.debug('raw challenge transaction has been sent to be signed and submitted'); +export async function revealChallenge(txDataToSign, sender) { + logger.debug('Revealing challenge'); // check that the websocket exists (it should) and its readyState is OPEN // before sending commit. If not wait until the challenger reconnects let tryCount = 0; @@ -73,181 +77,175 @@ export async function revealChallenge(txDataToSign) { ); if (tryCount++ > 100) throw new Error(`Websocket to $challenger has failed`); } - ws.send(JSON.stringify({ type: 'challenge', txDataToSign })); + ws.send(JSON.stringify({ type: 'challenge', txDataToSign, sender })); } export async function createChallenge(block, transactions, err) { let txDataToSign; - if (makeChallenges) { - const challengeContractInstance = await getContractInstance(CHALLENGES_CONTRACT_NAME); - const salt = (await rand(32)).hex(32); - switch (err.code) { - // challenge incorrect leaf count - case 0: { - const priorBlockL2 = await getBlockByBlockNumberL2(block.blockNumberL2 - 1); - const priorBlockTransactions = await getTransactionsByTransactionHashes( - priorBlockL2.transactionHashes, - ); - txDataToSign = await challengeContractInstance.methods - .challengeLeafCountCorrect( - Block.buildSolidityStruct(priorBlockL2), // the block immediately prior to this one - priorBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), // the transactions in the prior block - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - salt, - ) - .encodeABI(); - break; - } - // Challenge wrong root - case 1: { - logger.debug('Challenging incorrect root'); - // Getting prior block for the current block - const priorBlock = await getBlockByBlockNumberL2(Number(block.blockNumberL2) - 1); - if (priorBlock === null) - throw new Error( - `Could not find prior block with block number ${Number(block.blockNumberL2) - 1}`, - ); - // Retrieve last transaction from prior block using its transaction hash. - // Note that not all transactions in a block will have commitments. Loop until one is found - const priorBlockTransactions = await getTransactionsByTransactionHashes( - priorBlock.transactionHashes, + const challengeContractInstance = await getContractInstance(CHALLENGES_CONTRACT_NAME); + const salt = (await rand(32)).hex(32); + switch (err.code) { + // challenge incorrect leaf count + case 0: { + const priorBlockL2 = await getBlockByBlockNumberL2(block.blockNumberL2 - 1); + const priorBlockTransactions = await getTransactionsByTransactionHashes( + priorBlockL2.transactionHashes, + ); + txDataToSign = await challengeContractInstance.methods + .challengeLeafCountCorrect( + Block.buildSolidityStruct(priorBlockL2), // the block immediately prior to this one + priorBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), // the transactions in the prior block + Block.buildSolidityStruct(block), + transactions.map(t => Transaction.buildSolidityStruct(t)), + salt, + ) + .encodeABI(); + break; + } + // Challenge wrong root + case 1: { + logger.debug('Challenging incorrect root'); + // Getting prior block for the current block + const priorBlock = await getBlockByBlockNumberL2(Number(block.blockNumberL2) - 1); + if (priorBlock === null) + throw new Error( + `Could not find prior block with block number ${Number(block.blockNumberL2) - 1}`, ); + // Retrieve last transaction from prior block using its transaction hash. + // Note that not all transactions in a block will have commitments. Loop until one is found + const priorBlockTransactions = await getTransactionsByTransactionHashes( + priorBlock.transactionHashes, + ); - // We also need to grab the block 2 before the challenged block as it contains the frontier to - // calculate the root of the prior block. - const priorPriorBlock = await getBlockByBlockNumberL2(Number(block.blockNumberL2) - 2); - if (priorPriorBlock === null) priorPriorBlock.root = ZERO; + // We also need to grab the block 2 before the challenged block as it contains the frontier to + // calculate the root of the prior block. + const priorPriorBlock = await getBlockByBlockNumberL2(Number(block.blockNumberL2) - 2); + if (priorPriorBlock === null) priorPriorBlock.root = ZERO; - const priorPriorTree = await getTreeByRoot(priorPriorBlock.root); - // We need to pad our frontier as we don't store them with the trailing zeroes. - const frontierToValidatePreviousBlock = priorPriorTree.frontier.concat( - Array(TIMBER_HEIGHT - priorPriorTree.frontier.length + 1).fill(ZERO), - ); - // Create a challenge - txDataToSign = await challengeContractInstance.methods - .challengeNewRootCorrect( - Block.buildSolidityStruct(priorBlock), - priorBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), - frontierToValidatePreviousBlock, - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - salt, - ) - .encodeABI(); - break; - } - // challenge duplicate commitment - case 2: { - const { - block1, - transactions1, + const priorPriorTree = await getTreeByRoot(priorPriorBlock.root); + // We need to pad our frontier as we don't store them with the trailing zeroes. + const frontierToValidatePreviousBlock = priorPriorTree.frontier.concat( + Array(TIMBER_HEIGHT - priorPriorTree.frontier.length + 1).fill(ZERO), + ); + // Create a challenge + txDataToSign = await challengeContractInstance.methods + .challengeNewRootCorrect( + Block.buildSolidityStruct(priorBlock), + priorBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), + frontierToValidatePreviousBlock, + Block.buildSolidityStruct(block), + transactions.map(t => Transaction.buildSolidityStruct(t)), + salt, + ) + .encodeABI(); + break; + } + // challenge duplicate commitment + case 2: { + const { + block1, + transactions1, + transaction1Index, + duplicateCommitment1Index, + block2, + transactions2, + transaction2Index, + duplicateCommitment2Index, + } = err.metadata; + txDataToSign = await challengeContractInstance.methods + .challengeCommitment( + Block.buildSolidityStruct(block1), + Block.buildSolidityStruct(block2), + transactions1.map(t => Transaction.buildSolidityStruct(t)), + transactions2.map(t => Transaction.buildSolidityStruct(t)), transaction1Index, - duplicateCommitment1Index, - block2, - transactions2, transaction2Index, + duplicateCommitment1Index, duplicateCommitment2Index, - } = err.metadata; - txDataToSign = await challengeContractInstance.methods - .challengeCommitment( - Block.buildSolidityStruct(block1), - Block.buildSolidityStruct(block2), - transactions1.map(t => Transaction.buildSolidityStruct(t)), - transactions2.map(t => Transaction.buildSolidityStruct(t)), - transaction1Index, - transaction2Index, - duplicateCommitment1Index, - duplicateCommitment2Index, - salt, - ) - .encodeABI(); - break; - } - // challenge duplicate nullifier - case 3: { - const { - block1, - transactions1, + salt, + ) + .encodeABI(); + break; + } + // challenge duplicate nullifier + case 3: { + const { + block1, + transactions1, + transaction1Index, + duplicateNullifier1Index, + block2, + transactions2, + transaction2Index, + duplicateNullifier2Index, + } = err.metadata; + txDataToSign = await challengeContractInstance.methods + .challengeNullifier( + Block.buildSolidityStruct(block1), + Block.buildSolidityStruct(block2), + transactions1.map(t => Transaction.buildSolidityStruct(t)), + transactions2.map(t => Transaction.buildSolidityStruct(t)), transaction1Index, - duplicateNullifier1Index, - block2, - transactions2, transaction2Index, + duplicateNullifier1Index, duplicateNullifier2Index, - } = err.metadata; - txDataToSign = await challengeContractInstance.methods - .challengeNullifier( - Block.buildSolidityStruct(block1), - Block.buildSolidityStruct(block2), - transactions1.map(t => Transaction.buildSolidityStruct(t)), - transactions2.map(t => Transaction.buildSolidityStruct(t)), - transaction1Index, - transaction2Index, - duplicateNullifier1Index, - duplicateNullifier2Index, - salt, - ) - .encodeABI(); - break; - } - // proof does not verify - case 4: { - const { transactionHashIndex: transactionIndex } = err.metadata; - // Create a challenge - const uncompressedProof = transactions[transactionIndex].proof; - const [historicInput1, historicInput2, historicInput3, historicInput4] = await Promise.all( - transactions[transactionIndex].historicRootBlockNumberL2.map(async (b, i) => { - if (transactions[transactionIndex].nullifiers[i] === 0) { - return { - historicBlock: {}, - historicTxs: [], - }; - } - const historicBlock = await getBlockByBlockNumberL2(b); - const historicTxs = await getTransactionsByTransactionHashes(block.transactionHashes); + salt, + ) + .encodeABI(); + break; + } + // proof does not verify + case 4: { + const { transactionHashIndex: transactionIndex } = err.metadata; + // Create a challenge + const uncompressedProof = transactions[transactionIndex].proof; + const [historicInput1, historicInput2, historicInput3, historicInput4] = await Promise.all( + transactions[transactionIndex].historicRootBlockNumberL2.map(async (b, i) => { + if (transactions[transactionIndex].nullifiers[i] === 0) { return { - historicBlock: Block.buildSolidityStruct(historicBlock), - historicTxs, + historicBlock: {}, + historicTxs: [], }; - }), - ); + } + const historicBlock = await getBlockByBlockNumberL2(b); + const historicTxs = await getTransactionsByTransactionHashes(block.transactionHashes); + return { + historicBlock: Block.buildSolidityStruct(historicBlock), + historicTxs, + }; + }), + ); - txDataToSign = await challengeContractInstance.methods - .challengeProofVerification( - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - transactionIndex, - [ - historicInput1.historicBlock, - historicInput2.historicBlock, - historicInput3.historicBlock, - historicInput4.historicBlock, - ], - [ - historicInput1.historicTxs.map(t => Transaction.buildSolidityStruct(t)), - historicInput2.historicTxs.map(t => Transaction.buildSolidityStruct(t)), - historicInput3.historicTxs.map(t => Transaction.buildSolidityStruct(t)), - historicInput4.historicTxs.map(t => Transaction.buildSolidityStruct(t)), - ], - uncompressedProof, - salt, - ) - .encodeABI(); - break; - } - default: - // code block + txDataToSign = await challengeContractInstance.methods + .challengeProofVerification( + Block.buildSolidityStruct(block), + transactions.map(t => Transaction.buildSolidityStruct(t)), + transactionIndex, + [ + historicInput1.historicBlock, + historicInput2.historicBlock, + historicInput3.historicBlock, + historicInput4.historicBlock, + ], + [ + historicInput1.historicTxs.map(t => Transaction.buildSolidityStruct(t)), + historicInput2.historicTxs.map(t => Transaction.buildSolidityStruct(t)), + historicInput3.historicTxs.map(t => Transaction.buildSolidityStruct(t)), + historicInput4.historicTxs.map(t => Transaction.buildSolidityStruct(t)), + ], + uncompressedProof, + salt, + ) + .encodeABI(); + break; } - // now we need to commit to this challenge. When we have, this fact will be - // picked up by the challenge-commit event-handler and a reveal will be sent - // to intiate the challenge transaction (after checking we haven't been - // front-run) - commitToChallenge(txDataToSign); - } else { - // only proposer not a challenger - logger.info( - "Faulty block detected. Don't submit new blocks until the faulty blocks are removed", - ); + default: + // code block } + // now we need to commit to this challenge. When we have, this fact will be + // picked up by the challenge-commit event-handler and a reveal will be sent + // to intiate the challenge transaction (after checking we haven't been + // front-run) + logger.info("Faulty block detected. Don't submit new blocks until the faulty blocks are removed"); + return txDataToSign; } diff --git a/nightfall-optimist/src/services/database.mjs b/nightfall-optimist/src/services/database.mjs index 3c1360af3..f5c580b08 100644 --- a/nightfall-optimist/src/services/database.mjs +++ b/nightfall-optimist/src/services/database.mjs @@ -16,7 +16,6 @@ const { OPTIMIST_DB, TRANSACTIONS_COLLECTION, PROPOSER_COLLECTION, - CHALLENGER_COLLECTION, SUBMITTED_BLOCKS_COLLECTION, INVALID_BLOCKS_COLLECTION, COMMIT_COLLECTION, @@ -48,43 +47,6 @@ export async function getCommit(commitHash) { return commit; } -/** -function to store addresses that are used to sign challenge transactions. This -is done so that we can check that a challenge commit is from us and hasn't been -front-run (because that would change the origin address of the commit to that of -the front-runner). -*/ -export async function addChallengerAddress(address) { - const connection = await mongo.connection(MONGO_URL); - const db = connection.db(OPTIMIST_DB); - logger.debug(`Saving challenger address ${address}`); - const data = { challenger: address }; - return db.collection(CHALLENGER_COLLECTION).insertOne(data); -} - -/** -function to remove addresses that are used to sign challenge transactions. This -is needed in the case of a key compromise, or if we simply no longer wish to use -the address. -*/ -export async function removeChallengerAddress(address) { - const connection = await mongo.connection(MONGO_URL); - const db = connection.db(OPTIMIST_DB); - logger.debug(`Removing challenger address ${address}`); - const data = { challenger: address }; - return db.collection(CHALLENGER_COLLECTION).deleteOne(data); -} - -/** -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(CHALLENGER_COLLECTION).findOne({ challenger: address }); - return metadata !== null; -} - /** function to save a block, so that we can later search the block, for example to find which block a transaction went into. Note, we'll save all blocks, that get diff --git a/nightfall-optimist/src/services/state-sync.mjs b/nightfall-optimist/src/services/state-sync.mjs index 70bf2d9fc..58f16b764 100644 --- a/nightfall-optimist/src/services/state-sync.mjs +++ b/nightfall-optimist/src/services/state-sync.mjs @@ -1,8 +1,9 @@ /* eslint-disable no-await-in-loop */ import { getContractInstance } from 'common-files/utils/contract.mjs'; -import { pauseQueue, unpauseQueue } from 'common-files/utils/event-queue.mjs'; import constants from 'common-files/constants/index.mjs'; +import { pauseQueue, unpauseQueue, queues, flushQueue } from 'common-files/utils/event-queue.mjs'; +import logger from 'common-files/utils/logger.mjs'; import blockProposedEventHandler from '../event-handlers/block-proposed.mjs'; import transactionSubmittedEventHandler from '../event-handlers/transaction-submitted.mjs'; import newCurrentProposerEventHandler from '../event-handlers/new-current-proposer.mjs'; @@ -46,7 +47,6 @@ const syncState = async ( .sort((a, b) => a.blockNumber - b.blockNumber); for (let i = 0; i < splicedList.length; i++) { const pastEvent = splicedList[i]; - console.log('PAST EVENT', pastEvent); switch (pastEvent.event) { case 'NewCurrentProposer': await newCurrentProposerEventHandler(pastEvent, [proposer]); @@ -107,10 +107,12 @@ export default async proposer => { if (lastBlockNumberL2 === -1) { unpauseQueue(0); // queues are started paused, therefore we need to unpause them before proceeding. unpauseQueue(1); + startMakingChallenges(); return null; // The blockchain is empty } // pause the queues so we stop processing incoming events while we sync await Promise.all([pauseQueue(0), pauseQueue(1)]); + logger.info('Begining synchronisation with the blockchain'); const missingBlocks = await checkBlocks(); // Stores any gaps of missing blocks // const [fromBlock] = missingBlocks[0]; const latestBlockLocally = (await getBlockByBlockNumberL2(lastBlockNumberL2)) ?? undefined; @@ -121,10 +123,26 @@ export default async proposer => { for (let i = 0; i < missingBlocks.length; i++) { const [fromBlock, toBlock] = missingBlocks[i]; // Sync the state inbetween these blocks - await syncState(proposer, fromBlock, toBlock); } + // at this point, we have synchronised all the existing blocks. If there are no outstanding + // challenges (all rollbacks have completed) then we're done. It's possible however that + // we had a bad block that was not rolled back. If this is the case then there will still be + // a challenge in the stop queue that was not removed by a rollback. + // If this is the case we'll run the stop queue to challenge the bad block. await startMakingChallenges(); + if (queues[2].length === 0) + logger.info('After synchronisation, no challenges remain unresolved'); + else { + logger.info( + `After synchronisation, there were ${queues[2].length} unresolved challenges. Running them now.`, + ); + // start queue[2] and await all the unresolved challenges being run + const p = flushQueue(2); + queues[2].start(); + await p; + logger.debug('All challenges in the stop queue have now been made.'); + } } const currentProposer = (await stateContractInstance.methods.currentProposer().call()) .thisAddress; diff --git a/package.json b/package.json index 36b0453d6..38b7423e7 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,14 @@ "build-adversary": "node test/adversary/transpile-adversary.mjs", "test": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/protocol/*.test.mjs test/e2e/tokens/*.test.mjs", "test-e2e-protocol": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/protocol/*.test.mjs ", - "test-gas": "mocha --timeout 0 --bail --exit test/e2e/gas.test.mjs ", - "test-circuits": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/circuits.test.mjs ", + "test-gas": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/gas.test.mjs ", + "test-circuits": "LOG_LEVEL=debug mocha --timeout 0 --bail --exit test/e2e/circuits.test.mjs ", "test-e2e-tokens": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/tokens/*.test.mjs ", "test-erc20-tokens": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/tokens/erc20.test.mjs ", "test-erc721-tokens": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/tokens/erc721.test.mjs ", "test-erc1155-tokens": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/tokens/erc1155.test.mjs ", "test-erc20-cli": "LOG_LEVEL=debug mocha --timeout 0 --bail --exit test/client/erc20.test.mjs ", + "test-optimist-sync": "LOG_LEVEL=silent mocha --timeout 0 --bail --exit test/optimist-resync.test.mjs", "test-adversary": "CHALLENGE_TYPE=${CHALLENGE_TYPE} mocha --timeout 0 --bail --exit test/adversary.test.mjs", "test-all-adversary": "for CHALLENGE_TYPE in IncorrectTreeRoot IncorrectLeafCount IncorrectTreeRoot IncorrectLeafCount; do CHALLENGE_TYPE=${CHALLENGE_TYPE} mocha --timeout 0 --bail --exit test/adversary.test.mjs; sleep 5; done", "test-general-stuff": "LOG_LEVEL=debug mocha --timeout 0 --bail --exit test/kem-dem.test.mjs test/timber.test.mjs" diff --git a/setup-nightfall b/setup-nightfall index 8b42d92dc..7bb6ddd43 100755 --- a/setup-nightfall +++ b/setup-nightfall @@ -1,15 +1,13 @@ #! /bin/bash +set -e # Install node dependencies npm ci - -OS_ARCH=$(uname -m) -NO_CACHE_FLAG='' - -# Workaround when building in a Mac -if [ $OS_ARCH != "x86_64" ]; then - NO_CACHE_FLAG='--no-cache' -fi - -docker build ${NO_CACHE_FLAG} -t ghcr.io/eyblockchain/local-zokrates:0.7.13 -f zokrates.Dockerfile . -docker-compose -f docker-compose.yml -f docker-compose.dev.yml build ${NO_CACHE_FLAG} +docker build -t ghcr.io/eyblockchain/local-zokrates:0.7.13 -f zokrates.Dockerfile . +# containers built separately. A parallel build fails. +docker-compose -f docker-compose.yml -f docker-compose.dev.yml build administrator +docker-compose -f docker-compose.yml -f docker-compose.dev.yml build client +docker-compose -f docker-compose.yml -f docker-compose.dev.yml build optimist +docker-compose -f docker-compose.yml -f docker-compose.dev.yml build worker +docker-compose -f docker-compose.yml -f docker-compose.dev.yml build hosted-utils-api-server +docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.adversary.yml build adversary1 diff --git a/start-nightfall b/start-nightfall index 95001e666..46e67ada6 100755 --- a/start-nightfall +++ b/start-nightfall @@ -56,9 +56,9 @@ trap "docker-compose $FILE $STUBS $DEV $ADVERSARY down --remove-orphans -t 1; ex docker-compose -f docker-compose.yml $FILE $STUBS $DEV $ADVERSARY down --remove-orphans # if-else block checks - volume exist and then removes it. -if [[ $(echo $VOLUME_LIST | grep nightfall_3_mongodb1) ]]; then +if [[ $(echo $VOLUME_LIST | grep nightfall_3_mongodb) ]]; then echo -n 'Removing ' - docker volume rm nightfall_3_mongodb1 + docker volume rm nightfall_3_mongodb fi if [[ $(echo $VOLUME_LIST | grep nightfall_3_mongodb2) ]]; then @@ -92,4 +92,4 @@ if [[ -d "$DIR" ]]; then fi #docker-compose -f docker-compose.yml $FILE up -d deployer docker-compose $FILE $STUBS $DEV $ADVERSARY up -d --remove-orphans -docker-compose logs -f client1 optimist1 worker deployer +docker-compose logs -f client optimist worker deployer diff --git a/test/adversary.test.mjs b/test/adversary.test.mjs index 5717f1153..7df67041a 100644 --- a/test/adversary.test.mjs +++ b/test/adversary.test.mjs @@ -120,8 +120,6 @@ describe('Testing with an adversary', () => { registerProposerOnNoProposer(nf3AdversarialProposer); }, 5000); - // Challenger registration - await nf3Challenger.registerChallenger(); // Chalenger listening for incoming events const challengeEmitter = await nf3Challenger.startChallenger(); challengeEmitter @@ -170,7 +168,7 @@ describe('Testing with an adversary', () => { nDeposits++; expectedBalance += value2; } - + console.log('Number of deposits', nDeposits); for (let i = 0; i < TEST_LENGTH; i++) { await waitForSufficientBalance( nf3User, @@ -209,11 +207,13 @@ describe('Testing with an adversary', () => { nTransfers++; } } + console.log('Number of transfers', nTransfers); for (let k = 0; k < TRANSACTIONS_PER_BLOCK - 1; k++) { await nf3User.deposit(ercAddress, tokenType, value2, tokenId, fee); nDeposits++; expectedBalance += value2; } + console.log('Number of deposits', nDeposits); await new Promise(resolve => setTimeout(resolve, TX_WAIT)); // this may need to be longer on a real blockchain console.log(`Completed ${i + 1} pings with expectedBalance ${expectedBalance}`); } diff --git a/test/adversary/transpile-adversary.mjs b/test/adversary/transpile-adversary.mjs index dcdd8cca7..8ad5b08e0 100644 --- a/test/adversary/transpile-adversary.mjs +++ b/test/adversary/transpile-adversary.mjs @@ -43,11 +43,10 @@ const transpileBlockProposed = _pathToSrc => { let srcFile = fs.readFileSync(_pathToSrc, 'utf-8'); // We need to take into account variable NONSTOP_QUEUE_AFTER_INVALID_BLOCK - const regexReplaceComponents = - /(await enqueueEvent\(\(\) => logger.info\('Stop Until Rollback'\), 2\);)/g; + const regexReplaceComponents = /(await enqueueEvent\(commitToChallenge, 2, txDataToSign\);)/g; const reComponent = `logger.info(\`NONSTOP_QUEUE_AFTER_INVALID_BLOCK: \${process.env.NONSTOP_QUEUE_AFTER_INVALID_BLOCK}\`); - if (process.env.NONSTOP_QUEUE_AFTER_INVALID_BLOCK === "false") - await enqueueEvent(() => logger.info('Stop Until Rollback'), 2);`; + if (!process.env.NONSTOP_QUEUE_AFTER_INVALID_BLOCK === "false") + await enqueueEvent(commitToChallenge, 2, txDataToSign);`; srcFile = `/* THIS FILE CONTAINS CODE THAT HAS BEEN AUTOGENERATED DO NOT MODIFY MANUALLY */\n${srcFile.replace( regexReplaceComponents, reComponent, diff --git a/test/e2e/circuits.test.mjs b/test/e2e/circuits.test.mjs index b334e6403..1860c8524 100644 --- a/test/e2e/circuits.test.mjs +++ b/test/e2e/circuits.test.mjs @@ -144,7 +144,7 @@ describe('General Circuit Test', () => { await nf3Users[0].makeBlockNow(); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); logger.debug(`Sending withdrawal with no change...`); const withdrawalNoChange = await nf3Users[0].withdraw( diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index 97db948e4..fc15c78fd 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -82,7 +82,7 @@ describe('Gas test', () => { tokenId, 0, ); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); expect(gasCost).to.be.lessThan(expectedGasCostPerTx); }); @@ -97,7 +97,7 @@ describe('Gas test', () => { tokenId, 0, ); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); expect(gasCost).to.be.lessThan(expectedGasCostPerTx); console.log('Deposit L1 average gas used was', averageL1GasCost(receipts)); }); @@ -116,7 +116,7 @@ describe('Gas test', () => { nf3Users[0].zkpKeys.compressedZkpPublicKey, 0, ); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); expect(gasCost).to.be.lessThan(expectedGasCostPerTx); console.log( 'Single transfer L1 average gas used, if on-chain, was', @@ -138,7 +138,7 @@ describe('Gas test', () => { nf3Users[0].zkpKeys.compressedZkpPublicKey, 0, ); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); expect(gasCost).to.be.lessThan(expectedGasCostPerTx); console.log( 'Double transfer L1 average gas used, if on-chain, was', @@ -161,7 +161,7 @@ describe('Gas test', () => { 0, ); await nf3Users[0].makeBlockNow(); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); expect(gasCost).to.be.lessThan(expectedGasCostPerTx); console.log('Withdraw L1 average gas used, if on-chain, was', averageL1GasCost(receipts)); }); diff --git a/test/e2e/protocol/challenger.test.mjs b/test/e2e/protocol/challenger.test.mjs deleted file mode 100644 index 14890b6f5..000000000 --- a/test/e2e/protocol/challenger.test.mjs +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-await-in-loop */ -import chai from 'chai'; -import config from 'config'; -import chaiHttp from 'chai-http'; -import chaiAsPromised from 'chai-as-promised'; -import Nf3 from '../../../cli/lib/nf3.mjs'; - -// so we can use require with mjs file -const { expect } = chai; -chai.use(chaiHttp); -chai.use(chaiAsPromised); - -const environment = config.ENVIRONMENTS[process.env.ENVIRONMENT] || config.ENVIRONMENTS.localhost; - -const { signingKeys } = config.RESTRICTIONS; -const { mnemonics } = config.TEST_OPTIONS; - -const bootChallenger = new Nf3(signingKeys.bootChallengerKey, environment); - -describe('Basic Challenger tests', () => { - before(async () => { - await bootChallenger.init(mnemonics.challenger); - }); - - it('should register the boot challenger', async () => { - // Challenger registration - await bootChallenger.registerChallenger(); - // Chalenger listening for incoming events - bootChallenger.startChallenger(); - }); - - it('should de-register a challenger', async () => { - const res = await bootChallenger.deregisterChallenger(); - expect(res.status).to.be.equal(200); - }); - - after(async () => { - await bootChallenger.close(); - }); -}); diff --git a/test/e2e/tokens/erc20.test.mjs b/test/e2e/tokens/erc20.test.mjs index cfbf12c2f..bd8de3bb4 100644 --- a/test/e2e/tokens/erc20.test.mjs +++ b/test/e2e/tokens/erc20.test.mjs @@ -27,7 +27,7 @@ const { const { RESTRICTIONS: { - tokens: { blockchain1: maxWithdrawValue }, + tokens: { blockchain: maxWithdrawValue }, }, } = config; @@ -65,7 +65,7 @@ describe('ERC20 tests', () => { before(async () => { await nf3Proposer.init(mnemonics.proposer); // we must set the URL from the point of view of the client container - await nf3Proposer.registerProposer('http://optimist1'); + await nf3Proposer.registerProposer('http://optimist'); // Proposer listening for incoming events const newGasBlockEmitter = await nf3Proposer.startProposer(); diff --git a/test/optimist-resync.test.mjs b/test/optimist-resync.test.mjs new file mode 100644 index 000000000..83522dad2 --- /dev/null +++ b/test/optimist-resync.test.mjs @@ -0,0 +1,235 @@ +/* eslint-disable no-await-in-loop */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import chaiAsPromised from 'chai-as-promised'; +import config from 'config'; +import compose from 'docker-compose'; +import constants from 'common-files/constants/index.mjs'; +import Transaction from 'common-files/classes/transaction.mjs'; +import logger from 'common-files/utils/logger.mjs'; +import Nf3 from '../cli/lib/nf3.mjs'; +import { depositNTransactions, Web3Client } from './utils.mjs'; +import { buildBlockSolidityStruct } from '../nightfall-optimist/src/services/block-utils.mjs'; +// so we can use require with mjs file +const { expect } = chai; +chai.use(chaiHttp); +chai.use(chaiAsPromised); + +const environment = config.ENVIRONMENTS[process.env.ENVIRONMENT] || config.ENVIRONMENTS.localhost; + +const { + fee, + transferValue, + txPerBlock, + tokenConfigs: { tokenType, tokenId }, + mnemonics, + signingKeys, +} = config.TEST_OPTIONS; + +const { PROPOSE_BLOCK_TYPES } = constants; + +const nf3Users = [new Nf3(signingKeys.user1, environment), new Nf3(signingKeys.user2, environment)]; +const nf3Proposer1 = new Nf3(signingKeys.proposer1, environment); +const nf3Challenger = new Nf3(signingKeys.challenger, environment); + +const web3Client = new Web3Client(); + +let erc20Address; +let stateAddress; +let eventLogs = []; +let eventsSeen; + +describe('Optimist synchronisation tests', () => { + let blockProposeEmitter; + let challengeEmitter; + const options = { + config: ['docker-compose.yml', 'docker-compose.dev.yml', 'docker-compose.ganache.yml'], + log: process.env.LOG_LEVEL !== 'silent', + }; + + before(async () => { + await nf3Proposer1.init(mnemonics.proposer); + await nf3Challenger.init(mnemonics.challenger); + // we must set the URL from the point of view of the client container + await nf3Proposer1.registerProposer('http://optimist'); + + // Proposer listening for incoming events + blockProposeEmitter = await nf3Proposer1.startProposer(); + challengeEmitter = await nf3Challenger.startChallenger(); + challengeEmitter.on('receipt', (receipt, type) => + logger.debug(`challenge listener received challenge receipt of type ${type}`), + ); + challengeEmitter.on('error', (err, type) => + logger.debug(`challenge listener received error ${err.message} of type ${type}`), + ); + await nf3Users[0].init(mnemonics.user1); + await nf3Users[1].init(mnemonics.user2); + erc20Address = await nf3Users[0].getContractAddress('ERC20Mock'); + + stateAddress = await nf3Users[0].stateContractAddress; + web3Client.subscribeTo('logs', eventLogs, { address: stateAddress }); + }); + + describe('With and without a bad block', () => { + // setup a listener for a block proposal + const proposePromise = () => + new Promise(resolve => + blockProposeEmitter.on('receipt', (receipt, b, t) => + resolve({ receipt, block: b, transactions: t }), + ), + ); + // setup a listener for a block proposal + const rollbackPromise = () => + new Promise(resolve => blockProposeEmitter.on('rollback', () => resolve())); + + // setup a healthcheck wait + const healthy = async () => { + while (!(await nf3Proposer1.healthcheck('optimist'))) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + logger.debug('optimist is healthy'); + }; + + it('Resync optimist after making a good block', async function () { + // We create enough good transactions to fill a block full of deposits. + logger.debug(` Sending ${txPerBlock} deposits...`); + let p = proposePromise(); + await depositNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue, + tokenId, + fee, + ); + // we can use the emitter that nf3 provides to get the block and transactions we've just made. + // The promise resolves once the block is on-chain. + const { block } = await p; + const firstBlock = { ...block }; + // we still need to clean the 'BlockProposed' event from the test logs though. + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); + // Now we have a block, let's force Optimist to re-sync by turning it off and on again! + await compose.stopOne('optimist', options); + await compose.rm(options, 'optimist'); + await compose.upOne('optimist', options); + await healthy(); + + // we need to remind optimist which proposer it's connected to + await nf3Proposer1.registerProposer('http://optimist'); + // TODO - get optimist to do this automatically. + // Now we'll add another block and check that it's blocknumber is correct, indicating + // that a resync correctly occured + logger.debug(` Sending ${txPerBlock} deposits...`); + p = proposePromise(); + await depositNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue, + tokenId, + fee, + ); + // we can use the emitter that nf3 provides to get the block and transactions we've just made. + // The promise resolves once the block is on-chain. + const { block: secondBlock } = await p; + // we still need to clean the 'BlockProposed' event from the test logs though. + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); + expect(secondBlock.blockNumberL2 - firstBlock.blockNumberL2).to.equal(1); + }); + it('Resync optimist after making an un-resolved bad block', async function () { + // We create enough good transactions to fill a block full of deposits. + logger.debug(` Sending ${txPerBlock} deposits...`); + let p = proposePromise(); + await depositNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue, + tokenId, + fee, + ); + // we can use the emitter that nf3 provides to get the block and transactions we've just made. + // The promise resolves once the block is on-chain. + const { block, transactions } = await p; + const firstBlock = { ...block }; + // we still need to clean the 'BlockProposed' event from the test logs though. + ({ eventLogs, eventsSeen } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); + // turn off challenging. We're going to make a bad block and we don't want it challenged + await nf3Challenger.challengeEnable(false); + // update the block so we can submit it again + // we'll do the easiest thing and submit it again with no change other than to increment + // the L2 block number and block hash so that it doesn't get reverted straight away. + // It will be a bad block because we'll have submitted the same transactions and leafcount + // before. + // To achieve that, we'll manually assemble and submit a new proposeBlock call + // in the following lines... + + // retrieve the code for the 'proposeBlock' function. We'll check it every time rather than + // hard code it, in case someone changes the function. + const functionCode = ( + await web3Client.getWeb3().eth.getTransaction(eventsSeen[0].log.transactionHash) + ).input.slice(0, 10); + // fix up the blockHash and blockNumberL2 to prevent an immediate revert + block.previousBlockHash = block.blockHash; + block.blockNumberL2++; + // now assemble our bad-block transaction; first the parameters + const blockData = Object.values(buildBlockSolidityStruct(block)); + const transactionsData = Object.values( + transactions.map(t => Object.values(Transaction.buildSolidityStruct(t))), + ); + const encodedParams = web3Client + .getWeb3() + .eth.abi.encodeParameters(PROPOSE_BLOCK_TYPES, [blockData, transactionsData]); + // then the function identifier is added + const newTx = `${functionCode}${encodedParams.slice(2)}`; + // then send it! + logger.debug('Resubmitting the same transactions in the next block'); + await web3Client.submitTransaction(newTx, signingKeys.proposer1, stateAddress, 8000000, 1); + logger.debug('bad block submitted'); + const r = rollbackPromise(); + // Now we have a bad block, let's force Optimist to re-sync by turning it off and on again! + await compose.stopOne('optimist', options); + await compose.rm(options, 'optimist'); + await compose.upOne('optimist', options); + await healthy(); + + logger.debug('waiting for rollback to complete'); + await r; + logger.debug('rollback complete event received'); + // the rollback will have removed us as proposer. We need to re-register because we + // were the only proposer in town! + await nf3Proposer1.registerProposer('http://optimist'); + // Now we'll add another block and check that it's blocknumber is correct, indicating + // that a rollback correctly occured + logger.debug(` Sending ${txPerBlock} deposits...`); + p = proposePromise(); + await depositNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue, + tokenId, + fee, + ); + // we can use the emitter that nf3 provides to get the block and transactions we've just made. + // The promise resolves once the block is on-chain. + const { block: secondBlock } = await p; + // we still need to clean the 'BlockProposed' event from the test logs though. + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); + expect(secondBlock.blockNumberL2 - firstBlock.blockNumberL2).to.equal(1); + }); + }); + + after(async () => { + await nf3Proposer1.deregisterProposer(); + await nf3Proposer1.close(); + await nf3Challenger.close(); + await nf3Users[0].close(); + await nf3Users[1].close(); + await web3Client.closeWeb3(); + }); +}); diff --git a/test/ping-pong/docker-compose.standalone.ganache.yml b/test/ping-pong/docker-compose.standalone.ganache.yml index 9fd1d0449..83b7dd986 100644 --- a/test/ping-pong/docker-compose.standalone.ganache.yml +++ b/test/ping-pong/docker-compose.standalone.ganache.yml @@ -3,7 +3,7 @@ version: '3.5' # bindings and using a Ganache private blockchain. See the readme for more information. # It acts as an override file for docker-compose.yml services: - blockchain1: + blockchain: image: trufflesuite/ganache-cli:v6.12.1 ports: - 8546:8546 diff --git a/test/ping-pong/docker-compose.yml b/test/ping-pong/docker-compose.yml index 85773d223..b61a62c12 100644 --- a/test/ping-pong/docker-compose.yml +++ b/test/ping-pong/docker-compose.yml @@ -22,7 +22,7 @@ services: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin LOG_LEVEL: debug - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 ZOKRATES_WORKER_HOST: worker RABBITMQ_HOST: amqp://rabbitmq @@ -53,7 +53,7 @@ services: target: /app/build/ environment: WEBSOCKET_PORT: 8080 - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 HASH_TYPE: poseidon LOG_LEVEL: debug @@ -79,8 +79,8 @@ services: LOG_LEVEL: debug # ETH_NETWORK sets the network selected by Truffle from truffle-config.js # startup routines will wait for a blockchain client to be reachable on this network - ETH_NETWORK: blockchain1 - BLOCKCHAIN_WS_HOST: blockchain1 + ETH_NETWORK: blockchain + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 ZOKRATES_WORKER_HOST: worker USE_STUBS: 'false' @@ -112,7 +112,7 @@ services: PROPOSER_HOST: proposer PROPOSER_PORT: 8080 OPTIMIST_PORT: 80 - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 CLIENT_HOST: client CLIENT_PORT: 80 @@ -130,7 +130,7 @@ services: OPTIMIST_PORT: 80 CLIENT_HOST: client CLIENT_PORT: 80 - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 IS_TEST_RUNNER: 'yes' PROPOSER_PORT: 8080 @@ -149,7 +149,7 @@ services: OPTIMIST_PORT: 80 CLIENT_HOST: client CLIENT_PORT: 80 - BLOCKCHAIN_WS_HOST: blockchain1 + BLOCKCHAIN_WS_HOST: blockchain BLOCKCHAIN_PORT: 8546 PROPOSER_PORT: 8080 PROPOSER_HOST: proposer diff --git a/test/rollback-resync.test.mjs b/test/rollback-resync.test.mjs index c1c328262..87b971d93 100644 --- a/test/rollback-resync.test.mjs +++ b/test/rollback-resync.test.mjs @@ -180,7 +180,7 @@ describe('Running rollback and resync test', () => { ); await sendTransactions(depositTransactions, [privateKey, shieldAddress, gas, fee]); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); }); it('should make a block of transfers so we can make some bad blocks', async function () { @@ -193,7 +193,7 @@ describe('Running rollback and resync test', () => { // eslint-disable-next-line prefer-destructuring duplicateTransaction = transferTransactions[0]; await sendTransactions(transferTransactions, [privateKey, shieldAddress, gas, fee]); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); }); }); @@ -219,7 +219,7 @@ describe('Running rollback and resync test', () => { validTransactions.push(...transferTransactions); await sendTransactions(transferTransactions, [privateKey, shieldAddress, gas, fee]); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); }); it('should send the commit-challenge', async function () { while (!commitTxDataToSign) { @@ -229,7 +229,7 @@ describe('Running rollback and resync test', () => { commitTxDataToSign = null; }); it('Should delete the flawed block and rollback the leaves', async () => { - eventLogs = await web3Client.waitForEvent(eventLogs, ['Rollback']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['Rollback'])); await web3Client.testForEvents(stateAddress, [ web3.eth.abi.encodeEventSignature('Rollback(bytes32,uint256,uint256)'), web3.eth.abi.encodeParameter('bytes32', topicsBlockHashDuplicateTransaction), @@ -245,7 +245,7 @@ describe('Running rollback and resync test', () => { stdio: 'ignore', }); resetOptimistDB.on('close', async () => { - spawn('docker', ['restart', 'nightfall_3_optimist1_1'], { + spawn('docker', ['restart', 'nightfall_3_optimist_1'], { stdio: 'ignore', }); @@ -330,7 +330,7 @@ describe('Running rollback and resync test', () => { it('should automatically create a bad block, as the resync will re-populate our local db', async () => { // This block will be a bad block as it uses the blockSubmissionFunction from the first bad block. - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); }); it('should make another good block on top of the bad block', async function () { @@ -353,7 +353,7 @@ describe('Running rollback and resync test', () => { ); await sendTransactions(depositTransactions, [privateKey, shieldAddress, gas, fee]); validTransactions.push(...depositTransactions); - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + ({ eventLogs } = await web3Client.waitForEvent(eventLogs, ['blockProposed'])); }); it('should fire off the commit-challenge', async function () { @@ -380,7 +380,7 @@ describe('Running rollback and resync test', () => { stdio: 'ignore', }); resetOptimistDB.on('close', async () => { - spawn('docker', ['restart', 'nightfall_3_optimist1_1'], { + spawn('docker', ['restart', 'nightfall_3_optimist_1'], { stdio: 'ignore', }); let healthCheck; diff --git a/test/utils.mjs b/test/utils.mjs index 4009679a1..5545d570b 100644 --- a/test/utils.mjs +++ b/test/utils.mjs @@ -66,16 +66,16 @@ export class Web3Client { for (const topic of log.topics) { switch (topic) { case topicEventMapping.BlockProposed: - queue.push('blockProposed'); + queue.push({ eventName: 'blockProposed', log }); break; case topicEventMapping.TransactionSubmitted: - queue.push('TransactionSubmitted'); + queue.push({ eventName: 'TransactionSubmitted', log }); break; case topicEventMapping.NewCurrentProposer: - queue.push('NewCurrentProposer'); + queue.push({ eventName: 'NewCurrentProposer', log }); break; default: - queue.push('Challenge'); + queue.push({ eventName: 'Challenge', log }); break; } } @@ -142,7 +142,7 @@ export class Web3Client { receipt = await this.web3.eth.sendSignedTransaction(signed.rawTransaction); // the confirmations Promivent doesn't seem to terminate in Ganache, so we'll // just count 12 blocks before returning. TODO this won't handle a chain reorg. - console.log('waiting for twelve confirmations of transaction'); + // console.log('waiting for twelve confirmations of transaction'); const startBlock = await this.web3.eth.getBlock('latest'); await new Promise(resolve => { const id = setInterval(async () => { @@ -153,7 +153,7 @@ export class Web3Client { } }, 1000); }); - console.log('transaction confirmed'); + // console.log('transaction confirmed'); } finally { this.isSubmitTxLocked = false; } @@ -221,14 +221,14 @@ export class Web3Client { if (timeout === 0) throw new Error('Timeout in waitForEvent'); } - while (eventLogs[0] !== expectedEvents[0]) { + while (eventLogs[0]?.eventName !== expectedEvents[0]) { await waitForTimeout(3000); } - expect(eventLogs[0]).to.equal(expectedEvents[0]); - + expect(eventLogs[0].eventName).to.equal(expectedEvents[0]); + const eventsSeen = []; for (let i = 0; i < length; i++) { - eventLogs.shift(); + eventsSeen.push(eventLogs.shift()); } const blockHeaders = []; @@ -241,7 +241,7 @@ export class Web3Client { // Have to wait here as client block proposal takes longer now await waitForTimeout(3000); - return eventLogs; + return { eventLogs, eventsSeen }; } } diff --git a/worker.Dockerfile b/worker.Dockerfile index 3e17551ad..6eee39d26 100644 --- a/worker.Dockerfile +++ b/worker.Dockerfile @@ -29,4 +29,4 @@ COPY ./zokrates-worker/start-dev ./start-dev RUN npm ci -CMD npm start +CMD ["npm", "start"]