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

Introduction of P2P networking #666

Merged
merged 101 commits into from
May 25, 2023
Merged

Introduction of P2P networking #666

merged 101 commits into from
May 25, 2023

Conversation

PhilWindle
Copy link
Collaborator

@PhilWindle PhilWindle commented May 23, 2023

Description

This PR makes a number of changes all related to introducing basic P2P networking and multi-sequencer operation into the system. The primary changes are:

  • AztecNode is now an interface (which it probably should have always been). AztecNodeService is now the concrete implementation created by e2e tests.
  • There is now a rollup-provider package that creates a standalone instance of the AztecNodeService with a simple http interface. The client side to this http interface is HttpNode.
  • aztec-cli can be used to generate transactions and submit them to a running instance of rollup-provider as well as deploying the current set of rollup contracts to chain.
  • The sequencer no longer retries publishing a failed block. Instead it rolls back changes to it's world state and starts a new block. This is required for multi-sequencer operation where no orchestrated sequencer selection protocol is in place.
  • Introduction of an ethereum package containing configuration and common code for interaction with L1.
  • The L2Block calldata hash is published along with unverified data to chain. This enables the archiver to correctly identify which batch of unverified data corresponds with the successfully published L2Block in cases where a sequencer published unverified data but was unsuccessful in publishing a block.
  • The p2p package now has the ability to start P2P networking utilising LibP2P. Given the required configuration, the P2PClient will create a libp2p client and attempt to make contact with a bootstrap node. From there it uses Kademlia to discover other peers.
  • Once contact has been made with other peers their transaction pools are synchronised and further transactions are propagated through the network using a protocol id of '/aztec/tx/{rollup contract address}'
  • The messaging protocol is a partial implementation of Ethereum's transaction exchange but without the RLP message packing. We just use a simple binary serialisation for the time being.
  • There is a new package p2p-bootstrap which contains a standalone 'bootstrap' node service. This is used solely for seeding the peer tables of new joiners to the network. It doesn't take part in transaction gossiping.
  • A new end to end test that verifies the operation of the P2P network and successful transaction gossiping

Checklist:

  • I have reviewed my diff in github, line by line.
  • Every change is related to the PR description.
  • I have linked this pull request to the issue(s) that it resolves.
  • There are no unexpected formatting changes, superfluous debug logs, or commented-out code.
  • The branch has been merged or rebased against the head of its merge target.
  • I'm happy for the PR to be merged at the reviewer's next convenience.

@PhilWindle PhilWindle marked this pull request as ready for review May 24, 2023 22:22
@PhilWindle PhilWindle changed the title Pw/big branch Introduction of P2P networking May 24, 2023
Copy link
Collaborator

@spalladino spalladino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got up to yarn-project/aztec-node/src/aztec-node/http-node.ts, will continue with the rest on Monday. Looking awesome so far. I like that you also tackled the problems that arise from having to identify the source of unverified data due to having multiple sequencers.

- noir-contracts: *yarn_project
- noir-compiler: *yarn_project
- sequencer-client: *yarn_project
- types: *yarn_project
- circuits-js: *yarn_project
- rollup-provider: *yarn_project

- e2e-join:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the new jobs to e2e-join requirements, so all unit tests finish before we start the e2e ones

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed that, thanks

sender: EthAddress.random().toString(),
l2BlockNum: BigInt(l2Block.number),
l2BlockHash: `0x${l2Block.getCalldataHash().toString('hex')}`,
sender: EthAddress.random(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sender: EthAddress.random(),
sender: EthAddress.random().toString(),

This should let you remove the nasty as unknown in the cast. See #648.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, done.

transactionHash: `0x${l2BlockNum}`,
} as Log<bigint, number, undefined, typeof UnverifiedDataEmitterAbi, 'ContractDeployment'>;
transactionHash: `0x${l2Block.number}`,
} as unknown as Log<bigint, number, undefined, typeof UnverifiedDataEmitterAbi, 'ContractDeployment'>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try removing here as well

@@ -37,5 +49,7 @@ export function getConfigEnvVars(): ArchiverConfig {
unverifiedDataEmitterContract: UNVERIFIED_DATA_EMITTER_ADDRESS
? EthAddress.fromString(UNVERIFIED_DATA_EMITTER_ADDRESS)
: EthAddress.ZERO,
searchStartBlock: SEARCH_START_BLOCK ? +SEARCH_START_BLOCK : 0,
apiKey: API_KEY || '',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid using empty strings to signal empty values. Let's change apiKey to be string | undefined if it could be unset.

@@ -123,20 +130,28 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa
this.nextL2BlockFromBlock,
nextExpectedRollupId,
);

// create the block number -> block hash mapping to ensure we retrieve the appropriate events
const blockHashMapping: { [key: string]: Buffer } = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const blockHashMapping: { [key: string]: Buffer } = {};
const blockHashMapping: { [key: string]: Buffer | undefined } = {};

So the compiler forces you to check that a given key is in the mapping before trying to use it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's import the artifact from noir-contracts instead of copying it here manually

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, that change is already done. Removed the legacy file.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this copied from end-to-end/src/deploy_l1_contracts.ts? If so, let's delete it from there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is. In Aztec Connect we had a blockchain package which handled everything. Environment config, contract wrappers, chain scanning and event parsing, publishing, contract deployment scripts etc. In Aztec 3 everything is scattered. So the archiver has code for block scanning, sequencer-client has the block publishing etc. I'm wondering if we want to re-instate blockchain as a place for common chain operations/config. I could move environment into it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, moved contract deployment code to common blockchain package.

Comment on lines +30 to +39
export function txToJson(tx: Tx) {
return {
data: tx.data?.toBuffer().toString('hex'),
unverified: tx.unverifiedData?.toBuffer().toString('hex'),
txRequest: tx.txRequest?.toBuffer().toString('hex'),
proof: tx.proof?.toBuffer().toString('hex'),
newContractPublicFunctions: tx.newContractPublicFunctions?.map(f => f.toBuffer().toString('hex')) ?? [],
enqueuedPublicFunctions: tx.enqueuedPublicFunctionCalls?.map(f => f.toBuffer().toString('hex')) ?? [],
};
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pass in a custom serializer object rather than manually converting every buffer to string. See the implementation of toFriendlyJSON for an example.

And aside from that, why not use the serialization methods we already have, and push everything over the wire as binary data?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just used strings for ease of debugging. Being able to get legible output by cURLing the endpoints is pretty useful. We probably want to change this for at least some endpoints for a production system.

* @param json - The JSON representation of the transaction.
* @returns The deserialised transaction.
*/
export function txFromJson(json: any) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, you can pass a reviver to JSON.parse to make your life easier.

return message.subarray(4);
}

/**
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created these serialisation functions as we didn't have anything for Tx objects. Could possibly just become toBuffer and fromBuffer on Tx. Would prefer to use the existing serialisation but we would need to handle undefined properties.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we would need to handle undefined properties.

We could probably introduce a Maybe or Optional wrapper that just writes down an initial byte to indicate if the value is set or not, similar to how the Vector class writes the size first.

However, since we're moving to msgpack now, maybe it makes sense to do things the msgpack way instead? We may want to loop in @ludamad here.

Copy link
Member

@Maddiaa0 Maddiaa0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave this a power read but everything looks great, nothing much to add ontop of santaigos points / just got a couple of nits, (maybe something around the deepcopy, seems we could make a generic function for that)

yarn-project/end-to-end/scripts/run_tests Outdated Show resolved Hide resolved
yarn-project/environment/src/aztec_chain.ts Outdated Show resolved Hide resolved
yarn-project/sequencer-client/src/publisher/config.ts Outdated Show resolved Hide resolved
yarn-project/archiver/src/archiver/archiver.ts Outdated Show resolved Hide resolved
yarn-project/aztec-node/src/aztec-node/http-node.ts Outdated Show resolved Hide resolved
yarn-project/blockchain/src/aztec_chain.ts Outdated Show resolved Hide resolved
yarn-project/blockchain/package.json Outdated Show resolved Hide resolved
Copy link
Collaborator

@ludamad ludamad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM at a powerglance

@PhilWindle PhilWindle merged commit f919dd4 into master May 25, 2023
@PhilWindle PhilWindle deleted the pw/big-branch branch May 25, 2023 16:13
Copy link
Collaborator

@spalladino spalladino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hell of a PR! 🚀

Comment on lines +152 to +157
// shutdown all nodes.
for (const context of contexts) {
await context.node.stop();
await context.rpcServer.stop();
}
await bootstrapNode.stop();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving to an afterEach block, so it runs even if the test fails

Comment on lines +138 to +139
// now ensure that all txs were successfully mined
for (const context of contexts) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to also assert that the current "view" of the world state is the same for all nodes?

Comment on lines +9 to +19
p2p-bootstrap:
image: aztecprotocol/p2p-bootstrap:latest
ports:
- '40400:40400'
command: 'start'
environment:
DEBUG: 'aztec:*'
P2P_TCP_LISTEN_PORT: 40400
P2P_TCP_LISTEN_IP: '0.0.0.0'
P2P_ANNOUNCE_HOSTNAME: 'p2p-bootstrap'
PEER_ID_PRIVATE_KEY: '0a260024080112205ea53185db2e52dae74d0d4d6cadc494174810d0a713cd09b0ac517c38bc781e1224080112205ea53185db2e52dae74d0d4d6cadc494174810d0a713cd09b0ac517c38bc781e1a44080112402df8b977f356c6e34fa021c9647973234dff4df706c185794405aafb556723cf5ea53185db2e52dae74d0d4d6cadc494174810d0a713cd09b0ac517c38bc781e'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used? I saw a createBootstrap in the e2e test that hints that a different bootstrap node is created in the same process as the test.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't currently used. I had a vision of running the bootstrap node and the test as different containers but the build system isn't setup to publish the images. Everything has to run inside of e2e. I left this here in case I get time to take another look.

Comment on lines +20 to +23
return {
chainInfo: foundry,
rpcUrl,
} as EthereumChain;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid as, since it hides type errors. It's better to set the expected return type in the function declaration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +23 to +26
return {
chainInfo: chain,
rpcUrl: chain.rpcUrls.default.http[0],
} as EthereumChain;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return message.subarray(4);
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we would need to handle undefined properties.

We could probably introduce a Maybe or Optional wrapper that just writes down an initial byte to indicate if the value is set or not, similar to how the Vector class writes the size first.

However, since we're moving to msgpack now, maybe it makes sense to do things the msgpack way instead? We may want to loop in @ludamad here.

Comment on lines +178 to +179
const postData = JSON.parse((await stream.readAll()) as string);
const tx = txFromJson(postData);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to put in some validation here, to make sure we're not passing in garbage within a tx

@@ -35,7 +38,8 @@ export function getConfigEnvVars(): SequencerClientConfig {
unverifiedDataEmitterContract: UNVERIFIED_DATA_EMITTER_ADDRESS
? EthAddress.fromString(UNVERIFIED_DATA_EMITTER_ADDRESS)
: EthAddress.ZERO,
publisherPrivateKey: Buffer.from(SEQ_PUBLISHER_PRIVATE_KEY || ''),
publisherPrivateKey: Buffer.from(SEQ_PUBLISHER_PRIVATE_KEY || '', 'hex'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

@@ -272,7 +289,7 @@ export class L1Publisher implements L2BlockReceiver {
try {
return await this.txSender.getTransactionReceipt(txHash);
} catch (err) {
this.log(`Error getting tx receipt`, err);
//this.log(`Error getting tx receipt`, err);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for silencing those annoying logs in the e2e tests? If so, can we differentiate between a "receipt not yet available" and an actual error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, can't remember now. I've uncommented it for now.

* @param tx - The transaction to be cloned.
* @returns The cloned transaction.
*/
static clone(tx: Tx): Tx {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think cloneDeep could handle this automatically

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants