From 6b97f73442eeccbec6752b79b75c043af947d9e3 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 23 Mar 2023 07:36:01 -0300 Subject: [PATCH] L2 block publisher (#45) * Update l1-contracts submodule to latest * Port skeleton of the publisher from aztec-connect * Create yarn-projct/l1-contracts for ts contract abstractions * Initial version of rollup publisher with mock data using aztec/ethereum.js * Fix tests and add missing awaits on publisher * Integration test for rollup publisher using anvil * Remove querying env vars (not responsibility of each lib) * Export archiver modules and do not start unless invoked directly * Kill PublisherDataEncoder and use the encode method from archive instead * Add l1-contracts to yarn-project-base dockerfile * Merge fix --------- Co-authored-by: PhilWindle --- README.md | 1 + l1-contracts | 2 +- .../archiver/src/archiver/archiver.ts | 2 +- yarn-project/l1-contracts/.eslintrc.cjs | 6 + yarn-project/l1-contracts/Dockerfile | 14 ++ yarn-project/l1-contracts/README.md | 3 + yarn-project/l1-contracts/contracts.json | 9 ++ yarn-project/l1-contracts/package.json | 47 +++++++ .../src/aztec-ethereumjs-contracts/Rollup.ts | 42 ++++++ .../aztec-ethereumjs-contracts/RollupAbi.ts | 86 ++++++++++++ yarn-project/l1-contracts/src/index.ts | 6 + yarn-project/l1-contracts/tsconfig.dest.json | 4 + yarn-project/l1-contracts/tsconfig.json | 9 ++ yarn-project/package.json | 1 + yarn-project/sequencer-client/.eslintrc.cjs | 4 + .../jest.integration.config.json | 13 ++ yarn-project/sequencer-client/package.json | 10 +- yarn-project/sequencer-client/src/config.ts | 7 + .../publisher/aztec-ethereumjs-tx-sender.ts | 42 ++++++ .../sequencer-client/src/publisher/index.ts | 9 ++ .../src/publisher/l2-block-publisher.test.ts | 78 +++++++++++ .../src/publisher/l2-block-publisher.ts | 131 ++++++++++++++++++ yarn-project/sequencer-client/src/receiver.ts | 9 ++ yarn-project/sequencer-client/src/utils.ts | 9 ++ .../test/l2-block-publisher.test.ts | 75 ++++++++++ yarn-project/sequencer-client/tsconfig.json | 2 +- yarn-project/yarn-project-base/Dockerfile | 1 + yarn-project/yarn.lock | 97 ++++++++++++- 28 files changed, 711 insertions(+), 8 deletions(-) create mode 100644 yarn-project/l1-contracts/.eslintrc.cjs create mode 100644 yarn-project/l1-contracts/Dockerfile create mode 100644 yarn-project/l1-contracts/README.md create mode 100644 yarn-project/l1-contracts/contracts.json create mode 100644 yarn-project/l1-contracts/package.json create mode 100644 yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/Rollup.ts create mode 100644 yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/RollupAbi.ts create mode 100644 yarn-project/l1-contracts/src/index.ts create mode 100644 yarn-project/l1-contracts/tsconfig.dest.json create mode 100644 yarn-project/l1-contracts/tsconfig.json create mode 100644 yarn-project/sequencer-client/jest.integration.config.json create mode 100644 yarn-project/sequencer-client/src/config.ts create mode 100644 yarn-project/sequencer-client/src/publisher/aztec-ethereumjs-tx-sender.ts create mode 100644 yarn-project/sequencer-client/src/publisher/index.ts create mode 100644 yarn-project/sequencer-client/src/publisher/l2-block-publisher.test.ts create mode 100644 yarn-project/sequencer-client/src/publisher/l2-block-publisher.ts create mode 100644 yarn-project/sequencer-client/src/receiver.ts create mode 100644 yarn-project/sequencer-client/src/utils.ts create mode 100644 yarn-project/sequencer-client/test/l2-block-publisher.test.ts diff --git a/README.md b/README.md index 69247931a83..0089c0f3f07 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ The Aztec 3 system consists of the following sub projects. - `ethereum.js` - `kernel-simulator` - `key-store` +- `l1-contracts` - `p2p` - `prover-client` - `aztec-node` diff --git a/l1-contracts b/l1-contracts index 74316d6e71b..7f01e8875c1 160000 --- a/l1-contracts +++ b/l1-contracts @@ -1 +1 @@ -Subproject commit 74316d6e71bc995dad94abffa01a1a50ffc1ba4b +Subproject commit 7f01e8875c187e56feda3e3a7bf6d8d1ce638b62 diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index dbf45e2aeba..7d090ddd21e 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -212,7 +212,7 @@ export class Archiver implements L2BlockSource { * @param l2BlockNum - Block number. * @returns Random L2Block. */ -function mockRandomL2Block(l2BlockNum: number): L2Block { +export function mockRandomL2Block(l2BlockNum: number): L2Block { const newNullifiers = [randomBytes(32), randomBytes(32), randomBytes(32), randomBytes(32)]; const newCommitments = [randomBytes(32), randomBytes(32), randomBytes(32), randomBytes(32)]; const newContracts: Buffer[] = [randomBytes(32)]; diff --git a/yarn-project/l1-contracts/.eslintrc.cjs b/yarn-project/l1-contracts/.eslintrc.cjs new file mode 100644 index 00000000000..9cf806b1500 --- /dev/null +++ b/yarn-project/l1-contracts/.eslintrc.cjs @@ -0,0 +1,6 @@ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + extends: ['@aztec/eslint-config'], + parserOptions: { tsconfigRootDir: __dirname }, +}; diff --git a/yarn-project/l1-contracts/Dockerfile b/yarn-project/l1-contracts/Dockerfile new file mode 100644 index 00000000000..8c4489541f8 --- /dev/null +++ b/yarn-project/l1-contracts/Dockerfile @@ -0,0 +1,14 @@ +FROM 278380418400.dkr.ecr.eu-west-2.amazonaws.com/yarn-project-base AS builder + +COPY l1-contracts l1-contracts +WORKDIR /usr/src/yarn-project/l1-contracts +RUN yarn build && yarn formatting && yarn test + +# Prune dev dependencies. See comment in base image. +RUN yarn cache clean +RUN yarn workspaces focus --production > /dev/null + +FROM node:18-alpine +COPY --from=builder /usr/src/yarn-project/l1-contracts /usr/src/yarn-project/l1-contracts +WORKDIR /usr/src/yarn-project/l1-contracts +ENTRYPOINT ["yarn"] \ No newline at end of file diff --git a/yarn-project/l1-contracts/README.md b/yarn-project/l1-contracts/README.md new file mode 100644 index 00000000000..39a517e874c --- /dev/null +++ b/yarn-project/l1-contracts/README.md @@ -0,0 +1,3 @@ +# L1 Contracts + +Typed classes for the L1 contracts. diff --git a/yarn-project/l1-contracts/contracts.json b/yarn-project/l1-contracts/contracts.json new file mode 100644 index 00000000000..dc961d2a737 --- /dev/null +++ b/yarn-project/l1-contracts/contracts.json @@ -0,0 +1,9 @@ +{ + "outputPath": "./src/aztec-ethereumjs-contracts", + "contracts": { + "Rollup": { + "source": "foundry", + "buildFile": "../../l1-contracts/out/Rollup.sol/Rollup.json" + } + } +} \ No newline at end of file diff --git a/yarn-project/l1-contracts/package.json b/yarn-project/l1-contracts/package.json new file mode 100644 index 00000000000..0ea15e076fa --- /dev/null +++ b/yarn-project/l1-contracts/package.json @@ -0,0 +1,47 @@ +{ + "name": "@aztec/l1-contracts", + "version": "0.0.0", + "type": "module", + "exports": "./dest/index.js", + "typedoc": { + "entryPoint": "./src/index.ts", + "displayName": "L1 Contracts", + "tsconfig": "./tsconfig.dest.json" + }, + "scripts": { + "build": "yarn clean && yarn formatting && tsc -b tsconfig.dest.json", + "build:dev": "tsc -b tsconfig.dest.json --watch", + "generate": "contract_gen_def", + "clean": "rm -rf ./dest .tsbuildinfo", + "formatting": "run -T prettier --check ./src && run -T eslint --max-warnings 0 ./src", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --passWithNoTests" + }, + "jest": { + "preset": "ts-jest/presets/default-esm", + "globals": { + "ts-jest": { + "useESM": true + } + }, + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.js$": "$1" + }, + "testRegex": "./src/.*\\.test\\.ts$", + "rootDir": "./src" + }, + "dependencies": { + "tslib": "^2.4.0" + }, + "devDependencies": { + "@aztec/eslint-config": "workspace:^", + "@aztec/ethereum.js": "workspace:^", + "@jest/globals": "^29.4.3", + "@rushstack/eslint-patch": "^1.1.4", + "@types/jest": "^29.4.0", + "@types/node": "^18.7.23", + "jest": "^28.1.3", + "ts-jest": "^28.0.7", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + } +} diff --git a/yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/Rollup.ts b/yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/Rollup.ts new file mode 100644 index 00000000000..3f72c762e85 --- /dev/null +++ b/yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/Rollup.ts @@ -0,0 +1,42 @@ +// THIS IS GENERATED CODE, DO NOT EDIT! +/* eslint-disable */ +import { EthAddress } from '@aztec/ethereum.js/eth_address'; +import { EthereumRpc } from '@aztec/ethereum.js/eth_rpc'; +import { Contract, ContractTxReceipt, EventLog, Options, TxCall, TxSend } from '@aztec/ethereum.js/contract'; +import * as Bytes from '@aztec/ethereum.js/contract/bytes.js'; +import abi from './RollupAbi.js'; +export type RollupBlockProcessedEvent = { + rollupBlockNumber: bigint; +}; +export interface RollupBlockProcessedEventLog extends EventLog {} +interface RollupEvents { + RollupBlockProcessed: RollupBlockProcessedEvent; +} +interface RollupEventLogs { + RollupBlockProcessed: RollupBlockProcessedEventLog; +} +interface RollupTxEventLogs { + RollupBlockProcessed: RollupBlockProcessedEventLog[]; +} +export interface RollupTransactionReceipt extends ContractTxReceipt {} +interface RollupMethods { + processRollup(_proof: Bytes.Bytes, _inputs: Bytes.Bytes): TxSend; + rollupStateHash(): TxCall; + verifier(): TxCall; +} +export interface RollupDefinition { + methods: RollupMethods; + events: RollupEvents; + eventLogs: RollupEventLogs; +} +export class Rollup extends Contract { + constructor(eth: EthereumRpc, address?: EthAddress, options?: Options) { + super(eth, abi, address, options); + } + deploy(): TxSend { + return super.deployBytecode( + '0x60a060405234801561001057600080fd5b5060405161001d9061004b565b604051809103906000f080158015610039573d6000803e3d6000fd5b506001600160a01b0316608052610058565b61019d80610e8883390190565b608051610e0f610079600039600081816067015261016a0152610e0f6000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631ab9c603146100465780632b7ac3f314610062578063f81cccbe146100a1575b600080fd5b61004f60005481565b6040519081526020015b60405180910390f35b6100897f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610059565b6100b46100af366004610adf565b6100b6565b005b6000806000806100c585610235565b60005493975091955093509150158015906100e257508260005414155b1561011257600054604051632d2ef59f60e11b815260048101919091526024810184905260440160405180910390fd5b60408051600180825281830190925260009160208083019080368337019050509050818160008151811061014857610148610b43565b6020908102919091010152604051633a94343960e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ea50d0e4906101a1908a908590600401610b7d565b602060405180830381865afa1580156101be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e29190610bf7565b6101ff576040516309bde33960e01b815260040160405180910390fd5b600083815560405186917f3b8a2090d4235f22cdb27dec2d75d724354dd3c747beb2df2bd09f1f251428b391a250505050505050565b600080600080610249856020015160e01c90565b9350610261610259600186610c36565b600487610283565b925061026f8460b887610283565b915061027a85610335565b90509193509193565b6040805160b880825260e082019092526000918291906020820181803683370190505090508460181c60208201538460101c60218201538460081c602282015384602382015360b46024820160b486602001860160045afa506002816040516102ec9190610c4f565b602060405180830381855afa158015610309573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061032c9190610c6b565b95945050505050565b60008060006103468461016c61049a565b909250905060008061036e8661035d866020610c84565b61036990610170610c9b565b61049a565b90925090506000806103a0886103848689610c9b565b61038f906020610c84565b61039b90610174610c9b565b61060d565b909250905060006103dd89846103b6888b610c9b565b6103c09190610c9b565b6103cb906020610c84565b6103d790610174610c9b565b8561076e565b604080516101ec8082526102208201909252919250906000908260208201818036833701905050905081602082018360208e0160045afa5061018c81018690526101ac81018890526101cc81018490526101ec810183905260405161020c9060029061044a908490610c4f565b602060405180830381855afa158015610467573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061048a9190610c6b565b9c9b505050505050505050505050565b8181016020015160e01c60006008816104b38285610cae565b67ffffffffffffffff8111156104cb576104cb610a3c565b6040519080825280602002602001820160405280156104f4578160200160208202803683370190505b50905060005b6105048386610cae565b8110156105f5576000610518826008610c84565b610523906020610c84565b61052e886024610c9b565b6105389190610c9b565b6040805161010080825261012082019092529192506000919060208201818036833701905050905061010060208201610100848c0160045afa506002816040516105829190610c4f565b602060405180830381855afa15801561059f573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906105c29190610c6b565b8484815181106105d4576105d4610b43565b602002602001018181525050505080806105ed90610cd0565b9150506104fa565b506000610601826108c4565b93505050509250929050565b8181016020015160e01c60006002816106268285610cae565b67ffffffffffffffff81111561063e5761063e610a3c565b604051908082528060200260200182016040528015610667578160200160208202803683370190505b50905060005b6106778386610cae565b81101561075657600061068b826040610c84565b610696886024610c9b565b6106a09190610c9b565b60408051818152606081018252919250600091906020820181803683370190505090506040602082016040848c0160045afa506002816040516106e39190610c4f565b602060405180830381855afa158015610700573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107239190610c6b565b84848151811061073557610735610b43565b6020026020010181815250505050808061074e90610cd0565b91505061066d565b5083610761826108c4565b9350935050509250929050565b600060028161077d8285610cae565b67ffffffffffffffff81111561079557610795610a3c565b6040519080825280602002602001820160405280156107be578160200160208202803683370190505b50905060005b6107ce8386610cae565b8110156108b05760006107e2826068610c84565b6107ed886024610c9b565b6107f79190610c9b565b60408051606880825260a08201909252919250600091906020820181803683370190505090506068602082016068848c0160045afa5060028160405161083d9190610c4f565b602060405180830381855afa15801561085a573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061087d9190610c6b565b84848151811061088f5761088f610b43565b602002602001018181525050505080806108a890610cd0565b9150506107c4565b506108ba816108c4565b9695505050505050565b6000805b82516108d5826002610dcd565b10156108ed57806108e581610cd0565b9150506108c8565b60006108fa826002610dcd565b905080845260005b82811015610a175760005b82811015610a0457600286828151811061092957610929610b43565b60200260200101518783600161093f9190610c9b565b8151811061094f5761094f610b43565b6020026020010151604051602001610971929190918252602082015260400190565b60408051601f198184030181529082905261098b91610c4f565b602060405180830381855afa1580156109a8573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906109cb9190610c6b565b866109d7600284610cae565b815181106109e7576109e7610b43565b60209081029190910101526109fd600282610c9b565b905061090d565b5080610a0f81610cd0565b915050610902565b5083600081518110610a2b57610a2b610b43565b602002602001015192505050919050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112610a6357600080fd5b813567ffffffffffffffff80821115610a7e57610a7e610a3c565b604051601f8301601f19908116603f01168101908282118183101715610aa657610aa6610a3c565b81604052838152866020858801011115610abf57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060408385031215610af257600080fd5b823567ffffffffffffffff80821115610b0a57600080fd5b610b1686838701610a52565b93506020850135915080821115610b2c57600080fd5b50610b3985828601610a52565b9150509250929050565b634e487b7160e01b600052603260045260246000fd5b60005b83811015610b74578181015183820152602001610b5c565b50506000910152565b60408152600083518060408401526020610b9d8260608601838901610b59565b6060601f19601f93909301929092168401848103830185830152855192810183905285820192600091608001905b80831015610beb5784518252938301936001929092019190830190610bcb565b50979650505050505050565b600060208284031215610c0957600080fd5b81518015158114610c1957600080fd5b9392505050565b634e487b7160e01b600052601160045260246000fd5b81810381811115610c4957610c49610c20565b92915050565b60008251610c61818460208701610b59565b9190910192915050565b600060208284031215610c7d57600080fd5b5051919050565b8082028115828204841417610c4957610c49610c20565b80820180821115610c4957610c49610c20565b600082610ccb57634e487b7160e01b600052601260045260246000fd5b500490565b600060018201610ce257610ce2610c20565b5060010190565b600181815b80851115610d24578160001904821115610d0a57610d0a610c20565b80851615610d1757918102915b93841c9390800290610cee565b509250929050565b600082610d3b57506001610c49565b81610d4857506000610c49565b8160018114610d5e5760028114610d6857610d84565b6001915050610c49565b60ff841115610d7957610d79610c20565b50506001821b610c49565b5060208310610133831016604e8410600b8410161715610da7575081810a610c49565b610db18383610ce9565b8060001904821115610dc557610dc5610c20565b029392505050565b6000610c198383610d2c56fea2646970667358221220ece9d35f0dabd2ce67f2fc7b08e612bd5e3e292de94f6df90a3bb48f782fd9ef64736f6c63430008120033608060405234801561001057600080fd5b5061017d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063937f6a101461003b578063ea50d0e41461005a575b600080fd5b60405168496d2061206d6f636b60b81b81526020015b60405180910390f35b610072610068366004610082565b6001949350505050565b6040519015158152602001610051565b6000806000806040858703121561009857600080fd5b843567ffffffffffffffff808211156100b057600080fd5b818701915087601f8301126100c457600080fd5b8135818111156100d357600080fd5b8860208285010111156100e557600080fd5b60209283019650945090860135908082111561010057600080fd5b818701915087601f83011261011457600080fd5b81358181111561012357600080fd5b8860208260051b850101111561013857600080fd5b9598949750506020019450505056fea2646970667358221220dccfdb9ed1ec020c6477c07aa6b7354d591126e95a8df81bf7a547459d30b6cb64736f6c63430008120033', + ) as any; + } +} +export var RollupAbi = abi; diff --git a/yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/RollupAbi.ts b/yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/RollupAbi.ts new file mode 100644 index 00000000000..2c1bc7ba363 --- /dev/null +++ b/yarn-project/l1-contracts/src/aztec-ethereumjs-contracts/RollupAbi.ts @@ -0,0 +1,86 @@ +import { ContractAbi } from '@aztec/ethereum.js/contract'; +export default new ContractAbi([ + { + inputs: [], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'InvalidProof', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'expected', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'actual', + type: 'bytes32', + }, + ], + name: 'InvalidStateHash', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'rollupBlockNumber', + type: 'uint256', + }, + ], + name: 'RollupBlockProcessed', + type: 'event', + }, + { + inputs: [ + { + internalType: 'bytes', + name: '_proof', + type: 'bytes', + }, + { + internalType: 'bytes', + name: '_inputs', + type: 'bytes', + }, + ], + name: 'processRollup', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'rollupStateHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'verifier', + outputs: [ + { + internalType: 'contract MockVerifier', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]); diff --git a/yarn-project/l1-contracts/src/index.ts b/yarn-project/l1-contracts/src/index.ts new file mode 100644 index 00000000000..517b33aeefe --- /dev/null +++ b/yarn-project/l1-contracts/src/index.ts @@ -0,0 +1,6 @@ +export interface L1Addresses { + rollupContract: string; + feeDistributor: string; +} + +export * from './aztec-ethereumjs-contracts/Rollup.js'; diff --git a/yarn-project/l1-contracts/tsconfig.dest.json b/yarn-project/l1-contracts/tsconfig.dest.json new file mode 100644 index 00000000000..965aaa1c433 --- /dev/null +++ b/yarn-project/l1-contracts/tsconfig.dest.json @@ -0,0 +1,4 @@ +{ + "extends": ".", + "exclude": ["**/*.test.*", "**/fixtures/*"] +} diff --git a/yarn-project/l1-contracts/tsconfig.json b/yarn-project/l1-contracts/tsconfig.json new file mode 100644 index 00000000000..f67ddec9fd6 --- /dev/null +++ b/yarn-project/l1-contracts/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "..", + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "tsBuildInfoFile": ".tsbuildinfo" + }, + "include": ["src"] +} diff --git a/yarn-project/package.json b/yarn-project/package.json index 4afaad7811a..3ddd53b5ba7 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -21,6 +21,7 @@ "key-store", "merkle-tree", "noir-contracts", + "l1-contracts", "p2p", "prettier-config", "prover-client", diff --git a/yarn-project/sequencer-client/.eslintrc.cjs b/yarn-project/sequencer-client/.eslintrc.cjs index 9cf806b1500..f546457bbfb 100644 --- a/yarn-project/sequencer-client/.eslintrc.cjs +++ b/yarn-project/sequencer-client/.eslintrc.cjs @@ -3,4 +3,8 @@ require('@rushstack/eslint-patch/modern-module-resolution'); module.exports = { extends: ['@aztec/eslint-config'], parserOptions: { tsconfigRootDir: __dirname }, + rules: { + "jsdoc/require-jsdoc": "off", + "jsdoc/require-param": "off" + } }; diff --git a/yarn-project/sequencer-client/jest.integration.config.json b/yarn-project/sequencer-client/jest.integration.config.json new file mode 100644 index 00000000000..f89093c1acf --- /dev/null +++ b/yarn-project/sequencer-client/jest.integration.config.json @@ -0,0 +1,13 @@ +{ + "preset": "ts-jest/presets/default-esm", + "globals": { + "ts-jest": { + "useESM": true + } + }, + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.js$": "$1" + }, + "testRegex": "./test/.*\\.test\\.ts$", + "rootDir": "./test" +} \ No newline at end of file diff --git a/yarn-project/sequencer-client/package.json b/yarn-project/sequencer-client/package.json index e1408b020dd..a8e820b35cd 100644 --- a/yarn-project/sequencer-client/package.json +++ b/yarn-project/sequencer-client/package.json @@ -13,7 +13,10 @@ "build:dev": "tsc -b tsconfig.dest.json --watch", "clean": "rm -rf ./dest .tsbuildinfo", "formatting": "run -T prettier --check ./src && run -T eslint --max-warnings 0 ./src", - "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --passWithNoTests" + "formatting:fix": "run -T prettier -w ./src && run -T eslint --max-warnings 0 ./src", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --passWithNoTests", + "test:integration": "concurrently -k -s first -c reset,dim -n test,anvil \"yarn test:integration:run\" \"anvil\"", + "test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json" }, "jest": { "preset": "ts-jest/presets/default-esm", @@ -29,6 +32,9 @@ "rootDir": "./src" }, "dependencies": { + "@aztec/archiver": "workspace:^", + "@aztec/ethereum.js": "workspace:^", + "@aztec/l1-contracts": "workspace:^", "tslib": "^2.4.0" }, "devDependencies": { @@ -37,7 +43,9 @@ "@rushstack/eslint-patch": "^1.1.4", "@types/jest": "^29.4.0", "@types/node": "^18.7.23", + "concurrently": "^7.6.0", "jest": "^28.1.3", + "jest-mock-extended": "^3.0.3", "ts-jest": "^28.0.7", "ts-node": "^10.9.1", "typescript": "^4.9.5" diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts new file mode 100644 index 00000000000..d3596f7793b --- /dev/null +++ b/yarn-project/sequencer-client/src/config.ts @@ -0,0 +1,7 @@ +import { L1Addresses } from '@aztec/l1-contracts'; + +export interface Config extends L1Addresses { + sequencerPrivateKey: string; + ethereumHost: string; + requiredConfirmations: number; +} diff --git a/yarn-project/sequencer-client/src/publisher/aztec-ethereumjs-tx-sender.ts b/yarn-project/sequencer-client/src/publisher/aztec-ethereumjs-tx-sender.ts new file mode 100644 index 00000000000..f8e00e2694d --- /dev/null +++ b/yarn-project/sequencer-client/src/publisher/aztec-ethereumjs-tx-sender.ts @@ -0,0 +1,42 @@ +import { EthAddress } from '@aztec/ethereum.js/eth_address'; +import { EthereumRpc, TxHash, waitForTxReceipt } from '@aztec/ethereum.js/eth_rpc'; +import { WalletProvider } from '@aztec/ethereum.js/provider'; +import { Rollup } from '@aztec/l1-contracts'; +import { Config } from '../config.js'; +import { hexStringToBuffer } from '../utils.js'; +import { L1ProcessRollupArgs, PublisherTxSender } from './l2-block-publisher.js'; + +/** + * Pushes transactions to the L1 rollup contract using the custom aztec/ethereum.js library. + */ +export class AztecEthereumjsTxSender implements PublisherTxSender { + private ethRpc: EthereumRpc; + private rollupContract: Rollup; + private confirmations: number; + + constructor(config: Config) { + const { ethereumHost, sequencerPrivateKey, rollupContract: rollupContractAddress, requiredConfirmations } = config; + const provider = WalletProvider.fromHost(ethereumHost); + provider.addAccount(hexStringToBuffer(sequencerPrivateKey)); + this.ethRpc = new EthereumRpc(provider); + this.rollupContract = new Rollup(this.ethRpc, EthAddress.fromString(rollupContractAddress), { + from: provider.getAccount(0), + }); + this.confirmations = requiredConfirmations; + } + + getTransactionReceipt(txHash: string): Promise<{ status: boolean; transactionHash: string } | undefined> { + return waitForTxReceipt(TxHash.fromString(txHash), this.ethRpc, this.confirmations).then( + r => r && { ...r, transactionHash: r.transactionHash.toString() }, + ); + } + + async sendTransaction(encodedData: L1ProcessRollupArgs): Promise { + const methodCall = this.rollupContract.methods.processRollup(encodedData.proof, encodedData.inputs); + const gas = await methodCall.estimateGas(); + return methodCall + .send({ gas }) + .getTxHash() + .then(hash => hash.toString()); + } +} diff --git a/yarn-project/sequencer-client/src/publisher/index.ts b/yarn-project/sequencer-client/src/publisher/index.ts new file mode 100644 index 00000000000..b903b10a520 --- /dev/null +++ b/yarn-project/sequencer-client/src/publisher/index.ts @@ -0,0 +1,9 @@ +import { Config } from '../config.js'; +import { AztecEthereumjsTxSender } from './aztec-ethereumjs-tx-sender.js'; +import { L2BlockPublisher } from './l2-block-publisher.js'; + +export { L2BlockPublisher } from './l2-block-publisher.js'; + +export function getL2BlockPublisher(config: Config): L2BlockPublisher { + return new L2BlockPublisher(new AztecEthereumjsTxSender(config)); +} diff --git a/yarn-project/sequencer-client/src/publisher/l2-block-publisher.test.ts b/yarn-project/sequencer-client/src/publisher/l2-block-publisher.test.ts new file mode 100644 index 00000000000..28eb8ae006f --- /dev/null +++ b/yarn-project/sequencer-client/src/publisher/l2-block-publisher.test.ts @@ -0,0 +1,78 @@ +import { L2Block, mockRandomL2Block } from '@aztec/archiver'; +import { TxHash } from '@aztec/ethereum.js/eth_rpc'; +import { mock, MockProxy } from 'jest-mock-extended'; +import { sleep } from '../utils.js'; +import { L2BlockPublisher, PublisherTxSender } from './l2-block-publisher.js'; + +describe('L2BlockPublisher', () => { + let txSender: MockProxy; + let txHash: string; + let txReceipt: { transactionHash: string; status: boolean }; + let l2Block: L2Block; + let l2Inputs: Buffer; + let l2Proof: Buffer; + let publisher: L2BlockPublisher; + + beforeEach(() => { + l2Block = mockRandomL2Block(42); + l2Inputs = l2Block.encode(); + l2Proof = Buffer.alloc(0); + + txSender = mock(); + txHash = TxHash.random().toString(); + txReceipt = { transactionHash: txHash, status: true }; + txSender.sendTransaction.mockResolvedValueOnce(txHash); + txSender.getTransactionReceipt.mockResolvedValueOnce(txReceipt); + + publisher = new L2BlockPublisher(txSender, { sleepTimeMs: 1 }); + }); + + it('publishes l2 block to l1', async () => { + const result = await publisher.processL2Block(l2Block); + + expect(result).toEqual(true); + expect(txSender.sendTransaction).toHaveBeenCalledWith({ proof: l2Proof, inputs: l2Inputs }); + expect(txSender.getTransactionReceipt).toHaveBeenCalledWith(txHash); + }); + + it('retries if sending a tx fails', async () => { + txSender.sendTransaction.mockReset().mockRejectedValueOnce(new Error()).mockResolvedValueOnce(txHash); + + const result = await publisher.processL2Block(l2Block); + + expect(result).toEqual(true); + expect(txSender.sendTransaction).toHaveBeenCalledTimes(2); + }); + + it('retries if fetching the receipt fails', async () => { + txSender.getTransactionReceipt.mockReset().mockRejectedValueOnce(new Error()).mockResolvedValueOnce(txReceipt); + + const result = await publisher.processL2Block(l2Block); + + expect(result).toEqual(true); + expect(txSender.getTransactionReceipt).toHaveBeenCalledTimes(2); + }); + + it('returns false if tx reverts', async () => { + txSender.getTransactionReceipt.mockReset().mockResolvedValueOnce({ ...txReceipt, status: false }); + + const result = await publisher.processL2Block(l2Block); + + expect(result).toEqual(false); + }); + + it('returns false if interrupted', async () => { + txSender.sendTransaction.mockReset().mockImplementationOnce(() => sleep(10, txHash)); + + const resultPromise = publisher.processL2Block(l2Block); + publisher.interrupt(); + const result = await resultPromise; + + expect(result).toEqual(false); + expect(txSender.getTransactionReceipt).not.toHaveBeenCalled(); + }); + + it.skip('waits for fee distributor balance', () => {}); + + it.skip('fails if contract is changed underfoot', () => {}); +}); diff --git a/yarn-project/sequencer-client/src/publisher/l2-block-publisher.ts b/yarn-project/sequencer-client/src/publisher/l2-block-publisher.ts new file mode 100644 index 00000000000..cae144d5295 --- /dev/null +++ b/yarn-project/sequencer-client/src/publisher/l2-block-publisher.ts @@ -0,0 +1,131 @@ +import { L2Block } from '@aztec/archiver'; +import { L2BlockReceiver } from '../receiver.js'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const MIN_FEE_DISTRIBUTOR_BALANCE = 5n * 10n ** 17n; + +/** + * Component responsible of pushing the txs to the chain and waiting for completion. + */ +export interface PublisherTxSender { + sendTransaction(encodedData: L1ProcessRollupArgs): Promise; + getTransactionReceipt(txHash: string): Promise<{ status: boolean; transactionHash: string } | undefined>; +} + +/** + * Encoded block data and proof ready to be pushed to the L1 contract. + */ +export type L1ProcessRollupArgs = { + proof: Buffer; + inputs: Buffer; +}; + +/** + * Publishes L2 blocks to the L1 rollup contracts. This implementation does *not* retry a transaction in + * the event of network congestion. + * - If sending (not mining) a tx fails, it retries indefinitely at 1-minute intervals. + * - If the tx is not mined, keeps polling indefinitely at 1-second intervals. + * + * Adapted from https://github.com/AztecProtocol/aztec2-internal/blob/master/falafel/src/rollup_publisher.ts. + */ +export class L2BlockPublisher implements L2BlockReceiver { + private interrupted = false; + private interruptPromise = Promise.resolve(); + private interruptResolve = () => {}; + private sleepTimeMs: number; + + constructor(private txSender: PublisherTxSender, opts?: { sleepTimeMs?: number }) { + this.interruptPromise = new Promise(resolve => (this.interruptResolve = resolve)); + this.sleepTimeMs = opts?.sleepTimeMs ?? 60_000; + } + + /** + * Processes incoming L2 block data by publishing it to the L1 rollup contract. + * @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise. + */ + public async processL2Block(l2BlockData: L2Block): Promise { + const proof = Buffer.alloc(0); + const txData = { proof, inputs: l2BlockData.encode() }; + + while (!this.interrupted) { + if (!(await this.checkFeeDistributorBalance())) { + console.log(`Fee distributor ETH balance too low, awaiting top up...`); + await this.sleepOrInterrupted(); + continue; + } + + const txHash = await this.sendTransaction(txData); + if (!txHash) break; + + const receipt = await this.getTransactionReceipt(txHash); + if (!receipt) break; + + // Tx was mined successfully + if (receipt.status) return true; + + // Check if someone else moved the block id + if (!(await this.checkNextL2BlockId(l2BlockData.number))) { + console.log('Publish failed. Contract changed underfoot.'); + break; + } + + console.log(`Transaction status failed: ${receipt.transactionHash}`); + await this.sleepOrInterrupted(); + } + + console.log('Publish rollup interrupted.'); + return false; + } + + /** + * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap. + * Be warned, the call may return false even if the tx subsequently gets successfully mined. + * In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail. + * A call to `clearInterrupt` is required before you can continue publishing. + */ + public interrupt() { + this.interrupted = true; + this.interruptResolve(); + } + + // TODO: Check fee distributor has at least 0.5 ETH. + // eslint-disable-next-line require-await + private async checkFeeDistributorBalance(): Promise { + return true; + } + + // TODO: Fail if blockchainStatus.nextRollupId > thisBlockId. + // eslint-disable-next-line require-await, @typescript-eslint/no-unused-vars + private async checkNextL2BlockId(thisBlockId: number): Promise { + return true; + } + + private async sendTransaction(encodedData: L1ProcessRollupArgs): Promise { + while (!this.interrupted) { + try { + return await this.txSender.sendTransaction(encodedData); + } catch (err) { + console.log(`Error sending tx to L1`, err); + await this.sleepOrInterrupted(); + } + } + } + + private async getTransactionReceipt( + txHash: string, + ): Promise<{ status: boolean; transactionHash: string } | undefined> { + while (!this.interrupted) { + try { + return await this.txSender.getTransactionReceipt(txHash); + } catch (err) { + console.log(`Error getting tx receipt`, err); + await this.sleepOrInterrupted(); + } + } + } + + protected async sleepOrInterrupted() { + const ms = this.sleepTimeMs; + await Promise.race([new Promise(resolve => setTimeout(resolve, ms)), this.interruptPromise]); + } +} diff --git a/yarn-project/sequencer-client/src/receiver.ts b/yarn-project/sequencer-client/src/receiver.ts new file mode 100644 index 00000000000..ed8949730af --- /dev/null +++ b/yarn-project/sequencer-client/src/receiver.ts @@ -0,0 +1,9 @@ +import { L2Block } from '@aztec/archiver'; + +/** + * Given the necessary rollup data, verifies it, and updates the underlying state accordingly to advance the state of the system. + * See https://hackmd.io/ouVCnacHQRq2o1oRc5ksNA#RollupReceiver. + */ +export interface L2BlockReceiver { + processL2Block(l2BlockData: L2Block): Promise; +} diff --git a/yarn-project/sequencer-client/src/utils.ts b/yarn-project/sequencer-client/src/utils.ts new file mode 100644 index 00000000000..70b7eedaa3b --- /dev/null +++ b/yarn-project/sequencer-client/src/utils.ts @@ -0,0 +1,9 @@ +export function hexStringToBuffer(hex: string): Buffer { + if (!/^(0x)?[a-fA-F0-9]+$/.test(hex)) throw new Error(`Invalid format for hex string: "${hex}"`); + if (hex.length % 2 === 1) throw new Error(`Invalid length for hex string: "${hex}"`); + return Buffer.from(hex.replace(/^0x/, ''), 'hex'); +} + +export function sleep(ms: number, retval: T): Promise { + return new Promise(resolve => setTimeout(() => resolve(retval), ms)); +} diff --git a/yarn-project/sequencer-client/test/l2-block-publisher.test.ts b/yarn-project/sequencer-client/test/l2-block-publisher.test.ts new file mode 100644 index 00000000000..eff87471472 --- /dev/null +++ b/yarn-project/sequencer-client/test/l2-block-publisher.test.ts @@ -0,0 +1,75 @@ +import { L2Block, mockRandomL2Block } from '@aztec/archiver'; +import { EthAddress } from '@aztec/ethereum.js/eth_address'; +import { EthereumRpc } from '@aztec/ethereum.js/eth_rpc'; +import { WalletProvider } from '@aztec/ethereum.js/provider'; +import { Rollup } from '@aztec/l1-contracts'; +import { beforeAll, describe, expect, it } from '@jest/globals'; +import { AztecEthereumjsTxSender } from '../src/publisher/aztec-ethereumjs-tx-sender.js'; +import { L2BlockPublisher } from '../src/publisher/l2-block-publisher.js'; +import { hexStringToBuffer } from '../src/utils.js'; + +// Accounts 4 and 5 of Anvil default startup with mnemonic: 'test test test test test test test test test test test junk' +const sequencerPK = '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a'; +const deployerPK = '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'; +const anvilHost = process.env.ANVIL_HOST ?? 'http://127.0.0.1:8545'; + +describe('L2BlockPublisher integration', () => { + let rollup: Rollup; + let ethRpc: EthereumRpc; + let publisher: L2BlockPublisher; + let l2Block: L2Block; + let l2Proof: Buffer; + + beforeAll(async () => { + let deployer: EthAddress; + ({ ethRpc, rollup, deployer } = await deployRollup()); + + l2Block = mockRandomL2Block(42); + l2Proof = Buffer.alloc(0); + + publisher = new L2BlockPublisher( + new AztecEthereumjsTxSender({ + ethereumHost: anvilHost, + feeDistributor: deployer.toChecksumString(), + requiredConfirmations: 1, + rollupContract: rollup.address.toChecksumString(), + sequencerPrivateKey: sequencerPK + }), + { + sleepTimeMs: 100 + } + ); + }); + + it('publishes l2 block data to l1 rollup contract', async () => { + const blockNumber = await ethRpc.blockNumber(); + await publisher.processL2Block(l2Block); + + const logs = await rollup.getLogs('RollupBlockProcessed', { fromBlock: blockNumber }); + expect(logs).toHaveLength(1); + expect(logs[0].args.rollupBlockNumber).toEqual(42n); + + const tx = await ethRpc.getTransactionByHash(logs[0].transactionHash!); + const expectedData = rollup.methods.processRollup(l2Proof, l2Block.encode()).encodeABI(); + expect(tx.input).toEqual(expectedData); + }); +}); + + +async function deployRollup() { + // Set up client + const provider = WalletProvider.fromHost(anvilHost); + provider.addAccount(hexStringToBuffer(deployerPK)); + provider.addAccount(hexStringToBuffer(sequencerPK)); + const [sequencer, deployer] = provider.getAccounts(); + const ethRpc = new EthereumRpc(provider); + + // Deploy rollup contract + const deployedRollup = new Rollup(ethRpc, undefined, { from: deployer, gas: 1e6 }); + await deployedRollup.deploy().send().getReceipt(); + + // Create new instance so we can attach the sequencer as sender + const rollup = new Rollup(ethRpc, deployedRollup.address, { from: sequencer }); + + return { rollup, deployer, sequencer, ethRpc }; +} \ No newline at end of file diff --git a/yarn-project/sequencer-client/tsconfig.json b/yarn-project/sequencer-client/tsconfig.json index f67ddec9fd6..dec34639a4a 100644 --- a/yarn-project/sequencer-client/tsconfig.json +++ b/yarn-project/sequencer-client/tsconfig.json @@ -5,5 +5,5 @@ "rootDir": "src", "tsBuildInfoFile": ".tsbuildinfo" }, - "include": ["src"] + "include": ["src", "test"] } diff --git a/yarn-project/yarn-project-base/Dockerfile b/yarn-project/yarn-project-base/Dockerfile index b7ac586c186..3ad850d95f6 100644 --- a/yarn-project/yarn-project-base/Dockerfile +++ b/yarn-project/yarn-project-base/Dockerfile @@ -24,6 +24,7 @@ COPY kernel-simulator/package.json kernel-simulator/package.json COPY key-store/package.json key-store/package.json COPY merkle-tree/package.json merkle-tree/package.json COPY noir-contracts/package.json noir-contracts/package.json +COPY l1-contracts/package.json l1-contracts/package.json COPY p2p/package.json p2p/package.json COPY prover-client/package.json prover-client/package.json COPY aztec-node/package.json aztec-node/package.json diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 4a020a37f6c..9300820e727 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -272,6 +272,24 @@ __metadata: languageName: unknown linkType: soft +"@aztec/l1-contracts@workspace:^, @aztec/l1-contracts@workspace:l1-contracts": + version: 0.0.0-use.local + resolution: "@aztec/l1-contracts@workspace:l1-contracts" + dependencies: + "@aztec/eslint-config": "workspace:^" + "@aztec/ethereum.js": "workspace:^" + "@jest/globals": ^29.4.3 + "@rushstack/eslint-patch": ^1.1.4 + "@types/jest": ^29.4.0 + "@types/node": ^18.7.23 + jest: ^28.1.3 + ts-jest: ^28.0.7 + ts-node: ^10.9.1 + tslib: ^2.4.0 + typescript: ^4.9.5 + languageName: unknown + linkType: soft + "@aztec/merkle-tree@workspace:^, @aztec/merkle-tree@workspace:merkle-tree": version: 0.0.0-use.local resolution: "@aztec/merkle-tree@workspace:merkle-tree" @@ -365,12 +383,17 @@ __metadata: version: 0.0.0-use.local resolution: "@aztec/sequencer-client@workspace:sequencer-client" dependencies: + "@aztec/archiver": "workspace:^" "@aztec/eslint-config": "workspace:^" + "@aztec/ethereum.js": "workspace:^" + "@aztec/l1-contracts": "workspace:^" "@jest/globals": ^29.4.3 "@rushstack/eslint-patch": ^1.1.4 "@types/jest": ^29.4.0 "@types/node": ^18.7.23 + concurrently: ^7.6.0 jest: ^28.1.3 + jest-mock-extended: ^3.0.3 ts-jest: ^28.0.7 ts-node: ^10.9.1 tslib: ^2.4.0 @@ -2730,7 +2753,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -2872,6 +2895,26 @@ __metadata: languageName: node linkType: hard +"concurrently@npm:^7.6.0": + version: 7.6.0 + resolution: "concurrently@npm:7.6.0" + dependencies: + chalk: ^4.1.0 + date-fns: ^2.29.1 + lodash: ^4.17.21 + rxjs: ^7.0.0 + shell-quote: ^1.7.3 + spawn-command: ^0.0.2-1 + supports-color: ^8.1.0 + tree-kill: ^1.2.2 + yargs: ^17.3.1 + bin: + conc: dist/bin/concurrently.js + concurrently: dist/bin/concurrently.js + checksum: f705c9a7960f1b16559ca64958043faeeef6385c0bf30a03d1375e15ab2d96dba4f8166f1bbbb1c85e8da35ca0ce3c353875d71dff2aa132b2357bb533b3332e + languageName: node + linkType: hard + "console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -2961,6 +3004,13 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^2.29.1": + version: 2.29.3 + resolution: "date-fns@npm:2.29.3" + checksum: e01cf5b62af04e05dfff921bb9c9933310ed0e1ae9a81eb8653452e64dc841acf7f6e01e1a5ae5644d0337e9a7f936175fd2cb6819dc122fdd9c5e86c56be484 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -4617,7 +4667,7 @@ __metadata: languageName: node linkType: hard -"jest-mock-extended@npm:^3.0.1": +"jest-mock-extended@npm:^3.0.1, jest-mock-extended@npm:^3.0.3": version: 3.0.3 resolution: "jest-mock-extended@npm:3.0.3" dependencies: @@ -5266,6 +5316,13 @@ __metadata: languageName: node linkType: hard +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -6186,6 +6243,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.0.0": + version: 7.8.0 + resolution: "rxjs@npm:7.8.0" + dependencies: + tslib: ^2.1.0 + checksum: 61b4d4fd323c1043d8d6ceb91f24183b28bcf5def4f01ca111511d5c6b66755bc5578587fe714ef5d67cf4c9f2e26f4490d4e1d8cabf9bd5967687835e9866a2 + languageName: node + linkType: hard + "safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -6286,6 +6352,13 @@ __metadata: languageName: node linkType: hard +"shell-quote@npm:^1.7.3": + version: 1.8.0 + resolution: "shell-quote@npm:1.8.0" + checksum: 6ef7c5e308b9c77eedded882653a132214fa98b4a1512bb507588cf6cd2fc78bfee73e945d0c3211af028a1eabe09c6a19b96edd8977dc149810797e93809749 + languageName: node + linkType: hard + "shiki@npm:^0.14.1": version: 0.14.1 resolution: "shiki@npm:0.14.1" @@ -6381,6 +6454,13 @@ __metadata: languageName: node linkType: hard +"spawn-command@npm:^0.0.2-1": + version: 0.0.2 + resolution: "spawn-command@npm:0.0.2" + checksum: e35c5d28177b4d461d33c88cc11f6f3a5079e2b132c11e1746453bbb7a0c0b8a634f07541a2a234fa4758239d88203b758def509161b651e81958894c0b4b64b + languageName: node + linkType: hard + "spdx-exceptions@npm:^2.1.0": version: 2.3.0 resolution: "spdx-exceptions@npm:2.3.0" @@ -6533,7 +6613,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0": +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.0": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -6645,6 +6725,15 @@ __metadata: languageName: node linkType: hard +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 + languageName: node + linkType: hard + "ts-essentials@npm:^7.0.3": version: 7.0.3 resolution: "ts-essentials@npm:7.0.3" @@ -6814,7 +6903,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.4.0, tslib@npm:^2.5.0": +"tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0": version: 2.5.0 resolution: "tslib@npm:2.5.0" checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1