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 4 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
19 changes: 19 additions & 0 deletions cli/lib/nf3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,25 @@ class Nf3 {
);
}

/**
Registers a proposer locally with the Optimist instance only. This will cause
Optimist to make blocks when this proposer is current but these will revert if
the proposer isn't registered on the blockchain too. This method is useful only
if the proposer is already registered on the blockchain (has paid their bond) and
for some reason the Optimist instance does not know about them, e.g. a new instance
has been created. The method 'registerProposer' will both register the proposer
with the blockchain and register locally with the optimist instance. So, if
that method has been used successfully, there is no need to also call this method
@method
@async
@returns {Promise} A promise that resolves to the Ethereum transaction receipt.
*/
async registerProposerLocally() {
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
64 changes: 55 additions & 9 deletions nightfall-optimist/src/routes/proposer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import config from 'config';
import Timber from 'common-files/classes/timber.mjs';
import logger from 'common-files/utils/logger.mjs';
import { getContractInstance } from 'common-files/utils/contract.mjs';
import { enqueueEvent } from 'common-files/utils/event-queue.mjs';
import Block from '../classes/block.mjs';
import { Transaction, TransactionError } from '../classes/index.mjs';
import {
Expand All @@ -21,6 +22,11 @@ import transactionSubmittedEventHandler from '../event-handlers/transaction-subm
const router = express.Router();
const { STATE_CONTRACT_NAME, PROPOSERS_CONTRACT_NAME, SHIELD_CONTRACT_NAME, ZERO } = config;

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

/**
* Function to return a raw transaction that registers a proposer. This just
* provides the tx data, the user will need to append the registration bond
Expand All @@ -34,12 +40,52 @@ router.post('/register', async (req, res, next) => {
const proposersContractInstance = await getContractInstance(PROPOSERS_CONTRACT_NAME);
const txDataToSign = await proposersContractInstance.methods.registerProposer().encodeABI();
logger.debug('returning raw transaction data');
logger.silly(`raw transaction is ${JSON.stringify(txDataToSign, null, 2)}`);
res.json({ txDataToSign });
setRegisteredProposerAddress(address); // save the registration address
await setRegisteredProposerAddress(address); // save the registration address
} catch (err) {
logger.error(err);
next(err);
if (err.message.includes('E11000 duplicate key error'))
// if we get a duplicate key error then the EVM will have reverted so we don't need to handle this separately
logger.warn(
'Duplicate key detected. You are probably trying to to register the proposer twice. Second attempt ignored',
);
else {
logger.error(err);
next(err);
}
}
});

/**
* Function to locally register a proposer with optimist. This call is similar to
* /register but it won't touch the blockchain. It will just cause optimist to make
* blocks when this proposer is the current proposer. It's useful if you are starting
* a new optimist instance but your proposer address is already registed with the blockchain
* It's idempotent: attempting to re-register a proposer will have no effect.
*/
router.post('/registerlocally', async (req, res, next) => {
logger.debug(
`local register proposer endpoint received POST ${JSON.stringify(req.body, null, 2)}`,
);
const { address } = req.body;
try {
await setRegisteredProposerAddress(address); // save the registration address
// we should also check if we're the current proposer because we're registered on
// the blockchain so we could be
const stateContractInstance = await getContractInstance(STATE_CONTRACT_NAME);
const currentProposer = await stateContractInstance.methods.getCurrentProposer().call();
if (address === currentProposer.thisAddress) {
proposer.isMe = true;
await enqueueEvent(() => logger.info('Start Queue'), 0); // kickstart the queue
}
res.sendStatus(200);
} catch (err) {
if (err.message.includes('duplicate key')) {
logger.warn(`Proposer ${address} is already registered locally`);
res.sendStatus(200);
} else {
logger.error(err);
next(err);
}
}
});

Expand All @@ -58,9 +104,9 @@ router.get('/proposers', async (req, res, next) => {
// Loop through the circular list until we run back into the currentProposer.
do {
// eslint-disable-next-line no-await-in-loop
const proposer = await proposersContractInstance.methods.proposers(thisPtr).call();
proposers.push(proposer);
thisPtr = proposer.nextAddress;
const prop = await proposersContractInstance.methods.proposers(thisPtr).call();
proposers.push(prop);
thisPtr = prop.nextAddress;
} while (thisPtr !== currentProposer.thisAddress);

logger.debug('returning raw transaction data');
Expand Down Expand Up @@ -157,12 +203,12 @@ router.post('/propose', async (req, res, next) => {
logger.debug(`propose endpoint received POST`);
logger.silly(`With content ${JSON.stringify(req.body, null, 2)}`);
try {
const { transactions, proposer, currentLeafCount } = req.body;
const { transactions, proposer: prop, currentLeafCount } = req.body;
// use the information we've been POSTED to assemble a block
// we use a Builder pattern because an async constructor is bad form
const block = await Block.build({
transactions,
proposer,
proposer: prop,
currentLeafCount,
});
logger.debug(`New block assembled ${JSON.stringify(block, null, 2)}`);
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
2 changes: 1 addition & 1 deletion test/e2e/protocol/challenger.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('Basic Challenger tests', () => {
await nf3Challenger.init(mnemonics.challenger);
// Challenger registration
await nf3Challenger.registerChallenger();
// Chalenger listening for incoming events
// Challenger listening for incoming events
nf3Challenger.startChallenger();
});

Expand Down
Loading