Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Westlad/fix restart nightfall #564

Merged
merged 11 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions cli/lib/nf3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -563,13 +563,33 @@ class Nf3 {
const res = await axios.post(`${this.optimistBaseUrl}/proposer/register`, {
address: this.ethereumAddress,
});
if (res.data.txDataToSign === '') return false; // already registered
return this.submitTransaction(
res.data.txDataToSign,
this.proposersContractAddress,
this.PROPOSER_BOND,
);
}

/**
Registers a proposer locally with the Optimist instance only. This will cause
Optimist to make blocks when this proposer is current but these will revert if
the proposer isn't registered on the blockchain too. This method is useful only
if the proposer is already registered on the blockchain (has paid their bond) and
for some reason the Optimist instance does not know about them, e.g. a new instance
has been created. The method 'registerProposer' will both register the proposer
with the blockchain and register locally with the optimist instance. So, if
that method has been used successfully, there is no need to also call this method
@method
@async
@returns {Promise} A promise that resolves to the Ethereum transaction receipt.
*/
async registerProposerLocally() {
Westlad marked this conversation as resolved.
Show resolved Hide resolved
return axios.post(`${this.optimistBaseUrl}/proposer/registerlocally`, {
Westlad marked this conversation as resolved.
Show resolved Hide resolved
address: this.ethereumAddress,
});
}

/**
De-registers an existing proposer.
It will use the address of the Ethereum Signing key that is holds to de-register
Expand Down
1 change: 0 additions & 1 deletion common-files/utils/contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ export async function waitForContract(contractName) {
try {
error = undefined;
const address = await getContractAddress(contractName); // eslint-disable-line no-await-in-loop
logger.debug(`${contractName} contract address is ${address}`);
if (address === undefined) throw new Error(`${contractName} contract address was undefined`);
instance = getContractInstance(contractName, address);
return instance;
Expand Down
3 changes: 2 additions & 1 deletion config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ const { DOMAIN_NAME = '' } = process.env;
module.exports = {
COMMITMENTS_DB: 'nightfall_commitments',
OPTIMIST_DB: 'optimist_data',
METADATA_COLLECTION: 'metadata',
PROPOSER_COLLECTION: 'proposers',
CHALLENGER_COLLECTION: 'challengers',
TRANSACTIONS_COLLECTION: 'transactions',
SUBMITTED_BLOCKS_COLLECTION: 'blocks',
NULLIFIER_COLLECTION: 'nullifiers',
Expand Down
68 changes: 0 additions & 68 deletions doc/contract-update.md

This file was deleted.

Binary file removed doc/contract-upgrade.png
Binary file not shown.
3 changes: 1 addition & 2 deletions nightfall-client/src/event-handlers/subscribe.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ const { STATE_CONTRACT_NAME, RETRIES } = config;
* This is useful in case nightfall-client comes up before the contract
* is fully deployed.
*/
async function waitForContract(contractName) {
export async function waitForContract(contractName) {
let errorCount = 0;
let error;
let instance;
while (errorCount < RETRIES) {
try {
error = undefined;
const address = await getContractAddress(contractName);
logger.debug(`${contractName} contract address is ${address}`);
if (address === undefined) throw new Error(`${contractName} contract address was undefined`);
instance = getContractInstance(contractName, address);
return instance;
Expand Down
3 changes: 2 additions & 1 deletion nightfall-client/src/services/database.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export async function saveBlock(_block) {
if (!existing || !existing.blockNumber) {
return db.collection(SUBMITTED_BLOCKS_COLLECTION).updateOne(query, update, { upsert: true });
}
throw new Error('Attempted to replay existing layer 2 block');
logger.warn('Attempted to replay existing layer 2 block. This is expected if we are syncing');
Westlad marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

/**
Expand Down
7 changes: 2 additions & 5 deletions nightfall-client/src/services/state-sync.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ their local commitments databsae.

import config from 'config';
import logger from 'common-files/utils/logger.mjs';
import { getContractInstance } from 'common-files/utils/contract.mjs';
import mongo from 'common-files/utils/mongo.mjs';
import { waitForContract } from '../event-handlers/subscribe.mjs';
import blockProposedEventHandler from '../event-handlers/block-proposed.mjs';
import rollbackEventHandler from '../event-handlers/rollback.mjs';

const { MONGO_URL, COMMITMENTS_DB, COMMITMENTS_COLLECTION, STATE_CONTRACT_NAME } = config;

const syncState = async (fromBlock = 'earliest', toBlock = 'latest', eventFilter = 'allEvents') => {
const stateContractInstance = await getContractInstance(STATE_CONTRACT_NAME); // Rollback, BlockProposed
const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME); // Rollback, BlockProposed

const pastStateEvents = await stateContractInstance.getPastEvents(eventFilter, {
fromBlock,
toBlock,
});
logger.info(`pastStateEvents: ${JSON.stringify(pastStateEvents)}`);

for (let i = 0; i < pastStateEvents.length; i++) {
switch (pastStateEvents[i].event) {
Expand All @@ -46,8 +45,6 @@ const genGetCommitments = async (query = {}, proj = {}) => {
// eslint-disable-next-line import/prefer-default-export
export const initialClientSync = async () => {
const allCommitments = await genGetCommitments();
if (allCommitments.length === 0) return {};

const commitmentBlockNumbers = allCommitments.map(a => a.blockNumber).filter(n => n >= 0);
logger.info(`commitmentBlockNumbers: ${commitmentBlockNumbers}`);
const firstSeenBlockNumber = Math.min(...commitmentBlockNumbers);
Expand Down
11 changes: 10 additions & 1 deletion nightfall-deployer/src/index.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import Web3 from 'common-files/utils/web3.mjs';
import logger from 'common-files/utils/logger.mjs';
import circuits from './circuit-setup.mjs';
import setupContracts from './contract-setup.mjs';

// TODO these can be paralleled
async function main() {
await circuits.waitForZokrates();
await circuits.setupCircuits();
await setupContracts();
try {
await setupContracts();
} catch (err) {
if (err.message.includes('Transaction has been reverted by the EVM'))
logger.warn(
'Writing contract addresses to the State contract failed. This is probably because they are aready set. Did you already run deployer?',
);
else throw new Error(err);
}
Web3.disconnect();
}

Expand Down
1 change: 0 additions & 1 deletion nightfall-optimist/src/event-handlers/subscribe.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export async function waitForContract(contractName) {
try {
error = undefined;
const address = await getContractAddress(contractName);
logger.debug(`${contractName} contract address is ${address}`);
if (address === undefined) throw new Error(`${contractName} contract address was undefined`);
instance = getContractInstance(contractName, address);
return instance;
Expand Down
2 changes: 2 additions & 0 deletions nightfall-optimist/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import {
import { setChallengeWebSocketConnection } from './services/challenges.mjs';
import initialBlockSync from './services/state-sync.mjs';
import { setInstantWithdrawalWebSocketConnection } from './services/instant-withdrawal.mjs';
import { setProposer } from './routes/proposer.mjs';

const main = async () => {
try {
const proposer = new Proposer();
setProposer(proposer); // passes the proposer instance int the proposer routes
// subscribe to WebSocket events first
await subscribeToBlockAssembledWebSocketConnection(setBlockAssembledWebSocketConnection);
await subscribeToChallengeWebSocketConnection(setChallengeWebSocketConnection);
Expand Down
65 changes: 43 additions & 22 deletions nightfall-optimist/src/routes/proposer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,27 @@ import config from 'config';
import Timber from 'common-files/classes/timber.mjs';
import logger from 'common-files/utils/logger.mjs';
import { getContractInstance } from 'common-files/utils/contract.mjs';
import { enqueueEvent } from 'common-files/utils/event-queue.mjs';
import Block from '../classes/block.mjs';
import { Transaction, TransactionError } from '../classes/index.mjs';
import {
setRegisteredProposerAddress,
isRegisteredProposerAddressMine,
getMempoolTransactions,
getLatestTree,
} from '../services/database.mjs';
import { waitForContract } from '../event-handlers/subscribe.mjs';
import transactionSubmittedEventHandler from '../event-handlers/transaction-submitted.mjs';
import getProposers from '../services/proposer.mjs';

const router = express.Router();
const { STATE_CONTRACT_NAME, PROPOSERS_CONTRACT_NAME, SHIELD_CONTRACT_NAME, ZERO } = config;

let proposer;
export function setProposer(p) {
proposer = p;
}

/**
* Function to return a raw transaction that registers a proposer. This just
* provides the tx data, the user will need to append the registration bond
Expand All @@ -31,12 +39,39 @@ router.post('/register', async (req, res, next) => {
logger.debug(`register proposer endpoint received POST ${JSON.stringify(req.body, null, 2)}`);
try {
const { address } = req.body;
const proposersContractInstance = await getContractInstance(PROPOSERS_CONTRACT_NAME);
const txDataToSign = await proposersContractInstance.methods.registerProposer().encodeABI();
logger.debug('returning raw transaction data');
logger.silly(`raw transaction is ${JSON.stringify(txDataToSign, null, 2)}`);
const proposersContractInstance = await waitForContract(PROPOSERS_CONTRACT_NAME);
// the first thing to do is to check if the proposer is already registered on the blockchain
const proposers = (await getProposers()).map(p => p.thisAddress);
// if not, let's register it
let txDataToSign = '';
if (!proposers.includes(address)) {
txDataToSign = await proposersContractInstance.methods.registerProposer().encodeABI();
} else
logger.warn(
'Proposer was already registered on the blockchain - registration attempt ignored',
);
// when we get to here, either the proposer was already registered (txDataToSign === '')
// or we're just about to register them. We may or may not be registed locally
// with optimist though. Let's check and fix that if needed.
if (!(await isRegisteredProposerAddressMine(address))) {
logger.debug('Registering proposer locally');
await setRegisteredProposerAddress(address); // save the registration address
// We've just registered with optimist but if we were already registered on the blockchain,
// we should check if we're the current proposer and, if so, set things up so we start
// making blocks immediately
if (txDataToSign === '') {
logger.warn(
'Proposer was already registered on the blockchain but not with this Optimist instance - registering locally',
);
const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME);
const currentProposer = await stateContractInstance.methods.getCurrentProposer().call();
if (address === currentProposer.thisAddress) {
proposer.isMe = true;
await enqueueEvent(() => logger.info('Start Queue'), 0); // kickstart the queue
}
}
}
res.json({ txDataToSign });
setRegisteredProposerAddress(address); // save the registration address
} catch (err) {
logger.error(err);
next(err);
Expand All @@ -49,22 +84,8 @@ router.post('/register', async (req, res, next) => {
router.get('/proposers', async (req, res, next) => {
logger.debug(`list proposals endpoint received GET`);
try {
const proposersContractInstance = await getContractInstance(STATE_CONTRACT_NAME);
// proposers is an on-chain mapping so to get proposers we need to key to start iterating
// the safest to start with is the currentProposer
const currentProposer = await proposersContractInstance.methods.currentProposer().call();
const proposers = [];
let thisPtr = currentProposer.thisAddress;
// Loop through the circular list until we run back into the currentProposer.
do {
// eslint-disable-next-line no-await-in-loop
const proposer = await proposersContractInstance.methods.proposers(thisPtr).call();
proposers.push(proposer);
thisPtr = proposer.nextAddress;
} while (thisPtr !== currentProposer.thisAddress);

const proposers = getProposers();
logger.debug('returning raw transaction data');
logger.silly(`raw transaction is ${JSON.stringify(proposers, null, 2)}`);
res.json({ proposers });
} catch (err) {
logger.error(err);
Expand Down Expand Up @@ -157,12 +178,12 @@ router.post('/propose', async (req, res, next) => {
logger.debug(`propose endpoint received POST`);
logger.silly(`With content ${JSON.stringify(req.body, null, 2)}`);
try {
const { transactions, proposer, currentLeafCount } = req.body;
const { transactions, proposer: prop, currentLeafCount } = req.body;
// use the information we've been POSTED to assemble a block
// we use a Builder pattern because an async constructor is bad form
const block = await Block.build({
transactions,
proposer,
proposer: prop,
currentLeafCount,
});
logger.debug(`New block assembled ${JSON.stringify(block, null, 2)}`);
Expand Down
15 changes: 8 additions & 7 deletions nightfall-optimist/src/services/database.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const {
MONGO_URL,
OPTIMIST_DB,
TRANSACTIONS_COLLECTION,
METADATA_COLLECTION,
PROPOSER_COLLECTION,
CHALLENGER_COLLECTION,
SUBMITTED_BLOCKS_COLLECTION,
NULLIFIER_COLLECTION,
COMMIT_COLLECTION,
Expand Down Expand Up @@ -54,7 +55,7 @@ export async function addChallengerAddress(address) {
const db = connection.db(OPTIMIST_DB);
logger.debug(`Saving challenger address ${address}`);
const data = { challenger: address };
return db.collection(METADATA_COLLECTION).insertOne(data);
return db.collection(CHALLENGER_COLLECTION).insertOne(data);
}

/**
Expand All @@ -67,7 +68,7 @@ export async function removeChallengerAddress(address) {
const db = connection.db(OPTIMIST_DB);
logger.debug(`Removing challenger address ${address}`);
const data = { challenger: address };
return db.collection(METADATA_COLLECTION).deleteOne(data);
return db.collection(CHALLENGER_COLLECTION).deleteOne(data);
}

/**
Expand All @@ -76,7 +77,7 @@ Function to tell us if an address used to commit to a challenge belongs to us
export async function isChallengerAddressMine(address) {
const connection = await mongo.connection(MONGO_URL);
const db = connection.db(OPTIMIST_DB);
const metadata = await db.collection(METADATA_COLLECTION).findOne({ challenger: address });
const metadata = await db.collection(CHALLENGER_COLLECTION).findOne({ challenger: address });
return metadata !== null;
}

Expand Down Expand Up @@ -218,8 +219,8 @@ export async function setRegisteredProposerAddress(address) {
const connection = await mongo.connection(MONGO_URL);
const db = connection.db(OPTIMIST_DB);
logger.debug(`Saving proposer address ${address}`);
const data = { proposer: address };
return db.collection(METADATA_COLLECTION).insertOne(data);
const data = { _id: address };
Westlad marked this conversation as resolved.
Show resolved Hide resolved
return db.collection(PROPOSER_COLLECTION).insertOne(data);
}

/**
Expand All @@ -229,7 +230,7 @@ thus it should start assembling blocks of transactions.
export async function isRegisteredProposerAddressMine(address) {
const connection = await mongo.connection(MONGO_URL);
const db = connection.db(OPTIMIST_DB);
const metadata = await db.collection(METADATA_COLLECTION).findOne({ proposer: address });
const metadata = await db.collection(PROPOSER_COLLECTION).findOne({ _id: address });
logger.silly(`found registered proposer ${JSON.stringify(metadata, null, 2)}`);
return metadata;
}
Expand Down
Loading