Skip to content

Commit 34aec0e

Browse files
authored
Merge pull request #426 from EYBlockchain/westlad/ping-pong-test
Wait for twelve L1 confirmations before updating L2 data.
2 parents 0192862 + c933b08 commit 34aec0e

Some content is hidden

Large Commits have some content hidden by default. Use the searcx below for content that may be hidden.

47 files changed

+801
-5075
lines changed

‎./workflows/publish-docker-app-images.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ on:
33
push:
44
branches:
55
- master
6-
- westlad/ping-pong
6+
- westlad/ping-pong-test
77

88
jobs:
99
release:

‎./workflows/publish-docker-images.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ on:
33
push:
44
branches:
55
- master
6-
- westlad/ping-pong
6+
- westlad/ping-pong-test
77

88
jobs:
99
release:

‎cli/lib/nf3.mjs

+31-5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class Nf3 {
6464

6565
currentEnvironment;
6666

67+
notConfirmed = 0;
68+
6769
constructor(web3WsUrl, ethereumSigningKey, environment = ENVIRONMENTS.localhost, zkpKeys) {
6870
this.clientBaseUrl = environment.clientApiUrl;
6971
this.optimistBaseUrl = environment.optimistApiUrl;
@@ -183,8 +185,27 @@ class Nf3 {
183185

184186
if (this.ethereumSigningKey) {
185187
const signed = await this.web3.eth.accounts.signTransaction(tx, this.ethereumSigningKey);
186-
return this.web3.eth.sendSignedTransaction(signed.rawTransaction);
188+
// rather than waiting until we have a receipt, wait until we have enough confirmation blocks
189+
// then return the receipt.
190+
// TODO does this still work if there is a chain reorg or do we have to handle that?
191+
return new Promise(resolve => {
192+
console.log(`Confirming transaction ${signed.transactionHash}`);
193+
this.notConfirmed++;
194+
this.web3.eth
195+
.sendSignedTransaction(signed.rawTransaction)
196+
.on('confirmation', (number, receipt) => {
197+
if (number === 12) {
198+
this.notConfirmed--;
199+
console.log(
200+
`Transaction ${receipt.transactionHash} has been confirmed ${number} times.`,
201+
`Number of unconfirmed transactions is ${this.notConfirmed}`,
202+
);
203+
resolve(receipt);
204+
}
205+
});
206+
});
187207
}
208+
// TODO add wait for confirmations to the wallet functionality
188209
return this.web3.eth.sendTransaction(tx);
189210
}
190211

@@ -317,6 +338,9 @@ class Nf3 {
317338
ask: this.zkpKeys.ask,
318339
fee,
319340
});
341+
if (res.data.error && res.data.error === 'No suitable commitments') {
342+
throw new Error('No suitable commitments');
343+
}
320344
if (!offchain) {
321345
return this.submitTransaction(res.data.txDataToSign, this.shieldContractAddress, fee);
322346
}
@@ -708,7 +732,7 @@ class Nf3 {
708732
@method
709733
@async
710734
@param {Array} ercList - list of erc contract addresses to filter.
711-
@param {Boolean} filterByCompressedPkd - flag to indicate if request is filtered
735+
@param {Boolean} filterByCompressedPkd - flag to indicate if request is filtered
712736
ones compressed pkd
713737
@returns {Promise} This promise resolves into an object whose properties are the
714738
addresses of the ERC contracts of the tokens held by this account in Layer 2. The
@@ -748,7 +772,7 @@ class Nf3 {
748772
@method
749773
@async
750774
@param {Array} ercList - list of erc contract addresses to filter.
751-
@param {Boolean} filterByCompressedPkd - flag to indicate if request is filtered
775+
@param {Boolean} filterByCompressedPkd - flag to indicate if request is filtered
752776
ones compressed pkd
753777
@returns {Promise} This promise resolves into an object whose properties are the
754778
addresses of the ERC contracts of the tokens held by this account in Layer 2. The
@@ -769,7 +793,7 @@ class Nf3 {
769793
@method
770794
@async
771795
@param {Array} ercList - list of erc contract addresses to filter.
772-
@param {Boolean} filterByCompressedPkd - flag to indicate if request is filtered
796+
@param {Boolean} filterByCompressedPkd - flag to indicate if request is filtered
773797
ones compressed pkd
774798
@returns {Promise} This promise resolves into an object whose properties are the
775799
addresses of the ERC contracts of the tokens held by this account in Layer 2. The
@@ -816,7 +840,9 @@ class Nf3 {
816840
Set a Web3 Provider URL
817841
*/
818842
async setWeb3Provider() {
819-
this.web3 = new Web3(this.web3WsUrl, { transactionBlockTimeout: 200 }); // set a longer timeout
843+
this.web3 = new Web3(this.web3WsUrl);
844+
this.web3.eth.transactionBlockTimeout = 200;
845+
this.web3.eth.transactionConfirmationBlocks = 12;
820846
if (typeof window !== 'undefined') {
821847
if (window.ethereum && this.ethereumSigningKey === '') {
822848
this.web3 = new Web3(window.ethereum);

‎common-files/classes/public-inputs.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class PublicInputs {
1616
hash;
1717

1818
constructor(publicInputs) {
19+
// some inputs may be general numbers and some strings. We convert all to string, process and generalise.
1920
this.publicInputs = generalise(publicInputs.flat(Infinity));
2021
[, this.hash] = generalise(sha256(this.publicInputs).limbs(248, 2));
2122
}

‎common-files/package-lock.json

-5
This file was deleted.

‎common-files/utils/contract.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import config from 'config';
77
import Web3 from './web3.mjs';
88
import logger from './logger.mjs';
99

10-
const web3 = Web3.connection();
10+
export const web3 = Web3.connection();
1111

1212
const options = config.WEB3_OPTIONS;
1313

‎common-files/utils/event-queue.mjs

+65-40
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,15 @@ and catch these removals, processing them appropriately.
2222
import Queue from 'queue';
2323
import config from 'config';
2424
import logger from 'common-files/utils/logger.mjs';
25+
import { web3 } from 'common-files/utils/contract.mjs';
2526

26-
const { MAX_QUEUE } = config;
27+
const { MAX_QUEUE, CONFIRMATION_POLL_TIME, CONFIRMATIONS } = config;
2728
const fastQueue = new Queue({ autostart: true, concurrency: 1 });
2829
const slowQueue = new Queue({ autostart: true, concurrency: 1 });
30+
const removed = {}; // singleton holding transaction hashes of any removed events
2931
const stopQueue = new Queue({ autostart: false, concurrency: 1 });
3032
export const queues = [fastQueue, slowQueue, stopQueue];
3133

32-
/**
33-
This function will return a promise that resolves to true when the next highest
34-
priority queue is empty (priority goes in reverse order, prioity 0 is highest
35-
priority)
36-
*/
37-
function nextHigherPriorityQueueHasEmptied(priority) {
38-
return new Promise(resolve => {
39-
const listener = () => resolve();
40-
if (priority === 0) resolve(); // resolve if we're the highest priority queue
41-
queues[priority - 1].once('end', listener); // or when the higher priority queue empties
42-
if (queues[priority - 1].length === 0) {
43-
queues[priority - 1].removeListener('end', listener);
44-
resolve(); // or if it's already empty
45-
}
46-
});
47-
}
48-
4934
/**
5035
This function will wait until all the functions currently in a queue have been
5136
processed. It's useful if you want to ensure that Nightfall has had an opportunity
@@ -63,17 +48,64 @@ function flushQueue(priority) {
6348

6449
async function enqueueEvent(callback, priority, args) {
6550
queues[priority].push(async () => {
66-
// await nextHigherPriorityQueueHasEmptied(priority);
67-
// prevent conditionalmakeblock from running until fastQueue is emptied
6851
return callback(args);
6952
});
7053
}
7154

55+
/**
56+
This function will return when the event has been on chain for <confirmations> blocks
57+
It's useful to call this if you want to be sure that your event has been confirmed
58+
multiple times before you go ahead and process it.
59+
*/
60+
61+
function waitForConfirmation(eventObject) {
62+
logger.debug(`Confirming event ${eventObject.event}`);
63+
const { transactionHash, blockNumber } = eventObject;
64+
return new Promise((resolve, reject) => {
65+
let confirmedBlocks = 0;
66+
const id = setInterval(async () => {
67+
// get the transaction that caused the event
68+
// if it's been in a chain reorg then it will have been removed.
69+
if (removed[transactionHash] > 0) {
70+
clearInterval(id);
71+
removed[eventObject.transactionHash]--;
72+
reject(
73+
new Error(
74+
`Event removed; probable chain reorg. Event was ${eventObject.event}, transaction hash was ${transactionHash}`,
75+
),
76+
);
77+
}
78+
const currentBlock = await web3.eth.getBlock('latest');
79+
if (currentBlock.number - blockNumber > confirmedBlocks) {
80+
confirmedBlocks = currentBlock.number - blockNumber;
81+
}
82+
if (confirmedBlocks >= CONFIRMATIONS) {
83+
clearInterval(id);
84+
logger.debug(
85+
`Event ${eventObject.event} has been confirmed ${
86+
currentBlock.number - blockNumber
87+
} times`,
88+
);
89+
resolve(eventObject);
90+
}
91+
}, CONFIRMATION_POLL_TIME);
92+
});
93+
}
94+
7295
async function dequeueEvent(priority) {
7396
queues[priority].shift();
7497
}
7598

7699
async function queueManager(eventObject, eventArgs) {
100+
if (eventObject.removed) {
101+
// in this model we don't queue removals but we can use them to reject the event
102+
// Note the event object and its removal have the same transactionHash.
103+
// Also note that we can get more than one removal because the event could be re-mined
104+
// and removed again - so we need to keep count of the removals.
105+
if (!removed[eventObject.transactionHash]) removed[eventObject.transactionHash] = 0;
106+
removed[eventObject.transactionHash]++; // store the removal; waitForConfirmation will read this and reject.
107+
return;
108+
}
77109
// First element of eventArgs must be the eventHandlers object
78110
const [eventHandlers, ...args] = eventArgs;
79111
// handlers contains the functions needed to handle particular types of event,
@@ -84,30 +116,23 @@ async function queueManager(eventObject, eventArgs) {
84116
}
85117
// pull up the priority for the event being handled (removers have identical priority)
86118
const priority = eventHandlers.priority[eventObject.event];
87-
// if the event was removed then we have a chain reorg and need to reset our
88-
// layer 2 state accordingly.
89-
if (eventObject.removed) {
90-
if (!eventHandlers.removers[eventObject.event]) {
91-
logger.debug(`Unknown event removal ${eventObject.event} ignored`);
92-
return;
93-
}
94-
logger.info(`Queueing event removal ${eventObject.event}`);
95-
queues[priority].push(async () => {
96-
await nextHigherPriorityQueueHasEmptied(priority); // prevent eventHandlers running until the higher priority queue has emptied
97-
return eventHandlers.removers[eventObject.event](eventObject, args);
98-
});
99-
// otherwise queue the event for processing.
100-
} else {
101-
logger.info(`Queueing event ${eventObject.event}`);
102-
queues[priority].push(async () => {
103-
// await nextHigherPriorityQueueHasEmptied(priority); // prevent eventHandlers running until the higher priority queue has emptied
119+
logger.info(
120+
`Queueing event ${eventObject.event}, with transaction hash ${eventObject.transactionHash} and priority ${priority}`,
121+
);
122+
queues[priority].push(async () => {
123+
// we won't even think about processing an event until it's been confirmed many times
124+
try {
125+
await waitForConfirmation(eventObject);
104126
return eventHandlers[eventObject.event](eventObject, args);
105-
});
106-
}
127+
} catch (err) {
128+
return logger.warn(err.message);
129+
}
130+
});
131+
// }
107132
// the queue shouldn't get too long if we're keeping up with the blockchain.
108133
if (queues[priority].length > MAX_QUEUE)
109134
logger.warn(`The event queue has more than ${MAX_QUEUE} events`);
110135
}
111136

112137
/* ignore unused exports */
113-
export { flushQueue, enqueueEvent, dequeueEvent, queueManager };
138+
export { flushQueue, enqueueEvent, queueManager, dequeueEvent, waitForConfirmation };

‎compress-keys.mjs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// utility to compress a pkd
2+
import { generalise } from 'general-number';
3+
import { compressPublicKey } from './nightfall-client/src/services/keys.mjs';
4+
5+
const inputs = generalise(process.argv.slice(2, 4));
6+
console.log(`Compressed value = ${compressPublicKey(inputs).hex()}`);

‎config/default.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ module.exports = {
2222
LOG_LEVEL: process.env.LOG_LEVEL || 'debug',
2323
MONGO_URL: process.env.MONGO_URL || 'mongodb://localhost:27017/',
2424
ZKP_KEY_LENGTH: 32, // use a 32 byte key length for SHA compatibility
25+
CONFIRMATION_POLL_TIME: 1000, // time to wait before querying the blockchain (ms). Must be << block interval
26+
CONFIRMATIONS: 12, // number of confirmations to wait before accepting a transaction
2527
PROTOCOL: 'http://', // connect to zokrates microservice like this
2628
WEBSOCKET_PORT: process.env.WEBSOCKET_PORT || 8080,
2729
ZOKRATES_WORKER_HOST: process.env.ZOKRATES_WORKER_HOST || 'worker',
@@ -31,7 +33,6 @@ module.exports = {
3133
USE_INFURA: process.env.USE_INFURA === 'true',
3234
ETH_PRIVATE_KEY: process.env.ETH_PRIVATE_KEY, // owner's/deployer's private key
3335
ETH_ADDRESS: process.env.ETH_ADDRESS,
34-
USE_ROPSTEN_NODE: process.env.USE_ROPSTEN_NODE === 'true',
3536
OPTIMIST_HOST: process.env.OPTIMIST_HOST || 'optimist',
3637
OPTIMIST_PORT: process.env.OPTIMIST_PORT || 80,
3738
clientBaseUrl: `http://${process.env.CLIENT_HOST}:${process.env.CLIENT_PORT}`,

‎docker-compose.ganache.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ services:
88
ports:
99
- 8546:8546
1010
command:
11-
ganache-cli --accounts=10 --defaultBalanceEther=1000 --gasLimit=0x3B9ACA00 --deterministic -i 4378921 -p 8546
11+
ganache-cli --accounts=10 --defaultBalanceEther=1000 --gasLimit=0x3B9ACA00 --deterministic -i 4378921 -p 8546 -b 1
1212
--account="0x4775af73d6dc84a0ae76f8726bda4b9ecf187c377229cb39e1afa7a18236a69e,10000000000000000000000"
1313
--account="0x4775af73d6dc84a0ae76f8726bda4b9ecf187c377229cb39e1afa7a18236a69d,10000000000000000000000"
1414
--account="0xd42905d0582c476c4b74757be6576ec323d715a0c7dcff231b6348b7ab0190eb,10000000000000000000000"

‎docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ services:
4444
OPTIMIST_HOST: optimist1
4545
OPTIMIST_PORT: 80
4646
USE_STUBS: 'false' # make sure this flag is the same as in deployer service
47+
RETRIES: 80
4748
command: ['npm', 'run', 'dev']
4849

4950
client2:

‎nightfall-client/src/event-handlers/block-proposed.mjs

+18-13
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
storeCommitment,
88
countCommitments,
99
setSiblingInfo,
10-
countTransactionHashes,
1110
} from '../services/commitment-storage.mjs';
1211
import getProposeBlockCalldata from '../services/process-calldata.mjs';
1312
import Secrets from '../classes/secrets.mjs';
@@ -20,17 +19,20 @@ const { ZERO } = config;
2019
This handler runs whenever a BlockProposed event is emitted by the blockchain
2120
*/
2221
async function blockProposedEventHandler(data) {
23-
logger.info(`Received Block Proposed event`);
2422
// ivk will be used to decrypt secrets whilst nsk will be used to calculate nullifiers for commitments and store them
2523
const { blockNumber: currentBlockCount, transactionHash: transactionHashL1 } = data;
2624
const { transactions, block } = await getProposeBlockCalldata(data);
25+
logger.info(
26+
`Received Block Proposed event with layer 2 block number ${block.blockNumberL2} and tx hash ${transactionHashL1}`,
27+
);
2728
const latestTree = await getLatestTree();
2829
const blockCommitments = transactions.map(t => t.commitments.filter(c => c !== ZERO)).flat();
2930

30-
if ((await countTransactionHashes(block.transactionHashes)) > 0) {
31-
await saveBlock({ blockNumber: currentBlockCount, transactionHashL1, ...block });
32-
await Promise.all(transactions.map(t => saveTransaction({ transactionHashL1, ...t })));
33-
}
31+
// if ((await countCommitments(blockCommitments)) > 0) {
32+
await saveBlock({ blockNumber: currentBlockCount, transactionHashL1, ...block });
33+
logger.debug(`Saved L2 block ${block.blockNumberL2}, with tx hash ${transactionHashL1}`);
34+
await Promise.all(transactions.map(t => saveTransaction({ transactionHashL1, ...t })));
35+
// }
3436

3537
const dbUpdates = transactions.map(async transaction => {
3638
// filter out non zero commitments and nullifiers
@@ -41,6 +43,7 @@ async function blockProposedEventHandler(data) {
4143
(transaction.transactionType === '1' || transaction.transactionType === '2') &&
4244
(await countCommitments(nonZeroCommitments)) === 0
4345
) {
46+
let keysTried = 1;
4447
ivks.forEach((key, i) => {
4548
// decompress the secrets first and then we will decryp t the secrets from this
4649
const decompressedSecrets = Secrets.decompressSecrets(transaction.compressedSecrets);
@@ -51,20 +54,22 @@ async function blockProposedEventHandler(data) {
5154
nonZeroCommitments[0],
5255
);
5356
if (Object.keys(commitment).length === 0)
54-
logger.info("This encrypted message isn't for this recipient");
57+
logger.info(
58+
`This encrypted message isn't for this recipient, keys tried = ${keysTried++}`,
59+
);
5560
else {
5661
// console.log('PUSHED', commitment, 'nsks', nsks[i]);
5762
storeCommitments.push(storeCommitment(commitment, nsks[i]));
5863
}
5964
} catch (err) {
6065
logger.info(err);
61-
logger.info("This encrypted message isn't for this recipient");
66+
logger.info(
67+
`*This encrypted message isn't for this recipient, keys tried = ${keysTried++}`,
68+
);
6269
}
6370
});
6471
}
65-
await Promise.all(storeCommitments).catch(function (err) {
66-
logger.info(err);
67-
}); // control errors when storing commitments in order to ensure next Promise being executed
72+
await Promise.all(storeCommitments);
6873
return Promise.all([
6974
markOnChain(nonZeroCommitments, block.blockNumberL2, data.blockNumber, data.transactionHash),
7075
markNullifiedOnChain(
@@ -79,8 +84,8 @@ async function blockProposedEventHandler(data) {
7984
// await Promise.all(toStore);
8085
await Promise.all(dbUpdates);
8186
const updatedTimber = Timber.statelessUpdate(latestTree, blockCommitments);
82-
await saveTree(data.blockNumber, block.blockNumberL2, updatedTimber);
83-
87+
await saveTree(transactionHashL1, block.blockNumberL2, updatedTimber);
88+
logger.debug(`Saved tree for L2 block ${block.blockNumberL2}`);
8489
await Promise.all(
8590
// eslint-disable-next-line consistent-return
8691
blockCommitments.map(async (c, i) => {

0 commit comments

Comments
 (0)