-
Notifications
You must be signed in to change notification settings - Fork 310
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
feat: snapshotting for e2e p2p tests #8896
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
c848c89
feat: snapshotting for e2e p2p tests
Maddiaa0 c55bb14
fmt
Maddiaa0 b678b89
Merge branch 'master' into md/e2e-p2p-snapshots
Maddiaa0 d72ee95
fix
Maddiaa0 3342623
fmt
Maddiaa0 20730dc
fix
Maddiaa0 b12f81f
Merge branch 'master' into md/e2e-p2p-snapshots
Maddiaa0 481537e
fix: enable additional params in e2e tests
Maddiaa0 f4ab1bd
fix: e2e_test script
Maddiaa0 af95b82
fix: wait on validator deploymnets
Maddiaa0 049fb75
Merge branch 'master' into md/e2e-p2p-snapshots
Maddiaa0 5c50f07
fix: anvil failures issue
Maddiaa0 01e8eca
fmt
Maddiaa0 295ef75
Merge branch 'master' into md/e2e-p2p-snapshots
Maddiaa0 e416dd4
fix
Maddiaa0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { type AztecNodeService } from '@aztec/aztec-node'; | ||
import { sleep } from '@aztec/aztec.js'; | ||
|
||
import fs from 'fs'; | ||
|
||
import { type NodeContext, createNodes } from '../fixtures/setup_p2p_test.js'; | ||
import { P2PNetworkTest, WAIT_FOR_TX_TIMEOUT } from './p2p_network.js'; | ||
import { createPXEServiceAndSubmitTransactions } from './shared.js'; | ||
|
||
// Don't set this to a higher value than 9 because each node will use a different L1 publisher account and anvil seeds | ||
const NUM_NODES = 4; | ||
const NUM_TXS_PER_NODE = 2; | ||
const BOOT_NODE_UDP_PORT = 40600; | ||
|
||
const DATA_DIR = './data/gossip'; | ||
|
||
describe('e2e_p2p_network', () => { | ||
let t: P2PNetworkTest; | ||
let nodes: AztecNodeService[]; | ||
|
||
beforeEach(async () => { | ||
t = await P2PNetworkTest.create('e2e_p2p_network', NUM_NODES, BOOT_NODE_UDP_PORT); | ||
await t.applyBaseSnapshots(); | ||
await t.setup(); | ||
}); | ||
|
||
afterEach(async () => { | ||
await t.stopNodes(nodes); | ||
await t.teardown(); | ||
for (let i = 0; i < NUM_NODES; i++) { | ||
fs.rmSync(`${DATA_DIR}-${i}`, { recursive: true, force: true }); | ||
} | ||
}); | ||
|
||
it('should rollup txs from all peers', async () => { | ||
// create the bootstrap node for the network | ||
if (!t.bootstrapNodeEnr) { | ||
throw new Error('Bootstrap node ENR is not available'); | ||
} | ||
// create our network of nodes and submit txs into each of them | ||
// the number of txs per node and the number of txs per rollup | ||
// should be set so that the only way for rollups to be built | ||
// is if the txs are successfully gossiped around the nodes. | ||
const contexts: NodeContext[] = []; | ||
t.logger.info('Creating nodes'); | ||
nodes = await createNodes( | ||
t.ctx.aztecNodeConfig, | ||
t.peerIdPrivateKeys, | ||
t.bootstrapNodeEnr, | ||
NUM_NODES, | ||
BOOT_NODE_UDP_PORT, | ||
DATA_DIR, | ||
); | ||
|
||
// wait a bit for peers to discover each other | ||
await sleep(4000); | ||
|
||
t.logger.info('Submitting transactions'); | ||
for (const node of nodes) { | ||
const context = await createPXEServiceAndSubmitTransactions(t.logger, node, NUM_TXS_PER_NODE); | ||
contexts.push(context); | ||
} | ||
|
||
t.logger.info('Waiting for transactions to be mined'); | ||
// now ensure that all txs were successfully mined | ||
await Promise.all( | ||
contexts.flatMap((context, i) => | ||
context.txs.map(async (tx, j) => { | ||
t.logger.info(`Waiting for tx ${i}-${j}: ${await tx.getTxHash()} to be mined`); | ||
return tx.wait({ timeout: WAIT_FOR_TX_TIMEOUT }); | ||
}), | ||
), | ||
); | ||
t.logger.info('All transactions mined'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import { type AztecNodeConfig, type AztecNodeService } from '@aztec/aztec-node'; | ||
import { EthCheatCodes } from '@aztec/aztec.js'; | ||
import { AZTEC_SLOT_DURATION, ETHEREUM_SLOT_DURATION, EthAddress } from '@aztec/circuits.js'; | ||
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; | ||
import { RollupAbi } from '@aztec/l1-artifacts'; | ||
import { type BootstrapNode } from '@aztec/p2p'; | ||
|
||
import getPort from 'get-port'; | ||
import { getContract } from 'viem'; | ||
import { privateKeyToAccount } from 'viem/accounts'; | ||
|
||
import { | ||
createBootstrapNodeFromPrivateKey, | ||
createValidatorConfig, | ||
generateNodePrivateKeys, | ||
generatePeerIdPrivateKeys, | ||
} from '../fixtures/setup_p2p_test.js'; | ||
import { type ISnapshotManager, type SubsystemsContext, createSnapshotManager } from '../fixtures/snapshot_manager.js'; | ||
import { getPrivateKeyFromIndex } from '../fixtures/utils.js'; | ||
|
||
// Use a fixed bootstrap node private key so that we can re-use the same snapshot and the nodes can find each other | ||
const BOOTSTRAP_NODE_PRIVATE_KEY = '080212208f988fc0899e4a73a5aee4d271a5f20670603a756ad8d84f2c94263a6427c591'; | ||
export const WAIT_FOR_TX_TIMEOUT = AZTEC_SLOT_DURATION * 3; | ||
|
||
export class P2PNetworkTest { | ||
private snapshotManager: ISnapshotManager; | ||
private baseAccount; | ||
|
||
public logger: DebugLogger; | ||
|
||
public ctx!: SubsystemsContext; | ||
public nodePrivateKeys: `0x${string}`[] = []; | ||
public peerIdPrivateKeys: string[] = []; | ||
|
||
public bootstrapNodeEnr: string = ''; | ||
|
||
constructor( | ||
testName: string, | ||
public bootstrapNode: BootstrapNode, | ||
public bootNodePort: number, | ||
private numberOfNodes: number, | ||
initialValidatorAddress: string, | ||
initialValidatorConfig: AztecNodeConfig, | ||
) { | ||
this.logger = createDebugLogger(`aztec:e2e_p2p:${testName}`); | ||
|
||
// Set up the base account and node private keys for the initial network deployment | ||
this.baseAccount = privateKeyToAccount(`0x${getPrivateKeyFromIndex(0)!.toString('hex')}`); | ||
this.nodePrivateKeys = generateNodePrivateKeys(1, numberOfNodes); | ||
this.peerIdPrivateKeys = generatePeerIdPrivateKeys(numberOfNodes); | ||
|
||
this.bootstrapNodeEnr = bootstrapNode.getENR().encodeTxt(); | ||
|
||
const initialValidators = [EthAddress.fromString(initialValidatorAddress)]; | ||
|
||
this.snapshotManager = createSnapshotManager(`e2e_p2p_network/${testName}`, process.env.E2E_DATA_PATH, { | ||
...initialValidatorConfig, | ||
l1BlockTime: ETHEREUM_SLOT_DURATION, | ||
salt: 420, | ||
initialValidators, | ||
}); | ||
} | ||
|
||
static async create(testName: string, numberOfNodes: number, basePort?: number) { | ||
const port = basePort || (await getPort()); | ||
|
||
const bootstrapNode = await createBootstrapNodeFromPrivateKey(BOOTSTRAP_NODE_PRIVATE_KEY, port); | ||
const bootstrapNodeEnr = bootstrapNode.getENR().encodeTxt(); | ||
|
||
const initialValidatorConfig = await createValidatorConfig({} as AztecNodeConfig, bootstrapNodeEnr); | ||
const intiailValidatorAddress = privateKeyToAccount(initialValidatorConfig.publisherPrivateKey).address; | ||
|
||
return new P2PNetworkTest( | ||
testName, | ||
bootstrapNode, | ||
port, | ||
numberOfNodes, | ||
intiailValidatorAddress, | ||
initialValidatorConfig, | ||
); | ||
} | ||
|
||
async applyBaseSnapshots() { | ||
await this.snapshotManager.snapshot('add-validators', async ({ deployL1ContractsValues, aztecNodeConfig }) => { | ||
const rollup = getContract({ | ||
address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), | ||
abi: RollupAbi, | ||
client: deployL1ContractsValues.walletClient, | ||
}); | ||
|
||
const txHashes: `0x${string}`[] = []; | ||
for (let i = 0; i < this.numberOfNodes; i++) { | ||
const account = privateKeyToAccount(this.nodePrivateKeys[i]!); | ||
const txHash = await rollup.write.addValidator([account.address]); | ||
txHashes.push(txHash); | ||
this.logger.debug(`Adding ${account.address} as validator`); | ||
} | ||
|
||
// Remove the setup validator | ||
const initialValidatorAddress = privateKeyToAccount(`0x${getPrivateKeyFromIndex(0)!.toString('hex')}`).address; | ||
const txHash = await rollup.write.removeValidator([initialValidatorAddress]); | ||
txHashes.push(txHash); | ||
|
||
// Wait for all the transactions adding validators to be mined | ||
await Promise.all( | ||
txHashes.map(txHash => | ||
deployL1ContractsValues.publicClient.waitForTransactionReceipt({ | ||
hash: txHash, | ||
}), | ||
), | ||
); | ||
|
||
//@note Now we jump ahead to the next epoch such that the validator committee is picked | ||
// INTERVAL MINING: If we are using anvil interval mining this will NOT progress the time! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably update this comment slightly. Since we are still doing the jump, just not mining the block, it is alluded by the next comment, so not a biggie. |
||
// Which means that the validator set will still be empty! So anyone can propose. | ||
const slotsInEpoch = await rollup.read.EPOCH_DURATION(); | ||
const timestamp = await rollup.read.getTimestampForSlot([slotsInEpoch]); | ||
const cheatCodes = new EthCheatCodes(aztecNodeConfig.l1RpcUrl); | ||
try { | ||
await cheatCodes.warp(Number(timestamp)); | ||
} catch (err) { | ||
this.logger.debug('Warp failed, time already satisfied'); | ||
} | ||
|
||
// Send and await a tx to make sure we mine a block for the warp to correctly progress. | ||
await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ | ||
hash: await deployL1ContractsValues.walletClient.sendTransaction({ | ||
to: this.baseAccount.address, | ||
value: 1n, | ||
account: this.baseAccount, | ||
}), | ||
}); | ||
}); | ||
} | ||
|
||
async setup() { | ||
this.ctx = await this.snapshotManager.setup(); | ||
|
||
// TODO(md): make it such that the test can set these up | ||
this.ctx.aztecNodeConfig.minTxsPerBlock = 4; | ||
this.ctx.aztecNodeConfig.maxTxsPerBlock = 4; | ||
} | ||
|
||
async stopNodes(nodes: AztecNodeService[]) { | ||
this.logger.info('Stopping nodes'); | ||
for (const node of nodes) { | ||
await node.stop(); | ||
} | ||
await this.bootstrapNode.stop(); | ||
this.logger.info('Nodes stopped'); | ||
} | ||
|
||
async teardown() { | ||
await this.snapshotManager.teardown(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { type AztecNodeService } from '@aztec/aztec-node'; | ||
import { sleep } from '@aztec/aztec.js'; | ||
|
||
import fs from 'fs'; | ||
|
||
import { type NodeContext, createNode, createNodes } from '../fixtures/setup_p2p_test.js'; | ||
import { P2PNetworkTest, WAIT_FOR_TX_TIMEOUT } from './p2p_network.js'; | ||
import { createPXEServiceAndSubmitTransactions } from './shared.js'; | ||
|
||
// Don't set this to a higher value than 9 because each node will use a different L1 publisher account and anvil seeds | ||
const NUM_NODES = 4; | ||
const NUM_TXS_PER_NODE = 2; | ||
const BOOT_NODE_UDP_PORT = 40400; | ||
|
||
const DATA_DIR = './data/rediscovery'; | ||
|
||
describe('e2e_p2p_rediscovery', () => { | ||
let t: P2PNetworkTest; | ||
let nodes: AztecNodeService[]; | ||
|
||
beforeEach(async () => { | ||
t = await P2PNetworkTest.create('e2e_p2p_rediscovery', NUM_NODES, BOOT_NODE_UDP_PORT); | ||
await t.applyBaseSnapshots(); | ||
await t.setup(); | ||
}); | ||
|
||
afterEach(async () => { | ||
await t.stopNodes(nodes); | ||
await t.teardown(); | ||
for (let i = 0; i < NUM_NODES; i++) { | ||
fs.rmSync(`${DATA_DIR}-${i}`, { recursive: true, force: true }); | ||
} | ||
}); | ||
|
||
it('should re-discover stored peers without bootstrap node', async () => { | ||
const contexts: NodeContext[] = []; | ||
nodes = await createNodes( | ||
t.ctx.aztecNodeConfig, | ||
t.peerIdPrivateKeys, | ||
t.bootstrapNodeEnr, | ||
NUM_NODES, | ||
BOOT_NODE_UDP_PORT, | ||
DATA_DIR, | ||
); | ||
|
||
// wait a bit for peers to discover each other | ||
await sleep(3000); | ||
|
||
// stop bootstrap node | ||
await t.bootstrapNode.stop(); | ||
|
||
// create new nodes from datadir | ||
const newNodes: AztecNodeService[] = []; | ||
|
||
// stop all nodes | ||
for (let i = 0; i < NUM_NODES; i++) { | ||
const node = nodes[i]; | ||
await node.stop(); | ||
t.logger.info(`Node ${i} stopped`); | ||
await sleep(1200); | ||
|
||
const newNode = await createNode( | ||
t.ctx.aztecNodeConfig, | ||
t.peerIdPrivateKeys[i], | ||
i + 1 + BOOT_NODE_UDP_PORT, | ||
undefined, | ||
i, | ||
`${DATA_DIR}-${i}`, | ||
); | ||
t.logger.info(`Node ${i} restarted`); | ||
newNodes.push(newNode); | ||
} | ||
nodes = newNodes; | ||
|
||
// wait a bit for peers to discover each other | ||
await sleep(2000); | ||
|
||
for (const node of newNodes) { | ||
const context = await createPXEServiceAndSubmitTransactions(t.logger, node, NUM_TXS_PER_NODE); | ||
contexts.push(context); | ||
} | ||
|
||
// now ensure that all txs were successfully mined | ||
|
||
await Promise.all( | ||
contexts.flatMap((context, i) => | ||
context.txs.map(async (tx, j) => { | ||
t.logger.info(`Waiting for tx ${i}-${j}: ${await tx.getTxHash()} to be mined`); | ||
return tx.wait({ timeout: WAIT_FOR_TX_TIMEOUT }); | ||
}), | ||
), | ||
); | ||
}); | ||
}); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.