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

feat: introduce validator client #7854

Merged
merged 44 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
78dbe1b
feat(vc): introduce validator client
Maddiaa0 Aug 1, 2024
34b32af
temp
Maddiaa0 Aug 5, 2024
59b82a6
move some stuff around
Maddiaa0 Aug 6, 2024
f3fe10b
feat: memory attestation pull
Maddiaa0 Aug 7, 2024
3e95501
chore: add gossipable to cpell
Maddiaa0 Aug 7, 2024
2c0f307
chore: introduce block with attestations structure
Maddiaa0 Aug 7, 2024
0f515e5
chore: update validotr service
Maddiaa0 Aug 7, 2024
b2f78ed
feat: light integration with sequencer
Maddiaa0 Aug 7, 2024
8831381
fmt
Maddiaa0 Aug 7, 2024
b7a5131
fmt
Maddiaa0 Aug 7, 2024
8b3c55e
feat: start p2p test rework
Maddiaa0 Aug 7, 2024
6fa1fc1
temp: change attestation map
Maddiaa0 Aug 8, 2024
437635a
feat: test that requires collected attestations to produce block
Maddiaa0 Aug 8, 2024
5d2808b
fixes: fmt + types
Maddiaa0 Aug 8, 2024
0c50ee9
fix: header
Maddiaa0 Aug 8, 2024
4c0a943
Merge branch 'master' into md/08-01-feat_vc_introduce_validator_client
Maddiaa0 Aug 8, 2024
39f5d90
fixes
Maddiaa0 Aug 8, 2024
c8f1dba
Merge branch 'master' into md/08-01-feat_vc_introduce_validator_client
Maddiaa0 Aug 9, 2024
45ac399
chore: rename BaseHashType to Buffer32
Maddiaa0 Aug 12, 2024
a1ff397
chore: signature type rework
Maddiaa0 Aug 12, 2024
33fb87f
fix: viem types
Maddiaa0 Aug 12, 2024
759982b
f,t
Maddiaa0 Aug 12, 2024
d2f0854
fix: sign over archive
Maddiaa0 Aug 13, 2024
0cc6920
sweep
Maddiaa0 Aug 13, 2024
1a0d7e8
Merge branch 'master' into md/08-01-feat_vc_introduce_validator_client
Maddiaa0 Aug 13, 2024
07c4203
chore: rm attestations test + clean
Maddiaa0 Aug 13, 2024
064db45
forge fmt
Maddiaa0 Aug 13, 2024
95dfabf
chore: sweep + annotate issues
Maddiaa0 Aug 14, 2024
842d8ce
fmt
Maddiaa0 Aug 14, 2024
27b33c5
toggle is devnet
Maddiaa0 Aug 14, 2024
e9a32d2
fix: update constants
Maddiaa0 Aug 14, 2024
c284de0
fix: yarn prepare
Maddiaa0 Aug 14, 2024
27ad8bb
fix: yarn config
Maddiaa0 Aug 14, 2024
f5b45b6
fix: webpack
Maddiaa0 Aug 14, 2024
f3adc08
Merge branch 'master' into md/08-01-feat_vc_introduce_validator_client
Maddiaa0 Aug 14, 2024
2c1aeb7
fix: tests are coupled apparently
Maddiaa0 Aug 14, 2024
0e25b4e
fix: revert hash caching on header :(
Maddiaa0 Aug 14, 2024
c4614fc
fix: add default key for validator client in sandbox
Maddiaa0 Aug 14, 2024
8ee6efc
fix: fmt
Maddiaa0 Aug 14, 2024
8986e68
fmt
Maddiaa0 Aug 14, 2024
eefca64
Merge branch 'master' into md/08-01-feat_vc_introduce_validator_client
Maddiaa0 Aug 14, 2024
dd80451
review fixes
Maddiaa0 Aug 16, 2024
bde2469
Merge branch 'master' into md/08-01-feat_vc_introduce_validator_client
Maddiaa0 Aug 16, 2024
5a47571
fix: constants value wrong
Maddiaa0 Aug 16, 2024
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"fuzzers",
"gitmodules",
"gitrepo",
"Gossipable",
"gossipsub",
"grumpkin",
"gtest",
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@aztec/simulator": "workspace:^",
"@aztec/telemetry-client": "workspace:^",
"@aztec/types": "workspace:^",
"@aztec/validator-client": "workspace:^",
"@aztec/world-state": "workspace:^",
"koa": "^2.14.2",
"koa-router": "^12.0.0",
Expand Down
13 changes: 13 additions & 0 deletions yarn-project/aztec-node/src/aztec-node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundatio
import { type P2PConfig, p2pConfigMappings } from '@aztec/p2p';
import { type ProverClientConfig, proverClientConfigMappings } from '@aztec/prover-client';
import { type SequencerClientConfig, sequencerClientConfigMappings } from '@aztec/sequencer-client';
import { type ValidatorClientConfig, validatorClientConfigMappings } from '@aztec/validator-client';
import { type WorldStateConfig, worldStateConfigMappings } from '@aztec/world-state';

import { readFileSync } from 'fs';
Expand All @@ -16,6 +17,7 @@ export { sequencerClientConfigMappings, SequencerClientConfig } from '@aztec/seq
*/
export type AztecNodeConfig = ArchiverConfig &
SequencerClientConfig &
ValidatorClientConfig &
ProverClientConfig &
WorldStateConfig &
P2PConfig & {
Expand All @@ -24,11 +26,16 @@ export type AztecNodeConfig = ArchiverConfig &

/** Whether the prover is disabled for this node. */
disableProver: boolean;

// TODO(md): needed?
/** Whether the validator is disabled for this node */
disableValidator: boolean;
};

export const aztecNodeConfigMappings: ConfigMappingsType<AztecNodeConfig> = {
...archiverConfigMappings,
...sequencerClientConfigMappings,
...validatorClientConfigMappings,
...proverClientConfigMappings,
...worldStateConfigMappings,
...p2pConfigMappings,
Expand All @@ -44,6 +51,12 @@ export const aztecNodeConfigMappings: ConfigMappingsType<AztecNodeConfig> = {
default: false,
description: 'Whether the prover is disabled for this node.',
},
disableValidator: {
env: 'VALIDATOR_DISABLED',
parseEnv: (val: string) => ['1', 'true'].includes(val),
default: false,
description: 'Whether the validator is disabled for this node.',
},
};

/**
Expand Down
16 changes: 14 additions & 2 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { Timer } from '@aztec/foundation/timer';
import { type AztecKVStore } from '@aztec/kv-store';
import { createStore, openTmpStore } from '@aztec/kv-store/utils';
import { SHA256Trunc, StandardTree, UnbalancedTree } from '@aztec/merkle-tree';
import { AztecKVTxPool, type P2P, createP2PClient } from '@aztec/p2p';
import { AztecKVTxPool, InMemoryAttestationPool, type P2P, createP2PClient } from '@aztec/p2p';
import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer';
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';
Expand All @@ -76,6 +76,7 @@ import {
type ContractInstanceWithAddress,
type ProtocolContractAddresses,
} from '@aztec/types/contracts';
import { createValidatorClient } from '@aztec/validator-client';
import { MerkleTrees, type WorldStateSynchronizer, createWorldStateSynchronizer } from '@aztec/world-state';

import { type AztecNodeConfig, getPackageInfo } from './config.js';
Expand Down Expand Up @@ -152,7 +153,13 @@ export class AztecNodeService implements AztecNode {
config.transactionProtocol = `/aztec/tx/${config.l1Contracts.rollupAddress.toString()}`;

// create the tx pool and the p2p client, which will need the l2 block source
const p2pClient = await createP2PClient(config, store, new AztecKVTxPool(store, telemetry), archiver);
const p2pClient = await createP2PClient(
config,
store,
new AztecKVTxPool(store, telemetry),
new InMemoryAttestationPool(),
archiver,
);

// now create the merkle trees and the world state synchronizer
const worldStateSynchronizer = await createWorldStateSynchronizer(config, store, archiver);
Expand All @@ -175,11 +182,16 @@ export class AztecNodeService implements AztecNode {
throw new Error("Can't start a sequencer without a prover");
}

// TODO: likely that the sequencer will consume a validator client and use it
// to broadcast the blocks
const validatorClient = createValidatorClient(config, p2pClient);

// now create the sequencer
const sequencer = config.disableSequencer
? undefined
: await SequencerClient.new(
config,
validatorClient,
p2pClient,
worldStateSynchronizer,
archiver,
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/circuit-types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
"jest": "^29.5.0",
"jest-mock-extended": "^3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"viem": "^2.7.15"
},
"files": [
"dest",
Expand Down
29 changes: 28 additions & 1 deletion yarn-project/circuit-types/src/p2p/block_attestation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Header } from '@aztec/circuits.js';
import { EthAddress, Header } from '@aztec/circuits.js';
import { BaseHashType } from '@aztec/foundation/hash';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { recoverAddress } from 'viem';

import { Gossipable } from './gossipable.js';
import { TopicType, createTopicString } from './topic_type.js';

Expand All @@ -20,6 +22,8 @@ export class BlockAttestationHash extends BaseHashType {
export class BlockAttestation extends Gossipable {
static override p2pTopic: string;

private sender: EthAddress | undefined;

constructor(
/** The block header the attestation is made over */
public readonly header: Header,
Expand All @@ -37,6 +41,25 @@ export class BlockAttestation extends Gossipable {
return BlockAttestationHash.fromField(this.header.hash());
}

/**Get sender
*
* Lazily evaluate and cache the sender of the attestation
* @returns The sender of the attestation
*/
async getSender() {
if (!this.sender) {
// Recover the sender from the attestation
const address = await recoverAddress({
hash: this.p2pMessageIdentifier().to0xString(),
signature: this.signature,
});
// Cache the sender for later use
this.sender = EthAddress.fromString(address);
}

return this.sender;
}

toBuffer(): Buffer {
return serializeToBuffer([this.header, this.signature.length, this.signature]);
}
Expand All @@ -45,4 +68,8 @@ export class BlockAttestation extends Gossipable {
const reader = BufferReader.asReader(buf);
return new BlockAttestation(reader.readObject(Header), reader.readBuffer());
}

static empty(): BlockAttestation {
return new BlockAttestation(Header.empty(), Buffer.from([]));
}
}
2 changes: 1 addition & 1 deletion yarn-project/circuit-types/src/p2p/block_proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Header } from '@aztec/circuits.js';
import { BaseHashType } from '@aztec/foundation/hash';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { TxHash } from '../index.js';
import { TxHash } from '../tx/tx_hash.js';
import { Gossipable } from './gossipable.js';
import { TopicType, createTopicString } from './topic_type.js';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
describe('Block with Attestations', () => {
it('Should be able to be constructed from attestations', () => {
//todo
expect(true).toEqual(false);
});
});
21 changes: 21 additions & 0 deletions yarn-project/circuit-types/src/p2p/block_with_attestations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type BlockAttestation } from './block_attestation.js';
import { type BlockProposal } from './block_proposal.js';

/**
* BlockWithAttestations
* A data structure that contains a block proposal with it's attestations attached
*/
export class BlockWithAttestations {
constructor(
public readonly block: BlockProposal,
/** Signatures of the attestations */
public readonly attestations: Buffer[],
) {}

static fromBlockAndBlockAttestations(block: BlockProposal, attestations: BlockAttestation[]): BlockWithAttestations {
return new BlockWithAttestations(
block,
attestations.map(attestation => attestation.signature),
);
}
}
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/p2p/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './block_attestation.js';
export * from './block_with_attestations.js';
export * from './block_proposal.js';
export * from './interface.js';
export * from './gossipable.js';
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/circuits.js/src/structs/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { StateReference } from './state_reference.js';

/** A header of an L2 block. */
export class Header {
private blockHash: Fr | undefined;
Maddiaa0 marked this conversation as resolved.
Show resolved Hide resolved

constructor(
/** Snapshot of archive before the block is applied. */
public lastArchive: AppendOnlyTreeSnapshot,
Expand Down Expand Up @@ -123,6 +125,9 @@ export class Header {
}

hash(): Fr {
return poseidon2HashWithSeparator(this.toFields(), GeneratorIndex.BLOCK_HASH);
if (!this.blockHash) {
this.blockHash = poseidon2HashWithSeparator(this.toFields(), GeneratorIndex.BLOCK_HASH);
}
return this.blockHash;
}
}
140 changes: 140 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p_attestations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import { type AztecNodeConfig, type AztecNodeService } from '@aztec/aztec-node';
import { CompleteAddress, type DebugLogger, Fr, GrumpkinScalar, TxStatus, sleep } from '@aztec/aztec.js';
import { type BootstrapNode } from '@aztec/p2p';
import { type PXEService, createPXEService, getPXEServiceConfig as getRpcConfig } from '@aztec/pxe';

import fs from 'fs';

import {
type NodeContext,
createBootstrapNode,
createNodes,
generatePeerIdPrivateKeys,
} from './fixtures/setup_p2p_test.js';
import { setup } from './fixtures/utils.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_BLOCK = 4;
const BOOT_NODE_UDP_PORT = 40400;

const PEER_ID_PRIVATE_KEYS = generatePeerIdPrivateKeys(NUM_NODES);

describe('e2e_p2p_attestations', () => {
let config: AztecNodeConfig;
let logger: DebugLogger;
let teardown: () => Promise<void>;
let bootstrapNode: BootstrapNode;
let bootstrapNodeEnr: string;

beforeEach(async () => {
// TODO: cleanup this test Enable validators
({ teardown, config, logger } = await setup(0));
bootstrapNode = await createBootstrapNode(BOOT_NODE_UDP_PORT);
bootstrapNodeEnr = bootstrapNode.getENR().encodeTxt();

// TODO: refactor? Config settings
config.minTxsPerBlock = NUM_TXS_PER_BLOCK;
config.maxTxsPerBlock = NUM_TXS_PER_BLOCK;
});

afterEach(() => teardown());

afterAll(() => {
for (let i = 0; i < NUM_NODES; i++) {
fs.rmSync(`./data-${i}`, { recursive: true, force: true });
}
});

it('should collect attestations from all peers', async () => {
// create the bootstrap node for the network
if (!bootstrapNodeEnr) {
throw new Error('Bootstrap node ENR is not available');
}

// Create a network of nodes where they will not produce a block until
// they have collected enough attestations for the block from their peers
// at this moment in time the threshold is hardcoded within the validator client
const contexts: NodeContext[] = [];
const nodes: AztecNodeService[] = await createNodes(
config,
PEER_ID_PRIVATE_KEYS,
bootstrapNodeEnr,
NUM_NODES,
BOOT_NODE_UDP_PORT,
/*activate validators*/ true,
);

// wait a bit for peers to discover each other
await sleep(2000);

// just send one transaction for now
for (const node of nodes) {
const context = await createPXEServiceAndSubmitTransactions(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) => {
logger.info(`Waiting for tx ${i}-${j}: ${await tx.getTxHash()} to be mined`);
return tx.wait();
}),
),
);

// shutdown all nodes.
for (const context of contexts) {
await context.node.stop();
await context.pxeService.stop();
}
await bootstrapNode.stop();
});

// creates an instance of the PXE and submit a given number of transactions to it.
const createPXEServiceAndSubmitTransactions = async (node: AztecNodeService): Promise<NodeContext> => {
const rpcConfig = getRpcConfig();
const pxeService = await createPXEService(node, rpcConfig, true);

const secretKey = Fr.random();
const completeAddress = CompleteAddress.fromSecretKeyAndPartialAddress(secretKey, Fr.random());
await pxeService.registerAccount(secretKey, completeAddress.partialAddress);

const tx = await submitTx(pxeService);
return {
txs: [tx],
account: completeAddress.address,
pxeService,
node,
};
};

// submits a set of transactions to the provided Private eXecution Environment (PXE)
const submitTx = async (pxe: PXEService) => {
const accountManager = getSchnorrAccount(pxe, Fr.random(), GrumpkinScalar.random(), Fr.random());
const deployMethod = await accountManager.getDeployMethod();
await deployMethod.create({
contractAddressSalt: accountManager.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
universalDeploy: true,
});
await deployMethod.prove({});
const tx = deployMethod.send();

const txHash = await tx.getTxHash();

logger.info(`Tx sent with hash ${txHash}`);
const receipt = await tx.getReceipt();
expect(receipt).toEqual(
expect.objectContaining({
status: TxStatus.PENDING,
error: '',
}),
);
logger.info(`Receipt received for ${txHash}`);
return tx;
};
});
Loading
Loading