From e3ebbe31eaf116605df2a0a55e69374af860bd16 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 3 Dec 2025 16:06:28 +0100 Subject: [PATCH 001/155] Fixed wrong npmjs link in readmy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 962e4fd0b..154c2f3f0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
-[![npm version](https://img.shields.io/npm/v/@proto-kit/sdk.svg?style=flat&logo=npm)](https://www.npmjs.com/package/o1js) +[![npm version](https://img.shields.io/npm/v/@proto-kit/sdk.svg?style=flat&logo=npm)](https://www.npmjs.com/package/@proto-kit/sdk) [![Documentation](https://img.shields.io/badge/Documentation-website-blue.svg)](https://protokit.dev) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)]() From cada88d72ec1c1152375f74861d5e0c373f6ca83 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 5 Dec 2025 10:14:22 +0900 Subject: [PATCH 002/155] Removed need to initialize contracts separately --- .../contracts/DispatchSmartContract.ts | 32 ++++++---- .../contracts/SettlementSmartContract.ts | 63 ++++++------------- .../src/settlement/SettlementModule.ts | 41 +++++++----- 3 files changed, 65 insertions(+), 71 deletions(-) diff --git a/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts b/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts index 74d765e46..3e4de1ab1 100644 --- a/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts @@ -1,6 +1,7 @@ import { AccountUpdate, Bool, + DeployArgs, Field, method, Poseidon, @@ -13,6 +14,7 @@ import { state, TokenId, UInt64, + Permissions, } from "o1js"; import { InMemoryMerkleTreeStorage, TypedClass } from "@proto-kit/common"; @@ -42,7 +44,6 @@ export interface DispatchContractType { executedMessagesHash: Field, newPromisedMessagesHash: Field ) => Promise; - initialize: (settlementContract: PublicKey) => Promise; enableTokenDeposits: ( tokenId: Field, bridgeContractAddress: PublicKey, @@ -50,6 +51,12 @@ export interface DispatchContractType { ) => Promise; promisedMessagesHash: State; + + deployAndInitialize: ( + args: DeployArgs | undefined, + permissions: Permissions, + settlementContract: PublicKey + ) => Promise; } const tokenBridgeRoot = new TokenBridgeTree( @@ -116,12 +123,6 @@ export abstract class DispatchSmartContractBase extends SmartContract { } protected initializeBase(settlementContract: PublicKey) { - this.promisedMessagesHash.getAndRequireEquals().assertEquals(Field(0)); - this.honoredMessagesHash.getAndRequireEquals().assertEquals(Field(0)); - this.settlementContract - .getAndRequireEquals() - .assertEquals(PublicKey.empty()); - this.promisedMessagesHash.set(ACTIONS_EMPTY_HASH); this.honoredMessagesHash.set(ACTIONS_EMPTY_HASH); this.settlementContract.set(settlementContract); @@ -239,6 +240,18 @@ export class DispatchSmartContract @state(Field) public tokenBridgeCount = State(); + public async deployAndInitialize( + args: DeployArgs | undefined, + permissions: Permissions, + settlementContract: PublicKey + ): Promise { + await super.deploy(args); + + this.self.account.permissions.set(permissions); + + this.initializeBase(settlementContract); + } + @method public async enableTokenDeposits( tokenId: Field, @@ -263,11 +276,6 @@ export class DispatchSmartContract ); } - @method - public async initialize(settlementContract: PublicKey) { - return this.initializeBase(settlementContract); - } - @method public async deposit( amount: UInt64, diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index 7dc375d2f..06ee0ce68 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -18,13 +18,13 @@ import { UInt32, AccountUpdateForest, TokenContract, - PrivateKey, VerificationKey, Permissions, Struct, Provable, TokenId, DynamicProof, + DeployArgs, } from "o1js"; import { NetworkState } from "../../model/network/NetworkState"; @@ -65,12 +65,13 @@ export class TokenMapping extends Struct({ export interface SettlementContractType { authorizationField: State; - initialize: ( + deployAndInitialize: ( + args: DeployArgs | undefined, + permissions: Permissions, sequencer: PublicKey, - dispatchContract: PublicKey, - bridgeContract: PublicKey, - contractKey: PrivateKey + dispatchContract: PublicKey ) => Promise; + assertStateRoot: (root: Field) => AccountUpdate; settle: ( blockProof: DynamicBlockProof, @@ -245,35 +246,13 @@ export abstract class SettlementSmartContractBase extends TokenContract { protected async initializeBase( sequencer: PublicKey, - dispatchContract: PublicKey, - bridgeContract: PublicKey, - contractKey: PrivateKey + dispatchContract: PublicKey ) { - this.sequencerKey.getAndRequireEquals().assertEquals(Field(0)); - this.stateRoot.getAndRequireEquals().assertEquals(Field(0)); - this.blockHashRoot.getAndRequireEquals().assertEquals(Field(0)); - this.networkStateHash.getAndRequireEquals().assertEquals(Field(0)); - this.dispatchContractAddressX.getAndRequireEquals().assertEquals(Field(0)); - this.sequencerKey.set(sequencer.x); this.stateRoot.set(LinkedMerkleTree.EMPTY_ROOT); this.blockHashRoot.set(Field(BlockHashMerkleTree.EMPTY_ROOT)); this.networkStateHash.set(NetworkState.empty().hash()); this.dispatchContractAddressX.set(dispatchContract.x); - - const { DispatchContract } = SettlementSmartContractBase.args; - const contractInstance = new DispatchContract(dispatchContract); - await contractInstance.initialize(this.address); - - // Deploy bridge contract for $Mina - await this.deployTokenBridge( - this.tokenId, - bridgeContract, - dispatchContract, - true - ); - - contractKey.toPublicKey().assertEquals(this.address); } protected async settleBase( @@ -449,23 +428,21 @@ export class SettlementSmartContract @state(Field) public authorizationField = State(); - @method async approveBase(forest: AccountUpdateForest) { - this.checkZeroBalanceChange(forest); + public async deployAndInitialize( + args: DeployArgs | undefined, + permissions: Permissions, + sequencer: PublicKey, + dispatchContract: PublicKey + ): Promise { + await super.deploy(args); + + this.self.account.permissions.set(permissions); + + await this.initializeBase(sequencer, dispatchContract); } - @method - public async initialize( - sequencer: PublicKey, - dispatchContract: PublicKey, - bridgeContract: PublicKey, - contractKey: PrivateKey - ) { - await this.initializeBase( - sequencer, - dispatchContract, - bridgeContract, - contractKey - ); + @method async approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); } @method diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 2da841af2..9f70ffb89 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -16,6 +16,7 @@ import { PublicKey, Signature, TokenContract, + TokenId, Transaction, } from "o1js"; import { inject } from "tsyringe"; @@ -283,17 +284,25 @@ export class SettlementModule }, async () => { AccountUpdate.fundNewAccount(feepayer, 2); - await settlement.deploy({ - verificationKey: - verificationsKeys.SettlementSmartContract.verificationKey, - }); - settlement.account.permissions.set(permissions.settlementContract()); - - await dispatch.deploy({ - verificationKey: - verificationsKeys.DispatchSmartContract.verificationKey, - }); - dispatch.account.permissions.set(permissions.dispatchContract()); + + await dispatch.deployAndInitialize( + { + verificationKey: + verificationsKeys.DispatchSmartContract.verificationKey, + }, + permissions.dispatchContract(), + settlement.address + ); + + await settlement.deployAndInitialize( + { + verificationKey: + verificationsKeys.SettlementSmartContract.verificationKey, + }, + permissions.settlementContract(), + feepayerKey.toPublicKey(), + dispatchKey.toPublicKey() + ); } ).sign([feepayerKey, settlementKey, dispatchKey]); // Note: We can't use this.signTransaction on the above tx @@ -315,15 +324,15 @@ export class SettlementModule sender: feepayer, nonce: nonce + 1, fee: this.feeStrategy.getFee(), - memo: "Protokit settlement init", + memo: "Deploy MINA bridge", }, async () => { AccountUpdate.fundNewAccount(feepayer, 1); - await settlement.initialize( - feepayerKey.toPublicKey(), - dispatchKey.toPublicKey(), + // Deploy bridge contract for $Mina + await settlement.addTokenBridge( + TokenId.default, minaBridgeKey.toPublicKey(), - settlementKey + dispatchKey.toPublicKey() ); } ); From 466826d1403c83156276fe11229984660d7d558e Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 21 Nov 2025 11:36:31 +0300 Subject: [PATCH 003/155] feat: implement a separate MinaSigner interface --- packages/sequencer/src/index.ts | 1 + .../src/sequencer/SequencerStartupModule.ts | 8 +- .../src/settlement/BridgingModule.ts | 4 +- .../sequencer/src/settlement/MinaSigner.ts | 89 +++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 packages/sequencer/src/settlement/MinaSigner.ts diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 01314dee8..15b423ea7 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -97,6 +97,7 @@ export * from "./state/state/DummyStateService"; export * from "./state/state/CachedStateService"; export * from "./state/lmt/AsyncLinkedMerkleTreeDatabase"; export * from "./state/lmt/CachedLinkedLeafStore"; +export * from "./settlement/MinaSigner"; export * from "./settlement/SettlementModule"; export * from "./settlement/BridgingModule"; export * from "./settlement/messages/outgoing/DefaultOutgoingMessageAdapter"; diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 998b3e520..2885edba9 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -26,6 +26,7 @@ import { NoopBaseLayer } from "../protocol/baselayer/NoopBaseLayer"; import { SequencerModule, sequencerModule } from "./builder/SequencerModule"; import { Closeable, closeable } from "./builder/Closeable"; +import { MinaSigner } from "../settlement/MinaSigner"; @sequencerModule() @closeable() @@ -44,7 +45,9 @@ export class SequencerStartupModule @inject("BaseLayer", { isOptional: true }) private readonly baseLayer: MinaBaseLayer | undefined, @inject("AreProofsEnabled") - private readonly areProofsEnabled: AreProofsEnabled + private readonly areProofsEnabled: AreProofsEnabled, + @inject("Signer") + private readonly Signer: MinaSigner ) { super(); } @@ -145,7 +148,8 @@ export class SequencerStartupModule this.baseLayer !== undefined && !(this.baseLayer instanceof NoopBaseLayer) ? new SettlementUtils( this.areProofsEnabled, - this.baseLayer + this.baseLayer, + this.Signer ).isSignedSettlement() : undefined; diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 09f8fa407..e117e8c1a 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -55,6 +55,7 @@ import { SettlementUtils } from "./utils/SettlementUtils"; import { MinaTransactionSender } from "./transactions/MinaTransactionSender"; import { OutgoingMessageCollector } from "./messages/outgoing/OutgoingMessageCollector"; import { ArchiveNode } from "./utils/ArchiveNode"; +import { MinaSigner } from "./MinaSigner"; export type SettlementTokenConfig = Record< string, @@ -99,10 +100,11 @@ export class BridgingModule { private readonly feeStrategy: FeeStrategy, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, + @inject("Signer") signer: MinaSigner, @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender ) { - this.utils = new SettlementUtils(areProofsEnabled, baseLayer); + this.utils = new SettlementUtils(areProofsEnabled, baseLayer, signer); } private getMessageProcessors() { diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts new file mode 100644 index 000000000..9c282c481 --- /dev/null +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -0,0 +1,89 @@ +import { Field, PrivateKey, PublicKey, Signature, Transaction } from "o1js"; +import { injectable } from "tsyringe"; +import { AppChainModule } from "../appChain/AppChainModule"; + +export interface MinaSigner { + getContractKeys(): PublicKey[]; + + sign(signatureData: Field[]): Promise; + + signTransaction( + tx: Transaction + ): Promise>; +} + +export interface InMemorySignerConfig { + signer: PrivateKey; + contractKeys?: { + settlementKey: PrivateKey, + dispatchKey: PrivateKey, + minaBridgeKey: PrivateKey, + } + // optional way: + // additionalKeys?: PrivateKey[] +} + +@injectable() +export class SettlementSigner + extends AppChainModule + implements MinaSigner +{ + public constructor() { + super(); + } + + /** + * + * Contracts public key getter. Since we expect private keys to be managed here, public keys can be returned from this module. + * + * @returns Array of public keys, not ordered yet + * + */ + public getContractKeys(): PublicKey[] { + const contractKeysObject = this.config.contractKeys ?? {}; + + // Use Object.values to get the PrivateKey objects and filter out any undefined values. + const contractPrivateKeys: PrivateKey[] = Object.values(contractKeysObject).filter( + (key): key is PrivateKey => key !== undefined + ); + + return contractPrivateKeys.map(key => key.toPublicKey()); + } + + /** + * @param signatureData Data to be signed. + * @returns Signature signed by signer. + */ + public async sign(signatureData: Field[]): Promise { + return Signature.create(this.config.signer, signatureData); + } + + + /** + * + * Signs the transaction with all available keys (will/should be change and signing must be done in a selective way). + * + * @param tx Transaction to be signed with. + * @returns signed transaction object + */ + public async signTransaction( + tx: Transaction + ): Promise> { + const contractKeysObject = this.config.contractKeys ?? {}; + + // Extract the PrivateKey objects from the configuration object + const contractPrivateKeys: PrivateKey[] = Object.values(contractKeysObject).filter( + (key): key is PrivateKey => key !== undefined + ); + + // Combine the primary signer (feepayer) with the contract keys + const allKeys: PrivateKey[] = [ + this.config.signer, + ...contractPrivateKeys + ]; + + // Sign the transaction with the collected keys + return tx.sign(allKeys); + } + +} \ No newline at end of file From 3c73e32b4ff12424847f78e6395c2aedd812a9ce Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 21 Nov 2025 11:49:26 +0300 Subject: [PATCH 004/155] feat: add MinaSigner interface to Settlement module --- packages/sdk/src/client/ClientAppChain.ts | 4 +++- packages/sdk/src/transaction/AuroSigner.ts | 8 +++++--- .../src/settlement/SettlementModule.ts | 18 +++++++---------- .../src/settlement/utils/SettlementUtils.ts | 20 ++++++++++++++++++- .../sequencer/test/settlement/Settlement.ts | 15 +++++--------- 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/packages/sdk/src/client/ClientAppChain.ts b/packages/sdk/src/client/ClientAppChain.ts index bc1b1c63d..b5482f16c 100644 --- a/packages/sdk/src/client/ClientAppChain.ts +++ b/packages/sdk/src/client/ClientAppChain.ts @@ -31,6 +31,7 @@ import { MinimalAppChainDefinition, BlockExplorerQuery, BlockExplorerTransportModule, + MinaSigner, } from "@proto-kit/sequencer"; import { container } from "tsyringe"; import { Field, PublicKey, UInt64 } from "o1js"; @@ -181,7 +182,8 @@ export class ClientAppChain< isMessage: false, }); - const signer = this.container.resolve("Signer"); + // This is replaced with MinaSigner, in that case the 'Signer' interface itself can be replaced by MinaSigner in all around the repo. + const signer = this.container.resolve("Signer"); const transactionSender = this.container.resolve("TransactionSender"); diff --git a/packages/sdk/src/transaction/AuroSigner.ts b/packages/sdk/src/transaction/AuroSigner.ts index 14c999aec..6d07175bd 100644 --- a/packages/sdk/src/transaction/AuroSigner.ts +++ b/packages/sdk/src/transaction/AuroSigner.ts @@ -1,9 +1,10 @@ -import { Field, Signature } from "o1js"; +import { Field, Signature, PublicKey, Transaction } from "o1js"; import { injectable } from "tsyringe"; import { AppChainModule } from "@proto-kit/sequencer"; - import { Signer } from "./InMemorySigner"; + +// Will be implemented for MinaSigner here. @injectable() export class AuroSigner extends AppChainModule implements Signer { public async sign(message: Field[]): Promise { @@ -14,5 +15,6 @@ export class AuroSigner extends AppChainModule implements Signer { }); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return Signature.fromBase58(response.signature); + } -} +} \ No newline at end of file diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 9f70ffb89..48338151d 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -47,6 +47,7 @@ import { ProvenSettlementPermissions } from "./permissions/ProvenSettlementPermi import { SignedSettlementPermissions } from "./permissions/SignedSettlementPermissions"; import { SettlementUtils } from "./utils/SettlementUtils"; import { BridgingModule } from "./BridgingModule"; +import { MinaSigner } from "./MinaSigner"; export type SettlementModuleConfig = { feepayer: PrivateKey; @@ -93,12 +94,13 @@ export class SettlementModule @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, + @inject("Signer") signer: MinaSigner, @inject("FeeStrategy") private readonly feeStrategy: FeeStrategy, private readonly settlementStartupModule: SettlementStartupModule ) { super(); - this.utils = new SettlementUtils(areProofsEnabled, baseLayer); + this.utils = new SettlementUtils(areProofsEnabled, baseLayer, signer); } public dependencies() { @@ -115,6 +117,7 @@ export class SettlementModule ); } + // These will be removed and they will be obtained from SettlementSigner. public getContractKeys(): { settlement: PrivateKey; dispatch: PrivateKey; @@ -158,18 +161,11 @@ export class SettlementModule return this.contracts; } - public signTransaction( + public async signTransaction( tx: Transaction, - pks: PrivateKey[], - tokenContractKeys: PrivateKey[] = [], preventNoncePreconditionFor: PublicKey[] = [] - ): Transaction { - return this.utils.signTransaction( - tx, - pks, - this.getContractSigningKeys().concat(tokenContractKeys), - preventNoncePreconditionFor - ); + ): Promise> { + return await this.utils.signTransactionWithModule(tx, preventNoncePreconditionFor); } private async fetchContractAccounts() { diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 152c48775..6076fb24c 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -10,6 +10,7 @@ import { import { AreProofsEnabled, mapSequential } from "@proto-kit/common"; import type { MinaBaseLayer } from "../../protocol/baselayer/MinaBaseLayer"; +import { MinaSigner } from "../MinaSigner"; /** * Utils class that provides methods for sending transactions that are signed-settlement-enabled @@ -17,7 +18,8 @@ import type { MinaBaseLayer } from "../../protocol/baselayer/MinaBaseLayer"; export class SettlementUtils { public constructor( private readonly areProofsEnabled: AreProofsEnabled, - private readonly baseLayer: MinaBaseLayer + private readonly baseLayer: MinaBaseLayer, + private readonly signer: MinaSigner ) {} /** @@ -31,6 +33,22 @@ export class SettlementUtils { ); } + // Problem with this function for now is that it will not sign selectively. + // It should be added to make this signer mechanism applicable to everywhere. + public signTransactionWithModule( + tx: Transaction , + preventNoncePreconditionFor: PublicKey[] = [], + ){ + const contractPublicKeys = this.isSignedSettlement() ? this.signer.getContractKeys() : []; + this.requireSignatureIfNecessary( + tx, + contractPublicKeys, + preventNoncePreconditionFor + ) + + return this.signer.signTransaction(tx); + } + /** * Sign transaction with two variants: * diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 49704ba7f..e6b048cf8 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -18,7 +18,6 @@ import { import { ClientAppChain, BlockStorageNetworkStateModule, - InMemorySigner, InMemoryTransactionSender, StateServiceQueryModule, InMemoryBlockExplorer, @@ -63,7 +62,7 @@ import { SettlementUtils } from "../../src/settlement/utils/SettlementUtils"; import { FungibleTokenContractModule } from "../../src/settlement/utils/FungibleTokenContractModule"; import { FungibleTokenAdminContractModule } from "../../src/settlement/utils/FungibleTokenAdminContractModule"; import { MinaNetworkUtils } from "../../src/protocol/baselayer/network-utils/MinaNetworkUtils"; - +import { SettlementSigner } from "../../src"; import { Balances, BalancesKey } from "./mocks/Balances"; import { WithdrawalMessageProcessor, Withdrawals } from "./mocks/Withdrawals"; @@ -141,7 +140,8 @@ export const settlementTestFn = ( WithdrawalMessageProcessor, }), - Signer: InMemorySigner, + // Instead of InMemorySigner, using Settlement Signer here. + Signer: SettlementSigner, TransactionSender: InMemoryTransactionSender, QueryTransportModule: StateServiceQueryModule, NetworkStateTransportModule: BlockStorageNetworkStateModule, @@ -197,6 +197,7 @@ export const settlementTestFn = ( QueryTransportModule: {}, Signer: { signer: sequencerKey, + contractKeys: {settlementKey: settlementKey, dispatchKey: dispatchKey, minaBridgeKey: minaBridgeKey} }, NetworkStateTransportModule: {}, BlockExplorerTransportModule: {}, @@ -362,8 +363,6 @@ export const settlementTestFn = ( settlementModule.signTransaction( tx, - [sequencerKey, tokenOwnerKey.tokenOwner, tokenOwnerKey.admin], - [tokenOwnerKey.tokenOwner, tokenOwnerKey.admin] ); await appChain.sequencer @@ -539,8 +538,6 @@ export const settlementTestFn = ( settlementModule.signTransaction( tx, - [userKey], - [tokenOwnerKey.tokenOwner], [dispatch.address] ); @@ -720,10 +717,8 @@ export const settlementTestFn = ( } ); - const signed = settlementModule.signTransaction( + const signed = await settlementModule.signTransaction( tx, - [userKey], - [tokenBridgeKey, tokenOwnerKey.tokenOwner] ); await appChain.sequencer From 0c3a43c974fdee38cb48e5c43338ab18490af0b5 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 10:47:24 +0300 Subject: [PATCH 005/155] feat: change MinaSigner config type and signing logics --- .../sequencer/src/settlement/MinaSigner.ts | 156 ++++++++++++------ 1 file changed, 101 insertions(+), 55 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index 9c282c481..b67c0ee10 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -1,89 +1,135 @@ import { Field, PrivateKey, PublicKey, Signature, Transaction } from "o1js"; +import { noop } from "@proto-kit/common"; import { injectable } from "tsyringe"; -import { AppChainModule } from "../appChain/AppChainModule"; +export interface SignTxOptions { + pubKeys?: PublicKey[]; + additionalKeys?: PrivateKey[]; + signWithContract?: boolean; +} + +import { SequencerModule } from "../sequencer/builder/SequencerModule"; export interface MinaSigner { - getContractKeys(): PublicKey[]; - - sign(signatureData: Field[]): Promise; - - signTransaction( - tx: Transaction - ): Promise>; + getSignerAddress(): PublicKey; + + getContractAddresses(): PublicKey[]; + + sign(signatureData: Field[]): Signature; + signMessageWithContract(signatureData: Field[]): Signature, + + signTx( + tx: Transaction, + options?: SignTxOptions + ): Transaction; + } export interface InMemorySignerConfig { - signer: PrivateKey; - contractKeys?: { - settlementKey: PrivateKey, - dispatchKey: PrivateKey, - minaBridgeKey: PrivateKey, - } - // optional way: - // additionalKeys?: PrivateKey[] + signer: PrivateKey; + contractKeys: PrivateKey[]; + tokenControllers?: PrivateKey[]; + tokenBridgeKeys?: PrivateKey[]; } @injectable() -export class SettlementSigner - extends AppChainModule +export class InMemoryMinaSigner + extends SequencerModule implements MinaSigner { + + private _keyMap!: Map; + public constructor() { super(); } + private initializeKeyMap(): void { + if (this._keyMap) return; + + this._keyMap = new Map(); + + this.config.tokenBridgeKeys?.forEach(key => { + this._keyMap.set(key.toPublicKey().toBase58(), key); + }); + + this.config.contractKeys.forEach(key => { + this._keyMap.set(key.toPublicKey().toBase58(), key); + }); + + this.config.tokenControllers?.forEach(key => { + this._keyMap.set(key.toPublicKey().toBase58(), key); + }); + } + + public getSignerAddress(): PublicKey{ + return this.config.signer.toPublicKey(); + } + /** - * * Contracts public key getter. Since we expect private keys to be managed here, public keys can be returned from this module. + * With index order, returned public keys are public keys of: + * [0] -> settlement contract + * [1] -> dispatch contract + * [2] -> minaBridge contract * - * @returns Array of public keys, not ordered yet - * + * @returns Array of public keys. */ - public getContractKeys(): PublicKey[] { - const contractKeysObject = this.config.contractKeys ?? {}; + public getContractAddresses(): PublicKey[] { + if(!this.config.contractKeys){ + return []; + } - // Use Object.values to get the PrivateKey objects and filter out any undefined values. - const contractPrivateKeys: PrivateKey[] = Object.values(contractKeysObject).filter( - (key): key is PrivateKey => key !== undefined - ); + const contractPrivateKeys = this.config.contractKeys; - return contractPrivateKeys.map(key => key.toPublicKey()); + return contractPrivateKeys.map((key) => key.toPublicKey()); } /** * @param signatureData Data to be signed. * @returns Signature signed by signer. */ - public async sign(signatureData: Field[]): Promise { + public sign(signatureData: Field[]): Signature { return Signature.create(this.config.signer, signatureData); } - - /** - * - * Signs the transaction with all available keys (will/should be change and signing must be done in a selective way). - * - * @param tx Transaction to be signed with. - * @returns signed transaction object + /** + * @param signatureData Data to be signed. + * @returns Signature signed with private key of settlement contract address. */ - public async signTransaction( - tx: Transaction - ): Promise> { - const contractKeysObject = this.config.contractKeys ?? {}; - - // Extract the PrivateKey objects from the configuration object - const contractPrivateKeys: PrivateKey[] = Object.values(contractKeysObject).filter( - (key): key is PrivateKey => key !== undefined - ); - - // Combine the primary signer (feepayer) with the contract keys - const allKeys: PrivateKey[] = [ - this.config.signer, - ...contractPrivateKeys - ]; - - // Sign the transaction with the collected keys - return tx.sign(allKeys); + public signMessageWithContract(signatureData: Field[]): Signature{ + return Signature.create(this.config.contractKeys[0],signatureData); + } + + public signTx( + tx: Transaction, + options: SignTxOptions = {} +): Transaction { + const { + pubKeys = [], + additionalKeys = [], + signWithContract = false + } = options; + + const privateKeys: PrivateKey[] = []; + + for (const pubKey of pubKeys) { + const privKey = this._keyMap.get(pubKey.toBase58()); + if (!privKey) { + throw new Error(`Error in signTx function: relevant private key to ${pubKey.toBase58()} not found!`); + } + privateKeys.push(privKey); + } + + if (signWithContract) { + privateKeys.push(...this.config.contractKeys); } + + const keys = [this.config.signer, ...privateKeys, ...additionalKeys]; + return tx.sign(keys); +} -} \ No newline at end of file + public async start() { + this.initializeKeyMap(); + noop(); + } +} From 55819cd125b0110797931e1ada110a4747282635 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 10:47:35 +0300 Subject: [PATCH 006/155] refactor: update signTransaction utility function with MinaSigner --- .../src/settlement/utils/SettlementUtils.ts | 94 ++++++++++++------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 6076fb24c..051a6ef81 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -4,6 +4,7 @@ import { Field, PrivateKey, PublicKey, + Signature, Transaction, UInt32, } from "o1js"; @@ -12,6 +13,14 @@ import { AreProofsEnabled, mapSequential } from "@proto-kit/common"; import type { MinaBaseLayer } from "../../protocol/baselayer/MinaBaseLayer"; import { MinaSigner } from "../MinaSigner"; +interface SignTransactionOptions { + signingWithSignatureCheck?: PublicKey[]; + signingPublicKeys?: PublicKey[]; + additionalKeys?: PrivateKey[]; + preventNoncePreconditionFor?: PublicKey[]; + signWithContract?: boolean; +} + /** * Utils class that provides methods for sending transactions that are signed-settlement-enabled */ @@ -33,45 +42,60 @@ export class SettlementUtils { ); } - // Problem with this function for now is that it will not sign selectively. - // It should be added to make this signer mechanism applicable to everywhere. - public signTransactionWithModule( - tx: Transaction , - preventNoncePreconditionFor: PublicKey[] = [], - ){ - const contractPublicKeys = this.isSignedSettlement() ? this.signer.getContractKeys() : []; - this.requireSignatureIfNecessary( - tx, - contractPublicKeys, - preventNoncePreconditionFor - ) - - return this.signer.signTransaction(tx); + public getSigner(): PublicKey { + return this.signer.getSignerAddress(); + } + + + public sign(signatureData: Field[]): Signature{ + return this.signer.sign(signatureData); + } + + public signMessageWithContract(signatureData: Field[]): Signature{ + return this.signer.signMessageWithContract(signatureData); + } + + public getContractAddresses(): PublicKey[] { + const keys = this.signer.getContractAddresses(); + return [ + keys[0], + keys[1], + keys[2], + ] } - /** - * Sign transaction with two variants: - * - * - If it normal settlement (proofs enabled or mock proofs): - * Sign the transaction normally - * - * - If it is signed settlement: - * Signed the transactions and make all contract AUs where a private key is known - * via the contractKeys param require a signature and sign them using that key - */ public signTransaction( tx: Transaction, - pks: PrivateKey[], - contractKeys: PrivateKey[], - preventNoncePreconditionFor: PublicKey[] = [] - ): Transaction { - const contractKeyArray = this.isSignedSettlement() ? contractKeys : []; - this.requireSignatureIfNecessary( - tx, - contractKeyArray.map((key) => key.toPublicKey()), - preventNoncePreconditionFor - ); - return tx.sign([...pks, ...contractKeyArray]); + options: SignTransactionOptions = {} + ) { + const { + signingWithSignatureCheck = [], + signingPublicKeys = [], + additionalKeys = [], + preventNoncePreconditionFor = [], + signWithContract = false + } = options; + + let contractKeyArray = this.isSignedSettlement() ? signingWithSignatureCheck : []; + + if (signWithContract) { + this.requireSignatureIfNecessary( + tx, + this.getContractAddresses().concat(contractKeyArray), + preventNoncePreconditionFor + ); + } else { + this.requireSignatureIfNecessary( + tx, + contractKeyArray, + preventNoncePreconditionFor + ); + } + + const pubKeys = signingWithSignatureCheck + .concat(signingPublicKeys); + + return this.signer.signTx(tx, {pubKeys, additionalKeys, signWithContract}); } private requireSignatureIfNecessary( From ac04e3abfa62d88af1703425e1e9e3676f10b22c Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:03:09 +0300 Subject: [PATCH 007/155] refactor: update signing functions with MinaSigner based ones --- .../src/settlement/SettlementModule.ts | 111 ++++++++---------- 1 file changed, 47 insertions(+), 64 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 48338151d..8599b2ce1 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -14,7 +14,6 @@ import { Mina, PrivateKey, PublicKey, - Signature, TokenContract, TokenId, Transaction, @@ -74,6 +73,7 @@ export class SettlementModule dispatch: DispatchSmartContract; }; + // Those should be removed, so that everything will be handled private keys?: { settlement: PrivateKey; dispatch: PrivateKey; @@ -94,7 +94,7 @@ export class SettlementModule @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, - @inject("Signer") signer: MinaSigner, + @inject("SettlementSigner") private readonly signer: MinaSigner, @inject("FeeStrategy") private readonly feeStrategy: FeeStrategy, private readonly settlementStartupModule: SettlementStartupModule @@ -117,31 +117,18 @@ export class SettlementModule ); } - // These will be removed and they will be obtained from SettlementSigner. - public getContractKeys(): { - settlement: PrivateKey; - dispatch: PrivateKey; - minaBridge: PrivateKey; - } { - const keys = this.keys ?? this.config.keys; - if (keys === undefined) { - throw new Error("Contracts not initialized yet"); - } - return keys; - } - - public getContractSigningKeys() { - return Object.values(this.getContractKeys()); - } - public getAddresses() { - const keys = this.getContractKeys(); + const keysArray = this.utils.getContractAddresses(); return { - settlement: keys.settlement.toPublicKey(), - dispatch: keys.dispatch.toPublicKey(), + settlement: keysArray[0], + dispatch: keysArray[1] }; } + public getContractAddresses(){ + return this.utils.getContractAddresses(); + } + public getContracts() { if (this.contracts === undefined) { const addresses = this.getAddresses(); @@ -160,14 +147,7 @@ export class SettlementModule } return this.contracts; } - - public async signTransaction( - tx: Transaction, - preventNoncePreconditionFor: PublicKey[] = [] - ): Promise> { - return await this.utils.signTransactionWithModule(tx, preventNoncePreconditionFor); - } - + private async fetchContractAccounts() { const contracts = this.getContracts(); await this.utils.fetchContractAccounts( @@ -175,7 +155,6 @@ export class SettlementModule contracts.dispatch ); } - public async settleBatch( batch: SettleableBatch, options: { @@ -184,13 +163,12 @@ export class SettlementModule ): Promise { await this.fetchContractAccounts(); const { settlement: settlementContract, dispatch } = this.getContracts(); - const { feepayer } = this.config; - + const feepayer = this.utils.getSigner(); log.debug("Preparing settlement"); const lastSettlementL1BlockHeight = settlementContract.lastSettlementL1BlockHeight.get().value; - const signature = Signature.create(feepayer, [ + const signature = await this.utils.sign([ BATCH_SIGNATURE_PREFIX, lastSettlementL1BlockHeight, ]); @@ -205,7 +183,7 @@ export class SettlementModule const tx = await Mina.transaction( { - sender: feepayer.toPublicKey(), + sender: feepayer, nonce: options?.nonce, fee: this.feeStrategy.getFee(), memo: "Protokit settle", @@ -215,7 +193,7 @@ export class SettlementModule dynamicBlockProof, signature, dispatch.address, - feepayer.toPublicKey(), + feepayer, batch.fromNetworkState, batch.toNetworkState, latestSequenceStateHash @@ -223,7 +201,7 @@ export class SettlementModule } ); - this.utils.signTransaction(tx, [feepayer], this.getContractSigningKeys()); + this.utils.signTransaction(tx, { signWithContract: true }); const { hash: transactionHash } = await this.transactionSender.proveAndSendTransaction(tx, "included"); @@ -243,16 +221,16 @@ export class SettlementModule return settlement; } + // Can't do anything for now - initialize() method use settlementKey. public async deploy( - settlementKey: PrivateKey, - dispatchKey: PrivateKey, - minaBridgeKey: PrivateKey, + settlementKey: PublicKey, + dispatchKey: PublicKey, + minaBridgeKey: PublicKey, options: { nonce?: number; } = {} ) { - const feepayerKey = this.config.feepayer; - const feepayer = feepayerKey.toPublicKey(); + const feepayer = this.utils.getSigner(); const nonce = options?.nonce ?? 0; @@ -260,8 +238,8 @@ export class SettlementModule SettlementContractModule >("SettlementContractModule"); const { settlement, dispatch } = sm.createContracts({ - settlement: settlementKey.toPublicKey(), - dispatch: dispatchKey.toPublicKey(), + settlement: settlementKey, + dispatch: dispatchKey, }); const verificationsKeys = @@ -300,21 +278,24 @@ export class SettlementModule dispatchKey.toPublicKey() ); } - ).sign([feepayerKey, settlementKey, dispatchKey]); + ) + + this.utils.signTransaction( + tx, + { + signWithContract:true + } + ) // Note: We can't use this.signTransaction on the above tx // This should already apply the tx result to the // cached accounts / local blockchain await this.transactionSender.proveAndSendTransaction(tx, "included"); - this.keys = { - settlement: settlementKey, - dispatch: dispatchKey, - minaBridge: minaBridgeKey, - }; - await this.utils.fetchContractAccounts(settlement, dispatch); + const contractSignature = this.utils.signMessageWithContract(settlementKey.toFields()); + const initTx = await Mina.transaction( { sender: feepayer, @@ -335,10 +316,10 @@ export class SettlementModule const initTxSigned = this.utils.signTransaction( initTx, - // Specify the mina bridge key here explicitly, since initialize() will issue - // a account update to that address and by default new accounts have a signature permission - [feepayerKey, minaBridgeKey], - [...this.getContractSigningKeys(), minaBridgeKey] + { + signingWithSignatureCheck:[minaBridgeKey], + signWithContract: true + } ); await this.transactionSender.proveAndSendTransaction( @@ -349,14 +330,13 @@ export class SettlementModule public async deployTokenBridge( owner: TokenContract, - ownerKey: PrivateKey, - contractKey: PrivateKey, + ownerPublicKey: PublicKey, + contractKey: PublicKey, options: { nonce?: number; } ) { - const feepayerKey = this.config.feepayer; - const feepayer = feepayerKey.toPublicKey(); + const feepayer = this.utils.getSigner(); const nonce = options?.nonce ?? undefined; const tokenId = owner.deriveTokenId(); @@ -373,19 +353,22 @@ export class SettlementModule AccountUpdate.fundNewAccount(feepayer, 1); await settlement.addTokenBridge( tokenId, - contractKey.toPublicKey(), + contractKey, dispatch.address ); await owner.approveAccountUpdate(settlement.self); } ); + // Only ContractKeys and OwnerKey for check. + // Used all in signing process. const txSigned = this.utils.signTransaction( tx, - // Specify the mina bridge key here explicitly, since deploy() will issue - // a account update to that address and by default new accounts have a signature permission - [feepayerKey, contractKey], - [...this.getContractSigningKeys(), ownerKey] + { + signingWithSignatureCheck:[ownerPublicKey], + signingPublicKeys:[contractKey], + signWithContract: true + } ); await this.transactionSender.proveAndSendTransaction(txSigned, "included"); From 92632d2d4462b7260013150d1a76ae6c6859516f Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:08:22 +0300 Subject: [PATCH 008/155] refactor: update address check in settlement contract --- .../contracts/SettlementSmartContract.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index 06ee0ce68..56e7469d7 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -69,7 +69,14 @@ export interface SettlementContractType { args: DeployArgs | undefined, permissions: Permissions, sequencer: PublicKey, +<<<<<<< HEAD dispatchContract: PublicKey +======= + dispatchContract: PublicKey, + bridgeContract: PublicKey, + contractKey: PublicKey, + contractSignature: Signature +>>>>>>> de1c1c20 (refactor: update address check in settlement contract) ) => Promise; assertStateRoot: (root: Field) => AccountUpdate; @@ -246,13 +253,39 @@ export abstract class SettlementSmartContractBase extends TokenContract { protected async initializeBase( sequencer: PublicKey, +<<<<<<< HEAD dispatchContract: PublicKey +======= + dispatchContract: PublicKey, + bridgeContract: PublicKey, + contractKey: PublicKey, + contractSignature: Signature, +>>>>>>> de1c1c20 (refactor: update address check in settlement contract) ) { this.sequencerKey.set(sequencer.x); this.stateRoot.set(LinkedMerkleTree.EMPTY_ROOT); this.blockHashRoot.set(Field(BlockHashMerkleTree.EMPTY_ROOT)); this.networkStateHash.set(NetworkState.empty().hash()); this.dispatchContractAddressX.set(dispatchContract.x); +<<<<<<< HEAD +======= + + const { DispatchContract } = SettlementSmartContractBase.args; + const contractInstance = new DispatchContract(dispatchContract); + await contractInstance.initialize(this.address); + + // Deploy bridge contract for $Mina + await this.deployTokenBridge( + this.tokenId, + bridgeContract, + dispatchContract, + true + ); + + contractSignature.verify(contractKey, contractKey.toFields()); + // contractKey.toPublicKey().assertEquals(this.address); + +>>>>>>> de1c1c20 (refactor: update address check in settlement contract) } protected async settleBase( @@ -441,8 +474,26 @@ export class SettlementSmartContract await this.initializeBase(sequencer, dispatchContract); } +<<<<<<< HEAD @method async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); +======= + @method + public async initialize( + sequencer: PublicKey, + dispatchContract: PublicKey, + bridgeContract: PublicKey, + contractKey: PublicKey, + contractSignature: Signature + ) { + await this.initializeBase( + sequencer, + dispatchContract, + bridgeContract, + contractKey, + contractSignature + ); +>>>>>>> de1c1c20 (refactor: update address check in settlement contract) } @method From 57a5579e07d0872e2fb72524ec0d57d0facb7358 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:09:30 +0300 Subject: [PATCH 009/155] refactor: update bridging module tx signings w/ MinaSigner --- .../sequencer/src/settlement/BridgingModule.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index e117e8c1a..28b71f736 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -100,7 +100,7 @@ export class BridgingModule { private readonly feeStrategy: FeeStrategy, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, - @inject("Signer") signer: MinaSigner, + @inject("SettlementSigner") signer: MinaSigner, @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender ) { @@ -116,7 +116,7 @@ export class BridgingModule { protected settlementContractModule(): SettlementContractModule { return this.protocol.dependencyContainer.resolve( "SettlementContractModule" - ); + ); } public getBridgingModuleConfig(): BridgeContractConfig { @@ -400,10 +400,12 @@ export class BridgingModule { } ); + const signatureOnes = options.contractKeys.map(x => x.toPublicKey()); const signedTx = this.utils.signTransaction( tx, - [feepayer], - options.contractKeys + { + signingWithSignatureCheck:signatureOnes, + } ); await this.transactionSender.proveAndSendTransaction( @@ -537,10 +539,12 @@ export class BridgingModule { log.debug("Sending rollup transaction:"); log.debug(tx.toPretty()); + const signedTx = this.utils.signTransaction( tx, - [feepayer], - options.contractKeys + { + signingWithSignatureCheck:[...options.contractKeys.map(x => x.toPublicKey())] + } ); await this.transactionSender.proveAndSendTransaction( From e88e104b228c16df12956bd1e6339c7d71f9db70 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:12:40 +0300 Subject: [PATCH 010/155] refactor: inject MinaSigner optionally to SequencerStartupModule --- .../sequencer/src/sequencer/SequencerStartupModule.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 2885edba9..16062356b 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -23,10 +23,10 @@ import { VerificationKeyService } from "../protocol/runtime/RuntimeVerificationK import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; import { SettlementUtils } from "../settlement/utils/SettlementUtils"; import { NoopBaseLayer } from "../protocol/baselayer/NoopBaseLayer"; +import { MinaSigner } from "../settlement/MinaSigner"; import { SequencerModule, sequencerModule } from "./builder/SequencerModule"; import { Closeable, closeable } from "./builder/Closeable"; -import { MinaSigner } from "../settlement/MinaSigner"; @sequencerModule() @closeable() @@ -46,8 +46,7 @@ export class SequencerStartupModule private readonly baseLayer: MinaBaseLayer | undefined, @inject("AreProofsEnabled") private readonly areProofsEnabled: AreProofsEnabled, - @inject("Signer") - private readonly Signer: MinaSigner + @inject("SettlementSigner", {isOptional: true}) private readonly signer: MinaSigner | undefined, ) { super(); } @@ -145,11 +144,11 @@ export class SequencerStartupModule // TODO Find a way to generalize this or at least make it nicer - too much logic here const isSignedSettlement = - this.baseLayer !== undefined && !(this.baseLayer instanceof NoopBaseLayer) + this.baseLayer !== undefined && !(this.baseLayer instanceof NoopBaseLayer) && (this.signer !== undefined) ? new SettlementUtils( this.areProofsEnabled, this.baseLayer, - this.Signer + this.signer ).isSignedSettlement() : undefined; From 172b9067a23139c6b5de79d5a67be0f0829face3 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:27:06 +0300 Subject: [PATCH 011/155] refactor: update settlementTestFn with MinaSigner functions --- .../sequencer/test/settlement/Settlement.ts | 81 ++++++++++++++----- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index e6b048cf8..9009205b5 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -21,6 +21,7 @@ import { InMemoryTransactionSender, StateServiceQueryModule, InMemoryBlockExplorer, + InMemorySigner, } from "@proto-kit/sdk"; import { AccountUpdate, @@ -52,6 +53,7 @@ import { ProvenSettlementPermissions, VanillaTaskWorkerModules, Sequencer, + InMemoryMinaSigner, } from "../../src"; import { BlockProofSerializer } from "../../src/protocol/production/tasks/serializers/BlockProofSerializer"; import { testingSequencerModules } from "../TestingSequencer"; @@ -62,7 +64,7 @@ import { SettlementUtils } from "../../src/settlement/utils/SettlementUtils"; import { FungibleTokenContractModule } from "../../src/settlement/utils/FungibleTokenContractModule"; import { FungibleTokenAdminContractModule } from "../../src/settlement/utils/FungibleTokenAdminContractModule"; import { MinaNetworkUtils } from "../../src/protocol/baselayer/network-utils/MinaNetworkUtils"; -import { SettlementSigner } from "../../src"; + import { Balances, BalancesKey } from "./mocks/Balances"; import { WithdrawalMessageProcessor, Withdrawals } from "./mocks/Withdrawals"; @@ -87,6 +89,12 @@ export const settlementTestFn = ( tokenOwner: PrivateKey.random(), admin: PrivateKey.random(), }; + + const tokenOwnerPubKeys ={ + tokenOwner: tokenOwnerKey.tokenOwner.toPublicKey(), + admin: tokenOwnerKey.admin.toPublicKey() + } + const tokenOwner = tokenConfig !== undefined ? // eslint-disable-next-line new-cap @@ -120,6 +128,7 @@ export const settlementTestFn = ( { BaseLayer: MinaBaseLayer, SettlementModule: SettlementModule, + SettlementSigner: InMemoryMinaSigner, }, { SettlementProvingTask, @@ -140,8 +149,7 @@ export const settlementTestFn = ( WithdrawalMessageProcessor, }), - // Instead of InMemorySigner, using Settlement Signer here. - Signer: SettlementSigner, + Signer: InMemorySigner, TransactionSender: InMemoryTransactionSender, QueryTransportModule: StateServiceQueryModule, NetworkStateTransportModule: BlockStorageNetworkStateModule, @@ -163,6 +171,21 @@ export const settlementTestFn = ( BatchProducerModule: {}, LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), BaseLayer: baseLayerConfig, + SettlementSigner: { + signer: sequencerKey, + contractKeys: [ + settlementKey, + dispatchKey, + minaBridgeKey, + ], + tokenControllers:[ + tokenOwnerKey.tokenOwner, + tokenOwnerKey.admin + ], + tokenBridgeKeys:[ + tokenBridgeKey + ], + }, BlockProducerModule: {}, FeeStrategy: {}, SettlementModule: { @@ -197,7 +220,6 @@ export const settlementTestFn = ( QueryTransportModule: {}, Signer: { signer: sequencerKey, - contractKeys: {settlementKey: settlementKey, dispatchKey: dispatchKey, minaBridgeKey: minaBridgeKey} }, NetworkStateTransportModule: {}, BlockExplorerTransportModule: {}, @@ -260,7 +282,6 @@ export const settlementTestFn = ( bridgingModule = appChain.sequencer.resolve( "BridgingModule" ) as BridgingModule; - trigger = appChain.sequencer.dependencyContainer.resolve( "BlockTrigger" @@ -302,7 +323,7 @@ export const settlementTestFn = ( "should deploy", async () => { // Deploy contract - await settlementModule.deploy(settlementKey, dispatchKey, minaBridgeKey, { + await settlementModule.deploy(settlementKey.toPublicKey(), dispatchKey.toPublicKey(), minaBridgeKey.toPublicKey(), { nonce: nonceCounter, }); @@ -310,7 +331,7 @@ export const settlementTestFn = ( console.log("Deployed"); }, - timeout * 2 + timeout * 2 ); if (tokenConfig !== undefined) { @@ -361,9 +382,13 @@ export const settlementTestFn = ( ); console.log(tx.toPretty()); - settlementModule.signTransaction( + settlementModule.utils.signTransaction( tx, - ); + { + signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin], + signWithContract:true + } + ) await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -405,9 +430,8 @@ export const settlementTestFn = ( ); settlementModule.utils.signTransaction( tx, - [sequencerKey], - [tokenOwnerKey.tokenOwner, tokenOwnerKey.admin] - ); + {signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin],} + ) await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -419,10 +443,11 @@ export const settlementTestFn = ( it( "should deploy custom token bridge", async () => { + await settlementModule.deployTokenBridge( tokenOwner!, - tokenOwnerKey.tokenOwner, - tokenBridgeKey, + tokenOwnerPubKeys.tokenOwner, + tokenBridgeKey.toPublicKey(), { nonce: nonceCounter++, } @@ -443,6 +468,13 @@ export const settlementTestFn = ( "should settle", async () => { try { + console.log('seq Key: ', sequencerKey.toPublicKey().toBase58()) + console.log('sett Key: ', settlementKey.toPublicKey().toBase58()) + console.log('disp Key: ', dispatchKey.toPublicKey().toBase58()) + console.log('minaBridge Key: ', minaBridgeKey.toPublicKey().toBase58()) + console.log('tokenOwner Key: ', tokenOwnerKey.tokenOwner.toPublicKey().toBase58()) + console.log('token admin Key: ', tokenOwnerKey.admin.toPublicKey().toBase58()) + console.log('token bridge key: ', tokenBridgeKey.toPublicKey().toBase58()); const [, batch] = await createBatch(true); acc0L2Nonce++; @@ -535,10 +567,15 @@ export const settlementTestFn = ( } } ); - - settlementModule.signTransaction( + + settlementModule.utils.signTransaction( tx, - [dispatch.address] + { + signingWithSignatureCheck:[tokenOwnerPubKeys.tokenOwner], + signWithContract:true, + additionalKeys:[userKey], + preventNoncePreconditionFor:[dispatch.address] + } ); console.log(tx.toPretty()); @@ -717,9 +754,14 @@ export const settlementTestFn = ( } ); - const signed = await settlementModule.signTransaction( + const signed = settlementModule.utils.signTransaction( tx, - ); + { + signingWithSignatureCheck:[tokenBridgeKey.toPublicKey(), tokenOwnerPubKeys.tokenOwner], + signWithContract:true, + additionalKeys:[userKey], + } + ) await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -746,3 +788,4 @@ export const settlementTestFn = ( timeout ); }; + \ No newline at end of file From aebb1ba066bcac13480b5d6371b818980e76e16f Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:48:53 +0300 Subject: [PATCH 012/155] fix: resolve container is Signer instead of MinaSigner --- packages/sdk/src/client/ClientAppChain.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sdk/src/client/ClientAppChain.ts b/packages/sdk/src/client/ClientAppChain.ts index b5482f16c..105489c3e 100644 --- a/packages/sdk/src/client/ClientAppChain.ts +++ b/packages/sdk/src/client/ClientAppChain.ts @@ -182,8 +182,7 @@ export class ClientAppChain< isMessage: false, }); - // This is replaced with MinaSigner, in that case the 'Signer' interface itself can be replaced by MinaSigner in all around the repo. - const signer = this.container.resolve("Signer"); + const signer = this.container.resolve("Signer"); const transactionSender = this.container.resolve("TransactionSender"); From cef40c0e1a245cdad583b05f2990dcbae5b063c6 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:52:24 +0300 Subject: [PATCH 013/155] refactor: remove unnecessary conditional checks --- .../sequencer/src/settlement/MinaSigner.ts | 105 +++++++++--------- 1 file changed, 50 insertions(+), 55 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index b67c0ee10..1f3ae8b24 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -2,32 +2,31 @@ import { Field, PrivateKey, PublicKey, Signature, Transaction } from "o1js"; import { noop } from "@proto-kit/common"; import { injectable } from "tsyringe"; +import { SequencerModule } from "../sequencer/builder/SequencerModule"; + export interface SignTxOptions { pubKeys?: PublicKey[]; additionalKeys?: PrivateKey[]; signWithContract?: boolean; } - -import { SequencerModule } from "../sequencer/builder/SequencerModule"; export interface MinaSigner { getSignerAddress(): PublicKey; getContractAddresses(): PublicKey[]; sign(signatureData: Field[]): Signature; - signMessageWithContract(signatureData: Field[]): Signature, + signMessageWithContract(signatureData: Field[]): Signature; signTx( tx: Transaction, options?: SignTxOptions ): Transaction; - } export interface InMemorySignerConfig { signer: PrivateKey; - contractKeys: PrivateKey[]; - tokenControllers?: PrivateKey[]; + contractKeys: PrivateKey[]; + tokenControllers?: PrivateKey[]; tokenBridgeKeys?: PrivateKey[]; } @@ -36,51 +35,45 @@ export class InMemoryMinaSigner extends SequencerModule implements MinaSigner { + private keyMap!: Map; - private _keyMap!: Map; - public constructor() { super(); } private initializeKeyMap(): void { - if (this._keyMap) return; - - this._keyMap = new Map(); + this.keyMap = new Map(); - this.config.tokenBridgeKeys?.forEach(key => { - this._keyMap.set(key.toPublicKey().toBase58(), key); + this.config.tokenBridgeKeys?.forEach((key) => { + this.keyMap.set(key.toPublicKey().toBase58(), key); }); - this.config.contractKeys.forEach(key => { - this._keyMap.set(key.toPublicKey().toBase58(), key); + this.config.contractKeys.forEach((key) => { + this.keyMap.set(key.toPublicKey().toBase58(), key); }); - this.config.tokenControllers?.forEach(key => { - this._keyMap.set(key.toPublicKey().toBase58(), key); + this.config.tokenControllers?.forEach((key) => { + this.keyMap.set(key.toPublicKey().toBase58(), key); }); } - public getSignerAddress(): PublicKey{ + public getSignerAddress(): PublicKey { return this.config.signer.toPublicKey(); } - + /** - * Contracts public key getter. Since we expect private keys to be managed here, public keys can be returned from this module. - * With index order, returned public keys are public keys of: + * Contracts public key getter. Since we expect private keys to be managed here, + * public keys can be returned from this module. + * With index order, returned public keys are public keys of: * [0] -> settlement contract * [1] -> dispatch contract * [2] -> minaBridge contract - * - * @returns Array of public keys. + * + * @returns Array of public keys. */ public getContractAddresses(): PublicKey[] { - if(!this.config.contractKeys){ - return []; - } - const contractPrivateKeys = this.config.contractKeys; - + return contractPrivateKeys.map((key) => key.toPublicKey()); } @@ -92,41 +85,43 @@ export class InMemoryMinaSigner return Signature.create(this.config.signer, signatureData); } - /** + /** * @param signatureData Data to be signed. * @returns Signature signed with private key of settlement contract address. */ - public signMessageWithContract(signatureData: Field[]): Signature{ - return Signature.create(this.config.contractKeys[0],signatureData); + public signMessageWithContract(signatureData: Field[]): Signature { + return Signature.create(this.config.contractKeys[0], signatureData); } public signTx( - tx: Transaction, - options: SignTxOptions = {} -): Transaction { - const { - pubKeys = [], - additionalKeys = [], - signWithContract = false - } = options; - - const privateKeys: PrivateKey[] = []; - - for (const pubKey of pubKeys) { - const privKey = this._keyMap.get(pubKey.toBase58()); - if (!privKey) { - throw new Error(`Error in signTx function: relevant private key to ${pubKey.toBase58()} not found!`); + tx: Transaction, + options: SignTxOptions = {} + ): Transaction { + const { + pubKeys = [], + additionalKeys = [], + signWithContract = false, + } = options; + + const privateKeys: PrivateKey[] = []; + + for (const pubKey of pubKeys) { + const privKey = this.keyMap.get(pubKey.toBase58()); + if (!privKey) { + throw new Error( + `Error in signTx function: relevant private key to ${pubKey.toBase58()} not found!` + ); + } + privateKeys.push(privKey); } - privateKeys.push(privKey); - } - - if (signWithContract) { - privateKeys.push(...this.config.contractKeys); + + if (signWithContract) { + privateKeys.push(...this.config.contractKeys); + } + + const keys = [this.config.signer, ...privateKeys, ...additionalKeys]; + return tx.sign(keys); } - - const keys = [this.config.signer, ...privateKeys, ...additionalKeys]; - return tx.sign(keys); -} public async start() { this.initializeKeyMap(); From 743972cf48d5fc09baeadefec1a749b3f07961c6 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 28 Nov 2025 11:53:15 +0300 Subject: [PATCH 014/155] style: run lint fix --- .../contracts/SettlementSmartContract.ts | 51 -------- packages/sdk/src/client/ClientAppChain.ts | 1 - packages/sdk/src/transaction/AuroSigner.ts | 7 +- .../src/sequencer/SequencerStartupModule.ts | 7 +- .../src/settlement/BridgingModule.ts | 25 ++-- .../src/settlement/SettlementModule.ts | 58 ++++----- .../src/settlement/utils/SettlementUtils.ts | 26 ++-- .../sequencer/test/settlement/Settlement.ts | 116 +++++++++--------- 8 files changed, 114 insertions(+), 177 deletions(-) diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index 56e7469d7..06ee0ce68 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -69,14 +69,7 @@ export interface SettlementContractType { args: DeployArgs | undefined, permissions: Permissions, sequencer: PublicKey, -<<<<<<< HEAD dispatchContract: PublicKey -======= - dispatchContract: PublicKey, - bridgeContract: PublicKey, - contractKey: PublicKey, - contractSignature: Signature ->>>>>>> de1c1c20 (refactor: update address check in settlement contract) ) => Promise; assertStateRoot: (root: Field) => AccountUpdate; @@ -253,39 +246,13 @@ export abstract class SettlementSmartContractBase extends TokenContract { protected async initializeBase( sequencer: PublicKey, -<<<<<<< HEAD dispatchContract: PublicKey -======= - dispatchContract: PublicKey, - bridgeContract: PublicKey, - contractKey: PublicKey, - contractSignature: Signature, ->>>>>>> de1c1c20 (refactor: update address check in settlement contract) ) { this.sequencerKey.set(sequencer.x); this.stateRoot.set(LinkedMerkleTree.EMPTY_ROOT); this.blockHashRoot.set(Field(BlockHashMerkleTree.EMPTY_ROOT)); this.networkStateHash.set(NetworkState.empty().hash()); this.dispatchContractAddressX.set(dispatchContract.x); -<<<<<<< HEAD -======= - - const { DispatchContract } = SettlementSmartContractBase.args; - const contractInstance = new DispatchContract(dispatchContract); - await contractInstance.initialize(this.address); - - // Deploy bridge contract for $Mina - await this.deployTokenBridge( - this.tokenId, - bridgeContract, - dispatchContract, - true - ); - - contractSignature.verify(contractKey, contractKey.toFields()); - // contractKey.toPublicKey().assertEquals(this.address); - ->>>>>>> de1c1c20 (refactor: update address check in settlement contract) } protected async settleBase( @@ -474,26 +441,8 @@ export class SettlementSmartContract await this.initializeBase(sequencer, dispatchContract); } -<<<<<<< HEAD @method async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); -======= - @method - public async initialize( - sequencer: PublicKey, - dispatchContract: PublicKey, - bridgeContract: PublicKey, - contractKey: PublicKey, - contractSignature: Signature - ) { - await this.initializeBase( - sequencer, - dispatchContract, - bridgeContract, - contractKey, - contractSignature - ); ->>>>>>> de1c1c20 (refactor: update address check in settlement contract) } @method diff --git a/packages/sdk/src/client/ClientAppChain.ts b/packages/sdk/src/client/ClientAppChain.ts index 105489c3e..bc1b1c63d 100644 --- a/packages/sdk/src/client/ClientAppChain.ts +++ b/packages/sdk/src/client/ClientAppChain.ts @@ -31,7 +31,6 @@ import { MinimalAppChainDefinition, BlockExplorerQuery, BlockExplorerTransportModule, - MinaSigner, } from "@proto-kit/sequencer"; import { container } from "tsyringe"; import { Field, PublicKey, UInt64 } from "o1js"; diff --git a/packages/sdk/src/transaction/AuroSigner.ts b/packages/sdk/src/transaction/AuroSigner.ts index 6d07175bd..07b4a405a 100644 --- a/packages/sdk/src/transaction/AuroSigner.ts +++ b/packages/sdk/src/transaction/AuroSigner.ts @@ -1,8 +1,8 @@ -import { Field, Signature, PublicKey, Transaction } from "o1js"; +import { Field, Signature } from "o1js"; import { injectable } from "tsyringe"; import { AppChainModule } from "@proto-kit/sequencer"; -import { Signer } from "./InMemorySigner"; +import { Signer } from "./InMemorySigner"; // Will be implemented for MinaSigner here. @injectable() @@ -15,6 +15,5 @@ export class AuroSigner extends AppChainModule implements Signer { }); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return Signature.fromBase58(response.signature); - } -} \ No newline at end of file +} diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 16062356b..c2cfe2b0c 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -46,7 +46,8 @@ export class SequencerStartupModule private readonly baseLayer: MinaBaseLayer | undefined, @inject("AreProofsEnabled") private readonly areProofsEnabled: AreProofsEnabled, - @inject("SettlementSigner", {isOptional: true}) private readonly signer: MinaSigner | undefined, + @inject("SettlementSigner", { isOptional: true }) + private readonly signer: MinaSigner | undefined ) { super(); } @@ -144,7 +145,9 @@ export class SequencerStartupModule // TODO Find a way to generalize this or at least make it nicer - too much logic here const isSignedSettlement = - this.baseLayer !== undefined && !(this.baseLayer instanceof NoopBaseLayer) && (this.signer !== undefined) + this.baseLayer !== undefined && + !(this.baseLayer instanceof NoopBaseLayer) && + this.signer !== undefined ? new SettlementUtils( this.areProofsEnabled, this.baseLayer, diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 28b71f736..4e988bcf8 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -116,7 +116,7 @@ export class BridgingModule { protected settlementContractModule(): SettlementContractModule { return this.protocol.dependencyContainer.resolve( "SettlementContractModule" - ); + ); } public getBridgingModuleConfig(): BridgeContractConfig { @@ -400,13 +400,10 @@ export class BridgingModule { } ); - const signatureOnes = options.contractKeys.map(x => x.toPublicKey()); - const signedTx = this.utils.signTransaction( - tx, - { - signingWithSignatureCheck:signatureOnes, - } - ); + const signatureOnes = options.contractKeys.map((x) => x.toPublicKey()); + const signedTx = this.utils.signTransaction(tx, { + signingWithSignatureCheck: signatureOnes, + }); await this.transactionSender.proveAndSendTransaction( signedTx, @@ -539,13 +536,11 @@ export class BridgingModule { log.debug("Sending rollup transaction:"); log.debug(tx.toPretty()); - - const signedTx = this.utils.signTransaction( - tx, - { - signingWithSignatureCheck:[...options.contractKeys.map(x => x.toPublicKey())] - } - ); + const signedTx = this.utils.signTransaction(tx, { + signingWithSignatureCheck: [ + ...options.contractKeys.map((x) => x.toPublicKey()), + ], + }); await this.transactionSender.proveAndSendTransaction( signedTx, diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 8599b2ce1..cdf396c1b 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -121,14 +121,14 @@ export class SettlementModule const keysArray = this.utils.getContractAddresses(); return { settlement: keysArray[0], - dispatch: keysArray[1] + dispatch: keysArray[1], }; } - public getContractAddresses(){ + public getContractAddresses() { return this.utils.getContractAddresses(); } - + public getContracts() { if (this.contracts === undefined) { const addresses = this.getAddresses(); @@ -147,7 +147,7 @@ export class SettlementModule } return this.contracts; } - + private async fetchContractAccounts() { const contracts = this.getContracts(); await this.utils.fetchContractAccounts( @@ -155,6 +155,7 @@ export class SettlementModule contracts.dispatch ); } + public async settleBatch( batch: SettleableBatch, options: { @@ -221,7 +222,7 @@ export class SettlementModule return settlement; } - // Can't do anything for now - initialize() method use settlementKey. + // Can't do anything for now - initialize() method use settlementKey. public async deploy( settlementKey: PublicKey, dispatchKey: PublicKey, @@ -278,14 +279,11 @@ export class SettlementModule dispatchKey.toPublicKey() ); } - ) + ); - this.utils.signTransaction( - tx, - { - signWithContract:true - } - ) + this.utils.signTransaction(tx, { + signWithContract: true, + }); // Note: We can't use this.signTransaction on the above tx // This should already apply the tx result to the @@ -294,7 +292,9 @@ export class SettlementModule await this.utils.fetchContractAccounts(settlement, dispatch); - const contractSignature = this.utils.signMessageWithContract(settlementKey.toFields()); + const contractSignature = this.utils.signMessageWithContract( + settlementKey.toFields() + ); const initTx = await Mina.transaction( { @@ -314,13 +314,10 @@ export class SettlementModule } ); - const initTxSigned = this.utils.signTransaction( - initTx, - { - signingWithSignatureCheck:[minaBridgeKey], - signWithContract: true - } - ); + const initTxSigned = this.utils.signTransaction(initTx, { + signingWithSignatureCheck: [minaBridgeKey], + signWithContract: true, + }); await this.transactionSender.proveAndSendTransaction( initTxSigned, @@ -351,25 +348,18 @@ export class SettlementModule }, async () => { AccountUpdate.fundNewAccount(feepayer, 1); - await settlement.addTokenBridge( - tokenId, - contractKey, - dispatch.address - ); + await settlement.addTokenBridge(tokenId, contractKey, dispatch.address); await owner.approveAccountUpdate(settlement.self); } ); - // Only ContractKeys and OwnerKey for check. + // Only ContractKeys and OwnerKey for check. // Used all in signing process. - const txSigned = this.utils.signTransaction( - tx, - { - signingWithSignatureCheck:[ownerPublicKey], - signingPublicKeys:[contractKey], - signWithContract: true - } - ); + const txSigned = this.utils.signTransaction(tx, { + signingWithSignatureCheck: [ownerPublicKey], + signingPublicKeys: [contractKey], + signWithContract: true, + }); await this.transactionSender.proveAndSendTransaction(txSigned, "included"); } diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 051a6ef81..91ed23ddd 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -46,22 +46,17 @@ export class SettlementUtils { return this.signer.getSignerAddress(); } - - public sign(signatureData: Field[]): Signature{ + public sign(signatureData: Field[]): Signature { return this.signer.sign(signatureData); } - public signMessageWithContract(signatureData: Field[]): Signature{ + public signMessageWithContract(signatureData: Field[]): Signature { return this.signer.signMessageWithContract(signatureData); } public getContractAddresses(): PublicKey[] { const keys = this.signer.getContractAddresses(); - return [ - keys[0], - keys[1], - keys[2], - ] + return [keys[0], keys[1], keys[2]]; } public signTransaction( @@ -73,10 +68,12 @@ export class SettlementUtils { signingPublicKeys = [], additionalKeys = [], preventNoncePreconditionFor = [], - signWithContract = false + signWithContract = false, } = options; - let contractKeyArray = this.isSignedSettlement() ? signingWithSignatureCheck : []; + const contractKeyArray = this.isSignedSettlement() + ? signingWithSignatureCheck + : []; if (signWithContract) { this.requireSignatureIfNecessary( @@ -92,10 +89,13 @@ export class SettlementUtils { ); } - const pubKeys = signingWithSignatureCheck - .concat(signingPublicKeys); + const pubKeys = signingWithSignatureCheck.concat(signingPublicKeys); - return this.signer.signTx(tx, {pubKeys, additionalKeys, signWithContract}); + return this.signer.signTx(tx, { + pubKeys, + additionalKeys, + signWithContract, + }); } private requireSignatureIfNecessary( diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 9009205b5..16c7e6685 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -90,10 +90,10 @@ export const settlementTestFn = ( admin: PrivateKey.random(), }; - const tokenOwnerPubKeys ={ + const tokenOwnerPubKeys = { tokenOwner: tokenOwnerKey.tokenOwner.toPublicKey(), - admin: tokenOwnerKey.admin.toPublicKey() - } + admin: tokenOwnerKey.admin.toPublicKey(), + }; const tokenOwner = tokenConfig !== undefined @@ -173,18 +173,9 @@ export const settlementTestFn = ( BaseLayer: baseLayerConfig, SettlementSigner: { signer: sequencerKey, - contractKeys: [ - settlementKey, - dispatchKey, - minaBridgeKey, - ], - tokenControllers:[ - tokenOwnerKey.tokenOwner, - tokenOwnerKey.admin - ], - tokenBridgeKeys:[ - tokenBridgeKey - ], + contractKeys: [settlementKey, dispatchKey, minaBridgeKey], + tokenControllers: [tokenOwnerKey.tokenOwner, tokenOwnerKey.admin], + tokenBridgeKeys: [tokenBridgeKey], }, BlockProducerModule: {}, FeeStrategy: {}, @@ -323,15 +314,20 @@ export const settlementTestFn = ( "should deploy", async () => { // Deploy contract - await settlementModule.deploy(settlementKey.toPublicKey(), dispatchKey.toPublicKey(), minaBridgeKey.toPublicKey(), { - nonce: nonceCounter, - }); + await settlementModule.deploy( + settlementKey.toPublicKey(), + dispatchKey.toPublicKey(), + minaBridgeKey.toPublicKey(), + { + nonce: nonceCounter, + } + ); nonceCounter += 2; console.log("Deployed"); }, - timeout * 2 + timeout * 2 ); if (tokenConfig !== undefined) { @@ -382,13 +378,13 @@ export const settlementTestFn = ( ); console.log(tx.toPretty()); - settlementModule.utils.signTransaction( - tx, - { - signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin], - signWithContract:true - } - ) + settlementModule.utils.signTransaction(tx, { + signingWithSignatureCheck: [ + tokenOwnerPubKeys.tokenOwner, + tokenOwnerPubKeys.admin, + ], + signWithContract: true, + }); await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -428,10 +424,12 @@ export const settlementTestFn = ( // tokenOwner!.self.body.incrementNonce = Bool(false); } ); - settlementModule.utils.signTransaction( - tx, - {signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin],} - ) + settlementModule.utils.signTransaction(tx, { + signingWithSignatureCheck: [ + tokenOwnerPubKeys.tokenOwner, + tokenOwnerPubKeys.admin, + ], + }); await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -443,7 +441,6 @@ export const settlementTestFn = ( it( "should deploy custom token bridge", async () => { - await settlementModule.deployTokenBridge( tokenOwner!, tokenOwnerPubKeys.tokenOwner, @@ -468,13 +465,22 @@ export const settlementTestFn = ( "should settle", async () => { try { - console.log('seq Key: ', sequencerKey.toPublicKey().toBase58()) - console.log('sett Key: ', settlementKey.toPublicKey().toBase58()) - console.log('disp Key: ', dispatchKey.toPublicKey().toBase58()) - console.log('minaBridge Key: ', minaBridgeKey.toPublicKey().toBase58()) - console.log('tokenOwner Key: ', tokenOwnerKey.tokenOwner.toPublicKey().toBase58()) - console.log('token admin Key: ', tokenOwnerKey.admin.toPublicKey().toBase58()) - console.log('token bridge key: ', tokenBridgeKey.toPublicKey().toBase58()); + console.log("seq Key: ", sequencerKey.toPublicKey().toBase58()); + console.log("sett Key: ", settlementKey.toPublicKey().toBase58()); + console.log("disp Key: ", dispatchKey.toPublicKey().toBase58()); + console.log("minaBridge Key: ", minaBridgeKey.toPublicKey().toBase58()); + console.log( + "tokenOwner Key: ", + tokenOwnerKey.tokenOwner.toPublicKey().toBase58() + ); + console.log( + "token admin Key: ", + tokenOwnerKey.admin.toPublicKey().toBase58() + ); + console.log( + "token bridge key: ", + tokenBridgeKey.toPublicKey().toBase58() + ); const [, batch] = await createBatch(true); acc0L2Nonce++; @@ -567,16 +573,13 @@ export const settlementTestFn = ( } } ); - - settlementModule.utils.signTransaction( - tx, - { - signingWithSignatureCheck:[tokenOwnerPubKeys.tokenOwner], - signWithContract:true, - additionalKeys:[userKey], - preventNoncePreconditionFor:[dispatch.address] - } - ); + + settlementModule.utils.signTransaction(tx, { + signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner], + signWithContract: true, + additionalKeys: [userKey], + preventNoncePreconditionFor: [dispatch.address], + }); console.log(tx.toPretty()); @@ -754,14 +757,14 @@ export const settlementTestFn = ( } ); - const signed = settlementModule.utils.signTransaction( - tx, - { - signingWithSignatureCheck:[tokenBridgeKey.toPublicKey(), tokenOwnerPubKeys.tokenOwner], - signWithContract:true, - additionalKeys:[userKey], - } - ) + const signed = settlementModule.utils.signTransaction(tx, { + signingWithSignatureCheck: [ + tokenBridgeKey.toPublicKey(), + tokenOwnerPubKeys.tokenOwner, + ], + signWithContract: true, + additionalKeys: [userKey], + }); await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -788,4 +791,3 @@ export const settlementTestFn = ( timeout ); }; - \ No newline at end of file From 868b15c0abfa0e943dfb2b27c3ad5e655ad25e9e Mon Sep 17 00:00:00 2001 From: saitunc Date: Mon, 1 Dec 2025 17:00:27 +0300 Subject: [PATCH 015/155] refactor: remove keeping private keys in SettlementModule --- .../src/settlement/BridgingModule.ts | 17 +++++++--------- .../src/settlement/SettlementModule.ts | 20 +------------------ 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 4e988bcf8..e25a5cf1d 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -184,8 +184,8 @@ export class BridgingModule { } private async fetchFeepayerNonce() { - const { feepayer } = this.settlementModule.config; - return await this.transactionSender.getNextNonce(feepayer.toPublicKey()); + const feepayer = this.utils.getSigner(); + return await this.transactionSender.getNextNonce(feepayer); } public async sendRollupTransactions( @@ -383,12 +383,12 @@ export class BridgingModule { if (settledRoot.toBigInt() !== (tokenBridgeRoot?.toBigInt() ?? -1n)) { // Create transaction - const { feepayer } = this.settlementModule.config; + const feepayer = this.utils.getSigner(); let { nonce } = options; const tx = await Mina.transaction( { - sender: feepayer.toPublicKey(), + sender: feepayer, // eslint-disable-next-line no-plusplus nonce: nonce++, fee: this.feeStrategy.getFee(), @@ -430,7 +430,7 @@ export class BridgingModule { tx: Transaction; }[] > { - const { feepayer } = this.settlementModule.config; + const feepayer = this.utils.getSigner(); let { nonce } = options; const txs: { @@ -504,7 +504,7 @@ export class BridgingModule { const tx = await Mina.transaction( { - sender: feepayer.toPublicKey(), + sender: feepayer, // eslint-disable-next-line no-plusplus nonce: nonce++, fee: this.feeStrategy.getFee(), @@ -526,10 +526,7 @@ export class BridgingModule { }); // Pay account creation fees for internal token accounts - AccountUpdate.fundNewAccount( - feepayer.toPublicKey(), - numNewAccountsNumber - ); + AccountUpdate.fundNewAccount(feepayer, numNewAccountsNumber); } ); diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index cdf396c1b..ee34fcc64 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -48,24 +48,13 @@ import { SettlementUtils } from "./utils/SettlementUtils"; import { BridgingModule } from "./BridgingModule"; import { MinaSigner } from "./MinaSigner"; -export type SettlementModuleConfig = { - feepayer: PrivateKey; -} & { - // TODO Add possibility to only configure public keys (for proven operation) - keys?: { - settlement: PrivateKey; - dispatch: PrivateKey; - minaBridge: PrivateKey; - }; -}; - export type SettlementModuleEvents = { "settlement-submitted": [Batch]; }; @sequencerModule() export class SettlementModule - extends SequencerModule + extends SequencerModule implements EventEmittingComponent, DependencyFactory { protected contracts?: { @@ -73,13 +62,6 @@ export class SettlementModule dispatch: DispatchSmartContract; }; - // Those should be removed, so that everything will be handled - private keys?: { - settlement: PrivateKey; - dispatch: PrivateKey; - minaBridge: PrivateKey; - }; - public utils: SettlementUtils; public events = new EventEmitter(); From 6429f3a059c835ca271a6f1b00e5479d043d2759 Mon Sep 17 00:00:00 2001 From: saitunc Date: Mon, 1 Dec 2025 17:01:29 +0300 Subject: [PATCH 016/155] refactor: remove logs and private key config to settlement module --- .../sequencer/test/settlement/Settlement.ts | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 16c7e6685..da01e8228 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -179,9 +179,7 @@ export const settlementTestFn = ( }, BlockProducerModule: {}, FeeStrategy: {}, - SettlementModule: { - feepayer: sequencerKey, - }, + SettlementModule: {}, SequencerStartupModule: {}, TaskQueue: { @@ -465,22 +463,6 @@ export const settlementTestFn = ( "should settle", async () => { try { - console.log("seq Key: ", sequencerKey.toPublicKey().toBase58()); - console.log("sett Key: ", settlementKey.toPublicKey().toBase58()); - console.log("disp Key: ", dispatchKey.toPublicKey().toBase58()); - console.log("minaBridge Key: ", minaBridgeKey.toPublicKey().toBase58()); - console.log( - "tokenOwner Key: ", - tokenOwnerKey.tokenOwner.toPublicKey().toBase58() - ); - console.log( - "token admin Key: ", - tokenOwnerKey.admin.toPublicKey().toBase58() - ); - console.log( - "token bridge key: ", - tokenBridgeKey.toPublicKey().toBase58() - ); const [, batch] = await createBatch(true); acc0L2Nonce++; From e2097f45f241ef30c363590005092c2af38cf9ac Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 3 Dec 2025 00:19:23 +0300 Subject: [PATCH 017/155] refactor: rename signer to feepayer to remove ambiguity --- packages/sequencer/src/settlement/BridgingModule.ts | 6 +++--- packages/sequencer/src/settlement/MinaSigner.ts | 12 ++++++------ .../sequencer/src/settlement/SettlementModule.ts | 8 ++++---- .../src/settlement/utils/SettlementUtils.ts | 4 ++-- packages/sequencer/test/settlement/Settlement.ts | 3 +-- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index e25a5cf1d..a625cce70 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -184,7 +184,7 @@ export class BridgingModule { } private async fetchFeepayerNonce() { - const feepayer = this.utils.getSigner(); + const feepayer = this.utils.getFeepayerKey(); return await this.transactionSender.getNextNonce(feepayer); } @@ -383,7 +383,7 @@ export class BridgingModule { if (settledRoot.toBigInt() !== (tokenBridgeRoot?.toBigInt() ?? -1n)) { // Create transaction - const feepayer = this.utils.getSigner(); + const feepayer = this.utils.getFeepayerKey(); let { nonce } = options; const tx = await Mina.transaction( @@ -430,7 +430,7 @@ export class BridgingModule { tx: Transaction; }[] > { - const feepayer = this.utils.getSigner(); + const feepayer = this.utils.getFeepayerKey(); let { nonce } = options; const txs: { diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index 1f3ae8b24..a389b877f 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -10,7 +10,7 @@ export interface SignTxOptions { signWithContract?: boolean; } export interface MinaSigner { - getSignerAddress(): PublicKey; + getFeepayerKey(): PublicKey; getContractAddresses(): PublicKey[]; @@ -24,7 +24,7 @@ export interface MinaSigner { } export interface InMemorySignerConfig { - signer: PrivateKey; + feepayer: PrivateKey; contractKeys: PrivateKey[]; tokenControllers?: PrivateKey[]; tokenBridgeKeys?: PrivateKey[]; @@ -57,8 +57,8 @@ export class InMemoryMinaSigner }); } - public getSignerAddress(): PublicKey { - return this.config.signer.toPublicKey(); + public getFeepayerKey(): PublicKey { + return this.config.feepayer.toPublicKey(); } /** @@ -82,7 +82,7 @@ export class InMemoryMinaSigner * @returns Signature signed by signer. */ public sign(signatureData: Field[]): Signature { - return Signature.create(this.config.signer, signatureData); + return Signature.create(this.config.feepayer, signatureData); } /** @@ -119,7 +119,7 @@ export class InMemoryMinaSigner privateKeys.push(...this.config.contractKeys); } - const keys = [this.config.signer, ...privateKeys, ...additionalKeys]; + const keys = [this.config.feepayer, ...privateKeys, ...additionalKeys]; return tx.sign(keys); } diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index ee34fcc64..a3e74dd2b 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -82,7 +82,7 @@ export class SettlementModule private readonly settlementStartupModule: SettlementStartupModule ) { super(); - this.utils = new SettlementUtils(areProofsEnabled, baseLayer, signer); + this.utils = new SettlementUtils(areProofsEnabled, baseLayer, this.signer); } public dependencies() { @@ -146,7 +146,7 @@ export class SettlementModule ): Promise { await this.fetchContractAccounts(); const { settlement: settlementContract, dispatch } = this.getContracts(); - const feepayer = this.utils.getSigner(); + const feepayer = this.utils.getFeepayerKey(); log.debug("Preparing settlement"); const lastSettlementL1BlockHeight = @@ -213,7 +213,7 @@ export class SettlementModule nonce?: number; } = {} ) { - const feepayer = this.utils.getSigner(); + const feepayer = this.utils.getFeepayerKey(); const nonce = options?.nonce ?? 0; @@ -315,7 +315,7 @@ export class SettlementModule nonce?: number; } ) { - const feepayer = this.utils.getSigner(); + const feepayer = this.utils.getFeepayerKey(); const nonce = options?.nonce ?? undefined; const tokenId = owner.deriveTokenId(); diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 91ed23ddd..9d998583e 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -42,8 +42,8 @@ export class SettlementUtils { ); } - public getSigner(): PublicKey { - return this.signer.getSignerAddress(); + public getFeepayerKey(): PublicKey { + return this.signer.getFeepayerKey(); } public sign(signatureData: Field[]): Signature { diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index da01e8228..9031f3c0d 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -172,7 +172,7 @@ export const settlementTestFn = ( LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), BaseLayer: baseLayerConfig, SettlementSigner: { - signer: sequencerKey, + feepayer: sequencerKey, contractKeys: [settlementKey, dispatchKey, minaBridgeKey], tokenControllers: [tokenOwnerKey.tokenOwner, tokenOwnerKey.admin], tokenBridgeKeys: [tokenBridgeKey], @@ -450,7 +450,6 @@ export const settlementTestFn = ( console.log( `Token bridge address: ${tokenBridgeKey.toPublicKey().toBase58()} @ ${tokenOwner!.deriveTokenId().toString()}` ); - expect(tokenOwner!.deriveTokenId().toString()).toStrictEqual( bridgedTokenId.toString() ); From f0a65bab54c9ccd5f439fa0209e16ccddf1aab9b Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 3 Dec 2025 13:20:30 +0300 Subject: [PATCH 018/155] refactor: update message signing function to specify signer key --- packages/sequencer/src/settlement/MinaSigner.ts | 11 ++++++++--- packages/sequencer/src/settlement/SettlementModule.ts | 3 ++- .../sequencer/src/settlement/utils/SettlementUtils.ts | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index a389b877f..e5416b0b6 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -15,7 +15,8 @@ export interface MinaSigner { getContractAddresses(): PublicKey[]; sign(signatureData: Field[]): Signature; - signMessageWithContract(signatureData: Field[]): Signature; + + signWithKey(publicKey: string, signatureData: Field[]): Signature; signTx( tx: Transaction, @@ -89,8 +90,12 @@ export class InMemoryMinaSigner * @param signatureData Data to be signed. * @returns Signature signed with private key of settlement contract address. */ - public signMessageWithContract(signatureData: Field[]): Signature { - return Signature.create(this.config.contractKeys[0], signatureData); + public signWithKey(publicKey: string, signatureData: Field[]): Signature { + const key = this.keyMap.get(publicKey) + if(!key){ + throw new Error(`Relevant key not found for ${publicKey}`); + } + return Signature.create(key, signatureData); } public signTx( diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index a3e74dd2b..cb0ad1709 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -274,7 +274,8 @@ export class SettlementModule await this.utils.fetchContractAccounts(settlement, dispatch); - const contractSignature = this.utils.signMessageWithContract( + const contractSignature = this.utils.signWithKey( + settlementKey.toBase58(), settlementKey.toFields() ); diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 9d998583e..338367f93 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -50,8 +50,8 @@ export class SettlementUtils { return this.signer.sign(signatureData); } - public signMessageWithContract(signatureData: Field[]): Signature { - return this.signer.signMessageWithContract(signatureData); + public signWithKey(settlementKey:string, signatureData: Field[]): Signature { + return this.signer.signWithKey(settlementKey,signatureData); } public getContractAddresses(): PublicKey[] { From c2ad307f13d01f9416b2d3569b81341a4148d79e Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 3 Dec 2025 16:28:17 +0300 Subject: [PATCH 019/155] refactor: move isSignedSEttlement to MinaBaseLayer --- .../src/protocol/baselayer/MinaBaseLayer.ts | 11 +++++++++++ .../src/sequencer/SequencerStartupModule.ts | 19 ++++++------------- .../src/settlement/BridgingModule.ts | 2 +- .../src/settlement/SettlementModule.ts | 10 +++++----- .../src/settlement/utils/SettlementUtils.ts | 15 ++------------- .../sequencer/test/settlement/Settlement.ts | 3 +-- 6 files changed, 26 insertions(+), 34 deletions(-) diff --git a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts index 1d8e56fbd..78aa194c9 100644 --- a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts +++ b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts @@ -109,6 +109,17 @@ export class MinaBaseLayer return this.config.network.type === "local"; } + /** + * Signed settlement happens when proofs are disabled and the network is remote + * This is because on local network we can use mock proofs, while on remotes ones we can't + */ + public isSignedSettlement(): boolean { + return ( + !this.areProofsEnabled.areProofsEnabled && + !this.isLocalBlockChain() + ); + } + public async start(): Promise { const { network } = this.config; diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index c2cfe2b0c..ed78eca72 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -21,7 +21,6 @@ import { } from "../protocol/production/tasks/CircuitCompilerTask"; import { VerificationKeyService } from "../protocol/runtime/RuntimeVerificationKeyService"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; -import { SettlementUtils } from "../settlement/utils/SettlementUtils"; import { NoopBaseLayer } from "../protocol/baselayer/NoopBaseLayer"; import { MinaSigner } from "../settlement/MinaSigner"; @@ -143,18 +142,12 @@ export class SequencerStartupModule .resolve(ChildVerificationKeyService) .setCompileRegistry(this.compileRegistry); - // TODO Find a way to generalize this or at least make it nicer - too much logic here - const isSignedSettlement = - this.baseLayer !== undefined && - !(this.baseLayer instanceof NoopBaseLayer) && - this.signer !== undefined - ? new SettlementUtils( - this.areProofsEnabled, - this.baseLayer, - this.signer - ).isSignedSettlement() - : undefined; - + // baseLayer exists and it is not a NoopBaseLayer instance -> return signSettlement. if not, return undefined. + const isSignedSettlement = + this.baseLayer && !(this.baseLayer instanceof NoopBaseLayer) + ? this.baseLayer.isSignedSettlement() + : undefined; + log.info("Compiling Protocol circuits, this can take a few minutes"); const timeout = setTimeout( diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index a625cce70..487a9eebf 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -445,7 +445,7 @@ export class BridgingModule { ); } - if (this.utils.isSignedSettlement() && options.contractKeys.length === 0) { + if (this.baseLayer.isSignedSettlement() && options.contractKeys.length === 0) { throw new Error( "Bridging contract private key for signed settlement has to be provided" ); diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index cb0ad1709..f734352ad 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -67,7 +67,7 @@ export class SettlementModule public events = new EventEmitter(); public constructor( - @inject("BaseLayer") baseLayer: MinaBaseLayer, + @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, @inject("Protocol") private readonly protocol: Protocol, @inject("SettlementStorage") @@ -82,7 +82,7 @@ export class SettlementModule private readonly settlementStartupModule: SettlementStartupModule ) { super(); - this.utils = new SettlementUtils(areProofsEnabled, baseLayer, this.signer); + this.utils = new SettlementUtils(areProofsEnabled, this.baseLayer, this.signer); } public dependencies() { @@ -228,7 +228,7 @@ export class SettlementModule const verificationsKeys = await this.settlementStartupModule.retrieveVerificationKeys(); - const permissions = this.utils.isSignedSettlement() + const permissions = this.baseLayer.isSignedSettlement() ? new SignedSettlementPermissions() : new ProvenSettlementPermissions(); @@ -352,9 +352,9 @@ export class SettlementModule SettlementSmartContractBase.args = { ...contractArgs, - signedSettlements: this.utils.isSignedSettlement(), + signedSettlements: this.baseLayer.isSignedSettlement(), // TODO Add distinction between mina and custom tokens - BridgeContractPermissions: (this.utils.isSignedSettlement() + BridgeContractPermissions: (this.baseLayer.isSignedSettlement() ? new SignedSettlementPermissions() : new ProvenSettlementPermissions() ).bridgeContractMina(), diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 338367f93..4ae1c76a4 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -31,17 +31,6 @@ export class SettlementUtils { private readonly signer: MinaSigner ) {} - /** - * Signed settlement happens when proofs are disabled and the network is remote - * This is because on local network we can use mock proofs, while on remotes ones we can't - */ - public isSignedSettlement(): boolean { - return ( - !this.areProofsEnabled.areProofsEnabled && - !this.baseLayer.isLocalBlockChain() - ); - } - public getFeepayerKey(): PublicKey { return this.signer.getFeepayerKey(); } @@ -71,7 +60,7 @@ export class SettlementUtils { signWithContract = false, } = options; - const contractKeyArray = this.isSignedSettlement() + const contractKeyArray = this.baseLayer.isSignedSettlement() ? signingWithSignatureCheck : []; @@ -103,7 +92,7 @@ export class SettlementUtils { addresses: PublicKey[], preventNoncePreconditionFor: PublicKey[] ) { - if (this.isSignedSettlement() && addresses !== undefined) { + if (this.baseLayer.isSignedSettlement() && addresses !== undefined) { const nonces: Record = {}; tx.transaction.accountUpdates.forEach((au) => { diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 9031f3c0d..9afd32284 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -60,7 +60,6 @@ import { testingSequencerModules } from "../TestingSequencer"; import { createTransaction } from "../integration/utils"; import { FeeStrategy } from "../../src/protocol/baselayer/fees/FeeStrategy"; import { BridgingModule } from "../../src/settlement/BridgingModule"; -import { SettlementUtils } from "../../src/settlement/utils/SettlementUtils"; import { FungibleTokenContractModule } from "../../src/settlement/utils/FungibleTokenContractModule"; import { FungibleTokenAdminContractModule } from "../../src/settlement/utils/FungibleTokenAdminContractModule"; import { MinaNetworkUtils } from "../../src/protocol/baselayer/network-utils/MinaNetworkUtils"; @@ -120,7 +119,7 @@ export const settlementTestFn = ( }); // eslint-disable-next-line @typescript-eslint/dot-notation - SettlementUtils.prototype["isSignedSettlement"] = () => + MinaBaseLayer.prototype["isSignedSettlement"] = () => settlementType === "signed"; const sequencer = Sequencer.from( From 59b5eb97f0e384cabd177452748f3ac29ac79e7e Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 4 Dec 2025 11:56:40 +0300 Subject: [PATCH 020/155] refactor: remove signWithContract flag in signTransaction method --- .../sequencer/src/settlement/MinaSigner.ts | 6 ----- .../src/settlement/SettlementModule.ts | 13 +++++----- .../src/settlement/utils/SettlementUtils.ts | 24 ++++++------------- .../sequencer/test/settlement/Settlement.ts | 7 +++--- 4 files changed, 16 insertions(+), 34 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index e5416b0b6..34557696b 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -7,7 +7,6 @@ import { SequencerModule } from "../sequencer/builder/SequencerModule"; export interface SignTxOptions { pubKeys?: PublicKey[]; additionalKeys?: PrivateKey[]; - signWithContract?: boolean; } export interface MinaSigner { getFeepayerKey(): PublicKey; @@ -105,7 +104,6 @@ export class InMemoryMinaSigner const { pubKeys = [], additionalKeys = [], - signWithContract = false, } = options; const privateKeys: PrivateKey[] = []; @@ -120,10 +118,6 @@ export class InMemoryMinaSigner privateKeys.push(privKey); } - if (signWithContract) { - privateKeys.push(...this.config.contractKeys); - } - const keys = [this.config.feepayer, ...privateKeys, ...additionalKeys]; return tx.sign(keys); } diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index f734352ad..db35a6f5b 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -184,7 +184,7 @@ export class SettlementModule } ); - this.utils.signTransaction(tx, { signWithContract: true }); + this.utils.signTransaction(tx, { signingWithSignatureCheck:[...this.getContractAddresses()] }); const { hash: transactionHash } = await this.transactionSender.proveAndSendTransaction(tx, "included"); @@ -264,7 +264,7 @@ export class SettlementModule ); this.utils.signTransaction(tx, { - signWithContract: true, + signingWithSignatureCheck:[...this.getContractAddresses()] }); // Note: We can't use this.signTransaction on the above tx @@ -298,8 +298,8 @@ export class SettlementModule ); const initTxSigned = this.utils.signTransaction(initTx, { - signingWithSignatureCheck: [minaBridgeKey], - signWithContract: true, + signingWithSignatureCheck:[...this.getContractAddresses(), minaBridgeKey] + }); await this.transactionSender.proveAndSendTransaction( @@ -339,9 +339,8 @@ export class SettlementModule // Only ContractKeys and OwnerKey for check. // Used all in signing process. const txSigned = this.utils.signTransaction(tx, { - signingWithSignatureCheck: [ownerPublicKey], - signingPublicKeys: [contractKey], - signWithContract: true, + signingWithSignatureCheck:[...this.getContractAddresses(),ownerPublicKey], + signingPublicKeys: [contractKey] }); await this.transactionSender.proveAndSendTransaction(txSigned, "included"); diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 4ae1c76a4..ab9b50dad 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -18,7 +18,6 @@ interface SignTransactionOptions { signingPublicKeys?: PublicKey[]; additionalKeys?: PrivateKey[]; preventNoncePreconditionFor?: PublicKey[]; - signWithContract?: boolean; } /** @@ -57,33 +56,24 @@ export class SettlementUtils { signingPublicKeys = [], additionalKeys = [], preventNoncePreconditionFor = [], - signWithContract = false, } = options; const contractKeyArray = this.baseLayer.isSignedSettlement() ? signingWithSignatureCheck : []; - if (signWithContract) { - this.requireSignatureIfNecessary( - tx, - this.getContractAddresses().concat(contractKeyArray), - preventNoncePreconditionFor - ); - } else { - this.requireSignatureIfNecessary( - tx, - contractKeyArray, - preventNoncePreconditionFor - ); - } + this.requireSignatureIfNecessary( + tx, + contractKeyArray, + preventNoncePreconditionFor + ); + const pubKeys = signingWithSignatureCheck.concat(signingPublicKeys); return this.signer.signTx(tx, { pubKeys, additionalKeys, - signWithContract, }); } @@ -120,7 +110,7 @@ export class SettlementUtils { const key = `${au.publicKey.toBase58()}-${au.tokenId.toString()}`; const nonce = Number( - au.body.preconditions.account.nonce.value.lower.toString() + au.body.preconditions.account.nonce .value.lower.toString() ); if (nonces[key] === undefined) { nonces[key] = nonce; diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 9afd32284..c4ef066c9 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -379,8 +379,8 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin, + ...settlementModule.getContractAddresses() ], - signWithContract: true, }); await appChain.sequencer @@ -555,8 +555,7 @@ export const settlementTestFn = ( ); settlementModule.utils.signTransaction(tx, { - signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner], - signWithContract: true, + signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner,...settlementModule.getContractAddresses()], additionalKeys: [userKey], preventNoncePreconditionFor: [dispatch.address], }); @@ -741,8 +740,8 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenBridgeKey.toPublicKey(), tokenOwnerPubKeys.tokenOwner, + ...settlementModule.getContractAddresses() ], - signWithContract: true, additionalKeys: [userKey], }); From 0ad1b91261bae761ca5767f3e16af4798e685098 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 4 Dec 2025 12:55:55 +0300 Subject: [PATCH 021/155] feat: add key registration to InMemoryMinaSigner --- .../sequencer/src/settlement/MinaSigner.ts | 19 +++++++++++++++++++ .../src/settlement/utils/SettlementUtils.ts | 4 ++++ .../sequencer/test/settlement/Settlement.ts | 9 +++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index 34557696b..36bf1506d 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -21,6 +21,11 @@ export interface MinaSigner { tx: Transaction, options?: SignTxOptions ): Transaction; + + registerKey( + privateKey: PrivateKey + ): PublicKey; + } export interface InMemorySignerConfig { @@ -97,6 +102,20 @@ export class InMemoryMinaSigner return Signature.create(key, signatureData); } + public registerKey(privateKey: PrivateKey): PublicKey{ + const publicKey = privateKey.toPublicKey(); + const publicKeyString = publicKey.toBase58() + + const keyExist = this.keyMap.get(publicKeyString); + + if(keyExist){ + return publicKey; + } else{ + this.keyMap.set(publicKeyString, privateKey); + return publicKey; + } + } + public signTx( tx: Transaction, options: SignTxOptions = {} diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index ab9b50dad..73d87a6b4 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -128,6 +128,10 @@ export class SettlementUtils { } } + public registerKey(privateKey: PrivateKey): PublicKey{ + return this.signer.registerKey(privateKey); + } + /** * Fetch a set of accounts (and there update internally) with respect to what network is set */ diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index c4ef066c9..4dcf018e5 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -34,6 +34,7 @@ import { SmartContract, UInt8, Bool, + PublicKey, } from "o1js"; import "reflect-metadata"; import { container } from "tsyringe"; @@ -104,6 +105,7 @@ export const settlementTestFn = ( let settlementModule: SettlementModule; let bridgingModule: BridgingModule; let blockQueue: BlockQueue; + let userPublicKey: PublicKey; let feeStrategy: FeeStrategy; @@ -554,9 +556,12 @@ export const settlementTestFn = ( } ); + // Register userKey, to use later. + userPublicKey = settlementModule.utils.registerKey(userKey); + settlementModule.utils.signTransaction(tx, { signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner,...settlementModule.getContractAddresses()], - additionalKeys: [userKey], + signingPublicKeys:[userPublicKey], preventNoncePreconditionFor: [dispatch.address], }); @@ -742,7 +747,7 @@ export const settlementTestFn = ( tokenOwnerPubKeys.tokenOwner, ...settlementModule.getContractAddresses() ], - additionalKeys: [userKey], + signingPublicKeys:[userPublicKey], }); await appChain.sequencer From b480c5d79f9c9aedad776064f1f7d57e1f957369 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 4 Dec 2025 13:17:02 +0300 Subject: [PATCH 022/155] refactor: use PublicKey instead of PrivateKey in bridging module --- .../src/settlement/BridgingModule.ts | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 487a9eebf..a9003e6bc 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -60,12 +60,12 @@ import { MinaSigner } from "./MinaSigner"; export type SettlementTokenConfig = Record< string, | { - bridgingContractPrivateKey?: PrivateKey; + bridgingContractPublicKey?: PublicKey; } | { tokenOwner: FungibleToken; - bridgingContractPrivateKey?: PrivateKey; - tokenOwnerPrivateKey?: PrivateKey; + bridgingContractPublicKey?: PublicKey; + tokenOwnerPublicKey?: PublicKey; } >; @@ -246,13 +246,13 @@ export class BridgingModule { options: | { nonce: number; - bridgingContractPrivateKey?: PrivateKey; + bridgingContractPublicKey?: PublicKey; } | { nonce: number; tokenOwner: FungibleToken; - bridgingContractPrivateKey?: PrivateKey; - tokenOwnerPrivateKey?: PrivateKey; + bridgingContractPublicKey?: PublicKey; + tokenOwnerPublicKey?: PublicKey; } ) { return await match(options) @@ -260,18 +260,18 @@ export class BridgingModule { { nonce: Pattern.number, tokenOwner: Pattern.instanceOf(FungibleToken), - bridgingContractPrivateKey: Pattern.optional( - Pattern.instanceOf(PrivateKey) + bridgingContractPublicKey: Pattern.optional( + Pattern.instanceOf(PublicKey) ), - tokenOwnerPrivateKey: Pattern.optional( - Pattern.instanceOf(PrivateKey) + tokenOwnerPublicKey: Pattern.optional( + Pattern.instanceOf(PublicKey) ), }, ({ nonce, tokenOwner, - bridgingContractPrivateKey, - tokenOwnerPrivateKey, + bridgingContractPublicKey, + tokenOwnerPublicKey, }) => { return this.sendRollupTransactionsBase( async (au: AccountUpdate) => { @@ -282,8 +282,8 @@ export class BridgingModule { { nonce, contractKeys: [ - bridgingContractPrivateKey, - tokenOwnerPrivateKey, + bridgingContractPublicKey, + tokenOwnerPublicKey, ].filter(filterNonUndefined), } ); @@ -292,11 +292,11 @@ export class BridgingModule { .with( { nonce: Pattern.number, - bridgingContractPrivateKey: Pattern.optional( - Pattern.instanceOf(PrivateKey) + bridgingContractPublicKey: Pattern.optional( + Pattern.instanceOf(PublicKey) ), }, - ({ nonce, bridgingContractPrivateKey }) => { + ({ nonce, bridgingContractPublicKey }) => { return this.sendRollupTransactionsBase( async () => {}, TokenId.default, @@ -304,8 +304,8 @@ export class BridgingModule { { nonce, contractKeys: - bridgingContractPrivateKey !== undefined - ? [bridgingContractPrivateKey] + bridgingContractPublicKey !== undefined + ? [bridgingContractPublicKey] : [], } ); @@ -356,7 +356,7 @@ export class BridgingModule { public async pullStateRoot( tokenWrapper: (au: AccountUpdate) => Promise, tokenId: Field, - options: { nonce: number; contractKeys: PrivateKey[] } + options: { nonce: number; contractKeys: PublicKey[] } ): Promise< | { nonceUsed: false } | { nonceUsed: true; tx: Mina.Transaction } @@ -400,9 +400,8 @@ export class BridgingModule { } ); - const signatureOnes = options.contractKeys.map((x) => x.toPublicKey()); const signedTx = this.utils.signTransaction(tx, { - signingWithSignatureCheck: signatureOnes, + signingWithSignatureCheck: options.contractKeys, }); await this.transactionSender.proveAndSendTransaction( @@ -424,7 +423,7 @@ export class BridgingModule { tokenWrapper: (au: AccountUpdate) => Promise, tokenId: Field, events: OutgoingMessageEvent[], - options: { nonce: number; contractKeys: PrivateKey[] } + options: { nonce: number; contractKeys: PublicKey[] } ): Promise< { tx: Transaction; @@ -445,7 +444,10 @@ export class BridgingModule { ); } - if (this.baseLayer.isSignedSettlement() && options.contractKeys.length === 0) { + if ( + this.baseLayer.isSignedSettlement() && + options.contractKeys.length === 0 + ) { throw new Error( "Bridging contract private key for signed settlement has to be provided" ); @@ -535,7 +537,7 @@ export class BridgingModule { const signedTx = this.utils.signTransaction(tx, { signingWithSignatureCheck: [ - ...options.contractKeys.map((x) => x.toPublicKey()), + ...options.contractKeys ], }); From 5982b19764aad6455ae2986fdd9ccb2a8b7db21a Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 4 Dec 2025 13:19:00 +0300 Subject: [PATCH 023/155] style: run lint fix --- .../src/protocol/baselayer/MinaBaseLayer.ts | 7 ++---- .../src/sequencer/SequencerStartupModule.ts | 11 ++++---- .../src/settlement/BridgingModule.ts | 9 ++----- .../sequencer/src/settlement/MinaSigner.ts | 22 ++++++---------- .../src/settlement/SettlementModule.ts | 25 +++++++++++++------ .../src/settlement/utils/SettlementUtils.ts | 9 +++---- .../sequencer/test/settlement/Settlement.ts | 19 ++++++++------ 7 files changed, 50 insertions(+), 52 deletions(-) diff --git a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts index 78aa194c9..e37019b32 100644 --- a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts +++ b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts @@ -114,12 +114,9 @@ export class MinaBaseLayer * This is because on local network we can use mock proofs, while on remotes ones we can't */ public isSignedSettlement(): boolean { - return ( - !this.areProofsEnabled.areProofsEnabled && - !this.isLocalBlockChain() - ); + return !this.areProofsEnabled.areProofsEnabled && !this.isLocalBlockChain(); } - + public async start(): Promise { const { network } = this.config; diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index ed78eca72..bcb25274e 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -142,12 +142,11 @@ export class SequencerStartupModule .resolve(ChildVerificationKeyService) .setCompileRegistry(this.compileRegistry); - // baseLayer exists and it is not a NoopBaseLayer instance -> return signSettlement. if not, return undefined. - const isSignedSettlement = - this.baseLayer && !(this.baseLayer instanceof NoopBaseLayer) - ? this.baseLayer.isSignedSettlement() - : undefined; - + const isSignedSettlement = + this.baseLayer && !(this.baseLayer instanceof NoopBaseLayer) + ? this.baseLayer.isSignedSettlement() + : undefined; + log.info("Compiling Protocol circuits, this can take a few minutes"); const timeout = setTimeout( diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index a9003e6bc..6a4ad8a44 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -23,7 +23,6 @@ import { AccountUpdate, Field, Mina, - PrivateKey, Provable, PublicKey, TokenContract, @@ -263,9 +262,7 @@ export class BridgingModule { bridgingContractPublicKey: Pattern.optional( Pattern.instanceOf(PublicKey) ), - tokenOwnerPublicKey: Pattern.optional( - Pattern.instanceOf(PublicKey) - ), + tokenOwnerPublicKey: Pattern.optional(Pattern.instanceOf(PublicKey)), }, ({ nonce, @@ -536,9 +533,7 @@ export class BridgingModule { log.debug(tx.toPretty()); const signedTx = this.utils.signTransaction(tx, { - signingWithSignatureCheck: [ - ...options.contractKeys - ], + signingWithSignatureCheck: [...options.contractKeys], }); await this.transactionSender.proveAndSendTransaction( diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index 36bf1506d..dd5f55b9d 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -22,10 +22,7 @@ export interface MinaSigner { options?: SignTxOptions ): Transaction; - registerKey( - privateKey: PrivateKey - ): PublicKey; - + registerKey(privateKey: PrivateKey): PublicKey; } export interface InMemorySignerConfig { @@ -95,22 +92,22 @@ export class InMemoryMinaSigner * @returns Signature signed with private key of settlement contract address. */ public signWithKey(publicKey: string, signatureData: Field[]): Signature { - const key = this.keyMap.get(publicKey) - if(!key){ + const key = this.keyMap.get(publicKey); + if (!key) { throw new Error(`Relevant key not found for ${publicKey}`); } return Signature.create(key, signatureData); } - public registerKey(privateKey: PrivateKey): PublicKey{ + public registerKey(privateKey: PrivateKey): PublicKey { const publicKey = privateKey.toPublicKey(); - const publicKeyString = publicKey.toBase58() + const publicKeyString = publicKey.toBase58(); const keyExist = this.keyMap.get(publicKeyString); - if(keyExist){ + if (keyExist) { return publicKey; - } else{ + } else { this.keyMap.set(publicKeyString, privateKey); return publicKey; } @@ -120,10 +117,7 @@ export class InMemoryMinaSigner tx: Transaction, options: SignTxOptions = {} ): Transaction { - const { - pubKeys = [], - additionalKeys = [], - } = options; + const { pubKeys = [], additionalKeys = [] } = options; const privateKeys: PrivateKey[] = []; diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index db35a6f5b..2d81c0a1a 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -82,7 +82,11 @@ export class SettlementModule private readonly settlementStartupModule: SettlementStartupModule ) { super(); - this.utils = new SettlementUtils(areProofsEnabled, this.baseLayer, this.signer); + this.utils = new SettlementUtils( + areProofsEnabled, + this.baseLayer, + this.signer + ); } public dependencies() { @@ -184,7 +188,9 @@ export class SettlementModule } ); - this.utils.signTransaction(tx, { signingWithSignatureCheck:[...this.getContractAddresses()] }); + this.utils.signTransaction(tx, { + signingWithSignatureCheck: [...this.getContractAddresses()], + }); const { hash: transactionHash } = await this.transactionSender.proveAndSendTransaction(tx, "included"); @@ -264,7 +270,7 @@ export class SettlementModule ); this.utils.signTransaction(tx, { - signingWithSignatureCheck:[...this.getContractAddresses()] + signingWithSignatureCheck: [...this.getContractAddresses()], }); // Note: We can't use this.signTransaction on the above tx @@ -298,8 +304,10 @@ export class SettlementModule ); const initTxSigned = this.utils.signTransaction(initTx, { - signingWithSignatureCheck:[...this.getContractAddresses(), minaBridgeKey] - + signingWithSignatureCheck: [ + ...this.getContractAddresses(), + minaBridgeKey, + ], }); await this.transactionSender.proveAndSendTransaction( @@ -339,8 +347,11 @@ export class SettlementModule // Only ContractKeys and OwnerKey for check. // Used all in signing process. const txSigned = this.utils.signTransaction(tx, { - signingWithSignatureCheck:[...this.getContractAddresses(),ownerPublicKey], - signingPublicKeys: [contractKey] + signingWithSignatureCheck: [ + ...this.getContractAddresses(), + ownerPublicKey, + ], + signingPublicKeys: [contractKey], }); await this.transactionSender.proveAndSendTransaction(txSigned, "included"); diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 73d87a6b4..9e12aa493 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -38,8 +38,8 @@ export class SettlementUtils { return this.signer.sign(signatureData); } - public signWithKey(settlementKey:string, signatureData: Field[]): Signature { - return this.signer.signWithKey(settlementKey,signatureData); + public signWithKey(settlementKey: string, signatureData: Field[]): Signature { + return this.signer.signWithKey(settlementKey, signatureData); } public getContractAddresses(): PublicKey[] { @@ -67,7 +67,6 @@ export class SettlementUtils { contractKeyArray, preventNoncePreconditionFor ); - const pubKeys = signingWithSignatureCheck.concat(signingPublicKeys); @@ -110,7 +109,7 @@ export class SettlementUtils { const key = `${au.publicKey.toBase58()}-${au.tokenId.toString()}`; const nonce = Number( - au.body.preconditions.account.nonce .value.lower.toString() + au.body.preconditions.account.nonce.value.lower.toString() ); if (nonces[key] === undefined) { nonces[key] = nonce; @@ -128,7 +127,7 @@ export class SettlementUtils { } } - public registerKey(privateKey: PrivateKey): PublicKey{ + public registerKey(privateKey: PrivateKey): PublicKey { return this.signer.registerKey(privateKey); } diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 4dcf018e5..830369f78 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -381,7 +381,7 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin, - ...settlementModule.getContractAddresses() + ...settlementModule.getContractAddresses(), ], }); @@ -556,12 +556,15 @@ export const settlementTestFn = ( } ); - // Register userKey, to use later. + // Register userKey, to use later. userPublicKey = settlementModule.utils.registerKey(userKey); settlementModule.utils.signTransaction(tx, { - signingWithSignatureCheck: [tokenOwnerPubKeys.tokenOwner,...settlementModule.getContractAddresses()], - signingPublicKeys:[userPublicKey], + signingWithSignatureCheck: [ + tokenOwnerPubKeys.tokenOwner, + ...settlementModule.getContractAddresses(), + ], + signingPublicKeys: [userPublicKey], preventNoncePreconditionFor: [dispatch.address], }); @@ -665,8 +668,8 @@ export const settlementTestFn = ( const settlementResult = await trigger.settle(batch, { [bridgedTokenId.toString()]: { - bridgingContractPrivateKey: tokenBridgeKey, - tokenOwnerPrivateKey: tokenOwnerKey.tokenOwner, + bridgingContractPublicKey: tokenBridgeKey.toPublicKey(), + tokenOwnerPublicKey: tokenOwnerKey.tokenOwner.toPublicKey(), tokenOwner: tokenOwner, }, }); @@ -745,9 +748,9 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenBridgeKey.toPublicKey(), tokenOwnerPubKeys.tokenOwner, - ...settlementModule.getContractAddresses() + ...settlementModule.getContractAddresses(), ], - signingPublicKeys:[userPublicKey], + signingPublicKeys: [userPublicKey], }); await appChain.sequencer From cb89177ff4026152568c5d0a715be474e2609df4 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 4 Dec 2025 13:23:19 +0300 Subject: [PATCH 024/155] refactor: remove additionalKeys since it is not needed --- packages/sequencer/src/settlement/MinaSigner.ts | 5 ++--- packages/sequencer/src/settlement/utils/SettlementUtils.ts | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index dd5f55b9d..7fc6b2d52 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -6,7 +6,6 @@ import { SequencerModule } from "../sequencer/builder/SequencerModule"; export interface SignTxOptions { pubKeys?: PublicKey[]; - additionalKeys?: PrivateKey[]; } export interface MinaSigner { getFeepayerKey(): PublicKey; @@ -117,7 +116,7 @@ export class InMemoryMinaSigner tx: Transaction, options: SignTxOptions = {} ): Transaction { - const { pubKeys = [], additionalKeys = [] } = options; + const { pubKeys = [] } = options; const privateKeys: PrivateKey[] = []; @@ -131,7 +130,7 @@ export class InMemoryMinaSigner privateKeys.push(privKey); } - const keys = [this.config.feepayer, ...privateKeys, ...additionalKeys]; + const keys = [this.config.feepayer, ...privateKeys]; return tx.sign(keys); } diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 9e12aa493..da10eaa4f 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -16,7 +16,6 @@ import { MinaSigner } from "../MinaSigner"; interface SignTransactionOptions { signingWithSignatureCheck?: PublicKey[]; signingPublicKeys?: PublicKey[]; - additionalKeys?: PrivateKey[]; preventNoncePreconditionFor?: PublicKey[]; } @@ -54,7 +53,6 @@ export class SettlementUtils { const { signingWithSignatureCheck = [], signingPublicKeys = [], - additionalKeys = [], preventNoncePreconditionFor = [], } = options; @@ -72,7 +70,6 @@ export class SettlementUtils { return this.signer.signTx(tx, { pubKeys, - additionalKeys, }); } From fddf408352de5d8d06e7557aa9b1ecab6e90535b Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 5 Dec 2025 15:29:51 +0300 Subject: [PATCH 025/155] fix: input of deployandinitialize after rebase in Settlement.ts --- packages/sequencer/src/settlement/SettlementModule.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 2d81c0a1a..2814f2342 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -263,8 +263,8 @@ export class SettlementModule verificationsKeys.SettlementSmartContract.verificationKey, }, permissions.settlementContract(), - feepayerKey.toPublicKey(), - dispatchKey.toPublicKey() + feepayer, + dispatchKey ); } ); @@ -297,8 +297,8 @@ export class SettlementModule // Deploy bridge contract for $Mina await settlement.addTokenBridge( TokenId.default, - minaBridgeKey.toPublicKey(), - dispatchKey.toPublicKey() + minaBridgeKey, + dispatchKey ); } ); From 1b34ed86a53acd7ed40f8126b43cca82e49a1cbe Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 5 Dec 2025 15:52:57 +0300 Subject: [PATCH 026/155] style: run lint fix --- .../sequencer/src/settlement/SettlementModule.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 2814f2342..20e2f5a07 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -9,15 +9,7 @@ import { SettlementSmartContractBase, DynamicBlockProof, } from "@proto-kit/protocol"; -import { - AccountUpdate, - Mina, - PrivateKey, - PublicKey, - TokenContract, - TokenId, - Transaction, -} from "o1js"; +import { AccountUpdate, Mina, PublicKey, TokenContract, TokenId } from "o1js"; import { inject } from "tsyringe"; import { EventEmitter, @@ -280,11 +272,6 @@ export class SettlementModule await this.utils.fetchContractAccounts(settlement, dispatch); - const contractSignature = this.utils.signWithKey( - settlementKey.toBase58(), - settlementKey.toFields() - ); - const initTx = await Mina.transaction( { sender: feepayer, From e24c1e24a9d1787c06b91407bc6b858db884b457 Mon Sep 17 00:00:00 2001 From: saitunc Date: Mon, 8 Dec 2025 19:06:05 +0300 Subject: [PATCH 027/155] refactor: update input type of signWithKey from string to PublicKey --- packages/sequencer/src/settlement/MinaSigner.ts | 6 +++--- packages/sequencer/src/settlement/utils/SettlementUtils.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index 7fc6b2d52..d0ed5c8be 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -14,7 +14,7 @@ export interface MinaSigner { sign(signatureData: Field[]): Signature; - signWithKey(publicKey: string, signatureData: Field[]): Signature; + signWithKey(publicKey: PublicKey, signatureData: Field[]): Signature; signTx( tx: Transaction, @@ -90,8 +90,8 @@ export class InMemoryMinaSigner * @param signatureData Data to be signed. * @returns Signature signed with private key of settlement contract address. */ - public signWithKey(publicKey: string, signatureData: Field[]): Signature { - const key = this.keyMap.get(publicKey); + public signWithKey(publicKey: PublicKey, signatureData: Field[]): Signature { + const key = this.keyMap.get(publicKey.toBase58()); if (!key) { throw new Error(`Relevant key not found for ${publicKey}`); } diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index da10eaa4f..993483408 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -37,8 +37,8 @@ export class SettlementUtils { return this.signer.sign(signatureData); } - public signWithKey(settlementKey: string, signatureData: Field[]): Signature { - return this.signer.signWithKey(settlementKey, signatureData); + public signWithKey(publicKey: PublicKey, signatureData: Field[]): Signature { + return this.signer.signWithKey(publicKey, signatureData); } public getContractAddresses(): PublicKey[] { From b440748f55a66712c21e97664a3a7ce5e166bdfd Mon Sep 17 00:00:00 2001 From: saitunc Date: Mon, 8 Dec 2025 19:38:12 +0300 Subject: [PATCH 028/155] refactor: remove unnecessary functions and use signer api directly --- .../src/sequencer/SequencerStartupModule.ts | 3 --- .../src/settlement/BridgingModule.ts | 8 ++++---- .../src/settlement/SettlementModule.ts | 20 +++++++++---------- .../src/settlement/utils/SettlementUtils.ts | 19 +----------------- .../sequencer/test/settlement/Settlement.ts | 2 +- 5 files changed, 16 insertions(+), 36 deletions(-) diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index bcb25274e..30c133d6f 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -22,7 +22,6 @@ import { import { VerificationKeyService } from "../protocol/runtime/RuntimeVerificationKeyService"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; import { NoopBaseLayer } from "../protocol/baselayer/NoopBaseLayer"; -import { MinaSigner } from "../settlement/MinaSigner"; import { SequencerModule, sequencerModule } from "./builder/SequencerModule"; import { Closeable, closeable } from "./builder/Closeable"; @@ -45,8 +44,6 @@ export class SequencerStartupModule private readonly baseLayer: MinaBaseLayer | undefined, @inject("AreProofsEnabled") private readonly areProofsEnabled: AreProofsEnabled, - @inject("SettlementSigner", { isOptional: true }) - private readonly signer: MinaSigner | undefined ) { super(); } diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 6a4ad8a44..3a31fc987 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -99,7 +99,7 @@ export class BridgingModule { private readonly feeStrategy: FeeStrategy, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, - @inject("SettlementSigner") signer: MinaSigner, + @inject("SettlementSigner") private readonly signer: MinaSigner, @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender ) { @@ -183,7 +183,7 @@ export class BridgingModule { } private async fetchFeepayerNonce() { - const feepayer = this.utils.getFeepayerKey(); + const feepayer = this.signer.getFeepayerKey(); return await this.transactionSender.getNextNonce(feepayer); } @@ -380,7 +380,7 @@ export class BridgingModule { if (settledRoot.toBigInt() !== (tokenBridgeRoot?.toBigInt() ?? -1n)) { // Create transaction - const feepayer = this.utils.getFeepayerKey(); + const feepayer = this.signer.getFeepayerKey(); let { nonce } = options; const tx = await Mina.transaction( @@ -426,7 +426,7 @@ export class BridgingModule { tx: Transaction; }[] > { - const feepayer = this.utils.getFeepayerKey(); + const feepayer = this.signer.getFeepayerKey(); let { nonce } = options; const txs: { diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 20e2f5a07..642562d16 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -96,7 +96,7 @@ export class SettlementModule } public getAddresses() { - const keysArray = this.utils.getContractAddresses(); + const keysArray = this.signer.getContractAddresses(); return { settlement: keysArray[0], dispatch: keysArray[1], @@ -104,7 +104,7 @@ export class SettlementModule } public getContractAddresses() { - return this.utils.getContractAddresses(); + return this.signer.getContractAddresses(); } public getContracts() { @@ -142,12 +142,12 @@ export class SettlementModule ): Promise { await this.fetchContractAccounts(); const { settlement: settlementContract, dispatch } = this.getContracts(); - const feepayer = this.utils.getFeepayerKey(); + const feepayer = this.signer.getFeepayerKey(); log.debug("Preparing settlement"); const lastSettlementL1BlockHeight = settlementContract.lastSettlementL1BlockHeight.get().value; - const signature = await this.utils.sign([ + const signature = this.signer.sign([ BATCH_SIGNATURE_PREFIX, lastSettlementL1BlockHeight, ]); @@ -181,7 +181,7 @@ export class SettlementModule ); this.utils.signTransaction(tx, { - signingWithSignatureCheck: [...this.getContractAddresses()], + signingWithSignatureCheck: [...this.signer.getContractAddresses()], }); const { hash: transactionHash } = @@ -211,7 +211,7 @@ export class SettlementModule nonce?: number; } = {} ) { - const feepayer = this.utils.getFeepayerKey(); + const feepayer = this.signer.getFeepayerKey(); const nonce = options?.nonce ?? 0; @@ -262,7 +262,7 @@ export class SettlementModule ); this.utils.signTransaction(tx, { - signingWithSignatureCheck: [...this.getContractAddresses()], + signingWithSignatureCheck: [...this.signer.getContractAddresses()], }); // Note: We can't use this.signTransaction on the above tx @@ -292,7 +292,7 @@ export class SettlementModule const initTxSigned = this.utils.signTransaction(initTx, { signingWithSignatureCheck: [ - ...this.getContractAddresses(), + ...this.signer.getContractAddresses(), minaBridgeKey, ], }); @@ -311,7 +311,7 @@ export class SettlementModule nonce?: number; } ) { - const feepayer = this.utils.getFeepayerKey(); + const feepayer = this.signer.getFeepayerKey(); const nonce = options?.nonce ?? undefined; const tokenId = owner.deriveTokenId(); @@ -335,7 +335,7 @@ export class SettlementModule // Used all in signing process. const txSigned = this.utils.signTransaction(tx, { signingWithSignatureCheck: [ - ...this.getContractAddresses(), + ...this.signer.getContractAddresses(), ownerPublicKey, ], signingPublicKeys: [contractKey], diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 993483408..aa8c391c2 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -28,24 +28,7 @@ export class SettlementUtils { private readonly baseLayer: MinaBaseLayer, private readonly signer: MinaSigner ) {} - - public getFeepayerKey(): PublicKey { - return this.signer.getFeepayerKey(); - } - - public sign(signatureData: Field[]): Signature { - return this.signer.sign(signatureData); - } - - public signWithKey(publicKey: PublicKey, signatureData: Field[]): Signature { - return this.signer.signWithKey(publicKey, signatureData); - } - - public getContractAddresses(): PublicKey[] { - const keys = this.signer.getContractAddresses(); - return [keys[0], keys[1], keys[2]]; - } - + public signTransaction( tx: Transaction, options: SignTransactionOptions = {} diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 830369f78..5745285a4 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -381,7 +381,7 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin, - ...settlementModule.getContractAddresses(), + ...settlementModule.getContractAddresses() ], }); From a81983b70efff63cec9d981af9bdea3df9aae17b Mon Sep 17 00:00:00 2001 From: saitunc Date: Mon, 8 Dec 2025 19:38:55 +0300 Subject: [PATCH 029/155] refactor: remove tokenController to store all keys in tokenBridgeKeys --- packages/sequencer/src/settlement/MinaSigner.ts | 5 ----- packages/sequencer/test/settlement/Settlement.ts | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index d0ed5c8be..a4949b371 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -27,7 +27,6 @@ export interface MinaSigner { export interface InMemorySignerConfig { feepayer: PrivateKey; contractKeys: PrivateKey[]; - tokenControllers?: PrivateKey[]; tokenBridgeKeys?: PrivateKey[]; } @@ -52,10 +51,6 @@ export class InMemoryMinaSigner this.config.contractKeys.forEach((key) => { this.keyMap.set(key.toPublicKey().toBase58(), key); }); - - this.config.tokenControllers?.forEach((key) => { - this.keyMap.set(key.toPublicKey().toBase58(), key); - }); } public getFeepayerKey(): PublicKey { diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 5745285a4..92880aa7d 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -175,8 +175,7 @@ export const settlementTestFn = ( SettlementSigner: { feepayer: sequencerKey, contractKeys: [settlementKey, dispatchKey, minaBridgeKey], - tokenControllers: [tokenOwnerKey.tokenOwner, tokenOwnerKey.admin], - tokenBridgeKeys: [tokenBridgeKey], + tokenBridgeKeys: [tokenBridgeKey, tokenOwnerKey.tokenOwner, tokenOwnerKey.admin], }, BlockProducerModule: {}, FeeStrategy: {}, From 0896dbf768e12dd3df469c58e6d1aa0a9d695c48 Mon Sep 17 00:00:00 2001 From: saitunc Date: Mon, 8 Dec 2025 19:50:32 +0300 Subject: [PATCH 030/155] style: run fix lint --- .../sequencer/src/sequencer/SequencerStartupModule.ts | 2 +- packages/sequencer/src/settlement/MinaSigner.ts | 2 +- .../sequencer/src/settlement/utils/SettlementUtils.ts | 3 +-- packages/sequencer/test/settlement/Settlement.ts | 8 ++++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 30c133d6f..8d2294de0 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -43,7 +43,7 @@ export class SequencerStartupModule @inject("BaseLayer", { isOptional: true }) private readonly baseLayer: MinaBaseLayer | undefined, @inject("AreProofsEnabled") - private readonly areProofsEnabled: AreProofsEnabled, + private readonly areProofsEnabled: AreProofsEnabled ) { super(); } diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index a4949b371..43fd0e4f5 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -88,7 +88,7 @@ export class InMemoryMinaSigner public signWithKey(publicKey: PublicKey, signatureData: Field[]): Signature { const key = this.keyMap.get(publicKey.toBase58()); if (!key) { - throw new Error(`Relevant key not found for ${publicKey}`); + throw new Error(`Relevant key not found for ${publicKey.toBase58()}`); } return Signature.create(key, signatureData); } diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index aa8c391c2..18678904c 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -4,7 +4,6 @@ import { Field, PrivateKey, PublicKey, - Signature, Transaction, UInt32, } from "o1js"; @@ -28,7 +27,7 @@ export class SettlementUtils { private readonly baseLayer: MinaBaseLayer, private readonly signer: MinaSigner ) {} - + public signTransaction( tx: Transaction, options: SignTransactionOptions = {} diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 92880aa7d..179d2d7f0 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -175,7 +175,11 @@ export const settlementTestFn = ( SettlementSigner: { feepayer: sequencerKey, contractKeys: [settlementKey, dispatchKey, minaBridgeKey], - tokenBridgeKeys: [tokenBridgeKey, tokenOwnerKey.tokenOwner, tokenOwnerKey.admin], + tokenBridgeKeys: [ + tokenBridgeKey, + tokenOwnerKey.tokenOwner, + tokenOwnerKey.admin, + ], }, BlockProducerModule: {}, FeeStrategy: {}, @@ -380,7 +384,7 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin, - ...settlementModule.getContractAddresses() + ...settlementModule.getContractAddresses(), ], }); From 4194c66879e0a993b1116c5140e293c2fe465366 Mon Sep 17 00:00:00 2001 From: saitunc Date: Mon, 8 Dec 2025 19:52:07 +0300 Subject: [PATCH 031/155] refactor: remove unused areProofsEnabled from SettlementUtils --- packages/sequencer/src/settlement/BridgingModule.ts | 4 +--- packages/sequencer/src/settlement/SettlementModule.ts | 8 +------- .../sequencer/src/settlement/utils/SettlementUtils.ts | 3 +-- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 3a31fc987..90bf94282 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -31,7 +31,6 @@ import { UInt32, } from "o1js"; import { - AreProofsEnabled, filterNonUndefined, LinkedMerkleTree, log, @@ -97,13 +96,12 @@ export class BridgingModule { private readonly linkedLeafStore: AsyncLinkedLeafStore, @inject("FeeStrategy") private readonly feeStrategy: FeeStrategy, - @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, @inject("SettlementSigner") private readonly signer: MinaSigner, @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender ) { - this.utils = new SettlementUtils(areProofsEnabled, baseLayer, signer); + this.utils = new SettlementUtils(baseLayer, signer); } private getMessageProcessors() { diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 642562d16..a7edcdd94 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -15,7 +15,6 @@ import { EventEmitter, EventEmittingComponent, log, - AreProofsEnabled, DependencyFactory, } from "@proto-kit/common"; // eslint-disable-next-line import/no-extraneous-dependencies @@ -67,18 +66,13 @@ export class SettlementModule private readonly blockProofSerializer: BlockProofSerializer, @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender, - @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @inject("SettlementSigner") private readonly signer: MinaSigner, @inject("FeeStrategy") private readonly feeStrategy: FeeStrategy, private readonly settlementStartupModule: SettlementStartupModule ) { super(); - this.utils = new SettlementUtils( - areProofsEnabled, - this.baseLayer, - this.signer - ); + this.utils = new SettlementUtils(this.baseLayer, this.signer); } public dependencies() { diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 18678904c..9639a2fe5 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -7,7 +7,7 @@ import { Transaction, UInt32, } from "o1js"; -import { AreProofsEnabled, mapSequential } from "@proto-kit/common"; +import { mapSequential } from "@proto-kit/common"; import type { MinaBaseLayer } from "../../protocol/baselayer/MinaBaseLayer"; import { MinaSigner } from "../MinaSigner"; @@ -23,7 +23,6 @@ interface SignTransactionOptions { */ export class SettlementUtils { public constructor( - private readonly areProofsEnabled: AreProofsEnabled, private readonly baseLayer: MinaBaseLayer, private readonly signer: MinaSigner ) {} From 9c0f342a4b84d55e61c4188485716318fabe5a22 Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 10 Dec 2025 11:59:28 +0300 Subject: [PATCH 032/155] docs: add TSDocs for MinaSigner --- .../sequencer/src/settlement/MinaSigner.ts | 227 ++++++++++++++++-- 1 file changed, 213 insertions(+), 14 deletions(-) diff --git a/packages/sequencer/src/settlement/MinaSigner.ts b/packages/sequencer/src/settlement/MinaSigner.ts index 43fd0e4f5..47ded0928 100644 --- a/packages/sequencer/src/settlement/MinaSigner.ts +++ b/packages/sequencer/src/settlement/MinaSigner.ts @@ -4,86 +4,230 @@ import { injectable } from "tsyringe"; import { SequencerModule } from "../sequencer/builder/SequencerModule"; +/** + * Options for signing transactions. + */ export interface SignTxOptions { + /** + * Optional array of public keys whose corresponding private keys + * should be used to sign the transaction in addition to the feepayer key. + */ pubKeys?: PublicKey[]; } + +/** + * Interface for signing Mina blockchain transactions and managing cryptographic keys. + * Provides methods for signing transactions, managing contract addresses, and handling + * cryptographic signatures for the Mina protocol. + */ export interface MinaSigner { + /** + * Retrieves the public key of the feepayer account. + * The feepayer is the account that pays + * transaction fees on the Mina blockchain. + * + * @returns The {@link PublicKey} of the feepayer account. + */ getFeepayerKey(): PublicKey; + /** + * Retrieves the public keys of all managed smart contract addresses. + * These typically include settlement, dispatch, and bridge contract addresses. + * + * @returns Array of {@link PublicKey}s for the managed contracts. + */ getContractAddresses(): PublicKey[]; + /** + * Signs arbitrary data using the feepayer's private key. + * + * @param signatureData - Array of {@link Field} elements representing the data to be signed. + * @returns A cryptographic {@link Signature} over the provided data. + */ sign(signatureData: Field[]): Signature; + /** + * Signs arbitrary data using a specific registered private key. + * + * @param publicKey - The {@link PublicKey} whose corresponding private key + * will be used for signing. + * @param signatureData - Array of {@link Field} elements representing the data to be signed. + * @returns A cryptographic {@link Signature} over the provided data. + * @throws An {@link Error} If the corresponding private key is not found in the key registry. + */ signWithKey(publicKey: PublicKey, signatureData: Field[]): Signature; + /** + * Signs a Mina transaction with the feepayer key and optionally additional keys. + * + * @param tx - The unsigned {@link Transaction} to be signed. + * @param options - Optional {@link SignTxOptions} specifying additional keys to use for signing. + * @returns The signed {@link Transaction} ready for submission to the network. + * @throws An {@link Error} If any required private key is not found. + */ signTx( tx: Transaction, options?: SignTxOptions ): Transaction; + /** + * Registers a new private key in the signer's key management system. + * + * @param privateKey - The {@link PrivateKey} to register. + * @returns {PublicKey} The corresponding public key of the registered private key. + */ registerKey(privateKey: PrivateKey): PublicKey; + + /** + * Retrieves the public keys of all managed token bridge addresses. + * Token bridges facilitate cross-chain token transfers. + * + * @returns {PublicKey[]} Array of public keys for token bridge contracts, + * or empty array if none configured. + */ + getTokenAddresses(): PublicKey[]; } +/** + * Configuration for the in-memory Mina signer. + * Defines all private keys that the signer will manage. + */ export interface InMemorySignerConfig { + /** + * Private key of the feepayer account that will pay for transaction fees. + */ feepayer: PrivateKey; + + /** + * Private keys for smart contracts managed by this signer. + * Expected order: [settlement, dispatch, minaBridge] + */ contractKeys: PrivateKey[]; + + /** + * Optional private keys for token bridge contracts. + * Used when the application manages cross-chain token transfers. + */ tokenBridgeKeys?: PrivateKey[]; } +/** + * In-memory implementation of the MinaSigner interface. + * Stores private keys in memory and provides cryptographic signing operations + * for the Mina blockchain protocol. This implementation is suitable for + * server-side sequencer operations where keys can be securely stored in memory. + */ @injectable() export class InMemoryMinaSigner extends SequencerModule implements MinaSigner { + /** + * Internal map storing the relationship between public keys (in base58 format) + * and their corresponding private keys for efficient lookup during signing operations. + */ private keyMap!: Map; public constructor() { super(); } + /** + * Initializes the internal key mapping from configured private keys. + * Populates the keyMap with both token bridge keys and contract keys, + * using base58-encoded public keys as lookup identifiers. + * + * @private + */ private initializeKeyMap(): void { this.keyMap = new Map(); + // Register token bridge keys if configured this.config.tokenBridgeKeys?.forEach((key) => { this.keyMap.set(key.toPublicKey().toBase58(), key); }); + // Register contract keys this.config.contractKeys.forEach((key) => { this.keyMap.set(key.toPublicKey().toBase58(), key); }); } + /** + * Retrieves the public key of the feepayer account. + * The feepayer is responsible for paying transaction fees on the Mina blockchain. + * + * @returns {PublicKey} The public key derived from the configured feepayer private key. + */ public getFeepayerKey(): PublicKey { return this.config.feepayer.toPublicKey(); } /** - * Contracts public key getter. Since we expect private keys to be managed here, - * public keys can be returned from this module. - * With index order, returned public keys are public keys of: - * [0] -> settlement contract - * [1] -> dispatch contract - * [2] -> minaBridge contract + * Retrieves all configured token bridge public keys. * - * @returns Array of public keys. + * {@link PublicKey[]} Array of public keys for all configured token bridge contracts. + * Returns empty array if no token bridge keys are configured. + */ + public getTokenAddresses(): PublicKey[] { + if (this.config.tokenBridgeKeys) { + return this.config.tokenBridgeKeys.map((x) => x.toPublicKey()); + } + return []; + } + + /** + * Retrieves public keys for all managed smart contracts. + * + * **Expected key order**: + * - Index 0: Settlement contract public key + * - Index 1: Dispatch contract public key + * - Index 2: Mina bridge contract public key + * + * The order corresponds to the order of private keys in the configuration. + * + * @returnsArray of {@link PublicKey}s derived from configured contract private keys. */ public getContractAddresses(): PublicKey[] { const contractPrivateKeys = this.config.contractKeys; - return contractPrivateKeys.map((key) => key.toPublicKey()); } /** - * @param signatureData Data to be signed. - * @returns Signature signed by signer. + * Signs arbitrary data using the feepayer's private key. + * This is useful for creating signatures or signing + * custom protocol messages. + * + * @param signatureData - Array of {@link Field} elements to be signed. + * Field is the base type for Mina's cryptographic operations. + * @returns Cryptographic {@link Signature} created using the feepayer's private key. + * + * @example + * ```typescript + * const message = [Field(123), Field(456)]; + * const signature = signer.sign(message); + * ``` */ public sign(signatureData: Field[]): Signature { return Signature.create(this.config.feepayer, signatureData); } /** - * @param signatureData Data to be signed. - * @returns Signature signed with private key of settlement contract address. + * Signs arbitrary data using a specific registered private key. + * Enables signing with contract or bridge keys rather than the feepayer key. + * + * @param publicKey - The {@link PublicKey} identifying which private key to use for signing. + * This public key must have been previously registered in the keyMap. + * @param signatureData - Array of {@link Field} elements representing the data to be signed. + * @returns Cryptographic {@link Signature} created with the specified private key. + * @throws An {@link Error} if no private key is found for the provided public key. + * + * @example + * ```typescript + * const contractPubKey = signer.getContractAddresses()[0]; + * const message = [Field(789)]; + * const signature = signer.signWithKey(contractPubKey, message); + * ``` */ public signWithKey(publicKey: PublicKey, signatureData: Field[]): Signature { const key = this.keyMap.get(publicKey.toBase58()); @@ -93,10 +237,24 @@ export class InMemoryMinaSigner return Signature.create(key, signatureData); } + /** + * Registers a new private key in the signer's key management system. + * If the key already exists, returns the existing public key without modification. + * This method is idempotent - calling it multiple times with the same key is safe. + * + * @param privateKey - The {@link PrivateKey} to register for future signing operations. + * @returns {PublicKey} The public key corresponding to the registered private key. + * + * @example + * ```typescript + * const newKey = PrivateKey.random(); + * const publicKey = signer.registerKey(newKey); + * // Now this key can be used for signing operations. + * ``` + */ public registerKey(privateKey: PrivateKey): PublicKey { const publicKey = privateKey.toPublicKey(); const publicKeyString = publicKey.toBase58(); - const keyExist = this.keyMap.get(publicKeyString); if (keyExist) { @@ -107,14 +265,46 @@ export class InMemoryMinaSigner } } + /** + * Signs a Mina transaction with multiple private keys. + * Always signs with the feepayer key, optionally with additional + * keys specified in the options. This is required for transactions that + * need authorization from multiple parties (e.g., contract deployments in + * our case) + * + * @param tx - The unsigned {@link Transaction< false, false>} object to be signed. + * Must be of type Transaction (unsigned). + * @param options - Optional {@link SignTxOptions} configuration containing: + * - pubKeys: Array of public keys whose + * private keys should sign the transaction. + * Each public key must be registered in the keyMap. + * @returns The signed {@link Transaction} ready for network submission. + * @throws An {@link Error} If any required private key is not found in the key registry. + * + * @example + * ```typescript + * const unsignedTx = await Mina.transaction(sender, () => { + * // transaction logic + * }); + * + * // Sign with feepayer only + * const signedTx1 = signer.signTx(unsignedTx); + * + * // Sign with feepayer and additional keys + * const contractKeys = signer.getContractAddresses(); + * const signedTx2 = signer.signTx(unsignedTx, { + * pubKeys: [contractKeys[0], contractKeys[1]] + * }); + * ``` + */ public signTx( tx: Transaction, options: SignTxOptions = {} ): Transaction { const { pubKeys = [] } = options; - const privateKeys: PrivateKey[] = []; + // Gather all required private keys from the keyMap for (const pubKey of pubKeys) { const privKey = this.keyMap.get(pubKey.toBase58()); if (!privKey) { @@ -125,10 +315,19 @@ export class InMemoryMinaSigner privateKeys.push(privKey); } + // Sign with feepayer first, then additional keys const keys = [this.config.feepayer, ...privateKeys]; return tx.sign(keys); } + /** + * Lifecycle method called when the signer module starts. + * Initializes the internal key mapping from the configured private keys. + * This method is part of the SequencerModule lifecycle and is automatically + * called by the framework during sequencer startup. + * + * @async + */ public async start() { this.initializeKeyMap(); noop(); From d8a723cfcfc88a89a9c4192916bb06c4009ffd3a Mon Sep 17 00:00:00 2001 From: saitunc Date: Tue, 9 Dec 2025 13:06:51 +0300 Subject: [PATCH 033/155] feat: add deployment checker function to SetlementModule --- .../src/settlement/SettlementModule.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index a7edcdd94..89ca660bb 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -9,7 +9,7 @@ import { SettlementSmartContractBase, DynamicBlockProof, } from "@proto-kit/protocol"; -import { AccountUpdate, Mina, PublicKey, TokenContract, TokenId } from "o1js"; +import { AccountUpdate, fetchAccount, Mina, PublicKey, TokenContract, TokenId } from "o1js"; import { inject } from "tsyringe"; import { EventEmitter, @@ -351,4 +351,37 @@ export class SettlementModule ).bridgeContractMina(), }; } + + public async checkDeployment(): Promise { + const network = this.baseLayer.network; + if (!network) { + throw new Error('Network is not found!?'); + } + + const accountAddresses = this.getContractAddresses().concat( + // Add token bridge addresses, if exists + this.signer.getTokenAddresses() + ); + + if (this.baseLayer.config.network.type !== 'local') { + // Use Promise.all with map instead of forEach + await Promise.all( + accountAddresses.map(async (pubKey) => { + const { account, error } = await fetchAccount({ publicKey: pubKey.toBase58() }); + if (!account || !!error) { + throw new Error(`Error finding account ${pubKey.toBase58()} on chain`); + } + }) + ); + } else { + await Promise.all( + accountAddresses.map(async (pubKey) => { + const account_exists = Mina.hasAccount(pubKey); + if (!account_exists) { + throw new Error(`Error finding account ${pubKey.toBase58()} on local chain`); + } + }) + ); + } +} } From e54193b2a526ce8dd65a523984a55e20cf385c61 Mon Sep 17 00:00:00 2001 From: saitunc Date: Tue, 9 Dec 2025 13:07:21 +0300 Subject: [PATCH 034/155] feat: add lightnet network configaurations for testing --- .../sequencer/test/settlement/Settlement.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/sequencer/test/settlement/Settlement.test.ts b/packages/sequencer/test/settlement/Settlement.test.ts index fd7ee60c5..62603559e 100644 --- a/packages/sequencer/test/settlement/Settlement.test.ts +++ b/packages/sequencer/test/settlement/Settlement.test.ts @@ -13,6 +13,15 @@ describe.each(["mock-proofs", "signed"] as const)( }, }; + const localNetwork: MinaBaseLayerConfig = { + network: { + type:'lightnet', + archive:'http://127.0.0.1:8282', + accountManager:'http://127.0.0.1:8181', + graphql: 'http://127.0.0.1:8080/graphql', + } + } + describe("Default token", () => { settlementTestFn(type, network); }); @@ -22,5 +31,9 @@ describe.each(["mock-proofs", "signed"] as const)( tokenOwner: FungibleToken, }); }); + + describe("Default token in lightnet", () => { + settlementTestFn(type,localNetwork); + }); } ); From 09298946ade0db87ae2cef717eb3b3f575c55249 Mon Sep 17 00:00:00 2001 From: saitunc Date: Tue, 9 Dec 2025 13:09:52 +0300 Subject: [PATCH 035/155] test: add test cases for deployment check --- packages/sequencer/test/settlement/Settlement.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 179d2d7f0..98fb32714 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -312,6 +312,10 @@ export const settlementTestFn = ( let user0Nonce = 0; let acc0L2Nonce = 0; + it("should throw error", async () => { + await expect(settlementModule.checkDeployment()).rejects.toThrow(); + }); + it( "should deploy", async () => { @@ -332,6 +336,11 @@ export const settlementTestFn = ( timeout * 2 ); + it("should not throw error", async () => { + // If it doesn't throw anything, it indicates that deployment checs were succesful. + await settlementModule.checkDeployment(); + }); + if (tokenConfig !== undefined) { it( "should deploy custom token owner", From 9807be6929f676f5dba1728b9e79352c07044d18 Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 10 Dec 2025 14:38:44 +0300 Subject: [PATCH 036/155] chore: remove lighnet configurations from unit tests --- .../sequencer/test/settlement/Settlement.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/sequencer/test/settlement/Settlement.test.ts b/packages/sequencer/test/settlement/Settlement.test.ts index 62603559e..fd7ee60c5 100644 --- a/packages/sequencer/test/settlement/Settlement.test.ts +++ b/packages/sequencer/test/settlement/Settlement.test.ts @@ -13,15 +13,6 @@ describe.each(["mock-proofs", "signed"] as const)( }, }; - const localNetwork: MinaBaseLayerConfig = { - network: { - type:'lightnet', - archive:'http://127.0.0.1:8282', - accountManager:'http://127.0.0.1:8181', - graphql: 'http://127.0.0.1:8080/graphql', - } - } - describe("Default token", () => { settlementTestFn(type, network); }); @@ -31,9 +22,5 @@ describe.each(["mock-proofs", "signed"] as const)( tokenOwner: FungibleToken, }); }); - - describe("Default token in lightnet", () => { - settlementTestFn(type,localNetwork); - }); } ); From 1f1fca0df0ddf808a83909112b1ab21f9584b70b Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 10 Dec 2025 17:55:22 +0300 Subject: [PATCH 037/155] feat: add tokenId input for checkDeployment for token contracts --- .../src/settlement/SettlementModule.ts | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 89ca660bb..d06ca012f 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -9,7 +9,7 @@ import { SettlementSmartContractBase, DynamicBlockProof, } from "@proto-kit/protocol"; -import { AccountUpdate, fetchAccount, Mina, PublicKey, TokenContract, TokenId } from "o1js"; +import { AccountUpdate, fetchAccount, Field, Mina, PublicKey, TokenContract, TokenId } from "o1js"; import { inject } from "tsyringe"; import { EventEmitter, @@ -352,36 +352,52 @@ export class SettlementModule }; } - public async checkDeployment(): Promise { - const network = this.baseLayer.network; - if (!network) { - throw new Error('Network is not found!?'); - } - - const accountAddresses = this.getContractAddresses().concat( - // Add token bridge addresses, if exists - this.signer.getTokenAddresses() - ); + public async checkDeployment( + tokenBridges?: Array<{ address: PublicKey; tokenId: Field }> +): Promise { + const contractAddresses = this.getContractAddresses(); if (this.baseLayer.config.network.type !== 'local') { - // Use Promise.all with map instead of forEach + // Check main contracts await Promise.all( - accountAddresses.map(async (pubKey) => { - const { account, error } = await fetchAccount({ publicKey: pubKey.toBase58() }); + contractAddresses.map(async (pubKey) => { + const { account, error } = await fetchAccount({ publicKey: pubKey }); if (!account || !!error) { - throw new Error(`Error finding account ${pubKey.toBase58()} on chain`); + throw new Error(`Error finding account ${pubKey.toBase58()}`); } }) ); + + // Check token bridges with their tokenIds + if (tokenBridges) { + await Promise.all( + tokenBridges.map(async ({ address, tokenId }) => { + const { account, error } = await fetchAccount({ + publicKey: address, + tokenId + }); + if (!account || !!error) { + throw new Error(`Error finding token bridge ${address.toBase58()} @ ${tokenId.toString()}`); + } + }) + ); + } } else { - await Promise.all( - accountAddresses.map(async (pubKey) => { - const account_exists = Mina.hasAccount(pubKey); - if (!account_exists) { - throw new Error(`Error finding account ${pubKey.toBase58()} on local chain`); + // Local network + contractAddresses.forEach((pubKey) => { + if (!Mina.hasAccount(pubKey)) { + throw new Error(`Contract ${pubKey.toBase58()} not found on local chain`); + } + }); + + // Check token bridges + if (tokenBridges) { + tokenBridges.forEach(({ address, tokenId }) => { + if (!Mina.hasAccount(address, tokenId)) { + throw new Error(`Token bridge ${address.toBase58()} @ ${tokenId.toString()} not found`); } - }) - ); + }); + } } } } From d2e8d5bb8fb4558643c5747d58f5841212b5f6ec Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 10 Dec 2025 17:55:54 +0300 Subject: [PATCH 038/155] test: add deployment check test cases --- .../sequencer/test/settlement/Settlement.ts | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 98fb32714..720012873 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -313,7 +313,15 @@ export const settlementTestFn = ( let acc0L2Nonce = 0; it("should throw error", async () => { - await expect(settlementModule.checkDeployment()).rejects.toThrow(); + + const deploymentPromise = tokenConfig === undefined + ? settlementModule.checkDeployment() + : settlementModule.checkDeployment([{ + address: tokenBridgeKey.toPublicKey(), + tokenId: tokenOwner!.deriveTokenId() + }]); + + await expect(deploymentPromise).rejects.toThrow(); }); it( @@ -336,11 +344,6 @@ export const settlementTestFn = ( timeout * 2 ); - it("should not throw error", async () => { - // If it doesn't throw anything, it indicates that deployment checs were succesful. - await settlementModule.checkDeployment(); - }); - if (tokenConfig !== undefined) { it( "should deploy custom token owner", @@ -587,6 +590,12 @@ export const settlementTestFn = ( .proveAndSendTransaction(tx, "included"); const actions = await Mina.fetchActions(dispatch.address); + if (baseLayerConfig.network.type !== "local") { + await fetchAccount({ + publicKey: tokenBridgeKey.toPublicKey(), + tokenId: bridgedTokenId + }); + } const balanceDiff = bridge.account.balance .get() .sub(contractBalanceBefore); @@ -789,4 +798,20 @@ export const settlementTestFn = ( }, timeout ); + + it("should not throw error after settlement", async () => { + // If it doesn't throw anything, it indicates that deployment checs were succesful. + if(tokenConfig === undefined) + { + await settlementModule.checkDeployment(); + } + else{ + await settlementModule.checkDeployment([ + { + address: tokenBridgeKey.toPublicKey(), + tokenId: tokenOwner!.deriveTokenId() + } + ]); + } + }); }; From 136dbce954e50de605e427bf2161e8e4f6e4e73a Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 10 Dec 2025 18:11:58 +0300 Subject: [PATCH 039/155] refactor: improve error logging --- .../src/settlement/SettlementModule.ts | 106 +++++++++++------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index d06ca012f..89ec8b15f 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -9,7 +9,15 @@ import { SettlementSmartContractBase, DynamicBlockProof, } from "@proto-kit/protocol"; -import { AccountUpdate, fetchAccount, Field, Mina, PublicKey, TokenContract, TokenId } from "o1js"; +import { + AccountUpdate, + fetchAccount, + Field, + Mina, + PublicKey, + TokenContract, + TokenId, +} from "o1js"; import { inject } from "tsyringe"; import { EventEmitter, @@ -351,53 +359,71 @@ export class SettlementModule ).bridgeContractMina(), }; } - + public async checkDeployment( - tokenBridges?: Array<{ address: PublicKey; tokenId: Field }> -): Promise { - const contractAddresses = this.getContractAddresses(); - - if (this.baseLayer.config.network.type !== 'local') { - // Check main contracts - await Promise.all( - contractAddresses.map(async (pubKey) => { - const { account, error } = await fetchAccount({ publicKey: pubKey }); - if (!account || !!error) { - throw new Error(`Error finding account ${pubKey.toBase58()}`); - } - }) - ); - - // Check token bridges with their tokenIds - if (tokenBridges) { + tokenBridges?: Array<{ address: PublicKey; tokenId: Field }> + ): Promise { + const contractAddresses = this.getContractAddresses(); + + if (this.baseLayer.config.network.type !== "local") { + // Check main contracts await Promise.all( - tokenBridges.map(async ({ address, tokenId }) => { - const { account, error } = await fetchAccount({ - publicKey: address, - tokenId - }); - if (!account || !!error) { - throw new Error(`Error finding token bridge ${address.toBase58()} @ ${tokenId.toString()}`); + contractAddresses.map(async (pubKey) => { + const { account, error } = await fetchAccount({ publicKey: pubKey }); + + if (!account) { + let message = `Account ${pubKey.toBase58()} not found on chain`; + + if (error !== undefined) { + message += `: ${error.statusText}`; + } + + throw new Error(message); } }) ); - } - } else { - // Local network - contractAddresses.forEach((pubKey) => { - if (!Mina.hasAccount(pubKey)) { - throw new Error(`Contract ${pubKey.toBase58()} not found on local chain`); + + // Check token bridges with their tokenIds + if (tokenBridges) { + await Promise.all( + tokenBridges.map(async ({ address, tokenId }) => { + const { account, error } = await fetchAccount({ + publicKey: address, + tokenId, + }); + + if (!account) { + let message = `Account ${address.toBase58()} not found on chain`; + + if (error !== undefined) { + message += `: ${error.statusText}`; + } + + throw new Error(message); + } + }) + ); } - }); - - // Check token bridges - if (tokenBridges) { - tokenBridges.forEach(({ address, tokenId }) => { - if (!Mina.hasAccount(address, tokenId)) { - throw new Error(`Token bridge ${address.toBase58()} @ ${tokenId.toString()} not found`); + } else { + // Local network + contractAddresses.forEach((pubKey) => { + if (!Mina.hasAccount(pubKey)) { + throw new Error( + `Contract ${pubKey.toBase58()} not found on local chain` + ); } }); + + // Check token bridges + if (tokenBridges) { + tokenBridges.forEach(({ address, tokenId }) => { + if (!Mina.hasAccount(address, tokenId)) { + throw new Error( + `Token bridge ${address.toBase58()} @ ${tokenId.toString()} not found` + ); + } + }); + } } } } -} From 638e16ede1b3050bc2770bbe107d35757486a712 Mon Sep 17 00:00:00 2001 From: saitunc Date: Wed, 10 Dec 2025 18:12:37 +0300 Subject: [PATCH 040/155] style: run linter and format code --- .../sequencer/test/settlement/Settlement.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 720012873..b7a310893 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -313,14 +313,16 @@ export const settlementTestFn = ( let acc0L2Nonce = 0; it("should throw error", async () => { - - const deploymentPromise = tokenConfig === undefined - ? settlementModule.checkDeployment() - : settlementModule.checkDeployment([{ - address: tokenBridgeKey.toPublicKey(), - tokenId: tokenOwner!.deriveTokenId() - }]); - + const deploymentPromise = + tokenConfig === undefined + ? settlementModule.checkDeployment() + : settlementModule.checkDeployment([ + { + address: tokenBridgeKey.toPublicKey(), + tokenId: tokenOwner!.deriveTokenId(), + }, + ]); + await expect(deploymentPromise).rejects.toThrow(); }); @@ -591,11 +593,11 @@ export const settlementTestFn = ( const actions = await Mina.fetchActions(dispatch.address); if (baseLayerConfig.network.type !== "local") { - await fetchAccount({ - publicKey: tokenBridgeKey.toPublicKey(), - tokenId: bridgedTokenId - }); - } + await fetchAccount({ + publicKey: tokenBridgeKey.toPublicKey(), + tokenId: bridgedTokenId, + }); + } const balanceDiff = bridge.account.balance .get() .sub(contractBalanceBefore); @@ -800,17 +802,15 @@ export const settlementTestFn = ( ); it("should not throw error after settlement", async () => { - // If it doesn't throw anything, it indicates that deployment checs were succesful. - if(tokenConfig === undefined) - { - await settlementModule.checkDeployment(); - } - else{ - await settlementModule.checkDeployment([ + // If it doesn't throw anything, it indicates that deployment checs were succesful. + if (tokenConfig === undefined) { + await settlementModule.checkDeployment(); + } else { + await settlementModule.checkDeployment([ { address: tokenBridgeKey.toPublicKey(), - tokenId: tokenOwner!.deriveTokenId() - } + tokenId: tokenOwner!.deriveTokenId(), + }, ]); } }); From 17076517cbf60306fdf329808f996410992230c1 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 11 Dec 2025 12:53:13 +0300 Subject: [PATCH 041/155] refactor(test): remove repetitive code and resolve promise in test --- .../sequencer/test/settlement/Settlement.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index b7a310893..79265e528 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -704,12 +704,10 @@ export const settlementTestFn = ( expect(settlementResult.bridgeTransactions).toHaveLength(2); - if (baseLayerConfig.network.type !== "local") { - await fetchAccount({ - publicKey: userKey.toPublicKey(), + settlementModule.utils.fetchContractAccounts({ + address: userKey.toPublicKey(), tokenId: bridgingContract.deriveTokenId(), }); - } const account = Mina.getAccount( userKey.toPublicKey(), bridgingContract.deriveTokenId() @@ -802,16 +800,19 @@ export const settlementTestFn = ( ); it("should not throw error after settlement", async () => { - // If it doesn't throw anything, it indicates that deployment checs were succesful. - if (tokenConfig === undefined) { - await settlementModule.checkDeployment(); - } else { - await settlementModule.checkDeployment([ - { - address: tokenBridgeKey.toPublicKey(), - tokenId: tokenOwner!.deriveTokenId(), - }, - ]); - } + expect.assertions(1); + + // Obtain promise of deployment check + const deploymentCheckPromise = tokenConfig === undefined ? + settlementModule.checkDeployment() + : + settlementModule.checkDeployment([ + { + address: tokenBridgeKey.toPublicKey(), + tokenId: tokenOwner!.deriveTokenId(), + }, + ]) + + await expect(deploymentCheckPromise).resolves.toBeUndefined(); }); }; From 4f802b56ef9cece25306e87dab891245f809369b Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 11 Dec 2025 14:16:25 +0300 Subject: [PATCH 042/155] refactor: reduce conditional checks and log all missing contracts --- .../src/settlement/SettlementModule.ts | 91 +++++++------------ 1 file changed, 32 insertions(+), 59 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 89ec8b15f..338e91063 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -24,6 +24,7 @@ import { EventEmittingComponent, log, DependencyFactory, + mapSequential, } from "@proto-kit/common"; // eslint-disable-next-line import/no-extraneous-dependencies import truncate from "lodash/truncate"; @@ -359,71 +360,43 @@ export class SettlementModule ).bridgeContractMina(), }; } - public async checkDeployment( tokenBridges?: Array<{ address: PublicKey; tokenId: Field }> - ): Promise { - const contractAddresses = this.getContractAddresses(); - - if (this.baseLayer.config.network.type !== "local") { - // Check main contracts - await Promise.all( - contractAddresses.map(async (pubKey) => { - const { account, error } = await fetchAccount({ publicKey: pubKey }); - - if (!account) { - let message = `Account ${pubKey.toBase58()} not found on chain`; - - if (error !== undefined) { - message += `: ${error.statusText}`; - } - - throw new Error(message); + ): Promise { + const contracts: Array<{ address: PublicKey; tokenId?: Field }> = [ + ...this.getContractAddresses().map(addr => ({ address: addr })), + ...(tokenBridges ?? []) + ]; + + const isLocal = this.baseLayer.isLocalBlockChain(); + const missing: Array<{ address: string; error: string }> = []; + + await Promise.all( + contracts.map(async ({ address, tokenId }) => { + if (isLocal) { + if (!Mina.hasAccount(address, tokenId)) { + missing.push({ + address: address.toBase58(), + error: 'Not found on local chain' + }); } - }) - ); - - // Check token bridges with their tokenIds - if (tokenBridges) { - await Promise.all( - tokenBridges.map(async ({ address, tokenId }) => { - const { account, error } = await fetchAccount({ - publicKey: address, - tokenId, + } else { + const { account, error } = await fetchAccount({ publicKey: address, tokenId }); + if (!account) { + missing.push({ + address: address.toBase58(), + error: error?.statusText ?? 'Not found on chain' }); - - if (!account) { - let message = `Account ${address.toBase58()} not found on chain`; - - if (error !== undefined) { - message += `: ${error.statusText}`; - } - - throw new Error(message); - } - }) - ); - } - } else { - // Local network - contractAddresses.forEach((pubKey) => { - if (!Mina.hasAccount(pubKey)) { - throw new Error( - `Contract ${pubKey.toBase58()} not found on local chain` - ); + } } - }); + }) + ); - // Check token bridges - if (tokenBridges) { - tokenBridges.forEach(({ address, tokenId }) => { - if (!Mina.hasAccount(address, tokenId)) { - throw new Error( - `Token bridge ${address.toBase58()} @ ${tokenId.toString()} not found` - ); - } - }); - } + if (missing.length) { + const errorList = missing.map(m => ` ${m.address}: ${m.error}`).join('\n'); + throw new Error(` + Missing contracts:\n${errorList} + `); } } } From a7579fb80a1f136e9fcdc9991fc6ec5261033685 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 11 Dec 2025 14:38:14 +0300 Subject: [PATCH 043/155] fix: add missing await for fetchContractAccounts in Settlement.ts --- packages/sequencer/test/settlement/Settlement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 79265e528..a11f4a595 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -704,7 +704,7 @@ export const settlementTestFn = ( expect(settlementResult.bridgeTransactions).toHaveLength(2); - settlementModule.utils.fetchContractAccounts({ + await settlementModule.utils.fetchContractAccounts({ address: userKey.toPublicKey(), tokenId: bridgingContract.deriveTokenId(), }); From eb4bb70ba9b0cfe525eb2b196ed9a5dd55d66c75 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 11 Dec 2025 14:39:42 +0300 Subject: [PATCH 044/155] style: run lint fix --- .../src/settlement/SettlementModule.ts | 19 ++++++++----- .../sequencer/test/settlement/Settlement.ts | 27 ++++++++++--------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 338e91063..68aee7b07 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -24,7 +24,6 @@ import { EventEmittingComponent, log, DependencyFactory, - mapSequential, } from "@proto-kit/common"; // eslint-disable-next-line import/no-extraneous-dependencies import truncate from "lodash/truncate"; @@ -360,12 +359,13 @@ export class SettlementModule ).bridgeContractMina(), }; } + public async checkDeployment( tokenBridges?: Array<{ address: PublicKey; tokenId: Field }> ): Promise { const contracts: Array<{ address: PublicKey; tokenId?: Field }> = [ - ...this.getContractAddresses().map(addr => ({ address: addr })), - ...(tokenBridges ?? []) + ...this.getContractAddresses().map((addr) => ({ address: addr })), + ...(tokenBridges ?? []), ]; const isLocal = this.baseLayer.isLocalBlockChain(); @@ -377,15 +377,18 @@ export class SettlementModule if (!Mina.hasAccount(address, tokenId)) { missing.push({ address: address.toBase58(), - error: 'Not found on local chain' + error: "Not found on local chain", }); } } else { - const { account, error } = await fetchAccount({ publicKey: address, tokenId }); + const { account, error } = await fetchAccount({ + publicKey: address, + tokenId, + }); if (!account) { missing.push({ address: address.toBase58(), - error: error?.statusText ?? 'Not found on chain' + error: error?.statusText ?? "Not found on chain", }); } } @@ -393,7 +396,9 @@ export class SettlementModule ); if (missing.length) { - const errorList = missing.map(m => ` ${m.address}: ${m.error}`).join('\n'); + const errorList = missing + .map((m) => ` ${m.address}: ${m.error}`) + .join("\n"); throw new Error(` Missing contracts:\n${errorList} `); diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index a11f4a595..cfc97c045 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -705,9 +705,10 @@ export const settlementTestFn = ( expect(settlementResult.bridgeTransactions).toHaveLength(2); await settlementModule.utils.fetchContractAccounts({ - address: userKey.toPublicKey(), - tokenId: bridgingContract.deriveTokenId(), - }); + address: userKey.toPublicKey(), + tokenId: bridgingContract.deriveTokenId(), + }); + const account = Mina.getAccount( userKey.toPublicKey(), bridgingContract.deriveTokenId() @@ -801,17 +802,17 @@ export const settlementTestFn = ( it("should not throw error after settlement", async () => { expect.assertions(1); - + // Obtain promise of deployment check - const deploymentCheckPromise = tokenConfig === undefined ? - settlementModule.checkDeployment() - : - settlementModule.checkDeployment([ - { - address: tokenBridgeKey.toPublicKey(), - tokenId: tokenOwner!.deriveTokenId(), - }, - ]) + const deploymentCheckPromise = + tokenConfig === undefined + ? settlementModule.checkDeployment() + : settlementModule.checkDeployment([ + { + address: tokenBridgeKey.toPublicKey(), + tokenId: tokenOwner!.deriveTokenId(), + }, + ]); await expect(deploymentCheckPromise).resolves.toBeUndefined(); }); From 26145ab502ec4cd50a1c9c0adeaf2c7b95cf7b20 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 11 Dec 2025 16:25:53 +0300 Subject: [PATCH 045/155] refactor: remove truthy conditional checks --- packages/sequencer/src/settlement/SettlementModule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 68aee7b07..597da53b3 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -385,7 +385,7 @@ export class SettlementModule publicKey: address, tokenId, }); - if (!account) { + if (account === null || account === undefined) { missing.push({ address: address.toBase58(), error: error?.statusText ?? "Not found on chain", @@ -395,7 +395,7 @@ export class SettlementModule }) ); - if (missing.length) { + if (missing.length > 0) { const errorList = missing .map((m) => ` ${m.address}: ${m.error}`) .join("\n"); From c6b65cbc39fa36bee18ab2be82b4dba8adfe6612 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 12 Dec 2025 21:46:28 +0100 Subject: [PATCH 046/155] First draft of reworking the protocol structure --- .../protocol/src/hooks/NoopSettlementHook.ts | 4 +- packages/protocol/src/index.ts | 7 +- .../settlement/SettlementContractModule.ts | 104 ++++---- .../settlement/contracts/BridgeContract.ts | 4 +- ...ts => BridgingSettlementContractModule.ts} | 41 ++- .../contracts/DispatchSmartContract.ts | 8 +- .../SettlementSmartContractModule.ts | 80 ++++++ .../BridgingSettlementContract.ts} | 249 ++++-------------- .../contracts/settlement/SettlementBase.ts | 217 +++++++++++++++ .../settlement/SettlementContract.ts | 55 ++++ .../modularity/ProvableSettlementHook.ts | 4 +- .../src/settlement/modularity/types.ts | 3 +- .../modules/NetworkStateSettlementModule.ts | 4 +- 13 files changed, 493 insertions(+), 287 deletions(-) rename packages/protocol/src/settlement/contracts/{SettlementContractProtocolModule.ts => BridgingSettlementContractModule.ts} (75%) create mode 100644 packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts rename packages/protocol/src/settlement/contracts/{SettlementSmartContract.ts => settlement/BridgingSettlementContract.ts} (52%) create mode 100644 packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts create mode 100644 packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts diff --git a/packages/protocol/src/hooks/NoopSettlementHook.ts b/packages/protocol/src/hooks/NoopSettlementHook.ts index 166ff678f..99ecbde77 100644 --- a/packages/protocol/src/hooks/NoopSettlementHook.ts +++ b/packages/protocol/src/hooks/NoopSettlementHook.ts @@ -5,14 +5,14 @@ import { ProvableSettlementHook, SettlementHookInputs, } from "../settlement/modularity/ProvableSettlementHook"; -import { SettlementSmartContractBase } from "../settlement/contracts/SettlementSmartContract"; +import { SettlementContractType } from "../settlement/contracts/settlement/SettlementBase"; @injectable() export class NoopSettlementHook extends ProvableSettlementHook< Record > { public async beforeSettlement( - contract: SettlementSmartContractBase, + contract: SettlementContractType, state: SettlementHookInputs ) { noop(); diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index eeb2ba0d5..171157b6c 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -45,8 +45,11 @@ export * from "./state/assert/assert"; export * from "./settlement/contracts/authorizations/ContractAuthorization"; export * from "./settlement/contracts/authorizations/UpdateMessagesHashAuth"; export * from "./settlement/contracts/authorizations/TokenBridgeDeploymentAuth"; -export * from "./settlement/contracts/SettlementSmartContract"; -export * from "./settlement/contracts/SettlementContractProtocolModule"; +export * from "./settlement/contracts/settlement/SettlementBase"; +export * from "./settlement/contracts/settlement/SettlementContract"; +export * from "./settlement/contracts/settlement/BridgingSettlementContract"; +export * from "./settlement/contracts/BridgingSettlementContractModule"; +export * from "./settlement/contracts/SettlementSmartContractModule"; export * from "./settlement/contracts/DispatchSmartContract"; export * from "./settlement/contracts/DispatchContractProtocolModule"; export * from "./settlement/contracts/BridgeContract"; diff --git a/packages/protocol/src/settlement/SettlementContractModule.ts b/packages/protocol/src/settlement/SettlementContractModule.ts index 631842d22..88cb24327 100644 --- a/packages/protocol/src/settlement/SettlementContractModule.ts +++ b/packages/protocol/src/settlement/SettlementContractModule.ts @@ -16,17 +16,16 @@ import { ProtocolModule } from "../protocol/ProtocolModule"; import { ContractModule } from "./ContractModule"; import { DispatchContractProtocolModule } from "./contracts/DispatchContractProtocolModule"; import { DispatchContractType } from "./contracts/DispatchSmartContract"; -import { - SettlementContractConfig, - SettlementContractProtocolModule, -} from "./contracts/SettlementContractProtocolModule"; -import { SettlementContractType } from "./contracts/SettlementSmartContract"; +import { BridgingSettlementContractModule } from "./contracts/BridgingSettlementContractModule"; import { BridgeContractType } from "./contracts/BridgeContract"; import { BridgeContractConfig, BridgeContractProtocolModule, } from "./contracts/BridgeContractProtocolModule"; -import { GetContracts } from "./modularity/types"; +import { GetContracts, InferContractType } from "./modularity/types"; +import { BridgingSettlementContractType } from "./contracts/settlement/BridgingSettlementContract"; +import { SettlementContractType } from "./contracts/settlement/SettlementBase"; +import { SettlementContractConfig } from "./contracts/SettlementSmartContractModule"; export type SettlementModulesRecord = ModulesRecord< TypedClass> @@ -36,6 +35,12 @@ export type MandatorySettlementModulesRecord = { SettlementContract: TypedClass< ContractModule >; +}; + +export type BridgingSettlementModulesRecord = { + SettlementContract: TypedClass< + ContractModule + >; DispatchContract: TypedClass>; BridgeContract: TypedClass< ContractModule @@ -44,8 +49,7 @@ export type MandatorySettlementModulesRecord = { @injectable() export class SettlementContractModule< - SettlementModules extends SettlementModulesRecord & - MandatorySettlementModulesRecord, + SettlementModules extends SettlementModulesRecord, > extends ModuleContainer implements ProtocolModule @@ -54,10 +58,7 @@ export class SettlementContractModule< super(definition); } - public static from< - SettlementModules extends SettlementModulesRecord & - MandatorySettlementModulesRecord, - >( + public static from( modules: SettlementModules ): TypedClass> { return class ScopedSettlementContractModule extends SettlementContractModule { @@ -67,27 +68,18 @@ export class SettlementContractModule< }; } - public static mandatoryModules() { + public static settlementOnly() { return { - SettlementContract: SettlementContractProtocolModule, - DispatchContract: DispatchContractProtocolModule, - BridgeContract: BridgeContractProtocolModule, + SettlementContract: SettlementContractModule, } as const; } - public static fromDefaults() { - return SettlementContractModule.from( - SettlementContractModule.mandatoryModules() - ); - } - - public static with( - additionalModules: AdditionalModules - ) { - return SettlementContractModule.from({ - ...SettlementContractModule.mandatoryModules(), - ...additionalModules, - } as const); + public static settlementAndBridging() { + return { + SettlementContract: BridgingSettlementContractModule, + DispatchContract: DispatchContractProtocolModule, + BridgeContract: BridgeContractProtocolModule, + } as const; } // ** For protocol module @@ -116,30 +108,40 @@ export class SettlementContractModule< return Object.fromEntries(contracts); } - public createContracts(addresses: { - settlement: PublicKey; - dispatch: PublicKey; - }): { - settlement: SettlementContractType & SmartContract; - dispatch: DispatchContractType & SmartContract; - } { - const { DispatchContract, SettlementContract } = this.getContractClasses(); - - const dispatchInstance = new DispatchContract(addresses.dispatch); - const settlementInstance = new SettlementContract(addresses.settlement); - - return { - dispatch: dispatchInstance, - settlement: settlementInstance, - }; - } - - public createBridgeContract( + public createContract>( + contractName: ContractName, address: PublicKey, tokenId?: Field - ): BridgeContractType & SmartContract { - const { BridgeContract } = this.getContractClasses(); + ): InferContractType { + const module = this.resolve(contractName); + const ContractClass = module.contractFactory(); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return new ContractClass(address, tokenId) as InferContractType< + SettlementModules[ContractName] + >; + } - return new BridgeContract(address, tokenId); + public createContracts< + ContractName extends keyof SettlementModules, + >(addresses: { + [Key in ContractName]: PublicKey; + }): { + [Key in ContractName]: SmartContract & + InferContractType; + } { + const classes = this.getContractClasses(); + + const obj: Record = {}; + // eslint-disable-next-line guard-for-in + for (const key in addresses) { + const ContractClass = classes[key]; + obj[key] = new ContractClass(addresses[key]); + } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return obj as { + [Key in keyof SettlementModules]: SmartContract & + InferContractType; + }; } } diff --git a/packages/protocol/src/settlement/contracts/BridgeContract.ts b/packages/protocol/src/settlement/contracts/BridgeContract.ts index 34917d865..802bdccbf 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContract.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContract.ts @@ -29,7 +29,7 @@ import { Path } from "../../model/Path"; import { OutgoingMessageProcessor } from "../modularity/OutgoingMessageProcessor"; import { PROTOKIT_FIELD_PREFIXES } from "../../hashing/protokit-prefixes"; -import type { SettlementContractType } from "./SettlementSmartContract"; +import type { BridgingSettlementContractType } from "./settlement/BridgingSettlementContract"; export type BridgeContractType = { stateRoot: State; @@ -67,7 +67,7 @@ export class BridgeContractContext { export abstract class BridgeContractBase extends TokenContract { public static args: { SettlementContract: - | (TypedClass & typeof SmartContract) + | (TypedClass & typeof SmartContract) | undefined; messageProcessors: OutgoingMessageProcessor[]; batchSize?: number; diff --git a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts similarity index 75% rename from packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts rename to packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts index 6cdf51952..3fc1759aa 100644 --- a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts @@ -15,24 +15,21 @@ import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; import { DispatchSmartContractBase } from "./DispatchSmartContract"; import { - SettlementContractType, - SettlementSmartContract, - SettlementSmartContractBase, -} from "./SettlementSmartContract"; + BridgingSettlementContractType, + BridgingSettlementContractBase, + BridgingSettlementContract, +} from "./settlement/BridgingSettlementContract"; import { BridgeContractBase } from "./BridgeContract"; import { DispatchContractProtocolModule } from "./DispatchContractProtocolModule"; import { BridgeContractProtocolModule } from "./BridgeContractProtocolModule"; - -export type SettlementContractConfig = { - escapeHatchSlotsInterval?: number; -}; - -// 24 hours -const DEFAULT_ESCAPE_HATCH = (60 / 3) * 24; +import { + DEFAULT_ESCAPE_HATCH, + SettlementContractConfig, +} from "./SettlementSmartContractModule"; @injectable() -export class SettlementContractProtocolModule extends ContractModule< - SettlementContractType, +export class BridgingSettlementContractModule extends ContractModule< + BridgingSettlementContractType, SettlementContractConfig > { public constructor( @@ -49,7 +46,7 @@ export class SettlementContractProtocolModule extends ContractModule< super(); } - public contractFactory(): SmartContractClassFromInterface { + public contractFactory(): SmartContractClassFromInterface { const { hooks, config } = this; const dispatchContract = this.dispatchContractModule.contractFactory(); const bridgeContract = this.bridgeContractModule.contractFactory(); @@ -57,8 +54,8 @@ export class SettlementContractProtocolModule extends ContractModule< const escapeHatchSlotsInterval = config.escapeHatchSlotsInterval ?? DEFAULT_ESCAPE_HATCH; - const { args } = SettlementSmartContractBase; - SettlementSmartContractBase.args = { + const { args } = BridgingSettlementContractBase; + BridgingSettlementContractBase.args = { ...args, DispatchContract: dispatchContract, hooks, @@ -72,12 +69,12 @@ export class SettlementContractProtocolModule extends ContractModule< // Ideally we don't want to have this cyclic dependency, but we have it in the protocol, // So its logical that we can't avoid that here - BridgeContractBase.args.SettlementContract = SettlementSmartContract; + BridgeContractBase.args.SettlementContract = BridgingSettlementContract; DispatchSmartContractBase.args.settlementContractClass = - SettlementSmartContract; + BridgingSettlementContract; - return SettlementSmartContract; + return BridgingSettlementContract; } public async compile( @@ -91,10 +88,10 @@ export class SettlementContractProtocolModule extends ContractModule< this.contractFactory(); // Init params - SettlementSmartContractBase.args.BridgeContractVerificationKey = + BridgingSettlementContractBase.args.BridgeContractVerificationKey = bridgeArtifact.BridgeContract.verificationKey; - if (SettlementSmartContractBase.args.signedSettlements === undefined) { + if (BridgingSettlementContractBase.args.signedSettlements === undefined) { throw new Error( "Args not fully initialized - make sure to also include the SettlementModule in the sequencer" ); @@ -103,7 +100,7 @@ export class SettlementContractProtocolModule extends ContractModule< log.debug("Compiling Settlement Contract"); const artifact = await registry.forceProverExists( - async (reg) => await registry.compile(SettlementSmartContract) + async (reg) => await registry.compile(BridgingSettlementContract) ); return { diff --git a/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts b/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts index 3e4de1ab1..f79b77d37 100644 --- a/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts @@ -26,7 +26,7 @@ import { } from "../../utils/MinaPrefixedProvableHashList"; import { Deposit } from "../messages/Deposit"; -import type { SettlementContractType } from "./SettlementSmartContract"; +import type { BridgingSettlementContractType } from "./settlement/BridgingSettlementContract"; import { TokenBridgeDeploymentAuth } from "./authorizations/TokenBridgeDeploymentAuth"; import { UpdateMessagesHashAuth } from "./authorizations/UpdateMessagesHashAuth"; import { @@ -40,6 +40,10 @@ import { export const ACTIONS_EMPTY_HASH = Reducer.initialActionState; export interface DispatchContractType { + events: { + "token-bridge-added": typeof TokenBridgeTreeAddition; + }; + updateMessagesHash: ( executedMessagesHash: Field, newPromisedMessagesHash: Field @@ -67,7 +71,7 @@ export abstract class DispatchSmartContractBase extends SmartContract { public static args: { methodIdMappings: RuntimeMethodIdMapping; incomingMessagesPaths: Record; - settlementContractClass?: TypedClass & + settlementContractClass?: TypedClass & typeof SmartContract; }; diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts new file mode 100644 index 000000000..0106ff32f --- /dev/null +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts @@ -0,0 +1,80 @@ +import { inject, injectable, injectAll } from "tsyringe"; +import { + ArtifactRecord, + ChildVerificationKeyService, + CompileRegistry, + log, +} from "@proto-kit/common"; + +import { BlockProvable } from "../../prover/block/BlockProvable"; +import { + ContractModule, + SmartContractClassFromInterface, +} from "../ContractModule"; +import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; + +import { + BridgingSettlementContractType, + BridgingSettlementContract, +} from "./settlement/BridgingSettlementContract"; +import { SettlementBase } from "./settlement/SettlementBase"; + +export type SettlementContractConfig = { + escapeHatchSlotsInterval?: number; +}; + +// 24 hours +export const DEFAULT_ESCAPE_HATCH = (60 / 3) * 24; + +@injectable() +export class SettlementSmartContractModule extends ContractModule< + BridgingSettlementContractType, + SettlementContractConfig +> { + public constructor( + @injectAll("ProvableSettlementHook") + private readonly hooks: ProvableSettlementHook[], + @inject("BlockProver") + private readonly blockProver: BlockProvable, + private readonly childVerificationKeyService: ChildVerificationKeyService + ) { + super(); + } + + public contractFactory(): SmartContractClassFromInterface { + const { hooks, config } = this; + + const escapeHatchSlotsInterval = + config.escapeHatchSlotsInterval ?? DEFAULT_ESCAPE_HATCH; + + const { args } = SettlementBase; + SettlementBase.args = { + ...args, + hooks, + escapeHatchSlotsInterval, + signedSettlements: args?.signedSettlements, + ChildVerificationKeyService: this.childVerificationKeyService, + }; + + return BridgingSettlementContract; + } + + public async compile( + registry: CompileRegistry + ): Promise { + // Dependencies + await this.blockProver.compile(registry); + + this.contractFactory(); + + log.debug("Compiling Settlement Contract"); + + const artifact = await registry.forceProverExists( + async (reg) => await registry.compile(BridgingSettlementContract) + ); + + return { + SettlementSmartContract: artifact, + }; + } +} diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts similarity index 52% rename from packages/protocol/src/settlement/contracts/SettlementSmartContract.ts rename to packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts index 06ee0ce68..a823af09d 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts @@ -1,10 +1,4 @@ -import { - prefixToField, - TypedClass, - mapSequential, - ChildVerificationKeyService, - LinkedMerkleTree, -} from "@proto-kit/common"; +import { TypedClass, ChildVerificationKeyService } from "@proto-kit/common"; import { AccountUpdate, Bool, @@ -17,52 +11,31 @@ import { state, UInt32, AccountUpdateForest, - TokenContract, VerificationKey, Permissions, Struct, Provable, TokenId, - DynamicProof, DeployArgs, } from "o1js"; -import { NetworkState } from "../../model/network/NetworkState"; -import { BlockHashMerkleTree } from "../../prover/block/accummulators/BlockHashMerkleTree"; -import { - BlockProverPublicInput, - BlockProverPublicOutput, -} from "../../prover/block/BlockProvable"; -import { - ProvableSettlementHook, - SettlementHookInputs, - SettlementStateRecord, -} from "../modularity/ProvableSettlementHook"; +import { NetworkState } from "../../../model/network/NetworkState"; +import { ProvableSettlementHook } from "../../modularity/ProvableSettlementHook"; +import { DispatchContractType } from "../DispatchSmartContract"; +import { BridgeContractType } from "../BridgeContract"; +import { TokenBridgeDeploymentAuth } from "../authorizations/TokenBridgeDeploymentAuth"; +import { UpdateMessagesHashAuth } from "../authorizations/UpdateMessagesHashAuth"; -import { DispatchContractType } from "./DispatchSmartContract"; -import { BridgeContractType } from "./BridgeContract"; -import { TokenBridgeDeploymentAuth } from "./authorizations/TokenBridgeDeploymentAuth"; -import { UpdateMessagesHashAuth } from "./authorizations/UpdateMessagesHashAuth"; +import { DynamicBlockProof, SettlementBase } from "./SettlementBase"; /* eslint-disable @typescript-eslint/lines-between-class-members */ -export class DynamicBlockProof extends DynamicProof< - BlockProverPublicInput, - BlockProverPublicOutput -> { - public static publicInputType = BlockProverPublicInput; - - public static publicOutputType = BlockProverPublicOutput; - - public static maxProofsVerified = 2 as const; -} - export class TokenMapping extends Struct({ tokenId: Field, publicKey: PublicKey, }) {} -export interface SettlementContractType { +export interface BridgingSettlementContractType { authorizationField: State; deployAndInitialize: ( @@ -76,7 +49,6 @@ export interface SettlementContractType { settle: ( blockProof: DynamicBlockProof, signature: Signature, - dispatchContractAddress: PublicKey, publicKey: PublicKey, inputNetworkState: NetworkState, outputNetworkState: NetworkState, @@ -89,9 +61,6 @@ export interface SettlementContractType { ) => Promise; } -// Some random prefix for the sequencer signature -export const BATCH_SIGNATURE_PREFIX = prefixToField("pk-batchSignature"); - // @singleton() // export class SettlementSmartContractStaticArgs { // public args?: { @@ -106,7 +75,7 @@ export const BATCH_SIGNATURE_PREFIX = prefixToField("pk-batchSignature"); // }; // } -export abstract class SettlementSmartContractBase extends TokenContract { +export abstract class BridgingSettlementContractBase extends SettlementBase { // This pattern of injecting args into a smartcontract is currently the only // viable solution that works given the inheritance issues of o1js // public static args = container.resolve(SettlementSmartContractStaticArgs); @@ -126,12 +95,7 @@ export abstract class SettlementSmartContractBase extends TokenContract { "token-bridge-deployed": TokenMapping, }; - abstract sequencerKey: State; - abstract lastSettlementL1BlockHeight: State; - abstract stateRoot: State; - abstract networkStateHash: State; - abstract blockHashRoot: State; - abstract dispatchContractAddressX: State; + abstract dispatchContractAddress: State; abstract authorizationField: State; @@ -143,10 +107,19 @@ export abstract class SettlementSmartContractBase extends TokenContract { return this.self; } + protected async initializeBaseBridging( + sequencer: PublicKey, + dispatchContract: PublicKey + ) { + await super.initializeBase(sequencer); + + this.dispatchContractAddress.set(dispatchContract); + } + // TODO Like these properties, I am too lazy to properly infer the types here private assertLazyConfigsInitialized() { const uninitializedProperties: string[] = []; - const { args } = SettlementSmartContractBase; + const { args } = BridgingSettlementContractBase; if (args.BridgeContractPermissions === undefined) { uninitializedProperties.push("BridgeContractPermissions"); } @@ -162,17 +135,12 @@ export abstract class SettlementSmartContractBase extends TokenContract { } } - protected async deployTokenBridge( - tokenId: Field, - address: PublicKey, - dispatchContractAddress: PublicKey, - dispatchContractPreconditionEnforced = false - ) { + protected async deployTokenBridge(tokenId: Field, address: PublicKey) { Provable.asProver(() => { this.assertLazyConfigsInitialized(); }); - const { args } = SettlementSmartContractBase; + const { args } = BridgingSettlementContractBase; const BridgeContractClass = args.BridgeContract; const bridgeContract = new BridgeContractClass(address, tokenId); @@ -223,11 +191,8 @@ export abstract class SettlementSmartContractBase extends TokenContract { }) ); - // We can't set a precondition twice, for the $mina bridge deployment that - // would be the case, so we disable it in this case - if (!dispatchContractPreconditionEnforced) { - this.dispatchContractAddressX.requireEquals(dispatchContractAddress.x); - } + const dispatchContractAddress = + this.dispatchContractAddress.getAndRequireEquals(); // Set authorization for the auth callback, that we need this.authorizationField.set( @@ -238,61 +203,35 @@ export abstract class SettlementSmartContractBase extends TokenContract { }).hash() ); const dispatchContract = - new SettlementSmartContractBase.args.DispatchContract( + new BridgingSettlementContractBase.args.DispatchContract( dispatchContractAddress ); await dispatchContract.enableTokenDeposits(tokenId, address, this.address); } - protected async initializeBase( - sequencer: PublicKey, - dispatchContract: PublicKey - ) { - this.sequencerKey.set(sequencer.x); - this.stateRoot.set(LinkedMerkleTree.EMPTY_ROOT); - this.blockHashRoot.set(Field(BlockHashMerkleTree.EMPTY_ROOT)); - this.networkStateHash.set(NetworkState.empty().hash()); - this.dispatchContractAddressX.set(dispatchContract.x); - } - - protected async settleBase( + protected async settleBaseBridging( blockProof: DynamicBlockProof, signature: Signature, - dispatchContractAddress: PublicKey, publicKey: PublicKey, inputNetworkState: NetworkState, outputNetworkState: NetworkState, newPromisedMessagesHash: Field ) { - // Brought in as a constant - const blockProofVk = - SettlementSmartContractBase.args.ChildVerificationKeyService.getVerificationKey( - "BlockProver" - ); - if (!blockProofVk.hash.isConstant()) { - throw new Error("Sanity check - vk hash has to be constant"); - } - - // Verify the blockproof - blockProof.verify(blockProofVk); - - // Get and assert on-chain values - const stateRoot = this.stateRoot.getAndRequireEquals(); - const networkStateHash = this.networkStateHash.getAndRequireEquals(); - const blockHashRoot = this.blockHashRoot.getAndRequireEquals(); - const sequencerKey = this.sequencerKey.getAndRequireEquals(); - const lastSettlementL1BlockHeight = - this.lastSettlementL1BlockHeight.getAndRequireEquals(); - const onChainDispatchContractAddressX = - this.dispatchContractAddressX.getAndRequireEquals(); - - onChainDispatchContractAddressX.assertEquals( - dispatchContractAddress.x, - "DispatchContract address not provided correctly" + await super.settleBase( + blockProof, + signature, + publicKey, + inputNetworkState, + outputNetworkState, + newPromisedMessagesHash ); - const { DispatchContract, escapeHatchSlotsInterval, hooks } = - SettlementSmartContractBase.args; + const dispatchContractAddress = + this.dispatchContractAddress.getAndRequireEquals(); + + const { DispatchContract } = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (this.constructor as typeof BridgingSettlementContractBase).args; // Get dispatch contract values // These values are witnesses but will be checked later on the AU @@ -300,90 +239,6 @@ export abstract class SettlementSmartContractBase extends TokenContract { const dispatchContract = new DispatchContract(dispatchContractAddress); const promisedMessagesHash = dispatchContract.promisedMessagesHash.get(); - // Get block height and use the lower bound for all ops - const minBlockHeightIncluded = this.network.blockchainLength.get(); - this.network.blockchainLength.requireBetween( - minBlockHeightIncluded, - // 5 because that is the length the newPromisedMessagesHash will be valid - minBlockHeightIncluded.add(4) - ); - - // Check signature/escape catch - publicKey.x.assertEquals( - sequencerKey, - "Sequencer public key witness not matching" - ); - const signatureValid = signature.verify(publicKey, [ - BATCH_SIGNATURE_PREFIX, - lastSettlementL1BlockHeight.value, - ]); - const escapeHatchActivated = lastSettlementL1BlockHeight - .add(UInt32.from(escapeHatchSlotsInterval)) - .lessThan(minBlockHeightIncluded); - signatureValid - .or(escapeHatchActivated) - .assertTrue( - "Sequencer signature not valid and escape hatch not activated" - ); - - // Assert correctness of networkState witness - inputNetworkState - .hash() - .assertEquals(networkStateHash, "InputNetworkState witness not valid"); - outputNetworkState - .hash() - .assertEquals( - blockProof.publicOutput.networkStateHash, - "OutputNetworkState witness not valid" - ); - - blockProof.publicOutput.closed.assertEquals( - Bool(true), - "Supplied proof is not a closed BlockProof" - ); - blockProof.publicOutput.pendingSTBatchesHash.assertEquals( - Field(0), - "Supplied proof is has outstanding STs to be proven" - ); - - // Execute onSettlementHooks for additional checks - const stateRecord: SettlementStateRecord = { - blockHashRoot, - stateRoot, - networkStateHash, - lastSettlementL1BlockHeight, - sequencerKey: publicKey, - }; - const inputs: SettlementHookInputs = { - blockProof, - contractState: stateRecord, - newPromisedMessagesHash, - fromNetworkState: inputNetworkState, - toNetworkState: outputNetworkState, - currentL1BlockHeight: minBlockHeightIncluded, - }; - await mapSequential(hooks, async (hook) => { - await hook.beforeSettlement(this, inputs); - }); - - // Apply blockProof - stateRoot.assertEquals( - blockProof.publicInput.stateRoot, - "Input state root not matching" - ); - - networkStateHash.assertEquals( - blockProof.publicInput.networkStateHash, - "Input networkStateHash not matching" - ); - blockHashRoot.assertEquals( - blockProof.publicInput.blockHashRoot, - "Input blockHashRoot not matching" - ); - this.stateRoot.set(blockProof.publicOutput.stateRoot); - this.networkStateHash.set(blockProof.publicOutput.networkStateHash); - this.blockHashRoot.set(blockProof.publicOutput.blockHashRoot); - // Assert and apply deposit commitments promisedMessagesHash.assertEquals( blockProof.publicOutput.incomingMessagesHash, @@ -408,14 +263,12 @@ export abstract class SettlementSmartContractBase extends TokenContract { promisedMessagesHash, newPromisedMessagesHash ); - - this.lastSettlementL1BlockHeight.set(minBlockHeightIncluded); } } -export class SettlementSmartContract - extends SettlementSmartContractBase - implements SettlementContractType +export class BridgingSettlementContract + extends BridgingSettlementContractBase + implements BridgingSettlementContractType { @state(Field) public sequencerKey = State(); @state(UInt32) public lastSettlementL1BlockHeight = State(); @@ -424,7 +277,7 @@ export class SettlementSmartContract @state(Field) public networkStateHash = State(); @state(Field) public blockHashRoot = State(); - @state(Field) public dispatchContractAddressX = State(); + @state(PublicKey) public dispatchContractAddress = State(); @state(Field) public authorizationField = State(); @@ -438,7 +291,7 @@ export class SettlementSmartContract this.self.account.permissions.set(permissions); - await this.initializeBase(sequencer, dispatchContract); + await this.initializeBaseBridging(sequencer, dispatchContract); } @method async approveBase(forest: AccountUpdateForest) { @@ -446,28 +299,22 @@ export class SettlementSmartContract } @method - public async addTokenBridge( - tokenId: Field, - address: PublicKey, - dispatchContract: PublicKey - ) { - await this.deployTokenBridge(tokenId, address, dispatchContract); + public async addTokenBridge(tokenId: Field, address: PublicKey) { + await this.deployTokenBridge(tokenId, address); } @method public async settle( blockProof: DynamicBlockProof, signature: Signature, - dispatchContractAddress: PublicKey, publicKey: PublicKey, inputNetworkState: NetworkState, outputNetworkState: NetworkState, newPromisedMessagesHash: Field ) { - return await this.settleBase( + return await this.settleBaseBridging( blockProof, signature, - dispatchContractAddress, publicKey, inputNetworkState, outputNetworkState, diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts new file mode 100644 index 000000000..a35425275 --- /dev/null +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts @@ -0,0 +1,217 @@ +import { + Bool, + DynamicProof, + Field, + PublicKey, + Signature, + State, + TokenContract, + UInt32, +} from "o1js"; +import { + ChildVerificationKeyService, + LinkedMerkleTree, + mapSequential, + prefixToField, +} from "@proto-kit/common"; + +import { BlockHashMerkleTree } from "../../../prover/block/accummulators/BlockHashMerkleTree"; +import { NetworkState } from "../../../model/network/NetworkState"; +import { + ProvableSettlementHook, + SettlementHookInputs, + SettlementStateRecord, +} from "../../modularity/ProvableSettlementHook"; +import { + BlockProverPublicInput, + BlockProverPublicOutput, +} from "../../../prover/block/BlockProvable"; + +/* eslint-disable @typescript-eslint/lines-between-class-members */ + +// Some random prefix for the sequencer signature +export const BATCH_SIGNATURE_PREFIX = prefixToField("pk-batchSignature"); + +export class DynamicBlockProof extends DynamicProof< + BlockProverPublicInput, + BlockProverPublicOutput +> { + public static publicInputType = BlockProverPublicInput; + + public static publicOutputType = BlockProverPublicOutput; + + public static maxProofsVerified = 2 as const; +} + +export interface SettlementContractType { + sequencerKey: State; + lastSettlementL1BlockHeight: State; + stateRoot: State; + networkStateHash: State; + blockHashRoot: State; + + settle: ( + blockProof: DynamicBlockProof, + signature: Signature, + publicKey: PublicKey, + inputNetworkState: NetworkState, + outputNetworkState: NetworkState, + newPromisedMessagesHash: Field + ) => Promise; +} + +export abstract class SettlementBase extends TokenContract { + public static args: { + hooks: ProvableSettlementHook[]; + escapeHatchSlotsInterval: number; + signedSettlements: boolean | undefined; + ChildVerificationKeyService: ChildVerificationKeyService; + }; + + abstract sequencerKey: State; + abstract lastSettlementL1BlockHeight: State; + abstract stateRoot: State; + abstract networkStateHash: State; + abstract blockHashRoot: State; + + protected async initializeBase(sequencer: PublicKey) { + this.sequencerKey.set(sequencer.x); + this.stateRoot.set(LinkedMerkleTree.EMPTY_ROOT); + this.blockHashRoot.set(Field(BlockHashMerkleTree.EMPTY_ROOT)); + this.networkStateHash.set(NetworkState.empty().hash()); + } + + abstract settle( + blockProof: DynamicBlockProof, + signature: Signature, + publicKey: PublicKey, + inputNetworkState: NetworkState, + outputNetworkState: NetworkState, + newPromisedMessagesHash: Field + ): Promise; + + protected async settleBase( + blockProof: DynamicBlockProof, + signature: Signature, + publicKey: PublicKey, + inputNetworkState: NetworkState, + outputNetworkState: NetworkState, + newPromisedMessagesHash: Field + ) { + // Brought in as a constant + const blockProofVk = + SettlementBase.args.ChildVerificationKeyService.getVerificationKey( + "BlockProver" + ); + if (!blockProofVk.hash.isConstant()) { + throw new Error("Sanity check - vk hash has to be constant"); + } + + // Verify the blockproof + blockProof.verify(blockProofVk); + + // Get and assert on-chain values + const stateRoot = this.stateRoot.getAndRequireEquals(); + const networkStateHash = this.networkStateHash.getAndRequireEquals(); + const blockHashRoot = this.blockHashRoot.getAndRequireEquals(); + const sequencerKey = this.sequencerKey.getAndRequireEquals(); + const lastSettlementL1BlockHeight = + this.lastSettlementL1BlockHeight.getAndRequireEquals(); + + const { escapeHatchSlotsInterval, hooks } = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (this.constructor as typeof SettlementBase).args; + + // Get block height and use the lower bound for all ops + const minBlockHeightIncluded = this.network.blockchainLength.get(); + this.network.blockchainLength.requireBetween( + minBlockHeightIncluded, + // 5 because that is the length the newPromisedMessagesHash will be valid + minBlockHeightIncluded.add(4) + ); + + // Check signature/escape catch + publicKey.x.assertEquals( + sequencerKey, + "Sequencer public key witness not matching" + ); + const signatureValid = signature.verify(publicKey, [ + BATCH_SIGNATURE_PREFIX, + lastSettlementL1BlockHeight.value, + ]); + const escapeHatchActivated = lastSettlementL1BlockHeight + .add(UInt32.from(escapeHatchSlotsInterval)) + .lessThan(minBlockHeightIncluded); + signatureValid + .or(escapeHatchActivated) + .assertTrue( + "Sequencer signature not valid and escape hatch not activated" + ); + + // Assert correctness of networkState witness + inputNetworkState + .hash() + .assertEquals(networkStateHash, "InputNetworkState witness not valid"); + outputNetworkState + .hash() + .assertEquals( + blockProof.publicOutput.networkStateHash, + "OutputNetworkState witness not valid" + ); + + blockProof.publicOutput.closed.assertEquals( + Bool(true), + "Supplied proof is not a closed BlockProof" + ); + blockProof.publicOutput.pendingSTBatchesHash.assertEquals( + Field(0), + "Supplied proof is has outstanding STs to be proven" + ); + + // Execute onSettlementHooks for additional checks + const stateRecord: SettlementStateRecord = { + blockHashRoot, + stateRoot, + networkStateHash, + lastSettlementL1BlockHeight, + sequencerKey: publicKey, + }; + const inputs: SettlementHookInputs = { + blockProof, + contractState: stateRecord, + newPromisedMessagesHash, + fromNetworkState: inputNetworkState, + toNetworkState: outputNetworkState, + currentL1BlockHeight: minBlockHeightIncluded, + }; + await mapSequential(hooks, async (hook) => { + await hook.beforeSettlement(this, inputs); + }); + + // Apply blockProof + stateRoot.assertEquals( + blockProof.publicInput.stateRoot, + "Input state root not matching" + ); + + networkStateHash.assertEquals( + blockProof.publicInput.networkStateHash, + "Input networkStateHash not matching" + ); + blockHashRoot.assertEquals( + blockProof.publicInput.blockHashRoot, + "Input blockHashRoot not matching" + ); + this.stateRoot.set(blockProof.publicOutput.stateRoot); + this.networkStateHash.set(blockProof.publicOutput.networkStateHash); + this.blockHashRoot.set(blockProof.publicOutput.blockHashRoot); + + this.lastSettlementL1BlockHeight.set(minBlockHeightIncluded); + } + + // TODO Move all settlement-only logic here from the old impl +} + +// TODO Connect the above with the Smartcontract API implementing the abstract class + +/* eslint-enable @typescript-eslint/lines-between-class-members */ diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts new file mode 100644 index 000000000..0a0c802f8 --- /dev/null +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts @@ -0,0 +1,55 @@ +import { + State, + UInt32, + AccountUpdateForest, + state, + method, + PublicKey, + Field, + Signature, +} from "o1js"; + +import { NetworkState } from "../../../model/network/NetworkState"; + +import { + DynamicBlockProof, + SettlementBase, + SettlementContractType, +} from "./SettlementBase"; + +export class SettlementContract + extends SettlementBase + implements SettlementContractType +{ + @state(Field) sequencerKey = State(); + + @state(UInt32) lastSettlementL1BlockHeight = State(); + + @state(Field) stateRoot = State(); + + @state(Field) networkStateHash = State(); + + @state(Field) blockHashRoot = State(); + + @method async approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); + } + + @method async settle( + blockProof: DynamicBlockProof, + signature: Signature, + publicKey: PublicKey, + inputNetworkState: NetworkState, + outputNetworkState: NetworkState, + newPromisedMessagesHash: Field + ): Promise { + await super.settleBase( + blockProof, + signature, + publicKey, + inputNetworkState, + outputNetworkState, + newPromisedMessagesHash + ); + } +} diff --git a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts index ecb117129..65ccf7a12 100644 --- a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts +++ b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts @@ -4,7 +4,7 @@ import { InferProofBase } from "@proto-kit/common"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { NetworkState } from "../../model/network/NetworkState"; import type { BlockProof } from "../../prover/block/BlockProver"; -import type { SettlementSmartContractBase } from "../contracts/SettlementSmartContract"; +import type { SettlementContractType } from "../contracts/settlement/SettlementBase"; export type InputBlockProof = InferProofBase; @@ -30,7 +30,7 @@ export abstract class ProvableSettlementHook< Config, > extends ProtocolModule { public abstract beforeSettlement( - smartContract: SettlementSmartContractBase, + smartContract: SettlementContractType, inputs: SettlementHookInputs ): Promise; } diff --git a/packages/protocol/src/settlement/modularity/types.ts b/packages/protocol/src/settlement/modularity/types.ts index 8f7c706e6..03da15930 100644 --- a/packages/protocol/src/settlement/modularity/types.ts +++ b/packages/protocol/src/settlement/modularity/types.ts @@ -5,13 +5,14 @@ import { SmartContractClassFromInterface, } from "../ContractModule"; import type { SettlementModulesRecord } from "../SettlementContractModule"; +import { SmartContract } from "o1js"; export type InferContractType< Module extends TypedClass>, > = Module extends TypedClass ? ConcreteModule extends ContractModule - ? Contract + ? Contract & SmartContract : never : never; diff --git a/packages/protocol/src/settlement/modules/NetworkStateSettlementModule.ts b/packages/protocol/src/settlement/modules/NetworkStateSettlementModule.ts index 739ccc27a..169492175 100644 --- a/packages/protocol/src/settlement/modules/NetworkStateSettlementModule.ts +++ b/packages/protocol/src/settlement/modules/NetworkStateSettlementModule.ts @@ -4,7 +4,7 @@ import { ProvableSettlementHook, SettlementHookInputs, } from "../modularity/ProvableSettlementHook"; -import { SettlementSmartContract } from "../contracts/SettlementSmartContract"; +import { SettlementContractType } from "../contracts/settlement/SettlementBase"; type NetworkStateSettlementModuleConfig = { blocksPerL1Block: UInt64; @@ -13,7 +13,7 @@ type NetworkStateSettlementModuleConfig = { /* eslint-disable @typescript-eslint/no-unused-vars */ export class NetworkStateSettlementModule extends ProvableSettlementHook { public async beforeSettlement( - smartContract: SettlementSmartContract, + smartContract: SettlementContractType, { blockProof, fromNetworkState, From f00c371cda05998d533a5ef56cbdc3a7c7baafc2 Mon Sep 17 00:00:00 2001 From: saitunc Date: Tue, 16 Dec 2025 03:46:24 +0300 Subject: [PATCH 047/155] feat: add Query Service with private resolver functions --- packages/sdk/src/query/QueryService.ts | 96 ++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 packages/sdk/src/query/QueryService.ts diff --git a/packages/sdk/src/query/QueryService.ts b/packages/sdk/src/query/QueryService.ts new file mode 100644 index 000000000..0028a5b72 --- /dev/null +++ b/packages/sdk/src/query/QueryService.ts @@ -0,0 +1,96 @@ +import { DependencyContainer } from "tsyringe"; +import { Runtime, RuntimeModule, RuntimeModulesRecord } from "@proto-kit/module"; +import { + MandatoryProtocolModulesRecord, + Protocol, + ProtocolModule, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; +import { + BlockExplorerQuery, + BlockExplorerTransportModule, + NetworkStateQuery, + NetworkStateTransportModule, + Query, + QueryTransportModule, +} from "@proto-kit/sequencer"; + +export class QueryService< + RuntimeModules extends RuntimeModulesRecord, + ProtocolModules extends ProtocolModulesRecord & + MandatoryProtocolModulesRecord = ProtocolModulesRecord & + MandatoryProtocolModulesRecord, +> { + + // Here, fields are optional for lazy initialization. + private QueryTransport?: QueryTransportModule; + private NetworkStateTransport?: NetworkStateTransportModule; + private BlockExplorerTransport?: BlockExplorerTransportModule; + + + private RuntimeQuery?: Query, RuntimeModules>; + private ProtocolQuery?: Query, ProtocolModules>; + private NetworkQuery?: NetworkStateQuery; + private ExplorerQuery?: BlockExplorerQuery; + + public constructor( + private readonly runtimeInstance: Runtime, + private readonly protocolInstance: Protocol, + private readonly container: DependencyContainer + ) {} + + + /** + * A helper function that resolves QueryTrasnportModule. + * If not resolved before, it is resolved. + * @returns The registered transport module as {@link QueryTrasnportModule} + */ + private get queryTransport(): QueryTransportModule { + if (this.QueryTransport === undefined) { + if (!this.container.isRegistered("QueryTransportModule")) { + throw new Error( + "QueryTransportModule is not registered"); + } + this.QueryTransport = + this.container.resolve("QueryTransportModule"); + } + return this.QueryTransport; + } + + /** + * A helper function that resolves NetworkStateTransport. + * If not resolved before, it is resolved. + * @returns The registered transport module as {@link BlockExplorerTransport} + */ + private get networkStateTransport(): NetworkStateTransportModule { + if (this.NetworkStateTransport === undefined) { + if (!this.container.isRegistered("NetworkStateTransportModule")) { + throw new Error("NetworkStateTransportModule is not registered."); + } + this.NetworkStateTransport = + this.container.resolve( + "NetworkStateTransportModule" + ); + } + return this.NetworkStateTransport; + } + + /** + * A helper function that resolves BlockExplorerTransportModule. + * If not resolved before, it is resolved. + * @returns The registered transport module as {@link BlockExplorerTransport} + */ + private get blockExplorerTransport(): BlockExplorerTransportModule { + if (this.BlockExplorerTransport === undefined) { + if (!this.container.isRegistered("BlockExplorerTransportModule")) { + throw new Error( + "BlockExplorerTransportModule is not registered."); + } + this.BlockExplorerTransport = + this.container.resolve( + "BlockExplorerTransportModule" + ); + } + return this.BlockExplorerTransport; + } +} From 23b6d4b34f33851026403334b299ad5d8f912921 Mon Sep 17 00:00:00 2001 From: saitunc Date: Tue, 16 Dec 2025 03:51:30 +0300 Subject: [PATCH 048/155] feat: add lazy initializations for query services --- packages/sdk/src/query/QueryService.ts | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/sdk/src/query/QueryService.ts b/packages/sdk/src/query/QueryService.ts index 0028a5b72..0a0b94966 100644 --- a/packages/sdk/src/query/QueryService.ts +++ b/packages/sdk/src/query/QueryService.ts @@ -12,6 +12,7 @@ import { NetworkStateQuery, NetworkStateTransportModule, Query, + QueryBuilderFactory, QueryTransportModule, } from "@proto-kit/sequencer"; @@ -93,4 +94,62 @@ export class QueryService< } return this.BlockExplorerTransport; } + + /** + * Getter of query module for runtime modules. + * If not initialized before, it is initialized. + * @returns A {@link Query} module for runtime module. + */ + public get runtime(): Query, RuntimeModules> { + if (this.RuntimeQuery === undefined) { + this.RuntimeQuery = QueryBuilderFactory.fromRuntime( + this.runtimeInstance, + this.queryTransport + ); + } + return this.RuntimeQuery; + } + + /** + * Getter of query module for protocol. + * If not initialized before, it is initialized. + * @returns A {@link Query} module for protocol module. + */ + public get protocol(): Query, ProtocolModules> { + if (this.ProtocolQuery === undefined) { + this.ProtocolQuery = QueryBuilderFactory.fromProtocol( + this.protocolInstance, + this.queryTransport + ); + } + return this.ProtocolQuery; + } + + /** + * Getter of network state query module. + * If not initialized before, it is initialized. + * @returns A {@link NetworkStateQuery} module. + */ + public get network(): NetworkStateQuery { + if (this.NetworkQuery === undefined) { + this.NetworkQuery = new NetworkStateQuery( + this.networkStateTransport + ); + } + return this.NetworkQuery; + } + + /** + * Getter of block explorer query module. + * If not initialized before, it is initialized. + * @returns A {@link BlockExplorerQuery} module. + */ + public get explorer(): BlockExplorerQuery { + if (this.ExplorerQuery === undefined) { + this.ExplorerQuery = new BlockExplorerQuery( + this.blockExplorerTransport + ); + } + return this.ExplorerQuery; + } } From 880358f8dd6e83fc05275f0d34626f229e10d1b5 Mon Sep 17 00:00:00 2001 From: saitunc Date: Tue, 16 Dec 2025 04:14:13 +0300 Subject: [PATCH 049/155] update query logic by using QueryService --- packages/sdk/src/client/ClientAppChain.ts | 65 ++++++----------------- 1 file changed, 17 insertions(+), 48 deletions(-) diff --git a/packages/sdk/src/client/ClientAppChain.ts b/packages/sdk/src/client/ClientAppChain.ts index bc1b1c63d..8cd679e01 100644 --- a/packages/sdk/src/client/ClientAppChain.ts +++ b/packages/sdk/src/client/ClientAppChain.ts @@ -19,18 +19,12 @@ import { } from "@proto-kit/protocol"; import { DummyStateService, - NetworkStateQuery, - NetworkStateTransportModule, Query, - QueryBuilderFactory, - QueryTransportModule, Sequencer, UnsignedTransaction, AppChain, AppChainModule, MinimalAppChainDefinition, - BlockExplorerQuery, - BlockExplorerTransportModule, } from "@proto-kit/sequencer"; import { container } from "tsyringe"; import { Field, PublicKey, UInt64 } from "o1js"; @@ -42,6 +36,7 @@ import { GraphqlTransactionSender } from "../graphql/GraphqlTransactionSender"; import { Signer } from "../transaction/InMemorySigner"; import { AppChainTransaction } from "../transaction/AppChainTransaction"; import { TransactionSender } from "../transaction/InMemoryTransactionSender"; +import { QueryService } from "../query/QueryService"; export type InferModules>> = Container extends TypedClass @@ -53,6 +48,12 @@ export type InferModules>> = export class ClientAppChain< AppChainModules extends MinimalAppChainDefinition, > extends AppChain { + // Optional for our lazy initialization purpose. + private QueryService?: QueryService< + InferModules, + InferModules + >; + public static from( definition: Modules ) { @@ -192,49 +193,17 @@ export class ClientAppChain< return transaction; } - public get query(): { - runtime: Query< - RuntimeModule, - InferModules - >; - protocol: Query< - ProtocolModule, - InferModules - >; - network: NetworkStateQuery; - explorer: BlockExplorerQuery; - } { - const queryTransportModule = this.container.resolve( - "QueryTransportModule" - ); - - const networkStateTransportModule = - this.container.resolve( - "NetworkStateTransportModule" - ); - - const blockExplorerTransportModule = - this.container.resolve( - "BlockExplorerTransportModule" - ); - - const network = new NetworkStateQuery(networkStateTransportModule); - const explorer = new BlockExplorerQuery(blockExplorerTransportModule); - - return { - runtime: QueryBuilderFactory.fromRuntime( + public get query(): QueryService< + InferModules, + InferModules + > { + if (this.QueryService === undefined) { + this.QueryService = new QueryService( this.runtime, - queryTransportModule - ), - - protocol: QueryBuilderFactory.fromProtocol( this.protocol, - queryTransportModule - ), - - network, - - explorer, - }; + this.container + ); + } + return this.QueryService; } } From 4761810b0df6c6b42d4ba216029e1d5dc82b813c Mon Sep 17 00:00:00 2001 From: saitunc Date: Tue, 16 Dec 2025 13:45:18 +0300 Subject: [PATCH 050/155] style: run lint fix --- packages/sdk/src/client/ClientAppChain.ts | 2 +- packages/sdk/src/query/QueryService.ts | 65 ++++++++++++----------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/packages/sdk/src/client/ClientAppChain.ts b/packages/sdk/src/client/ClientAppChain.ts index 8cd679e01..f816b483b 100644 --- a/packages/sdk/src/client/ClientAppChain.ts +++ b/packages/sdk/src/client/ClientAppChain.ts @@ -48,7 +48,7 @@ export type InferModules>> = export class ClientAppChain< AppChainModules extends MinimalAppChainDefinition, > extends AppChain { - // Optional for our lazy initialization purpose. + // Optional for our lazy initialization purpose. private QueryService?: QueryService< InferModules, InferModules diff --git a/packages/sdk/src/query/QueryService.ts b/packages/sdk/src/query/QueryService.ts index 0a0b94966..05babff1e 100644 --- a/packages/sdk/src/query/QueryService.ts +++ b/packages/sdk/src/query/QueryService.ts @@ -1,5 +1,9 @@ import { DependencyContainer } from "tsyringe"; -import { Runtime, RuntimeModule, RuntimeModulesRecord } from "@proto-kit/module"; +import { + Runtime, + RuntimeModule, + RuntimeModulesRecord, +} from "@proto-kit/module"; import { MandatoryProtocolModulesRecord, Protocol, @@ -22,16 +26,19 @@ export class QueryService< MandatoryProtocolModulesRecord = ProtocolModulesRecord & MandatoryProtocolModulesRecord, > { - - // Here, fields are optional for lazy initialization. + // Here, fields are optional for lazy initialization. private QueryTransport?: QueryTransportModule; + private NetworkStateTransport?: NetworkStateTransportModule; - private BlockExplorerTransport?: BlockExplorerTransportModule; + private BlockExplorerTransport?: BlockExplorerTransportModule; private RuntimeQuery?: Query, RuntimeModules>; + private ProtocolQuery?: Query, ProtocolModules>; + private NetworkQuery?: NetworkStateQuery; + private ExplorerQuery?: BlockExplorerQuery; public constructor( @@ -40,27 +47,26 @@ export class QueryService< private readonly container: DependencyContainer ) {} - /** * A helper function that resolves QueryTrasnportModule. - * If not resolved before, it is resolved. + * If not resolved before, it is resolved. * @returns The registered transport module as {@link QueryTrasnportModule} */ private get queryTransport(): QueryTransportModule { if (this.QueryTransport === undefined) { if (!this.container.isRegistered("QueryTransportModule")) { - throw new Error( - "QueryTransportModule is not registered"); + throw new Error("QueryTransportModule is not registered"); } - this.QueryTransport = - this.container.resolve("QueryTransportModule"); + this.QueryTransport = this.container.resolve( + "QueryTransportModule" + ); } return this.QueryTransport; } /** * A helper function that resolves NetworkStateTransport. - * If not resolved before, it is resolved. + * If not resolved before, it is resolved. * @returns The registered transport module as {@link BlockExplorerTransport} */ private get networkStateTransport(): NetworkStateTransportModule { @@ -78,14 +84,13 @@ export class QueryService< /** * A helper function that resolves BlockExplorerTransportModule. - * If not resolved before, it is resolved. + * If not resolved before, it is resolved. * @returns The registered transport module as {@link BlockExplorerTransport} */ private get blockExplorerTransport(): BlockExplorerTransportModule { if (this.BlockExplorerTransport === undefined) { if (!this.container.isRegistered("BlockExplorerTransportModule")) { - throw new Error( - "BlockExplorerTransportModule is not registered."); + throw new Error("BlockExplorerTransportModule is not registered."); } this.BlockExplorerTransport = this.container.resolve( @@ -96,9 +101,9 @@ export class QueryService< } /** - * Getter of query module for runtime modules. - * If not initialized before, it is initialized. - * @returns A {@link Query} module for runtime module. + * Getter of query module for runtime modules. + * If not initialized before, it is initialized. + * @returns A {@link Query} module for runtime module. */ public get runtime(): Query, RuntimeModules> { if (this.RuntimeQuery === undefined) { @@ -111,9 +116,9 @@ export class QueryService< } /** - * Getter of query module for protocol. - * If not initialized before, it is initialized. - * @returns A {@link Query} module for protocol module. + * Getter of query module for protocol. + * If not initialized before, it is initialized. + * @returns A {@link Query} module for protocol module. */ public get protocol(): Query, ProtocolModules> { if (this.ProtocolQuery === undefined) { @@ -126,29 +131,25 @@ export class QueryService< } /** - * Getter of network state query module. - * If not initialized before, it is initialized. - * @returns A {@link NetworkStateQuery} module. + * Getter of network state query module. + * If not initialized before, it is initialized. + * @returns A {@link NetworkStateQuery} module. */ public get network(): NetworkStateQuery { if (this.NetworkQuery === undefined) { - this.NetworkQuery = new NetworkStateQuery( - this.networkStateTransport - ); + this.NetworkQuery = new NetworkStateQuery(this.networkStateTransport); } return this.NetworkQuery; } /** - * Getter of block explorer query module. - * If not initialized before, it is initialized. - * @returns A {@link BlockExplorerQuery} module. + * Getter of block explorer query module. + * If not initialized before, it is initialized. + * @returns A {@link BlockExplorerQuery} module. */ public get explorer(): BlockExplorerQuery { if (this.ExplorerQuery === undefined) { - this.ExplorerQuery = new BlockExplorerQuery( - this.blockExplorerTransport - ); + this.ExplorerQuery = new BlockExplorerQuery(this.blockExplorerTransport); } return this.ExplorerQuery; } From 468a75adc66ea77e01c23a4d06d7ba268a2e0dae Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 17 Dec 2025 22:05:46 +0100 Subject: [PATCH 051/155] Changed static args injection to global container injection --- packages/common/src/types.ts | 12 +- packages/protocol/src/index.ts | 1 + .../src/settlement/ContractArgsRegistry.ts | 29 +++++ .../settlement/SettlementContractModule.ts | 7 +- .../BridgingSettlementContractModule.ts | 29 +++-- .../SettlementSmartContractModule.ts | 25 ++-- .../settlement/BridgingSettlementContract.ts | 117 ++++++++++-------- .../contracts/settlement/SettlementBase.ts | 70 ++++++++--- .../settlement/SettlementContract.ts | 20 +++ .../src/settlement/modularity/types.ts | 2 +- 10 files changed, 221 insertions(+), 91 deletions(-) create mode 100644 packages/protocol/src/settlement/ContractArgsRegistry.ts diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 4d25b1192..f2a0e452e 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -1,5 +1,13 @@ // allows to reference interfaces as 'classes' rather than instances -import { Bool, DynamicProof, Field, Proof, ProofBase, PublicKey } from "o1js"; +import { + Bool, + DynamicProof, + Field, + Proof, + ProofBase, + PublicKey, + Option, +} from "o1js"; export type TypedClass = new (...args: any[]) => Class; @@ -56,3 +64,5 @@ export type InferProofBase< : ProofType extends DynamicProof ? ProofBase : undefined; + +export class O1PublicKeyOption extends Option(PublicKey) {} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 171157b6c..080dfc646 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -63,6 +63,7 @@ export * from "./settlement/messages/OutgoingMessageArgument"; export * from "./settlement/messages/OutgoingMessage"; export * from "./settlement/modules/NetworkStateSettlementModule"; export * from "./settlement/messages/Deposit"; +export * from "./settlement/ContractArgsRegistry"; export { constants as ProtocolConstants } from "./Constants"; export * from "./hashing/protokit-prefixes"; export * from "./hashing/mina-prefixes"; diff --git a/packages/protocol/src/settlement/ContractArgsRegistry.ts b/packages/protocol/src/settlement/ContractArgsRegistry.ts new file mode 100644 index 000000000..d0b733645 --- /dev/null +++ b/packages/protocol/src/settlement/ContractArgsRegistry.ts @@ -0,0 +1,29 @@ +import { injectable, singleton } from "tsyringe"; + +export interface StaticInitializationContract { + getInitializationArgs(): Args; + + // name: string; +} + +@injectable() +@singleton() +export class ContractArgsRegistry { + args: Record = {}; + + public setArgs( + // contract: TypedClass>, + name: string, + args: Type + ) { + this.args[name] = args; + } + + public getArgs( + // contract: TypedClass> + name: string + ): Type | undefined { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return this.args[name] as Type; + } +} diff --git a/packages/protocol/src/settlement/SettlementContractModule.ts b/packages/protocol/src/settlement/SettlementContractModule.ts index 88cb24327..3bfaa778b 100644 --- a/packages/protocol/src/settlement/SettlementContractModule.ts +++ b/packages/protocol/src/settlement/SettlementContractModule.ts @@ -25,7 +25,10 @@ import { import { GetContracts, InferContractType } from "./modularity/types"; import { BridgingSettlementContractType } from "./contracts/settlement/BridgingSettlementContract"; import { SettlementContractType } from "./contracts/settlement/SettlementBase"; -import { SettlementContractConfig } from "./contracts/SettlementSmartContractModule"; +import { + SettlementContractConfig, + SettlementSmartContractModule, +} from "./contracts/SettlementSmartContractModule"; export type SettlementModulesRecord = ModulesRecord< TypedClass> @@ -70,7 +73,7 @@ export class SettlementContractModule< public static settlementOnly() { return { - SettlementContract: SettlementContractModule, + SettlementContract: SettlementSmartContractModule, } as const; } diff --git a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts index 3fc1759aa..09b1270e9 100644 --- a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts @@ -12,12 +12,13 @@ import { SmartContractClassFromInterface, } from "../ContractModule"; import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; +import { ContractArgsRegistry } from "../ContractArgsRegistry"; import { DispatchSmartContractBase } from "./DispatchSmartContract"; import { BridgingSettlementContractType, - BridgingSettlementContractBase, BridgingSettlementContract, + BridgingSettlementContractArgs, } from "./settlement/BridgingSettlementContract"; import { BridgeContractBase } from "./BridgeContract"; import { DispatchContractProtocolModule } from "./DispatchContractProtocolModule"; @@ -26,6 +27,7 @@ import { DEFAULT_ESCAPE_HATCH, SettlementContractConfig, } from "./SettlementSmartContractModule"; +import { SettlementContract } from "./settlement/SettlementContract"; @injectable() export class BridgingSettlementContractModule extends ContractModule< @@ -41,7 +43,8 @@ export class BridgingSettlementContractModule extends ContractModule< private readonly dispatchContractModule: DispatchContractProtocolModule, @inject("BridgeContract") private readonly bridgeContractModule: BridgeContractProtocolModule, - private readonly childVerificationKeyService: ChildVerificationKeyService + private readonly childVerificationKeyService: ChildVerificationKeyService, + private readonly argsRegistry: ContractArgsRegistry ) { super(); } @@ -54,8 +57,11 @@ export class BridgingSettlementContractModule extends ContractModule< const escapeHatchSlotsInterval = config.escapeHatchSlotsInterval ?? DEFAULT_ESCAPE_HATCH; - const { args } = BridgingSettlementContractBase; - BridgingSettlementContractBase.args = { + const args = + this.argsRegistry.getArgs( + "SettlementContract" + ); + const newArgs = { ...args, DispatchContract: dispatchContract, hooks, @@ -66,6 +72,7 @@ export class BridgingSettlementContractModule extends ContractModule< signedSettlements: args?.signedSettlements, ChildVerificationKeyService: this.childVerificationKeyService, }; + this.argsRegistry.setArgs("SettlementContract", newArgs); // Ideally we don't want to have this cyclic dependency, but we have it in the protocol, // So its logical that we can't avoid that here @@ -88,10 +95,14 @@ export class BridgingSettlementContractModule extends ContractModule< this.contractFactory(); // Init params - BridgingSettlementContractBase.args.BridgeContractVerificationKey = + const args = + this.argsRegistry.getArgs( + "SettlementContract" + )!; + args.BridgeContractVerificationKey = bridgeArtifact.BridgeContract.verificationKey; - if (BridgingSettlementContractBase.args.signedSettlements === undefined) { + if (args.signedSettlements === undefined) { throw new Error( "Args not fully initialized - make sure to also include the SettlementModule in the sequencer" ); @@ -100,7 +111,11 @@ export class BridgingSettlementContractModule extends ContractModule< log.debug("Compiling Settlement Contract"); const artifact = await registry.forceProverExists( - async (reg) => await registry.compile(BridgingSettlementContract) + async (reg) => + await registry.compile( + BridgingSettlementContract, + SettlementContract.name + ) ); return { diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts index 0106ff32f..d1afb3a4b 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts @@ -12,12 +12,14 @@ import { SmartContractClassFromInterface, } from "../ContractModule"; import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; +import { ContractArgsRegistry } from "../ContractArgsRegistry"; +import { BridgingSettlementContract } from "./settlement/BridgingSettlementContract"; import { - BridgingSettlementContractType, - BridgingSettlementContract, -} from "./settlement/BridgingSettlementContract"; -import { SettlementBase } from "./settlement/SettlementBase"; + SettlementContractArgs, + SettlementContractType, +} from "./settlement/SettlementBase"; +import { SettlementContract } from "./settlement/SettlementContract"; export type SettlementContractConfig = { escapeHatchSlotsInterval?: number; @@ -28,7 +30,7 @@ export const DEFAULT_ESCAPE_HATCH = (60 / 3) * 24; @injectable() export class SettlementSmartContractModule extends ContractModule< - BridgingSettlementContractType, + SettlementContractType, SettlementContractConfig > { public constructor( @@ -36,25 +38,28 @@ export class SettlementSmartContractModule extends ContractModule< private readonly hooks: ProvableSettlementHook[], @inject("BlockProver") private readonly blockProver: BlockProvable, - private readonly childVerificationKeyService: ChildVerificationKeyService + private readonly childVerificationKeyService: ChildVerificationKeyService, + private readonly argsRegistry: ContractArgsRegistry ) { super(); } - public contractFactory(): SmartContractClassFromInterface { + public contractFactory(): SmartContractClassFromInterface { const { hooks, config } = this; const escapeHatchSlotsInterval = config.escapeHatchSlotsInterval ?? DEFAULT_ESCAPE_HATCH; - const { args } = SettlementBase; - SettlementBase.args = { + const args = + this.argsRegistry.getArgs("SettlementContract"); + const newArgs = { ...args, hooks, escapeHatchSlotsInterval, signedSettlements: args?.signedSettlements, ChildVerificationKeyService: this.childVerificationKeyService, }; + this.argsRegistry.setArgs("SettlementContract", newArgs); return BridgingSettlementContract; } @@ -70,7 +75,7 @@ export class SettlementSmartContractModule extends ContractModule< log.debug("Compiling Settlement Contract"); const artifact = await registry.forceProverExists( - async (reg) => await registry.compile(BridgingSettlementContract) + async (reg) => await registry.compile(SettlementContract) ); return { diff --git a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts index a823af09d..f5aeee9ff 100644 --- a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts +++ b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts @@ -1,4 +1,4 @@ -import { TypedClass, ChildVerificationKeyService } from "@proto-kit/common"; +import { TypedClass, O1PublicKeyOption } from "@proto-kit/common"; import { AccountUpdate, Bool, @@ -18,15 +18,24 @@ import { TokenId, DeployArgs, } from "o1js"; +import { container } from "tsyringe"; import { NetworkState } from "../../../model/network/NetworkState"; -import { ProvableSettlementHook } from "../../modularity/ProvableSettlementHook"; import { DispatchContractType } from "../DispatchSmartContract"; import { BridgeContractType } from "../BridgeContract"; import { TokenBridgeDeploymentAuth } from "../authorizations/TokenBridgeDeploymentAuth"; import { UpdateMessagesHashAuth } from "../authorizations/UpdateMessagesHashAuth"; +import { + ContractArgsRegistry, + StaticInitializationContract, +} from "../../ContractArgsRegistry"; -import { DynamicBlockProof, SettlementBase } from "./SettlementBase"; +import { + DynamicBlockProof, + SettlementBase, + SettlementContractArgs, + SettlementContractType, +} from "./SettlementBase"; /* eslint-disable @typescript-eslint/lines-between-class-members */ @@ -35,30 +44,11 @@ export class TokenMapping extends Struct({ publicKey: PublicKey, }) {} -export interface BridgingSettlementContractType { +export interface BridgingSettlementContractType extends SettlementContractType { authorizationField: State; - deployAndInitialize: ( - args: DeployArgs | undefined, - permissions: Permissions, - sequencer: PublicKey, - dispatchContract: PublicKey - ) => Promise; - assertStateRoot: (root: Field) => AccountUpdate; - settle: ( - blockProof: DynamicBlockProof, - signature: Signature, - publicKey: PublicKey, - inputNetworkState: NetworkState, - outputNetworkState: NetworkState, - newPromisedMessagesHash: Field - ) => Promise; - addTokenBridge: ( - tokenId: Field, - address: PublicKey, - dispatchContract: PublicKey - ) => Promise; + addTokenBridge: (tokenId: Field, address: PublicKey) => Promise; } // @singleton() @@ -75,21 +65,39 @@ export interface BridgingSettlementContractType { // }; // } -export abstract class BridgingSettlementContractBase extends SettlementBase { +export interface BridgingSettlementContractArgs extends SettlementContractArgs { + DispatchContract: TypedClass; + BridgeContract: TypedClass & typeof SmartContract; + // Lazily initialized + BridgeContractVerificationKey: VerificationKey | undefined; + BridgeContractPermissions: Permissions | undefined; + signedSettlements: boolean | undefined; +} + +export abstract class BridgingSettlementContractBase + extends SettlementBase + implements StaticInitializationContract +{ // This pattern of injecting args into a smartcontract is currently the only // viable solution that works given the inheritance issues of o1js // public static args = container.resolve(SettlementSmartContractStaticArgs); - public static args: { - DispatchContract: TypedClass; - hooks: ProvableSettlementHook[]; - escapeHatchSlotsInterval: number; - BridgeContract: TypedClass & typeof SmartContract; - // Lazily initialized - BridgeContractVerificationKey: VerificationKey | undefined; - BridgeContractPermissions: Permissions | undefined; - signedSettlements: boolean | undefined; - ChildVerificationKeyService: ChildVerificationKeyService; - }; + // public static args: { + // DispatchContract: TypedClass; + // hooks: ProvableSettlementHook[]; + // escapeHatchSlotsInterval: number; + // BridgeContract: TypedClass & typeof SmartContract; + // // Lazily initialized + // BridgeContractVerificationKey: VerificationKey | undefined; + // BridgeContractPermissions: Permissions | undefined; + // signedSettlements: boolean | undefined; + // ChildVerificationKeyService: ChildVerificationKeyService; + // }; + + public getInitializationArgs(): BridgingSettlementContractArgs { + return container + .resolve(ContractArgsRegistry) + .getArgs("SettlementContract")!; + } events = { "token-bridge-deployed": TokenMapping, @@ -119,7 +127,7 @@ export abstract class BridgingSettlementContractBase extends SettlementBase { // TODO Like these properties, I am too lazy to properly infer the types here private assertLazyConfigsInitialized() { const uninitializedProperties: string[] = []; - const { args } = BridgingSettlementContractBase; + const args = this.getInitializationArgs(); if (args.BridgeContractPermissions === undefined) { uninitializedProperties.push("BridgeContractPermissions"); } @@ -135,20 +143,22 @@ export abstract class BridgingSettlementContractBase extends SettlementBase { } } + // TODO We should move this to the dispatchcontract eventually - or after mesa + // to the combined settlement & dispatch contract protected async deployTokenBridge(tokenId: Field, address: PublicKey) { Provable.asProver(() => { this.assertLazyConfigsInitialized(); }); - const { args } = BridgingSettlementContractBase; - const BridgeContractClass = args.BridgeContract; - const bridgeContract = new BridgeContractClass(address, tokenId); - const { BridgeContractVerificationKey, signedSettlements, BridgeContractPermissions, - } = args; + BridgeContract: BridgeContractClass, + DispatchContract, + } = this.getInitializationArgs(); + + const bridgeContract = new BridgeContractClass(address, tokenId); if ( signedSettlements === undefined || @@ -169,9 +179,9 @@ export abstract class BridgingSettlementContractBase extends SettlementBase { // This function is not a zkapps method, therefore it will be part of this methods execution // The returning account update (owner.self) is therefore part of this circuit and is assertable const deploymentAccountUpdate = await bridgeContract.deployProvable( - args.BridgeContractVerificationKey, - args.signedSettlements!, - args.BridgeContractPermissions!, + BridgeContractVerificationKey, + signedSettlements!, + BridgeContractPermissions!, this.address ); @@ -202,10 +212,7 @@ export abstract class BridgingSettlementContractBase extends SettlementBase { address, }).hash() ); - const dispatchContract = - new BridgingSettlementContractBase.args.DispatchContract( - dispatchContractAddress - ); + const dispatchContract = new DispatchContract(dispatchContractAddress); await dispatchContract.enableTokenDeposits(tokenId, address, this.address); } @@ -229,9 +236,7 @@ export abstract class BridgingSettlementContractBase extends SettlementBase { const dispatchContractAddress = this.dispatchContractAddress.getAndRequireEquals(); - const { DispatchContract } = - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - (this.constructor as typeof BridgingSettlementContractBase).args; + const { DispatchContract } = this.getInitializationArgs(); // Get dispatch contract values // These values are witnesses but will be checked later on the AU @@ -285,13 +290,17 @@ export class BridgingSettlementContract args: DeployArgs | undefined, permissions: Permissions, sequencer: PublicKey, - dispatchContract: PublicKey + dispatchContract: O1PublicKeyOption ): Promise { + dispatchContract.assertSome( + "Bridging-enabled settlement contract requires a dispatch contract address" + ); + await super.deploy(args); this.self.account.permissions.set(permissions); - await this.initializeBaseBridging(sequencer, dispatchContract); + await this.initializeBaseBridging(sequencer, dispatchContract.value); } @method async approveBase(forest: AccountUpdateForest) { diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts index a35425275..0cd5b2beb 100644 --- a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts @@ -1,12 +1,15 @@ import { Bool, + DeployArgs, DynamicProof, Field, + Option, PublicKey, Signature, State, TokenContract, UInt32, + Permissions, } from "o1js"; import { ChildVerificationKeyService, @@ -14,6 +17,7 @@ import { mapSequential, prefixToField, } from "@proto-kit/common"; +import { container } from "tsyringe"; import { BlockHashMerkleTree } from "../../../prover/block/accummulators/BlockHashMerkleTree"; import { NetworkState } from "../../../model/network/NetworkState"; @@ -26,6 +30,10 @@ import { BlockProverPublicInput, BlockProverPublicOutput, } from "../../../prover/block/BlockProvable"; +import { + ContractArgsRegistry, + StaticInitializationContract, +} from "../../ContractArgsRegistry"; /* eslint-disable @typescript-eslint/lines-between-class-members */ @@ -50,6 +58,13 @@ export interface SettlementContractType { networkStateHash: State; blockHashRoot: State; + deployAndInitialize: ( + args: DeployArgs | undefined, + permissions: Permissions, + sequencer: PublicKey, + dispatchContract: Option + ) => Promise; + settle: ( blockProof: DynamicBlockProof, signature: Signature, @@ -60,13 +75,29 @@ export interface SettlementContractType { ) => Promise; } -export abstract class SettlementBase extends TokenContract { - public static args: { - hooks: ProvableSettlementHook[]; - escapeHatchSlotsInterval: number; - signedSettlements: boolean | undefined; - ChildVerificationKeyService: ChildVerificationKeyService; - }; +export interface SettlementContractArgs { + hooks: ProvableSettlementHook[]; + escapeHatchSlotsInterval: number; + signedSettlements: boolean | undefined; + ChildVerificationKeyService: ChildVerificationKeyService; +} + +export abstract class SettlementBase + extends TokenContract + implements StaticInitializationContract +{ + getInitializationArgs(): SettlementContractArgs { + return container + .resolve(ContractArgsRegistry) + .getArgs("SettlementContract")!; + } + // + // public static args: { + // hooks: ProvableSettlementHook[]; + // escapeHatchSlotsInterval: number; + // signedSettlements: boolean | undefined; + // ChildVerificationKeyService: ChildVerificationKeyService; + // }; abstract sequencerKey: State; abstract lastSettlementL1BlockHeight: State; @@ -90,6 +121,13 @@ export abstract class SettlementBase extends TokenContract { newPromisedMessagesHash: Field ): Promise; + abstract deployAndInitialize( + args: DeployArgs | undefined, + permissions: Permissions, + sequencer: PublicKey, + dispatchContract: Option + ): Promise; + protected async settleBase( blockProof: DynamicBlockProof, signature: Signature, @@ -98,30 +136,30 @@ export abstract class SettlementBase extends TokenContract { outputNetworkState: NetworkState, newPromisedMessagesHash: Field ) { + const { + escapeHatchSlotsInterval, + hooks, + ChildVerificationKeyService: childVerificationKeyService, + } = this.getInitializationArgs(); + // Brought in as a constant const blockProofVk = - SettlementBase.args.ChildVerificationKeyService.getVerificationKey( - "BlockProver" - ); + childVerificationKeyService.getVerificationKey("BlockProver"); if (!blockProofVk.hash.isConstant()) { throw new Error("Sanity check - vk hash has to be constant"); } - // Verify the blockproof - blockProof.verify(blockProofVk); + blockProof.verify(blockProofVk); // Get and assert on-chain values const stateRoot = this.stateRoot.getAndRequireEquals(); const networkStateHash = this.networkStateHash.getAndRequireEquals(); const blockHashRoot = this.blockHashRoot.getAndRequireEquals(); const sequencerKey = this.sequencerKey.getAndRequireEquals(); + const lastSettlementL1BlockHeight = this.lastSettlementL1BlockHeight.getAndRequireEquals(); - const { escapeHatchSlotsInterval, hooks } = - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - (this.constructor as typeof SettlementBase).args; - // Get block height and use the lower bound for all ops const minBlockHeightIncluded = this.network.blockchainLength.get(); this.network.blockchainLength.requireBetween( diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts index 0a0c802f8..95321684f 100644 --- a/packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementContract.ts @@ -7,7 +7,10 @@ import { PublicKey, Field, Signature, + DeployArgs, + Permissions, } from "o1js"; +import { O1PublicKeyOption } from "@proto-kit/common"; import { NetworkState } from "../../../model/network/NetworkState"; @@ -31,6 +34,23 @@ export class SettlementContract @state(Field) blockHashRoot = State(); + public async deployAndInitialize( + args: DeployArgs | undefined, + permissions: Permissions, + sequencer: PublicKey, + dispatchContract: O1PublicKeyOption + ): Promise { + dispatchContract.assertNone( + "Non-bridging settlement contract doesn't require a dispatch contract" + ); + + await super.deploy(args); + + this.self.account.permissions.set(permissions); + + await this.initializeBase(sequencer); + } + @method async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); } diff --git a/packages/protocol/src/settlement/modularity/types.ts b/packages/protocol/src/settlement/modularity/types.ts index 03da15930..b05abdf6f 100644 --- a/packages/protocol/src/settlement/modularity/types.ts +++ b/packages/protocol/src/settlement/modularity/types.ts @@ -1,11 +1,11 @@ import { TypedClass } from "@proto-kit/common"; +import { SmartContract } from "o1js"; import { ContractModule, SmartContractClassFromInterface, } from "../ContractModule"; import type { SettlementModulesRecord } from "../SettlementContractModule"; -import { SmartContract } from "o1js"; export type InferContractType< Module extends TypedClass>, From c4b3f52888f01e0361722b22a4a0070350dc8d5c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 17 Dec 2025 22:10:40 +0100 Subject: [PATCH 052/155] Restructured Settlement and Bridging to be functional in isolation --- .../common/src/compiling/CompileRegistry.ts | 15 +- .../production/tasks/CircuitCompilerTask.ts | 28 +- .../src/sequencer/SequencerStartupModule.ts | 14 +- .../src/sequencer/SettlementStartupModule.ts | 6 +- .../src/settlement/BridgingModule.ts | 156 ++++++- .../src/settlement/SettlementModule.ts | 382 ++++++------------ .../interactions/AddressRegistry.ts | 19 + .../interactions/DeployInteraction.ts | 11 + .../interactions/SettleInteraction.ts | 11 + .../bridging/BridgingDeployInteraction.ts | 131 ++++++ .../bridging/BridgingSettlementInteraction.ts | 127 ++++++ .../vanilla/VanillaDeployInteraction.ts | 120 ++++++ .../vanilla/VanillaSettlementInteraction.ts | 118 ++++++ .../messages/IncomingMessagesService.ts | 8 +- .../worker/startup/WorkerRegistrationTask.ts | 24 +- .../sequencer/test/integration/Proven.test.ts | 19 +- .../sequencer/test/settlement/Settlement.ts | 90 +++-- 17 files changed, 931 insertions(+), 348 deletions(-) create mode 100644 packages/sequencer/src/settlement/interactions/AddressRegistry.ts create mode 100644 packages/sequencer/src/settlement/interactions/DeployInteraction.ts create mode 100644 packages/sequencer/src/settlement/interactions/SettleInteraction.ts create mode 100644 packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts create mode 100644 packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts create mode 100644 packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts create mode 100644 packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts diff --git a/packages/common/src/compiling/CompileRegistry.ts b/packages/common/src/compiling/CompileRegistry.ts index e34dbc4b5..a85766ffe 100644 --- a/packages/common/src/compiling/CompileRegistry.ts +++ b/packages/common/src/compiling/CompileRegistry.ts @@ -47,22 +47,17 @@ export class CompileRegistry { return result; } - public async compile(target: CompileTarget, proverNeeded: boolean = true) { - if (this.artifacts[target.name] === undefined || this.inForceProverBlock) { + public async compile(target: CompileTarget, nameOverride?: string) { + const name = nameOverride ?? target.name; + if (this.artifacts[name] === undefined || this.inForceProverBlock) { const artifact = await this.compiler.compileContract(target); - this.artifacts[target.name] = artifact; + this.artifacts[name] = artifact; return artifact; } - return this.artifacts[target.name]; + return this.artifacts[name]; } public getArtifact(name: string): CompileArtifact | undefined { - if (this.artifacts[name] === undefined) { - throw new Error( - `Artifact for ${name} not available, did you compile it via the CompileRegistry?` - ); - } - return this.artifacts[name]; } diff --git a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts index f3a06bd72..61d2de0c6 100644 --- a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts @@ -15,8 +15,10 @@ import { Protocol, SettlementContractModule, RuntimeVerificationKeyRootService, - SettlementSmartContractBase, MandatoryProtocolModulesRecord, + type SettlementModulesRecord, + BridgingSettlementContractArgs, + ContractArgsRegistry, } from "@proto-kit/protocol"; import { TaskSerializer } from "../../../worker/flow/Task"; @@ -48,7 +50,8 @@ export class CircuitCompilerTask extends UnpreparingTask< @inject("Runtime") protected readonly runtime: Runtime, @inject("Protocol") protected readonly protocol: Protocol, - private readonly compileRegistry: CompileRegistry + private readonly compileRegistry: CompileRegistry, + private readonly contractArgsRegistry: ContractArgsRegistry ) { super(); } @@ -97,7 +100,7 @@ export class CircuitCompilerTask extends UnpreparingTask< const container = this.protocol.dependencyContainer; if (container.isRegistered("SettlementContractModule")) { const settlementModule = container.resolve< - SettlementContractModule + SettlementContractModule >("SettlementContractModule"); // Needed so that all contractFactory functions are called, because @@ -115,9 +118,10 @@ export class CircuitCompilerTask extends UnpreparingTask< const sumModule = { compile: async (registry: CompileRegistry) => { - await reduceSequential( - modules.map(([, module]) => module), - async (record, module) => { + await reduceSequential<[string, CompilableModule], ArtifactRecord>( + modules, + async (record, [moduleName, module]) => { + log.info(`Compiling ${moduleName}`); const artifacts = await module.compile(registry); return { ...record, @@ -129,9 +133,9 @@ export class CircuitCompilerTask extends UnpreparingTask< }, }; - modules.push(["Settlement", sumModule]); + const combinedModules = [...modules, ["Settlement", sumModule]]; - return Object.fromEntries(modules); + return Object.fromEntries(combinedModules); } return {}; } @@ -150,8 +154,11 @@ export class CircuitCompilerTask extends UnpreparingTask< } if (input.isSignedSettlement !== undefined) { - const contractArgs = SettlementSmartContractBase.args; - SettlementSmartContractBase.args = { + const contractArgs = + this.contractArgsRegistry.getArgs( + "SettlementContract" + ); + const newArgs = { ...contractArgs, signedSettlements: input.isSignedSettlement, // TODO Add distinction between mina and custom tokens @@ -160,6 +167,7 @@ export class CircuitCompilerTask extends UnpreparingTask< : new ProvenSettlementPermissions() ).bridgeContractMina(), }; + this.contractArgsRegistry.setArgs("SettlementContract", newArgs); } // TODO make adaptive diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 8d2294de0..b3fec37ff 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -1,9 +1,10 @@ import { inject } from "tsyringe"; import { + BridgingSettlementContractArgs, + ContractArgsRegistry, MandatoryProtocolModulesRecord, Protocol, RuntimeVerificationKeyRootService, - SettlementSmartContractBase, } from "@proto-kit/protocol"; import { log, @@ -43,7 +44,8 @@ export class SequencerStartupModule @inject("BaseLayer", { isOptional: true }) private readonly baseLayer: MinaBaseLayer | undefined, @inject("AreProofsEnabled") - private readonly areProofsEnabled: AreProofsEnabled + private readonly areProofsEnabled: AreProofsEnabled, + private readonly contractArgsRegistry: ContractArgsRegistry ) { super(); } @@ -168,8 +170,12 @@ export class SequencerStartupModule // Init BridgeContract vk for settlement contract const bridgeVk = protocolBridgeArtifacts.BridgeContract; if (bridgeVk !== undefined) { - SettlementSmartContractBase.args.BridgeContractVerificationKey = - bridgeVk.verificationKey; + // TODO Inject CompileRegistry directly + const args = + this.contractArgsRegistry.getArgs( + "SettlementContract" + )!; + args.BridgeContractVerificationKey = bridgeVk.verificationKey; } await this.registrationFlow.start({ diff --git a/packages/sequencer/src/sequencer/SettlementStartupModule.ts b/packages/sequencer/src/sequencer/SettlementStartupModule.ts index 8a76e7284..be3827bbd 100644 --- a/packages/sequencer/src/sequencer/SettlementStartupModule.ts +++ b/packages/sequencer/src/sequencer/SettlementStartupModule.ts @@ -11,6 +11,7 @@ import { CircuitCompilerTask } from "../protocol/production/tasks/CircuitCompile @injectable() export class SettlementStartupModule { + // TODO Why is this a separate module? public constructor( private readonly compileRegistry: CompileRegistry, private readonly flowCreator: FlowCreator, @@ -42,9 +43,8 @@ export class SettlementStartupModule { SettlementSmartContract: CompileArtifact; DispatchSmartContract: CompileArtifact; }> { - const settlementVerificationKey = this.compileRegistry.getArtifact( - "SettlementSmartContract" - ); + const settlementVerificationKey = + this.compileRegistry.getArtifact("SettlementContract"); const dispatchVerificationKey = this.compileRegistry.getArtifact( "DispatchSmartContract" ); diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 90bf94282..0f1d74fe0 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -3,7 +3,6 @@ import { BridgeContractConfig, BridgeContractType, MandatoryProtocolModulesRecord, - MandatorySettlementModulesRecord, OUTGOING_MESSAGE_BATCH_SIZE, OutgoingMessageArgument, OutgoingMessageArgumentBatch, @@ -18,6 +17,11 @@ import { PROTOKIT_FIELD_PREFIXES, OutgoingMessageEvent, BridgeContractContext, + BridgingSettlementModulesRecord, + DispatchContractType, + BridgingSettlementContractType, + ContractArgsRegistry, + BridgingSettlementContractArgs, } from "@proto-kit/protocol"; import { AccountUpdate, @@ -25,6 +29,7 @@ import { Mina, Provable, PublicKey, + SmartContract, TokenContract, TokenId, Transaction, @@ -41,12 +46,15 @@ import { match, Pattern } from "ts-pattern"; import { FungibleToken } from "mina-fungible-token"; // eslint-disable-next-line import/no-extraneous-dependencies import groupBy from "lodash/groupBy"; +// eslint-disable-next-line import/no-extraneous-dependencies +import truncate from "lodash/truncate"; import { FeeStrategy } from "../protocol/baselayer/fees/FeeStrategy"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; import { AsyncLinkedLeafStore } from "../state/async/AsyncLinkedLeafStore"; import { CachedLinkedLeafStore } from "../state/lmt/CachedLinkedLeafStore"; import { SettleableBatch } from "../storage/model/Batch"; +import { SequencerModule } from "../sequencer/builder/SequencerModule"; import type { SettlementModule } from "./SettlementModule"; import { SettlementUtils } from "./utils/SettlementUtils"; @@ -54,6 +62,9 @@ import { MinaTransactionSender } from "./transactions/MinaTransactionSender"; import { OutgoingMessageCollector } from "./messages/outgoing/OutgoingMessageCollector"; import { ArchiveNode } from "./utils/ArchiveNode"; import { MinaSigner } from "./MinaSigner"; +import { SignedSettlementPermissions } from "./permissions/SignedSettlementPermissions"; +import { ProvenSettlementPermissions } from "./permissions/ProvenSettlementPermissions"; +import { AddressRegistry } from "./interactions/AddressRegistry"; export type SettlementTokenConfig = Record< string, @@ -67,6 +78,12 @@ export type SettlementTokenConfig = Record< } >; +export type BridgingModuleConfig = { + addresses?: { + DispatchContract: PublicKey; + }; +}; + /** * Module that facilitates all transaction creation and monitoring for * bridging related operations. @@ -74,7 +91,7 @@ export type SettlementTokenConfig = Record< * for those as needed */ @injectable() -export class BridgingModule { +export class BridgingModule extends SequencerModule { private seenBridgeDeployments: { latestDeployment: number; // tokenId => Bridge address @@ -86,6 +103,8 @@ export class BridgingModule { private utils: SettlementUtils; + protected dispatchContract?: DispatchContractType & SmartContract; + public constructor( @inject("Protocol") private readonly protocol: Protocol, @@ -99,18 +118,44 @@ export class BridgingModule { @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, @inject("SettlementSigner") private readonly signer: MinaSigner, @inject("TransactionSender") - private readonly transactionSender: MinaTransactionSender + private readonly transactionSender: MinaTransactionSender, + @inject("AddressRegistry") + private readonly addressRegistry: AddressRegistry, + private readonly argsRegistry: ContractArgsRegistry ) { + super(); + this.utils = new SettlementUtils(baseLayer, signer); } + public getDispatchContract() { + if (this.dispatchContract === undefined) { + const address = this.getDispatchContractAddress(); + this.dispatchContract = this.settlementContractModule().createContract( + "DispatchContract", + address + ); + } + return this.dispatchContract; + } + + public getDispatchContractAddress(): PublicKey { + const keys = + this.addressRegistry.getContractAddress("DispatchContract") ?? + this.config.addresses?.DispatchContract; + if (keys === undefined) { + throw new Error("Contracts not initialized yet"); + } + return keys; + } + private getMessageProcessors() { return this.protocol.dependencyContainer.resolveAll< OutgoingMessageProcessor >("OutgoingMessageProcessor"); } - protected settlementContractModule(): SettlementContractModule { + protected settlementContractModule(): SettlementContractModule { return this.protocol.dependencyContainer.resolve( "SettlementContractModule" ); @@ -127,10 +172,11 @@ export class BridgingModule { return config; } + // TODO Use AddressRegistry for bridge addresses public async updateBridgeAddresses() { const events = await this.settlementModule - .getContracts() - .settlement.fetchEvents( + .getContract() + .fetchEvents( UInt32.from(this.seenBridgeDeployments.latestDeployment + 1) ); const tuples = events @@ -154,6 +200,71 @@ export class BridgingModule { }; } + public async deployMinaBridge( + contractKey: PublicKey, + options: { + nonce?: number; + } + ) { + return await this.deployTokenBridge(undefined, contractKey, options); + } + + /** + * Deploys a token bridge (BridgeContract) and authorizes it on the DispatchContract + * + * Invariant: The owner has to be specified, unless the bridge is for the mina token + * + * @param owner reference to the token owner contract (used to approve the deployment AUs) + * @param contractKey PublicKey to which the new bridge contract should be deployed to + * @param options + */ + public async deployTokenBridge( + owner: TokenContract | undefined, + contractKey: PublicKey, + options: { + nonce?: number; + } + ) { + const feepayer = this.signer.getFeepayerKey(); + const nonce = options?.nonce ?? undefined; + + const tokenId = owner?.deriveTokenId() ?? TokenId.default; + const settlementContract = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + this.settlementModule.getContract() as BridgingSettlementContractType & + SmartContract; + + const tx = await Mina.transaction( + { + sender: feepayer, + nonce: nonce, + memo: `Deploy token bridge for ${truncate(tokenId.toString(), { length: 6 })}`, + fee: this.feeStrategy.getFee(), + }, + async () => { + AccountUpdate.fundNewAccount(feepayer, 1); + + await settlementContract.addTokenBridge(tokenId, contractKey); + + if (owner !== undefined) { + await owner.approveAccountUpdate(settlementContract.self); + } + } + ); + + // Only ContractKeys and OwnerKey for check. + // Used all in signing process. + const txSigned = this.utils.signTransaction(tx, { + signingWithSignatureCheck: [ + ...this.signer.getContractAddresses(), + ...(owner ? [owner.address] : []), + ], + signingPublicKeys: [contractKey], + }); + + await this.transactionSender.proveAndSendTransaction(txSigned, "included"); + } + public async getBridgeAddress( tokenId: Field ): Promise { @@ -170,9 +281,9 @@ export class BridgingModule { public async getDepositContractAttestation(tokenId: Field) { await ArchiveNode.waitOnSync(this.baseLayer.config); - const { dispatch } = this.settlementModule.getContracts(); + const DispatchContract = this.getDispatchContract(); - const tree = await TokenBridgeTree.buildTreeFromEvents(dispatch); + const tree = await TokenBridgeTree.buildTreeFromEvents(DispatchContract); const index = tree.getIndex(tokenId); return new TokenBridgeAttestation({ index: Field(index), @@ -311,7 +422,8 @@ export class BridgingModule { public createBridgeContract(contractAddress: PublicKey, tokenId: Field) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.settlementContractModule().createBridgeContract( + return this.settlementContractModule().createContract( + "BridgeContract", contractAddress, tokenId ) as BridgeContractType & TokenContract; @@ -356,7 +468,7 @@ export class BridgingModule { | { nonceUsed: false } | { nonceUsed: true; tx: Mina.Transaction } > { - const settlementContract = this.settlementModule.getContracts().settlement; + const settlementContract = this.settlementModule.getSettlementContract(); const bridge = await this.getBridgeContract(tokenId); log.debug( @@ -546,5 +658,29 @@ export class BridgingModule { return txs; } + + public async start(): Promise { + const contractArgs = + this.argsRegistry.getArgs( + "SettlementContract" + ); + + this.argsRegistry.setArgs("SettlementContract", { + ...contractArgs, + // TODO Add distinction between mina and custom tokens + BridgeContractPermissions: (this.baseLayer.isSignedSettlement() + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions() + ).bridgeContractMina(), + }); + + const dispatchAddress = this.config.addresses?.DispatchContract; + if (dispatchAddress !== undefined) { + this.addressRegistry.addContractAddress( + "DispatchContract", + dispatchAddress + ); + } + } /* eslint-enable no-await-in-loop */ } diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 597da53b3..a2004d45a 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -1,32 +1,22 @@ import { Protocol, SettlementContractModule, - BATCH_SIGNATURE_PREFIX, - DispatchSmartContract, - SettlementSmartContract, MandatorySettlementModulesRecord, MandatoryProtocolModulesRecord, - SettlementSmartContractBase, - DynamicBlockProof, + type SettlementContractType, + ContractArgsRegistry, + SettlementContractArgs, } from "@proto-kit/protocol"; -import { - AccountUpdate, - fetchAccount, - Field, - Mina, - PublicKey, - TokenContract, - TokenId, -} from "o1js"; +import { fetchAccount, Field, Mina, PublicKey, SmartContract } from "o1js"; import { inject } from "tsyringe"; import { EventEmitter, EventEmittingComponent, - log, DependencyFactory, + ModuleContainerLike, + DependencyRecord, + log, } from "@proto-kit/common"; -// eslint-disable-next-line import/no-extraneous-dependencies -import truncate from "lodash/truncate"; import { SequencerModule, @@ -34,18 +24,26 @@ import { } from "../sequencer/builder/SequencerModule"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; import { Batch, SettleableBatch } from "../storage/model/Batch"; -import { BlockProofSerializer } from "../protocol/production/tasks/serializers/BlockProofSerializer"; import { Settlement } from "../storage/model/Settlement"; -import { FeeStrategy } from "../protocol/baselayer/fees/FeeStrategy"; -import { SettlementStartupModule } from "../sequencer/SettlementStartupModule"; import { SettlementStorage } from "../storage/repositories/SettlementStorage"; -import { MinaTransactionSender } from "./transactions/MinaTransactionSender"; -import { ProvenSettlementPermissions } from "./permissions/ProvenSettlementPermissions"; -import { SignedSettlementPermissions } from "./permissions/SignedSettlementPermissions"; import { SettlementUtils } from "./utils/SettlementUtils"; -import { BridgingModule } from "./BridgingModule"; +import type { BridgingModule } from "./BridgingModule"; import { MinaSigner } from "./MinaSigner"; +import { BridgingDeployInteraction } from "./interactions/bridging/BridgingDeployInteraction"; +import { VanillaDeployInteraction } from "./interactions/vanilla/VanillaDeployInteraction"; +import { BridgingSettlementInteraction } from "./interactions/bridging/BridgingSettlementInteraction"; +import { VanillaSettlementInteraction } from "./interactions/vanilla/VanillaSettlementInteraction"; +import { + AddressRegistry, + InMemoryAddressRegistry, +} from "./interactions/AddressRegistry"; + +export type SettlementModuleConfig = { + addresses?: { + SettlementContract: PublicKey; + }; +}; export type SettlementModuleEvents = { "settlement-submitted": [Batch]; @@ -53,13 +51,10 @@ export type SettlementModuleEvents = { @sequencerModule() export class SettlementModule - extends SequencerModule - implements EventEmittingComponent, DependencyFactory + extends SequencerModule + implements EventEmittingComponent { - protected contracts?: { - settlement: SettlementSmartContract; - dispatch: DispatchSmartContract; - }; + protected contract?: SettlementContractType & SmartContract; public utils: SettlementUtils; @@ -71,69 +66,77 @@ export class SettlementModule private readonly protocol: Protocol, @inject("SettlementStorage") private readonly settlementStorage: SettlementStorage, - private readonly blockProofSerializer: BlockProofSerializer, - @inject("TransactionSender") - private readonly transactionSender: MinaTransactionSender, @inject("SettlementSigner") private readonly signer: MinaSigner, - @inject("FeeStrategy") - private readonly feeStrategy: FeeStrategy, - private readonly settlementStartupModule: SettlementStartupModule + @inject("Sequencer") + private readonly parentContainer: ModuleContainerLike, + @inject("AddressRegistry") + private readonly addressRegistry: AddressRegistry, + private readonly argsRegistry: ContractArgsRegistry ) { super(); this.utils = new SettlementUtils(this.baseLayer, this.signer); } - public dependencies() { + public static dependencies(): DependencyRecord { return { - BridgingModule: { - useClass: BridgingModule, + AddressRegistry: { + useClass: InMemoryAddressRegistry, }, }; } + private bridgingModule(): BridgingModule | undefined { + const container = this.parentContainer.dependencyContainer; + if (container.isRegistered("BridgingModule")) { + return container.resolve("BridgingModule"); + } + return undefined; + } + protected settlementContractModule(): SettlementContractModule { return this.protocol.dependencyContainer.resolve( "SettlementContractModule" ); } - public getAddresses() { - const keysArray = this.signer.getContractAddresses(); - return { - settlement: keysArray[0], - dispatch: keysArray[1], - }; + public getSettlementContractAddress(): PublicKey { + const keys = + this.addressRegistry.getContractAddress("SettlementContract") ?? + this.config.addresses?.SettlementContract; + + if (keys === undefined) { + throw new Error("Contracts not initialized yet"); + } + return keys; } - public getContractAddresses() { - return this.signer.getContractAddresses(); + public getSettlementContract() { + if (this.contract === undefined) { + const address = this.getSettlementContractAddress(); + this.contract = this.settlementContractModule().createContract( + "SettlementContract", + address + ); + } + + return this.contract; } - public getContracts() { - if (this.contracts === undefined) { - const addresses = this.getAddresses(); + public getContract() { + if (this.contract === undefined) { + const address = this.getSettlementContractAddress(); const { protocol } = this; const settlementContractModule = protocol.dependencyContainer.resolve< SettlementContractModule >("SettlementContractModule"); - // TODO Add generic inference of concrete Contract types - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - this.contracts = settlementContractModule.createContracts(addresses) as { - settlement: SettlementSmartContract; - dispatch: DispatchSmartContract; - }; + const contracts = settlementContractModule.createContracts({ + SettlementContract: address, + }); + this.contract = contracts.SettlementContract; } - return this.contracts; - } - - private async fetchContractAccounts() { - const contracts = this.getContracts(); - await this.utils.fetchContractAccounts( - contracts.settlement, - contracts.dispatch - ); + return this.contract; } public async settleBatch( @@ -142,60 +145,18 @@ export class SettlementModule nonce?: number; } = {} ): Promise { - await this.fetchContractAccounts(); - const { settlement: settlementContract, dispatch } = this.getContracts(); - const feepayer = this.signer.getFeepayerKey(); log.debug("Preparing settlement"); - const lastSettlementL1BlockHeight = - settlementContract.lastSettlementL1BlockHeight.get().value; - const signature = this.signer.sign([ - BATCH_SIGNATURE_PREFIX, - lastSettlementL1BlockHeight, - ]); - - const latestSequenceStateHash = dispatch.account.actionState.get(); - - const blockProof = await this.blockProofSerializer - .getBlockProofSerializer() - .fromJSONProof(batch.proof); - - const dynamicBlockProof = DynamicBlockProof.fromProof(blockProof); - - const tx = await Mina.transaction( - { - sender: feepayer, - nonce: options?.nonce, - fee: this.feeStrategy.getFee(), - memo: "Protokit settle", - }, - async () => { - await settlementContract.settle( - dynamicBlockProof, - signature, - dispatch.address, - feepayer, - batch.fromNetworkState, - batch.toNetworkState, - latestSequenceStateHash - ); - } - ); - - this.utils.signTransaction(tx, { - signingWithSignatureCheck: [...this.signer.getContractAddresses()], - }); - - const { hash: transactionHash } = - await this.transactionSender.proveAndSendTransaction(tx, "included"); - - log.info("Settlement transaction sent and included"); - - const settlement = { - batches: [batch.height], - promisedMessagesHash: latestSequenceStateHash.toString(), - transactionHash, - }; + const bridgingModule = this.bridgingModule(); + const interaction = + bridgingModule !== undefined + ? this.parentContainer.dependencyContainer.resolve( + BridgingSettlementInteraction + ) + : this.parentContainer.dependencyContainer.resolve( + VanillaSettlementInteraction + ); + const settlement = await interaction.settle(batch, options); await this.settlementStorage.pushSettlement(settlement); @@ -205,166 +166,65 @@ export class SettlementModule } // Can't do anything for now - initialize() method use settlementKey. + // TODO Rethink that interface - deploy with addresses as args would be pretty nice public async deploy( - settlementKey: PublicKey, - dispatchKey: PublicKey, - minaBridgeKey: PublicKey, + addresses: { + settlementContract: PublicKey; + dispatchContract?: PublicKey; + }, options: { nonce?: number; } = {} ) { - const feepayer = this.signer.getFeepayerKey(); - - const nonce = options?.nonce ?? 0; - - const sm = this.protocol.dependencyContainer.resolve< - SettlementContractModule - >("SettlementContractModule"); - const { settlement, dispatch } = sm.createContracts({ - settlement: settlementKey, - dispatch: dispatchKey, - }); - - const verificationsKeys = - await this.settlementStartupModule.retrieveVerificationKeys(); - - const permissions = this.baseLayer.isSignedSettlement() - ? new SignedSettlementPermissions() - : new ProvenSettlementPermissions(); - - const tx = await Mina.transaction( - { - sender: feepayer, - nonce, - fee: this.feeStrategy.getFee(), - memo: "Protokit settlement deploy", - }, - async () => { - AccountUpdate.fundNewAccount(feepayer, 2); - - await dispatch.deployAndInitialize( - { - verificationKey: - verificationsKeys.DispatchSmartContract.verificationKey, - }, - permissions.dispatchContract(), - settlement.address - ); - - await settlement.deployAndInitialize( - { - verificationKey: - verificationsKeys.SettlementSmartContract.verificationKey, - }, - permissions.settlementContract(), - feepayer, - dispatchKey - ); - } - ); - - this.utils.signTransaction(tx, { - signingWithSignatureCheck: [...this.signer.getContractAddresses()], - }); - // Note: We can't use this.signTransaction on the above tx - - // This should already apply the tx result to the - // cached accounts / local blockchain - await this.transactionSender.proveAndSendTransaction(tx, "included"); - - await this.utils.fetchContractAccounts(settlement, dispatch); - - const initTx = await Mina.transaction( - { - sender: feepayer, - nonce: nonce + 1, - fee: this.feeStrategy.getFee(), - memo: "Deploy MINA bridge", - }, - async () => { - AccountUpdate.fundNewAccount(feepayer, 1); - // Deploy bridge contract for $Mina - await settlement.addTokenBridge( - TokenId.default, - minaBridgeKey, - dispatchKey - ); - } - ); - - const initTxSigned = this.utils.signTransaction(initTx, { - signingWithSignatureCheck: [ - ...this.signer.getContractAddresses(), - minaBridgeKey, - ], - }); - - await this.transactionSender.proveAndSendTransaction( - initTxSigned, - "included" - ); - } - - public async deployTokenBridge( - owner: TokenContract, - ownerPublicKey: PublicKey, - contractKey: PublicKey, - options: { - nonce?: number; - } - ) { - const feepayer = this.signer.getFeepayerKey(); - const nonce = options?.nonce ?? undefined; - - const tokenId = owner.deriveTokenId(); - const { settlement, dispatch } = this.getContracts(); - - const tx = await Mina.transaction( - { - sender: feepayer, - nonce: nonce, - memo: `Deploy token bridge for ${truncate(tokenId.toString(), { length: 6 })}`, - fee: this.feeStrategy.getFee(), - }, - async () => { - AccountUpdate.fundNewAccount(feepayer, 1); - await settlement.addTokenBridge(tokenId, contractKey, dispatch.address); - await owner.approveAccountUpdate(settlement.self); - } - ); - - // Only ContractKeys and OwnerKey for check. - // Used all in signing process. - const txSigned = this.utils.signTransaction(tx, { - signingWithSignatureCheck: [ - ...this.signer.getContractAddresses(), - ownerPublicKey, - ], - signingPublicKeys: [contractKey], - }); - - await this.transactionSender.proveAndSendTransaction(txSigned, "included"); + const bridgingModule = this.bridgingModule(); + // TODO Add overwriting in dependency factories and then resolve based on that here + const interaction = + bridgingModule !== undefined + ? this.parentContainer.dependencyContainer.resolve( + BridgingDeployInteraction + ) + : this.parentContainer.dependencyContainer.resolve( + VanillaDeployInteraction + ); + + await interaction.deploy(addresses, options); } public async start(): Promise { - const contractArgs = SettlementSmartContractBase.args; + const contractArgs = + this.argsRegistry.getArgs("SettlementContract"); - SettlementSmartContractBase.args = { + this.argsRegistry.setArgs("SettlementContract", { ...contractArgs, signedSettlements: this.baseLayer.isSignedSettlement(), - // TODO Add distinction between mina and custom tokens - BridgeContractPermissions: (this.baseLayer.isSignedSettlement() - ? new SignedSettlementPermissions() - : new ProvenSettlementPermissions() - ).bridgeContractMina(), - }; + }); + + const settlementContractAddress = this.config.addresses?.SettlementContract; + if (settlementContractAddress !== undefined) { + this.addressRegistry.addContractAddress( + "SettlementContract", + settlementContractAddress + ); + + await this.checkDeployment(); + } } public async checkDeployment( tokenBridges?: Array<{ address: PublicKey; tokenId: Field }> ): Promise { + const addresses = [ + this.addressRegistry.getContractAddress("SettlementContract")!, + ]; + + const bridgeContractAddress = + this.bridgingModule()?.config.addresses?.DispatchContract; + if (bridgeContractAddress !== undefined) { + addresses.push(bridgeContractAddress); + } + const contracts: Array<{ address: PublicKey; tokenId?: Field }> = [ - ...this.getContractAddresses().map((addr) => ({ address: addr })), + ...addresses.map((addr) => ({ address: addr })), ...(tokenBridges ?? []), ]; @@ -405,3 +265,5 @@ export class SettlementModule } } } + +SettlementModule satisfies DependencyFactory; diff --git a/packages/sequencer/src/settlement/interactions/AddressRegistry.ts b/packages/sequencer/src/settlement/interactions/AddressRegistry.ts new file mode 100644 index 000000000..cf88cc90d --- /dev/null +++ b/packages/sequencer/src/settlement/interactions/AddressRegistry.ts @@ -0,0 +1,19 @@ +import { PublicKey } from "o1js"; + +export interface AddressRegistry { + getContractAddress(identifier: string): PublicKey | undefined; + + addContractAddress(identifier: string, address: PublicKey): void; +} + +export class InMemoryAddressRegistry implements AddressRegistry { + addresses: Record = {}; + + addContractAddress(identifier: string, address: PublicKey): void { + this.addresses[identifier] = address; + } + + getContractAddress(identifier: string): PublicKey { + return this.addresses[identifier]; + } +} diff --git a/packages/sequencer/src/settlement/interactions/DeployInteraction.ts b/packages/sequencer/src/settlement/interactions/DeployInteraction.ts new file mode 100644 index 000000000..e2501d866 --- /dev/null +++ b/packages/sequencer/src/settlement/interactions/DeployInteraction.ts @@ -0,0 +1,11 @@ +import { PublicKey } from "o1js"; + +export interface DeployInteraction { + deploy( + addresses: { + settlementContract: PublicKey; + dispatchContract: PublicKey; + }, + options?: { nonce?: number } + ): void; +} diff --git a/packages/sequencer/src/settlement/interactions/SettleInteraction.ts b/packages/sequencer/src/settlement/interactions/SettleInteraction.ts new file mode 100644 index 000000000..3291df213 --- /dev/null +++ b/packages/sequencer/src/settlement/interactions/SettleInteraction.ts @@ -0,0 +1,11 @@ +import { SettleableBatch } from "../../storage/model/Batch"; +import { Settlement } from "../../storage/model/Settlement"; + +export interface SettleInteraction { + settle( + batch: SettleableBatch, + options: { + nonce?: number; + } + ): Promise; +} diff --git a/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts new file mode 100644 index 000000000..a75afc5c2 --- /dev/null +++ b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts @@ -0,0 +1,131 @@ +import { inject, injectable } from "tsyringe"; +import { AccountUpdate, Mina, PublicKey } from "o1js"; +import { + BridgingSettlementModulesRecord, + MandatoryProtocolModulesRecord, + Protocol, + SettlementContractModule, +} from "@proto-kit/protocol"; +import { O1PublicKeyOption } from "@proto-kit/common"; + +import { DeployInteraction } from "../DeployInteraction"; +import { AddressRegistry } from "../AddressRegistry"; +import { MinaSigner } from "../../MinaSigner"; +import { MinaBaseLayer } from "../../../protocol/baselayer/MinaBaseLayer"; +import { SettlementStartupModule } from "../../../sequencer/SettlementStartupModule"; +import { SignedSettlementPermissions } from "../../permissions/SignedSettlementPermissions"; +import { ProvenSettlementPermissions } from "../../permissions/ProvenSettlementPermissions"; +import { FeeStrategy } from "../../../protocol/baselayer/fees/FeeStrategy"; +import { MinaTransactionSender } from "../../transactions/MinaTransactionSender"; +import { SettlementUtils } from "../../utils/SettlementUtils"; + +@injectable() +export class BridgingDeployInteraction implements DeployInteraction { + public constructor( + @inject("AddressRegistry") + private readonly addressRegistry: AddressRegistry, + @inject("SettlementSigner") private readonly signer: MinaSigner, + @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, + @inject("Protocol") + private readonly protocol: Protocol, + private readonly settlementStartupModule: SettlementStartupModule, + @inject("FeeStrategy") + private readonly feeStrategy: FeeStrategy, + @inject("TransactionSender") + private readonly transactionSender: MinaTransactionSender + ) {} + + protected settlementContractModule(): SettlementContractModule { + return this.protocol.dependencyContainer.resolve( + "SettlementContractModule" + ); + } + + public async deploy( + { + dispatchContract: dispatchKey, + settlementContract: settlementKey, + }: { + settlementContract: PublicKey; + dispatchContract?: PublicKey; + }, + options: { + nonce?: number; + } = {} + ) { + if (dispatchKey === undefined) { + throw new Error("DispatchContract address not provided"); + } + + const feepayer = this.signer.getFeepayerKey(); + + const nonce = options?.nonce ?? 0; + + // TODO Move this workflow to AddressRegistry + const sm = this.settlementContractModule(); + const { + SettlementContract: settlementContract, + DispatchContract: dispatchContract, + } = sm.createContracts({ + SettlementContract: settlementKey, + DispatchContract: dispatchKey, + }); + + const verificationsKeys = + await this.settlementStartupModule.retrieveVerificationKeys(); + + const utils = new SettlementUtils(this.baseLayer, this.signer); + await utils.fetchContractAccounts(settlementContract, dispatchContract); + + const permissions = this.baseLayer.isSignedSettlement() + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions(); + + const tx = await Mina.transaction( + { + sender: feepayer, + nonce, + fee: this.feeStrategy.getFee(), + memo: "Protokit settlement deploy", + }, + async () => { + AccountUpdate.fundNewAccount(feepayer, 2); + + await dispatchContract.deployAndInitialize( + { + verificationKey: + verificationsKeys.DispatchSmartContract.verificationKey, + }, + permissions.dispatchContract(), + settlementContract.address + ); + + await settlementContract.deployAndInitialize( + { + verificationKey: + verificationsKeys.SettlementSmartContract.verificationKey, + }, + permissions.settlementContract(), + feepayer, + O1PublicKeyOption.from(dispatchKey) + ); + } + ); + + utils.signTransaction(tx, { + signingWithSignatureCheck: [...this.signer.getContractAddresses()], + }); + // this.signer.signTx(tx); + // Note: We can't use this.signTransaction on the above tx + + // This should already apply the tx result to the + // cached accounts / local blockchain + await this.transactionSender.proveAndSendTransaction(tx, "included"); + + this.addressRegistry.addContractAddress( + "SettlementContract", + settlementKey + ); + this.addressRegistry.addContractAddress("DispatchContract", dispatchKey); + } +} diff --git a/packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts b/packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts new file mode 100644 index 000000000..62b34c106 --- /dev/null +++ b/packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts @@ -0,0 +1,127 @@ +import { inject, injectable } from "tsyringe"; +import { Mina } from "o1js"; +import { + BATCH_SIGNATURE_PREFIX, + BridgingSettlementModulesRecord, + DynamicBlockProof, + MandatoryProtocolModulesRecord, + Protocol, + SettlementContractModule, +} from "@proto-kit/protocol"; +import { log } from "@proto-kit/common"; + +import { AddressRegistry } from "../AddressRegistry"; +import { MinaSigner } from "../../MinaSigner"; +import { MinaBaseLayer } from "../../../protocol/baselayer/MinaBaseLayer"; +import { FeeStrategy } from "../../../protocol/baselayer/fees/FeeStrategy"; +import { MinaTransactionSender } from "../../transactions/MinaTransactionSender"; +import { SettlementUtils } from "../../utils/SettlementUtils"; +import { BlockProofSerializer } from "../../../protocol/production/tasks/serializers/BlockProofSerializer"; +import { SettleableBatch } from "../../../storage/model/Batch"; +import { Settlement } from "../../../storage/model/Settlement"; +import { SettleInteraction } from "../SettleInteraction"; + +@injectable() +export class BridgingSettlementInteraction implements SettleInteraction { + public constructor( + @inject("AddressRegistry") + private readonly addressRegistry: AddressRegistry, + @inject("SettlementSigner") private readonly signer: MinaSigner, + @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, + @inject("Protocol") + private readonly protocol: Protocol, + @inject("FeeStrategy") + private readonly feeStrategy: FeeStrategy, + @inject("TransactionSender") + private readonly transactionSender: MinaTransactionSender, + private readonly blockProofSerializer: BlockProofSerializer + ) {} + + protected settlementContractModule(): SettlementContractModule { + return this.protocol.dependencyContainer.resolve( + "SettlementContractModule" + ); + } + + public async settle( + batch: SettleableBatch, + options: { + nonce?: number; + } = {} + ): Promise { + const feepayer = this.signer.getFeepayerKey(); + + const settlementKey = + this.addressRegistry.getContractAddress("SettlementContract"); + const dispatchKey = + this.addressRegistry.getContractAddress("DispatchContract"); + + if (settlementKey === undefined || dispatchKey === undefined) { + throw new Error( + "Settlement and/or DispatchContract addresses haven't been initialized" + ); + } + + // TODO Move this workflow to AddressRegistry + const sm = this.settlementContractModule(); + const { + SettlementContract: settlementContract, + DispatchContract: dispatchContract, + } = sm.createContracts({ + SettlementContract: settlementKey, + DispatchContract: dispatchKey, + }); + + const utils = new SettlementUtils(this.baseLayer, this.signer); + await utils.fetchContractAccounts(settlementContract, dispatchContract); + + const lastSettlementL1BlockHeight = + settlementContract.lastSettlementL1BlockHeight.get().value; + const signature = this.signer.sign([ + BATCH_SIGNATURE_PREFIX, + lastSettlementL1BlockHeight, + ]); + + const latestSequenceStateHash = dispatchContract.account.actionState.get(); + + const blockProof = await this.blockProofSerializer + .getBlockProofSerializer() + .fromJSONProof(batch.proof); + + const dynamicBlockProof = DynamicBlockProof.fromProof(blockProof); + + const tx = await Mina.transaction( + { + sender: feepayer, + nonce: options?.nonce, + fee: this.feeStrategy.getFee(), + memo: "Protokit settle", + }, + async () => { + await settlementContract.settle( + dynamicBlockProof, + signature, + feepayer, + batch.fromNetworkState, + batch.toNetworkState, + latestSequenceStateHash + ); + } + ); + + utils.signTransaction(tx, { + signingWithSignatureCheck: this.signer.getContractAddresses(), + }); + + const { hash: transactionHash } = + await this.transactionSender.proveAndSendTransaction(tx, "included"); + + log.info("Settlement transaction sent and included"); + + return { + batches: [batch.height], + promisedMessagesHash: latestSequenceStateHash.toString(), + transactionHash, + }; + } +} diff --git a/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts new file mode 100644 index 000000000..054882db0 --- /dev/null +++ b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts @@ -0,0 +1,120 @@ +import { inject, injectable } from "tsyringe"; +import { AccountUpdate, Mina, PublicKey } from "o1js"; +import { + MandatoryProtocolModulesRecord, + MandatorySettlementModulesRecord, + Protocol, + SettlementContractModule, +} from "@proto-kit/protocol"; +import { log, O1PublicKeyOption } from "@proto-kit/common"; + +import { DeployInteraction } from "../DeployInteraction"; +import { AddressRegistry } from "../AddressRegistry"; +import { MinaSigner } from "../../MinaSigner"; +import { MinaBaseLayer } from "../../../protocol/baselayer/MinaBaseLayer"; +import { SettlementStartupModule } from "../../../sequencer/SettlementStartupModule"; +import { SignedSettlementPermissions } from "../../permissions/SignedSettlementPermissions"; +import { ProvenSettlementPermissions } from "../../permissions/ProvenSettlementPermissions"; +import { FeeStrategy } from "../../../protocol/baselayer/fees/FeeStrategy"; +import { MinaTransactionSender } from "../../transactions/MinaTransactionSender"; +import { SettlementUtils } from "../../utils/SettlementUtils"; + +@injectable() +export class VanillaDeployInteraction implements DeployInteraction { + public constructor( + @inject("AddressRegistry") + private readonly addressRegistry: AddressRegistry, + @inject("SettlementSigner") private readonly signer: MinaSigner, + @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, + @inject("Protocol") + private readonly protocol: Protocol, + private readonly settlementStartupModule: SettlementStartupModule, + @inject("FeeStrategy") + private readonly feeStrategy: FeeStrategy, + @inject("TransactionSender") + private readonly transactionSender: MinaTransactionSender + ) {} + + protected settlementContractModule(): SettlementContractModule { + return this.protocol.dependencyContainer.resolve( + "SettlementContractModule" + ); + } + + public async deploy( + { + settlementContract: settlementKey, + dispatchContract: dispatchKey, + }: { + settlementContract: PublicKey; + dispatchContract?: PublicKey; + }, + options: { + nonce?: number; + } = {} + ) { + if (dispatchKey !== undefined) { + log.error( + "DispatchContract address provided for deploy(), however the module configuration hints at a " + + "settlement-only deployment, therefore the DispatchContract will not be deployed" + ); + } + + const feepayer = this.signer.getFeepayerKey(); + + const nonce = options?.nonce ?? 0; + + // TODO Move this workflow to AddressRegistry + const sm = this.settlementContractModule(); + const { SettlementContract: settlementContract } = sm.createContracts({ + SettlementContract: settlementKey, + }); + + const utils = new SettlementUtils(this.baseLayer, this.signer); + await utils.fetchContractAccounts(settlementContract); + + const verificationsKeys = + await this.settlementStartupModule.retrieveVerificationKeys(); + + const permissions = this.baseLayer.isSignedSettlement() + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions(); + + const tx = await Mina.transaction( + { + sender: feepayer, + nonce, + fee: this.feeStrategy.getFee(), + memo: "Protokit settlement deploy", + }, + async () => { + AccountUpdate.fundNewAccount(feepayer, 2); + + await settlementContract.deployAndInitialize( + { + verificationKey: + verificationsKeys.SettlementSmartContract.verificationKey, + }, + permissions.settlementContract(), + feepayer, + O1PublicKeyOption.none() + ); + } + ); + + utils.signTransaction(tx, { + signingWithSignatureCheck: [...this.signer.getContractAddresses()], + }); + // this.signer.signTx(tx); + // Note: We can't use this.signTransaction on the above tx + + // This should already apply the tx result to the + // cached accounts / local blockchain + await this.transactionSender.proveAndSendTransaction(tx, "included"); + + this.addressRegistry.addContractAddress( + "SettlementContract", + settlementKey + ); + } +} diff --git a/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts b/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts new file mode 100644 index 000000000..35f3ebb53 --- /dev/null +++ b/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts @@ -0,0 +1,118 @@ +import { inject } from "tsyringe"; +import { Field, Mina } from "o1js"; +import { + BATCH_SIGNATURE_PREFIX, + BridgingSettlementModulesRecord, + DynamicBlockProof, + MandatoryProtocolModulesRecord, + Protocol, + SettlementContractModule, +} from "@proto-kit/protocol"; +import { log } from "@proto-kit/common"; + +import { AddressRegistry } from "../AddressRegistry"; +import { MinaSigner } from "../../MinaSigner"; +import { MinaBaseLayer } from "../../../protocol/baselayer/MinaBaseLayer"; +import { FeeStrategy } from "../../../protocol/baselayer/fees/FeeStrategy"; +import { MinaTransactionSender } from "../../transactions/MinaTransactionSender"; +import { SettlementUtils } from "../../utils/SettlementUtils"; +import { BlockProofSerializer } from "../../../protocol/production/tasks/serializers/BlockProofSerializer"; +import { SettleableBatch } from "../../../storage/model/Batch"; +import { Settlement } from "../../../storage/model/Settlement"; +import { SettleInteraction } from "../SettleInteraction"; + +export class VanillaSettlementInteraction implements SettleInteraction { + public constructor( + @inject("AddressRegistry") + private readonly addressRegistry: AddressRegistry, + @inject("SettlementSigner") private readonly signer: MinaSigner, + @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, + @inject("Protocol") + private readonly protocol: Protocol, + @inject("FeeStrategy") + private readonly feeStrategy: FeeStrategy, + @inject("TransactionSender") + private readonly transactionSender: MinaTransactionSender, + private readonly blockProofSerializer: BlockProofSerializer + ) {} + + protected settlementContractModule(): SettlementContractModule { + return this.protocol.dependencyContainer.resolve( + "SettlementContractModule" + ); + } + + public async settle( + batch: SettleableBatch, + options: { + nonce?: number; + } = {} + ): Promise { + const feepayer = this.signer.getFeepayerKey(); + + const settlementKey = + this.addressRegistry.getContractAddress("SettlementContract"); + + if (settlementKey === undefined) { + throw new Error("Settlement addresses haven't been initialized"); + } + + // TODO Move this workflow to AddressRegistry + const sm = this.settlementContractModule(); + const { SettlementContract: settlementContract } = sm.createContracts({ + SettlementContract: settlementKey, + }); + + const utils = new SettlementUtils(this.baseLayer, this.signer); + await utils.fetchContractAccounts(settlementContract); + + const lastSettlementL1BlockHeight = + settlementContract.lastSettlementL1BlockHeight.get().value; + const signature = this.signer.sign([ + BATCH_SIGNATURE_PREFIX, + lastSettlementL1BlockHeight, + ]); + + const latestSequenceStateHash = Field(0); + + const blockProof = await this.blockProofSerializer + .getBlockProofSerializer() + .fromJSONProof(batch.proof); + + const dynamicBlockProof = DynamicBlockProof.fromProof(blockProof); + + const tx = await Mina.transaction( + { + sender: feepayer, + nonce: options?.nonce, + fee: this.feeStrategy.getFee(), + memo: "Protokit settle", + }, + async () => { + await settlementContract.settle( + dynamicBlockProof, + signature, + feepayer, + batch.fromNetworkState, + batch.toNetworkState, + latestSequenceStateHash + ); + } + ); + + utils.signTransaction(tx, { + signingWithSignatureCheck: this.signer.getContractAddresses(), + }); + + const { hash: transactionHash } = + await this.transactionSender.proveAndSendTransaction(tx, "included"); + + log.info("Settlement transaction sent and included"); + + return { + batches: [batch.height], + promisedMessagesHash: latestSequenceStateHash.toString(), + transactionHash, + }; + } +} diff --git a/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts b/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts index 509500df3..a7aef8bd7 100644 --- a/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts +++ b/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts @@ -5,7 +5,7 @@ import { SettlementStorage } from "../../storage/repositories/SettlementStorage" import { MessageStorage } from "../../storage/repositories/MessageStorage"; import { BlockStorage } from "../../storage/repositories/BlockStorage"; import { PendingTransaction } from "../../mempool/PendingTransaction"; -import type { SettlementModule } from "../SettlementModule"; +import { BridgingModule } from "../BridgingModule"; import { IncomingMessageAdapter } from "./IncomingMessageAdapter"; @@ -20,8 +20,8 @@ export class IncomingMessagesService { private readonly messagesAdapter: IncomingMessageAdapter, @inject("BlockStorage") private readonly blockStorage: BlockStorage, - @inject("SettlementModule") - private readonly settlementModule: SettlementModule + @inject("BridgingModule") + private readonly bridgingModule: BridgingModule ) {} private async fetchRemaining( @@ -29,7 +29,7 @@ export class IncomingMessagesService { toMessagesHash: string ) { const dispatchContractAddress = - this.settlementModule.getAddresses().dispatch; + this.bridgingModule.getDispatchContractAddress(); const fetched = await this.messagesAdapter.fetchPendingMessages( dispatchContractAddress, diff --git a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts index 3c53dfd44..1ff4c0373 100644 --- a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts +++ b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts @@ -9,9 +9,10 @@ import { } from "@proto-kit/common"; import { inject, injectable } from "tsyringe"; import { + BridgingSettlementContractArgs, + ContractArgsRegistry, RuntimeVerificationKeyRootService, SettlementContractModule, - SettlementSmartContractBase, } from "@proto-kit/protocol"; import { VerificationKey } from "o1js"; @@ -48,7 +49,8 @@ export class WorkerRegistrationTask public constructor( @inject("Protocol") private readonly protocol: ModuleContainerLike, - private readonly compileRegistry: CompileRegistry + private readonly compileRegistry: CompileRegistry, + private readonly contractArgsRegistry: ContractArgsRegistry ) { super(); } @@ -78,7 +80,7 @@ export class WorkerRegistrationTask this.protocol.dependencyContainer .resolve< SettlementContractModule< - ReturnType + ReturnType > >("SettlementContractModule") .resolve("SettlementContract") @@ -86,13 +88,19 @@ export class WorkerRegistrationTask } if (input.bridgeContractVerificationKey !== undefined) { - SettlementSmartContractBase.args.BridgeContractVerificationKey = - input.bridgeContractVerificationKey; + const args = + this.contractArgsRegistry.getArgs( + "SettlementContract" + )!; + args.BridgeContractVerificationKey = input.bridgeContractVerificationKey; } if (input.isSignedSettlement !== undefined) { - const contractArgs = SettlementSmartContractBase.args; - SettlementSmartContractBase.args = { + const contractArgs = + this.contractArgsRegistry.getArgs( + "SettlementContract" + )!; + this.contractArgsRegistry.setArgs("SettlementContract", { ...contractArgs, signedSettlements: input.isSignedSettlement, // TODO Add distinction between mina and custom tokens @@ -100,7 +108,7 @@ export class WorkerRegistrationTask ? new SignedSettlementPermissions() : new ProvenSettlementPermissions() ).bridgeContractMina(), - }; + }); } this.compileRegistry.addArtifactsRaw(input.compiledArtifacts); diff --git a/packages/sequencer/test/integration/Proven.test.ts b/packages/sequencer/test/integration/Proven.test.ts index 6de1a5517..86ad46bd0 100644 --- a/packages/sequencer/test/integration/Proven.test.ts +++ b/packages/sequencer/test/integration/Proven.test.ts @@ -9,11 +9,11 @@ import { import { Runtime } from "@proto-kit/module"; import { BridgeContract, + BridgingSettlementContract, + ContractArgsRegistry, DispatchSmartContract, Protocol, SettlementContractModule, - SettlementSmartContract, - SettlementSmartContractBase, } from "@proto-kit/protocol"; import { VanillaProtocolModules } from "@proto-kit/library"; import { container } from "tsyringe"; @@ -66,7 +66,8 @@ describe.skip("Proven", () => { ProtocolStateTestHook, // ProtocolStateTestHook2, }), - SettlementContractModule: SettlementContractModule.with({ + SettlementContractModule: SettlementContractModule.from({ + ...SettlementContractModule.settlementAndBridging(), // FungibleToken: FungibleTokenContractModule, // FungibleTokenAdmin: FungibleTokenAdminContractModule, }), @@ -103,10 +104,7 @@ describe.skip("Proven", () => { type: "local", }, }, - SettlementModule: { - // TODO - feepayer: PrivateKey.random(), - }, + SettlementModule: {}, }, Runtime: { Balances: {}, @@ -172,7 +170,8 @@ describe.skip("Proven", () => { }, }); vkService.setCompileRegistry(registry); - SettlementSmartContractBase.args = { + + container.resolve(ContractArgsRegistry).setArgs("SettlementContract", { DispatchContract: DispatchSmartContract, ChildVerificationKeyService: vkService, BridgeContractVerificationKey: MOCK_VERIFICATION_KEY, @@ -182,8 +181,8 @@ describe.skip("Proven", () => { BridgeContractPermissions: new ProvenSettlementPermissions().bridgeContractMina(), escapeHatchSlotsInterval: 1000, - }; - const vk = await SettlementSmartContract.compile(); + }); + const vk = await BridgingSettlementContract.compile(); console.log(vk.verificationKey); } catch (e) { console.error(e); diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index cfc97c045..6cad77ff8 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -3,17 +3,19 @@ import { mapSequential, TypedClass, LinkedMerkleTree, + log, } from "@proto-kit/common"; import { VanillaProtocolModules } from "@proto-kit/library"; import { Runtime } from "@proto-kit/module"; import { BlockProverPublicInput, BridgeContract, + ContractArgsRegistry, + DispatchSmartContract, NetworkState, Protocol, ReturnType, SettlementContractModule, - SettlementSmartContractBase, } from "@proto-kit/protocol"; import { ClientAppChain, @@ -129,6 +131,7 @@ export const settlementTestFn = ( { BaseLayer: MinaBaseLayer, SettlementModule: SettlementModule, + BridgingModule: BridgingModule, SettlementSigner: InMemoryMinaSigner, }, { @@ -143,7 +146,8 @@ export const settlementTestFn = ( Protocol: Protocol.from({ ...VanillaProtocolModules.mandatoryModules({}), - SettlementContractModule: SettlementContractModule.with({ + SettlementContractModule: SettlementContractModule.from({ + ...SettlementContractModule.settlementAndBridging(), FungibleToken: FungibleTokenContractModule, FungibleTokenAdmin: FungibleTokenAdminContractModule, }), @@ -170,7 +174,9 @@ export const settlementTestFn = ( BlockTrigger: {}, Mempool: {}, BatchProducerModule: {}, - LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + LocalTaskWorkerModule: { + ...VanillaTaskWorkerModules.defaultConfig(), + }, BaseLayer: baseLayerConfig, SettlementSigner: { feepayer: sequencerKey, @@ -184,6 +190,7 @@ export const settlementTestFn = ( BlockProducerModule: {}, FeeStrategy: {}, SettlementModule: {}, + BridgingModule: {}, SequencerStartupModule: {}, TaskQueue: { @@ -262,6 +269,8 @@ export const settlementTestFn = ( } beforeAll(async () => { + log.setLevel("INFO"); + appChain = setupAppChain(); await appChain.start( @@ -302,8 +311,9 @@ export const settlementTestFn = ( }, timeout * 3); afterAll(async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - SettlementSmartContractBase.args = undefined as any; + container + .resolve(ContractArgsRegistry) + .setArgs("SettlementContract", undefined); await appChain.close(); }); @@ -313,37 +323,55 @@ export const settlementTestFn = ( let acc0L2Nonce = 0; it("should throw error", async () => { - const deploymentPromise = + const additionalAddresses = tokenConfig === undefined - ? settlementModule.checkDeployment() - : settlementModule.checkDeployment([ + ? undefined + : [ { address: tokenBridgeKey.toPublicKey(), tokenId: tokenOwner!.deriveTokenId(), }, - ]); + ]; - await expect(deploymentPromise).rejects.toThrow(); + await expect( + settlementModule.checkDeployment(additionalAddresses) + ).rejects.toThrow(); }); it( - "should deploy", + "should deploy settlement contracts", async () => { // Deploy contract await settlementModule.deploy( - settlementKey.toPublicKey(), - dispatchKey.toPublicKey(), - minaBridgeKey.toPublicKey(), + { + dispatchContract: dispatchKey.toPublicKey(), + settlementContract: settlementKey.toPublicKey(), + }, { nonce: nonceCounter, } ); - nonceCounter += 2; + nonceCounter += 1; console.log("Deployed"); }, - timeout * 2 + timeout + ); + + it( + "should deploy mina bridge", + async () => { + // Deploy contract + await bridgingModule.deployMinaBridge(minaBridgeKey.toPublicKey(), { + nonce: nonceCounter, + }); + + nonceCounter += 1; + + console.log("Deployed mina bridge"); + }, + timeout ); if (tokenConfig !== undefined) { @@ -398,7 +426,8 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenOwnerPubKeys.tokenOwner, tokenOwnerPubKeys.admin, - ...settlementModule.getContractAddresses(), + settlementModule.getSettlementContractAddress(), + bridgingModule.getDispatchContractAddress(), ], }); @@ -457,9 +486,8 @@ export const settlementTestFn = ( it( "should deploy custom token bridge", async () => { - await settlementModule.deployTokenBridge( + await bridgingModule.deployTokenBridge( tokenOwner!, - tokenOwnerPubKeys.tokenOwner, tokenBridgeKey.toPublicKey(), { nonce: nonceCounter++, @@ -500,9 +528,9 @@ export const settlementTestFn = ( console.log("Block settled"); await settlementModule.utils.fetchContractAccounts({ - address: settlementModule.getAddresses().settlement, + address: settlementModule.getSettlementContractAddress(), }); - const { settlement } = settlementModule.getContracts(); + const settlement = settlementModule.getSettlementContract(); expectDefined(lastBlock); expectDefined(lastBlock.result); expect(settlement.networkStateHash.get().toString()).toStrictEqual( @@ -526,7 +554,9 @@ export const settlementTestFn = ( "should include deposit", async () => { try { - const { settlement, dispatch } = settlementModule.getContracts(); + const settlement = settlementModule.getSettlementContract(); + const dispatch = + bridgingModule.getDispatchContract() as DispatchSmartContract; const bridge = new BridgeContract( tokenBridgeKey.toPublicKey(), bridgedTokenId @@ -579,7 +609,7 @@ export const settlementTestFn = ( settlementModule.utils.signTransaction(tx, { signingWithSignatureCheck: [ tokenOwnerPubKeys.tokenOwner, - ...settlementModule.getContractAddresses(), + settlementModule.getSettlementContractAddress(), ], signingPublicKeys: [userPublicKey], preventNoncePreconditionFor: [dispatch.address], @@ -770,7 +800,7 @@ export const settlementTestFn = ( signingWithSignatureCheck: [ tokenBridgeKey.toPublicKey(), tokenOwnerPubKeys.tokenOwner, - ...settlementModule.getContractAddresses(), + settlementModule.getSettlementContractAddress(), ], signingPublicKeys: [userPublicKey], }); @@ -804,16 +834,18 @@ export const settlementTestFn = ( expect.assertions(1); // Obtain promise of deployment check - const deploymentCheckPromise = + const additionalAddresses = tokenConfig === undefined - ? settlementModule.checkDeployment() - : settlementModule.checkDeployment([ + ? undefined + : [ { address: tokenBridgeKey.toPublicKey(), tokenId: tokenOwner!.deriveTokenId(), }, - ]); + ]; - await expect(deploymentCheckPromise).resolves.toBeUndefined(); + await expect( + settlementModule.checkDeployment(additionalAddresses) + ).resolves.toBeUndefined(); }); }; From e4e4ae64cfda21429680db0a94148083b086cd33 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 17 Dec 2025 22:20:17 +0100 Subject: [PATCH 053/155] Some refactorings --- .../src/settlement/ContractArgsRegistry.ts | 13 ++------- .../settlement/SettlementContractModule.ts | 26 +---------------- .../settlement/BridgingSettlementContract.ts | 29 ------------------- .../contracts/settlement/SettlementBase.ts | 11 ------- .../bridging/BridgingDeployInteraction.ts | 4 --- .../vanilla/VanillaDeployInteraction.ts | 4 --- .../sequencer/test/settlement/Settlement.ts | 3 -- 7 files changed, 3 insertions(+), 87 deletions(-) diff --git a/packages/protocol/src/settlement/ContractArgsRegistry.ts b/packages/protocol/src/settlement/ContractArgsRegistry.ts index d0b733645..d6c8e5e35 100644 --- a/packages/protocol/src/settlement/ContractArgsRegistry.ts +++ b/packages/protocol/src/settlement/ContractArgsRegistry.ts @@ -2,8 +2,6 @@ import { injectable, singleton } from "tsyringe"; export interface StaticInitializationContract { getInitializationArgs(): Args; - - // name: string; } @injectable() @@ -11,18 +9,11 @@ export interface StaticInitializationContract { export class ContractArgsRegistry { args: Record = {}; - public setArgs( - // contract: TypedClass>, - name: string, - args: Type - ) { + public setArgs(name: string, args: Type) { this.args[name] = args; } - public getArgs( - // contract: TypedClass> - name: string - ): Type | undefined { + public getArgs(name: string): Type | undefined { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return this.args[name] as Type; } diff --git a/packages/protocol/src/settlement/SettlementContractModule.ts b/packages/protocol/src/settlement/SettlementContractModule.ts index 3bfaa778b..7cacb16af 100644 --- a/packages/protocol/src/settlement/SettlementContractModule.ts +++ b/packages/protocol/src/settlement/SettlementContractModule.ts @@ -7,7 +7,7 @@ import { noop, StringKeyOf, } from "@proto-kit/common"; -import { Field, PublicKey, SmartContract } from "o1js"; +import { Field, PublicKey } from "o1js"; import { injectable } from "tsyringe"; import { ProtocolEnvironment } from "../protocol/ProtocolEnvironment"; @@ -123,28 +123,4 @@ export class SettlementContractModule< SettlementModules[ContractName] >; } - - public createContracts< - ContractName extends keyof SettlementModules, - >(addresses: { - [Key in ContractName]: PublicKey; - }): { - [Key in ContractName]: SmartContract & - InferContractType; - } { - const classes = this.getContractClasses(); - - const obj: Record = {}; - // eslint-disable-next-line guard-for-in - for (const key in addresses) { - const ContractClass = classes[key]; - obj[key] = new ContractClass(addresses[key]); - } - - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return obj as { - [Key in keyof SettlementModules]: SmartContract & - InferContractType; - }; - } } diff --git a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts index f5aeee9ff..40c2625f7 100644 --- a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts +++ b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts @@ -51,20 +51,6 @@ export interface BridgingSettlementContractType extends SettlementContractType { addTokenBridge: (tokenId: Field, address: PublicKey) => Promise; } -// @singleton() -// export class SettlementSmartContractStaticArgs { -// public args?: { -// DispatchContract: TypedClass; -// hooks: ProvableSettlementHook[]; -// escapeHatchSlotsInterval: number; -// BridgeContract: TypedClass & typeof SmartContract; -// // Lazily initialized -// BridgeContractVerificationKey: VerificationKey | undefined; -// BridgeContractPermissions: Permissions | undefined; -// signedSettlements: boolean | undefined; -// }; -// } - export interface BridgingSettlementContractArgs extends SettlementContractArgs { DispatchContract: TypedClass; BridgeContract: TypedClass & typeof SmartContract; @@ -78,21 +64,6 @@ export abstract class BridgingSettlementContractBase extends SettlementBase implements StaticInitializationContract { - // This pattern of injecting args into a smartcontract is currently the only - // viable solution that works given the inheritance issues of o1js - // public static args = container.resolve(SettlementSmartContractStaticArgs); - // public static args: { - // DispatchContract: TypedClass; - // hooks: ProvableSettlementHook[]; - // escapeHatchSlotsInterval: number; - // BridgeContract: TypedClass & typeof SmartContract; - // // Lazily initialized - // BridgeContractVerificationKey: VerificationKey | undefined; - // BridgeContractPermissions: Permissions | undefined; - // signedSettlements: boolean | undefined; - // ChildVerificationKeyService: ChildVerificationKeyService; - // }; - public getInitializationArgs(): BridgingSettlementContractArgs { return container .resolve(ContractArgsRegistry) diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts index 0cd5b2beb..6cc920052 100644 --- a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts @@ -91,13 +91,6 @@ export abstract class SettlementBase .resolve(ContractArgsRegistry) .getArgs("SettlementContract")!; } - // - // public static args: { - // hooks: ProvableSettlementHook[]; - // escapeHatchSlotsInterval: number; - // signedSettlements: boolean | undefined; - // ChildVerificationKeyService: ChildVerificationKeyService; - // }; abstract sequencerKey: State; abstract lastSettlementL1BlockHeight: State; @@ -246,10 +239,6 @@ export abstract class SettlementBase this.lastSettlementL1BlockHeight.set(minBlockHeightIncluded); } - - // TODO Move all settlement-only logic here from the old impl } -// TODO Connect the above with the Smartcontract API implementing the abstract class - /* eslint-enable @typescript-eslint/lines-between-class-members */ diff --git a/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts index a75afc5c2..174bd7453 100644 --- a/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts @@ -115,11 +115,7 @@ export class BridgingDeployInteraction implements DeployInteraction { utils.signTransaction(tx, { signingWithSignatureCheck: [...this.signer.getContractAddresses()], }); - // this.signer.signTx(tx); - // Note: We can't use this.signTransaction on the above tx - // This should already apply the tx result to the - // cached accounts / local blockchain await this.transactionSender.proveAndSendTransaction(tx, "included"); this.addressRegistry.addContractAddress( diff --git a/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts index 054882db0..382699478 100644 --- a/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts @@ -105,11 +105,7 @@ export class VanillaDeployInteraction implements DeployInteraction { utils.signTransaction(tx, { signingWithSignatureCheck: [...this.signer.getContractAddresses()], }); - // this.signer.signTx(tx); - // Note: We can't use this.signTransaction on the above tx - // This should already apply the tx result to the - // cached accounts / local blockchain await this.transactionSender.proveAndSendTransaction(tx, "included"); this.addressRegistry.addContractAddress( diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 6cad77ff8..b972f0286 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -3,7 +3,6 @@ import { mapSequential, TypedClass, LinkedMerkleTree, - log, } from "@proto-kit/common"; import { VanillaProtocolModules } from "@proto-kit/library"; import { Runtime } from "@proto-kit/module"; @@ -269,8 +268,6 @@ export const settlementTestFn = ( } beforeAll(async () => { - log.setLevel("INFO"); - appChain = setupAppChain(); await appChain.start( From eb1113506a1b73ed5b1fea077362da59ca303ceb Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 17 Dec 2025 23:15:58 +0100 Subject: [PATCH 054/155] Refactored ArgsRegistry --- .../src/settlement/ContractArgsRegistry.ts | 50 +++++++++++++++++-- .../settlement/SettlementContractModule.ts | 26 +++++++++- .../BridgingSettlementContractModule.ts | 44 ++++++++-------- .../SettlementSmartContractModule.ts | 9 +--- .../settlement/BridgingSettlementContract.ts | 48 +++++------------- .../contracts/settlement/SettlementBase.ts | 13 ++++- .../production/tasks/CircuitCompilerTask.ts | 25 ++++------ .../src/sequencer/SequencerStartupModule.ts | 11 ++-- .../src/settlement/BridgingModule.ts | 23 ++++----- .../src/settlement/SettlementModule.ts | 14 ++---- .../worker/startup/WorkerRegistrationTask.ts | 33 ++++++------ .../sequencer/test/settlement/Settlement.ts | 4 +- 12 files changed, 166 insertions(+), 134 deletions(-) diff --git a/packages/protocol/src/settlement/ContractArgsRegistry.ts b/packages/protocol/src/settlement/ContractArgsRegistry.ts index d6c8e5e35..f9c2f0020 100644 --- a/packages/protocol/src/settlement/ContractArgsRegistry.ts +++ b/packages/protocol/src/settlement/ContractArgsRegistry.ts @@ -1,20 +1,60 @@ import { injectable, singleton } from "tsyringe"; +import merge from "lodash/merge"; export interface StaticInitializationContract { getInitializationArgs(): Args; } +export type NaiveObjectSchema = { + [Key in keyof Obj]: undefined extends Obj[Key] ? "Optional" : "Required"; +}; + +/* +interface Test { + one: string; + two?: string; +} + +const x: NaiveObjectSchema = { + one: "Required", + two: "Optional", +}; +*/ + @injectable() @singleton() export class ContractArgsRegistry { args: Record = {}; - public setArgs(name: string, args: Type) { - this.args[name] = args; + public addArgs(name: string, addition: Partial) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const args: Partial = this.args[name] ?? {}; + this.args[name] = merge(args, addition); } - public getArgs(name: string): Type | undefined { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.args[name] as Type; + public resetArgs(name: string) { + delete this.args[name]; + } + + public getArgs(name: string, schema: NaiveObjectSchema): Type { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const args = this.args[name] ?? {}; + + const missing = Object.entries<"Optional" | "Required">(schema).filter( + ([key, type]) => { + // We filter only if the key is required and isn't present + return type === "Required" && args[key] === undefined; + } + ); + + if (missing.length > 0) { + const missingKeys = missing.map(([key]) => key); + throw new Error( + `Contract args for ${name} not all present, ${missingKeys} are missing` + ); + } else { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return args as Type; + } } } diff --git a/packages/protocol/src/settlement/SettlementContractModule.ts b/packages/protocol/src/settlement/SettlementContractModule.ts index 7cacb16af..3bfaa778b 100644 --- a/packages/protocol/src/settlement/SettlementContractModule.ts +++ b/packages/protocol/src/settlement/SettlementContractModule.ts @@ -7,7 +7,7 @@ import { noop, StringKeyOf, } from "@proto-kit/common"; -import { Field, PublicKey } from "o1js"; +import { Field, PublicKey, SmartContract } from "o1js"; import { injectable } from "tsyringe"; import { ProtocolEnvironment } from "../protocol/ProtocolEnvironment"; @@ -123,4 +123,28 @@ export class SettlementContractModule< SettlementModules[ContractName] >; } + + public createContracts< + ContractName extends keyof SettlementModules, + >(addresses: { + [Key in ContractName]: PublicKey; + }): { + [Key in ContractName]: SmartContract & + InferContractType; + } { + const classes = this.getContractClasses(); + + const obj: Record = {}; + // eslint-disable-next-line guard-for-in + for (const key in addresses) { + const ContractClass = classes[key]; + obj[key] = new ContractClass(addresses[key]); + } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return obj as { + [Key in keyof SettlementModules]: SmartContract & + InferContractType; + }; + } } diff --git a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts index 09b1270e9..bdd8a461b 100644 --- a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts @@ -19,6 +19,7 @@ import { BridgingSettlementContractType, BridgingSettlementContract, BridgingSettlementContractArgs, + BridgingSettlementContractArgsSchema, } from "./settlement/BridgingSettlementContract"; import { BridgeContractBase } from "./BridgeContract"; import { DispatchContractProtocolModule } from "./DispatchContractProtocolModule"; @@ -57,22 +58,16 @@ export class BridgingSettlementContractModule extends ContractModule< const escapeHatchSlotsInterval = config.escapeHatchSlotsInterval ?? DEFAULT_ESCAPE_HATCH; - const args = - this.argsRegistry.getArgs( - "SettlementContract" - ); - const newArgs = { - ...args, - DispatchContract: dispatchContract, - hooks, - escapeHatchSlotsInterval, - BridgeContract: bridgeContract, - BridgeContractVerificationKey: args?.BridgeContractVerificationKey, - BridgeContractPermissions: args?.BridgeContractPermissions, - signedSettlements: args?.signedSettlements, - ChildVerificationKeyService: this.childVerificationKeyService, - }; - this.argsRegistry.setArgs("SettlementContract", newArgs); + this.argsRegistry.addArgs( + "SettlementContract", + { + DispatchContract: dispatchContract, + hooks, + escapeHatchSlotsInterval, + BridgeContract: bridgeContract, + ChildVerificationKeyService: this.childVerificationKeyService, + } + ); // Ideally we don't want to have this cyclic dependency, but we have it in the protocol, // So its logical that we can't avoid that here @@ -95,13 +90,18 @@ export class BridgingSettlementContractModule extends ContractModule< this.contractFactory(); // Init params - const args = - this.argsRegistry.getArgs( - "SettlementContract" - )!; - args.BridgeContractVerificationKey = - bridgeArtifact.BridgeContract.verificationKey; + this.argsRegistry.addArgs( + "SettlementContract", + { + BridgeContractVerificationKey: + bridgeArtifact.BridgeContract.verificationKey, + } + ); + const args = this.argsRegistry.getArgs( + "SettlementContract", + BridgingSettlementContractArgsSchema + ); if (args.signedSettlements === undefined) { throw new Error( "Args not fully initialized - make sure to also include the SettlementModule in the sequencer" diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts index d1afb3a4b..e93a28eaa 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts @@ -50,16 +50,11 @@ export class SettlementSmartContractModule extends ContractModule< const escapeHatchSlotsInterval = config.escapeHatchSlotsInterval ?? DEFAULT_ESCAPE_HATCH; - const args = - this.argsRegistry.getArgs("SettlementContract"); - const newArgs = { - ...args, + this.argsRegistry.addArgs("SettlementContract", { hooks, escapeHatchSlotsInterval, - signedSettlements: args?.signedSettlements, ChildVerificationKeyService: this.childVerificationKeyService, - }; - this.argsRegistry.setArgs("SettlementContract", newArgs); + }); return BridgingSettlementContract; } diff --git a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts index 40c2625f7..7610f86d5 100644 --- a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts +++ b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts @@ -27,6 +27,7 @@ import { TokenBridgeDeploymentAuth } from "../authorizations/TokenBridgeDeployme import { UpdateMessagesHashAuth } from "../authorizations/UpdateMessagesHashAuth"; import { ContractArgsRegistry, + NaiveObjectSchema, StaticInitializationContract, } from "../../ContractArgsRegistry"; @@ -34,6 +35,7 @@ import { DynamicBlockProof, SettlementBase, SettlementContractArgs, + SettlementContractArgsSchema, SettlementContractType, } from "./SettlementBase"; @@ -56,10 +58,18 @@ export interface BridgingSettlementContractArgs extends SettlementContractArgs { BridgeContract: TypedClass & typeof SmartContract; // Lazily initialized BridgeContractVerificationKey: VerificationKey | undefined; - BridgeContractPermissions: Permissions | undefined; - signedSettlements: boolean | undefined; + BridgeContractPermissions: Permissions; } +export const BridgingSettlementContractArgsSchema: NaiveObjectSchema = + { + ...SettlementContractArgsSchema, + DispatchContract: "Required", + BridgeContract: "Required", + BridgeContractVerificationKey: "Optional", + BridgeContractPermissions: "Required", + }; + export abstract class BridgingSettlementContractBase extends SettlementBase implements StaticInitializationContract @@ -67,7 +77,7 @@ export abstract class BridgingSettlementContractBase public getInitializationArgs(): BridgingSettlementContractArgs { return container .resolve(ContractArgsRegistry) - .getArgs("SettlementContract")!; + .getArgs("SettlementContract", BridgingSettlementContractArgsSchema); } events = { @@ -95,32 +105,9 @@ export abstract class BridgingSettlementContractBase this.dispatchContractAddress.set(dispatchContract); } - // TODO Like these properties, I am too lazy to properly infer the types here - private assertLazyConfigsInitialized() { - const uninitializedProperties: string[] = []; - const args = this.getInitializationArgs(); - if (args.BridgeContractPermissions === undefined) { - uninitializedProperties.push("BridgeContractPermissions"); - } - if (args.signedSettlements === undefined) { - uninitializedProperties.push("signedSettlements"); - } - if (uninitializedProperties.length > 0) { - throw new Error( - `Lazy configs of SettlementSmartContract haven't been initialized ${uninitializedProperties.reduce( - (a, b) => `${a},${b}` - )}` - ); - } - } - // TODO We should move this to the dispatchcontract eventually - or after mesa // to the combined settlement & dispatch contract protected async deployTokenBridge(tokenId: Field, address: PublicKey) { - Provable.asProver(() => { - this.assertLazyConfigsInitialized(); - }); - const { BridgeContractVerificationKey, signedSettlements, @@ -131,15 +118,6 @@ export abstract class BridgingSettlementContractBase const bridgeContract = new BridgeContractClass(address, tokenId); - if ( - signedSettlements === undefined || - BridgeContractPermissions === undefined - ) { - throw new Error( - "Static arguments for SettlementSmartContract not initialized" - ); - } - if ( BridgeContractVerificationKey !== undefined && !BridgeContractVerificationKey.hash.isConstant() diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts index 6cc920052..cc6401c03 100644 --- a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts @@ -32,6 +32,7 @@ import { } from "../../../prover/block/BlockProvable"; import { ContractArgsRegistry, + NaiveObjectSchema, StaticInitializationContract, } from "../../ContractArgsRegistry"; @@ -78,10 +79,18 @@ export interface SettlementContractType { export interface SettlementContractArgs { hooks: ProvableSettlementHook[]; escapeHatchSlotsInterval: number; - signedSettlements: boolean | undefined; + signedSettlements: boolean; ChildVerificationKeyService: ChildVerificationKeyService; } +export const SettlementContractArgsSchema: NaiveObjectSchema = + { + ChildVerificationKeyService: "Required", + hooks: "Required", + escapeHatchSlotsInterval: "Required", + signedSettlements: "Required", + }; + export abstract class SettlementBase extends TokenContract implements StaticInitializationContract @@ -89,7 +98,7 @@ export abstract class SettlementBase getInitializationArgs(): SettlementContractArgs { return container .resolve(ContractArgsRegistry) - .getArgs("SettlementContract")!; + .getArgs("SettlementContract", SettlementContractArgsSchema); } abstract sequencerKey: State; diff --git a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts index 61d2de0c6..2e14928d8 100644 --- a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts @@ -154,20 +154,17 @@ export class CircuitCompilerTask extends UnpreparingTask< } if (input.isSignedSettlement !== undefined) { - const contractArgs = - this.contractArgsRegistry.getArgs( - "SettlementContract" - ); - const newArgs = { - ...contractArgs, - signedSettlements: input.isSignedSettlement, - // TODO Add distinction between mina and custom tokens - BridgeContractPermissions: (input.isSignedSettlement - ? new SignedSettlementPermissions() - : new ProvenSettlementPermissions() - ).bridgeContractMina(), - }; - this.contractArgsRegistry.setArgs("SettlementContract", newArgs); + this.contractArgsRegistry.addArgs( + "SettlementContract", + { + signedSettlements: input.isSignedSettlement, + // TODO Add distinction between mina and custom tokens + BridgeContractPermissions: (input.isSignedSettlement + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions() + ).bridgeContractMina(), + } + ); } // TODO make adaptive diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index b3fec37ff..603835f7a 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -171,11 +171,12 @@ export class SequencerStartupModule const bridgeVk = protocolBridgeArtifacts.BridgeContract; if (bridgeVk !== undefined) { // TODO Inject CompileRegistry directly - const args = - this.contractArgsRegistry.getArgs( - "SettlementContract" - )!; - args.BridgeContractVerificationKey = bridgeVk.verificationKey; + this.contractArgsRegistry.addArgs( + "SettlementContract", + { + BridgeContractVerificationKey: bridgeVk.verificationKey, + } + ); } await this.registrationFlow.start({ diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 0f1d74fe0..1f5afaeb1 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -660,19 +660,16 @@ export class BridgingModule extends SequencerModule { } public async start(): Promise { - const contractArgs = - this.argsRegistry.getArgs( - "SettlementContract" - ); - - this.argsRegistry.setArgs("SettlementContract", { - ...contractArgs, - // TODO Add distinction between mina and custom tokens - BridgeContractPermissions: (this.baseLayer.isSignedSettlement() - ? new SignedSettlementPermissions() - : new ProvenSettlementPermissions() - ).bridgeContractMina(), - }); + this.argsRegistry.addArgs( + "SettlementContract", + { + // TODO Add distinction between mina and custom tokens + BridgeContractPermissions: (this.baseLayer.isSignedSettlement() + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions() + ).bridgeContractMina(), + } + ); const dispatchAddress = this.config.addresses?.DispatchContract; if (dispatchAddress !== undefined) { diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index a2004d45a..4ab3439d3 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -131,10 +131,10 @@ export class SettlementModule SettlementContractModule >("SettlementContractModule"); - const contracts = settlementContractModule.createContracts({ - SettlementContract: address, - }); - this.contract = contracts.SettlementContract; + this.contract = settlementContractModule.createContract( + "SettlementContract", + address + ); } return this.contract; } @@ -191,11 +191,7 @@ export class SettlementModule } public async start(): Promise { - const contractArgs = - this.argsRegistry.getArgs("SettlementContract"); - - this.argsRegistry.setArgs("SettlementContract", { - ...contractArgs, + this.argsRegistry.addArgs("SettlementContract", { signedSettlements: this.baseLayer.isSignedSettlement(), }); diff --git a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts index 1ff4c0373..30339d0fc 100644 --- a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts +++ b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts @@ -88,27 +88,24 @@ export class WorkerRegistrationTask } if (input.bridgeContractVerificationKey !== undefined) { - const args = - this.contractArgsRegistry.getArgs( - "SettlementContract" - )!; - args.BridgeContractVerificationKey = input.bridgeContractVerificationKey; + this.contractArgsRegistry.addArgs( + "SettlementContract", + { BridgeContractVerificationKey: input.bridgeContractVerificationKey } + ); } if (input.isSignedSettlement !== undefined) { - const contractArgs = - this.contractArgsRegistry.getArgs( - "SettlementContract" - )!; - this.contractArgsRegistry.setArgs("SettlementContract", { - ...contractArgs, - signedSettlements: input.isSignedSettlement, - // TODO Add distinction between mina and custom tokens - BridgeContractPermissions: (input.isSignedSettlement - ? new SignedSettlementPermissions() - : new ProvenSettlementPermissions() - ).bridgeContractMina(), - }); + this.contractArgsRegistry.addArgs( + "SettlementContract", + { + signedSettlements: input.isSignedSettlement, + // TODO Add distinction between mina and custom tokens + BridgeContractPermissions: (input.isSignedSettlement + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions() + ).bridgeContractMina(), + } + ); } this.compileRegistry.addArtifactsRaw(input.compiledArtifacts); diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index b972f0286..f0012e9cc 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -308,9 +308,7 @@ export const settlementTestFn = ( }, timeout * 3); afterAll(async () => { - container - .resolve(ContractArgsRegistry) - .setArgs("SettlementContract", undefined); + container.resolve(ContractArgsRegistry).resetArgs("SettlementContract"); await appChain.close(); }); From d7dc1019da16ee2c8c650a9a044015b6e5239c52 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 18 Dec 2025 12:26:32 +0100 Subject: [PATCH 055/155] Moved bridge addresses to registry --- .../src/settlement/BridgingModule.ts | 32 +++++++++++-------- .../interactions/AddressRegistry.ts | 12 +++++++ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 1f5afaeb1..25ef3fcbb 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -92,13 +92,11 @@ export type BridgingModuleConfig = { */ @injectable() export class BridgingModule extends SequencerModule { + // TODO Eventually, we don't want to store this here either, but build a smarter AddressRegistry private seenBridgeDeployments: { latestDeployment: number; - // tokenId => Bridge address - deployments: Record; } = { latestDeployment: -1, - deployments: {}, }; private utils: SettlementUtils; @@ -184,18 +182,20 @@ export class BridgingModule extends SequencerModule { .map((event) => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const mapping = event.event.data as unknown as TokenMapping; - return [mapping.tokenId.toString(), mapping.publicKey]; + return [mapping.tokenId.toBigInt(), mapping.publicKey] as const; }); - const mergedDeployments = { - ...this.seenBridgeDeployments.deployments, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - ...(Object.fromEntries(tuples) as Record), - }; + + tuples.forEach(([tokenId, publicKey]) => { + this.addressRegistry.addContractAddress( + this.addressRegistry.getIdentifier("BridgeContract", tokenId), + publicKey + ); + }); + const latestDeployment = events .map((event) => Number(event.blockHeight.toString())) .reduce((a, b) => (a > b ? a : b), 0); this.seenBridgeDeployments = { - deployments: mergedDeployments, latestDeployment, }; } @@ -268,14 +268,18 @@ export class BridgingModule extends SequencerModule { public async getBridgeAddress( tokenId: Field ): Promise { - const { deployments } = this.seenBridgeDeployments; + const identifier = this.addressRegistry.getIdentifier( + "BridgeContract", + tokenId.toBigInt() + ); + const deployment = this.addressRegistry.getContractAddress(identifier); - if (Object.keys(deployments).includes(tokenId.toString())) { - return deployments[tokenId.toString()]; + if (deployment !== undefined) { + return deployment; } await this.updateBridgeAddresses(); - return this.seenBridgeDeployments.deployments[tokenId.toString()]; + return this.addressRegistry.getContractAddress(identifier); } public async getDepositContractAttestation(tokenId: Field) { diff --git a/packages/sequencer/src/settlement/interactions/AddressRegistry.ts b/packages/sequencer/src/settlement/interactions/AddressRegistry.ts index cf88cc90d..449f345cd 100644 --- a/packages/sequencer/src/settlement/interactions/AddressRegistry.ts +++ b/packages/sequencer/src/settlement/interactions/AddressRegistry.ts @@ -1,14 +1,22 @@ import { PublicKey } from "o1js"; export interface AddressRegistry { + getIdentifier(name: string, tokenId?: bigint): string; + getContractAddress(identifier: string): PublicKey | undefined; addContractAddress(identifier: string, address: PublicKey): void; + + hasContractAddress(identifier: string): boolean; } export class InMemoryAddressRegistry implements AddressRegistry { addresses: Record = {}; + getIdentifier(name: string, tokenId?: bigint) { + return `${name}-${tokenId}`; + } + addContractAddress(identifier: string, address: PublicKey): void { this.addresses[identifier] = address; } @@ -16,4 +24,8 @@ export class InMemoryAddressRegistry implements AddressRegistry { getContractAddress(identifier: string): PublicKey { return this.addresses[identifier]; } + + hasContractAddress(identifier: string): boolean { + return this.addresses[identifier] !== undefined; + } } From 33dbd45e455dfc987c48a8f6cb1b971c09a58a70 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 18 Dec 2025 12:28:42 +0100 Subject: [PATCH 056/155] Removed not planned todo --- .../interactions/bridging/BridgingDeployInteraction.ts | 1 - .../interactions/bridging/BridgingSettlementInteraction.ts | 1 - .../settlement/interactions/vanilla/VanillaDeployInteraction.ts | 1 - .../interactions/vanilla/VanillaSettlementInteraction.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts index 174bd7453..c1743ae09 100644 --- a/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts @@ -61,7 +61,6 @@ export class BridgingDeployInteraction implements DeployInteraction { const nonce = options?.nonce ?? 0; - // TODO Move this workflow to AddressRegistry const sm = this.settlementContractModule(); const { SettlementContract: settlementContract, diff --git a/packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts b/packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts index 62b34c106..ee8b74983 100644 --- a/packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/bridging/BridgingSettlementInteraction.ts @@ -62,7 +62,6 @@ export class BridgingSettlementInteraction implements SettleInteraction { ); } - // TODO Move this workflow to AddressRegistry const sm = this.settlementContractModule(); const { SettlementContract: settlementContract, diff --git a/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts index 382699478..0d5cec0d6 100644 --- a/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts @@ -64,7 +64,6 @@ export class VanillaDeployInteraction implements DeployInteraction { const nonce = options?.nonce ?? 0; - // TODO Move this workflow to AddressRegistry const sm = this.settlementContractModule(); const { SettlementContract: settlementContract } = sm.createContracts({ SettlementContract: settlementKey, diff --git a/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts b/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts index 35f3ebb53..6f54961a6 100644 --- a/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts @@ -57,7 +57,6 @@ export class VanillaSettlementInteraction implements SettleInteraction { throw new Error("Settlement addresses haven't been initialized"); } - // TODO Move this workflow to AddressRegistry const sm = this.settlementContractModule(); const { SettlementContract: settlementContract } = sm.createContracts({ SettlementContract: settlementKey, From 43143373d07212b2de3ba902303e80171d84decd Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 18 Dec 2025 13:21:35 +0100 Subject: [PATCH 057/155] Moved other contracts to ContractArgsRegistry architecture --- .../settlement/contracts/BridgeContract.ts | 54 +++++++++++++------ .../contracts/BridgeContractProtocolModule.ts | 10 ++-- .../BridgingSettlementContractModule.ts | 31 +++++------ .../DispatchContractProtocolModule.ts | 23 ++++---- .../contracts/DispatchSmartContract.ts | 52 ++++++++++++------ .../SettlementSmartContractModule.ts | 2 +- .../sequencer/test/integration/Proven.test.ts | 25 +++++---- 7 files changed, 121 insertions(+), 76 deletions(-) diff --git a/packages/protocol/src/settlement/contracts/BridgeContract.ts b/packages/protocol/src/settlement/contracts/BridgeContract.ts index 802bdccbf..1771bbfed 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContract.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContract.ts @@ -16,7 +16,7 @@ import { TokenId, VerificationKey, } from "o1js"; -import { noop, range, TypedClass } from "@proto-kit/common"; +import { batch, noop, range, TypedClass } from "@proto-kit/common"; import { container, injectable, singleton } from "tsyringe"; import { @@ -30,6 +30,12 @@ import { OutgoingMessageProcessor } from "../modularity/OutgoingMessageProcessor import { PROTOKIT_FIELD_PREFIXES } from "../../hashing/protokit-prefixes"; import type { BridgingSettlementContractType } from "./settlement/BridgingSettlementContract"; +import { + ContractArgsRegistry, + NaiveObjectSchema, + StaticInitializationContract, +} from "../ContractArgsRegistry"; +import { hash } from "node:crypto"; export type BridgeContractType = { stateRoot: State; @@ -64,19 +70,33 @@ export class BridgeContractContext { } = { messageInputs: [] }; } -export abstract class BridgeContractBase extends TokenContract { - public static args: { - SettlementContract: - | (TypedClass & typeof SmartContract) - | undefined; - messageProcessors: OutgoingMessageProcessor[]; - batchSize?: number; - }; +export interface BridgeContractArgs { + SettlementContract: TypedClass & + typeof SmartContract; + messageProcessors: OutgoingMessageProcessor[]; + batchSize?: number; +} + +export const BridgeContractArgsSchema: NaiveObjectSchema = { + batchSize: "Optional", + SettlementContract: "Required", + messageProcessors: "Required", +}; +export abstract class BridgeContractBase + extends TokenContract + implements StaticInitializationContract +{ public constructor(address: PublicKey, tokenId?: Field) { super(address, tokenId); } + getInitializationArgs(): BridgeContractArgs { + return container + .resolve(ContractArgsRegistry) + .getArgs("BridgeContract", BridgeContractArgsSchema); + } + abstract settlementContractAddress: State; abstract stateRoot: State; @@ -134,14 +154,11 @@ export abstract class BridgeContractBase extends TokenContract { // witness values, not update/insert this.stateRoot.set(root); + const args = this.getInitializationArgs(); + const settlementContractAddress = this.settlementContractAddress.getAndRequireEquals(); - const SettlementContractClass = BridgeContractBase.args.SettlementContract; - if (SettlementContractClass === undefined) { - throw new Error( - "Settlement Contract class hasn't been set yet, something is wrong with your module composition" - ); - } + const SettlementContractClass = args.SettlementContract; const settlementContract = new SettlementContractClass( settlementContractAddress ); @@ -150,11 +167,14 @@ export abstract class BridgeContractBase extends TokenContract { } private batchSize() { - return BridgeContractBase.args.batchSize ?? OUTGOING_MESSAGE_BATCH_SIZE; + return ( + this.getInitializationArgs().batchSize ?? OUTGOING_MESSAGE_BATCH_SIZE + ); } private executeProcessors(batchIndex: number, args: OutgoingMessageArgument) { - return BridgeContractBase.args.messageProcessors.map((processor, j) => { + const { messageProcessors } = this.getInitializationArgs(); + return messageProcessors.map((processor, j) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const value = Experimental.memoizeWitness(processor.type, () => { return container.resolve(BridgeContractContext).data.messageInputs[ diff --git a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts index 1ab75c588..33582903d 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts @@ -6,9 +6,11 @@ import { OutgoingMessageProcessor } from "../modularity/OutgoingMessageProcessor import { BridgeContract, + BridgeContractArgs, BridgeContractBase, BridgeContractType, } from "./BridgeContract"; +import { ContractArgsRegistry } from "../ContractArgsRegistry"; export type BridgeContractConfig = { outgoingBatchSize?: number; @@ -21,7 +23,8 @@ export class BridgeContractProtocolModule extends ContractModule< > { public constructor( @injectAll("OutgoingMessageProcessor", { isOptional: true }) - private readonly messageProcessors: OutgoingMessageProcessor[] + private readonly messageProcessors: OutgoingMessageProcessor[], + private readonly contractArgsRegistry: ContractArgsRegistry ) { super(); } @@ -29,11 +32,10 @@ export class BridgeContractProtocolModule extends ContractModule< public contractFactory() { const { config } = this; - BridgeContractBase.args = { - SettlementContract: BridgeContractBase.args?.SettlementContract, + this.contractArgsRegistry.addArgs("BridgeContract", { messageProcessors: this.messageProcessors, batchSize: config.outgoingBatchSize, - }; + }); return BridgeContract; } diff --git a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts index bdd8a461b..430fde6af 100644 --- a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts @@ -14,14 +14,17 @@ import { import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; import { ContractArgsRegistry } from "../ContractArgsRegistry"; -import { DispatchSmartContractBase } from "./DispatchSmartContract"; +import { + DispatchContractArgs, + DispatchSmartContractBase, +} from "./DispatchSmartContract"; import { BridgingSettlementContractType, BridgingSettlementContract, BridgingSettlementContractArgs, BridgingSettlementContractArgsSchema, } from "./settlement/BridgingSettlementContract"; -import { BridgeContractBase } from "./BridgeContract"; +import { BridgeContractArgs, BridgeContractBase } from "./BridgeContract"; import { DispatchContractProtocolModule } from "./DispatchContractProtocolModule"; import { BridgeContractProtocolModule } from "./BridgeContractProtocolModule"; import { @@ -69,12 +72,14 @@ export class BridgingSettlementContractModule extends ContractModule< } ); - // Ideally we don't want to have this cyclic dependency, but we have it in the protocol, - // So its logical that we can't avoid that here - BridgeContractBase.args.SettlementContract = BridgingSettlementContract; - - DispatchSmartContractBase.args.settlementContractClass = - BridgingSettlementContract; + // Ideally, we don't want to have this cyclic dependency, but we have it in the protocol, + // So it's logical that we can't avoid that here + this.argsRegistry.addArgs("BridgeContract", { + SettlementContract: BridgingSettlementContract, + }); + this.argsRegistry.addArgs("DispatchContract", { + settlementContractClass: BridgingSettlementContract, + }); return BridgingSettlementContract; } @@ -98,16 +103,6 @@ export class BridgingSettlementContractModule extends ContractModule< } ); - const args = this.argsRegistry.getArgs( - "SettlementContract", - BridgingSettlementContractArgsSchema - ); - if (args.signedSettlements === undefined) { - throw new Error( - "Args not fully initialized - make sure to also include the SettlementModule in the sequencer" - ); - } - log.debug("Compiling Settlement Contract"); const artifact = await registry.forceProverExists( diff --git a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts index 5b3663a82..6a27e25de 100644 --- a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts @@ -7,11 +7,13 @@ import { ContractModule, SmartContractClassFromInterface, } from "../ContractModule"; +import { ContractArgsRegistry } from "../ContractArgsRegistry"; import { DispatchSmartContract, DispatchContractType, DispatchSmartContractBase, + DispatchContractArgs, } from "./DispatchSmartContract"; export type DispatchContractConfig = { @@ -23,7 +25,10 @@ export class DispatchContractProtocolModule extends ContractModule< DispatchContractType, DispatchContractConfig > { - public constructor(@inject("Runtime") private readonly runtime: RuntimeLike) { + public constructor( + @inject("Runtime") private readonly runtime: RuntimeLike, + private readonly contractArgsRegistry: ContractArgsRegistry + ) { super(); } @@ -52,20 +57,18 @@ export class DispatchContractProtocolModule extends ContractModule< this.checkConfigIntegrity(incomingMessagesMethods, methodIdMappings); - DispatchSmartContractBase.args = { - incomingMessagesPaths: incomingMessagesMethods, - methodIdMappings, - settlementContractClass: - DispatchSmartContractBase.args?.settlementContractClass, - }; + this.contractArgsRegistry.addArgs( + "DispatchContract", + { + incomingMessagesPaths: incomingMessagesMethods, + methodIdMappings, + } + ); return DispatchSmartContract; } public async compile(registry: CompileRegistry) { - if (DispatchSmartContractBase.args.settlementContractClass === undefined) { - throw new Error("Reference to Settlement Contract not set"); - } return { DispatchSmartContract: await registry.forceProverExists( async () => await registry.compile(DispatchSmartContract) diff --git a/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts b/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts index f79b77d37..1767b4ebc 100644 --- a/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/DispatchSmartContract.ts @@ -17,6 +17,7 @@ import { Permissions, } from "o1js"; import { InMemoryMerkleTreeStorage, TypedClass } from "@proto-kit/common"; +import { container } from "tsyringe"; import { RuntimeMethodIdMapping } from "../../model/RuntimeLike"; import { RuntimeTransaction } from "../../model/transaction/RuntimeTransaction"; @@ -25,6 +26,11 @@ import { MinaEvents, } from "../../utils/MinaPrefixedProvableHashList"; import { Deposit } from "../messages/Deposit"; +import { + ContractArgsRegistry, + NaiveObjectSchema, + StaticInitializationContract, +} from "../ContractArgsRegistry"; import type { BridgingSettlementContractType } from "./settlement/BridgingSettlementContract"; import { TokenBridgeDeploymentAuth } from "./authorizations/TokenBridgeDeploymentAuth"; @@ -67,14 +73,24 @@ const tokenBridgeRoot = new TokenBridgeTree( new InMemoryMerkleTreeStorage() ).getRoot(); -export abstract class DispatchSmartContractBase extends SmartContract { - public static args: { - methodIdMappings: RuntimeMethodIdMapping; - incomingMessagesPaths: Record; - settlementContractClass?: TypedClass & - typeof SmartContract; +export interface DispatchContractArgs { + methodIdMappings: RuntimeMethodIdMapping; + incomingMessagesPaths: Record; + settlementContractClass: TypedClass & + typeof SmartContract; +} + +export const DispatchContractArgsSchema: NaiveObjectSchema = + { + incomingMessagesPaths: "Required", + methodIdMappings: "Required", + settlementContractClass: "Required", }; +export abstract class DispatchSmartContractBase + extends SmartContract + implements StaticInitializationContract +{ events = { "token-bridge-added": TokenBridgeTreeAddition, // We need a placeholder event here, so that o1js internally adds a identifier to the @@ -93,6 +109,12 @@ export abstract class DispatchSmartContractBase extends SmartContract { abstract tokenBridgeCount: State; + getInitializationArgs(): DispatchContractArgs { + return container + .resolve(ContractArgsRegistry) + .getArgs("DispatchContract", DispatchContractArgsSchema); + } + protected updateMessagesHashBase( executedMessagesHash: Field, newPromisedMessagesHash: Field @@ -109,12 +131,12 @@ export abstract class DispatchSmartContractBase extends SmartContract { this.self.account.actionState.requireEquals(newPromisedMessagesHash); this.promisedMessagesHash.set(newPromisedMessagesHash); + const args = this.getInitializationArgs(); const settlementContractAddress = this.settlementContract.getAndRequireEquals(); - const settlementContract = - new DispatchSmartContractBase.args.settlementContractClass!( - settlementContractAddress - ); + const settlementContract = new args.settlementContractClass!( + settlementContractAddress + ); settlementContract.authorizationField.requireEquals( new UpdateMessagesHashAuth({ @@ -179,10 +201,10 @@ export abstract class DispatchSmartContractBase extends SmartContract { // treeWitness: TokenBridgeTreeWitness ) { this.settlementContract.requireEquals(settlementContractAddress); - const settlementContract = - new DispatchSmartContractBase.args.settlementContractClass!( - settlementContractAddress - ); + const args = this.getInitializationArgs(); + const settlementContract = new args.settlementContractClass!( + settlementContractAddress + ); // Append bridge address to the tree // TODO This not concurrent and will fail if multiple users deploy bridges at the same time @@ -322,7 +344,7 @@ export class DispatchSmartContract }); const { methodIdMappings, incomingMessagesPaths } = - DispatchSmartContractBase.args; + this.getInitializationArgs(); const methodId = Field( methodIdMappings[incomingMessagesPaths.deposit].methodId diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts index e93a28eaa..817f03e3d 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts @@ -56,7 +56,7 @@ export class SettlementSmartContractModule extends ContractModule< ChildVerificationKeyService: this.childVerificationKeyService, }); - return BridgingSettlementContract; + return SettlementContract; } public async compile( diff --git a/packages/sequencer/test/integration/Proven.test.ts b/packages/sequencer/test/integration/Proven.test.ts index 86ad46bd0..6413ac2ff 100644 --- a/packages/sequencer/test/integration/Proven.test.ts +++ b/packages/sequencer/test/integration/Proven.test.ts @@ -10,6 +10,7 @@ import { Runtime } from "@proto-kit/module"; import { BridgeContract, BridgingSettlementContract, + BridgingSettlementContractArgs, ContractArgsRegistry, DispatchSmartContract, Protocol, @@ -171,17 +172,19 @@ describe.skip("Proven", () => { }); vkService.setCompileRegistry(registry); - container.resolve(ContractArgsRegistry).setArgs("SettlementContract", { - DispatchContract: DispatchSmartContract, - ChildVerificationKeyService: vkService, - BridgeContractVerificationKey: MOCK_VERIFICATION_KEY, - signedSettlements: false, - BridgeContract: BridgeContract, - hooks: [], - BridgeContractPermissions: - new ProvenSettlementPermissions().bridgeContractMina(), - escapeHatchSlotsInterval: 1000, - }); + container + .resolve(ContractArgsRegistry) + .addArgs("SettlementContract", { + DispatchContract: DispatchSmartContract, + ChildVerificationKeyService: vkService, + BridgeContractVerificationKey: MOCK_VERIFICATION_KEY, + signedSettlements: false, + BridgeContract: BridgeContract, + hooks: [], + BridgeContractPermissions: + new ProvenSettlementPermissions().bridgeContractMina(), + escapeHatchSlotsInterval: 1000, + }); const vk = await BridgingSettlementContract.compile(); console.log(vk.verificationKey); } catch (e) { From 12381f17709a12cce1c819a7d2c2d8740aa7b71c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 18 Dec 2025 13:23:13 +0100 Subject: [PATCH 058/155] Fix linting errors --- .../protocol/src/settlement/contracts/BridgeContract.ts | 7 +++---- .../settlement/contracts/BridgeContractProtocolModule.ts | 3 +-- .../contracts/BridgingSettlementContractModule.ts | 8 ++------ .../contracts/DispatchContractProtocolModule.ts | 1 - .../settlement/contracts/SettlementSmartContractModule.ts | 1 - .../contracts/settlement/BridgingSettlementContract.ts | 1 - 6 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/protocol/src/settlement/contracts/BridgeContract.ts b/packages/protocol/src/settlement/contracts/BridgeContract.ts index 1771bbfed..f56f8ec95 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContract.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContract.ts @@ -16,7 +16,7 @@ import { TokenId, VerificationKey, } from "o1js"; -import { batch, noop, range, TypedClass } from "@proto-kit/common"; +import { noop, range, TypedClass } from "@proto-kit/common"; import { container, injectable, singleton } from "tsyringe"; import { @@ -28,14 +28,13 @@ import { import { Path } from "../../model/Path"; import { OutgoingMessageProcessor } from "../modularity/OutgoingMessageProcessor"; import { PROTOKIT_FIELD_PREFIXES } from "../../hashing/protokit-prefixes"; - -import type { BridgingSettlementContractType } from "./settlement/BridgingSettlementContract"; import { ContractArgsRegistry, NaiveObjectSchema, StaticInitializationContract, } from "../ContractArgsRegistry"; -import { hash } from "node:crypto"; + +import type { BridgingSettlementContractType } from "./settlement/BridgingSettlementContract"; export type BridgeContractType = { stateRoot: State; diff --git a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts index 33582903d..e4537d242 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts @@ -3,14 +3,13 @@ import { CompileRegistry } from "@proto-kit/common"; import { ContractModule } from "../ContractModule"; import { OutgoingMessageProcessor } from "../modularity/OutgoingMessageProcessor"; +import { ContractArgsRegistry } from "../ContractArgsRegistry"; import { BridgeContract, BridgeContractArgs, - BridgeContractBase, BridgeContractType, } from "./BridgeContract"; -import { ContractArgsRegistry } from "../ContractArgsRegistry"; export type BridgeContractConfig = { outgoingBatchSize?: number; diff --git a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts index 430fde6af..9d1fef1ed 100644 --- a/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgingSettlementContractModule.ts @@ -14,17 +14,13 @@ import { import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; import { ContractArgsRegistry } from "../ContractArgsRegistry"; -import { - DispatchContractArgs, - DispatchSmartContractBase, -} from "./DispatchSmartContract"; +import { DispatchContractArgs } from "./DispatchSmartContract"; import { BridgingSettlementContractType, BridgingSettlementContract, BridgingSettlementContractArgs, - BridgingSettlementContractArgsSchema, } from "./settlement/BridgingSettlementContract"; -import { BridgeContractArgs, BridgeContractBase } from "./BridgeContract"; +import { BridgeContractArgs } from "./BridgeContract"; import { DispatchContractProtocolModule } from "./DispatchContractProtocolModule"; import { BridgeContractProtocolModule } from "./BridgeContractProtocolModule"; import { diff --git a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts index 6a27e25de..f421d9f1e 100644 --- a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts @@ -12,7 +12,6 @@ import { ContractArgsRegistry } from "../ContractArgsRegistry"; import { DispatchSmartContract, DispatchContractType, - DispatchSmartContractBase, DispatchContractArgs, } from "./DispatchSmartContract"; diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts index 817f03e3d..c776e5ff5 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContractModule.ts @@ -14,7 +14,6 @@ import { import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; import { ContractArgsRegistry } from "../ContractArgsRegistry"; -import { BridgingSettlementContract } from "./settlement/BridgingSettlementContract"; import { SettlementContractArgs, SettlementContractType, diff --git a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts index 7610f86d5..4865e4ec5 100644 --- a/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts +++ b/packages/protocol/src/settlement/contracts/settlement/BridgingSettlementContract.ts @@ -14,7 +14,6 @@ import { VerificationKey, Permissions, Struct, - Provable, TokenId, DeployArgs, } from "o1js"; From a6a6a2b9f7cd771c223e9125881e9d1780f8b2d2 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 18 Dec 2025 15:04:54 +0100 Subject: [PATCH 059/155] Added settlement-only test --- .../src/protocol/baselayer/MinaBaseLayer.ts | 12 - .../src/sequencer/SettlementStartupModule.ts | 37 ++- .../src/settlement/BridgingModule.ts | 12 + .../bridging/BridgingDeployInteraction.ts | 7 +- .../vanilla/VanillaDeployInteraction.ts | 8 +- .../vanilla/VanillaSettlementInteraction.ts | 3 +- .../messages/IncomingMessagesService.ts | 2 +- .../test/settlement/Settlement-only.ts | 306 ++++++++++++++++++ .../test/settlement/Settlement.test.ts | 5 + 9 files changed, 354 insertions(+), 38 deletions(-) create mode 100644 packages/sequencer/test/settlement/Settlement-only.ts diff --git a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts index e37019b32..2681a29aa 100644 --- a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts +++ b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts @@ -2,7 +2,6 @@ import { AreProofsEnabled, DependencyFactory, ModuleContainerLike, - DependencyRecord, } from "@proto-kit/common"; import { Mina } from "o1js"; import { match } from "ts-pattern"; @@ -15,7 +14,6 @@ import { } from "../../sequencer/builder/SequencerModule"; import { MinaTransactionSender } from "../../settlement/transactions/MinaTransactionSender"; import { DefaultOutgoingMessageAdapter } from "../../settlement/messages/outgoing/DefaultOutgoingMessageAdapter"; -import { IncomingMessagesService } from "../../settlement/messages/IncomingMessagesService"; import { BaseLayer } from "./BaseLayer"; import { LocalBlockchainUtils } from "./network-utils/LocalBlockchainUtils"; @@ -64,14 +62,6 @@ export class MinaBaseLayer super(); } - public static dependencies() { - return { - IncomingMessagesService: { - useClass: IncomingMessagesService, - }, - } satisfies DependencyRecord; - } - public dependencies() { const NetworkUtilsClass = match(this.config.network.type) .with("local", () => LocalBlockchainUtils) @@ -151,5 +141,3 @@ export class MinaBaseLayer this.network = Network; } } - -MinaBaseLayer satisfies DependencyFactory; diff --git a/packages/sequencer/src/sequencer/SettlementStartupModule.ts b/packages/sequencer/src/sequencer/SettlementStartupModule.ts index be3827bbd..1936fb3c1 100644 --- a/packages/sequencer/src/sequencer/SettlementStartupModule.ts +++ b/packages/sequencer/src/sequencer/SettlementStartupModule.ts @@ -39,39 +39,38 @@ export class SettlementStartupModule { return artifacts; } - private async getArtifacts(retry: boolean): Promise<{ - SettlementSmartContract: CompileArtifact; - DispatchSmartContract: CompileArtifact; - }> { - const settlementVerificationKey = - this.compileRegistry.getArtifact("SettlementContract"); - const dispatchVerificationKey = this.compileRegistry.getArtifact( - "DispatchSmartContract" + private async getArtifacts>( + contracts: Contracts, + retry: boolean + ): Promise> { + const artifacts = Object.entries(contracts).map( + ([contract]) => + [contract, this.compileRegistry.getArtifact(contract)] as const ); - if ( - settlementVerificationKey === undefined || - dispatchVerificationKey === undefined - ) { + if (artifacts.some((x) => x[1] === undefined)) { if (retry) { log.info( "Settlement Contracts not yet compiled, initializing compilation" ); await this.compile(); - return await this.getArtifacts(false); + return await this.getArtifacts(contracts, false); } throw new Error( "Settlement contract verification keys not available for deployment" ); } - return { - SettlementSmartContract: settlementVerificationKey, - DispatchSmartContract: dispatchVerificationKey, - }; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return Object.fromEntries(artifacts) as Record< + keyof Contracts, + CompileArtifact + >; } - public async retrieveVerificationKeys() { - return await this.getArtifacts(true); + public async retrieveVerificationKeys>( + contracts: Contracts + ) { + return await this.getArtifacts(contracts, true); } } diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 25ef3fcbb..4c13102bc 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -36,6 +36,7 @@ import { UInt32, } from "o1js"; import { + DependencyRecord, filterNonUndefined, LinkedMerkleTree, log, @@ -65,6 +66,7 @@ import { MinaSigner } from "./MinaSigner"; import { SignedSettlementPermissions } from "./permissions/SignedSettlementPermissions"; import { ProvenSettlementPermissions } from "./permissions/ProvenSettlementPermissions"; import { AddressRegistry } from "./interactions/AddressRegistry"; +import { IncomingMessagesService } from "./messages/IncomingMessagesService"; export type SettlementTokenConfig = Record< string, @@ -126,6 +128,14 @@ export class BridgingModule extends SequencerModule { this.utils = new SettlementUtils(baseLayer, signer); } + public dependencies() { + return { + IncomingMessagesService: { + useClass: IncomingMessagesService, + }, + } satisfies DependencyRecord; + } + public getDispatchContract() { if (this.dispatchContract === undefined) { const address = this.getDispatchContractAddress(); @@ -685,3 +695,5 @@ export class BridgingModule extends SequencerModule { } /* eslint-enable no-await-in-loop */ } + +// BridgingModule satisfies DependencyFactory; diff --git a/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts index c1743ae09..c1c24d4a4 100644 --- a/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/bridging/BridgingDeployInteraction.ts @@ -71,7 +71,10 @@ export class BridgingDeployInteraction implements DeployInteraction { }); const verificationsKeys = - await this.settlementStartupModule.retrieveVerificationKeys(); + await this.settlementStartupModule.retrieveVerificationKeys({ + SettlementContract: true, + DispatchSmartContract: true, + }); const utils = new SettlementUtils(this.baseLayer, this.signer); await utils.fetchContractAccounts(settlementContract, dispatchContract); @@ -102,7 +105,7 @@ export class BridgingDeployInteraction implements DeployInteraction { await settlementContract.deployAndInitialize( { verificationKey: - verificationsKeys.SettlementSmartContract.verificationKey, + verificationsKeys.SettlementContract.verificationKey, }, permissions.settlementContract(), feepayer, diff --git a/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts index 0d5cec0d6..c5fb126fe 100644 --- a/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/vanilla/VanillaDeployInteraction.ts @@ -73,7 +73,9 @@ export class VanillaDeployInteraction implements DeployInteraction { await utils.fetchContractAccounts(settlementContract); const verificationsKeys = - await this.settlementStartupModule.retrieveVerificationKeys(); + await this.settlementStartupModule.retrieveVerificationKeys({ + SettlementContract: true, + }); const permissions = this.baseLayer.isSignedSettlement() ? new SignedSettlementPermissions() @@ -87,12 +89,12 @@ export class VanillaDeployInteraction implements DeployInteraction { memo: "Protokit settlement deploy", }, async () => { - AccountUpdate.fundNewAccount(feepayer, 2); + AccountUpdate.fundNewAccount(feepayer, 1); await settlementContract.deployAndInitialize( { verificationKey: - verificationsKeys.SettlementSmartContract.verificationKey, + verificationsKeys.SettlementContract.verificationKey, }, permissions.settlementContract(), feepayer, diff --git a/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts b/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts index 6f54961a6..096438ab5 100644 --- a/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts +++ b/packages/sequencer/src/settlement/interactions/vanilla/VanillaSettlementInteraction.ts @@ -1,4 +1,4 @@ -import { inject } from "tsyringe"; +import { inject, injectable } from "tsyringe"; import { Field, Mina } from "o1js"; import { BATCH_SIGNATURE_PREFIX, @@ -21,6 +21,7 @@ import { SettleableBatch } from "../../../storage/model/Batch"; import { Settlement } from "../../../storage/model/Settlement"; import { SettleInteraction } from "../SettleInteraction"; +@injectable() export class VanillaSettlementInteraction implements SettleInteraction { public constructor( @inject("AddressRegistry") diff --git a/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts b/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts index a7aef8bd7..6311cb30a 100644 --- a/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts +++ b/packages/sequencer/src/settlement/messages/IncomingMessagesService.ts @@ -5,7 +5,7 @@ import { SettlementStorage } from "../../storage/repositories/SettlementStorage" import { MessageStorage } from "../../storage/repositories/MessageStorage"; import { BlockStorage } from "../../storage/repositories/BlockStorage"; import { PendingTransaction } from "../../mempool/PendingTransaction"; -import { BridgingModule } from "../BridgingModule"; +import type { BridgingModule } from "../BridgingModule"; import { IncomingMessageAdapter } from "./IncomingMessageAdapter"; diff --git a/packages/sequencer/test/settlement/Settlement-only.ts b/packages/sequencer/test/settlement/Settlement-only.ts new file mode 100644 index 000000000..87a4e952d --- /dev/null +++ b/packages/sequencer/test/settlement/Settlement-only.ts @@ -0,0 +1,306 @@ +import "reflect-metadata"; +import { Field, PrivateKey } from "o1js"; +import { Runtime } from "@proto-kit/module"; +import { + BlockStorageNetworkStateModule, + ClientAppChain, + InMemoryBlockExplorer, + InMemorySigner, + InMemoryTransactionSender, + StateServiceQueryModule, +} from "@proto-kit/sdk"; +import { + BlockProverPublicInput, + ContractArgsRegistry, + Protocol, + SettlementContractModule, +} from "@proto-kit/protocol"; +import { UInt64, VanillaProtocolModules } from "@proto-kit/library"; +import { + expectDefined, + LinkedMerkleTree, + mapSequential, +} from "@proto-kit/common"; +import { container } from "tsyringe"; +import { afterAll } from "@jest/globals"; + +import { createTransaction } from "../integration/utils"; +import { testingSequencerModules } from "../TestingSequencer"; +import { + BlockQueue, + InMemoryMinaSigner, + ManualBlockTrigger, + MinaBaseLayer, + MinaBaseLayerConfig, + MinaNetworkUtils, + PendingTransaction, + PrivateMempool, + Sequencer, + SettlementModule, + SettlementProvingTask, + VanillaTaskWorkerModules, +} from "../../src"; + +import { Withdrawals } from "./mocks/Withdrawals"; +import { Balances } from "./mocks/Balances"; + +// Most of this code is copied from test/Settlement.ts and thinned down to +// settlement-only - eventually we should consolidate and/or make the API nicer +// to require less code +export const settlementOnlyTestFn = ( + settlementType: "signed" | "mock-proofs" | "proven", + baseLayerConfig: MinaBaseLayerConfig, + timeout: number = 120_000 +) => { + let testAccounts: PrivateKey[] = []; + + const sequencerKey = PrivateKey.random(); + const settlementKey = PrivateKey.random(); + + let trigger: ManualBlockTrigger; + let settlementModule: SettlementModule; + let blockQueue: BlockQueue; + + function setupAppChain() { + const runtime = Runtime.from({ + Balances, + Withdrawals, + }); + + // eslint-disable-next-line @typescript-eslint/dot-notation + MinaBaseLayer.prototype["isSignedSettlement"] = () => + settlementType === "signed"; + + const sequencer = Sequencer.from( + testingSequencerModules( + { + BaseLayer: MinaBaseLayer, + SettlementModule: SettlementModule, + SettlementSigner: InMemoryMinaSigner, + }, + { + SettlementProvingTask, + } + ) + ); + + const appchain = ClientAppChain.from({ + Runtime: runtime, + Sequencer: sequencer, + + Protocol: Protocol.from({ + ...VanillaProtocolModules.mandatoryModules({}), + SettlementContractModule: SettlementContractModule.from({ + ...SettlementContractModule.settlementOnly(), + }), + }), + + Signer: InMemorySigner, + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + BlockExplorerTransportModule: InMemoryBlockExplorer, + }); + + appchain.configure({ + Runtime: { + Balances: { + totalSupply: UInt64.from(1000), + }, + Withdrawals: {}, + }, + + Sequencer: { + Database: {}, + BlockTrigger: {}, + Mempool: {}, + BatchProducerModule: {}, + LocalTaskWorkerModule: { + ...VanillaTaskWorkerModules.defaultConfig(), + }, + BaseLayer: baseLayerConfig, + SettlementSigner: { + feepayer: sequencerKey, + contractKeys: [settlementKey], + }, + BlockProducerModule: {}, + FeeStrategy: {}, + SettlementModule: {}, + SequencerStartupModule: {}, + + TaskQueue: { + simulatedDuration: 0, + }, + }, + Protocol: { + StateTransitionProver: {}, + BlockHeight: {}, + AccountState: {}, + BlockProver: {}, + LastStateRoot: {}, + SettlementContractModule: { + SettlementContract: {}, + }, + }, + TransactionSender: {}, + QueryTransportModule: {}, + Signer: { + signer: sequencerKey, + }, + NetworkStateTransportModule: {}, + BlockExplorerTransportModule: {}, + }); + + return appchain; + } + + let appChain: ReturnType; + + async function createBatch( + withTransactions: boolean, + customNonce: number = 0, + txs: PendingTransaction[] = [] + ) { + const mempool = appChain.sequencer.resolve("Mempool") as PrivateMempool; + if (withTransactions) { + const key = testAccounts[0]; + const tx = createTransaction({ + runtime: appChain.runtime, + method: ["Balances", "mint"], + privateKey: key, + args: [Field(1), key.toPublicKey(), UInt64.from(1e9 * 100)], + nonce: customNonce, + }); + + await mempool.add(tx); + } + await mapSequential(txs, async (tx) => { + await mempool.add(tx); + }); + + const result = await trigger.produceBlockAndBatch(); + const [block, batch] = result; + + console.log( + `block ${block?.height.toString()} ${block?.fromMessagesHash.toString()} -> ${block?.toMessagesHash.toString()}` + ); + + return result; + } + + beforeAll(async () => { + appChain = setupAppChain(); + + await appChain.start( + settlementType === "proven", + container.createChildContainer() + ); + + settlementModule = appChain.sequencer.resolve( + "SettlementModule" + ) as SettlementModule; + trigger = + appChain.sequencer.dependencyContainer.resolve( + "BlockTrigger" + ); + blockQueue = appChain.sequencer.resolve("BlockQueue") as BlockQueue; + + const networkUtils = + appChain.sequencer.dependencyContainer.resolve( + "NetworkUtils" + ); + const accs = await networkUtils.getFundedAccounts(3); + testAccounts = accs.slice(1); + + await networkUtils.waitForNetwork(); + + console.log( + `Funding ${sequencerKey.toPublicKey().toBase58()} from ${accs[0].toPublicKey().toBase58()}` + ); + + await networkUtils.faucet(sequencerKey.toPublicKey(), 20 * 1e9); + }, timeout * 3); + + afterAll(async () => { + container.resolve(ContractArgsRegistry).resetArgs("SettlementContract"); + + await appChain.close(); + }); + + let nonceCounter = 0; + + it("should throw error", async () => { + await expect(settlementModule.checkDeployment()).rejects.toThrow(); + }); + + it( + "should deploy settlement contracts", + async () => { + // Deploy contract + await settlementModule.deploy( + { + settlementContract: settlementKey.toPublicKey(), + }, + { + nonce: nonceCounter, + } + ); + + nonceCounter += 1; + + console.log("Deployed"); + }, + timeout + ); + + it( + "should settle", + async () => { + try { + const [, batch] = await createBatch(true); + + const input = BlockProverPublicInput.fromFields( + batch!.proof.publicInput.map((x) => Field(x)) + ); + expect(input.stateRoot.toString()).toStrictEqual( + LinkedMerkleTree.EMPTY_ROOT.toString() + ); + + const lastBlock = await blockQueue.getLatestBlockAndResult(); + + await trigger.settle(batch!, {}); + nonceCounter++; + + // TODO Check Smartcontract tx layout (call to dispatch with good preconditions, etc) + + console.log("Block settled"); + + await settlementModule.utils.fetchContractAccounts({ + address: settlementModule.getSettlementContractAddress(), + }); + const settlement = settlementModule.getSettlementContract(); + expectDefined(lastBlock); + expectDefined(lastBlock.result); + expect(settlement.networkStateHash.get().toString()).toStrictEqual( + lastBlock!.result.afterNetworkState.hash().toString() + ); + expect(settlement.stateRoot.get().toString()).toStrictEqual( + lastBlock!.result.stateRoot.toString() + ); + expect(settlement.blockHashRoot.get().toString()).toStrictEqual( + lastBlock!.result.blockHashRoot.toString() + ); + } catch (e) { + console.error(e); + throw e; + } + }, + timeout + ); + + it("should not throw error after settlement", async () => { + expect.assertions(1); + + await expect(settlementModule.checkDeployment()).resolves.toBeUndefined(); + }); +}; diff --git a/packages/sequencer/test/settlement/Settlement.test.ts b/packages/sequencer/test/settlement/Settlement.test.ts index fd7ee60c5..b1aa7044a 100644 --- a/packages/sequencer/test/settlement/Settlement.test.ts +++ b/packages/sequencer/test/settlement/Settlement.test.ts @@ -3,6 +3,7 @@ import { FungibleToken } from "mina-fungible-token"; import { MinaBaseLayerConfig } from "../../src"; import { settlementTestFn } from "./Settlement"; +import { settlementOnlyTestFn } from "./Settlement-only"; describe.each(["mock-proofs", "signed"] as const)( "Settlement contracts: local blockchain - %s", @@ -22,5 +23,9 @@ describe.each(["mock-proofs", "signed"] as const)( tokenOwner: FungibleToken, }); }); + + describe("Settlement only", () => { + settlementOnlyTestFn(type, network); + }); } ); From 4fd61f27476044c8633878f9c71fe2edf3ecf1b6 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 18 Dec 2025 15:35:40 +0100 Subject: [PATCH 060/155] Fixed dependency issue --- packages/sequencer/src/settlement/BridgingModule.ts | 2 +- packages/sequencer/test/settlement/Settlement-only.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 4c13102bc..a575c5e3a 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -128,7 +128,7 @@ export class BridgingModule extends SequencerModule { this.utils = new SettlementUtils(baseLayer, signer); } - public dependencies() { + public static dependencies() { return { IncomingMessagesService: { useClass: IncomingMessagesService, diff --git a/packages/sequencer/test/settlement/Settlement-only.ts b/packages/sequencer/test/settlement/Settlement-only.ts index 87a4e952d..6fe5d8dad 100644 --- a/packages/sequencer/test/settlement/Settlement-only.ts +++ b/packages/sequencer/test/settlement/Settlement-only.ts @@ -179,7 +179,7 @@ export const settlementOnlyTestFn = ( }); const result = await trigger.produceBlockAndBatch(); - const [block, batch] = result; + const [block] = result; console.log( `block ${block?.height.toString()} ${block?.fromMessagesHash.toString()} -> ${block?.toMessagesHash.toString()}` From 6e792274963454ba23d9149166a487e098b7ccc4 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 18 Dec 2025 15:48:59 +0100 Subject: [PATCH 061/155] Fixed Proven test again --- packages/sequencer/test/integration/Proven.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/test/integration/Proven.test.ts b/packages/sequencer/test/integration/Proven.test.ts index 6413ac2ff..0526d93d2 100644 --- a/packages/sequencer/test/integration/Proven.test.ts +++ b/packages/sequencer/test/integration/Proven.test.ts @@ -151,12 +151,15 @@ describe.skip("Proven", () => { SettlementStartupModule ); - const vks = await module.retrieveVerificationKeys(); + const vks = await module.retrieveVerificationKeys({ + SettlementContract: true, + DispatchSmartContract: true, + }); console.log(vks); expect(vks.DispatchSmartContract).toBeDefined(); - expect(vks.SettlementSmartContract).toBeDefined(); + expect(vks.SettlementContract).toBeDefined(); }); it.skip("Hello", async () => { From b43cb8f81d82ed11055b73380efc011755d2fecd Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 18 Dec 2025 19:45:16 +0300 Subject: [PATCH 062/155] refactor: remove token registration check --- packages/sdk/src/query/QueryService.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/sdk/src/query/QueryService.ts b/packages/sdk/src/query/QueryService.ts index 05babff1e..ac1aea039 100644 --- a/packages/sdk/src/query/QueryService.ts +++ b/packages/sdk/src/query/QueryService.ts @@ -54,9 +54,6 @@ export class QueryService< */ private get queryTransport(): QueryTransportModule { if (this.QueryTransport === undefined) { - if (!this.container.isRegistered("QueryTransportModule")) { - throw new Error("QueryTransportModule is not registered"); - } this.QueryTransport = this.container.resolve( "QueryTransportModule" ); @@ -71,9 +68,6 @@ export class QueryService< */ private get networkStateTransport(): NetworkStateTransportModule { if (this.NetworkStateTransport === undefined) { - if (!this.container.isRegistered("NetworkStateTransportModule")) { - throw new Error("NetworkStateTransportModule is not registered."); - } this.NetworkStateTransport = this.container.resolve( "NetworkStateTransportModule" @@ -89,9 +83,6 @@ export class QueryService< */ private get blockExplorerTransport(): BlockExplorerTransportModule { if (this.BlockExplorerTransport === undefined) { - if (!this.container.isRegistered("BlockExplorerTransportModule")) { - throw new Error("BlockExplorerTransportModule is not registered."); - } this.BlockExplorerTransport = this.container.resolve( "BlockExplorerTransportModule" From 343e2e6be72b294c0f1d7e980ab7cf326b023321 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 18 Dec 2025 19:51:13 +0300 Subject: [PATCH 063/155] style: lowercase QueryService --- packages/sdk/src/client/ClientAppChain.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sdk/src/client/ClientAppChain.ts b/packages/sdk/src/client/ClientAppChain.ts index f816b483b..67bf17792 100644 --- a/packages/sdk/src/client/ClientAppChain.ts +++ b/packages/sdk/src/client/ClientAppChain.ts @@ -49,7 +49,7 @@ export class ClientAppChain< AppChainModules extends MinimalAppChainDefinition, > extends AppChain { // Optional for our lazy initialization purpose. - private QueryService?: QueryService< + private queryService?: QueryService< InferModules, InferModules >; @@ -197,13 +197,13 @@ export class ClientAppChain< InferModules, InferModules > { - if (this.QueryService === undefined) { - this.QueryService = new QueryService( + if (this.queryService === undefined) { + this.queryService = new QueryService( this.runtime, this.protocol, this.container ); } - return this.QueryService; + return this.queryService; } } From 011abc8b478384142a7b39ef7f6ceb91699c595d Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 19 Dec 2025 14:35:29 +0300 Subject: [PATCH 064/155] refactor: resolve query service from parent container --- packages/sdk/src/client/ClientAppChain.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/sdk/src/client/ClientAppChain.ts b/packages/sdk/src/client/ClientAppChain.ts index 67bf17792..e46414d1b 100644 --- a/packages/sdk/src/client/ClientAppChain.ts +++ b/packages/sdk/src/client/ClientAppChain.ts @@ -48,12 +48,6 @@ export type InferModules>> = export class ClientAppChain< AppChainModules extends MinimalAppChainDefinition, > extends AppChain { - // Optional for our lazy initialization purpose. - private queryService?: QueryService< - InferModules, - InferModules - >; - public static from( definition: Modules ) { @@ -197,13 +191,11 @@ export class ClientAppChain< InferModules, InferModules > { - if (this.queryService === undefined) { - this.queryService = new QueryService( - this.runtime, - this.protocol, - this.container - ); - } - return this.queryService; + return this.container.resolve< + QueryService< + InferModules, + InferModules + > + >(QueryService); } } From 3385f1dada68c820c05be5250db2fbeabaecef61 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 19 Dec 2025 14:35:48 +0300 Subject: [PATCH 065/155] refactor: inject transport modules instead of registering in class --- packages/sdk/src/query/QueryService.ts | 65 +++++--------------------- 1 file changed, 12 insertions(+), 53 deletions(-) diff --git a/packages/sdk/src/query/QueryService.ts b/packages/sdk/src/query/QueryService.ts index ac1aea039..46b174848 100644 --- a/packages/sdk/src/query/QueryService.ts +++ b/packages/sdk/src/query/QueryService.ts @@ -1,4 +1,4 @@ -import { DependencyContainer } from "tsyringe"; +import { inject, injectable, Lifecycle, scoped } from "tsyringe"; import { Runtime, RuntimeModule, @@ -20,19 +20,15 @@ import { QueryTransportModule, } from "@proto-kit/sequencer"; +@scoped(Lifecycle.ContainerScoped) +@injectable() export class QueryService< RuntimeModules extends RuntimeModulesRecord, ProtocolModules extends ProtocolModulesRecord & MandatoryProtocolModulesRecord = ProtocolModulesRecord & MandatoryProtocolModulesRecord, > { - // Here, fields are optional for lazy initialization. - private QueryTransport?: QueryTransportModule; - - private NetworkStateTransport?: NetworkStateTransportModule; - - private BlockExplorerTransport?: BlockExplorerTransportModule; - + // Lazily initialized query instances private RuntimeQuery?: Query, RuntimeModules>; private ProtocolQuery?: Query, ProtocolModules>; @@ -42,55 +38,18 @@ export class QueryService< private ExplorerQuery?: BlockExplorerQuery; public constructor( + @inject("Runtime") private readonly runtimeInstance: Runtime, + @inject("Protocol") private readonly protocolInstance: Protocol, - private readonly container: DependencyContainer + @inject("QueryTransportModule", { isOptional: true }) + private readonly queryTransport: QueryTransportModule, + @inject("NetworkStateTransportModule", { isOptional: true }) + private readonly networkStateTransport: NetworkStateTransportModule, + @inject("BlockExplorerTransportModule", { isOptional: true }) + private readonly blockExplorerTransport: BlockExplorerTransportModule ) {} - /** - * A helper function that resolves QueryTrasnportModule. - * If not resolved before, it is resolved. - * @returns The registered transport module as {@link QueryTrasnportModule} - */ - private get queryTransport(): QueryTransportModule { - if (this.QueryTransport === undefined) { - this.QueryTransport = this.container.resolve( - "QueryTransportModule" - ); - } - return this.QueryTransport; - } - - /** - * A helper function that resolves NetworkStateTransport. - * If not resolved before, it is resolved. - * @returns The registered transport module as {@link BlockExplorerTransport} - */ - private get networkStateTransport(): NetworkStateTransportModule { - if (this.NetworkStateTransport === undefined) { - this.NetworkStateTransport = - this.container.resolve( - "NetworkStateTransportModule" - ); - } - return this.NetworkStateTransport; - } - - /** - * A helper function that resolves BlockExplorerTransportModule. - * If not resolved before, it is resolved. - * @returns The registered transport module as {@link BlockExplorerTransport} - */ - private get blockExplorerTransport(): BlockExplorerTransportModule { - if (this.BlockExplorerTransport === undefined) { - this.BlockExplorerTransport = - this.container.resolve( - "BlockExplorerTransportModule" - ); - } - return this.BlockExplorerTransport; - } - /** * Getter of query module for runtime modules. * If not initialized before, it is initialized. From 09f423f86d27924e187ce6cb6367716e5eb6a4ea Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 19 Dec 2025 15:03:55 +0300 Subject: [PATCH 066/155] refactor: remove unnecessary default parameter --- packages/sdk/src/query/QueryService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sdk/src/query/QueryService.ts b/packages/sdk/src/query/QueryService.ts index 46b174848..f9fe28e52 100644 --- a/packages/sdk/src/query/QueryService.ts +++ b/packages/sdk/src/query/QueryService.ts @@ -25,8 +25,7 @@ import { export class QueryService< RuntimeModules extends RuntimeModulesRecord, ProtocolModules extends ProtocolModulesRecord & - MandatoryProtocolModulesRecord = ProtocolModulesRecord & - MandatoryProtocolModulesRecord, + MandatoryProtocolModulesRecord > { // Lazily initialized query instances private RuntimeQuery?: Query, RuntimeModules>; From 7491d0cff0b37cba5e5b404b83616ada5e038be7 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 14:44:31 +0100 Subject: [PATCH 067/155] Separated TransactionProver from BlockProver: Protocol --- packages/protocol/src/index.ts | 3 + packages/protocol/src/protocol/Protocol.ts | 14 + .../src/protocol/ProvableBlockHook.ts | 76 ++- .../src/protocol/ProvableTransactionHook.ts | 70 ++- .../src/prover/block/BlockProvable.ts | 70 +-- .../protocol/src/prover/block/BlockProver.ts | 513 +--------------- .../prover/transaction/TransactionProvable.ts | 178 ++++++ .../prover/transaction/TransactionProver.ts | 573 ++++++++++++++++++ packages/protocol/src/prover/utils.ts | 106 ++++ .../modularity/ProvableSettlementHook.ts | 2 +- 10 files changed, 988 insertions(+), 617 deletions(-) create mode 100644 packages/protocol/src/prover/transaction/TransactionProvable.ts create mode 100644 packages/protocol/src/prover/transaction/TransactionProver.ts create mode 100644 packages/protocol/src/prover/utils.ts diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 080dfc646..8d8cf7d9d 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -29,6 +29,9 @@ export * from "./prover/block/accummulators/BlockHashMerkleTree"; export * from "./prover/block/services/RuntimeVerificationKeyRootService"; export * from "./prover/statetransition/StateTransitionProver"; export * from "./prover/statetransition/StateTransitionProvable"; +export * from "./prover/transaction/TransactionProver"; +export * from "./prover/transaction/TransactionProvable"; +export * from "./prover/utils"; export * from "./protocol/Protocol"; export * from "./protocol/ProtocolModule"; export * from "./protocol/ProtocolEnvironment"; diff --git a/packages/protocol/src/protocol/Protocol.ts b/packages/protocol/src/protocol/Protocol.ts index 0d1496d86..cbdf4c130 100644 --- a/packages/protocol/src/protocol/Protocol.ts +++ b/packages/protocol/src/protocol/Protocol.ts @@ -21,6 +21,7 @@ import { ProvableSettlementHook } from "../settlement/modularity/ProvableSettlem import { NoopSettlementHook } from "../hooks/NoopSettlementHook"; import { AccountStateHook } from "../hooks/AccountStateHook"; import { NoopTransactionHook } from "../hooks/NoopTransactionHook"; +import { TransactionProvable } from "../prover/transaction/TransactionProvable"; import { ProtocolModule } from "./ProtocolModule"; import { ProvableTransactionHook } from "./ProvableTransactionHook"; @@ -44,6 +45,10 @@ export type ProtocolModulesRecord = ModulesRecord< TypedClass> >; +export interface TransactionProverType + extends ProtocolModule, + TransactionProvable {} + export interface BlockProverType extends ProtocolModule, BlockProvable {} export interface StateTransitionProverType @@ -51,6 +56,7 @@ export interface StateTransitionProverType StateTransitionProvable {} export type MandatoryProtocolModulesRecord = { + TransactionProver: TypedClass; BlockProver: TypedClass; StateTransitionProver: TypedClass; AccountState: TypedClass; @@ -115,6 +121,14 @@ export class Protocol< return this.definition[moduleName] !== undefined; } + public get transactionProver(): TransactionProvable { + // Why do I resolve directly here? + // I don't know exactly but generics don't let me use .resolve() + return this.container.resolve>( + "TransactionProver" + ); + } + public get blockProver(): BlockProvable { // Why do I resolve directly here? // I don't know exactly but generics don't let me use .resolve() diff --git a/packages/protocol/src/protocol/ProvableBlockHook.ts b/packages/protocol/src/protocol/ProvableBlockHook.ts index b5381e193..b15863b92 100644 --- a/packages/protocol/src/protocol/ProvableBlockHook.ts +++ b/packages/protocol/src/protocol/ProvableBlockHook.ts @@ -2,54 +2,48 @@ import { Field } from "o1js"; import { NoConfig } from "@proto-kit/common"; import { NetworkState } from "../model/network/NetworkState"; -import { MethodPublicOutput } from "../model/MethodPublicOutput"; -import { BlockProverTransactionArguments } from "../prover/block/BlockProvable"; - -import { TransitioningProtocolModule } from "./TransitioningProtocolModule"; import { - AfterTransactionHookArguments, - BeforeTransactionHookArguments, - ProvableHookBlockState, - toProvableHookBlockState, -} from "./ProvableTransactionHook"; - -export interface BeforeBlockHookArguments extends ProvableHookBlockState {} - -export interface AfterBlockHookArguments extends BeforeBlockHookArguments { - stateRoot: Field; -} + BlockProverState, + BlockProverStateCommitments, +} from "../prover/block/BlockProvable"; -export function toBeforeTransactionHookArgument( - executionData: Omit< - BlockProverTransactionArguments, - "verificationKeyAttestation" - >, - networkState: NetworkState, - state: Parameters[0] -): BeforeTransactionHookArguments { - const { transaction, signature } = executionData; +import { TransitioningProtocolModule } from "./TransitioningProtocolModule"; +export type ProvableHookBlockState = Pick< + BlockProverStateCommitments, + | "transactionsHash" + | "eternalTransactionsHash" + | "incomingMessagesHash" + | "blockHashRoot" +>; + +export function toProvableHookBlockState( + state: Pick< + BlockProverState, + | "transactionList" + | "eternalTransactionsList" + | "incomingMessages" + | "blockHashRoot" + > +) { + const { + transactionList, + eternalTransactionsList, + incomingMessages, + blockHashRoot, + } = state; return { - networkState, - transaction, - signature, - prover: toProvableHookBlockState(state), + transactionsHash: transactionList.commitment, + eternalTransactionsHash: eternalTransactionsList.commitment, + incomingMessagesHash: incomingMessages.commitment, + blockHashRoot, }; } -export function toAfterTransactionHookArgument( - executionData: Omit< - BlockProverTransactionArguments, - "verificationKeyAttestation" - >, - networkState: NetworkState, - state: Parameters[0], - runtimeResult: MethodPublicOutput -): AfterTransactionHookArguments { - return { - ...toBeforeTransactionHookArgument(executionData, networkState, state), - runtimeResult, - }; +export interface BeforeBlockHookArguments extends ProvableHookBlockState {} + +export interface AfterBlockHookArguments extends BeforeBlockHookArguments { + stateRoot: Field; } // Purpose is to build transition from -> to network state diff --git a/packages/protocol/src/protocol/ProvableTransactionHook.ts b/packages/protocol/src/protocol/ProvableTransactionHook.ts index 32aec366f..68fa3bedf 100644 --- a/packages/protocol/src/protocol/ProvableTransactionHook.ts +++ b/packages/protocol/src/protocol/ProvableTransactionHook.ts @@ -4,41 +4,63 @@ import { Signature } from "o1js"; import { RuntimeTransaction } from "../model/transaction/RuntimeTransaction"; import { NetworkState } from "../model/network/NetworkState"; import { MethodPublicOutput } from "../model/MethodPublicOutput"; -import type { - BlockProverState, - BlockProverStateCommitments, -} from "../prover/block/BlockProvable"; +import { + TransactionProverPublicInput, + TransactionProverState, + TransactionProverTransactionArguments, +} from "../prover/transaction/TransactionProvable"; import { TransitioningProtocolModule } from "./TransitioningProtocolModule"; -export type ProvableHookBlockState = Pick< - BlockProverStateCommitments, - | "transactionsHash" - | "eternalTransactionsHash" - | "incomingMessagesHash" - | "blockHashRoot" +export type ProvableHookTransactionState = Pick< + TransactionProverPublicInput, + "transactionsHash" | "eternalTransactionsHash" | "incomingMessagesHash" >; -export function toProvableHookBlockState( +export function toProvableHookTransactionState( state: Pick< - BlockProverState, - | "transactionList" - | "eternalTransactionsList" - | "incomingMessages" - | "blockHashRoot" + TransactionProverState, + "transactionList" | "eternalTransactionsList" | "incomingMessages" > ) { - const { - transactionList, - eternalTransactionsList, - incomingMessages, - blockHashRoot, - } = state; + const { transactionList, eternalTransactionsList, incomingMessages } = state; return { transactionsHash: transactionList.commitment, eternalTransactionsHash: eternalTransactionsList.commitment, incomingMessagesHash: incomingMessages.commitment, - blockHashRoot, + }; +} + +export function toBeforeTransactionHookArgument( + executionData: Omit< + TransactionProverTransactionArguments, + "verificationKeyAttestation" + >, + networkState: NetworkState, + state: Parameters[0] +): BeforeTransactionHookArguments { + const { transaction, signature } = executionData; + + return { + networkState, + transaction, + signature, + prover: toProvableHookTransactionState(state), + }; +} + +export function toAfterTransactionHookArgument( + executionData: Omit< + TransactionProverTransactionArguments, + "verificationKeyAttestation" + >, + networkState: NetworkState, + state: Parameters[0], + runtimeResult: MethodPublicOutput +): AfterTransactionHookArguments { + return { + ...toBeforeTransactionHookArgument(executionData, networkState, state), + runtimeResult, }; } @@ -56,7 +78,7 @@ export interface BeforeTransactionHookArguments { transaction: RuntimeTransaction; signature: Signature; networkState: NetworkState; - prover: ProvableHookBlockState; + prover: ProvableHookTransactionState; } export interface AfterTransactionHookArguments diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index 84d6493af..c7f7299ba 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -1,18 +1,7 @@ -// eslint-disable-next-line max-classes-per-file -import { - Bool, - DynamicProof, - Field, - Proof, - Signature, - Struct, - Void, -} from "o1js"; -import { WithZkProgrammable, CompilableModule } from "@proto-kit/common"; +import { Bool, Field, Proof, Struct } from "o1js"; +import { CompilableModule, WithZkProgrammable } from "@proto-kit/common"; import { StateTransitionProof } from "../statetransition/StateTransitionProvable"; -import { MethodPublicOutput } from "../../model/MethodPublicOutput"; -import { RuntimeTransaction } from "../../model/transaction/RuntimeTransaction"; import { NetworkState } from "../../model/network/NetworkState"; import { TransactionHashList } from "../accumulators/TransactionHashList"; import { MinaActionsHashList } from "../../utils/MinaPrefixedProvableHashList"; @@ -21,9 +10,9 @@ import { WitnessedRootHashList, WitnessedRootWitness, } from "../accumulators/WitnessedRootHashList"; +import { TransactionProof } from "../transaction/TransactionProvable"; import { BlockHashMerkleTreeWitness } from "./accummulators/BlockHashMerkleTree"; -import { RuntimeVerificationKeyAttestation } from "./accummulators/RuntimeVerificationKeyTree"; // Should be equal to BlockProver.PublicInput export interface BlockProverState { @@ -150,56 +139,11 @@ export class BlockProverPublicOutput extends Struct({ } } -export type BlockProverProof = Proof< - BlockProverPublicInput, - BlockProverPublicOutput ->; - -export class BlockProverTransactionArguments extends Struct({ - transaction: RuntimeTransaction, - signature: Signature, - verificationKeyAttestation: RuntimeVerificationKeyAttestation, -}) {} - -export class DynamicRuntimeProof extends DynamicProof< - Void, - MethodPublicOutput -> { - static publicInputType = Void; - - static publicOutputType = MethodPublicOutput; - - // TODO this won't be 0 for proofs-as-args - static maxProofsVerified = 0 as const; -} - -export class BlockProverSingleTransactionExecutionData extends Struct({ - transaction: BlockProverTransactionArguments, - networkState: NetworkState, -}) {} - -export class BlockProverMultiTransactionExecutionData extends Struct({ - transaction1: BlockProverTransactionArguments, - transaction2: BlockProverTransactionArguments, - networkState: NetworkState, -}) {} +export type BlockProof = Proof; export interface BlockProvable extends WithZkProgrammable, CompilableModule { - proveTransaction: ( - publicInput: BlockProverPublicInput, - runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData - ) => Promise; - - proveTransactions: ( - publicInput: BlockProverPublicInput, - runtimeProof1: DynamicRuntimeProof, - runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData - ) => Promise; - proveBlock: ( publicInput: BlockProverPublicInput, networkState: NetworkState, @@ -207,12 +151,12 @@ export interface BlockProvable stateTransitionProof: StateTransitionProof, deferSTs: Bool, afterBlockRootWitness: WitnessedRootWitness, - transactionProof: BlockProverProof + transactionProof: TransactionProof ) => Promise; merge: ( publicInput: BlockProverPublicInput, - proof1: BlockProverProof, - proof2: BlockProverProof + proof1: BlockProof, + proof2: BlockProof ) => Promise; } diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index f97edd403..6b3d1e02d 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -1,12 +1,4 @@ -import { - Bool, - Field, - Proof, - Provable, - SelfProof, - VerificationKey, - ZkProgram, -} from "o1js"; +import { Bool, Field, Provable, SelfProof, ZkProgram } from "o1js"; import { container, inject, injectable, injectAll } from "tsyringe"; import { AreProofsEnabled, @@ -30,58 +22,34 @@ import { StateTransitionProverPublicOutput, } from "../statetransition/StateTransitionProvable"; import { RuntimeTransaction } from "../../model/transaction/RuntimeTransaction"; -import { - ProvableStateTransition, - StateTransition, -} from "../../model/StateTransition"; -import { - AfterTransactionHookArguments, - BeforeTransactionHookArguments, - ProvableTransactionHook, - toProvableHookBlockState, -} from "../../protocol/ProvableTransactionHook"; -import { - RuntimeMethodExecutionContext, - RuntimeMethodExecutionData, -} from "../../state/context/RuntimeMethodExecutionContext"; +import { RuntimeMethodExecutionContext } from "../../state/context/RuntimeMethodExecutionContext"; import { AfterBlockHookArguments, BeforeBlockHookArguments, ProvableBlockHook, - toAfterTransactionHookArgument, - toBeforeTransactionHookArgument, + toProvableHookBlockState, } from "../../protocol/ProvableBlockHook"; import { NetworkState } from "../../model/network/NetworkState"; -import { SignedTransaction } from "../../model/transaction/SignedTransaction"; -import { MinaActions } from "../../utils/MinaPrefixedProvableHashList"; -import { StateTransitionReductionList } from "../accumulators/StateTransitionReductionList"; import { assertEqualsIf } from "../../utils/utils"; import { WitnessedRootWitness } from "../accumulators/WitnessedRootHashList"; import { StateServiceProvider } from "../../state/StateServiceProvider"; -import { AppliedStateTransitionBatch } from "../../model/AppliedStateTransitionBatch"; +import { executeHooks } from "../utils"; +import { + TransactionProof, + TransactionProverPublicInput, +} from "../transaction/TransactionProvable"; import { BlockProvable, - BlockProverProof, + BlockProof, BlockProverPublicInput, BlockProverPublicOutput, - DynamicRuntimeProof, - BlockProverMultiTransactionExecutionData, - BlockProverTransactionArguments, - BlockProverSingleTransactionExecutionData, - BlockProverState, BlockProverStateCommitments, } from "./BlockProvable"; import { BlockHashMerkleTreeWitness, BlockHashTreeEntry, } from "./accummulators/BlockHashMerkleTree"; -import { - MethodVKConfigData, - MinimalVKTreeService, - RuntimeVerificationKeyAttestation, -} from "./accummulators/RuntimeVerificationKeyTree"; -import { RuntimeVerificationKeyRootService } from "./services/RuntimeVerificationKeyRootService"; const errors = { propertyNotMatchingStep: (propertyName: string, step: string) => @@ -97,18 +65,8 @@ const errors = { networkStateHashNotMatching: (step: string) => errors.propertyNotMatchingStep("Network state hash", step), - - invalidZkProgramTreeRoot: () => - "Root hash of the provided zkProgram config witness is invalid", }; -type ApplyTransactionArguments = Omit< - BlockProverTransactionArguments, - "verificationKeyAttestation" ->; - -export type BlockProof = Proof; - export class BlockProverProgrammable extends ZkProgrammable< BlockProverPublicInput, BlockProverPublicOutput @@ -119,10 +77,8 @@ export class BlockProverProgrammable extends ZkProgrammable< StateTransitionProverPublicInput, StateTransitionProverPublicOutput >, - private readonly transactionHooks: ProvableTransactionHook[], private readonly blockHooks: ProvableBlockHook[], - private readonly stateServiceProvider: StateServiceProvider, - private readonly verificationKeyService: MinimalVKTreeService + private readonly stateServiceProvider: StateServiceProvider ) { super(); } @@ -133,203 +89,6 @@ export class BlockProverProgrammable extends ZkProgrammable< return this.prover.areProofsEnabled; } - /** - * Applies and checks the two proofs and applies the corresponding state - * changes to the given state. - * - * The rough high level workflow of this function: - * 1. Execute beforeTransaction hooks, pushing the ST batch - * 2. Add Transaction to bundle, meaning appending it to all the respective commitments - * 3. Push the runtime ST batch - * 4. Execute afterTransaction hooks, pushing the ST batch - * 5. Some consistency checks and signature verification - * - * @param fromState The from-state of the BlockProver - * @param runtimeOutput - * @param executionData - * @param networkState - * @returns The new BlockProver-state to be used as public output - */ - public async applyTransaction( - fromState: BlockProverState, - runtimeOutput: MethodPublicOutput, - executionData: ApplyTransactionArguments, - networkState: NetworkState - ): Promise { - const { transaction, signature } = executionData; - - let state = { ...fromState }; - - const { isMessage } = runtimeOutput; - - const beforeTxHookArguments = toBeforeTransactionHookArgument( - executionData, - networkState, - state - ); - - // Apply beforeTransaction hook state transitions - const beforeBatch = await this.executeTransactionHooks( - async (module, args) => await module.beforeTransaction(args), - beforeTxHookArguments, - isMessage - ); - - state = this.addTransactionToBundle( - state, - runtimeOutput.isMessage, - transaction - ); - - state.pendingSTBatches.push(beforeBatch); - - state.pendingSTBatches.push({ - batchHash: runtimeOutput.stateTransitionsHash, - applied: runtimeOutput.status, - }); - - // Apply afterTransaction hook state transitions - const afterTxHookArguments = toAfterTransactionHookArgument( - executionData, - networkState, - state, - runtimeOutput - ); - - // Switch to different state set for afterTx hooks - this.stateServiceProvider.popCurrentStateService(); - - const afterBatch = await this.executeTransactionHooks( - async (module, args) => await module.afterTransaction(args), - afterTxHookArguments, - isMessage - ); - state.pendingSTBatches.push(afterBatch); - - // Check transaction integrity against appProof - const blockTransactionHash = transaction.hash(); - - blockTransactionHash.assertEquals( - runtimeOutput.transactionHash, - "Transactions provided in AppProof and BlockProof do not match" - ); - - // Check transaction signature - new SignedTransaction({ - transaction, - signature, - }) - .validateSignature() - .or(isMessage) - .assertTrue("Transaction signature not valid"); - - // Validate layout of transaction witness - transaction.assertTransactionType(isMessage); - - // Check network state integrity against appProof - state.networkState - .hash() - .assertEquals( - runtimeOutput.networkStateHash, - "Network state does not match state used in AppProof" - ); - - return state; - } - - // eslint-disable-next-line max-len - // TODO How does this interact with the RuntimeMethodExecutionContext when executing runtimemethods? - - /** - * Constructs a AppliedBatch based on a list of STs and the flag whether to - * be applied or not. The AppliedBatch is a condensed commitment to a batch - * of STs. - */ - private constructBatch( - stateTransitions: StateTransition[], - applied: Bool - ) { - const transitions = stateTransitions.map((transition) => - transition.toProvable() - ); - - const hashList = new StateTransitionReductionList(ProvableStateTransition); - transitions.forEach((transition) => { - hashList.push(transition); - }); - - return new AppliedStateTransitionBatch({ - batchHash: hashList.commitment, - applied, - }); - } - - private async executeTransactionHooks< - T extends BeforeTransactionHookArguments | AfterTransactionHookArguments, - >( - hook: (module: ProvableTransactionHook, args: T) => Promise, - hookArguments: T, - isMessage: Bool - ) { - const { batch, rawStatus } = await this.executeHooks( - hookArguments, - async () => { - for (const module of this.transactionHooks) { - // eslint-disable-next-line no-await-in-loop - await hook(module, hookArguments); - } - }, - isMessage - ); - - // This is going to set applied to false in case the hook fails - // (that's only possible for messages though as others are hard-asserted) - batch.applied = rawStatus; - - return batch; - } - - private async executeHooks( - contextArguments: RuntimeMethodExecutionData, - method: () => Promise, - isMessage: Bool | undefined = undefined - ) { - const executionContext = container.resolve(RuntimeMethodExecutionContext); - executionContext.clear(); - - // Setup context for potential calls to runtime methods. - // This way they can use this.transaction etc. while still having provable - // integrity between data - executionContext.setup(contextArguments); - executionContext.beforeMethod("", "", []); - - const result = await method(); - - executionContext.afterMethod(); - - const { stateTransitions, status, statusMessage } = - executionContext.current().result; - - // See https://github.com/proto-kit/framework/issues/321 for why we do this here - if (isMessage !== undefined) { - // isMessage is defined for all tx hooks - status - .or(isMessage) - .assertTrue( - `Transaction hook call failed for non-message tx: ${statusMessage ?? "-"}` - ); - } else { - // isMessage is undefined for all block hooks - status.assertTrue(`Block hook call failed: ${statusMessage ?? "-"}`); - } - - return { - batch: this.constructBatch(stateTransitions, Bool(true)), - result, - rawStatus: status, - }; - } - public async executeBlockHooks< T extends BeforeBlockHookArguments | AfterBlockHookArguments, >( @@ -347,7 +106,7 @@ export class BlockProverProgrammable extends ZkProgrammable< networkState: inputNetworkState, }; - return await this.executeHooks(startingInputs, async () => { + return await executeHooks(startingInputs, async () => { const executionContext = container.resolve(RuntimeMethodExecutionContext); return await this.blockHooks.reduce>( @@ -369,135 +128,6 @@ export class BlockProverProgrammable extends ZkProgrammable< }); } - public addTransactionToBundle< - T extends Pick< - BlockProverState, - "transactionList" | "eternalTransactionsList" | "incomingMessages" - >, - >(state: T, isMessage: Bool, transaction: RuntimeTransaction): T { - const transactionHash = transaction.hash(); - - // Append tx to transaction list - state.transactionList.pushIf(transactionHash, isMessage.not()); - - // Append tx to eternal transaction list - // TODO Change that to the a sequence-state compatible transaction struct - state.eternalTransactionsList.push(transactionHash); - - // Append tx to incomingMessagesHash - const actionHash = MinaActions.actionHash(transaction.hashData()); - - state.incomingMessages.pushIf(actionHash, isMessage); - - return state; - } - - private verifyVerificationKeyAttestation( - attestation: RuntimeVerificationKeyAttestation, - methodId: Field - ): VerificationKey { - // Verify the [methodId, vk] tuple against the baked-in vk tree root - const { verificationKey, witness: verificationKeyTreeWitness } = - attestation; - - const root = Field(this.verificationKeyService.getRoot()); - const calculatedRoot = verificationKeyTreeWitness.calculateRoot( - new MethodVKConfigData({ - methodId: methodId, - vkHash: verificationKey.hash, - }).hash() - ); - root.assertEquals(calculatedRoot, errors.invalidZkProgramTreeRoot()); - - return verificationKey; - } - - public async proveTransactionInternal( - fromState: BlockProverState, - runtimeProof: DynamicRuntimeProof, - { transaction, networkState }: BlockProverSingleTransactionExecutionData - ): Promise { - const verificationKey = this.verifyVerificationKeyAttestation( - transaction.verificationKeyAttestation, - transaction.transaction.methodId - ); - - runtimeProof.verify(verificationKey); - - return await this.applyTransaction( - fromState, - runtimeProof.publicOutput, - transaction, - networkState - ); - } - - private staticChecks(publicInput: BlockProverPublicInput) { - publicInput.blockNumber.assertEquals( - MAX_FIELD, - "blockNumber has to be MAX for transaction proofs" - ); - } - - @provableMethod() - public async proveTransaction( - publicInput: BlockProverPublicInput, - runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData - ): Promise { - const state = BlockProverStateCommitments.toBlockProverState( - publicInput, - executionData.networkState - ); - - this.staticChecks(publicInput); - - const stateTo = await this.proveTransactionInternal( - state, - runtimeProof, - executionData - ); - - return new BlockProverPublicOutput({ - ...BlockProverStateCommitments.fromBlockProverState(stateTo), - closed: Bool(false), - }); - } - - @provableMethod() - public async proveTransactions( - publicInput: BlockProverPublicInput, - runtimeProof1: DynamicRuntimeProof, - runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData - ): Promise { - const state = BlockProverStateCommitments.toBlockProverState( - publicInput, - executionData.networkState - ); - - this.staticChecks(publicInput); - - const state1 = await this.proveTransactionInternal(state, runtimeProof1, { - transaction: executionData.transaction1, - networkState: executionData.networkState, - }); - - // Switch to next state record for 2nd tx beforeTx hook - // TODO Can be prevented by merging 1st afterTx + 2nd beforeTx - this.stateServiceProvider.popCurrentStateService(); - - const stateTo = await this.proveTransactionInternal(state1, runtimeProof2, { - transaction: executionData.transaction2, - networkState: executionData.networkState, - }); - - return new BlockProverPublicOutput({ - ...BlockProverStateCommitments.fromBlockProverState(stateTo), - closed: Bool(false), - }); - } - public includeSTProof( stateTransitionProof: StateTransitionProof, apply: Bool, @@ -588,7 +218,7 @@ export class BlockProverProgrammable extends ZkProgrammable< stateTransitionProof: StateTransitionProof, deferSTProof: Bool, afterBlockRootWitness: WitnessedRootWitness, - transactionProof: BlockProverProof + transactionProof: TransactionProof ): Promise { // 1. Make assertions about the inputs publicInput.transactionsHash.assertEquals( @@ -597,14 +227,6 @@ export class BlockProverProgrammable extends ZkProgrammable< ); // TransactionProof format checks - transactionProof.publicInput.blockHashRoot.assertEquals( - Field(0), - "TransactionProof cannot carry the blockHashRoot - publicInput" - ); - transactionProof.publicOutput.blockHashRoot.assertEquals( - Field(0), - "TransactionProof cannot carry the blockHashRoot - publicOutput" - ); transactionProof.publicInput.networkStateHash.assertEquals( transactionProof.publicOutput.networkStateHash, "TransactionProof cannot alter the network state" @@ -619,10 +241,9 @@ export class BlockProverProgrammable extends ZkProgrammable< // input and output doesn't match fully // We have to compare the whole input and output because we can make no // assumptions about the values, since it can be an arbitrary dummy-proof - const txProofOutput = transactionProof.publicOutput; - const isEmptyTransition = txProofOutput.equals( - transactionProof.publicInput, - txProofOutput.closed + const isEmptyTransition = TransactionProverPublicInput.equals( + transactionProof.publicOutput, + transactionProof.publicInput ); const skipTransactionProofVerification = isEmptyTransition; const verifyTransactionProof = isEmptyTransition.not(); @@ -647,10 +268,6 @@ export class BlockProverProgrammable extends ZkProgrammable< .assertTrue( "TransactionProof networkstate hash not matching beforeBlock hook result" ); - transactionProof.publicInput.stateRoot.assertEquals( - transactionProof.publicOutput.stateRoot, - "TransactionProofs can't change the state root" - ); // Check that the transaction proof's STs start after the beforeBlock hook transactionProof.publicInput.pendingSTBatchesHash.assertEquals( @@ -767,8 +384,8 @@ export class BlockProverProgrammable extends ZkProgrammable< @provableMethod() public async merge( publicInput: BlockProverPublicInput, - proof1: BlockProverProof, - proof2: BlockProverProof + proof1: BlockProof, + proof2: BlockProof ): Promise { proof1.verify(); proof2.verify(); @@ -935,8 +552,6 @@ export class BlockProverProgrammable extends ZkProgrammable< >[] { const { prover, stateTransitionProver } = this; const StateTransitionProofClass = stateTransitionProver.zkProgram[0].Proof; - const proveTransaction = prover.proveTransaction.bind(prover); - const proveTransactions = prover.proveTransactions.bind(prover); const proveBlock = prover.proveBlock.bind(prover); const merge = prover.merge.bind(prover); @@ -946,51 +561,6 @@ export class BlockProverProgrammable extends ZkProgrammable< publicOutput: BlockProverPublicOutput, methods: { - proveTransaction: { - privateInputs: [ - DynamicRuntimeProof, - BlockProverSingleTransactionExecutionData, - ], - - async method( - publicInput: BlockProverPublicInput, - runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData - ) { - return { - publicOutput: await proveTransaction( - publicInput, - runtimeProof, - executionData - ), - }; - }, - }, - - proveTransactions: { - privateInputs: [ - DynamicRuntimeProof, - DynamicRuntimeProof, - BlockProverMultiTransactionExecutionData, - ], - - async method( - publicInput: BlockProverPublicInput, - runtimeProof1: DynamicRuntimeProof, - runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData - ) { - return { - publicOutput: await proveTransactions( - publicInput, - runtimeProof1, - runtimeProof2, - executionData - ), - }; - }, - }, - proveBlock: { privateInputs: [ NetworkState, @@ -1007,7 +577,7 @@ export class BlockProverProgrammable extends ZkProgrammable< stateTransitionProof: StateTransitionProof, deferSTs: Bool, afterBlockRootWitness: WitnessedRootWitness, - transactionProof: BlockProverProof + transactionProof: BlockProof ) { return { publicOutput: await proveBlock( @@ -1031,8 +601,8 @@ export class BlockProverProgrammable extends ZkProgrammable< async method( publicInput: BlockProverPublicInput, - proof1: BlockProverProof, - proof2: BlockProverProof + proof1: BlockProof, + proof2: BlockProof ) { return { publicOutput: await merge(publicInput, proof1, proof2) }; }, @@ -1041,8 +611,6 @@ export class BlockProverProgrammable extends ZkProgrammable< }); const methods = { - proveTransaction: program.proveTransaction, - proveTransactions: program.proveTransactions, proveBlock: program.proveBlock, merge: program.merge, }; @@ -1084,22 +652,17 @@ export class BlockProver @inject("Runtime") public readonly runtime: WithZkProgrammable & CompilableModule, - @injectAll("ProvableTransactionHook") - transactionHooks: ProvableTransactionHook[], @injectAll("ProvableBlockHook") blockHooks: ProvableBlockHook[], @inject("StateServiceProvider") - stateServiceProvider: StateServiceProvider, - verificationKeyService: RuntimeVerificationKeyRootService + stateServiceProvider: StateServiceProvider ) { super(); this.zkProgrammable = new BlockProverProgrammable( this, stateTransitionProver.zkProgrammable, - transactionHooks, blockHooks, - stateServiceProvider, - verificationKeyService + stateServiceProvider ); } @@ -1113,32 +676,6 @@ export class BlockProver }); } - public proveTransaction( - publicInput: BlockProverPublicInput, - runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData - ): Promise { - return this.zkProgrammable.proveTransaction( - publicInput, - runtimeProof, - executionData - ); - } - - public proveTransactions( - publicInput: BlockProverPublicInput, - runtimeProof1: DynamicRuntimeProof, - runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData - ): Promise { - return this.zkProgrammable.proveTransactions( - publicInput, - runtimeProof1, - runtimeProof2, - executionData - ); - } - public proveBlock( publicInput: BlockProverPublicInput, networkState: NetworkState, @@ -1146,7 +683,7 @@ export class BlockProver stateTransitionProof: StateTransitionProof, deferSTs: Bool, afterBlockRootWitness: WitnessedRootWitness, - transactionProof: BlockProverProof + transactionProof: TransactionProof ): Promise { return this.zkProgrammable.proveBlock( publicInput, @@ -1161,8 +698,8 @@ export class BlockProver public merge( publicInput: BlockProverPublicInput, - proof1: BlockProverProof, - proof2: BlockProverProof + proof1: BlockProof, + proof2: BlockProof ): Promise { return this.zkProgrammable.merge(publicInput, proof1, proof2); } diff --git a/packages/protocol/src/prover/transaction/TransactionProvable.ts b/packages/protocol/src/prover/transaction/TransactionProvable.ts new file mode 100644 index 000000000..4180fd906 --- /dev/null +++ b/packages/protocol/src/prover/transaction/TransactionProvable.ts @@ -0,0 +1,178 @@ +// eslint-disable-next-line max-classes-per-file +import { CompilableModule, WithZkProgrammable } from "@proto-kit/common"; +import { DynamicProof, Field, Proof, Signature, Struct, Void } from "o1js"; + +import { RuntimeTransaction } from "../../model/transaction/RuntimeTransaction"; +import { RuntimeVerificationKeyAttestation } from "../block/accummulators/RuntimeVerificationKeyTree"; +import { MethodPublicOutput } from "../../model/MethodPublicOutput"; +import { NetworkState } from "../../model/network/NetworkState"; +import { TransactionHashList } from "../accumulators/TransactionHashList"; +import { AppliedBatchHashList } from "../accumulators/AppliedBatchHashList"; +import { MinaActionsHashList } from "../../utils/MinaPrefixedProvableHashList"; +import { WitnessedRootHashList } from "../accumulators/WitnessedRootHashList"; + +export class TransactionProverState { + /** + * The current commitment of the transaction-list which + * will at the end equal the bundle hash + */ + transactionList: TransactionHashList; + + /** + * The network state which gives access to values such as blockHeight + * This value is the same for the whole batch (L2 block) + */ + networkState: NetworkState; + + /** + * A variant of the transactionsHash that is never reset. + * Thought for usage in the sequence state mempool. + * In comparison, transactionsHash restarts at 0 for every new block + */ + eternalTransactionsList: TransactionHashList; + + pendingSTBatches: AppliedBatchHashList; + + incomingMessages: MinaActionsHashList; + + witnessedRoots: WitnessedRootHashList; + + constructor(args: { + transactionList: TransactionHashList; + networkState: NetworkState; + eternalTransactionsList: TransactionHashList; + pendingSTBatches: AppliedBatchHashList; + incomingMessages: MinaActionsHashList; + witnessedRoots: WitnessedRootHashList; + }) { + this.transactionList = args.transactionList; + this.networkState = args.networkState; + this.eternalTransactionsList = args.eternalTransactionsList; + this.pendingSTBatches = args.pendingSTBatches; + this.incomingMessages = args.incomingMessages; + this.witnessedRoots = args.witnessedRoots; + } + + public toCommitments(): TransactionProverPublicInput { + return { + networkStateHash: this.networkState.hash(), + pendingSTBatchesHash: this.pendingSTBatches.commitment, + transactionsHash: this.transactionList.commitment, + eternalTransactionsHash: this.eternalTransactionsList.commitment, + incomingMessagesHash: this.incomingMessages.commitment, + witnessedRootsHash: this.witnessedRoots.commitment, + }; + } + + public static fromCommitments( + publicInput: TransactionProverPublicInput, + networkState: NetworkState + ): TransactionProverState { + publicInput.networkStateHash.assertEquals( + networkState.hash(), + "ExecutionData Networkstate doesn't equal public input hash" + ); + + return new TransactionProverState({ + networkState, + transactionList: new TransactionHashList(publicInput.transactionsHash), + eternalTransactionsList: new TransactionHashList( + publicInput.eternalTransactionsHash + ), + incomingMessages: new MinaActionsHashList( + publicInput.incomingMessagesHash + ), + pendingSTBatches: new AppliedBatchHashList( + publicInput.pendingSTBatchesHash + ), + witnessedRoots: new WitnessedRootHashList(publicInput.witnessedRootsHash), + }); + } +} + +export const TransactionProverStateCommitments = { + transactionsHash: Field, + // Commitment to the list of unprocessed (pending) batches of STs that need to be proven + pendingSTBatchesHash: Field, + witnessedRootsHash: Field, + networkStateHash: Field, + eternalTransactionsHash: Field, + incomingMessagesHash: Field, +}; + +export class TransactionProverPublicInput extends Struct( + TransactionProverStateCommitments +) { + public static equals( + input1: TransactionProverPublicInput, + input2: TransactionProverPublicInput + ) { + const output2 = TransactionProverPublicInput.toFields(input2); + const output1 = TransactionProverPublicInput.toFields(input1); + return output1 + .map((value1, index) => value1.equals(output2[index])) + .reduce((a, b) => a.and(b)); + } +} + +export class TransactionProverPublicOutput extends TransactionProverPublicInput {} + +export class TransactionProverTransactionArguments extends Struct({ + transaction: RuntimeTransaction, + signature: Signature, + verificationKeyAttestation: RuntimeVerificationKeyAttestation, +}) {} + +export class DynamicRuntimeProof extends DynamicProof< + Void, + MethodPublicOutput +> { + static publicInputType = Void; + + static publicOutputType = MethodPublicOutput; + + // TODO this won't be 0 for proofs-as-args + static maxProofsVerified = 0 as const; +} + +export class BlockProverSingleTransactionExecutionData extends Struct({ + transaction: TransactionProverTransactionArguments, + networkState: NetworkState, +}) {} + +export class BlockProverMultiTransactionExecutionData extends Struct({ + transaction1: TransactionProverTransactionArguments, + transaction2: TransactionProverTransactionArguments, + networkState: NetworkState, +}) {} + +export type TransactionProof = Proof< + TransactionProverPublicInput, + TransactionProverPublicOutput +>; + +export interface TransactionProvable + extends WithZkProgrammable< + TransactionProverPublicInput, + TransactionProverPublicOutput + >, + CompilableModule { + proveTransaction: ( + publicInput: TransactionProverPublicInput, + runtimeProof: DynamicRuntimeProof, + executionData: BlockProverSingleTransactionExecutionData + ) => Promise; + + proveTransactions: ( + publicInput: TransactionProverPublicInput, + runtimeProof1: DynamicRuntimeProof, + runtimeProof2: DynamicRuntimeProof, + executionData: BlockProverMultiTransactionExecutionData + ) => Promise; + + merge: ( + publicInput: TransactionProverPublicInput, + proof1: TransactionProof, + proof2: TransactionProof + ) => Promise; +} diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts new file mode 100644 index 000000000..9294edda8 --- /dev/null +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -0,0 +1,573 @@ +import { + AreProofsEnabled, + CompilableModule, + CompileArtifact, + CompileRegistry, + PlainZkProgram, + provableMethod, + WithZkProgrammable, + ZkProgrammable, +} from "@proto-kit/common"; +import { Bool, Field, SelfProof, VerificationKey, ZkProgram } from "o1js"; +import { inject, injectable, injectAll } from "tsyringe"; + +import { NetworkState } from "../../model/network/NetworkState"; +import { ProtocolModule } from "../../protocol/ProtocolModule"; +import { MethodPublicOutput } from "../../model/MethodPublicOutput"; +import { + AfterTransactionHookArguments, + BeforeTransactionHookArguments, + ProvableTransactionHook, + toAfterTransactionHookArgument, + toBeforeTransactionHookArgument, +} from "../../protocol/ProvableTransactionHook"; +import { StateServiceProvider } from "../../state/StateServiceProvider"; +import { RuntimeVerificationKeyRootService } from "../block/services/RuntimeVerificationKeyRootService"; +import { addTransactionToBundle, executeHooks } from "../utils"; +import { SignedTransaction } from "../../model/transaction/SignedTransaction"; +import { + MethodVKConfigData, + MinimalVKTreeService, + RuntimeVerificationKeyAttestation, +} from "../block/accummulators/RuntimeVerificationKeyTree"; + +import { + BlockProverMultiTransactionExecutionData, + BlockProverSingleTransactionExecutionData, + DynamicRuntimeProof, + TransactionProof, + TransactionProvable, + TransactionProverPublicInput, + TransactionProverPublicOutput, + TransactionProverState, + TransactionProverTransactionArguments, +} from "./TransactionProvable"; + +const errors = { + invalidZkProgramTreeRoot: () => + "Root hash of the provided zkProgram config witness is invalid", + + propertyNotMatchingStep: (propertyName: string, step: string) => + `${propertyName} not matching: ${step}`, + + stateRootNotMatching: (step: string) => + errors.propertyNotMatchingStep("StateRoots", step), + + transactionsHashNotMatching: (step: string) => + errors.propertyNotMatchingStep("Transactions hash", step), + + networkStateHashNotMatching: (step: string) => + errors.propertyNotMatchingStep("Network state hash", step), +}; + +type ApplyTransactionArguments = Omit< + TransactionProverTransactionArguments, + "verificationKeyAttestation" +>; + +export class TransactionProverZkProgrammable extends ZkProgrammable< + TransactionProverPublicInput, + TransactionProverPublicOutput +> { + public constructor( + private readonly prover: TransactionProver, + private readonly transactionHooks: ProvableTransactionHook[], + private readonly stateServiceProvider: StateServiceProvider, + private readonly verificationKeyService: MinimalVKTreeService + ) { + super(); + } + + name = "TransactionProver"; + + public get areProofsEnabled(): AreProofsEnabled | undefined { + return this.prover.areProofsEnabled; + } + + /** + * Applies and checks the two proofs and applies the corresponding state + * changes to the given state. + * + * The rough high level workflow of this function: + * 1. Execute beforeTransaction hooks, pushing the ST batch + * 2. Add Transaction to bundle, meaning appending it to all the respective commitments + * 3. Push the runtime ST batch + * 4. Execute afterTransaction hooks, pushing the ST batch + * 5. Some consistency checks and signature verification + * + * @param fromState The from-state of the BlockProver + * @param runtimeOutput + * @param executionData + * @param networkState + * @returns The new BlockProver-state to be used as public output + */ + public async applyTransaction( + fromState: TransactionProverState, + runtimeOutput: MethodPublicOutput, + executionData: ApplyTransactionArguments, + networkState: NetworkState + ): Promise { + const { transaction, signature } = executionData; + + let state = { ...fromState }; + + const { isMessage } = runtimeOutput; + + const beforeTxHookArguments = toBeforeTransactionHookArgument( + executionData, + networkState, + state + ); + + // Apply beforeTransaction hook state transitions + const beforeBatch = await this.executeTransactionHooks( + async (module, args) => await module.beforeTransaction(args), + beforeTxHookArguments, + isMessage + ); + + state = addTransactionToBundle(state, runtimeOutput.isMessage, transaction); + + state.pendingSTBatches.push(beforeBatch); + + state.pendingSTBatches.push({ + batchHash: runtimeOutput.stateTransitionsHash, + applied: runtimeOutput.status, + }); + + // Apply afterTransaction hook state transitions + const afterTxHookArguments = toAfterTransactionHookArgument( + executionData, + networkState, + state, + runtimeOutput + ); + + // Switch to different state set for afterTx hooks + this.stateServiceProvider.popCurrentStateService(); + + const afterBatch = await this.executeTransactionHooks( + async (module, args) => await module.afterTransaction(args), + afterTxHookArguments, + isMessage + ); + state.pendingSTBatches.push(afterBatch); + + // Check transaction integrity against appProof + const blockTransactionHash = transaction.hash(); + + blockTransactionHash.assertEquals( + runtimeOutput.transactionHash, + "Transactions provided in AppProof and BlockProof do not match" + ); + + // Check transaction signature + new SignedTransaction({ + transaction, + signature, + }) + .validateSignature() + .or(isMessage) + .assertTrue("Transaction signature not valid"); + + // Validate layout of transaction witness + transaction.assertTransactionType(isMessage); + + // Check network state integrity against appProof + state.networkState + .hash() + .assertEquals( + runtimeOutput.networkStateHash, + "Network state does not match state used in AppProof" + ); + + return new TransactionProverState(state); + } + + private verifyVerificationKeyAttestation( + attestation: RuntimeVerificationKeyAttestation, + methodId: Field + ): VerificationKey { + // Verify the [methodId, vk] tuple against the baked-in vk tree root + const { verificationKey, witness: verificationKeyTreeWitness } = + attestation; + + const root = Field(this.verificationKeyService.getRoot()); + const calculatedRoot = verificationKeyTreeWitness.calculateRoot( + new MethodVKConfigData({ + methodId: methodId, + vkHash: verificationKey.hash, + }).hash() + ); + root.assertEquals(calculatedRoot, errors.invalidZkProgramTreeRoot()); + + return verificationKey; + } + + private async executeTransactionHooks< + T extends BeforeTransactionHookArguments | AfterTransactionHookArguments, + >( + hook: (module: ProvableTransactionHook, args: T) => Promise, + hookArguments: T, + isMessage: Bool + ) { + const { batch, rawStatus } = await executeHooks( + hookArguments, + async () => { + for (const module of this.transactionHooks) { + // eslint-disable-next-line no-await-in-loop + await hook(module, hookArguments); + } + }, + isMessage + ); + + // This is going to set applied to false in case the hook fails + // (that's only possible for messages though as others are hard-asserted) + batch.applied = rawStatus; + + return batch; + } + + public async proveTransactionInternal( + fromState: TransactionProverState, + runtimeProof: DynamicRuntimeProof, + { transaction, networkState }: BlockProverSingleTransactionExecutionData + ): Promise { + const verificationKey = this.verifyVerificationKeyAttestation( + transaction.verificationKeyAttestation, + transaction.transaction.methodId + ); + + runtimeProof.verify(verificationKey); + + return await this.applyTransaction( + fromState, + runtimeProof.publicOutput, + transaction, + networkState + ); + } + + @provableMethod() + public async proveTransaction( + publicInput: TransactionProverPublicInput, + runtimeProof: DynamicRuntimeProof, + executionData: BlockProverSingleTransactionExecutionData + ): Promise { + const state = TransactionProverState.fromCommitments( + publicInput, + executionData.networkState + ); + + const stateTo = await this.proveTransactionInternal( + state, + runtimeProof, + executionData + ); + + return new TransactionProverPublicOutput(stateTo.toCommitments()); + } + + @provableMethod() + public async proveTransactions( + publicInput: TransactionProverPublicInput, + runtimeProof1: DynamicRuntimeProof, + runtimeProof2: DynamicRuntimeProof, + executionData: BlockProverMultiTransactionExecutionData + ): Promise { + const state = TransactionProverState.fromCommitments( + publicInput, + executionData.networkState + ); + + // this.staticChecks(publicInput); + + const state1 = await this.proveTransactionInternal(state, runtimeProof1, { + transaction: executionData.transaction1, + networkState: executionData.networkState, + }); + + // Switch to next state record for 2nd tx beforeTx hook + // TODO Can be prevented by merging 1st afterTx + 2nd beforeTx + this.stateServiceProvider.popCurrentStateService(); + + const stateTo = await this.proveTransactionInternal(state1, runtimeProof2, { + transaction: executionData.transaction2, + networkState: executionData.networkState, + }); + + return new TransactionProverPublicOutput(stateTo.toCommitments()); + } + + @provableMethod() + public async merge( + publicInput: TransactionProverPublicInput, + proof1: TransactionProof, + proof2: TransactionProof + ): Promise { + proof1.verify(); + proof2.verify(); + + // Check transaction list hash. + // Only assert them if these are tx proofs, skip for closed proofs + publicInput.transactionsHash + .equals(proof1.publicInput.transactionsHash) + .assertTrue( + errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + ); + proof1.publicOutput.transactionsHash + .equals(proof2.publicInput.transactionsHash) + .assertTrue( + errors.transactionsHashNotMatching("proof1.to -> proof2.from") + ); + + // Check networkhash + publicInput.networkStateHash.assertEquals( + proof1.publicInput.networkStateHash, + errors.networkStateHashNotMatching("publicInput.from -> proof1.from") + ); + proof1.publicOutput.networkStateHash.assertEquals( + proof2.publicInput.networkStateHash, + errors.networkStateHashNotMatching("proof1.to -> proof2.from") + ); + + // Check eternalTransactionsHash + publicInput.eternalTransactionsHash.assertEquals( + proof1.publicInput.eternalTransactionsHash, + errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + ); + proof1.publicOutput.eternalTransactionsHash.assertEquals( + proof2.publicInput.eternalTransactionsHash, + errors.transactionsHashNotMatching("proof1.to -> proof2.from") + ); + + // Check incomingMessagesHash + publicInput.incomingMessagesHash.assertEquals( + proof1.publicInput.incomingMessagesHash, + errors.propertyNotMatchingStep( + "IncomingMessagesHash", + "publicInput.from -> proof1.from" + ) + ); + proof1.publicOutput.incomingMessagesHash.assertEquals( + proof2.publicInput.incomingMessagesHash, + errors.propertyNotMatchingStep( + "IncomingMessagesHash", + "proof1.to -> proof2.from" + ) + ); + + // Check pendingSTBatchesHash + publicInput.pendingSTBatchesHash.assertEquals( + proof1.publicInput.pendingSTBatchesHash, + errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + ); + proof1.publicOutput.pendingSTBatchesHash.assertEquals( + proof2.publicInput.pendingSTBatchesHash, + errors.transactionsHashNotMatching("proof1.to -> proof2.from") + ); + + // Check witnessedRootsHash + publicInput.witnessedRootsHash.assertEquals( + proof1.publicInput.witnessedRootsHash, + errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + ); + proof1.publicOutput.witnessedRootsHash.assertEquals( + proof2.publicInput.witnessedRootsHash, + errors.transactionsHashNotMatching("proof1.to -> proof2.from") + ); + + return new TransactionProverPublicOutput({ + transactionsHash: proof2.publicOutput.transactionsHash, + networkStateHash: proof2.publicOutput.networkStateHash, + eternalTransactionsHash: proof2.publicOutput.eternalTransactionsHash, + incomingMessagesHash: proof2.publicOutput.incomingMessagesHash, + pendingSTBatchesHash: proof2.publicOutput.pendingSTBatchesHash, + witnessedRootsHash: proof2.publicOutput.witnessedRootsHash, + }); + } + + /** + * Creates the BlockProver ZkProgram. + * Recursive linking of proofs is done via the previously + * injected StateTransitionProver and the required AppChainProof class + */ + public zkProgramFactory(): PlainZkProgram< + TransactionProverPublicInput, + TransactionProverPublicOutput + >[] { + const { prover } = this; + const proveTransaction = prover.proveTransaction.bind(prover); + const proveTransactions = prover.proveTransactions.bind(prover); + const merge = prover.merge.bind(prover); + + const program = ZkProgram({ + name: "BlockProver", + publicInput: TransactionProverPublicInput, + publicOutput: TransactionProverPublicOutput, + + methods: { + proveTransaction: { + privateInputs: [ + DynamicRuntimeProof, + BlockProverSingleTransactionExecutionData, + ], + + async method( + publicInput: TransactionProverPublicInput, + runtimeProof: DynamicRuntimeProof, + executionData: BlockProverSingleTransactionExecutionData + ) { + return { + publicOutput: await proveTransaction( + publicInput, + runtimeProof, + executionData + ), + }; + }, + }, + + proveTransactions: { + privateInputs: [ + DynamicRuntimeProof, + DynamicRuntimeProof, + BlockProverMultiTransactionExecutionData, + ], + + async method( + publicInput: TransactionProverPublicInput, + runtimeProof1: DynamicRuntimeProof, + runtimeProof2: DynamicRuntimeProof, + executionData: BlockProverMultiTransactionExecutionData + ) { + return { + publicOutput: await proveTransactions( + publicInput, + runtimeProof1, + runtimeProof2, + executionData + ), + }; + }, + }, + + merge: { + privateInputs: [ + SelfProof< + TransactionProverPublicInput, + TransactionProverPublicOutput + >, + SelfProof< + TransactionProverPublicInput, + TransactionProverPublicOutput + >, + ], + + async method( + publicInput: TransactionProverPublicInput, + proof1: TransactionProof, + proof2: TransactionProof + ) { + return { publicOutput: await merge(publicInput, proof1, proof2) }; + }, + }, + }, + }); + + const methods = { + proveTransaction: program.proveTransaction, + proveTransactions: program.proveTransactions, + merge: program.merge, + }; + + const SelfProofClass = ZkProgram.Proof(program); + + return [ + { + name: program.name, + compile: program.compile.bind(program), + verify: program.verify.bind(program), + analyzeMethods: program.analyzeMethods.bind(program), + Proof: SelfProofClass, + methods, + }, + ]; + } +} + +/** + * BlockProver class, which aggregates a AppChainProof and + * a StateTransitionProof into a single BlockProof, that can + * then be merged to be committed to the base-layer contract + */ +@injectable() +export class TransactionProver + extends ProtocolModule + implements TransactionProvable, CompilableModule +{ + public zkProgrammable: TransactionProverZkProgrammable; + + public constructor( + @inject("Runtime") + public readonly runtime: WithZkProgrammable & + CompilableModule, + @injectAll("ProvableTransactionHook") + transactionHooks: ProvableTransactionHook[], + @inject("StateServiceProvider") + stateServiceProvider: StateServiceProvider, + verificationKeyService: RuntimeVerificationKeyRootService + ) { + super(); + this.zkProgrammable = new TransactionProverZkProgrammable( + this, + transactionHooks, + stateServiceProvider, + verificationKeyService + ); + } + + public async compile( + registry: CompileRegistry + ): Promise | undefined> { + return await registry.forceProverExists(async () => { + await this.runtime.compile(registry); + return await this.zkProgrammable.compile(registry); + }); + } + + public proveTransaction( + publicInput: TransactionProverPublicInput, + runtimeProof: DynamicRuntimeProof, + executionData: BlockProverSingleTransactionExecutionData + ): Promise { + return this.zkProgrammable.proveTransaction( + publicInput, + runtimeProof, + executionData + ); + } + + public proveTransactions( + publicInput: TransactionProverPublicInput, + runtimeProof1: DynamicRuntimeProof, + runtimeProof2: DynamicRuntimeProof, + executionData: BlockProverMultiTransactionExecutionData + ): Promise { + return this.zkProgrammable.proveTransactions( + publicInput, + runtimeProof1, + runtimeProof2, + executionData + ); + } + + public merge( + publicInput: TransactionProverPublicInput, + proof1: TransactionProof, + proof2: TransactionProof + ): Promise { + return this.zkProgrammable.merge(publicInput, proof1, proof2); + } +} diff --git a/packages/protocol/src/prover/utils.ts b/packages/protocol/src/prover/utils.ts new file mode 100644 index 000000000..5b67de0f1 --- /dev/null +++ b/packages/protocol/src/prover/utils.ts @@ -0,0 +1,106 @@ +import { Bool } from "o1js"; +import { container } from "tsyringe"; + +import { + ProvableStateTransition, + StateTransition, +} from "../model/StateTransition"; +import { AppliedStateTransitionBatch } from "../model/AppliedStateTransitionBatch"; +import { + RuntimeMethodExecutionContext, + RuntimeMethodExecutionData, +} from "../state/context/RuntimeMethodExecutionContext"; +import { RuntimeTransaction } from "../model/transaction/RuntimeTransaction"; +import { MinaActions } from "../utils/MinaPrefixedProvableHashList"; + +import { StateTransitionReductionList } from "./accumulators/StateTransitionReductionList"; +import { TransactionProverState } from "./transaction/TransactionProvable"; + +/** + * Constructs a AppliedBatch based on a list of STs and the flag whether to + * be applied or not. The AppliedBatch is a condensed commitment to a batch + * of STs. + */ +export function constructBatch( + stateTransitions: StateTransition[], + applied: Bool +) { + const transitions = stateTransitions.map((transition) => + transition.toProvable() + ); + + const hashList = new StateTransitionReductionList(ProvableStateTransition); + transitions.forEach((transition) => { + hashList.push(transition); + }); + + return new AppliedStateTransitionBatch({ + batchHash: hashList.commitment, + applied, + }); +} + +// TODO How does this interact with the RuntimeMethodExecutionContext when executing runtimemethods? +export async function executeHooks( + contextArguments: RuntimeMethodExecutionData, + method: () => Promise, + isMessage: Bool | undefined = undefined +) { + const executionContext = container.resolve(RuntimeMethodExecutionContext); + executionContext.clear(); + + // Setup context for potential calls to runtime methods. + // This way they can use this.transaction etc. while still having provable + // integrity between data + executionContext.setup(contextArguments); + executionContext.beforeMethod("", "", []); + + const result = await method(); + + executionContext.afterMethod(); + + const { stateTransitions, status, statusMessage } = + executionContext.current().result; + + // See https://github.com/proto-kit/framework/issues/321 for why we do this here + if (isMessage !== undefined) { + // isMessage is defined for all tx hooks + status + .or(isMessage) + .assertTrue( + `Transaction hook call failed for non-message tx: ${statusMessage ?? "-"}` + ); + } else { + // isMessage is undefined for all block hooks + status.assertTrue(`Block hook call failed: ${statusMessage ?? "-"}`); + } + + return { + batch: constructBatch(stateTransitions, Bool(true)), + result, + rawStatus: status, + }; +} + +export function addTransactionToBundle< + T extends Pick< + TransactionProverState, + "transactionList" | "eternalTransactionsList" | "incomingMessages" + >, +>(state: T, isMessage: Bool, transaction: RuntimeTransaction): T { + const transactionHash = transaction.hash(); + + // Append tx to transaction list + state.transactionList.pushIf(transactionHash, isMessage.not()); + + // Append tx to eternal transaction list + // TODO Change that to the a sequence-state compatible transaction struct + state.eternalTransactionsList.push(transactionHash); + + // Append tx to incomingMessagesHash + const actionHash = MinaActions.actionHash(transaction.hashData()); + + state.incomingMessages.pushIf(actionHash, isMessage); + + return state; +} diff --git a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts index 65ccf7a12..67148bfa1 100644 --- a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts +++ b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts @@ -3,7 +3,7 @@ import { InferProofBase } from "@proto-kit/common"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { NetworkState } from "../../model/network/NetworkState"; -import type { BlockProof } from "../../prover/block/BlockProver"; +import type { BlockProof } from "../../prover/block/BlockProvable"; import type { SettlementContractType } from "../contracts/settlement/SettlementBase"; export type InputBlockProof = InferProofBase; From d6b341810be0b175aa52eb5db7f575823e5bcec3 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 14:44:49 +0100 Subject: [PATCH 068/155] Separated TransactionProver from BlockProver: Sequencer + Library --- .../src/protocol/VanillaProtocolModules.ts | 2 + .../src/protocol/production/flow/BlockFlow.ts | 23 +++---- .../sequencing/TransactionExecutionService.ts | 9 +-- .../production/tasks/BlockReductionTask.ts | 4 +- .../protocol/production/tasks/NewBlockTask.ts | 9 ++- .../tasks/TransactionProvingTask.ts | 19 ++--- .../tasks/TransactionReductionTask.ts | 69 +++++++++++++++++++ .../NewBlockProvingParametersSerializer.ts | 17 ++--- ...ansactionProvingTaskParameterSerializer.ts | 36 +++++----- .../tracing/TransactionTracingService.ts | 23 ++----- .../sequencer/test/settlement/Settlement.ts | 1 + packages/stack/src/scripts/graphql/server.ts | 1 + 12 files changed, 135 insertions(+), 78 deletions(-) create mode 100644 packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts diff --git a/packages/library/src/protocol/VanillaProtocolModules.ts b/packages/library/src/protocol/VanillaProtocolModules.ts index 89a87d08b..81332e714 100644 --- a/packages/library/src/protocol/VanillaProtocolModules.ts +++ b/packages/library/src/protocol/VanillaProtocolModules.ts @@ -6,6 +6,7 @@ import { ProtocolModulesRecord, StateTransitionProver, LastStateRootBlockHook, + TransactionProver, } from "@proto-kit/protocol"; import { PrivateKey } from "o1js"; @@ -21,6 +22,7 @@ export class VanillaProtocolModules { ): MandatoryProtocolModulesRecord & ProtocolModules { return { StateTransitionProver, + TransactionProver, BlockProver, AccountState: AccountStateHook, BlockHeight: BlockHeightHook, diff --git a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts index 8dea8b59c..7f5a9de00 100644 --- a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts @@ -1,19 +1,19 @@ import { inject, injectable, Lifecycle, scoped } from "tsyringe"; import { - BlockProof, BlockProverPublicInput, BlockProverPublicOutput, MandatoryProtocolModulesRecord, Protocol, + TransactionProof, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { MAX_FIELD } from "@proto-kit/common"; import { TransactionProvingTask } from "../tasks/TransactionProvingTask"; -import { BlockReductionTask } from "../tasks/BlockReductionTask"; import { TransactionProvingTaskParameters } from "../tasks/serializers/types/TransactionProvingTypes"; import { FlowCreator } from "../../../worker/flow/Flow"; import { BlockTrace } from "../tracing/BlockTracingService"; +import { TransactionReductionTask } from "../tasks/TransactionReductionTask"; import { ReductionTaskFlow } from "./ReductionTaskFlow"; import { TransactionFlow } from "./TransactionFlow"; @@ -26,7 +26,7 @@ export class BlockFlow { @inject("Protocol") private readonly protocol: Protocol, private readonly transactionProvingTask: TransactionProvingTask, - private readonly blockReductionTask: BlockReductionTask, + private readonly transactionReductionTask: TransactionReductionTask, private readonly transactionFlow: TransactionFlow ) {} @@ -54,22 +54,19 @@ export class BlockFlow { private async executeTransactions( trace: BlockTrace - ): Promise> { + ): Promise< + ReductionTaskFlow + > { const transactionFlow = new ReductionTaskFlow( { name: `transactions-${trace.height}`, inputLength: trace.transactions.length, mappingTask: this.transactionProvingTask, - reductionTask: this.blockReductionTask, + reductionTask: this.transactionReductionTask, mergableFunction: (a, b) => - a.publicOutput.stateRoot - .equals(b.publicInput.stateRoot) - .and( - a.publicOutput.transactionsHash.equals( - b.publicInput.transactionsHash - ) - ) + a.publicOutput.transactionsHash + .equals(b.publicInput.transactionsHash) .and( a.publicInput.networkStateHash.equals( b.publicInput.networkStateHash @@ -99,7 +96,7 @@ export class BlockFlow { public async executeBlock( trace: BlockTrace, - callback: (proof: BlockProof) => Promise + callback: (proof: TransactionProof) => Promise ) { if (trace.transactions.length === 0) { const proof = await this.dummyTransactionProof(trace); diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index 753cfbb8f..24dc9852a 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -13,8 +13,6 @@ import { MandatoryProtocolModulesRecord, reduceStateTransitions, StateTransition, - BlockProver, - BlockProverProgrammable, BeforeTransactionHookArguments, AfterTransactionHookArguments, BlockProverState, @@ -23,6 +21,7 @@ import { toAfterTransactionHookArgument, ProvableStateTransition, DefaultProvableHashList, + addTransactionToBundle, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { AreProofsEnabled, log, mapSequential } from "@proto-kit/common"; @@ -210,8 +209,6 @@ export type TransactionExecutionResultStatus = export class TransactionExecutionService { private readonly transactionHooks: ProvableTransactionHook[]; - private readonly blockProver: BlockProverProgrammable; - private readonly txHooks: ProvableTransactionHook[]; public constructor( @@ -227,8 +224,6 @@ export class TransactionExecutionService { this.transactionHooks = protocol.dependencyContainer.resolveAll( "ProvableTransactionHook" ); - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - this.blockProver = (protocol.blockProver as BlockProver).zkProgrammable; this.txHooks = protocol.dependencyContainer.resolveAll( @@ -320,7 +315,7 @@ export class TransactionExecutionService { ): BlockTrackers { const signedTransaction = tx.toProtocolTransaction(); // Add tx to commitments - return this.blockProver.addTransactionToBundle( + return addTransactionToBundle( state, Bool(tx.isMessage), signedTransaction.transaction diff --git a/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts b/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts index 068bd3488..24b5423a0 100644 --- a/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts @@ -18,7 +18,6 @@ import { PairTuple, ProofTaskSerializer, } from "../../../helpers/utils"; -import { VerificationKeyService } from "../../runtime/RuntimeVerificationKeyService"; @injectable() @scoped(Lifecycle.ContainerScoped) @@ -36,8 +35,7 @@ export class BlockReductionTask MandatoryProtocolModulesRecord & ProtocolModulesRecord >, private readonly executionContext: ProvableMethodExecutionContext, - private readonly compileRegistry: CompileRegistry, - private readonly verificationKeyService: VerificationKeyService + private readonly compileRegistry: CompileRegistry ) { super(); this.blockProver = this.protocol.blockProver; diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 460d0c337..806a5b459 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -2,7 +2,6 @@ import { inject, injectable, Lifecycle, scoped } from "tsyringe"; import { BlockProvable, BlockProverPublicInput, - BlockProverPublicOutput, NetworkState, Protocol, StateTransitionProof, @@ -10,8 +9,10 @@ import { BlockHashMerkleTreeWitness, MandatoryProtocolModulesRecord, WitnessedRootWitness, + TransactionProof, + BlockProof, } from "@proto-kit/protocol"; -import { Bool, Proof } from "o1js"; +import { Bool } from "o1js"; import { ProvableMethodExecutionContext, CompileRegistry, @@ -26,8 +27,6 @@ import type { TaskStateRecord } from "../tracing/BlockTracingService"; import { NewBlockProvingParametersSerializer } from "./serializers/NewBlockProvingParametersSerializer"; import { executeWithPrefilledStateService } from "./TransactionProvingTask"; -type BlockProof = Proof; - export interface NewBlockProverParameters { publicInput: BlockProverPublicInput; networkState: NetworkState; @@ -40,7 +39,7 @@ export interface NewBlockProverParameters { export type NewBlockProvingParameters = PairingDerivedInput< StateTransitionProof, - BlockProof, + TransactionProof, NewBlockProverParameters >; diff --git a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts index 38473a22c..2de3ac069 100644 --- a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts @@ -1,11 +1,12 @@ import { BlockProof, - BlockProvable, MandatoryProtocolModulesRecord, Protocol, ProtocolModulesRecord, StateServiceProvider, DynamicRuntimeProof, + TransactionProvable, + TransactionProof, } from "@proto-kit/protocol"; import { Runtime } from "@proto-kit/module"; import { inject, injectable, Lifecycle, scoped } from "tsyringe"; @@ -53,9 +54,9 @@ export async function executeWithPrefilledStateService( @scoped(Lifecycle.ContainerScoped) export class TransactionProvingTask extends TaskWorkerModule - implements Task + implements Task { - private readonly blockProver: BlockProvable; + private readonly transactionProver: TransactionProvable; private readonly runtimeProofType = this.runtime.zkProgrammable.zkProgram[0].Proof; @@ -72,7 +73,7 @@ export class TransactionProvingTask private readonly compileRegistry: CompileRegistry ) { super(); - this.blockProver = protocol.blockProver; + this.transactionProver = protocol.transactionProver; } public inputSerializer(): TaskSerializer { @@ -84,9 +85,9 @@ export class TransactionProvingTask ); } - public resultSerializer(): TaskSerializer { + public resultSerializer(): TaskSerializer { return new ProofTaskSerializer( - this.blockProver.zkProgrammable.zkProgram[0].Proof + this.transactionProver.zkProgrammable.zkProgram[0].Proof ); } @@ -102,13 +103,13 @@ export class TransactionProvingTask const proof1 = DynamicRuntimeProof.fromProof(input.proof1); if (type === TransactionProvingType.SINGLE) { - await this.blockProver.proveTransaction( + await this.transactionProver.proveTransaction( parameters.publicInput, proof1, parameters.executionData ); } else { - await this.blockProver.proveTransactions( + await this.transactionProver.proveTransactions( parameters.publicInput, proof1, DynamicRuntimeProof.fromProof(input.proof2), @@ -128,6 +129,6 @@ export class TransactionProvingTask public async prepare(): Promise { // Compile - await this.blockProver.compile(this.compileRegistry); + await this.transactionProver.compile(this.compileRegistry); } } diff --git a/packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts b/packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts new file mode 100644 index 000000000..46e5ddac9 --- /dev/null +++ b/packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts @@ -0,0 +1,69 @@ +import { inject, injectable, Lifecycle, scoped } from "tsyringe"; +import { + MandatoryProtocolModulesRecord, + Protocol, + ProtocolModulesRecord, + TransactionProof, + TransactionProvable, +} from "@proto-kit/protocol"; +import { + CompileRegistry, + ProvableMethodExecutionContext, +} from "@proto-kit/common"; + +import { TaskWorkerModule } from "../../../worker/worker/TaskWorkerModule"; +import { Task, TaskSerializer } from "../../../worker/flow/Task"; +import { + PairProofTaskSerializer, + PairTuple, + ProofTaskSerializer, +} from "../../../helpers/utils"; + +@injectable() +@scoped(Lifecycle.ContainerScoped) +export class TransactionReductionTask + extends TaskWorkerModule + implements Task, TransactionProof> +{ + private readonly transactionProver: TransactionProvable; + + public name = "transactionReduction"; + + public constructor( + @inject("Protocol") + private readonly protocol: Protocol< + MandatoryProtocolModulesRecord & ProtocolModulesRecord + >, + private readonly executionContext: ProvableMethodExecutionContext, + private readonly compileRegistry: CompileRegistry + ) { + super(); + this.transactionProver = this.protocol.transactionProver; + } + + public inputSerializer(): TaskSerializer> { + return new PairProofTaskSerializer( + this.transactionProver.zkProgrammable.zkProgram[0].Proof + ); + } + + public resultSerializer(): TaskSerializer { + return new ProofTaskSerializer( + this.transactionProver.zkProgrammable.zkProgram[0].Proof + ); + } + + public async compute( + input: PairTuple + ): Promise { + const [r1, r2] = input; + await this.transactionProver.merge(r1.publicInput, r1, r2); + return await this.executionContext + .current() + .result.prove(); + } + + public async prepare(): Promise { + await this.transactionProver.compile(this.compileRegistry); + } +} diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts index c8a1f2640..58771b1df 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts @@ -1,13 +1,14 @@ import { BlockHashMerkleTreeWitness, - BlockProof, BlockProverPublicInput, - BlockProverPublicOutput, NetworkState, ReturnType, StateTransitionProof, StateTransitionProverPublicInput, StateTransitionProverPublicOutput, + TransactionProof, + TransactionProverPublicInput, + TransactionProverPublicOutput, WitnessedRootWitness, } from "@proto-kit/protocol"; import { Bool } from "o1js"; @@ -38,7 +39,7 @@ interface JsonType { type NewBlockPayload = PairingDerivedInput< StateTransitionProof, - BlockProof, + TransactionProof, NewBlockProverParameters >; @@ -50,16 +51,16 @@ export class NewBlockProvingParametersSerializer StateTransitionProverPublicInput, StateTransitionProverPublicOutput >, - private readonly blockProofSerializer: ProofTaskSerializer< - BlockProverPublicInput, - BlockProverPublicOutput + private readonly transactionProofSerializer: ProofTaskSerializer< + TransactionProverPublicInput, + TransactionProverPublicOutput > ) {} public toJSON(input: NewBlockPayload) { return JSON.stringify({ input1: this.stProofSerializer.toJSON(input.input1), - input2: this.blockProofSerializer.toJSON(input.input2), + input2: this.transactionProofSerializer.toJSON(input.input2), params: { publicInput: BlockProverPublicInput.toJSON(input.params.publicInput), @@ -92,7 +93,7 @@ export class NewBlockProvingParametersSerializer const jsonObject: JsonType = JSON.parse(json); return { input1: await this.stProofSerializer.fromJSON(jsonObject.input1), - input2: await this.blockProofSerializer.fromJSON(jsonObject.input2), + input2: await this.transactionProofSerializer.fromJSON(jsonObject.input2), params: { publicInput: BlockProverPublicInput.fromJSON( diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts index da655d863..3e08b804e 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts @@ -1,10 +1,10 @@ import { BlockProverPublicInput, - BlockProverTransactionArguments, MethodPublicOutput, NetworkState, ReturnType, RuntimeTransaction, + TransactionProverTransactionArguments, } from "@proto-kit/protocol"; import { JsonProof, Signature } from "o1js"; @@ -21,7 +21,7 @@ import { } from "./DecodedStateSerializer"; import { RuntimeVerificationKeyAttestationSerializer } from "./RuntimeVerificationKeyAttestationSerializer"; -export type BlockProverTransactionArgumentsJSON = { +export type TransactionProverTransactionArgumentsJSON = { transaction: ReturnType; signature: ReturnType; verificationKeyAttestation: ReturnType< @@ -30,13 +30,13 @@ export type BlockProverTransactionArgumentsJSON = { }; export type SingleExecutionDataJSON = { - transaction: BlockProverTransactionArgumentsJSON; + transaction: TransactionProverTransactionArgumentsJSON; networkState: ReturnType; }; export type MultiExecutionDataJSON = { - transaction1: BlockProverTransactionArgumentsJSON; - transaction2: BlockProverTransactionArgumentsJSON; + transaction1: TransactionProverTransactionArgumentsJSON; + transaction2: TransactionProverTransactionArgumentsJSON; networkState: ReturnType; }; @@ -71,9 +71,9 @@ export class TransactionProvingTaskParameterSerializer > ) {} - private blockProverArgumentsToJson( - args: BlockProverTransactionArguments - ): BlockProverTransactionArgumentsJSON { + private transactionProverArgumentsToJson( + args: TransactionProverTransactionArguments + ): TransactionProverTransactionArgumentsJSON { return { transaction: RuntimeTransaction.toJSON(args.transaction), // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment @@ -85,9 +85,9 @@ export class TransactionProvingTaskParameterSerializer }; } - private blockProverArgumentsFromJson( - args: BlockProverTransactionArgumentsJSON - ): BlockProverTransactionArguments { + private transactionProverArgumentsFromJson( + args: TransactionProverTransactionArgumentsJSON + ): TransactionProverTransactionArguments { return { transaction: new RuntimeTransaction( RuntimeTransaction.fromJSON(args.transaction) @@ -120,7 +120,9 @@ export class TransactionProvingTaskParameterSerializer const { executionData } = parameters; const executionDataJson: SingleExecutionDataJSON = { networkState: NetworkState.toJSON(executionData.networkState), - transaction: this.blockProverArgumentsToJson(executionData.transaction), + transaction: this.transactionProverArgumentsToJson( + executionData.transaction + ), }; taskParamsJson = { @@ -135,10 +137,10 @@ export class TransactionProvingTaskParameterSerializer const { executionData } = parameters; const executionDataJson: MultiExecutionDataJSON = { networkState: NetworkState.toJSON(executionData.networkState), - transaction1: this.blockProverArgumentsToJson( + transaction1: this.transactionProverArgumentsToJson( executionData.transaction1 ), - transaction2: this.blockProverArgumentsToJson( + transaction2: this.transactionProverArgumentsToJson( executionData.transaction2 ), }; @@ -183,7 +185,7 @@ export class TransactionProvingTaskParameterSerializer parameters: { ...partialParameters, executionData: { - transaction: this.blockProverArgumentsFromJson( + transaction: this.transactionProverArgumentsFromJson( parameters.executionData.transaction ), networkState: new NetworkState( @@ -205,10 +207,10 @@ export class TransactionProvingTaskParameterSerializer parameters: { ...partialParameters, executionData: { - transaction1: this.blockProverArgumentsFromJson( + transaction1: this.transactionProverArgumentsFromJson( parameters.executionData.transaction1 ), - transaction2: this.blockProverArgumentsFromJson( + transaction2: this.transactionProverArgumentsFromJson( parameters.executionData.transaction2 ), networkState: new NetworkState( diff --git a/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts b/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts index 4b30745b5..ee591d282 100644 --- a/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts @@ -1,18 +1,15 @@ import { - BlockProver, + addTransactionToBundle, BlockProverMultiTransactionExecutionData, - BlockProverProgrammable, BlockProverPublicInput, BlockProverSingleTransactionExecutionData, - BlockProverTransactionArguments, - MandatoryProtocolModulesRecord, NetworkState, - Protocol, + TransactionProverTransactionArguments, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { MAX_FIELD } from "@proto-kit/common"; import { toStateTransitionsHash } from "@proto-kit/module"; -import { inject, injectable } from "tsyringe"; +import { injectable } from "tsyringe"; import { TransactionExecutionResult } from "../../../storage/model/Block"; import { PendingTransaction } from "../../../mempool/PendingTransaction"; @@ -59,19 +56,13 @@ export function collectStartingState( @injectable() export class TransactionTracingService { - private readonly blockProver: BlockProverProgrammable; - public constructor( - private readonly verificationKeyService: VerificationKeyService, - @inject("Protocol") protocol: Protocol - ) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - this.blockProver = (protocol.blockProver as BlockProver).zkProgrammable; - } + private readonly verificationKeyService: VerificationKeyService + ) {} public async getTransactionData( transaction: PendingTransaction - ): Promise { + ): Promise { const verificationKeyAttestation = this.verificationKeyService.getAttestation( transaction.methodId.toBigInt() @@ -105,7 +96,7 @@ export class TransactionTracingService { transaction: TransactionExecutionResult ) { // TODO Remove this call and instead reuse results from sequencing - const newState = this.blockProver.addTransactionToBundle( + const newState = addTransactionToBundle( previousState, Bool(transaction.tx.isMessage), transaction.tx.toRuntimeTransaction() diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index f0012e9cc..ec4aeb5db 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -198,6 +198,7 @@ export const settlementTestFn = ( }, Protocol: { StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, AccountState: {}, BlockProver: {}, diff --git a/packages/stack/src/scripts/graphql/server.ts b/packages/stack/src/scripts/graphql/server.ts index a927694bf..2cdaf2604 100644 --- a/packages/stack/src/scripts/graphql/server.ts +++ b/packages/stack/src/scripts/graphql/server.ts @@ -137,6 +137,7 @@ export async function startServer() { Protocol: { BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, AccountState: {}, BlockHeight: {}, TransactionFee: { From 2fdbbab25174566cc2014a1ec5128faca3405e6f Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 14:48:35 +0100 Subject: [PATCH 069/155] Separated TransactionProver from BlockProver: Tests --- packages/sequencer/test/integration/BlockProduction-test.ts | 1 + packages/sequencer/test/integration/BlockProductionSize.test.ts | 1 + packages/sequencer/test/integration/Mempool.test.ts | 1 + packages/sequencer/test/integration/Proven.test.ts | 1 + packages/sequencer/test/integration/StorageIntegration.test.ts | 1 + .../production/sequencing/atomic-block-production.test.ts | 1 + 6 files changed, 6 insertions(+) diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 3189f4798..103e5ff8f 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -160,6 +160,7 @@ export function testBlockProduction< AccountState: {}, BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, LastStateRoot: {}, ProtocolStateTestHook: {}, diff --git a/packages/sequencer/test/integration/BlockProductionSize.test.ts b/packages/sequencer/test/integration/BlockProductionSize.test.ts index ad263f018..166b26884 100644 --- a/packages/sequencer/test/integration/BlockProductionSize.test.ts +++ b/packages/sequencer/test/integration/BlockProductionSize.test.ts @@ -91,6 +91,7 @@ describe("block limit", () => { AccountState: {}, BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, LastStateRoot: {}, ProtocolStateTestHook: {}, diff --git a/packages/sequencer/test/integration/Mempool.test.ts b/packages/sequencer/test/integration/Mempool.test.ts index 2609ef78f..bedb6406a 100644 --- a/packages/sequencer/test/integration/Mempool.test.ts +++ b/packages/sequencer/test/integration/Mempool.test.ts @@ -112,6 +112,7 @@ describe.each([["InMemory", InMemoryDatabase]])( AccountState: {}, BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, LastStateRoot: {}, }, diff --git a/packages/sequencer/test/integration/Proven.test.ts b/packages/sequencer/test/integration/Proven.test.ts index 0526d93d2..8d6b48d08 100644 --- a/packages/sequencer/test/integration/Proven.test.ts +++ b/packages/sequencer/test/integration/Proven.test.ts @@ -114,6 +114,7 @@ describe.skip("Proven", () => { AccountState: {}, BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, LastStateRoot: {}, ProtocolStateTestHook: {}, diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index b26a5008b..d326827db 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -111,6 +111,7 @@ describe.each([["InMemory", InMemoryDatabase]])( AccountState: {}, BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, LastStateRoot: {}, }, diff --git a/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts b/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts index a9b156023..0212cb622 100644 --- a/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts +++ b/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts @@ -66,6 +66,7 @@ describe("atomic block production", () => { AccountState: {}, BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, LastStateRoot: {}, ProtocolStateTestHook: {}, From 41edc557e12bdaa1fa8b9f4b434f6abc6a59ebe1 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 15:14:39 +0100 Subject: [PATCH 070/155] Fixed compile and linting errors --- .../src/protocol/VanillaProtocolModules.ts | 20 +++----------- packages/protocol/src/protocol/Protocol.ts | 26 +++++++++++++++++++ .../statetransition/StateTransitionProver.ts | 6 +---- .../test/integration/BlockProduction-test.ts | 7 +---- .../integration/BlockProductionSize.test.ts | 7 +---- .../test/integration/Mempool.test.ts | 14 ++-------- .../sequencer/test/integration/Proven.test.ts | 7 +---- .../integration/StorageIntegration.test.ts | 17 ++---------- .../atomic-block-production.test.ts | 7 +---- .../sequencer/test/settlement/Settlement.ts | 7 +---- packages/stack/src/scripts/worker/app.ts | 1 + 11 files changed, 40 insertions(+), 79 deletions(-) diff --git a/packages/library/src/protocol/VanillaProtocolModules.ts b/packages/library/src/protocol/VanillaProtocolModules.ts index 81332e714..20b936b05 100644 --- a/packages/library/src/protocol/VanillaProtocolModules.ts +++ b/packages/library/src/protocol/VanillaProtocolModules.ts @@ -1,12 +1,7 @@ import { - AccountStateHook, - BlockHeightHook, - BlockProver, MandatoryProtocolModulesRecord, ProtocolModulesRecord, - StateTransitionProver, - LastStateRootBlockHook, - TransactionProver, + Protocol, } from "@proto-kit/protocol"; import { PrivateKey } from "o1js"; @@ -21,12 +16,7 @@ export class VanillaProtocolModules { additionalModules: ProtocolModules ): MandatoryProtocolModulesRecord & ProtocolModules { return { - StateTransitionProver, - TransactionProver, - BlockProver, - AccountState: AccountStateHook, - BlockHeight: BlockHeightHook, - LastStateRoot: LastStateRootBlockHook, + ...Protocol.defaultModules(), ...additionalModules, }; } @@ -42,11 +32,7 @@ export class VanillaProtocolModules { public static mandatoryConfig() { return { - BlockProver: {}, - StateTransitionProver: {}, - AccountState: {}, - BlockHeight: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), }; } diff --git a/packages/protocol/src/protocol/Protocol.ts b/packages/protocol/src/protocol/Protocol.ts index cbdf4c130..4b0e4d240 100644 --- a/packages/protocol/src/protocol/Protocol.ts +++ b/packages/protocol/src/protocol/Protocol.ts @@ -3,6 +3,7 @@ import { ChildContainerProvider, log, ModuleContainer, + ModulesConfig, ModulesRecord, Startable, StringKeyOf, @@ -22,6 +23,9 @@ import { NoopSettlementHook } from "../hooks/NoopSettlementHook"; import { AccountStateHook } from "../hooks/AccountStateHook"; import { NoopTransactionHook } from "../hooks/NoopTransactionHook"; import { TransactionProvable } from "../prover/transaction/TransactionProvable"; +import { StateTransitionProver } from "../prover/statetransition/StateTransitionProver"; +import { TransactionProver } from "../prover/transaction/TransactionProver"; +import { BlockProver } from "../prover/block/BlockProver"; import { ProtocolModule } from "./ProtocolModule"; import { ProvableTransactionHook } from "./ProvableTransactionHook"; @@ -143,6 +147,28 @@ export class Protocol< >("StateTransitionProver"); } + public static defaultModules() { + return { + StateTransitionProver, + TransactionProver, + BlockProver, + AccountState: AccountStateHook, + BlockHeight: BlockHeightHook, + LastStateRoot: LastStateRootBlockHook, + }; + } + + public static defaultConfig() { + return { + StateTransitionProver: {}, + TransactionProver: {}, + BlockProver: {}, + AccountState: {}, + BlockHeight: {}, + LastStateRoot: {}, + } satisfies ModulesConfig>; + } + public getAreProofsEnabled(): AreProofsEnabled { return this.container.resolve("AreProofsEnabled"); } diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index e8716c529..dcfeeaaa7 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -20,7 +20,6 @@ import { StateTransitionProvableBatch, StateTransitionType, } from "../../model/StateTransitionProvableBatch"; -import { StateTransitionProverType } from "../../protocol/Protocol"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { DefaultProvableHashList } from "../../utils/ProvableHashList"; import { WitnessedRootHashList } from "../accumulators/WitnessedRootHashList"; @@ -433,10 +432,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< @injectable() export class StateTransitionProver extends ProtocolModule - implements - StateTransitionProvable, - StateTransitionProverType, - CompilableModule + implements StateTransitionProvable, CompilableModule { public zkProgrammable: StateTransitionProverProgrammable; diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 103e5ff8f..71c4172f9 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -157,12 +157,7 @@ export function testBlockProduction< EventMaker: {}, }, Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - TransactionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), ProtocolStateTestHook: {}, }, }); diff --git a/packages/sequencer/test/integration/BlockProductionSize.test.ts b/packages/sequencer/test/integration/BlockProductionSize.test.ts index 166b26884..109b576c5 100644 --- a/packages/sequencer/test/integration/BlockProductionSize.test.ts +++ b/packages/sequencer/test/integration/BlockProductionSize.test.ts @@ -88,12 +88,7 @@ describe("block limit", () => { NoopRuntime: {}, }, Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - TransactionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), ProtocolStateTestHook: {}, }, }); diff --git a/packages/sequencer/test/integration/Mempool.test.ts b/packages/sequencer/test/integration/Mempool.test.ts index bedb6406a..d5aec4037 100644 --- a/packages/sequencer/test/integration/Mempool.test.ts +++ b/packages/sequencer/test/integration/Mempool.test.ts @@ -1,5 +1,4 @@ import { log, TypedClass } from "@proto-kit/common"; -import { VanillaProtocolModules } from "@proto-kit/library"; import { Runtime } from "@proto-kit/module"; import { Protocol } from "@proto-kit/protocol"; import { Bool, PrivateKey, UInt64 } from "o1js"; @@ -74,9 +73,7 @@ describe.each([["InMemory", InMemoryDatabase]])( const sequencerClass = Sequencer.from(testingSequencerModules({})); - const protocolClass = Protocol.from( - VanillaProtocolModules.mandatoryModules({}) - ); + const protocolClass = Protocol.from(Protocol.defaultModules()); return AppChain.from({ Sequencer: sequencerClass, @@ -108,14 +105,7 @@ describe.each([["InMemory", InMemoryDatabase]])( TaskQueue: {}, SequencerStartupModule: {}, }, - Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - TransactionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, - }, + Protocol: Protocol.defaultConfig(), }); // Start AppChain diff --git a/packages/sequencer/test/integration/Proven.test.ts b/packages/sequencer/test/integration/Proven.test.ts index 8d6b48d08..bca7f53af 100644 --- a/packages/sequencer/test/integration/Proven.test.ts +++ b/packages/sequencer/test/integration/Proven.test.ts @@ -111,12 +111,7 @@ describe.skip("Proven", () => { Balances: {}, }, Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - TransactionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), ProtocolStateTestHook: {}, SettlementContractModule: { SettlementContract: {}, diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index d326827db..16f17bbe0 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -1,6 +1,5 @@ import "reflect-metadata"; import { expect } from "@jest/globals"; -import { VanillaProtocolModules } from "@proto-kit/library"; import { Protocol } from "@proto-kit/protocol"; import { Runtime } from "@proto-kit/module"; import { Bool, Field, PrivateKey, UInt64 } from "o1js"; @@ -59,9 +58,6 @@ describe.each([["InMemory", InMemoryDatabase]])( let unprovenState: AsyncStateService; let provenState: AsyncStateService; - // let unprovenTreeStore: AsyncMerkleTreeStore; - // let provenTreeStore: AsyncMerkleTreeStore; - const sk = PrivateKey.random(); const pk = sk.toPublicKey(); let pkNonce = 0; @@ -77,9 +73,7 @@ describe.each([["InMemory", InMemoryDatabase]])( Balance, }); - const protocolClass = Protocol.from( - VanillaProtocolModules.mandatoryModules({}) - ); + const protocolClass = Protocol.from(Protocol.defaultModules()); return AppChain.from({ Sequencer: sequencerClass, @@ -107,14 +101,7 @@ describe.each([["InMemory", InMemoryDatabase]])( FeeStrategy: {}, SequencerStartupModule: {}, }, - Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - TransactionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, - }, + Protocol: Protocol.defaultConfig(), }); await appChain.start(false); diff --git a/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts b/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts index 0212cb622..e34b72af6 100644 --- a/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts +++ b/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts @@ -63,12 +63,7 @@ describe("atomic block production", () => { Balance: {}, }, Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - TransactionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), ProtocolStateTestHook: {}, }, }); diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index ec4aeb5db..3e1c86f26 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -197,12 +197,7 @@ export const settlementTestFn = ( }, }, Protocol: { - StateTransitionProver: {}, - TransactionProver: {}, - BlockHeight: {}, - AccountState: {}, - BlockProver: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), SettlementContractModule: { SettlementContract: {}, BridgeContract: {}, diff --git a/packages/stack/src/scripts/worker/app.ts b/packages/stack/src/scripts/worker/app.ts index 2ae296279..f1964c30d 100644 --- a/packages/stack/src/scripts/worker/app.ts +++ b/packages/stack/src/scripts/worker/app.ts @@ -34,6 +34,7 @@ export const app = { protocol: { AccountState: {}, BlockProver: {}, + TransactionProver: {}, BlockHeight: {}, StateTransitionProver: {}, LastStateRoot: {}, From 29582bc2c1a77eee5a626602f2714b017f9fc1c8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 15:17:52 +0100 Subject: [PATCH 071/155] Added CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..c1d2667b1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) + +## Unreleased + +### Added + +- Introduced Changelog From 418c8801a364d6f58a9733049d9a39ab34cc9072 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 15:19:04 +0100 Subject: [PATCH 072/155] Added PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d2667b1..0c0d2da03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,4 +8,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added -- Introduced Changelog +- Introduced Changelog [#378](https://github.com/proto-kit/framework/pull/378) From ed4bab192bf4513b1ce1363a0ed55ab1e59cb63b Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 19 Dec 2025 17:21:15 +0300 Subject: [PATCH 073/155] style:run lint fix --- packages/sdk/src/query/QueryService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/query/QueryService.ts b/packages/sdk/src/query/QueryService.ts index f9fe28e52..e49f750fc 100644 --- a/packages/sdk/src/query/QueryService.ts +++ b/packages/sdk/src/query/QueryService.ts @@ -25,7 +25,7 @@ import { export class QueryService< RuntimeModules extends RuntimeModulesRecord, ProtocolModules extends ProtocolModulesRecord & - MandatoryProtocolModulesRecord + MandatoryProtocolModulesRecord, > { // Lazily initialized query instances private RuntimeQuery?: Query, RuntimeModules>; From 9b51fbaf8e5211604896d487a6c90b9cb82b26c8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 15:45:45 +0100 Subject: [PATCH 074/155] Fixed typing and tests --- .../src/protocol/ProvableBlockHook.ts | 4 +- .../src/prover/block/BlockProvable.ts | 129 ++++++------------ .../protocol/src/prover/block/BlockProver.ts | 9 +- .../prover/transaction/TransactionProver.ts | 3 - packages/protocol/test/TestingProtocol.ts | 26 +--- 5 files changed, 52 insertions(+), 119 deletions(-) diff --git a/packages/protocol/src/protocol/ProvableBlockHook.ts b/packages/protocol/src/protocol/ProvableBlockHook.ts index b15863b92..7ba6dcd32 100644 --- a/packages/protocol/src/protocol/ProvableBlockHook.ts +++ b/packages/protocol/src/protocol/ProvableBlockHook.ts @@ -4,13 +4,13 @@ import { NoConfig } from "@proto-kit/common"; import { NetworkState } from "../model/network/NetworkState"; import { BlockProverState, - BlockProverStateCommitments, + BlockProverPublicInput, } from "../prover/block/BlockProvable"; import { TransitioningProtocolModule } from "./TransitioningProtocolModule"; export type ProvableHookBlockState = Pick< - BlockProverStateCommitments, + BlockProverPublicInput, | "transactionsHash" | "eternalTransactionsHash" | "incomingMessagesHash" diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index c7f7299ba..dbcb311fe 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -10,122 +10,81 @@ import { WitnessedRootHashList, WitnessedRootWitness, } from "../accumulators/WitnessedRootHashList"; -import { TransactionProof } from "../transaction/TransactionProvable"; +import { + TransactionProof, + TransactionProverState, + TransactionProverStateCommitments, +} from "../transaction/TransactionProvable"; import { BlockHashMerkleTreeWitness } from "./accummulators/BlockHashMerkleTree"; -// Should be equal to BlockProver.PublicInput -export interface BlockProverState { +export class BlockProverState extends TransactionProverState { /** * The current state root of the block prover */ stateRoot: Field; - /** - * The current commitment of the transaction-list which - * will at the end equal the bundle hash - */ - transactionList: TransactionHashList; - - /** - * The network state which gives access to values such as blockHeight - * This value is the same for the whole batch (L2 block) - */ - networkState: NetworkState; - /** * The root of the merkle tree encoding all block hashes, * see `BlockHashMerkleTree` */ blockHashRoot: Field; - /** - * A variant of the transactionsHash that is never reset. - * Thought for usage in the sequence state mempool. - * In comparison, transactionsHash restarts at 0 for every new block - */ - eternalTransactionsList: TransactionHashList; - - pendingSTBatches: AppliedBatchHashList; - - incomingMessages: MinaActionsHashList; - - witnessedRoots: WitnessedRootHashList; - blockNumber: Field; -} -// TODO Sort and organize public inputs and outputs -export class BlockProverStateCommitments extends Struct({ - transactionsHash: Field, - stateRoot: Field, - // Commitment to the list of unprocessed (pending) batches of STs that need to be proven - pendingSTBatchesHash: Field, - witnessedRootsHash: Field, - networkStateHash: Field, - blockHashRoot: Field, - eternalTransactionsHash: Field, - incomingMessagesHash: Field, - blockNumber: Field, -}) { - public static fromBlockProverState( - state: BlockProverState - ): BlockProverStateCommitments { + constructor(args: { + transactionList: TransactionHashList; + networkState: NetworkState; + eternalTransactionsList: TransactionHashList; + pendingSTBatches: AppliedBatchHashList; + incomingMessages: MinaActionsHashList; + witnessedRoots: WitnessedRootHashList; + stateRoot: Field; + blockHashRoot: Field; + blockNumber: Field; + }) { + super(args); + this.stateRoot = args.stateRoot; + this.blockHashRoot = args.blockHashRoot; + this.blockNumber = args.blockNumber; + } + + public toCommitments(): BlockProverPublicInput { return { - networkStateHash: state.networkState.hash(), - stateRoot: state.stateRoot, - blockNumber: state.blockNumber, - blockHashRoot: state.blockHashRoot, - pendingSTBatchesHash: state.pendingSTBatches.commitment, - transactionsHash: state.transactionList.commitment, - eternalTransactionsHash: state.eternalTransactionsList.commitment, - incomingMessagesHash: state.incomingMessages.commitment, - witnessedRootsHash: state.witnessedRoots.commitment, + ...super.toCommitments(), + stateRoot: this.stateRoot, + blockHashRoot: this.blockHashRoot, + blockNumber: this.blockNumber, }; } - public static toBlockProverState( - publicInput: BlockProverStateCommitments, + public static fromCommitments( + publicInput: BlockProverPublicInput, networkState: NetworkState ): BlockProverState { - publicInput.networkStateHash.assertEquals( - networkState.hash(), - "ExecutionData Networkstate doesn't equal public input hash" - ); - - return { - networkState, + return new BlockProverState({ + ...super.fromCommitments(publicInput, networkState), stateRoot: publicInput.stateRoot, blockHashRoot: publicInput.blockHashRoot, - transactionList: new TransactionHashList(publicInput.transactionsHash), - eternalTransactionsList: new TransactionHashList( - publicInput.eternalTransactionsHash - ), - incomingMessages: new MinaActionsHashList( - publicInput.incomingMessagesHash - ), - pendingSTBatches: new AppliedBatchHashList( - publicInput.pendingSTBatchesHash - ), - witnessedRoots: new WitnessedRootHashList(publicInput.witnessedRootsHash), blockNumber: publicInput.blockNumber, - }; + }); } } -export class BlockProverPublicInput extends BlockProverStateCommitments {} - -export class BlockProverPublicOutput extends Struct({ - transactionsHash: Field, +export const BlockProverStateCommitments = { + ...TransactionProverStateCommitments, stateRoot: Field, - pendingSTBatchesHash: Field, - witnessedRootsHash: Field, - networkStateHash: Field, blockHashRoot: Field, - eternalTransactionsHash: Field, - incomingMessagesHash: Field, - closed: Bool, blockNumber: Field, +}; + +export class BlockProverPublicInput extends Struct( + BlockProverStateCommitments +) {} + +export class BlockProverPublicOutput extends Struct({ + ...BlockProverStateCommitments, + closed: Bool, }) { public equals(input: BlockProverPublicInput, closed: Bool): Bool { const output2 = BlockProverPublicOutput.toFields({ diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 6b3d1e02d..42bfbae40 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -44,7 +44,7 @@ import { BlockProof, BlockProverPublicInput, BlockProverPublicOutput, - BlockProverStateCommitments, + BlockProverState, } from "./BlockProvable"; import { BlockHashMerkleTreeWitness, @@ -232,10 +232,7 @@ export class BlockProverProgrammable extends ZkProgrammable< "TransactionProof cannot alter the network state" ); - const state = BlockProverStateCommitments.toBlockProverState( - publicInput, - networkState - ); + const state = BlockProverState.fromCommitments(publicInput, networkState); // Verify Transaction proof if it has at least 1 tx - i.e. the // input and output doesn't match fully @@ -376,7 +373,7 @@ export class BlockProverProgrammable extends ZkProgrammable< state.blockNumber = blockIndex.add(1); return new BlockProverPublicOutput({ - ...BlockProverStateCommitments.fromBlockProverState(state), + ...state.toCommitments(), closed: Bool(true), }); } diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts index 9294edda8..6c686ba17 100644 --- a/packages/protocol/src/prover/transaction/TransactionProver.ts +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -50,9 +50,6 @@ const errors = { propertyNotMatchingStep: (propertyName: string, step: string) => `${propertyName} not matching: ${step}`, - stateRootNotMatching: (step: string) => - errors.propertyNotMatchingStep("StateRoots", step), - transactionsHashNotMatching: (step: string) => errors.propertyNotMatchingStep("Transactions hash", step), diff --git a/packages/protocol/test/TestingProtocol.ts b/packages/protocol/test/TestingProtocol.ts index 6370201e8..9e8d85fa2 100644 --- a/packages/protocol/test/TestingProtocol.ts +++ b/packages/protocol/test/TestingProtocol.ts @@ -3,33 +3,13 @@ import { Runtime } from "@proto-kit/module"; import { Balance } from "@proto-kit/sequencer/test/integration/mocks/Balance"; import { NoopRuntime } from "@proto-kit/sequencer/test/integration/mocks/NoopRuntime"; -import { - AccountStateHook, - BlockHeightHook, - BlockProver, - LastStateRootBlockHook, - Protocol, - StateServiceProvider, - StateTransitionProver, -} from "../src"; +import { Protocol, StateServiceProvider } from "../src"; export function createAndInitTestingProtocol() { - const ProtocolClass = Protocol.from({ - StateTransitionProver: StateTransitionProver, - BlockProver: BlockProver, - AccountState: AccountStateHook, - BlockHeight: BlockHeightHook, - LastStateRoot: LastStateRootBlockHook, - }); + const ProtocolClass = Protocol.from(Protocol.defaultModules()); const protocol = new ProtocolClass(); - protocol.configure({ - BlockProver: {}, - AccountState: {}, - BlockHeight: {}, - StateTransitionProver: {}, - LastStateRoot: {}, - }); + protocol.configure(Protocol.defaultConfig()); const appChain = container.createChildContainer(); From 8415723ba023e3ae18249f4551d21ca167adb954 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 19 Dec 2025 15:56:58 +0100 Subject: [PATCH 075/155] Fixed BlockProver compilation order --- .../protocol/src/prover/block/BlockProver.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 42bfbae40..ede4ea8c9 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -13,7 +13,6 @@ import { ZkProgrammable, } from "@proto-kit/common"; -import { MethodPublicOutput } from "../../model/MethodPublicOutput"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { StateTransitionProof, @@ -36,7 +35,9 @@ import { StateServiceProvider } from "../../state/StateServiceProvider"; import { executeHooks } from "../utils"; import { TransactionProof, + TransactionProvable, TransactionProverPublicInput, + TransactionProverPublicOutput, } from "../transaction/TransactionProvable"; import { @@ -77,6 +78,10 @@ export class BlockProverProgrammable extends ZkProgrammable< StateTransitionProverPublicInput, StateTransitionProverPublicOutput >, + public readonly transactionProver: ZkProgrammable< + TransactionProverPublicInput, + TransactionProverPublicOutput + >, private readonly blockHooks: ProvableBlockHook[], private readonly stateServiceProvider: StateServiceProvider ) { @@ -547,8 +552,9 @@ export class BlockProverProgrammable extends ZkProgrammable< BlockProverPublicInput, BlockProverPublicOutput >[] { - const { prover, stateTransitionProver } = this; + const { prover, stateTransitionProver, transactionProver } = this; const StateTransitionProofClass = stateTransitionProver.zkProgram[0].Proof; + const TransactionProofClass = transactionProver.zkProgram[0].Proof; const proveBlock = prover.proveBlock.bind(prover); const merge = prover.merge.bind(prover); @@ -565,7 +571,7 @@ export class BlockProverProgrammable extends ZkProgrammable< StateTransitionProofClass, Bool, WitnessedRootWitness, - SelfProof, + TransactionProofClass, ], async method( publicInput: BlockProverPublicInput, @@ -646,9 +652,12 @@ export class BlockProver StateTransitionProverPublicOutput > & StateTransitionProvable, - @inject("Runtime") - public readonly runtime: WithZkProgrammable & - CompilableModule, + @inject("TransactionProver") + public readonly transactionProver: WithZkProgrammable< + TransactionProverPublicInput, + TransactionProverPublicOutput + > & + TransactionProvable, @injectAll("ProvableBlockHook") blockHooks: ProvableBlockHook[], @inject("StateServiceProvider") @@ -658,6 +667,7 @@ export class BlockProver this.zkProgrammable = new BlockProverProgrammable( this, stateTransitionProver.zkProgrammable, + transactionProver.zkProgrammable, blockHooks, stateServiceProvider ); @@ -668,7 +678,7 @@ export class BlockProver ): Promise | undefined> { return await registry.forceProverExists(async () => { await this.stateTransitionProver.compile(registry); - await this.runtime.compile(registry); + await this.transactionProver.compile(registry); return await this.zkProgrammable.compile(registry); }); } From bae4575771a0268e50809d11e9f88fdd719c1bb9 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 20 Dec 2025 13:21:45 +0100 Subject: [PATCH 076/155] Fixed tests and missing task in definition --- packages/sdk/src/testing/TestingAppChain.ts | 6 +- .../src/protocol/production/flow/BatchFlow.ts | 3 + .../src/protocol/production/flow/BlockFlow.ts | 16 ++--- .../protocol/production/tasks/NewBlockTask.ts | 14 ++-- .../tasks/TransactionProvingTask.ts | 7 +- packages/sequencer/src/worker/flow/Flow.ts | 72 ++++++++++--------- .../worker/worker/LocalTaskWorkerModule.ts | 3 + 7 files changed, 64 insertions(+), 57 deletions(-) diff --git a/packages/sdk/src/testing/TestingAppChain.ts b/packages/sdk/src/testing/TestingAppChain.ts index 63e703715..3487a4175 100644 --- a/packages/sdk/src/testing/TestingAppChain.ts +++ b/packages/sdk/src/testing/TestingAppChain.ts @@ -53,11 +53,7 @@ export class TestingAppChain< appChain.configurePartial({ Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), TransactionFee: { tokenId: 0n, feeRecipient: randomFeeRecipient, diff --git a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts index 6fc80b17e..63720c4fc 100644 --- a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts @@ -124,6 +124,9 @@ export class BatchFlow { map[index].input1 = dummySTProof; }); + // TODO Make sure we use deferErrorsTo to everywhere (preferably with a nice pattern) + // Currently, a lot of errors just get eaten and the chain just halts with no + // error being thrown await this.stateTransitionFlow.executeBatches( batch.stateTransitionTrace, batchId, diff --git a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts index 7f5a9de00..542bd1ca3 100644 --- a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts @@ -1,13 +1,12 @@ import { inject, injectable, Lifecycle, scoped } from "tsyringe"; import { - BlockProverPublicInput, - BlockProverPublicOutput, MandatoryProtocolModulesRecord, Protocol, TransactionProof, + TransactionProverPublicInput, + TransactionProverPublicOutput, } from "@proto-kit/protocol"; -import { Bool, Field } from "o1js"; -import { MAX_FIELD } from "@proto-kit/common"; +import { Field } from "o1js"; import { TransactionProvingTask } from "../tasks/TransactionProvingTask"; import { TransactionProvingTaskParameters } from "../tasks/serializers/types/TransactionProvingTypes"; @@ -35,17 +34,14 @@ export class BlockFlow { ...trace.blockParams.publicInput, networkStateHash: Field(0), transactionsHash: Field(0), - blockHashRoot: Field(0), - blockNumber: MAX_FIELD, - } satisfies BlockProverPublicInput; + } satisfies TransactionProverPublicInput; // TODO Set publicInput.stateRoot to result after block hooks! - const publicOutput = new BlockProverPublicOutput({ + const publicOutput = new TransactionProverPublicOutput({ ...publicInput, - closed: Bool(true), }); - return await this.protocol.blockProver.zkProgrammable.zkProgram[0].Proof.dummy( + return await this.protocol.transactionProver.zkProgrammable.zkProgram[0].Proof.dummy( publicInput, publicOutput, 2 diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 806a5b459..338ce9514 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -11,6 +11,7 @@ import { WitnessedRootWitness, TransactionProof, BlockProof, + TransactionProvable, } from "@proto-kit/protocol"; import { Bool } from "o1js"; import { @@ -51,6 +52,8 @@ export class NewBlockTask { private readonly stateTransitionProver: StateTransitionProvable; + private readonly transactionProver: TransactionProvable; + private readonly blockProver: BlockProvable; public readonly name = "newBlock"; @@ -63,7 +66,8 @@ export class NewBlockTask ) { super(); this.stateTransitionProver = protocol.stateTransitionProver; - this.blockProver = this.protocol.blockProver; + this.transactionProver = protocol.transactionProver; + this.blockProver = protocol.blockProver; } public inputSerializer(): TaskSerializer { @@ -71,13 +75,13 @@ export class NewBlockTask this.stateTransitionProver.zkProgrammable.zkProgram[0].Proof ); - const blockProofSerializer = new ProofTaskSerializer( - this.blockProver.zkProgrammable.zkProgram[0].Proof + const transactionProofSerializer = new ProofTaskSerializer( + this.transactionProver.zkProgrammable.zkProgram[0].Proof ); return new NewBlockProvingParametersSerializer( stProofSerializer, - blockProofSerializer + transactionProofSerializer ); } @@ -125,6 +129,6 @@ export class NewBlockTask public async prepare(): Promise { // Compile - await this.blockProver.compile(this.compileRegistry); + await this.transactionProver.compile(this.compileRegistry); } } diff --git a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts index 2de3ac069..de4b80b63 100644 --- a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts @@ -1,5 +1,4 @@ import { - BlockProof, MandatoryProtocolModulesRecord, Protocol, ProtocolModulesRecord, @@ -61,7 +60,7 @@ export class TransactionProvingTask private readonly runtimeProofType = this.runtime.zkProgrammable.zkProgram[0].Proof; - public name = "block"; + public name = "transaction"; public constructor( @inject("Protocol") @@ -93,7 +92,7 @@ export class TransactionProvingTask public async compute( input: TransactionProvingTaskParameters - ): Promise { + ): Promise { await executeWithPrefilledStateService( this.protocol.stateServiceProvider, input.parameters.startingState, @@ -123,7 +122,7 @@ export class TransactionProvingTask this.protocol.stateServiceProvider, input.parameters.startingState, async () => - await this.executionContext.current().result.prove() + await this.executionContext.current().result.prove() ); } diff --git a/packages/sequencer/src/worker/flow/Flow.ts b/packages/sequencer/src/worker/flow/Flow.ts index bd9dd7fd0..6dd7d5e02 100644 --- a/packages/sequencer/src/worker/flow/Flow.ts +++ b/packages/sequencer/src/worker/flow/Flow.ts @@ -114,40 +114,46 @@ export class Flow implements Closeable { taskName?: string; } ): Promise { - const queueName = task.name; - const taskName = overrides?.taskName ?? task.name; - const queue = await this.queueImpl.getQueue(queueName); - - const payload = await task.inputSerializer().toJSON(input); - - this.taskCounter += 1; - const taskId = String(this.taskCounter); - - log.trace(`Pushing task ${task.name}`); - - await queue.addTask({ - name: taskName, - taskId, - flowId: this.flowId, - payload, - sequencerId: this.sequencerId, - }); + // We wrap this in a try-catch here, because the flow architecture + // sometimes lets errors vanish + try { + const queueName = task.name; + const taskName = overrides?.taskName ?? task.name; + const queue = await this.queueImpl.getQueue(queueName); + + const payload = await task.inputSerializer().toJSON(input); + this.taskCounter += 1; + const taskId = String(this.taskCounter); + + log.trace(`Pushing task ${task.name}`); + + await queue.addTask({ + name: taskName, + taskId, + flowId: this.flowId, + payload, + sequencerId: this.sequencerId, + }); - this.tasksInProgress += 1; - - const callback = async (returnPayload: TaskPayload) => { - log.trace( - `Completed ${returnPayload.name}, task: ${returnPayload.flowId}:${ - returnPayload?.taskId ?? "-" - }` - ); - const decoded = await task - .resultSerializer() - .fromJSON(returnPayload.payload); - this.tasksInProgress -= 1; - return await completed?.(decoded, input); - }; - await this.waitForResult(queue, taskId, callback); + this.tasksInProgress += 1; + + const callback = async (returnPayload: TaskPayload) => { + log.trace( + `Completed ${returnPayload.name}, task: ${returnPayload.flowId}:${ + returnPayload?.taskId ?? "-" + }` + ); + const decoded = await task + .resultSerializer() + .fromJSON(returnPayload.payload); + this.tasksInProgress -= 1; + return await completed?.(decoded, input); + }; + await this.waitForResult(queue, taskId, callback); + } catch (e) { + log.error(e); + throw e; + } } public async forEach( diff --git a/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts b/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts index 97aec991a..85fa05663 100644 --- a/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts +++ b/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts @@ -28,6 +28,7 @@ import { closeable } from "../../sequencer/builder/Closeable"; import { StateTransitionReductionTask } from "../../protocol/production/tasks/StateTransitionReductionTask"; import { TransactionProvingTask } from "../../protocol/production/tasks/TransactionProvingTask"; import { BlockReductionTask } from "../../protocol/production/tasks/BlockReductionTask"; +import { TransactionReductionTask } from "../../protocol/production/tasks/TransactionReductionTask"; import { FlowTaskWorker } from "./FlowTaskWorker"; import { TaskWorkerModule } from "./TaskWorkerModule"; @@ -135,6 +136,7 @@ export class VanillaTaskWorkerModules { StateTransitionReductionTask, RuntimeProvingTask, TransactionProvingTask, + TransactionReductionTask, BlockReductionTask, NewBlockTask, CircuitCompilerTask, @@ -154,6 +156,7 @@ export class VanillaTaskWorkerModules { StateTransitionTask: {}, RuntimeProvingTask: {}, TransactionProvingTask: {}, + TransactionReductionTask: {}, BlockReductionTask: {}, NewBlockTask: {}, StateTransitionReductionTask: {}, From 33e3028e4387f1753a5b2076d9db8757d5d1d839 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 21 Dec 2025 14:05:42 +0100 Subject: [PATCH 077/155] Fixed config for settlement-only test --- packages/sequencer/test/settlement/Settlement-only.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/sequencer/test/settlement/Settlement-only.ts b/packages/sequencer/test/settlement/Settlement-only.ts index 6fe5d8dad..c894bfbcf 100644 --- a/packages/sequencer/test/settlement/Settlement-only.ts +++ b/packages/sequencer/test/settlement/Settlement-only.ts @@ -133,11 +133,7 @@ export const settlementOnlyTestFn = ( }, }, Protocol: { - StateTransitionProver: {}, - BlockHeight: {}, - AccountState: {}, - BlockProver: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), SettlementContractModule: { SettlementContract: {}, }, From c316ad324b61a98aae16540f5e1dcfee89b55656 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 21 Dec 2025 14:06:39 +0100 Subject: [PATCH 078/155] Implemented CircuitAnalysisModule --- packages/common/src/log.ts | 2 + .../src/helpers/CircuitAnalysisModule.ts | 75 +++++++++++++++++++ packages/sequencer/src/index.ts | 1 + .../sequencer/test/settlement/Settlement.ts | 7 ++ 4 files changed, 85 insertions(+) create mode 100644 packages/sequencer/src/helpers/CircuitAnalysisModule.ts diff --git a/packages/common/src/log.ts b/packages/common/src/log.ts index ed3c39ef1..a8659d239 100644 --- a/packages/common/src/log.ts +++ b/packages/common/src/log.ts @@ -28,6 +28,8 @@ function logProvable( // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (process.env?.IN_CI ?? false) { loglevel.setLevel("ERROR"); +} else { + loglevel.setLevel("INFO"); } const timeMap: Record = {}; diff --git a/packages/sequencer/src/helpers/CircuitAnalysisModule.ts b/packages/sequencer/src/helpers/CircuitAnalysisModule.ts new file mode 100644 index 000000000..c9b33572f --- /dev/null +++ b/packages/sequencer/src/helpers/CircuitAnalysisModule.ts @@ -0,0 +1,75 @@ +import { inject, injectable } from "tsyringe"; +import { RuntimeEnvironment } from "@proto-kit/module"; +import { log, mapSequential, PlainZkProgram } from "@proto-kit/common"; +import { + MandatoryProtocolModulesRecord, + Protocol, + ProtocolModulesRecord, + SettlementContractModule, + SettlementModulesRecord, +} from "@proto-kit/protocol"; + +@injectable() +export class CircuitAnalysisModule { + public constructor( + @inject("Protocol") + private readonly protocol: Protocol< + ProtocolModulesRecord & MandatoryProtocolModulesRecord + >, + @inject("Runtime") + private readonly runtime: RuntimeEnvironment + ) {} + + public async printSummary() { + const summary = await this.analyseMethods(); + log.info(summary); + } + + public async analyseMethods() { + const zkProgrammables = [ + this.runtime, + this.protocol.stateTransitionProver, + this.protocol.transactionProver, + this.protocol.blockProver, + ]; + + const zkProgrammablePromises = await mapSequential( + zkProgrammables, + (withZkProgrammable) => + mapSequential( + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + withZkProgrammable.zkProgrammable.zkProgramFactory() as PlainZkProgram< + unknown, + unknown + >[], + async (program) => { + const result = await program.analyzeMethods(); + return [program.name, result] as const; + } + ) + ); + + const settlementModule = this.protocol.dependencyContainer.resolve< + SettlementContractModule + >("SettlementContractModule"); + + const contractPromises = await mapSequential( + Object.entries(settlementModule.getContractClasses()), + async ([key, clas]) => { + const result = await clas.analyzeMethods(); + return [key, result] as const; + } + ); + + const allResults = [...zkProgrammablePromises.flat(), ...contractPromises]; + + const summary = allResults.map(([program, result]) => { + const methods = Object.entries(result).map( + ([methodName, constraints]) => [methodName, constraints.rows] as const + ); + return [program, Object.fromEntries(methods)] as const; + }); + + return Object.fromEntries(summary); + } +} diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 15b423ea7..a0fee0179 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -87,6 +87,7 @@ export * from "./helpers/query/NetworkStateQuery"; export * from "./helpers/query/NetworkStateTransportModule"; export * from "./helpers/query/BlockExplorerQuery"; export * from "./helpers/query/BlockExplorerTransportModule"; +export * from "./helpers/CircuitAnalysisModule"; export * from "./state/prefilled/PreFilledStateService"; export * from "./state/async/AsyncMerkleTreeStore"; export * from "./state/async/AsyncStateService"; diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 3e1c86f26..0291577cd 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -56,6 +56,7 @@ import { VanillaTaskWorkerModules, Sequencer, InMemoryMinaSigner, + CircuitAnalysisModule, } from "../../src"; import { BlockProofSerializer } from "../../src/protocol/production/tasks/serializers/BlockProofSerializer"; import { testingSequencerModules } from "../TestingSequencer"; @@ -313,6 +314,12 @@ export const settlementTestFn = ( let user0Nonce = 0; let acc0L2Nonce = 0; + it.only("Print constraint summary", async () => { + await appChain.protocol.dependencyContainer + .resolve(CircuitAnalysisModule) + .printSummary(); + }); + it("should throw error", async () => { const additionalAddresses = tokenConfig === undefined From 111d9bfefb182cd047e83669a8c1c2c601704694 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 21 Dec 2025 14:11:24 +0100 Subject: [PATCH 079/155] Disabled circuit summary test case --- packages/sequencer/test/settlement/Settlement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 0291577cd..0b89e8871 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -314,7 +314,7 @@ export const settlementTestFn = ( let user0Nonce = 0; let acc0L2Nonce = 0; - it.only("Print constraint summary", async () => { + it.skip("Print constraint summary", async () => { await appChain.protocol.dependencyContainer .resolve(CircuitAnalysisModule) .printSummary(); From eef46f5f73085af5950c9fa1544b40d6af26f5e2 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 20 Dec 2025 13:24:14 +0100 Subject: [PATCH 080/155] Implemented calculateRootIncrement for merkle witnesses --- .../src/trees/sparse/RollupMerkleTree.ts | 109 +++++++++++++++--- packages/common/test/trees/MerkleTree.test.ts | 33 +++++- 2 files changed, 122 insertions(+), 20 deletions(-) diff --git a/packages/common/src/trees/sparse/RollupMerkleTree.ts b/packages/common/src/trees/sparse/RollupMerkleTree.ts index 1974e7078..f901ccf29 100644 --- a/packages/common/src/trees/sparse/RollupMerkleTree.ts +++ b/packages/common/src/trees/sparse/RollupMerkleTree.ts @@ -7,6 +7,17 @@ import { TypedClass } from "../../types"; import { MerkleTreeStore } from "./MerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; +/** + * More efficient version of `maybeSwapBad` which + * reuses an intermediate variable + */ +export function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { + const m = b.toField().mul(x.sub(y)); // b*(x - y) + const x1 = y.add(m); // y + b*(x - y) + const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x) + return [x1, y2]; +} + export class StructTemplate extends Struct({ path: Provable.Array(Field, 0), isLeft: Provable.Array(Bool, 0), @@ -22,6 +33,11 @@ export interface AbstractMerkleWitness extends StructTemplate { */ calculateRoot(hash: Field): Field; + calculateRootIncrement( + index: Field, + leaf: Field + ): [Field, AbstractMerkleWitness]; + /** * Calculates the index of the leaf node that belongs to this Witness. * @returns Index of the leaf. @@ -119,6 +135,15 @@ export interface AbstractMerkleTreeClass { * It also holds the Witness class under tree.WITNESS */ export function createMerkleTree(height: number): AbstractMerkleTreeClass { + function generateZeroes() { + const zeroes = [0n]; + for (let index = 1; index < height; index += 1) { + const previousLevel = Field(zeroes[index - 1]); + zeroes.push(Poseidon.hash([previousLevel, previousLevel]).toBigInt()); + } + return zeroes; + } + /** * The {@link RollupMerkleWitness} class defines a circuit-compatible base class * for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof). @@ -147,7 +172,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { for (let index = 1; index < n; ++index) { const isLeft = this.isLeft[index - 1]; - // eslint-disable-next-line @typescript-eslint/no-use-before-define + const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); hash = Poseidon.hash([left, right]); } @@ -155,6 +180,68 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { return hash; } + public calculateRootIncrement( + leafIndex: Field, + leaf: Field + ): [Field, RollupMerkleWitness] { + let zero: bigint[] = []; + Provable.asProver(() => { + zero = generateZeroes(); + }); + + if (zero.length === 0) { + throw new Error("Zeroes not initialized"); + } + const zeroes = zero.map((x) => Field(x)); + + let hash = leaf; + const n = this.height(); + + let notDiverged = Bool(true); + const newPath = leafIndex.add(1).toBits(); + newPath.push(Bool(false)); + + const newSiblings: Field[] = []; + const newIsLefts: Bool[] = []; + + for (let index = 0; index < n - 1; ++index) { + const isLeft = this.isLeft[index]; + const sibling = this.path[index]; + + const newIsLeft = newPath[index].not(); + + // Bool(true) default for root level + let convergesNextLevel = Bool(true); + if (index < n - 2) { + convergesNextLevel = newPath[index + 1] + .equals(this.isLeft[index + 1]) + .not(); + } + + const nextSibling = Provable.if( + convergesNextLevel.and(notDiverged), + hash, + Provable.if(notDiverged, zeroes[index], sibling) + ); + + notDiverged = notDiverged.and(convergesNextLevel.not()); + + newSiblings.push(nextSibling); + newIsLefts.push(newIsLeft); + + const [left, right] = maybeSwap(isLeft, hash, sibling); + hash = Poseidon.hash([left, right]); + } + + return [ + hash, + new RollupMerkleWitness({ + isLeft: newIsLefts, + path: newSiblings, + }), + ]; + } + /** * Calculates the index of the leaf node that belongs to this Witness. * @returns Index of the leaf. @@ -215,6 +302,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { }); } } + return class AbstractRollupMerkleTree implements AbstractMerkleTree { public static HEIGHT = height; @@ -238,13 +326,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { public constructor(store: MerkleTreeStore) { this.store = store; - this.zeroes = [0n]; - for (let index = 1; index < AbstractRollupMerkleTree.HEIGHT; index += 1) { - const previousLevel = Field(this.zeroes[index - 1]); - this.zeroes.push( - Poseidon.hash([previousLevel, previousLevel]).toBigInt() - ); - } + this.zeroes = generateZeroes(); } public assertIndexRange(index: bigint) { @@ -414,14 +496,3 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { export class RollupMerkleTree extends createMerkleTree(256) {} export class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {} - -/** - * More efficient version of `maybeSwapBad` which - * reuses an intermediate variable - */ -export function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { - const m = b.toField().mul(x.sub(y)); // b*(x - y) - const x1 = y.add(m); // y + b*(x - y) - const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x) - return [x1, y2]; -} diff --git a/packages/common/test/trees/MerkleTree.test.ts b/packages/common/test/trees/MerkleTree.test.ts index 221c8d70e..933e07504 100644 --- a/packages/common/test/trees/MerkleTree.test.ts +++ b/packages/common/test/trees/MerkleTree.test.ts @@ -1,5 +1,5 @@ import { beforeEach } from "@jest/globals"; -import { Field } from "o1js"; +import { Field, Provable } from "o1js"; import { createMerkleTree, @@ -217,4 +217,35 @@ describe.each([4, 16, 256])("cachedMerkleTree - %s", (height) => { tree.getNode(0, index); }).toThrow("Index greater than maximum leaf number"); }); + + it("witness incrementing", () => { + tree.setLeaf(0n, Field(3256)); + tree.setLeaf(1n, Field(3256)); + tree.setLeaf(2n, Field(3256)); + + const witness = tree.getWitness(3n); + + const [root, newWitness] = witness.calculateRootIncrement( + Field(3), + Field(1234) + ); + tree.setLeaf(3n, Field(1234)); + + expect(tree.getRoot().toString()).toStrictEqual(root.toString()); + expect(newWitness.calculateIndex().toString()).toStrictEqual("4"); + + const [root2, newWitness2] = newWitness.calculateRootIncrement( + Field(4), + Field(4321) + ); + tree.setLeaf(4n, Field(4321)); + + expect(tree.getRoot().toString()).toStrictEqual(root2.toString()); + expect(newWitness2.calculateIndex().toString()).toStrictEqual("5"); + + const root3 = newWitness2.calculateRoot(Field(555)); + tree.setLeaf(5n, Field(555)); + + expect(tree.getRoot().toString()).toStrictEqual(root3.toString()); + }); }); From b824a5b8d2ea736a15de4a8dcf3c0c8c7dc02877 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 21 Dec 2025 14:19:21 +0100 Subject: [PATCH 081/155] Fixed transaction prover tests again --- packages/sdk/test/blockProof/blockProof.test.ts | 1 - packages/sdk/test/modularization.test.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/sdk/test/blockProof/blockProof.test.ts b/packages/sdk/test/blockProof/blockProof.test.ts index f2c81ad29..2bc920780 100644 --- a/packages/sdk/test/blockProof/blockProof.test.ts +++ b/packages/sdk/test/blockProof/blockProof.test.ts @@ -101,7 +101,6 @@ describe.skip("blockProof", () => { incomingMessagesHash: Field(0), transactionsHash: Field(0), eternalTransactionsHash: Field(0), - blockHashRoot: Field(0), }, }); }); diff --git a/packages/sdk/test/modularization.test.ts b/packages/sdk/test/modularization.test.ts index 307082a71..f0ee7a8bb 100644 --- a/packages/sdk/test/modularization.test.ts +++ b/packages/sdk/test/modularization.test.ts @@ -67,11 +67,7 @@ describe("modularization", () => { TestRuntimeModule: {}, }, Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, + ...Protocol.defaultConfig(), TransactionFee: { tokenId: 0n, feeRecipient: PrivateKey.random().toPublicKey().toBase58(), From 71554dbda0b2693cec6b8cd8b6f785a6f9f62015 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 21 Dec 2025 15:52:36 +0100 Subject: [PATCH 082/155] Fixed transaction prover tests again again --- packages/stack/test/graphql/graphql-server.ts | 6 +----- packages/stack/test/graphql/graphql.test.ts | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/stack/test/graphql/graphql-server.ts b/packages/stack/test/graphql/graphql-server.ts index 01c26e565..584fb6fb2 100644 --- a/packages/stack/test/graphql/graphql-server.ts +++ b/packages/stack/test/graphql/graphql-server.ts @@ -135,10 +135,7 @@ export async function startGraphqlServer() { }, Protocol: { - BlockProver: {}, - StateTransitionProver: {}, - AccountState: {}, - BlockHeight: {}, + ...Protocol.defaultConfig(), TransactionFee: { tokenId: 0n, feeRecipient: PrivateKey.random().toPublicKey().toBase58(), @@ -146,7 +143,6 @@ export async function startGraphqlServer() { methods: {}, perWeightUnitFee: 0n, }, - LastStateRoot: {}, }, Sequencer: { diff --git a/packages/stack/test/graphql/graphql.test.ts b/packages/stack/test/graphql/graphql.test.ts index 2cbb2526f..45b29206b 100644 --- a/packages/stack/test/graphql/graphql.test.ts +++ b/packages/stack/test/graphql/graphql.test.ts @@ -57,10 +57,7 @@ function prepareClient() { }, Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - BlockHeight: {}, + ...Protocol.defaultConfig(), TransactionFee: { tokenId: 0n, feeRecipient: PrivateKey.random().toPublicKey().toBase58(), @@ -68,7 +65,6 @@ function prepareClient() { methods: {}, perWeightUnitFee: 0n, }, - LastStateRoot: {}, }, Sequencer: { From 80d9a69e6090ceb66dfc7e9f23ea8b419fde0c74 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 9 Jan 2026 00:20:55 +0100 Subject: [PATCH 083/155] Implemented BlockProver v3 circuits --- packages/protocol/src/index.ts | 1 + .../src/protocol/ProvableBlockHook.ts | 41 +- .../src/protocol/ProvableTransactionHook.ts | 14 +- .../src/prover/accumulators/BlockHashList.ts | 117 ++++ .../accumulators/WitnessedRootHashList.ts | 2 +- .../src/prover/block/BlockProvable.ts | 236 ++++++- .../protocol/src/prover/block/BlockProver.ts | 585 ++++++++---------- .../prover/transaction/TransactionProvable.ts | 69 ++- .../prover/transaction/TransactionProver.ts | 198 +++--- packages/protocol/src/prover/utils.ts | 12 +- .../contracts/settlement/SettlementBase.ts | 14 +- .../src/utils/MinaPrefixedProvableHashList.ts | 8 +- .../src/utils/PrefixedProvableHashList.ts | 4 + .../protocol/src/utils/ProvableHashList.ts | 39 +- 14 files changed, 821 insertions(+), 519 deletions(-) create mode 100644 packages/protocol/src/prover/accumulators/BlockHashList.ts diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 8d8cf7d9d..34e794b3d 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -22,6 +22,7 @@ export * from "./prover/accumulators/StateTransitionReductionList"; export * from "./prover/accumulators/AppliedBatchHashList"; export * from "./prover/accumulators/WitnessedRootHashList"; export * from "./prover/accumulators/TransactionHashList"; +export * from "./prover/accumulators/BlockHashList"; export * from "./prover/block/BlockProver"; export * from "./prover/block/BlockProvable"; export * from "./prover/block/accummulators/RuntimeVerificationKeyTree"; diff --git a/packages/protocol/src/protocol/ProvableBlockHook.ts b/packages/protocol/src/protocol/ProvableBlockHook.ts index 7ba6dcd32..4de723a6f 100644 --- a/packages/protocol/src/protocol/ProvableBlockHook.ts +++ b/packages/protocol/src/protocol/ProvableBlockHook.ts @@ -3,47 +3,52 @@ import { NoConfig } from "@proto-kit/common"; import { NetworkState } from "../model/network/NetworkState"; import { - BlockProverState, BlockProverPublicInput, + BlockArguments, + BlockProverState, } from "../prover/block/BlockProvable"; import { TransitioningProtocolModule } from "./TransitioningProtocolModule"; export type ProvableHookBlockState = Pick< - BlockProverPublicInput, - | "transactionsHash" - | "eternalTransactionsHash" - | "incomingMessagesHash" - | "blockHashRoot" + BlockProverPublicInput & BlockArguments, + "eternalTransactionsHash" | "incomingMessagesHash" | "blockHashRoot" >; -export function toProvableHookBlockState( +export function toBeforeBlockHookArgument( state: Pick< BlockProverState, - | "transactionList" - | "eternalTransactionsList" - | "incomingMessages" - | "blockHashRoot" + "eternalTransactionsList" | "incomingMessages" | "blockHashRoot" > ) { - const { - transactionList, - eternalTransactionsList, - incomingMessages, - blockHashRoot, - } = state; + const { eternalTransactionsList, incomingMessages, blockHashRoot } = state; return { - transactionsHash: transactionList.commitment, eternalTransactionsHash: eternalTransactionsList.commitment, incomingMessagesHash: incomingMessages.commitment, blockHashRoot, }; } +export function toAfterBlockHookArgument( + state: Pick< + BlockProverState, + "eternalTransactionsList" | "incomingMessages" | "blockHashRoot" + >, + stateRoot: Field, + transactionsHash: Field +) { + return { + ...toBeforeBlockHookArgument(state), + stateRoot, + transactionsHash, + }; +} + export interface BeforeBlockHookArguments extends ProvableHookBlockState {} export interface AfterBlockHookArguments extends BeforeBlockHookArguments { stateRoot: Field; + transactionsHash: Field; } // Purpose is to build transition from -> to network state diff --git a/packages/protocol/src/protocol/ProvableTransactionHook.ts b/packages/protocol/src/protocol/ProvableTransactionHook.ts index 68fa3bedf..43280ff3c 100644 --- a/packages/protocol/src/protocol/ProvableTransactionHook.ts +++ b/packages/protocol/src/protocol/ProvableTransactionHook.ts @@ -1,28 +1,28 @@ import { NoConfig } from "@proto-kit/common"; -import { Signature } from "o1js"; +import { Field, Signature } from "o1js"; import { RuntimeTransaction } from "../model/transaction/RuntimeTransaction"; import { NetworkState } from "../model/network/NetworkState"; import { MethodPublicOutput } from "../model/MethodPublicOutput"; import { - TransactionProverPublicInput, TransactionProverState, TransactionProverTransactionArguments, } from "../prover/transaction/TransactionProvable"; import { TransitioningProtocolModule } from "./TransitioningProtocolModule"; -export type ProvableHookTransactionState = Pick< - TransactionProverPublicInput, - "transactionsHash" | "eternalTransactionsHash" | "incomingMessagesHash" ->; +export type ProvableHookTransactionState = { + transactionsHash: Field; + eternalTransactionsHash: Field; + incomingMessagesHash: Field; +}; export function toProvableHookTransactionState( state: Pick< TransactionProverState, "transactionList" | "eternalTransactionsList" | "incomingMessages" > -) { +): ProvableHookTransactionState { const { transactionList, eternalTransactionsList, incomingMessages } = state; return { transactionsHash: transactionList.commitment, diff --git a/packages/protocol/src/prover/accumulators/BlockHashList.ts b/packages/protocol/src/prover/accumulators/BlockHashList.ts new file mode 100644 index 000000000..b0e732e65 --- /dev/null +++ b/packages/protocol/src/prover/accumulators/BlockHashList.ts @@ -0,0 +1,117 @@ +import { Field, Struct } from "o1js"; + +import { DefaultProvableHashList } from "../../utils/ProvableHashList"; +import type { TransactionProverState } from "../transaction/TransactionProvable"; +import { NetworkState } from "../../model/network/NetworkState"; + +export class BundlePreimage extends Struct({ + preimage: Field, + fromStateTransitionsHash: Field, + fromWitnessedRootsHash: Field, +}) {} + +export class FieldTransition extends Struct({ + from: Field, + to: Field, +}) {} + +/** + * A bundle represents an ordered list of transactions and their evaluated effects. + * Specifically, this includes beforeTransaction, runtime and afterTransaction evaluation, + * but not block hooks. + */ +export class Bundle extends Struct({ + // Those are per-block trackers + networkStateHash: Field, + transactionsHash: Field, + + // Those are non-linear trackers that we assert later in the blockprover + pendingSTBatchesHash: FieldTransition, + witnessedRootsHash: FieldTransition, +}) {} + +/** + * This hash list collects an ordered list of Bundle instances. + * "Pushing" onto this list can mean either appending a new bundle or updating the + * bundle at the tip of this list, according to the following rules: + * The validated preimage (via checkLastBundleElement) is: + * - == commitment: A new bundle will be appended + * - something else: The preimage is the actual preimage, therefore as a operation, + * the old one will be popped (silently) and the updates bundle will be pushed, + * resulting in an semantic update of the tip. + */ +export class BundleHashList extends DefaultProvableHashList { + public constructor( + commitment: Field = Field(0), + // TODO Refactor this into preimage and "auxiliary batch information" - this is confusing + public preimage?: BundlePreimage + ) { + super(Bundle, commitment); + } + + /** Verifies this list's preimage against the prover's state + * The main impact this function has is that it makes the preimage trusted + * i.e. we can safely use it to add to the bundle/open a new bundle + */ + public checkLastBundleElement( + state: TransactionProverState, + networkState: NetworkState + ) { + const { preimage, fromWitnessedRootsHash, fromStateTransitionsHash } = + this.preimage!; + + // Check and append to bundlelist + const lastElement = new Bundle({ + networkStateHash: networkState.hash(), + transactionsHash: state.transactionList.commitment, + pendingSTBatchesHash: { + from: fromStateTransitionsHash, + to: state.pendingSTBatches.commitment, + }, + witnessedRootsHash: { + from: fromWitnessedRootsHash, + to: state.witnessedRoots.commitment, + }, + }); + + const newBundle = this.commitment.equals(preimage); + this.witnessTip(preimage, lastElement) + .or(newBundle) + .assertTrue("Last element not valid"); + + newBundle + .implies(state.transactionList.isEmpty()) + .assertTrue("Transaction list not empty for new bundle"); + } + + /** + * This function pushes a new bundle onto this list or updates the bundle at + * the tip of this list, according to the rules of the preimage algorithms (see class docs) + */ + public addToBundle( + state: TransactionProverState, + networkState: NetworkState + ) { + const { preimage, fromWitnessedRootsHash, fromStateTransitionsHash } = + this.preimage!; + + const newElement = new Bundle({ + networkStateHash: networkState.hash(), + transactionsHash: state.transactionList.commitment, + pendingSTBatchesHash: { + from: fromStateTransitionsHash, + to: state.pendingSTBatches.commitment, + }, + witnessedRootsHash: { + from: fromWitnessedRootsHash, + to: state.witnessedRoots.commitment, + }, + }); + + // We always overwrite here, the invariant is that the preimage is + // either the actual preimage in case of addition to the existing bundle + // or the current commitment in case of a new bundle + this.commitment = preimage; + this.push(newElement); + } +} diff --git a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts index 9a8250168..7226492da 100644 --- a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts +++ b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts @@ -52,7 +52,7 @@ export class WitnessedRootHashList extends DefaultProvableHashList Already covered in BlockProver + // (1) don't append if witnessedRoot == finalizedRoot -> Already covered in BlockProver // (2) don't append if preimage.push({ finalizedRoot, pendingSTBatchesHash }) == this.commitment const skipPush = preimageCheckList.commitment.equals(this.commitment); diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index dbcb311fe..c0396b177 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -1,4 +1,4 @@ -import { Bool, Field, Proof, Struct } from "o1js"; +import { Bool, Field, Proof, Provable, Struct } from "o1js"; import { CompilableModule, WithZkProgrammable } from "@proto-kit/common"; import { StateTransitionProof } from "../statetransition/StateTransitionProvable"; @@ -10,15 +10,68 @@ import { WitnessedRootHashList, WitnessedRootWitness, } from "../accumulators/WitnessedRootHashList"; -import { - TransactionProof, - TransactionProverState, - TransactionProverStateCommitments, -} from "../transaction/TransactionProvable"; +import { TransactionProof } from "../transaction/TransactionProvable"; +import { BundleHashList, FieldTransition } from "../accumulators/BlockHashList"; +import { NonMethods } from "../../utils/utils"; import { BlockHashMerkleTreeWitness } from "./accummulators/BlockHashMerkleTree"; -export class BlockProverState extends TransactionProverState { +export const BLOCK_ARGUMENT_BATCH_SIZE = 4; + +export class BlockArguments extends Struct({ + afterBlockRootWitness: WitnessedRootWitness, + transactionsHash: Field, + pendingSTBatchesHash: FieldTransition, + witnessedRootsHash: FieldTransition, + isDummy: Bool, +}) { + public static noop( + state: NonMethods>, + stateRoot: Field + ) { + return new BlockArguments({ + afterBlockRootWitness: { + witnessedRoot: stateRoot, + preimage: Field(0), + }, + transactionsHash: Field(0), + pendingSTBatchesHash: { + from: state.pendingSTBatches.commitment, + to: state.pendingSTBatches.commitment, + }, + witnessedRootsHash: { + from: state.witnessedRoots.commitment, + to: state.witnessedRoots.commitment, + }, + isDummy: Bool(true), + }); + } +} + +export class BlockArgumentsBatch extends Struct({ + batch: Provable.Array(BlockArguments, BLOCK_ARGUMENT_BATCH_SIZE), +}) {} + +export class BlockProverState { + /** + * The network state which gives access to values such as blockHeight + * This value is the same for the whole batch (L2 block) + */ + bundleList: BundleHashList; + + /** + * A variant of the transactionsHash that is never reset. + * Thought for usage in the sequence state mempool. + * In comparison, transactionsHash restarts at 0 for every new block + */ + eternalTransactionsList: TransactionHashList; + + pendingSTBatches: AppliedBatchHashList; + + incomingMessages: MinaActionsHashList; + + witnessedRoots: WitnessedRootHashList; + /** * The current state root of the block prover */ @@ -32,50 +85,184 @@ export class BlockProverState extends TransactionProverState { blockNumber: Field; + blockWitness: BlockHashMerkleTreeWitness; + + networkState: NetworkState; + constructor(args: { - transactionList: TransactionHashList; networkState: NetworkState; eternalTransactionsList: TransactionHashList; pendingSTBatches: AppliedBatchHashList; - incomingMessages: MinaActionsHashList; - witnessedRoots: WitnessedRootHashList; stateRoot: Field; blockHashRoot: Field; blockNumber: Field; + bundleList: BundleHashList; + blockWitness: BlockHashMerkleTreeWitness; + witnessedRoots: WitnessedRootHashList; + incomingMessages: MinaActionsHashList; }) { - super(args); + this.bundleList = args.bundleList; + this.eternalTransactionsList = args.eternalTransactionsList; + this.pendingSTBatches = args.pendingSTBatches; this.stateRoot = args.stateRoot; this.blockHashRoot = args.blockHashRoot; this.blockNumber = args.blockNumber; + this.networkState = args.networkState; + this.blockWitness = args.blockWitness; + this.witnessedRoots = args.witnessedRoots; + this.incomingMessages = args.incomingMessages; } public toCommitments(): BlockProverPublicInput { return { - ...super.toCommitments(), + remainders: { + bundlesHash: this.bundleList.commitment, + pendingSTBatchesHash: this.pendingSTBatches.commitment, + witnessedRootsHash: this.witnessedRoots.commitment, + }, + eternalTransactionsHash: this.eternalTransactionsList.commitment, + incomingMessagesHash: this.incomingMessages.commitment, stateRoot: this.stateRoot, blockHashRoot: this.blockHashRoot, blockNumber: this.blockNumber, + networkStateHash: this.networkState.hash(), }; } - public static fromCommitments( + public static blockProverFromCommitments( publicInput: BlockProverPublicInput, - networkState: NetworkState + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness ): BlockProverState { return new BlockProverState({ - ...super.fromCommitments(publicInput, networkState), + bundleList: new BundleHashList(publicInput.remainders.bundlesHash), + eternalTransactionsList: new TransactionHashList( + publicInput.eternalTransactionsHash + ), + incomingMessages: new MinaActionsHashList( + publicInput.incomingMessagesHash + ), + pendingSTBatches: new AppliedBatchHashList( + publicInput.remainders.pendingSTBatchesHash + ), + witnessedRoots: new WitnessedRootHashList( + publicInput.remainders.witnessedRootsHash + ), stateRoot: publicInput.stateRoot, blockHashRoot: publicInput.blockHashRoot, blockNumber: publicInput.blockNumber, + networkState, + blockWitness, + }); + } + + public copy() { + return BlockProverState.fromFields(this.toFields()); + } + + public toFields() { + return [ + this.bundleList.commitment, + this.eternalTransactionsList.commitment, + this.pendingSTBatches.commitment, + this.incomingMessages.commitment, + this.witnessedRoots.commitment, + this.stateRoot, + this.blockHashRoot, + this.blockNumber, + ...NetworkState.toFields(this.networkState), + ...BlockHashMerkleTreeWitness.toFields(this.blockWitness), + ]; + } + + // TODO Unit test + public static fromFields(fields: Field[]) { + return new BlockProverState({ + bundleList: new BundleHashList(fields[0]), + eternalTransactionsList: new TransactionHashList(fields[1]), + pendingSTBatches: new AppliedBatchHashList(fields[2]), + incomingMessages: new MinaActionsHashList(fields[3]), + witnessedRoots: new WitnessedRootHashList(fields[4]), + stateRoot: fields[5], + blockHashRoot: fields[6], + blockNumber: fields[7], + networkState: new NetworkState(NetworkState.fromFields(fields.slice(8))), + blockWitness: new BlockHashMerkleTreeWitness( + BlockHashMerkleTreeWitness.fromFields( + fields.slice(8 + NetworkState.sizeInFields()) + ) + ), + }); + } + + public static choose( + condition: Bool, + a: BlockProverState, + b: BlockProverState + ) { + return new BlockProverState({ + bundleList: new BundleHashList( + Provable.if(condition, a.bundleList.commitment, b.bundleList.commitment) + ), + eternalTransactionsList: new TransactionHashList( + Provable.if( + condition, + a.eternalTransactionsList.commitment, + b.eternalTransactionsList.commitment + ) + ), + pendingSTBatches: new AppliedBatchHashList( + Provable.if( + condition, + a.pendingSTBatches.commitment, + b.pendingSTBatches.commitment + ) + ), + incomingMessages: new MinaActionsHashList( + Provable.if( + condition, + a.incomingMessages.commitment, + b.incomingMessages.commitment + ) + ), + witnessedRoots: new WitnessedRootHashList( + Provable.if( + condition, + a.witnessedRoots.commitment, + b.witnessedRoots.commitment + ) + ), + stateRoot: Provable.if(condition, a.stateRoot, b.stateRoot), + blockHashRoot: Provable.if(condition, a.blockHashRoot, b.blockHashRoot), + blockWitness: new BlockHashMerkleTreeWitness( + Provable.if( + condition, + BlockHashMerkleTreeWitness, + a.blockWitness, + b.blockWitness + ) + ), + blockNumber: Provable.if(condition, a.blockNumber, b.blockNumber), + networkState: new NetworkState( + Provable.if(condition, NetworkState, a.networkState, b.networkState) + ), }); } } export const BlockProverStateCommitments = { - ...TransactionProverStateCommitments, + remainders: { + // Commitment to the list of unprocessed (pending) batches of STs that need to be proven + pendingSTBatchesHash: Field, + witnessedRootsHash: Field, + bundlesHash: Field, + }, + eternalTransactionsHash: Field, + incomingMessagesHash: Field, stateRoot: Field, blockHashRoot: Field, blockNumber: Field, + networkStateHash: Field, }; export class BlockProverPublicInput extends Struct( @@ -84,13 +271,9 @@ export class BlockProverPublicInput extends Struct( export class BlockProverPublicOutput extends Struct({ ...BlockProverStateCommitments, - closed: Bool, }) { - public equals(input: BlockProverPublicInput, closed: Bool): Bool { - const output2 = BlockProverPublicOutput.toFields({ - ...input, - closed, - }); + public equals(input: BlockProverPublicInput): Bool { + const output2 = BlockProverPublicOutput.toFields(input); const output1 = BlockProverPublicOutput.toFields(this); return output1 .map((value1, index) => value1.equals(output2[index])) @@ -103,14 +286,15 @@ export type BlockProof = Proof; export interface BlockProvable extends WithZkProgrammable, CompilableModule { - proveBlock: ( + proveBlockBatch: ( publicInput: BlockProverPublicInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, - deferSTs: Bool, - afterBlockRootWitness: WitnessedRootWitness, - transactionProof: TransactionProof + deferSTProof: Bool, + transactionProof: TransactionProof, + deferTransactionProof: Bool, + batch: BlockArgumentsBatch ) => Promise; merge: ( diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index ede4ea8c9..c110e28eb 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -6,9 +6,9 @@ import { CompileArtifact, CompileRegistry, log, - MAX_FIELD, PlainZkProgram, provableMethod, + reduceSequential, WithZkProgrammable, ZkProgrammable, } from "@proto-kit/common"; @@ -26,11 +26,11 @@ import { AfterBlockHookArguments, BeforeBlockHookArguments, ProvableBlockHook, - toProvableHookBlockState, + toAfterBlockHookArgument, + toBeforeBlockHookArgument, } from "../../protocol/ProvableBlockHook"; import { NetworkState } from "../../model/network/NetworkState"; import { assertEqualsIf } from "../../utils/utils"; -import { WitnessedRootWitness } from "../accumulators/WitnessedRootHashList"; import { StateServiceProvider } from "../../state/StateServiceProvider"; import { executeHooks } from "../utils"; import { @@ -39,6 +39,7 @@ import { TransactionProverPublicInput, TransactionProverPublicOutput, } from "../transaction/TransactionProvable"; +import { Bundle } from "../accumulators/BlockHashList"; import { BlockProvable, @@ -46,6 +47,8 @@ import { BlockProverPublicInput, BlockProverPublicOutput, BlockProverState, + BlockArgumentsBatch, + BlockArguments, } from "./BlockProvable"; import { BlockHashMerkleTreeWitness, @@ -57,17 +60,12 @@ const errors = { `${propertyName} not matching: ${step}`, propertyNotMatching: (propertyName: string) => `${propertyName} not matching`, - - stateRootNotMatching: (step: string) => - errors.propertyNotMatchingStep("StateRoots", step), - - transactionsHashNotMatching: (step: string) => - errors.propertyNotMatchingStep("Transactions hash", step), - - networkStateHashNotMatching: (step: string) => - errors.propertyNotMatchingStep("Network state hash", step), }; +type BlockHookArgument = T extends "before" + ? BeforeBlockHookArguments + : AfterBlockHookArguments; + export class BlockProverProgrammable extends ZkProgrammable< BlockProverPublicInput, BlockProverPublicOutput @@ -94,16 +92,16 @@ export class BlockProverProgrammable extends ZkProgrammable< return this.prover.areProofsEnabled; } - public async executeBlockHooks< - T extends BeforeBlockHookArguments | AfterBlockHookArguments, - >( + public async executeBlockHooks( + type: T, hook: ( module: ProvableBlockHook, networkState: NetworkState, - args: T + args: BlockHookArgument ) => Promise, - hookArguments: T, - inputNetworkState: NetworkState + hookArguments: BlockHookArgument, + inputNetworkState: NetworkState, + isDummy: Bool ) { const transaction = RuntimeTransaction.dummyTransaction(); const startingInputs = { @@ -111,26 +109,33 @@ export class BlockProverProgrammable extends ZkProgrammable< networkState: inputNetworkState, }; - return await executeHooks(startingInputs, async () => { - const executionContext = container.resolve(RuntimeMethodExecutionContext); - - return await this.blockHooks.reduce>( - async (networkStatePromise, blockHook) => { - const networkState = await networkStatePromise; - - // Setup context for potential calls to runtime methods. - // With the special case that we set the new networkstate for every hook - // We also have to put in a dummy transaction for network.transaction - executionContext.setup({ - transaction: RuntimeTransaction.dummyTransaction(), - networkState, - }); - - return await hook(blockHook, networkState, hookArguments); - }, - Promise.resolve(inputNetworkState) - ); - }); + return await executeHooks( + startingInputs, + `${type}Block`, + async () => { + const executionContext = container.resolve( + RuntimeMethodExecutionContext + ); + + return await this.blockHooks.reduce>( + async (networkStatePromise, blockHook) => { + const networkState = await networkStatePromise; + + // Setup context for potential calls to runtime methods. + // With the special case that we set the new networkstate for every hook + // We also have to put in a dummy transaction for network.transaction + executionContext.setup({ + transaction: RuntimeTransaction.dummyTransaction(), + networkState, + }); + + return await hook(blockHook, networkState, hookArguments); + }, + Promise.resolve(inputNetworkState) + ); + }, + isDummy + ); } public includeSTProof( @@ -216,102 +221,22 @@ export class BlockProverProgrammable extends ZkProgrammable< } @provableMethod() - public async proveBlock( + public async proveBlockBatch( publicInput: BlockProverPublicInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, deferSTProof: Bool, - afterBlockRootWitness: WitnessedRootWitness, - transactionProof: TransactionProof + transactionProof: TransactionProof, + deferTransactionProof: Bool, + batch: BlockArgumentsBatch ): Promise { - // 1. Make assertions about the inputs - publicInput.transactionsHash.assertEquals( - Field(0), - "Transactionshash has to start at 0" - ); - - // TransactionProof format checks - transactionProof.publicInput.networkStateHash.assertEquals( - transactionProof.publicOutput.networkStateHash, - "TransactionProof cannot alter the network state" - ); - - const state = BlockProverState.fromCommitments(publicInput, networkState); - - // Verify Transaction proof if it has at least 1 tx - i.e. the - // input and output doesn't match fully - // We have to compare the whole input and output because we can make no - // assumptions about the values, since it can be an arbitrary dummy-proof - const isEmptyTransition = TransactionProverPublicInput.equals( - transactionProof.publicOutput, - transactionProof.publicInput - ); - const skipTransactionProofVerification = isEmptyTransition; - const verifyTransactionProof = isEmptyTransition.not(); - log.provable.debug("VerifyIf TxProof", verifyTransactionProof); - transactionProof.verifyIf(verifyTransactionProof); - - // 2. Execute beforeBlock hooks - const beforeBlockArgs = toProvableHookBlockState(state); - const beforeBlockResult = await this.executeBlockHooks( - async (module, networkStateArg, args) => - await module.beforeBlock(networkStateArg, args), - beforeBlockArgs, - networkState - ); - - state.pendingSTBatches.push(beforeBlockResult.batch); - - // 4. Apply TX-type BlockProof - transactionProof.publicInput.networkStateHash - .equals(beforeBlockResult.result.hash()) - .or(skipTransactionProofVerification) - .assertTrue( - "TransactionProof networkstate hash not matching beforeBlock hook result" - ); - - // Check that the transaction proof's STs start after the beforeBlock hook - transactionProof.publicInput.pendingSTBatchesHash.assertEquals( - state.pendingSTBatches.commitment, - "Transaction proof doesn't start their STs after the beforeBlockHook" - ); - // Fast-forward the stBatchHashList to after all transactions appended - state.pendingSTBatches.commitment = - transactionProof.publicOutput.pendingSTBatchesHash; - - // Fast-forward block content commitments by the results of the aggregated transaction proof - // Implicitly, the 'from' values here are asserted against the publicInput, since the hashlists - // are created out of the public input - state.transactionList.fastForward({ - from: transactionProof.publicInput.transactionsHash, - to: transactionProof.publicOutput.transactionsHash, - }); - state.eternalTransactionsList.fastForward({ - from: transactionProof.publicInput.eternalTransactionsHash, - to: transactionProof.publicOutput.eternalTransactionsHash, - }); - state.incomingMessages.fastForward({ - from: transactionProof.publicInput.incomingMessagesHash, - to: transactionProof.publicOutput.incomingMessagesHash, - }); - - // Witness root - const isEmpty = state.pendingSTBatches.commitment.equals(0); - isEmpty - .implies(state.stateRoot.equals(afterBlockRootWitness.witnessedRoot)) - .assertTrue(); - - state.witnessedRoots.witnessRoot( - { - appliedBatchListState: state.pendingSTBatches.commitment, - root: afterBlockRootWitness.witnessedRoot, - }, - afterBlockRootWitness.preimage, - isEmpty.not() + publicInput.networkStateHash.assertEquals( + networkState.hash(), + "Network state not valid" ); - // 5. Calculate the new block tree hash + // Calculate the new block tree hash const blockIndex = blockWitness.calculateIndex(); blockIndex.assertEquals(publicInput.blockNumber); @@ -323,37 +248,70 @@ export class BlockProverProgrammable extends ZkProgrammable< "Supplied block hash witness not matching state root" ); - state.blockHashRoot = blockWitness.calculateRoot( - new BlockHashTreeEntry({ - block: { - index: blockIndex, - transactionListHash: state.transactionList.commitment, - }, - closed: Bool(true), - }).hash() + let state = BlockProverState.blockProverFromCommitments( + publicInput, + networkState, + blockWitness ); - // 6. Execute afterBlock hooks + // Prove blocks iteratively + state = await reduceSequential( + batch.batch, + async (current, block) => { + const result = await this.proveBlock(current.copy(), block); - // Switch state service to afterBlock one - this.stateServiceProvider.popCurrentStateService(); + this.stateServiceProvider.popCurrentStateService(); - const afterBlockHookArgs = toProvableHookBlockState(state); - const afterBlockResult = await this.executeBlockHooks( - async (module, networkStateArg, args) => - await module.afterBlock(networkStateArg, args), + return BlockProverState.choose(block.isDummy, current, result); + }, + state + ); + + // Verify Transaction proof if it has at least 1 tx and it isn't deferred + const verifyTransactionProof = deferTransactionProof + .not() + .and(state.bundleList.isEmpty().not()); + + transactionProof.verifyIf(verifyTransactionProof); + + // Fast-forward transaction trackers by the results of the aggregated transaction proof + // Implicitly, the 'from' values here are asserted against the publicInput, since the hashlists + // are created out of the public input + state.eternalTransactionsList.fastForwardIf( { - ...afterBlockHookArgs, - stateRoot: afterBlockRootWitness.witnessedRoot, + from: transactionProof.publicInput.eternalTransactionsHash, + to: transactionProof.publicOutput.eternalTransactionsHash, }, - beforeBlockResult.result + verifyTransactionProof, + "eternalTransactionsList" ); - state.pendingSTBatches.push(afterBlockResult.batch); + state.incomingMessages.fastForwardIf( + { + from: transactionProof.publicInput.incomingMessagesHash, + to: transactionProof.publicOutput.incomingMessagesHash, + }, + verifyTransactionProof, + "incomingMessages" + ); - state.networkState = afterBlockResult.result; + // Cancel out remainders for transaction proof + assertEqualsIf( + transactionProof.publicInput.bundlesHash, + Field(0), + verifyTransactionProof, + "TransactionProof has to start bundles at 0" + ); - // 7. Close block + // Fast Backwards actually, but logic holds + state.bundleList.fastForwardIf( + { + from: transactionProof.publicOutput.bundlesHash, + to: state.bundleList.empty(), + }, + verifyTransactionProof, + "bundles hash" + ); // Verify ST Proof only if STs have been emitted, // and we don't defer the verification of the STs @@ -375,172 +333,174 @@ export class BlockProverProgrammable extends ZkProgrammable< state.pendingSTBatches.commitment = stateProofResult.pendingSTBatchesHash; state.witnessedRoots.commitment = stateProofResult.witnessedRootsHash; - state.blockNumber = blockIndex.add(1); - - return new BlockProverPublicOutput({ - ...state.toCommitments(), - closed: Bool(true), - }); + return new BlockProverPublicOutput(state.toCommitments()); } - @provableMethod() - public async merge( - publicInput: BlockProverPublicInput, - proof1: BlockProof, - proof2: BlockProof - ): Promise { - proof1.verify(); - proof2.verify(); + private async proveBlock( + state: BlockProverState, + args: BlockArguments + ): Promise { + const { networkState, blockWitness } = state; + const { afterBlockRootWitness, transactionsHash, isDummy } = args; - // Check state - publicInput.stateRoot.assertEquals( - proof1.publicInput.stateRoot, - errors.stateRootNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.stateRoot.assertEquals( - proof2.publicInput.stateRoot, - errors.stateRootNotMatching("proof1.to -> proof2.from") + // 1. Execute beforeBlock hooks + const beforeBlockArgs = toBeforeBlockHookArgument(state); + const beforeBlockResult = await this.executeBlockHooks( + "before", + async (module, networkStateArg, hookArgs) => + await module.beforeBlock(networkStateArg, hookArgs), + beforeBlockArgs, + networkState, + isDummy ); - // Check transaction list hash. - // Only assert them if these are tx proofs, skip for closed proofs - publicInput.transactionsHash - .equals(proof1.publicInput.transactionsHash) - .or(proof1.publicOutput.closed) - .assertTrue( - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.transactionsHash - .equals(proof2.publicInput.transactionsHash) - .or(proof1.publicOutput.closed) - .assertTrue( - errors.transactionsHashNotMatching("proof1.to -> proof2.from") - ); + state.pendingSTBatches.push(beforeBlockResult.batch); - // Check networkhash - publicInput.networkStateHash.assertEquals( - proof1.publicInput.networkStateHash, - errors.networkStateHashNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.networkStateHash.assertEquals( - proof2.publicInput.networkStateHash, - errors.networkStateHashNotMatching("proof1.to -> proof2.from") + // 2. "Apply" TX-type BlockProof + args.pendingSTBatchesHash.from.assertEquals( + state.pendingSTBatches.commitment ); + args.witnessedRootsHash.from.assertEquals(state.witnessedRoots.commitment); + const isEmptyBlock = transactionsHash.equals(Field(0)); + const isNotEmptyBlock = isEmptyBlock.not(); - // Check blockHashRoot - publicInput.blockHashRoot.assertEquals( - proof1.publicInput.blockHashRoot, - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + // Check & fast-forward the stBatchHashList to after all transactions appended + state.pendingSTBatches.fastForward( + args.pendingSTBatchesHash, + "Transaction proof doesn't start their STs after the beforeBlockHook" ); - proof1.publicOutput.blockHashRoot.assertEquals( - proof2.publicInput.blockHashRoot, - errors.transactionsHashNotMatching("proof1.to -> proof2.from") + // Same for witnessedRootsHash + state.witnessedRoots.fastForward( + args.witnessedRootsHash, + "Transaction proof doesn't start with correct witnessed roots hash" ); - // Check eternalTransactionsHash - publicInput.eternalTransactionsHash.assertEquals( - proof1.publicInput.eternalTransactionsHash, - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.eternalTransactionsHash.assertEquals( - proof2.publicInput.eternalTransactionsHash, - errors.transactionsHashNotMatching("proof1.to -> proof2.from") + // Add block to bundles list + const bundle = new Bundle({ + transactionsHash: transactionsHash, + networkStateHash: beforeBlockResult.result.hash(), + pendingSTBatchesHash: args.pendingSTBatchesHash, + witnessedRootsHash: args.witnessedRootsHash, + }); + Provable.log("Pushing", isNotEmptyBlock, bundle); + state.bundleList.pushIf(bundle, isNotEmptyBlock); + + // 3. + // Calculate new block tree root and increment witness + // Blocknumber as the index here is already authenticated previously + const [root, newWitness] = blockWitness.calculateRootIncrement( + state.blockNumber, + new BlockHashTreeEntry({ + block: { + index: state.blockNumber, + transactionListHash: transactionsHash, + }, + closed: Bool(true), + }).hash() ); - // Check incomingMessagesHash - publicInput.incomingMessagesHash.assertEquals( - proof1.publicInput.incomingMessagesHash, - errors.propertyNotMatchingStep( - "IncomingMessagesHash", - "publicInput.from -> proof1.from" - ) - ); - proof1.publicOutput.incomingMessagesHash.assertEquals( - proof2.publicInput.incomingMessagesHash, - errors.propertyNotMatchingStep( - "IncomingMessagesHash", - "proof1.to -> proof2.from" - ) - ); + state.blockHashRoot = root; + state.blockWitness = newWitness; - // Check pendingSTBatchesHash - publicInput.pendingSTBatchesHash.assertEquals( - proof1.publicInput.pendingSTBatchesHash, - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.pendingSTBatchesHash.assertEquals( - proof2.publicInput.pendingSTBatchesHash, - errors.transactionsHashNotMatching("proof1.to -> proof2.from") + state.blockNumber = state.blockNumber.add(1); + + // 4. Execute afterBlock hooks + // Witness root + const isEmpty = state.pendingSTBatches.commitment.equals(0); + isEmpty + .implies(state.stateRoot.equals(afterBlockRootWitness.witnessedRoot)) + .assertTrue(); + + state.witnessedRoots.witnessRoot( + { + appliedBatchListState: state.pendingSTBatches.commitment, + root: afterBlockRootWitness.witnessedRoot, + }, + afterBlockRootWitness.preimage, + isEmpty.not() ); - // Check witnessedRootsHash - publicInput.witnessedRootsHash.assertEquals( - proof1.publicInput.witnessedRootsHash, - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + // Switch state service to afterBlock one + this.stateServiceProvider.popCurrentStateService(); + + // Execute hooks + const afterBlockHookArgs = toAfterBlockHookArgument( + state, + afterBlockRootWitness.witnessedRoot, + transactionsHash ); - proof1.publicOutput.witnessedRootsHash.assertEquals( - proof2.publicInput.witnessedRootsHash, - errors.transactionsHashNotMatching("proof1.to -> proof2.from") + const afterBlockResult = await this.executeBlockHooks( + "after", + async (module, networkStateArg, hookArgs) => + await module.afterBlock(networkStateArg, hookArgs), + { + ...afterBlockHookArgs, + }, + beforeBlockResult.result, + isDummy ); - // Assert closed indicator matches - // (i.e. we can only merge TX-Type and Block-Type with each other) - proof1.publicOutput.closed.assertEquals( - proof2.publicOutput.closed, - "Closed indicators not matching" - ); + // Apply state and network state changes + state.pendingSTBatches.push(afterBlockResult.batch); + state.networkState = afterBlockResult.result; - // Either - // blockNumbers are unset and proofs are unclosed or - // both blocks are closed, then they have to increment or - // one block is closed, then height has to be the same - - // Imperative algo would look like - // if(proof1.height == MAX && proof2.height == MAX){ - // assert !proof1.closed && !proof2.closed; - // }else if(proof1.closed && proof2.closed){ - // assert proof1.height + 1 == proof2.height - // // next one is omitted for now - // }else if(proof1.closed || proof2.closed{ - // assert proof1.height == proof2.height - // } - - const proof1Closed = proof1.publicOutput.closed; - const proof2Closed = proof2.publicOutput.closed; - - const blockNumberProgressionValid = publicInput.blockNumber - .equals(proof1.publicInput.blockNumber) - .and( - proof1.publicOutput.blockNumber.equals(proof2.publicInput.blockNumber) + return state; + } + + @provableMethod() + public async merge( + publicInput: BlockProverPublicInput, + proof1: BlockProof, + proof2: BlockProof + ): Promise { + proof1.verify(); + proof2.verify(); + + function checkProperty< + Key extends + | "stateRoot" + | "networkStateHash" + | "blockHashRoot" + | "eternalTransactionsHash" + | "incomingMessagesHash" + | "blockNumber", + >(key: Key) { + // Check state + publicInput[key].assertEquals( + proof1.publicInput[key], + errors.propertyNotMatchingStep(key, "publicInput.from -> proof1.from") + ); + proof1.publicOutput[key].assertEquals( + proof2.publicInput[key], + errors.propertyNotMatchingStep(key, "proof1.to -> proof2.from") + ); + } + + function checkRemainderProperty< + Key extends "pendingSTBatchesHash" | "witnessedRootsHash" | "bundlesHash", + >(key: Key) { + // Check state + publicInput.remainders[key].assertEquals( + proof1.publicInput.remainders[key], + errors.propertyNotMatchingStep(key, "publicInput.from -> proof1.from") ); + proof1.publicOutput.remainders[key].assertEquals( + proof2.publicInput.remainders[key], + errors.propertyNotMatchingStep(key, "proof1.to -> proof2.from") + ); + } - // For tx proofs, we check that the progression starts and end with MAX - // in addition to that both proofs are non-closed - const isValidTransactionMerge = publicInput.blockNumber - .equals(MAX_FIELD) - .and(blockNumberProgressionValid) - .and(proof1Closed.or(proof2Closed).not()); - - const isValidClosedMerge = proof1Closed - .and(proof2Closed) - .and(blockNumberProgressionValid); - - isValidTransactionMerge - .or(isValidClosedMerge) - .assertTrue("Invalid BlockProof merge"); - - return new BlockProverPublicOutput({ - stateRoot: proof2.publicOutput.stateRoot, - transactionsHash: proof2.publicOutput.transactionsHash, - networkStateHash: proof2.publicOutput.networkStateHash, - blockHashRoot: proof2.publicOutput.blockHashRoot, - eternalTransactionsHash: proof2.publicOutput.eternalTransactionsHash, - incomingMessagesHash: proof2.publicOutput.incomingMessagesHash, - closed: isValidClosedMerge, - blockNumber: proof2.publicOutput.blockNumber, - pendingSTBatchesHash: proof2.publicOutput.pendingSTBatchesHash, - witnessedRootsHash: proof2.publicOutput.witnessedRootsHash, - }); + checkProperty("stateRoot"); + checkProperty("networkStateHash"); + checkProperty("blockHashRoot"); + checkProperty("eternalTransactionsHash"); + checkProperty("incomingMessagesHash"); + + checkRemainderProperty("bundlesHash"); + checkRemainderProperty("pendingSTBatchesHash"); + checkRemainderProperty("witnessedRootsHash"); + + return proof2.publicOutput; } /** @@ -555,7 +515,7 @@ export class BlockProverProgrammable extends ZkProgrammable< const { prover, stateTransitionProver, transactionProver } = this; const StateTransitionProofClass = stateTransitionProver.zkProgram[0].Proof; const TransactionProofClass = transactionProver.zkProgram[0].Proof; - const proveBlock = prover.proveBlock.bind(prover); + const proveBlockBatch = prover.proveBlockBatch.bind(prover); const merge = prover.merge.bind(prover); const program = ZkProgram({ @@ -564,33 +524,36 @@ export class BlockProverProgrammable extends ZkProgrammable< publicOutput: BlockProverPublicOutput, methods: { - proveBlock: { + proveBlockBatch: { privateInputs: [ NetworkState, BlockHashMerkleTreeWitness, StateTransitionProofClass, Bool, - WitnessedRootWitness, TransactionProofClass, + Bool, + BlockArgumentsBatch, ], async method( publicInput: BlockProverPublicInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, - deferSTs: Bool, - afterBlockRootWitness: WitnessedRootWitness, - transactionProof: BlockProof + deferSTProof: Bool, + transactionProof: TransactionProof, + deferTransactionProof: Bool, + batch: BlockArgumentsBatch ) { return { - publicOutput: await proveBlock( + publicOutput: await proveBlockBatch( publicInput, networkState, blockWitness, stateTransitionProof, - deferSTs, - afterBlockRootWitness, - transactionProof + deferSTProof, + transactionProof, + deferTransactionProof, + batch ), }; }, @@ -614,7 +577,7 @@ export class BlockProverProgrammable extends ZkProgrammable< }); const methods = { - proveBlock: program.proveBlock, + proveBlockBatch: program.proveBlockBatch, merge: program.merge, }; @@ -683,23 +646,25 @@ export class BlockProver }); } - public proveBlock( + public proveBlockBatch( publicInput: BlockProverPublicInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, - deferSTs: Bool, - afterBlockRootWitness: WitnessedRootWitness, - transactionProof: TransactionProof + deferSTProof: Bool, + transactionProof: TransactionProof, + deferTransactionProof: Bool, + batch: BlockArgumentsBatch ): Promise { - return this.zkProgrammable.proveBlock( + return this.zkProgrammable.proveBlockBatch( publicInput, networkState, blockWitness, stateTransitionProof, - deferSTs, - afterBlockRootWitness, - transactionProof + deferSTProof, + transactionProof, + deferTransactionProof, + batch ); } diff --git a/packages/protocol/src/prover/transaction/TransactionProvable.ts b/packages/protocol/src/prover/transaction/TransactionProvable.ts index 4180fd906..bb99ab0cc 100644 --- a/packages/protocol/src/prover/transaction/TransactionProvable.ts +++ b/packages/protocol/src/prover/transaction/TransactionProvable.ts @@ -10,6 +10,7 @@ import { TransactionHashList } from "../accumulators/TransactionHashList"; import { AppliedBatchHashList } from "../accumulators/AppliedBatchHashList"; import { MinaActionsHashList } from "../../utils/MinaPrefixedProvableHashList"; import { WitnessedRootHashList } from "../accumulators/WitnessedRootHashList"; +import { BundleHashList, BundlePreimage } from "../accumulators/BlockHashList"; export class TransactionProverState { /** @@ -22,7 +23,7 @@ export class TransactionProverState { * The network state which gives access to values such as blockHeight * This value is the same for the whole batch (L2 block) */ - networkState: NetworkState; + bundleList: BundleHashList; /** * A variant of the transactionsHash that is never reset. @@ -39,14 +40,14 @@ export class TransactionProverState { constructor(args: { transactionList: TransactionHashList; - networkState: NetworkState; + bundleList: BundleHashList; eternalTransactionsList: TransactionHashList; pendingSTBatches: AppliedBatchHashList; incomingMessages: MinaActionsHashList; witnessedRoots: WitnessedRootHashList; }) { this.transactionList = args.transactionList; - this.networkState = args.networkState; + this.bundleList = args.bundleList; this.eternalTransactionsList = args.eternalTransactionsList; this.pendingSTBatches = args.pendingSTBatches; this.incomingMessages = args.incomingMessages; @@ -55,51 +56,56 @@ export class TransactionProverState { public toCommitments(): TransactionProverPublicInput { return { - networkStateHash: this.networkState.hash(), - pendingSTBatchesHash: this.pendingSTBatches.commitment, - transactionsHash: this.transactionList.commitment, + bundlesHash: this.bundleList.commitment, + // pendingSTBatchesHash: this.pendingSTBatches.commitment, + // transactionsHash: this.transactionList.commitment, eternalTransactionsHash: this.eternalTransactionsList.commitment, incomingMessagesHash: this.incomingMessages.commitment, - witnessedRootsHash: this.witnessedRoots.commitment, + // witnessedRootsHash: this.witnessedRoots.commitment, }; } public static fromCommitments( publicInput: TransactionProverPublicInput, - networkState: NetworkState + args: TransactionProverArguments ): TransactionProverState { - publicInput.networkStateHash.assertEquals( - networkState.hash(), - "ExecutionData Networkstate doesn't equal public input hash" - ); - return new TransactionProverState({ - networkState, - transactionList: new TransactionHashList(publicInput.transactionsHash), + // Stuff that has to be authenticated via public input, since it's not inside the bundle hash + bundleList: new BundleHashList( + publicInput.bundlesHash, + args.bundleListPreimage + ), eternalTransactionsList: new TransactionHashList( publicInput.eternalTransactionsHash ), incomingMessages: new MinaActionsHashList( publicInput.incomingMessagesHash ), - pendingSTBatches: new AppliedBatchHashList( - publicInput.pendingSTBatchesHash - ), - witnessedRoots: new WitnessedRootHashList(publicInput.witnessedRootsHash), + // Remainders (i.e. stuff that goes into the bundle) + transactionList: new TransactionHashList(args.transactionHash), + pendingSTBatches: new AppliedBatchHashList(args.pendingSTBatchesHash), + witnessedRoots: new WitnessedRootHashList(args.witnessedRootsHash), }); } } +// These are all linear trackers, i.e. continuously progressing without +// interruptions from the block prover export const TransactionProverStateCommitments = { - transactionsHash: Field, - // Commitment to the list of unprocessed (pending) batches of STs that need to be proven - pendingSTBatchesHash: Field, - witnessedRootsHash: Field, - networkStateHash: Field, + bundlesHash: Field, eternalTransactionsHash: Field, incomingMessagesHash: Field, }; +export class TransactionProverArguments extends Struct({ + // Commitment to the list of unprocessed (pending) batches of STs that need to be proven + pendingSTBatchesHash: Field, + witnessedRootsHash: Field, + transactionHash: Field, + bundleListPreimage: BundlePreimage, + networkState: NetworkState, +}) {} + export class TransactionProverPublicInput extends Struct( TransactionProverStateCommitments ) { @@ -135,15 +141,9 @@ export class DynamicRuntimeProof extends DynamicProof< static maxProofsVerified = 0 as const; } -export class BlockProverSingleTransactionExecutionData extends Struct({ +export class TransactionProverExecutionData extends Struct({ transaction: TransactionProverTransactionArguments, - networkState: NetworkState, -}) {} - -export class BlockProverMultiTransactionExecutionData extends Struct({ - transaction1: TransactionProverTransactionArguments, - transaction2: TransactionProverTransactionArguments, - networkState: NetworkState, + args: TransactionProverArguments, }) {} export type TransactionProof = Proof< @@ -160,14 +160,15 @@ export interface TransactionProvable proveTransaction: ( publicInput: TransactionProverPublicInput, runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData + executionData: TransactionProverExecutionData ) => Promise; proveTransactions: ( publicInput: TransactionProverPublicInput, runtimeProof1: DynamicRuntimeProof, runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData + executionData1: TransactionProverExecutionData, + executionData2: TransactionProverExecutionData ) => Promise; merge: ( diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts index 6c686ba17..b74a3660f 100644 --- a/packages/protocol/src/prover/transaction/TransactionProver.ts +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -32,11 +32,11 @@ import { } from "../block/accummulators/RuntimeVerificationKeyTree"; import { - BlockProverMultiTransactionExecutionData, - BlockProverSingleTransactionExecutionData, + TransactionProverExecutionData, DynamicRuntimeProof, TransactionProof, TransactionProvable, + TransactionProverArguments, TransactionProverPublicInput, TransactionProverPublicOutput, TransactionProverState, @@ -53,8 +53,8 @@ const errors = { transactionsHashNotMatching: (step: string) => errors.propertyNotMatchingStep("Transactions hash", step), - networkStateHashNotMatching: (step: string) => - errors.propertyNotMatchingStep("Network state hash", step), + bundlesHashNotMatching: (step: string) => + errors.propertyNotMatchingStep("Bundles hash", step), }; type ApplyTransactionArguments = Omit< @@ -62,6 +62,10 @@ type ApplyTransactionArguments = Omit< "verificationKeyAttestation" >; +type TransactionHookArgument = T extends "before" + ? BeforeTransactionHookArguments + : AfterTransactionHookArguments; + export class TransactionProverZkProgrammable extends ZkProgrammable< TransactionProverPublicInput, TransactionProverPublicOutput @@ -96,6 +100,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< * @param runtimeOutput * @param executionData * @param networkState + * @param bundleListPreimage * @returns The new BlockProver-state to be used as public output */ public async applyTransaction( @@ -118,13 +123,12 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< // Apply beforeTransaction hook state transitions const beforeBatch = await this.executeTransactionHooks( + "before", async (module, args) => await module.beforeTransaction(args), beforeTxHookArguments, isMessage ); - state = addTransactionToBundle(state, runtimeOutput.isMessage, transaction); - state.pendingSTBatches.push(beforeBatch); state.pendingSTBatches.push({ @@ -132,6 +136,8 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< applied: runtimeOutput.status, }); + state = addTransactionToBundle(state, runtimeOutput.isMessage, transaction); + // Apply afterTransaction hook state transitions const afterTxHookArguments = toAfterTransactionHookArgument( executionData, @@ -144,6 +150,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< this.stateServiceProvider.popCurrentStateService(); const afterBatch = await this.executeTransactionHooks( + "after", async (module, args) => await module.afterTransaction(args), afterTxHookArguments, isMessage @@ -170,14 +177,6 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< // Validate layout of transaction witness transaction.assertTransactionType(isMessage); - // Check network state integrity against appProof - state.networkState - .hash() - .assertEquals( - runtimeOutput.networkStateHash, - "Network state does not match state used in AppProof" - ); - return new TransactionProverState(state); } @@ -201,15 +200,18 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< return verificationKey; } - private async executeTransactionHooks< - T extends BeforeTransactionHookArguments | AfterTransactionHookArguments, - >( - hook: (module: ProvableTransactionHook, args: T) => Promise, - hookArguments: T, + private async executeTransactionHooks( + type: T, + hook: ( + module: ProvableTransactionHook, + args: TransactionHookArgument + ) => Promise, + hookArguments: TransactionHookArgument, isMessage: Bool ) { const { batch, rawStatus } = await executeHooks( hookArguments, + `${type}Transaction`, async () => { for (const module of this.transactionHooks) { // eslint-disable-next-line no-await-in-loop @@ -227,10 +229,15 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< } public async proveTransactionInternal( - fromState: TransactionProverState, + publicInput: TransactionProverPublicInput, runtimeProof: DynamicRuntimeProof, - { transaction, networkState }: BlockProverSingleTransactionExecutionData - ): Promise { + transaction: TransactionProverTransactionArguments, + args: TransactionProverArguments + ): Promise { + const state = TransactionProverState.fromCommitments(publicInput, args); + + state.bundleList.checkLastBundleElement(state, args.networkState); + const verificationKey = this.verifyVerificationKeyAttestation( transaction.verificationKeyAttestation, transaction.transaction.methodId @@ -238,32 +245,30 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< runtimeProof.verify(verificationKey); - return await this.applyTransaction( - fromState, + const result = await this.applyTransaction( + state, runtimeProof.publicOutput, transaction, - networkState + args.networkState ); + + result.bundleList.addToBundle(result, args.networkState); + + return result.toCommitments(); } @provableMethod() public async proveTransaction( publicInput: TransactionProverPublicInput, runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData + executionData: TransactionProverExecutionData ): Promise { - const state = TransactionProverState.fromCommitments( + return await this.proveTransactionInternal( publicInput, - executionData.networkState - ); - - const stateTo = await this.proveTransactionInternal( - state, runtimeProof, - executionData + executionData.transaction, + executionData.args ); - - return new TransactionProverPublicOutput(stateTo.toCommitments()); } @provableMethod() @@ -271,30 +276,25 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< publicInput: TransactionProverPublicInput, runtimeProof1: DynamicRuntimeProof, runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData + executionData1: TransactionProverExecutionData, + executionData2: TransactionProverExecutionData ): Promise { - const state = TransactionProverState.fromCommitments( + const state1 = await this.proveTransactionInternal( publicInput, - executionData.networkState + runtimeProof1, + executionData1.transaction, + executionData1.args ); - // this.staticChecks(publicInput); - - const state1 = await this.proveTransactionInternal(state, runtimeProof1, { - transaction: executionData.transaction1, - networkState: executionData.networkState, - }); - // Switch to next state record for 2nd tx beforeTx hook - // TODO Can be prevented by merging 1st afterTx + 2nd beforeTx this.stateServiceProvider.popCurrentStateService(); - const stateTo = await this.proveTransactionInternal(state1, runtimeProof2, { - transaction: executionData.transaction2, - networkState: executionData.networkState, - }); - - return new TransactionProverPublicOutput(stateTo.toCommitments()); + return await this.proveTransactionInternal( + state1, + runtimeProof2, + executionData2.transaction, + executionData2.args + ); } @provableMethod() @@ -306,27 +306,14 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< proof1.verify(); proof2.verify(); - // Check transaction list hash. - // Only assert them if these are tx proofs, skip for closed proofs - publicInput.transactionsHash - .equals(proof1.publicInput.transactionsHash) - .assertTrue( - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.transactionsHash - .equals(proof2.publicInput.transactionsHash) - .assertTrue( - errors.transactionsHashNotMatching("proof1.to -> proof2.from") - ); - - // Check networkhash - publicInput.networkStateHash.assertEquals( - proof1.publicInput.networkStateHash, - errors.networkStateHashNotMatching("publicInput.from -> proof1.from") + // Check bundlesHash + publicInput.bundlesHash.assertEquals( + proof1.publicInput.bundlesHash, + errors.bundlesHashNotMatching("publicInput.from -> proof1.from") ); - proof1.publicOutput.networkStateHash.assertEquals( - proof2.publicInput.networkStateHash, - errors.networkStateHashNotMatching("proof1.to -> proof2.from") + proof1.publicOutput.bundlesHash.assertEquals( + proof2.publicInput.bundlesHash, + errors.bundlesHashNotMatching("proof1.to -> proof2.from") ); // Check eternalTransactionsHash @@ -356,32 +343,31 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< ); // Check pendingSTBatchesHash - publicInput.pendingSTBatchesHash.assertEquals( - proof1.publicInput.pendingSTBatchesHash, - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.pendingSTBatchesHash.assertEquals( - proof2.publicInput.pendingSTBatchesHash, - errors.transactionsHashNotMatching("proof1.to -> proof2.from") - ); - - // Check witnessedRootsHash - publicInput.witnessedRootsHash.assertEquals( - proof1.publicInput.witnessedRootsHash, - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - ); - proof1.publicOutput.witnessedRootsHash.assertEquals( - proof2.publicInput.witnessedRootsHash, - errors.transactionsHashNotMatching("proof1.to -> proof2.from") - ); + // publicInput.pendingSTBatchesHash.assertEquals( + // proof1.publicInput.pendingSTBatchesHash, + // errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + // ); + // proof1.publicOutput.pendingSTBatchesHash.assertEquals( + // proof2.publicInput.pendingSTBatchesHash, + // errors.transactionsHashNotMatching("proof1.to -> proof2.from") + // ); + // + // // Check witnessedRootsHash + // publicInput.witnessedRootsHash.assertEquals( + // proof1.publicInput.witnessedRootsHash, + // errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + // ); + // proof1.publicOutput.witnessedRootsHash.assertEquals( + // proof2.publicInput.witnessedRootsHash, + // errors.transactionsHashNotMatching("proof1.to -> proof2.from") + // ); return new TransactionProverPublicOutput({ - transactionsHash: proof2.publicOutput.transactionsHash, - networkStateHash: proof2.publicOutput.networkStateHash, + bundlesHash: proof2.publicOutput.bundlesHash, eternalTransactionsHash: proof2.publicOutput.eternalTransactionsHash, incomingMessagesHash: proof2.publicOutput.incomingMessagesHash, - pendingSTBatchesHash: proof2.publicOutput.pendingSTBatchesHash, - witnessedRootsHash: proof2.publicOutput.witnessedRootsHash, + // pendingSTBatchesHash: proof2.publicOutput.pendingSTBatchesHash, + // witnessedRootsHash: proof2.publicOutput.witnessedRootsHash, }); } @@ -406,15 +392,12 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< methods: { proveTransaction: { - privateInputs: [ - DynamicRuntimeProof, - BlockProverSingleTransactionExecutionData, - ], + privateInputs: [DynamicRuntimeProof, TransactionProverExecutionData], async method( publicInput: TransactionProverPublicInput, runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData + executionData: TransactionProverExecutionData ) { return { publicOutput: await proveTransaction( @@ -430,21 +413,24 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< privateInputs: [ DynamicRuntimeProof, DynamicRuntimeProof, - BlockProverMultiTransactionExecutionData, + TransactionProverExecutionData, + TransactionProverExecutionData, ], async method( publicInput: TransactionProverPublicInput, runtimeProof1: DynamicRuntimeProof, runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData + executionData1: TransactionProverExecutionData, + executionData2: TransactionProverExecutionData ) { return { publicOutput: await proveTransactions( publicInput, runtimeProof1, runtimeProof2, - executionData + executionData1, + executionData2 ), }; }, @@ -537,7 +523,7 @@ export class TransactionProver public proveTransaction( publicInput: TransactionProverPublicInput, runtimeProof: DynamicRuntimeProof, - executionData: BlockProverSingleTransactionExecutionData + executionData: TransactionProverExecutionData ): Promise { return this.zkProgrammable.proveTransaction( publicInput, @@ -550,13 +536,15 @@ export class TransactionProver publicInput: TransactionProverPublicInput, runtimeProof1: DynamicRuntimeProof, runtimeProof2: DynamicRuntimeProof, - executionData: BlockProverMultiTransactionExecutionData + executionData1: TransactionProverExecutionData, + executionData2: TransactionProverExecutionData ): Promise { return this.zkProgrammable.proveTransactions( publicInput, runtimeProof1, runtimeProof2, - executionData + executionData1, + executionData2 ); } diff --git a/packages/protocol/src/prover/utils.ts b/packages/protocol/src/prover/utils.ts index 5b67de0f1..611561baf 100644 --- a/packages/protocol/src/prover/utils.ts +++ b/packages/protocol/src/prover/utils.ts @@ -43,8 +43,10 @@ export function constructBatch( // TODO How does this interact with the RuntimeMethodExecutionContext when executing runtimemethods? export async function executeHooks( contextArguments: RuntimeMethodExecutionData, + hookName: string, method: () => Promise, - isMessage: Bool | undefined = undefined + // This can be either that the tx is a message, or we are inside a dummy block hook + skipEnforceStatus: Bool | undefined = undefined ) { const executionContext = container.resolve(RuntimeMethodExecutionContext); executionContext.clear(); @@ -63,16 +65,16 @@ export async function executeHooks( executionContext.current().result; // See https://github.com/proto-kit/framework/issues/321 for why we do this here - if (isMessage !== undefined) { + if (skipEnforceStatus !== undefined) { // isMessage is defined for all tx hooks status - .or(isMessage) + .or(skipEnforceStatus) .assertTrue( - `Transaction hook call failed for non-message tx: ${statusMessage ?? "-"}` + `${hookName} hook call failed for non-message tx: ${statusMessage ?? "-"}` ); } else { // isMessage is undefined for all block hooks - status.assertTrue(`Block hook call failed: ${statusMessage ?? "-"}`); + status.assertTrue(`${hookName} hook call failed: ${statusMessage ?? "-"}`); } return { diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts index cc6401c03..e37e35a60 100644 --- a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts @@ -1,5 +1,4 @@ import { - Bool, DeployArgs, DynamicProof, Field, @@ -199,14 +198,19 @@ export abstract class SettlementBase "OutputNetworkState witness not valid" ); - blockProof.publicOutput.closed.assertEquals( - Bool(true), - "Supplied proof is not a closed BlockProof" + // Check remainders are zero + blockProof.publicOutput.remainders.bundlesHash.assertEquals( + Field(0), + "Bundles list has not been fully proven" ); - blockProof.publicOutput.pendingSTBatchesHash.assertEquals( + blockProof.publicOutput.remainders.pendingSTBatchesHash.assertEquals( Field(0), "Supplied proof is has outstanding STs to be proven" ); + blockProof.publicOutput.remainders.witnessedRootsHash.assertEquals( + Field(0), + "Supplied proof is has outstanding witnessed roots hashes to be proven" + ); // Execute onSettlementHooks for additional checks const stateRecord: SettlementStateRecord = { diff --git a/packages/protocol/src/utils/MinaPrefixedProvableHashList.ts b/packages/protocol/src/utils/MinaPrefixedProvableHashList.ts index eaa2ff8f8..bfa6cb98e 100644 --- a/packages/protocol/src/utils/MinaPrefixedProvableHashList.ts +++ b/packages/protocol/src/utils/MinaPrefixedProvableHashList.ts @@ -47,11 +47,15 @@ export class MinaPrefixedProvableHashList< public constructor( valueType: ProvablePure, public readonly prefix: string, - internalCommitment: Field = Field(0) + internalCommitment?: Field ) { super(valueType, internalCommitment); } + public empty(): Field { + return Field(0); + } + protected hash(elements: Field[]): Field { const init = salt(this.prefix); const digest = Poseidon.update(init, elements); @@ -60,7 +64,7 @@ export class MinaPrefixedProvableHashList< } export class MinaActionsHashList extends MinaPrefixedProvableHashList { - public constructor(internalCommitment: Field = Field(0)) { + public constructor(internalCommitment?: Field) { super(Field, MINA_PREFIXES.sequenceEvents, internalCommitment); } } diff --git a/packages/protocol/src/utils/PrefixedProvableHashList.ts b/packages/protocol/src/utils/PrefixedProvableHashList.ts index a31b78eea..4e9d4a755 100644 --- a/packages/protocol/src/utils/PrefixedProvableHashList.ts +++ b/packages/protocol/src/utils/PrefixedProvableHashList.ts @@ -15,6 +15,10 @@ export class PrefixedProvableHashList extends ProvableHashList { this.prefix = stringToField(prefix); } + public empty(): Field { + return Field(0); + } + protected hash(elements: Field[]): Field { return Poseidon.hash([this.prefix, ...elements]); } diff --git a/packages/protocol/src/utils/ProvableHashList.ts b/packages/protocol/src/utils/ProvableHashList.ts index 9b824d60a..1061b167b 100644 --- a/packages/protocol/src/utils/ProvableHashList.ts +++ b/packages/protocol/src/utils/ProvableHashList.ts @@ -23,13 +23,19 @@ export type VerifiedTransition = { * Utilities for creating a hash list from a given value type. */ export abstract class ProvableHashList { + public commitment: Field; + public constructor( protected readonly valueType: ProvablePure, - public commitment: Field = Field(0), + commitment?: Field | undefined, private unconstrainedList: Unconstrained< ProvableHashListData[] > = Unconstrained.from([]) - ) {} + ) { + this.commitment = commitment ?? this.empty(); + } + + protected abstract empty(): Field; protected abstract hash(elements: Field[]): Field; @@ -61,11 +67,24 @@ export abstract class ProvableHashList { this.commitment = to; } + public fastForwardIf( + transition: VerifiedTransition, + condition: Bool, + message: string = "some hashlist" + ) { + const { from, to } = transition; + + condition + .implies(from.equals(this.commitment)) + .assertTrue(`From-commitment for ${message} not matching`); + + this.commitment = Provable.if(condition, to, this.commitment); + } + public witnessTip(preimage: Field, value: Value): Bool { - return this.hash([ - this.commitment, - ...this.valueType.toFields(value), - ]).equals(this.commitment); + return this.hash([preimage, ...this.valueType.toFields(value)]).equals( + this.commitment + ); } /** @@ -111,6 +130,10 @@ export abstract class ProvableHashList { return this.commitment; } + public isEmpty(): Bool { + return this.commitment.equals(this.empty()); + } + public getUnconstrainedValues(): Unconstrained< ProvableHashListData[] > { @@ -122,4 +145,8 @@ export class DefaultProvableHashList extends ProvableHashList { public hash(elements: Field[]): Field { return Poseidon.hash(elements); } + + public empty(): Field { + return Field(0); + } } From 6bf6511e055041b40e3cde09711ea0bb4b7f61e5 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 9 Jan 2026 00:21:49 +0100 Subject: [PATCH 084/155] Adapted tracing to new block prover architecture --- .../sequencing/BlockProductionService.ts | 6 +- .../sequencing/TransactionExecutionService.ts | 11 +- .../production/tracing/BatchTracingService.ts | 119 ++++++++--- .../production/tracing/BlockTracingService.ts | 195 ++++++++++++------ .../tracing/TransactionTracingService.ts | 135 +++++------- 5 files changed, 277 insertions(+), 189 deletions(-) diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts index 18010fa28..60fc710b5 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts @@ -10,7 +10,7 @@ import { reduceStateTransitions, RuntimeTransaction, StateServiceProvider, - toProvableHookBlockState, + toBeforeBlockHookArgument, TransactionHashList, } from "@proto-kit/protocol"; import { Field } from "o1js"; @@ -128,7 +128,7 @@ export class BlockProductionService { // Get used networkState by executing beforeBlock() hooks const beforeHookResult = await this.executeBeforeBlockHook( - toProvableHookBlockState(blockState), + toBeforeBlockHookArgument(blockState), lastResult.afterNetworkState, stateService ); @@ -171,7 +171,7 @@ export class BlockProductionService { height: lastBlock.hash.toBigInt() !== 0n ? lastBlock.height.add(1) : Field(0), fromBlockHashRoot: Field(lastResult.blockHashRoot), - fromMessagesHash: lastBlock.toMessagesHash, + fromMessagesHash: lastBlock.fromMessagesHash, fromStateRoot: Field(lastResult.stateRoot), toMessagesHash: newBlockState.incomingMessages.commitment, previousBlockHash, diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index 24dc9852a..e3dc6ecdb 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -22,6 +22,7 @@ import { ProvableStateTransition, DefaultProvableHashList, addTransactionToBundle, + TransactionProverState, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { AreProofsEnabled, log, mapSequential } from "@proto-kit/common"; @@ -58,12 +59,10 @@ export type RuntimeContextReducedExecutionResult = Pick< >; export type BlockTrackers = Pick< - BlockProverState, - | "transactionList" - | "eternalTransactionsList" - | "incomingMessages" - | "blockHashRoot" ->; + TransactionProverState, + "eternalTransactionsList" | "incomingMessages" | "transactionList" +> & + Pick; function getAreProofsEnabledFromModule( module: RuntimeModule diff --git a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts index f54d52e6f..f09e20710 100644 --- a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts @@ -1,29 +1,43 @@ -import { log, yieldSequential } from "@proto-kit/common"; +import { log, range, unzip, yieldSequential } from "@proto-kit/common"; import { AppliedBatchHashList, + BLOCK_ARGUMENT_BATCH_SIZE, MinaActionsHashList, TransactionHashList, WitnessedRootHashList, + BundleHashList, + BlockArguments, } from "@proto-kit/protocol"; import { inject, injectable } from "tsyringe"; +// eslint-disable-next-line import/no-extraneous-dependencies +import chunk from "lodash/chunk"; +import { Bool, Field } from "o1js"; import { StateTransitionProofParameters } from "../tasks/StateTransitionTask"; import { BlockWithResult } from "../../../storage/model/Block"; import { trace } from "../../../logging/trace"; import { Tracer } from "../../../logging/Tracer"; import { CachedLinkedLeafStore } from "../../../state/lmt/CachedLinkedLeafStore"; - import { - BlockTrace, - BlockTracingService, - BlockTracingState, -} from "./BlockTracingService"; + NewBlockArguments, + NewBlockProverParameters, +} from "../tasks/NewBlockTask"; + +import { BlockTracingService, BlockTracingState } from "./BlockTracingService"; import { StateTransitionTracingService } from "./StateTransitionTracingService"; +import { TransactionTrace } from "./TransactionTracingService"; + +type BatchTracingState = BlockTracingState; -type BatchTracingState = Omit; +export type BlockTrace = { + block: NewBlockProverParameters; + // Only for debugging and logging + heights: [string, string]; +}; export type BatchTrace = { blocks: BlockTrace[]; + transactions: TransactionTrace[]; stateTransitionTrace: StateTransitionProofParameters[]; }; @@ -40,12 +54,15 @@ export class BatchTracingService { return { pendingSTBatches: new AppliedBatchHashList(), witnessedRoots: new WitnessedRootHashList(), + bundleList: new BundleHashList(), stateRoot: block.block.fromStateRoot, eternalTransactionsList: new TransactionHashList( block.block.fromEternalTransactionsHash ), incomingMessages: new MinaActionsHashList(block.block.fromMessagesHash), networkState: block.block.networkState.before, + blockNumber: block.block.height, + blockHashRoot: block.block.fromBlockHashRoot, }; } @@ -57,25 +74,71 @@ export class BatchTracingService { // Trace blocks const numBlocks = blocks.length; + const numBatches = Math.ceil(numBlocks / BLOCK_ARGUMENT_BATCH_SIZE); + const [, blockTraces] = await yieldSequential( - blocks, - async (state, block, index) => { - const blockProverState: BlockTracingState = { - ...state, - transactionList: new TransactionHashList(), + chunk(blocks, BLOCK_ARGUMENT_BATCH_SIZE), + async (state, batch, index) => { + // Trace batch of blocks fitting in single proof + const batchTrace = this.blockTracingService.openBlock(state, batch[0]); + const start = state.blockNumber.toString(); + + const [newState, combinedTraces] = await yieldSequential( + batch, + async (state2, block, jndex) => { + const [newState2, blockTrace, transactions] = + await this.blockTracingService.traceBlock(state2, block); + return [ + newState2, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + [blockTrace, transactions] as [ + NewBlockArguments, + TransactionTrace[], + ], + ]; + }, + state + ); + + // Fill up with dummies + const dummyBlockArgs = BlockArguments.noop( + newState, + Field(blocks.at(-1)!.result.stateRoot) + ); + const dummies = range( + blocks.length, + BLOCK_ARGUMENT_BATCH_SIZE + ).map(() => ({ + args: dummyBlockArgs, + startingStateAfterHook: {}, + startingStateBeforeHook: {}, + })); + + const [blockArgumentBatch, transactionTraces] = unzip(combinedTraces); + + const blockTrace: BlockTrace = { + block: { + ...batchTrace, + blocks: blockArgumentBatch.concat(dummies), + deferTransactionProof: Bool(numBatches - 1 < index), + deferSTProof: Bool(numBatches - 1 < index), + }, + heights: [start, newState.blockNumber.toString()], }; - const [newState, blockTrace] = - await this.blockTracingService.traceBlock( - blockProverState, - block, - index === numBlocks - 1 - ); - return [newState, blockTrace]; + + return [ + newState, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + [blockTrace, transactionTraces] as [BlockTrace, TransactionTrace[][]], + ]; }, batchState ); - return blockTraces; + return { + blockTraces: blockTraces.map(([x]) => x), + transactionTraces: blockTraces.map(([, x]) => x), + }; } @trace("batch.trace.transitions") @@ -102,20 +165,22 @@ export class BatchTracingService { batchId: number ): Promise { if (blocks.length === 0) { - return { blocks: [], stateTransitionTrace: [] }; + return { blocks: [], stateTransitionTrace: [], transactions: [] }; } // Traces the STs and the blocks in parallel, however not in separate processes // Therefore, we only optimize the idle time for async operations like DB reads - const [blockTraces, stateTransitionTrace] = await Promise.all([ - // Trace blocks - this.traceBlocks(blocks), - // Trace STs - this.traceStateTransitions(blocks, merkleTreeStore), - ]); + const [{ blockTraces, transactionTraces }, stateTransitionTrace] = + await Promise.all([ + // Trace blocks + this.traceBlocks(blocks), + // Trace STs + this.traceStateTransitions(blocks, merkleTreeStore), + ]); return { blocks: blockTraces, + transactions: transactionTraces.flat(2), stateTransitionTrace, }; } diff --git a/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts index 8b6a98bbf..e0e8867c8 100644 --- a/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts @@ -1,17 +1,24 @@ import { + BlockArguments, BlockProverPublicInput, BlockProverState, + Bundle, + TransactionHashList, + TransactionProverState, WitnessedRootWitness, + BundleHashList, + BundlePreimage, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { toStateTransitionsHash } from "@proto-kit/module"; -import { yieldSequential } from "@proto-kit/common"; -// eslint-disable-next-line import/no-extraneous-dependencies -import chunk from "lodash/chunk"; +import { NonMethods, yieldSequential } from "@proto-kit/common"; import { inject, injectable } from "tsyringe"; import { BlockWithResult } from "../../../storage/model/Block"; -import type { NewBlockProverParameters } from "../tasks/NewBlockTask"; +import type { + NewBlockArguments, + NewBlockProverParameters, +} from "../tasks/NewBlockTask"; import { Tracer } from "../../../logging/Tracer"; import { trace } from "../../../logging/trace"; @@ -23,24 +30,10 @@ import { export type TaskStateRecord = Record; -export type BlockTracingState = Pick< - BlockProverState, - | "witnessedRoots" - | "stateRoot" - | "pendingSTBatches" - | "networkState" - | "transactionList" - | "eternalTransactionsList" - | "incomingMessages" +export type BlockTracingState = NonMethods< + Omit >; -export type BlockTrace = { - blockParams: NewBlockProverParameters; - transactions: TransactionTrace[]; - // Only for debugging and logging - height: string; -}; - @injectable() export class BlockTracingService { public constructor( @@ -49,38 +42,45 @@ export class BlockTracingService { public readonly tracer: Tracer ) {} - @trace("batch.trace.block", ([, block]) => ({ - height: block.block.height.toString(), - })) - public async traceBlock( + public openBlock( state: BlockTracingState, - block: BlockWithResult, - includeSTProof: boolean - ): Promise<[BlockTracingState, BlockTrace]> { + { block: firstBlock, result: firstResult }: BlockWithResult + ): Pick< + NewBlockProverParameters, + "publicInput" | "networkState" | "blockWitness" + > { const publicInput: BlockProverPublicInput = new BlockProverPublicInput({ stateRoot: state.stateRoot, - blockNumber: block.block.height, - blockHashRoot: block.block.fromBlockHashRoot, - eternalTransactionsHash: block.block.fromEternalTransactionsHash, - incomingMessagesHash: block.block.fromMessagesHash, - transactionsHash: Field(0), - networkStateHash: block.block.networkState.before.hash(), - witnessedRootsHash: state.witnessedRoots.commitment, - pendingSTBatchesHash: state.pendingSTBatches.commitment, + blockNumber: firstBlock.height, + blockHashRoot: firstBlock.fromBlockHashRoot, + eternalTransactionsHash: firstBlock.fromEternalTransactionsHash, + incomingMessagesHash: firstBlock.fromMessagesHash, + networkStateHash: firstBlock.networkState.before.hash(), + remainders: { + witnessedRootsHash: state.witnessedRoots.commitment, + pendingSTBatchesHash: state.pendingSTBatches.commitment, + bundlesHash: state.bundleList.commitment, + }, }); + return { + publicInput, + networkState: firstBlock.networkState.before, + blockWitness: firstResult.blockHashWitness, + }; + } + + @trace("batch.trace.block", ([, block]) => ({ + height: block.block.height.toString(), + })) + public async traceBlock( + state: BlockTracingState, + block: BlockWithResult + ): Promise<[BlockTracingState, NewBlockArguments, TransactionTrace[]]> { const startingStateBeforeHook = collectStartingState( block.block.beforeBlockStateTransitions ); - const blockTrace = { - publicInput, - networkState: block.block.networkState.before, - deferSTProof: Bool(!includeSTProof), - blockWitness: block.result.blockHashWitness, - startingStateBeforeHook, - } satisfies Partial; - state.pendingSTBatches.push({ batchHash: toStateTransitionsHash( block.block.beforeBlockStateTransitions @@ -89,26 +89,71 @@ export class BlockTracingService { }); state.networkState = block.block.networkState.during; + const blockArgsPartial = { + fromPendingSTBatchesHash: state.pendingSTBatches.commitment, + fromWitnessedRootsHash: state.witnessedRoots.commitment, + }; + + const transactionProverState = new TransactionProverState({ + transactionList: new TransactionHashList(), + witnessedRoots: state.witnessedRoots, + pendingSTBatches: state.pendingSTBatches, + incomingMessages: state.incomingMessages, + eternalTransactionsList: state.eternalTransactionsList, + bundleList: new BundleHashList( + state.bundleList.commitment, + // The preimage here is just the current state (the start of the block) + // Internally, both provers will detect commitment == preimage and start + // a new bundle + new BundlePreimage({ + preimage: state.bundleList.commitment, + fromStateTransitionsHash: state.pendingSTBatches.commitment, + fromWitnessedRootsHash: state.witnessedRoots.commitment, + }) + ), + }); + const [afterState, transactionTraces] = await yieldSequential( - chunk(block.block.transactions, 2), - async (input, [transaction1, transaction2]) => { + block.block.transactions, + async (input, transaction) => { const [output, transactionTrace] = - transaction2 !== undefined - ? await this.transactionTracing.createMultiTransactionTrace( - input, - transaction1, - transaction2 - ) - : await this.transactionTracing.createSingleTransactionTrace( - input, - transaction1 - ); + await this.transactionTracing.createTransactionTrace( + input, + state.networkState, + transaction + ); return [output, transactionTrace]; }, - state + transactionProverState + ); + + // TODO Maybe replace this with replicating the in-circuit version inside createTransactionTrace + // Add to bundleList (before all the afterBlock stuff since bundles only care about + // all the stuff that happens in the TransactionProver) + // Also, this list is a different instance than the one used in transaction tracing + const finishedBundle = new Bundle({ + networkStateHash: state.networkState.hash(), + transactionsHash: block.block.transactionsHash, + pendingSTBatchesHash: { + from: blockArgsPartial.fromPendingSTBatchesHash, + to: afterState.pendingSTBatches.commitment, + }, + witnessedRootsHash: { + from: blockArgsPartial.fromWitnessedRootsHash, + to: afterState.witnessedRoots.commitment, + }, + }); + state.bundleList.pushIf( + finishedBundle, + afterState.transactionList.isEmpty().not() ); + state.pendingSTBatches = afterState.pendingSTBatches; + state.witnessedRoots = afterState.witnessedRoots; + state.incomingMessages = afterState.incomingMessages; + state.eternalTransactionsList = afterState.eternalTransactionsList; + const preimage = afterState.witnessedRoots .getUnconstrainedValues() .get() @@ -119,6 +164,23 @@ export class BlockTracingService { preimage: preimage ?? Field(0), }; + // We create the batch here, because we need the afterBlockRootWitness, + // but the afterBlock's witnessed root can't be in the arguments, because + // it is temporally **after** the bundle, not inside it + const args = new BlockArguments({ + transactionsHash: afterState.transactionList.commitment, + afterBlockRootWitness, + witnessedRootsHash: { + from: blockArgsPartial.fromWitnessedRootsHash, + to: state.witnessedRoots.commitment, + }, + pendingSTBatchesHash: { + from: blockArgsPartial.fromPendingSTBatchesHash, + to: state.pendingSTBatches.commitment, + }, + isDummy: Bool(false), + }); + if (afterState.pendingSTBatches.commitment.equals(0).not().toBoolean()) { state.witnessedRoots.witnessRoot( { @@ -133,19 +195,24 @@ export class BlockTracingService { const startingStateAfterHook = collectStartingState( block.result.afterBlockStateTransitions ); + state.pendingSTBatches.push({ + batchHash: toStateTransitionsHash( + block.result.afterBlockStateTransitions + ), + applied: Bool(true), + }); state.networkState = block.result.afterNetworkState; + state.blockNumber = state.blockNumber.add(1); + return [ - afterState, + state, { - blockParams: { - ...blockTrace, - startingStateAfterHook, - afterBlockRootWitness, - }, - transactions: transactionTraces, - height: block.block.height.toString(), + args, + startingStateBeforeHook, + startingStateAfterHook, }, + transactionTraces, ]; } } diff --git a/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts b/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts index ee591d282..89e391e50 100644 --- a/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/TransactionTracingService.ts @@ -1,39 +1,30 @@ import { addTransactionToBundle, - BlockProverMultiTransactionExecutionData, - BlockProverPublicInput, - BlockProverSingleTransactionExecutionData, NetworkState, + TransactionProverArguments, + TransactionProverPublicInput, + TransactionProverState, TransactionProverTransactionArguments, } from "@proto-kit/protocol"; -import { Bool, Field } from "o1js"; -import { MAX_FIELD } from "@proto-kit/common"; +import { Bool } from "o1js"; import { toStateTransitionsHash } from "@proto-kit/module"; import { injectable } from "tsyringe"; import { TransactionExecutionResult } from "../../../storage/model/Block"; import { PendingTransaction } from "../../../mempool/PendingTransaction"; import type { RuntimeProofParameters } from "../tasks/RuntimeProvingTask"; -import { - TransactionProverTaskParameters, - TransactionProvingType, -} from "../tasks/serializers/types/TransactionProvingTypes"; +import { TransactionProverTaskParameters } from "../tasks/serializers/types/TransactionProvingTypes"; import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; import { VerificationKeyService } from "../../runtime/RuntimeVerificationKeyService"; -import type { BlockTracingState, TaskStateRecord } from "./BlockTracingService"; - -export type TransactionTrace = - | { - type: TransactionProvingType.SINGLE; - transaction: TransactionProverTaskParameters; - runtime: [RuntimeProofParameters]; - } - | { - type: TransactionProvingType.MULTI; - transaction: TransactionProverTaskParameters; - runtime: [RuntimeProofParameters, RuntimeProofParameters]; - }; +import type { TaskStateRecord } from "./BlockTracingService"; + +export type TransactionTrace = { + transaction: TransactionProverTaskParameters; + runtime: RuntimeProofParameters; +}; + +export type TransactionTracingState = TransactionProverState; export function collectStartingState( stateTransitions: UntypedStateTransition[] @@ -76,23 +67,17 @@ export class TransactionTracingService { } private getTransactionProofPublicInput( - previousState: BlockTracingState - ): BlockProverPublicInput { + previousState: TransactionTracingState + ): TransactionProverPublicInput { return { - stateRoot: previousState.stateRoot, - transactionsHash: previousState.transactionList.commitment, + bundlesHash: previousState.bundleList.commitment, eternalTransactionsHash: previousState.eternalTransactionsList.commitment, incomingMessagesHash: previousState.incomingMessages.commitment, - networkStateHash: previousState.networkState.hash(), - witnessedRootsHash: previousState.witnessedRoots.commitment, - pendingSTBatchesHash: previousState.pendingSTBatches.commitment, - blockHashRoot: Field(0), - blockNumber: MAX_FIELD, }; } private appendTransactionToState( - previousState: BlockTracingState, + previousState: TransactionTracingState, transaction: TransactionExecutionResult ) { // TODO Remove this call and instead reuse results from sequencing @@ -128,7 +113,8 @@ export class TransactionTracingService { } private async traceTransaction( - previousState: BlockTracingState, + previousState: TransactionTracingState, + networkState: NetworkState, transaction: TransactionExecutionResult ) { const beforeHookStartingState = collectStartingState( @@ -137,90 +123,61 @@ export class TransactionTracingService { const runtimeTrace1 = this.createRuntimeProofParams( transaction, - previousState.networkState + networkState ); const afterHookStartingState = collectStartingState( transaction.stateTransitions[2].stateTransitions.flat() ); + const args: TransactionProverArguments = { + networkState: networkState, + transactionHash: previousState.transactionList.commitment, + pendingSTBatchesHash: previousState.pendingSTBatches.commitment, + witnessedRootsHash: previousState.witnessedRoots.commitment, + bundleListPreimage: previousState.bundleList.preimage!, + }; + const newState = this.appendTransactionToState(previousState, transaction); + newState.bundleList.addToBundle(newState, networkState); + return { state: newState, runtime: runtimeTrace1, startingState: [beforeHookStartingState, afterHookStartingState], + args, }; } - public async createSingleTransactionTrace( - previousState: BlockTracingState, + public async createTransactionTrace( + previousState: TransactionTracingState, + networkState: NetworkState, transaction: TransactionExecutionResult - ): Promise<[BlockTracingState, TransactionTrace]> { + ): Promise<[TransactionTracingState, TransactionTrace]> { const publicInput = this.getTransactionProofPublicInput(previousState); const { state: newState, startingState, runtime, - } = await this.traceTransaction(previousState, transaction); + args, + } = await this.traceTransaction(previousState, networkState, transaction); - const transactionTrace: TransactionProverTaskParameters = - { - executionData: { - transaction: await this.getTransactionData(transaction.tx), - networkState: previousState.networkState, - }, - startingState, - publicInput, - }; - - return [ - newState, - { - type: TransactionProvingType.SINGLE, - transaction: transactionTrace, - runtime: [runtime], + const transactionTrace: TransactionProverTaskParameters = { + executionData: { + transaction: await this.getTransactionData(transaction.tx), + args, }, - ]; - } - - public async createMultiTransactionTrace( - previousState: BlockTracingState, - transaction1: TransactionExecutionResult, - transaction2: TransactionExecutionResult - ): Promise<[BlockTracingState, TransactionTrace]> { - const publicInput = this.getTransactionProofPublicInput(previousState); - - const { - state: tmpState, - startingState: startingState1, - runtime: runtime1, - } = await this.traceTransaction(previousState, transaction1); - - const { - state: resultState, - startingState: startingState2, - runtime: runtime2, - } = await this.traceTransaction(tmpState, transaction2); - - const transactionTrace: TransactionProverTaskParameters = - { - executionData: { - transaction1: await this.getTransactionData(transaction1.tx), - transaction2: await this.getTransactionData(transaction2.tx), - networkState: previousState.networkState, - }, - startingState: [...startingState1, ...startingState2], - publicInput, - }; + startingState, + publicInput, + }; return [ - resultState, + newState, { - type: TransactionProvingType.MULTI, transaction: transactionTrace, - runtime: [runtime1, runtime2], + runtime, }, ]; } From 157135604cc57bcedc2e53ad13c922016d1beabd Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 9 Jan 2026 00:22:16 +0100 Subject: [PATCH 085/155] Adapted flows to new block prover architecture --- packages/common/src/utils.ts | 18 +- packages/common/test/trees/MerkleTree.test.ts | 2 +- packages/module/src/method/runtimeMethod.ts | 5 - .../test-integration/SequencerRestart.test.ts | 2 +- .../src/mempool/private/PrivateMempool.ts | 8 +- .../src/protocol/production/flow/BatchFlow.ts | 98 ++++----- .../src/protocol/production/flow/BlockFlow.ts | 82 ++++---- .../production/flow/TransactionFlow.ts | 65 +++--- .../protocol/production/tasks/NewBlockTask.ts | 53 +++-- .../tasks/TransactionProvingTask.ts | 24 ++- .../NewBlockProvingParametersSerializer.ts | 57 ++--- ...ansactionProvingTaskParameterSerializer.ts | 195 ++++++------------ .../types/TransactionProvingTypes.ts | 38 +--- .../src/worker/queue/LocalTaskQueue.ts | 8 +- .../test/integration/BlockProduction-test.ts | 2 +- 15 files changed, 293 insertions(+), 364 deletions(-) diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 0576aacb5..27a0a036c 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -87,7 +87,11 @@ export function yieldSequential( array, async ([state, collectedTargets], curr, index, arr) => { const [newState, addition] = await callbackfn(state, curr, index, arr); - return [newState, collectedTargets.concat(addition)]; + // The reason we wrap this in an array here is for a special case where Target is a tuple + // or array itself. In this case, js interprets by flattening the Value in the array + // (which it does when a function (like concat) uses a spread operator and the + // input is an array) + return [newState, collectedTargets.concat([addition])]; }, [initialValue, []] ); @@ -105,6 +109,18 @@ export function mapSequential( }, Promise.resolve([])); } +export function unzip(array: [A, B][]): [A[], B[]] { + const as = array.map(([a]) => a); + const bs = array.map(([, b]) => b); + return [as, bs]; +} + +export function assertSizeOneOrTwo(arr: T[]): asserts arr is [T] | [T, T] { + if (!(arr.length === 1 || arr.length === 2)) { + throw new Error("Given array not size 1 or 2"); + } +} + /** * Computes a dummy value for the given value type. * diff --git a/packages/common/test/trees/MerkleTree.test.ts b/packages/common/test/trees/MerkleTree.test.ts index 933e07504..b2c673eb8 100644 --- a/packages/common/test/trees/MerkleTree.test.ts +++ b/packages/common/test/trees/MerkleTree.test.ts @@ -1,5 +1,5 @@ import { beforeEach } from "@jest/globals"; -import { Field, Provable } from "o1js"; +import { Field } from "o1js"; import { createMerkleTree, diff --git a/packages/module/src/method/runtimeMethod.ts b/packages/module/src/method/runtimeMethod.ts index d53a9439c..ffa8145af 100644 --- a/packages/module/src/method/runtimeMethod.ts +++ b/packages/module/src/method/runtimeMethod.ts @@ -25,11 +25,6 @@ const errors = { runtimeNotProvided: (name: string) => new Error(`Runtime was not provided for module: ${name}`), - methodInputsNotProvided: () => - new Error( - "Method execution inputs not provided, provide them via context.inputs" - ), - runtimeNameNotSet: () => new Error("Runtime name was not set"), fieldNotConstant: (name: string) => diff --git a/packages/persistance/test-integration/SequencerRestart.test.ts b/packages/persistance/test-integration/SequencerRestart.test.ts index 940bc40ca..2864424b9 100644 --- a/packages/persistance/test-integration/SequencerRestart.test.ts +++ b/packages/persistance/test-integration/SequencerRestart.test.ts @@ -40,7 +40,7 @@ describe("sequencer restart", () => { }; const teardown = async () => { - await appChain.sequencer.resolve("Database").close(); + await appChain.close(); }; beforeAll(async () => { diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index e750060ea..ca82f42cc 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -7,11 +7,10 @@ import { import { container, inject } from "tsyringe"; import { AccountStateHook, - BlockHashMerkleTree, MandatoryProtocolModulesRecord, NetworkState, Protocol, - ProvableHookBlockState, + ProvableHookTransactionState, RuntimeMethodExecutionContext, RuntimeMethodExecutionData, StateServiceProvider, @@ -170,10 +169,7 @@ export class PrivateMempool // TODO This is not sound currently as the prover state changes all the time // in the actual blockprover. We need to properly simulate that - const proverState: ProvableHookBlockState = { - blockHashRoot: Field( - previousBlock?.result.blockHashRoot ?? BlockHashMerkleTree.EMPTY_ROOT - ), + const proverState: ProvableHookTransactionState = { eternalTransactionsHash: previousBlock?.block.toEternalTransactionsHash ?? Field(0), transactionsHash: previousBlock?.block.transactionsHash ?? Field(0), diff --git a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts index 63720c4fc..824970891 100644 --- a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts @@ -5,14 +5,9 @@ import { Protocol, StateTransitionProverPublicInput, StateTransitionProverPublicOutput, + TransactionProverPublicInput, } from "@proto-kit/protocol"; -import { - isFull, - mapSequential, - MAX_FIELD, - Nullable, - range, -} from "@proto-kit/common"; +import { isFull, mapSequential, Nullable } from "@proto-kit/common"; import { FlowCreator } from "../../../worker/flow/Flow"; import { NewBlockProvingParameters, NewBlockTask } from "../tasks/NewBlockTask"; @@ -33,7 +28,7 @@ export class BatchFlow { private readonly blockProvingTask: NewBlockTask, private readonly blockReductionTask: BlockReductionTask, private readonly stateTransitionFlow: StateTransitionFlow, - private readonly blockFlow: BlockFlow, + private readonly transactionFlow: BlockFlow, @inject("Protocol") private readonly protocol: Protocol, @inject("Tracer") @@ -42,7 +37,7 @@ export class BatchFlow { private isBlockProofsMergable(a: BlockProof, b: BlockProof): boolean { // TODO Proper replication of merge logic - const part1 = a.publicOutput.stateRoot + return a.publicOutput.stateRoot .equals(b.publicInput.stateRoot) .and(a.publicOutput.blockHashRoot.equals(b.publicInput.blockHashRoot)) .and( @@ -53,26 +48,7 @@ export class BatchFlow { b.publicInput.eternalTransactionsHash ) ) - .and(a.publicOutput.closed.equals(b.publicOutput.closed)) .toBoolean(); - - const proof1Closed = a.publicOutput.closed; - const proof2Closed = b.publicOutput.closed; - - const blockNumberProgressionValid = a.publicOutput.blockNumber.equals( - b.publicInput.blockNumber - ); - - const isValidTransactionMerge = a.publicInput.blockNumber - .equals(MAX_FIELD) - .and(blockNumberProgressionValid) - .and(proof1Closed.or(proof2Closed).not()); - - const isValidClosedMerge = proof1Closed - .and(proof2Closed) - .and(blockNumberProgressionValid); - - return part1 && isValidClosedMerge.or(isValidTransactionMerge).toBoolean(); } private async pushBlockInput( @@ -92,6 +68,14 @@ export class BatchFlow { ); } + private dummyTransactionProof() { + return this.protocol.transactionProver.zkProgrammable.zkProgram[0].Proof.dummy( + TransactionProverPublicInput.empty(), + TransactionProverPublicInput.empty(), + 2 + ); + } + @trace("batch.prove", ([, batchId]) => ({ batchId })) public async executeBatch(batch: BatchTrace, batchId: number) { const batchFlow = new ReductionTaskFlow( @@ -105,24 +89,14 @@ export class BatchFlow { this.flowCreator ); - const map: Record< - number, - Nullable - > = Object.fromEntries( - batch.blocks.map((blockTrace, i) => [ - i, - { - params: blockTrace.blockParams, - input1: undefined, - input2: undefined, - }, - ]) - ); + const lastBlockProofCollector: Nullable = { + params: batch.blocks.at(-1)!.block, + input1: undefined, + input2: undefined, + }; const dummySTProof = await this.dummySTProof(); - range(0, batch.blocks.length - 1).forEach((index) => { - map[index].input1 = dummySTProof; - }); + const dummyTransactionProof = await this.dummyTransactionProof(); // TODO Make sure we use deferErrorsTo to everywhere (preferably with a nice pattern) // Currently, a lot of errors just get eaten and the chain just halts with no @@ -131,18 +105,36 @@ export class BatchFlow { batch.stateTransitionTrace, batchId, async (proof) => { - const index = batch.blocks.length - 1; - map[index].input1 = proof; - await this.pushBlockInput(map[index], batchFlow); + lastBlockProofCollector.input1 = proof; + await this.pushBlockInput(lastBlockProofCollector, batchFlow); } ); - await mapSequential(batch.blocks, async (blockTrace, blockIndex) => { - await this.blockFlow.executeBlock(blockTrace, async (proof) => { - map[blockIndex].input2 = proof; - await this.pushBlockInput(map[blockIndex], batchFlow); - }); - }); + // TODO Proper height + await this.transactionFlow.createTransactionProof( + batch.blocks[0].heights[0], + batch.transactions, + async (proof) => { + lastBlockProofCollector.input2 = proof; + await this.pushBlockInput(lastBlockProofCollector, batchFlow); + } + ); + + // Push all blocks except the last one with dummy proofs + // except the last one, which will wait on the two proofs to complete + await mapSequential( + batch.blocks.slice(0, batch.blocks.length - 1), + async (blockTrace) => { + await this.pushBlockInput( + { + input1: dummySTProof, + input2: dummyTransactionProof, + params: blockTrace.block, + }, + batchFlow + ); + } + ); return await new Promise((res, rej) => { batchFlow.onCompletion(async (result) => res(result)); diff --git a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts index 542bd1ca3..527dd0833 100644 --- a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts @@ -7,16 +7,19 @@ import { TransactionProverPublicOutput, } from "@proto-kit/protocol"; import { Field } from "o1js"; +import { mapSequential } from "@proto-kit/common"; +// eslint-disable-next-line import/no-extraneous-dependencies +import chunk from "lodash/chunk"; import { TransactionProvingTask } from "../tasks/TransactionProvingTask"; -import { TransactionProvingTaskParameters } from "../tasks/serializers/types/TransactionProvingTypes"; import { FlowCreator } from "../../../worker/flow/Flow"; -import { BlockTrace } from "../tracing/BlockTracingService"; import { TransactionReductionTask } from "../tasks/TransactionReductionTask"; +import { TransactionTrace } from "../tracing/TransactionTracingService"; import { ReductionTaskFlow } from "./ReductionTaskFlow"; import { TransactionFlow } from "./TransactionFlow"; +// TODO Rename to TransactionFlow @injectable() @scoped(Lifecycle.ContainerScoped) export class BlockFlow { @@ -24,16 +27,16 @@ export class BlockFlow { private readonly flowCreator: FlowCreator, @inject("Protocol") private readonly protocol: Protocol, - private readonly transactionProvingTask: TransactionProvingTask, - private readonly transactionReductionTask: TransactionReductionTask, - private readonly transactionFlow: TransactionFlow + private readonly runtimeFlow: TransactionFlow, + private readonly transactionTask: TransactionProvingTask, + private readonly transactionMergeTask: TransactionReductionTask ) {} - private async dummyTransactionProof(trace: BlockTrace) { + private async dummyTransactionProof() { const publicInput = { - ...trace.blockParams.publicInput, - networkStateHash: Field(0), - transactionsHash: Field(0), + bundlesHash: Field(0), + eternalTransactionsHash: Field(0), + incomingMessagesHash: Field(0), } satisfies TransactionProverPublicInput; // TODO Set publicInput.stateRoot to result after block hooks! @@ -48,24 +51,19 @@ export class BlockFlow { ); } - private async executeTransactions( - trace: BlockTrace - ): Promise< - ReductionTaskFlow - > { - const transactionFlow = new ReductionTaskFlow( + private async proveTransactions(height: string, traces: TransactionTrace[]) { + const flow = new ReductionTaskFlow( { - name: `transactions-${trace.height}`, - inputLength: trace.transactions.length, - mappingTask: this.transactionProvingTask, - reductionTask: this.transactionReductionTask, - + name: `transaction-${height}`, + inputLength: Math.ceil(traces.length / 2), + mappingTask: this.transactionTask, + reductionTask: this.transactionMergeTask, mergableFunction: (a, b) => - a.publicOutput.transactionsHash - .equals(b.publicInput.transactionsHash) + a.publicOutput.eternalTransactionsHash + .equals(b.publicInput.eternalTransactionsHash) .and( - a.publicInput.networkStateHash.equals( - b.publicInput.networkStateHash + a.publicOutput.incomingMessagesHash.equals( + b.publicInput.incomingMessagesHash ) ) .toBoolean(), @@ -73,32 +71,30 @@ export class BlockFlow { this.flowCreator ); - await transactionFlow.flow.forEach( - trace.transactions, - async (transactionTrace, txIndex) => { - await this.transactionFlow.proveRuntimes( - transactionTrace, - trace.height, - txIndex, - async (parameters) => { - await transactionFlow.pushInput(parameters); - } - ); - } - ); + await mapSequential(chunk(traces, 2), async (traceChunk, index) => { + await this.runtimeFlow.proveRuntimes( + traceChunk, + height, + index, + async (result) => { + await flow.pushInput(result); + } + ); + }); - return transactionFlow; + return flow; } - public async executeBlock( - trace: BlockTrace, + public async createTransactionProof( + height: string, + trace: TransactionTrace[], callback: (proof: TransactionProof) => Promise ) { - if (trace.transactions.length === 0) { - const proof = await this.dummyTransactionProof(trace); + if (trace.length === 0) { + const proof = await this.dummyTransactionProof(); await callback(proof); } else { - const flow = await this.executeTransactions(trace); + const flow = await this.proveTransactions(height, trace); flow.onCompletion(async (result) => { await callback(result); }); diff --git a/packages/sequencer/src/protocol/production/flow/TransactionFlow.ts b/packages/sequencer/src/protocol/production/flow/TransactionFlow.ts index d124a352c..5d968758a 100644 --- a/packages/sequencer/src/protocol/production/flow/TransactionFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/TransactionFlow.ts @@ -1,10 +1,10 @@ import { injectable } from "tsyringe"; +import { assertSizeOneOrTwo } from "@proto-kit/common"; import { Flow, FlowCreator } from "../../../worker/flow/Flow"; import { RuntimeProof, TransactionProvingTaskParameters, - TransactionProvingType, } from "../tasks/serializers/types/TransactionProvingTypes"; import { RuntimeProvingTask } from "../tasks/RuntimeProvingTask"; import { TransactionTrace } from "../tracing/TransactionTracingService"; @@ -20,31 +20,31 @@ export class TransactionFlow { flow: Flow<{ runtimeProofs: { proof: RuntimeProof; index: number }[]; }>, - trace: TransactionTrace, + trace: [TransactionTrace] | [TransactionTrace, TransactionTrace], callback: (params: TransactionProvingTaskParameters) => Promise ) { - const requiredLength = trace.type === TransactionProvingType.MULTI ? 2 : 1; + const requiredLength = trace.length; if (flow.state.runtimeProofs.length === requiredLength) { let parameters: TransactionProvingTaskParameters; - if (trace.type === TransactionProvingType.MULTI) { + if (requiredLength === 2) { // Sort ascending const sorted = flow.state.runtimeProofs.sort( ({ index: a }, { index: b }) => a - b ); - parameters = { - type: trace.type, - parameters: trace.transaction, - proof1: sorted[0].proof, - proof2: sorted[1].proof, - }; + + parameters = [ + { parameters: trace[0].transaction, proof: sorted[0].proof }, + { parameters: trace[1].transaction, proof: sorted[1].proof }, + ]; } else { - parameters = { - type: trace.type, - parameters: trace.transaction, - proof1: flow.state.runtimeProofs[0].proof, - }; + parameters = [ + { + parameters: trace[0].transaction, + proof: flow.state.runtimeProofs[0].proof, + }, + ]; } await callback(parameters); @@ -52,13 +52,15 @@ export class TransactionFlow { } public async proveRuntimes( - trace: TransactionTrace, + trace: TransactionTrace[], blockHeight: string, txIndex: number, callback: (params: TransactionProvingTaskParameters) => Promise ) { + assertSizeOneOrTwo(trace); + const name = `transaction-${blockHeight}-${txIndex}${ - trace.type === TransactionProvingType.MULTI ? "-double" : "" + trace.length === 2 ? "-double" : "" }`; const flow = this.flowCreator.createFlow<{ runtimeProofs: { proof: RuntimeProof; index: number }[]; @@ -66,24 +68,17 @@ export class TransactionFlow { runtimeProofs: [], }); - await flow.pushTask( - this.runtimeProvingTask, - trace.runtime[0], - async (proof) => { - flow.state.runtimeProofs.push({ proof, index: 0 }); - await this.resolveTransactionFlow(flow, trace, callback); - } + await Promise.all( + trace.map(async (transaction, index) => { + await flow.pushTask( + this.runtimeProvingTask, + transaction.runtime, + async (proof) => { + flow.state.runtimeProofs.push({ proof, index }); + await this.resolveTransactionFlow(flow, trace, callback); + } + ); + }) ); - - if (trace.type === TransactionProvingType.MULTI) { - await flow.pushTask( - this.runtimeProvingTask, - trace.runtime[1], - async (proof) => { - flow.state.runtimeProofs.push({ proof, index: 1 }); - await this.resolveTransactionFlow(flow, trace, callback); - } - ); - } } } diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 338ce9514..de5c15015 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -8,10 +8,11 @@ import { StateTransitionProvable, BlockHashMerkleTreeWitness, MandatoryProtocolModulesRecord, - WitnessedRootWitness, TransactionProof, BlockProof, TransactionProvable, + BlockArguments, + BlockArgumentsBatch, } from "@proto-kit/protocol"; import { Bool } from "o1js"; import { @@ -28,14 +29,19 @@ import type { TaskStateRecord } from "../tracing/BlockTracingService"; import { NewBlockProvingParametersSerializer } from "./serializers/NewBlockProvingParametersSerializer"; import { executeWithPrefilledStateService } from "./TransactionProvingTask"; +export type NewBlockArguments = { + args: BlockArguments; + startingStateBeforeHook: TaskStateRecord; + startingStateAfterHook: TaskStateRecord; +}; + export interface NewBlockProverParameters { publicInput: BlockProverPublicInput; networkState: NetworkState; blockWitness: BlockHashMerkleTreeWitness; deferSTProof: Bool; - afterBlockRootWitness: WitnessedRootWitness; - startingStateBeforeHook: TaskStateRecord; - startingStateAfterHook: TaskStateRecord; + deferTransactionProof: Bool; + blocks: NewBlockArguments[]; } export type NewBlockProvingParameters = PairingDerivedInput< @@ -96,32 +102,41 @@ export class NewBlockTask const { networkState, blockWitness, - startingStateBeforeHook, - startingStateAfterHook, publicInput, deferSTProof, - afterBlockRootWitness, + deferTransactionProof, + blocks, } = parameters; - await this.blockProver.proveBlock( - publicInput, - networkState, - blockWitness, - input1, - deferSTProof, - afterBlockRootWitness, - input2 - ); + const blockArgumentBatch = new BlockArgumentsBatch({ + batch: blocks.map((block) => block.args), + }); + + const stateRecords = blocks.flatMap((block) => [ + block.startingStateBeforeHook, + block.startingStateAfterHook, + ]); await executeWithPrefilledStateService( this.protocol.stateServiceProvider, - [startingStateBeforeHook, startingStateAfterHook], - async () => {} + stateRecords, + async () => { + await this.blockProver.proveBlockBatch( + publicInput, + networkState, + blockWitness, + input1, + deferSTProof, + input2, + deferTransactionProof, + blockArgumentBatch + ); + } ); return await executeWithPrefilledStateService( this.protocol.stateServiceProvider, - [startingStateBeforeHook, startingStateAfterHook], + stateRecords, async () => await this.executionContext.current().result.prove() ); diff --git a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts index de4b80b63..88c77d4db 100644 --- a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts @@ -21,10 +21,7 @@ import { TaskWorkerModule } from "../../../worker/worker/TaskWorkerModule"; import type { TaskStateRecord } from "../tracing/BlockTracingService"; import { TransactionProvingTaskParameterSerializer } from "./serializers/TransactionProvingTaskParameterSerializer"; -import { - TransactionProvingTaskParameters, - TransactionProvingType, -} from "./serializers/types/TransactionProvingTypes"; +import { TransactionProvingTaskParameters } from "./serializers/types/TransactionProvingTypes"; export async function executeWithPrefilledStateService( stateServiceProvider: StateServiceProvider, @@ -93,26 +90,31 @@ export class TransactionProvingTask public async compute( input: TransactionProvingTaskParameters ): Promise { + const startingState = input.flatMap((i) => i.parameters.startingState); + await executeWithPrefilledStateService( this.protocol.stateServiceProvider, - input.parameters.startingState, + startingState, async () => { - const { type, parameters } = input; + const { parameters, proof } = input[0]; - const proof1 = DynamicRuntimeProof.fromProof(input.proof1); + const proof1 = DynamicRuntimeProof.fromProof(proof); - if (type === TransactionProvingType.SINGLE) { + if (input.length === 1) { await this.transactionProver.proveTransaction( parameters.publicInput, proof1, parameters.executionData ); } else { + const { parameters: parameters2, proof: proof2 } = input[1]; + await this.transactionProver.proveTransactions( parameters.publicInput, proof1, - DynamicRuntimeProof.fromProof(input.proof2), - parameters.executionData + DynamicRuntimeProof.fromProof(proof2), + parameters.executionData, + parameters2.executionData ); } } @@ -120,7 +122,7 @@ export class TransactionProvingTask return await executeWithPrefilledStateService( this.protocol.stateServiceProvider, - input.parameters.startingState, + startingState, async () => await this.executionContext.current().result.prove() ); diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts index 58771b1df..d0f8bc122 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts @@ -1,4 +1,5 @@ import { + BlockArguments, BlockHashMerkleTreeWitness, BlockProverPublicInput, NetworkState, @@ -9,7 +10,6 @@ import { TransactionProof, TransactionProverPublicInput, TransactionProverPublicOutput, - WitnessedRootWitness, } from "@proto-kit/protocol"; import { Bool } from "o1js"; @@ -30,10 +30,13 @@ interface JsonType { publicInput: ReturnType; networkState: ReturnType; blockWitness: ReturnType; - startingStateBeforeHook: JSONEncodableState; - startingStateAfterHook: JSONEncodableState; deferSTProof: boolean; - afterBlockRootWitness: ReturnType; + deferTransactionProof: boolean; + blocks: { + startingStateBeforeHook: JSONEncodableState; + startingStateAfterHook: JSONEncodableState; + args: ReturnType; + }[]; }; } @@ -71,19 +74,22 @@ export class NewBlockProvingParametersSerializer input.params.blockWitness ), - startingStateBeforeHook: DecodedStateSerializer.toJSON( - input.params.startingStateBeforeHook - ), + blocks: input.params.blocks.map((block) => { + return { + startingStateBeforeHook: DecodedStateSerializer.toJSON( + block.startingStateBeforeHook + ), - startingStateAfterHook: DecodedStateSerializer.toJSON( - input.params.startingStateAfterHook - ), + startingStateAfterHook: DecodedStateSerializer.toJSON( + block.startingStateAfterHook + ), - deferSTProof: input.params.deferSTProof.toBoolean(), + args: BlockArguments.toJSON(block.args), + }; + }), - afterBlockRootWitness: WitnessedRootWitness.toJSON( - input.params.afterBlockRootWitness - ), + deferSTProof: input.params.deferSTProof.toBoolean(), + deferTransactionProof: input.params.deferTransactionProof.toBoolean(), }, } satisfies JsonType); } @@ -108,19 +114,22 @@ export class NewBlockProvingParametersSerializer BlockHashMerkleTreeWitness.fromJSON(jsonObject.params.blockWitness) ), - startingStateBeforeHook: DecodedStateSerializer.fromJSON( - jsonObject.params.startingStateBeforeHook - ), + blocks: jsonObject.params.blocks.map((block) => { + return { + startingStateBeforeHook: DecodedStateSerializer.fromJSON( + block.startingStateBeforeHook + ), - startingStateAfterHook: DecodedStateSerializer.fromJSON( - jsonObject.params.startingStateBeforeHook - ), + startingStateAfterHook: DecodedStateSerializer.fromJSON( + block.startingStateBeforeHook + ), - deferSTProof: Bool(jsonObject.params.deferSTProof), + args: BlockArguments.fromJSON(block.args), + }; + }), - afterBlockRootWitness: WitnessedRootWitness.fromJSON( - jsonObject.params.afterBlockRootWitness - ), + deferSTProof: Bool(jsonObject.params.deferSTProof), + deferTransactionProof: Bool(jsonObject.params.deferTransactionProof), }, }; } diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts index 3e08b804e..2cb30a520 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts @@ -1,19 +1,20 @@ import { - BlockProverPublicInput, MethodPublicOutput, - NetworkState, ReturnType, RuntimeTransaction, + TransactionProverPublicInput, TransactionProverTransactionArguments, + TransactionProverArguments, } from "@proto-kit/protocol"; import { JsonProof, Signature } from "o1js"; +import { assertSizeOneOrTwo, mapSequential } from "@proto-kit/common"; import { TaskSerializer } from "../../../../worker/flow/Task"; import { ProofTaskSerializer } from "../../../../helpers/utils"; import { + TransactionProverTaskParameters, TransactionProvingTaskParameters, - TransactionProvingType, } from "./types/TransactionProvingTypes"; import { DecodedStateSerializer, @@ -21,6 +22,20 @@ import { } from "./DecodedStateSerializer"; import { RuntimeVerificationKeyAttestationSerializer } from "./RuntimeVerificationKeyAttestationSerializer"; +export type TransactionProvingTaskParametersJSON = { + parameters: TransactionProverTaskParametersJSON; + proof: JsonProof; +}[]; + +export type TransactionProverTaskParametersJSON = { + startingState: JSONEncodableState[]; + publicInput: ReturnType; + executionData: { + transaction: TransactionProverTransactionArgumentsJSON; + args: ReturnType; + }; +}; + export type TransactionProverTransactionArgumentsJSON = { transaction: ReturnType; signature: ReturnType; @@ -29,38 +44,6 @@ export type TransactionProverTransactionArgumentsJSON = { >; }; -export type SingleExecutionDataJSON = { - transaction: TransactionProverTransactionArgumentsJSON; - networkState: ReturnType; -}; - -export type MultiExecutionDataJSON = { - transaction1: TransactionProverTransactionArgumentsJSON; - transaction2: TransactionProverTransactionArgumentsJSON; - networkState: ReturnType; -}; - -export type TransactionProverTaskParametersJSON< - ExecutionData extends SingleExecutionDataJSON | MultiExecutionDataJSON, -> = { - startingState: JSONEncodableState[]; - publicInput: ReturnType; - executionData: ExecutionData; -}; - -export type TransactionProvingTaskParametersJSON = - | { - type: TransactionProvingType.SINGLE; - proof1: JsonProof; - parameters: TransactionProverTaskParametersJSON; - } - | { - type: TransactionProvingType.MULTI; - proof1: JsonProof; - proof2: JsonProof; - parameters: TransactionProverTaskParametersJSON; - }; - export class TransactionProvingTaskParameterSerializer implements TaskSerializer { @@ -100,61 +83,35 @@ export class TransactionProvingTaskParameterSerializer }; } - public toJSON(input: TransactionProvingTaskParameters): string { - let taskParamsJson: TransactionProvingTaskParametersJSON; + public toJSON(inputs: TransactionProvingTaskParameters): string { + const taskParamsJson: TransactionProvingTaskParametersJSON = inputs.map( + (input) => { + const { parameters, proof } = input; + const { executionData } = parameters; - const { type, parameters } = input; + const proofJSON = this.runtimeProofSerializer.toJSONProof(proof); - const partialParameters = { - publicInput: BlockProverPublicInput.toJSON(parameters.publicInput), + const parametersJSON: TransactionProverTaskParametersJSON = { + publicInput: TransactionProverPublicInput.toJSON( + parameters.publicInput + ), - startingState: parameters.startingState.map((stateRecord) => - DecodedStateSerializer.toJSON(stateRecord) - ), - }; + startingState: parameters.startingState.map((stateRecord) => + DecodedStateSerializer.toJSON(stateRecord) + ), - // The reason we can't just use the structs toJSON is that the VerificationKey - // toJSON and fromJSON isn't consistent -> i.e. the serialization doesn't work - // the same both ways. We fix that in our custom serializer - if (type === TransactionProvingType.SINGLE) { - const { executionData } = parameters; - const executionDataJson: SingleExecutionDataJSON = { - networkState: NetworkState.toJSON(executionData.networkState), - transaction: this.transactionProverArgumentsToJson( - executionData.transaction - ), - }; + executionData: { + args: TransactionProverArguments.toJSON(executionData.args), - taskParamsJson = { - type, - proof1: this.runtimeProofSerializer.toJSONProof(input.proof1), - parameters: { - ...partialParameters, - executionData: executionDataJson, - }, - }; - } else { - const { executionData } = parameters; - const executionDataJson: MultiExecutionDataJSON = { - networkState: NetworkState.toJSON(executionData.networkState), - transaction1: this.transactionProverArgumentsToJson( - executionData.transaction1 - ), - transaction2: this.transactionProverArgumentsToJson( - executionData.transaction2 - ), - }; + transaction: this.transactionProverArgumentsToJson( + executionData.transaction + ), + }, + }; - taskParamsJson = { - type, - proof1: this.runtimeProofSerializer.toJSONProof(input.proof1), - proof2: this.runtimeProofSerializer.toJSONProof(input.proof2), - parameters: { - ...partialParameters, - executionData: executionDataJson, - }, - }; - } + return { parameters: parametersJSON, proof: proofJSON }; + } + ); return JSON.stringify(taskParamsJson); } @@ -166,58 +123,36 @@ export class TransactionProvingTaskParameterSerializer const jsonReadyObject: TransactionProvingTaskParametersJSON = JSON.parse(json); - const { type, parameters } = jsonReadyObject; + const result = await mapSequential(jsonReadyObject, async (input) => { + const { parameters, proof } = input; - const partialParameters = { - publicInput: BlockProverPublicInput.fromJSON(parameters.publicInput), + const decodedProof = + await this.runtimeProofSerializer.fromJSONProof(proof); - startingState: parameters.startingState.map((stateRecord) => - DecodedStateSerializer.fromJSON(stateRecord) - ), - }; - - if (type === TransactionProvingType.SINGLE) { - return { - type, - proof1: await this.runtimeProofSerializer.fromJSONProof( - jsonReadyObject.proof1 + const decodedParameters: TransactionProverTaskParameters = { + publicInput: TransactionProverPublicInput.fromJSON( + parameters.publicInput + ), + startingState: parameters.startingState.map((stateRecord) => + DecodedStateSerializer.fromJSON(stateRecord) ), - parameters: { - ...partialParameters, - executionData: { - transaction: this.transactionProverArgumentsFromJson( - parameters.executionData.transaction - ), - networkState: new NetworkState( - NetworkState.fromJSON(parameters.executionData.networkState) - ), - }, - }, - }; - } - - return { - type, - proof1: await this.runtimeProofSerializer.fromJSONProof( - jsonReadyObject.proof1 - ), - proof2: await this.runtimeProofSerializer.fromJSONProof( - jsonReadyObject.proof2 - ), - parameters: { - ...partialParameters, executionData: { - transaction1: this.transactionProverArgumentsFromJson( - parameters.executionData.transaction1 - ), - transaction2: this.transactionProverArgumentsFromJson( - parameters.executionData.transaction2 + transaction: this.transactionProverArgumentsFromJson( + parameters.executionData.transaction ), - networkState: new NetworkState( - NetworkState.fromJSON(parameters.executionData.networkState) + args: TransactionProverArguments.fromJSON( + parameters.executionData.args ), }, - }, - }; + }; + return { + parameters: decodedParameters, + proof: decodedProof, + }; + }); + + assertSizeOneOrTwo(result); + + return result; } } diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts b/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts index b091abdc2..0db92f4b7 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts @@ -1,8 +1,7 @@ import { - BlockProverMultiTransactionExecutionData, - BlockProverPublicInput, - BlockProverSingleTransactionExecutionData, MethodPublicOutput, + TransactionProverExecutionData, + TransactionProverPublicInput, } from "@proto-kit/protocol"; import { Proof } from "o1js"; @@ -10,30 +9,15 @@ import type { TaskStateRecord } from "../../../tracing/BlockTracingService"; export type RuntimeProof = Proof; -export enum TransactionProvingType { - SINGLE, - MULTI, -} - -export interface TransactionProverTaskParameters< - ExecutionData extends - | BlockProverSingleTransactionExecutionData - | BlockProverMultiTransactionExecutionData, -> { - publicInput: BlockProverPublicInput; - executionData: ExecutionData; +export interface TransactionProverTaskParameters { + publicInput: TransactionProverPublicInput; + executionData: TransactionProverExecutionData; startingState: TaskStateRecord[]; } -export type TransactionProvingTaskParameters = - | { - type: TransactionProvingType.SINGLE; - parameters: TransactionProverTaskParameters; - proof1: RuntimeProof; - } - | { - type: TransactionProvingType.MULTI; - parameters: TransactionProverTaskParameters; - proof1: RuntimeProof; - proof2: RuntimeProof; - }; +export type OneOrTwo = [Type] | [Type, Type]; + +export type TransactionProvingTaskParameters = OneOrTwo<{ + parameters: TransactionProverTaskParameters; + proof: RuntimeProof; +}>; diff --git a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts index c6046477e..e325508ca 100644 --- a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts +++ b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts @@ -1,4 +1,4 @@ -import { log, mapSequential, noop } from "@proto-kit/common"; +import { log, mapSequential, noop, sleep } from "@proto-kit/common"; import { sequencerModule } from "../../sequencer/builder/SequencerModule"; import { TaskPayload } from "../flow/Task"; @@ -8,12 +8,6 @@ import { InstantiatedQueue, TaskQueue } from "./TaskQueue"; import { ListenerList } from "./ListenerList"; import { AbstractTaskQueue } from "./AbstractTaskQueue"; -async function sleep(ms: number) { - await new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} - // Had to extract it to here bc eslint would ruin the code interface QueueListener { (payload: TaskPayload): Promise; diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 71c4172f9..62f29565c 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -418,7 +418,7 @@ export function testBlockProduction< const numberTxs = 3; - it("should produce block with multiple transaction", async () => { + it("should produce block with multiple transactions", async () => { log.setLevel("TRACE"); expect.assertions(6 + 4 * numberTxs); From e96b20890770da640476df3fe57b1071cd20fcb9 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 9 Jan 2026 00:42:25 +0100 Subject: [PATCH 086/155] Removed dormant log --- packages/protocol/src/prover/block/BlockProver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index c110e28eb..83485f3be 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -382,7 +382,6 @@ export class BlockProverProgrammable extends ZkProgrammable< pendingSTBatchesHash: args.pendingSTBatchesHash, witnessedRootsHash: args.witnessedRootsHash, }); - Provable.log("Pushing", isNotEmptyBlock, bundle); state.bundleList.pushIf(bundle, isNotEmptyBlock); // 3. From 3373909ffefec521dcd0611e975d86fcaf7cef24 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 9 Jan 2026 17:53:35 +0100 Subject: [PATCH 087/155] Fixed two logical bugs in TransactionProver --- packages/protocol/src/prover/utils.ts | 2 +- .../protocol/production/sequencing/BlockProductionService.ts | 2 +- packages/sequencer/src/worker/worker/FlowTaskWorker.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/protocol/src/prover/utils.ts b/packages/protocol/src/prover/utils.ts index 611561baf..9adc0e68b 100644 --- a/packages/protocol/src/prover/utils.ts +++ b/packages/protocol/src/prover/utils.ts @@ -93,7 +93,7 @@ export function addTransactionToBundle< const transactionHash = transaction.hash(); // Append tx to transaction list - state.transactionList.pushIf(transactionHash, isMessage.not()); + state.transactionList.push(transactionHash); // Append tx to eternal transaction list // TODO Change that to the a sequence-state compatible transaction struct diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts index 60fc710b5..f9e57eb16 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts @@ -171,7 +171,7 @@ export class BlockProductionService { height: lastBlock.hash.toBigInt() !== 0n ? lastBlock.height.add(1) : Field(0), fromBlockHashRoot: Field(lastResult.blockHashRoot), - fromMessagesHash: lastBlock.fromMessagesHash, + fromMessagesHash: lastBlock.toMessagesHash, fromStateRoot: Field(lastResult.stateRoot), toMessagesHash: newBlockState.incomingMessages.commitment, previousBlockHash, diff --git a/packages/sequencer/src/worker/worker/FlowTaskWorker.ts b/packages/sequencer/src/worker/worker/FlowTaskWorker.ts index d2cac1f89..d07142898 100644 --- a/packages/sequencer/src/worker/worker/FlowTaskWorker.ts +++ b/packages/sequencer/src/worker/worker/FlowTaskWorker.ts @@ -92,7 +92,7 @@ export class FlowTaskWorker[]> // Call them in order of registration, because the prepare methods // might depend on each other or a result that is saved in a DI singleton for (const task of tasks) { - log.debug(`Preparing task ${task.constructor.name}`); + log.info(`Preparing task ${task.constructor.name}`); // eslint-disable-next-line no-await-in-loop await task.prepare(); log.debug(`${task.constructor.name} prepared`); From 884e24238c6a2cb2ff726cc1b02a3a805d44c9bd Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 10 Jan 2026 00:18:20 +0100 Subject: [PATCH 088/155] Added pkg-pr-new for nightly releases to workflow --- .github/workflows/release-develop.yml | 4 +- package-lock.json | 647 +++++++++++++++++++++++++- package.json | 5 +- 3 files changed, 651 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index 793b57dc7..c2bc7ffa4 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -31,8 +31,8 @@ jobs: - name: "Build" run: npm run build - - name: Run Lerna Release - run: npm run publish:canary + - name: "Publish nightly" + run: npx pkg-pr-new publish --compact './packages/*' - name: Trigger website build & deployment env: diff --git a/package-lock.json b/package-lock.json index 761e3033b..239dab20e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "lint-staged": "^13.1.0", "nx": "15.6.2", "nyc": "^17.1.0", + "pkg-pr-new": "^0.0.62", "prettier": "^3.2.5", "ts-jest": "^29.0.5", "typedoc": "^0.26.11", @@ -60,6 +61,45 @@ } } }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.1.3", "license": "MIT", @@ -778,6 +818,16 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "dev": true, @@ -1913,6 +1963,22 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@jsdevtools/ez-spawn": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@jsdevtools/ez-spawn/-/ez-spawn-3.0.4.tgz", + "integrity": "sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-me-maybe": "^1.0.1", + "cross-spawn": "^7.0.3", + "string-argv": "^0.3.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@kamilkisiela/fast-url-parser": { "version": "1.1.4", "license": "MIT" @@ -3086,6 +3152,298 @@ "tao": "index.js" } }, + "node_modules/@octokit/action": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/action/-/action-6.1.0.tgz", + "integrity": "sha512-lo+nHx8kAV86bxvOVOI3vFjX3gXPd/L7guAUbvs3pUvnR2KC+R7yjBkA1uACt4gYhs4LcWP3AXSGQzsbeN2XXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-action": "^4.0.0", + "@octokit/core": "^5.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/types": "^12.0.0", + "undici": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/action/node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/action/node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/action/node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/action/node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/action/node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/action/node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/action/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/action/node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@octokit/auth-action": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-action/-/auth-action-4.1.0.tgz", + "integrity": "sha512-m+3t7K46IYyMk7Bl6/lF4Rv09GqDZjYmNg8IWycJ2Fa3YE3DE7vQcV6G2hUPmR9NDqenefNJwVtlisMjzymPiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-action/node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-action/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, "node_modules/@octokit/auth-token": { "version": "3.0.4", "dev": true, @@ -6155,7 +6513,9 @@ } }, "node_modules/acorn": { - "version": "8.12.0", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7291,6 +7651,13 @@ "node": ">= 0.4" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "license": "MIT", @@ -7869,6 +8236,13 @@ "typedarray": "^0.0.6" } }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, "node_modules/config-chain": { "version": "1.1.12", "dev": true, @@ -8526,6 +8900,16 @@ "node": ">=0.10.0" } }, + "node_modules/decode-uri-component": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz", + "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, "node_modules/dedent": { "version": "0.7.0", "dev": true, @@ -10027,6 +10411,19 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz", + "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "license": "MIT", @@ -13645,6 +14042,19 @@ "dev": true, "license": "MIT" }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, "node_modules/isexe": { "version": "2.0.0", "license": "ISC" @@ -17872,6 +18282,19 @@ "node": ">=10" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, "node_modules/modify-values": { "version": "1.0.1", "dev": true, @@ -20028,6 +20451,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pg-int8": { "version": "1.0.1", "license": "ISC", @@ -20151,6 +20581,38 @@ "node": ">=8" } }, + "node_modules/pkg-pr-new": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/pkg-pr-new/-/pkg-pr-new-0.0.62.tgz", + "integrity": "sha512-K2jtf1PLCJJFDimQIpasPXDdnRTZSYPy36Ldz+QhMpLz2YN1Wi0ZQkomVt5Wi3NdBZcYuYGXekyFWfJ6fAHYjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/core": "^1.11.1", + "@jsdevtools/ez-spawn": "^3.0.4", + "@octokit/action": "^6.1.0", + "ignore": "^5.3.1", + "isbinaryfile": "^5.0.2", + "pkg-types": "^1.1.1", + "query-registry": "^3.0.1", + "tinyglobby": "^0.2.9" + }, + "bin": { + "pkg-pr-new": "bin/cli.js" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/pluralize": { "version": "8.0.0", "license": "MIT", @@ -20570,6 +21032,65 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-registry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/query-registry/-/query-registry-3.0.1.tgz", + "integrity": "sha512-M9RxRITi2mHMVPU5zysNjctUT8bAPx6ltEXo/ir9+qmiM47Y7f0Ir3+OxUO5OjYAWdicBQRew7RtHtqUXydqlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "query-string": "^9.0.0", + "quick-lru": "^7.0.0", + "url-join": "^5.0.0", + "validate-npm-package-name": "^5.0.1", + "zod": "^3.23.8", + "zod-package-json": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/query-registry/node_modules/quick-lru": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.3.0.tgz", + "integrity": "sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/query-registry/node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/query-string": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.3.1.tgz", + "integrity": "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.4.1", + "filter-obj": "^5.1.0", + "split-on-first": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "funding": [ @@ -22041,6 +22562,19 @@ "node": "*" } }, + "node_modules/split-on-first": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz", + "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/split2": { "version": "3.2.2", "dev": true, @@ -22699,6 +23233,54 @@ "ms": "^2.1.1" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.0.33", "dev": true, @@ -23096,6 +23678,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -23352,6 +23944,13 @@ "dev": true, "license": "MIT" }, + "node_modules/ufo": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz", + "integrity": "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.18.0", "license": "BSD-2-Clause", @@ -23396,6 +23995,19 @@ "node": "*" } }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "license": "MIT" @@ -23590,6 +24202,16 @@ "node": ">= 0.10" } }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/urlpattern-polyfill": { "version": "10.0.0", "license": "MIT" @@ -24230,6 +24852,29 @@ "version": "0.3.3", "license": "MIT" }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-package-json/-/zod-package-json-1.2.0.tgz", + "integrity": "sha512-tamtgPM3MkP+obfO2dLr/G+nYoYkpJKmuHdYEy6IXRKfLybruoJ5NUj0lM0LxwOpC9PpoGLbll1ecoeyj43Wsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.25.64" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/zwitch": { "version": "2.0.4", "dev": true, diff --git a/package.json b/package.json index 7ffba608c..a27f72dc1 100644 --- a/package.json +++ b/package.json @@ -45,20 +45,21 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.25.1", "husky": "^8.0.3", + "istanbul-merge": "^2.0.0", "istanbul-reporter-html-monorepo": "^1.1.3", "jest": "^29.4.0", "lerna": "^6.4.1", "lint-staged": "^13.1.0", "nx": "15.6.2", "nyc": "^17.1.0", + "pkg-pr-new": "^0.0.62", "prettier": "^3.2.5", "ts-jest": "^29.0.5", "typedoc": "^0.26.11", "typedoc-plugin-frontmatter": "1.0.0", "typedoc-plugin-inline-sources": "1.2.0", "typedoc-plugin-markdown": "4.2.10", - "typescript": "5.1", - "istanbul-merge": "^2.0.0" + "typescript": "5.1" }, "workspaces": [ "packages/*" From 189443e74b832d2708cd2c754d6e8779883f63d1 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 10 Jan 2026 00:22:05 +0100 Subject: [PATCH 089/155] Added changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0d2da03..e1d61318d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,4 +8,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added +- Added nightly releases via pkg.pr.new [#384](https://github.com/proto-kit/framework/pull/384) - Introduced Changelog [#378](https://github.com/proto-kit/framework/pull/378) From a754e10e112997fb72f6e0218447abe28767fd93 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 10 Jan 2026 00:25:40 +0100 Subject: [PATCH 090/155] Removed compact flag --- .github/workflows/release-develop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index c2bc7ffa4..c9c6d67b1 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -32,7 +32,7 @@ jobs: run: npm run build - name: "Publish nightly" - run: npx pkg-pr-new publish --compact './packages/*' + run: npx pkg-pr-new publish ./packages/*' - name: Trigger website build & deployment env: From 59585ed0f6a2262c7030c76947a7224c81809c62 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 10 Jan 2026 00:28:25 +0100 Subject: [PATCH 091/155] Added missing comma --- .github/workflows/release-develop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index c9c6d67b1..f2f88e3aa 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -32,7 +32,7 @@ jobs: run: npm run build - name: "Publish nightly" - run: npx pkg-pr-new publish ./packages/*' + run: npx pkg-pr-new publish './packages/*' - name: Trigger website build & deployment env: From e386b94ade23a6a808238eafb88754fc6f4ca54e Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 10 Jan 2026 11:30:34 +0100 Subject: [PATCH 092/155] Added pkg.pr.new badge to readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 154c2f3f0..37e93f0c1 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@
-[![npm version](https://img.shields.io/npm/v/@proto-kit/sdk.svg?style=flat&logo=npm)](https://www.npmjs.com/package/@proto-kit/sdk) [![Documentation](https://img.shields.io/badge/Documentation-website-blue.svg)](https://protokit.dev) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)]() +[![npm version](https://img.shields.io/npm/v/@proto-kit/sdk.svg?style=flat&logo=npm)](https://www.npmjs.com/package/@proto-kit/sdk) +[![pkg.pr.new](https://pkg.pr.new/badge/proto-kit/framework)](https://pkg.pr.new/~/proto-kit/framework) # Protokit From c686710561260ef2b8e47c29945bd77b21b18432 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 10 Jan 2026 11:39:27 +0100 Subject: [PATCH 093/155] Added missing changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d61318d..06386f0ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,5 +8,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added +- Separated settlement and bridging functionally, so now settlement can be used without bridging [#376](https://github.com/proto-kit/framework/pull/376) - Added nightly releases via pkg.pr.new [#384](https://github.com/proto-kit/framework/pull/384) - Introduced Changelog [#378](https://github.com/proto-kit/framework/pull/378) From 6eb39981f19c5eaf56e9e67e5d7fbc1859ff2480 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 10 Jan 2026 11:46:03 +0100 Subject: [PATCH 094/155] Added changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06386f0ae..ae00ccb73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added +- Added CircuitAnalysisModule for easy analysis of protocol circuits [#379](https://github.com/proto-kit/framework/pull/379) - Separated settlement and bridging functionally, so now settlement can be used without bridging [#376](https://github.com/proto-kit/framework/pull/376) - Added nightly releases via pkg.pr.new [#384](https://github.com/proto-kit/framework/pull/384) - Introduced Changelog [#378](https://github.com/proto-kit/framework/pull/378) From 22d32f4f0f404d86d129f79fecdbe4d7eb6ec259 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Jan 2026 09:39:30 +0100 Subject: [PATCH 095/155] Fixed wrong name in TransactionProver ZkProgram --- packages/protocol/src/prover/transaction/TransactionProver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts index b74a3660f..4ca81a089 100644 --- a/packages/protocol/src/prover/transaction/TransactionProver.ts +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -386,7 +386,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< const merge = prover.merge.bind(prover); const program = ZkProgram({ - name: "BlockProver", + name: "TransactionProver", publicInput: TransactionProverPublicInput, publicOutput: TransactionProverPublicOutput, From 6671e34135899ec82029f9d5ca01e219c57955d5 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Jan 2026 10:04:16 +0100 Subject: [PATCH 096/155] Fix proving bug with witness zeroes --- packages/common/src/trees/sparse/RollupMerkleTree.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/common/src/trees/sparse/RollupMerkleTree.ts b/packages/common/src/trees/sparse/RollupMerkleTree.ts index f901ccf29..7b57c912d 100644 --- a/packages/common/src/trees/sparse/RollupMerkleTree.ts +++ b/packages/common/src/trees/sparse/RollupMerkleTree.ts @@ -144,6 +144,8 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { return zeroes; } + const zero = generateZeroes(); + /** * The {@link RollupMerkleWitness} class defines a circuit-compatible base class * for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof). @@ -184,11 +186,6 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { leafIndex: Field, leaf: Field ): [Field, RollupMerkleWitness] { - let zero: bigint[] = []; - Provable.asProver(() => { - zero = generateZeroes(); - }); - if (zero.length === 0) { throw new Error("Zeroes not initialized"); } From eee030e9eac532a34268f38ef7418079dd115ec2 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Jan 2026 10:07:12 +0100 Subject: [PATCH 097/155] Made zero generation more efficient --- packages/common/src/trees/sparse/RollupMerkleTree.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/common/src/trees/sparse/RollupMerkleTree.ts b/packages/common/src/trees/sparse/RollupMerkleTree.ts index 7b57c912d..c701a091d 100644 --- a/packages/common/src/trees/sparse/RollupMerkleTree.ts +++ b/packages/common/src/trees/sparse/RollupMerkleTree.ts @@ -144,7 +144,14 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { return zeroes; } - const zero = generateZeroes(); + let zeroCache: bigint[] | undefined = undefined; + + function getZeroes() { + if (zeroCache === undefined) { + zeroCache = generateZeroes(); + } + return zeroCache; + } /** * The {@link RollupMerkleWitness} class defines a circuit-compatible base class @@ -186,6 +193,9 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { leafIndex: Field, leaf: Field ): [Field, RollupMerkleWitness] { + // This won't generate any constraints, since it's purely a computation on constants + const zero = getZeroes(); + if (zero.length === 0) { throw new Error("Zeroes not initialized"); } From 403906c8348629a5772c1b47d5f5115edaf446d1 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Jan 2026 11:20:55 +0100 Subject: [PATCH 098/155] Merge conflicts, added changelog --- CHANGELOG.md | 1 + package-lock.json | 4131 +++++++++++++++-- .../src/settlement/SettlementModule.ts | 3 +- 3 files changed, 3826 insertions(+), 309 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae00ccb73..c2eb240f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added +- Introduced block explorer [#381](https://github.com/proto-kit/framework/pull/381) - Added CircuitAnalysisModule for easy analysis of protocol circuits [#379](https://github.com/proto-kit/framework/pull/379) - Separated settlement and bridging functionally, so now settlement can be used without bridging [#376](https://github.com/proto-kit/framework/pull/376) - Added nightly releases via pkg.pr.new [#384](https://github.com/proto-kit/framework/pull/384) diff --git a/package-lock.json b/package-lock.json index 239dab20e..f5626d39d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,6 +121,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "license": "Apache-2.0", @@ -690,6 +702,40 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@envelop/core": { "version": "5.0.2", "license": "MIT", @@ -828,6 +874,44 @@ "node": ">=14" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "dev": true, @@ -1070,6 +1154,15 @@ "node": ">=6" } }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "dev": true, @@ -2595,6 +2688,221 @@ "node": ">=10" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz", + "integrity": "sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.4.tgz", + "integrity": "sha512-svSFxW9f3xDaZA3idQmlFw7SusOuWTpDTAeBlO3AEPDltrraV+lqs7mAc6A27YdnpQVVIA3sODqUAAHdWhVWsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "10.3.10" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz", + "integrity": "sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz", + "integrity": "sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz", + "integrity": "sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz", + "integrity": "sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz", + "integrity": "sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz", + "integrity": "sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz", + "integrity": "sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz", + "integrity": "sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz", + "integrity": "sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", @@ -2624,6 +2932,16 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/arborist": { "version": "6.2.3", "dev": true, @@ -5229,6 +5547,10 @@ "resolved": "packages/deployment", "link": true }, + "node_modules/@proto-kit/explorer": { + "resolved": "packages/explorer", + "link": true + }, "node_modules/@proto-kit/indexer": { "resolved": "packages/indexer", "link": true @@ -5309,31 +5631,996 @@ "version": "1.1.0", "license": "BSD-3-Clause" }, - "node_modules/@redis/bloom": { - "version": "1.2.0", + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@redis/client": { - "version": "1.5.16", + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", "license": "MIT", "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, - "engines": { - "node": ">=14" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@redis/client/node_modules/yallist": { - "version": "4.0.0", - "license": "ISC" - }, - "node_modules/@redis/graph": { - "version": "1.1.1", + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.16", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" @@ -5364,6 +6651,13 @@ "version": "3.0.6", "license": "MIT" }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", + "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "dev": true, + "license": "MIT" + }, "node_modules/@shikijs/core": { "version": "1.29.2", "dev": true, @@ -5572,6 +6866,22 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "dev": true, @@ -5641,6 +6951,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/accepts": { "version": "1.3.7", "dev": true, @@ -5984,6 +7305,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "license": "MIT" @@ -6346,26 +7677,295 @@ "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.14.1", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.14.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@typescript-eslint/types": "7.14.1", - "eslint-visitor-keys": "^3.4.3" + "@napi-rs/wasm-runtime": "^0.2.11" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=14.0.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@urql/core": { "version": "4.3.0", @@ -6652,6 +8252,12 @@ "node": ">=4" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "license": "ISC", @@ -6698,20 +8304,43 @@ }, "node_modules/arg": { "version": "5.0.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -6779,6 +8408,27 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.5", "dev": true, @@ -6816,14 +8466,16 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6832,19 +8484,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -6876,6 +8546,13 @@ "node": ">=11.14.0" } }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, "node_modules/astral-regex": { "version": "2.0.0", "license": "MIT", @@ -6887,6 +8564,16 @@ "version": "3.2.5", "license": "MIT" }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" @@ -6911,6 +8598,8 @@ }, "node_modules/available-typed-arrays": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6923,6 +8612,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axe-core": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.7.2", "dev": true, @@ -6933,6 +8632,16 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "license": "MIT", @@ -7206,6 +8915,18 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bl": { "version": "4.1.0", "dev": true, @@ -7623,14 +9344,16 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -7651,6 +9374,22 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -7672,6 +9411,15 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/camelcase-keys": { "version": "6.2.2", "dev": true, @@ -7864,6 +9612,18 @@ "validator": "^13.9.0" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -7978,6 +9738,12 @@ "node": ">= 10" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, "node_modules/cliui": { "version": "7.0.4", "dev": true, @@ -8020,6 +9786,15 @@ "node": ">=0.10.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "license": "Apache-2.0", @@ -8038,6 +9813,22 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/co": { "version": "4.6.0", "license": "MIT", @@ -8754,7 +10545,6 @@ }, "node_modules/cssesc": { "version": "3.0.0", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -8787,6 +10577,13 @@ "@commitlint/load": ">6.1.1" } }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/dargs": { "version": "7.0.0", "dev": true, @@ -8796,13 +10593,15 @@ } }, "node_modules/data-view-buffer": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8812,27 +10611,31 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -8855,10 +10658,12 @@ } }, "node_modules/debug": { - "version": "4.3.5", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -8958,6 +10763,7 @@ }, "node_modules/define-data-property": { "version": "1.1.4", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -9096,6 +10902,12 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/devlop": { "version": "1.1.0", "dev": true, @@ -9108,6 +10920,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, "node_modules/diff": { "version": "4.0.2", "license": "BSD-3-Clause", @@ -9132,6 +10950,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, "node_modules/doctrine": { "version": "3.0.0", "dev": true, @@ -9420,56 +11244,66 @@ } }, "node_modules/es-abstract": { - "version": "1.23.3", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -9493,6 +11327,34 @@ "node": ">= 0.4" } }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -9527,13 +11389,15 @@ } }, "node_modules/es-to-primitive": { - "version": "1.2.1", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -9658,6 +11522,192 @@ "eslint": "^8.56.0" } }, + "node_modules/eslint-config-next": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.4.tgz", + "integrity": "sha512-Qr0wMgG9m6m4uYy2jrYJmyuNlYZzPRQq5Kvb9IDlYwn+7yq6W6sfMNFgb+9guM1KYwuIo6TIaiFhZJ6SnQ/Efw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "14.2.4", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/eslint-config-prettier": { "version": "9.1.0", "dev": true, @@ -9748,67 +11798,246 @@ "dev": true, "license": "MIT", "dependencies": { - "@phenomnomnominal/tsquery": "^5.0.0", - "@typescript-eslint/experimental-utils": "^5.0.0", - "eslint-etc": "^5.1.0", - "requireindex": "~1.2.0", - "tslib": "^2.0.0", - "tsutils": "^3.0.0" + "@phenomnomnominal/tsquery": "^5.0.0", + "@typescript-eslint/experimental-utils": "^5.0.0", + "eslint-etc": "^5.1.0", + "requireindex": "~1.2.0", + "tslib": "^2.0.0", + "tsutils": "^3.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0", + "typescript": ">=4.0.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { - "eslint": "^8.0.0", - "typescript": ">=4.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", + "node_modules/eslint-plugin-react-hooks": { + "version": "5.0.0-canary-7118f5dd7-20230705", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { + "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -9818,8 +12047,10 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { + "node_modules/eslint-plugin-react/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -9829,41 +12060,32 @@ "node": "*" } }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": "^14.18.0 || >=16.0.0" + "bin": { + "resolve": "bin/resolve" }, "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": "*", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, "node_modules/eslint-plugin-sonarjs": { @@ -10625,11 +12847,19 @@ } }, "node_modules/for-each": { - "version": "0.3.3", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/for-in": { @@ -10759,6 +12989,20 @@ "version": "1.0.0", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", @@ -10767,14 +13011,18 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.6", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -10893,6 +13141,15 @@ "node": ">=14" } }, + "node_modules/geist": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/geist/-/geist-1.5.1.tgz", + "integrity": "sha512-mAHZxIsL2o3ZITFaBVFBnwyDOw+zNLYum6A6nIjpzCGIO8QtC3V76XF2RnZTyLx1wlDTmMDy8jg3Ib52MIjGvQ==", + "license": "SIL OPEN FONT LICENSE", + "peerDependencies": { + "next": ">=13.2.0" + } + }, "node_modules/generic-pool": { "version": "3.9.0", "license": "MIT", @@ -10937,6 +13194,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "license": "MIT", @@ -11080,13 +13346,15 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.2", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -11095,6 +13363,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/getobject": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", @@ -11214,7 +13495,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -12224,9 +14504,14 @@ } }, "node_modules/has-bigints": { - "version": "1.0.2", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12240,6 +14525,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -12249,9 +14535,14 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -13517,13 +15808,15 @@ } }, "node_modules/internal-slot": { - "version": "1.0.7", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -13589,12 +15882,15 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.4", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -13607,24 +15903,63 @@ "version": "0.2.1", "license": "MIT" }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { - "version": "1.0.4", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { - "version": "1.1.2", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -13633,6 +15968,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, "node_modules/is-callable": { "version": "1.2.7", "dev": true, @@ -13672,10 +16017,14 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -13686,11 +16035,14 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -13719,6 +16071,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "4.0.0", "license": "MIT", @@ -13779,6 +16147,19 @@ "tslib": "^2.0.3" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-negative-zero": { "version": "2.0.3", "dev": true, @@ -13806,11 +16187,14 @@ } }, "node_modules/is-number-object": { - "version": "1.0.7", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -13860,12 +16244,16 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -13882,15 +16270,30 @@ "is-unc-path": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -13918,11 +16321,14 @@ } }, "node_modules/is-string": { - "version": "1.0.7", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -13932,11 +16338,15 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -13957,11 +16367,13 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -14009,12 +16421,47 @@ "dev": true, "license": "MIT" }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { - "version": "1.0.2", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14039,6 +16486,8 @@ }, "node_modules/isarray": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, @@ -14359,6 +16808,24 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jackspeak": { "version": "3.4.0", "license": "BlueOak-1.0.0", @@ -15819,10 +18286,10 @@ } }, "node_modules/jiti": { - "version": "1.21.6", - "dev": true, + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "optional": true, "bin": { "jiti": "bin/jiti.js" } @@ -16015,6 +18482,22 @@ "node": "*" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/just-diff": { "version": "6.0.2", "dev": true, @@ -16104,6 +18587,26 @@ "node": ">= 10" } }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/lerna": { "version": "6.6.2", "dev": true, @@ -17274,6 +19777,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.395.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.395.0.tgz", + "integrity": "sha512-6hzdNH5723A4FLaYZWpK50iyZH8iS2Jq5zuPRRotOFkhu6kxxJiebVdJ72tCR5XkiIeYFOU5NUawFZOac+VeYw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/lunr": { "version": "2.3.9", "dev": true, @@ -17963,7 +20475,9 @@ "license": "MIT" }, "node_modules/micromatch": { - "version": "4.0.7", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -18347,7 +20861,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/msgpackr": { @@ -18409,6 +20925,17 @@ "dev": true, "license": "ISC" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -18426,6 +20953,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "license": "MIT" @@ -18441,6 +20984,85 @@ "version": "2.6.2", "license": "MIT" }, + "node_modules/next": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.4.tgz", + "integrity": "sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==", + "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.4", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.4", + "@next/swc-darwin-x64": "14.2.4", + "@next/swc-linux-arm64-gnu": "14.2.4", + "@next/swc-linux-arm64-musl": "14.2.4", + "@next/swc-linux-x64-gnu": "14.2.4", + "@next/swc-linux-x64-musl": "14.2.4", + "@next/swc-win32-arm64-msvc": "14.2.4", + "@next/swc-win32-ia32-msvc": "14.2.4", + "@next/swc-win32-x64-msvc": "14.2.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/node-addon-api": { "version": "3.2.1", "dev": true, @@ -19593,8 +22215,19 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { - "version": "1.13.2", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -19612,13 +22245,17 @@ } }, "node_modules/object.assign": { - "version": "4.1.5", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -19643,13 +22280,16 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -19709,11 +22349,14 @@ } }, "node_modules/object.values": { - "version": "1.2.0", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -19917,6 +22560,24 @@ "os-tmpdir": "^1.0.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-finally": { "version": "1.0.0", "dev": true, @@ -20642,44 +23303,168 @@ "lodash": "^4.17.14" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "dev": true, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "funding": [ { "type": "opencollective", "url": "https://opencollective.com/postcss/" }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "postcss-selector-parser": "^6.1.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" } }, "node_modules/postcss-selector-parser": { - "version": "6.1.0", - "dev": true, + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -20689,6 +23474,12 @@ "node": ">=4" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/postgres-array": { "version": "2.0.0", "license": "MIT", @@ -21168,6 +23959,35 @@ "node": ">=0.10.0" } }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-hook-form": { + "version": "7.71.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.0.tgz", + "integrity": "sha512-oFDt/iIFMV9ZfV52waONXzg4xuSlbwKUPvXVH2jumL1me5qFhBMc4knZxuXiZ2+j6h546sYe3ZKJcg/900/iHw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "18.3.1", "license": "MIT" @@ -21196,6 +24016,84 @@ "react": "^18.3.1" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-truncate-inside": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/react-truncate-inside/-/react-truncate-inside-1.0.3.tgz", + "integrity": "sha512-XwQ+9ayvygHI4WjJwaTDYFiYLrLMbHiBc5VdhWuDrCAm8MWFT6YR/+BCgq5JUwyuISYleJyZ0yCAGbXMhGr9hw==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.x.x" + } + }, "node_modules/read": { "version": "1.0.7", "dev": true, @@ -21207,6 +24105,24 @@ "node": ">=0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-cmd-shim": { "version": "3.0.0", "dev": true, @@ -21562,6 +24478,29 @@ "version": "0.1.14", "license": "Apache-2.0" }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regex": { "version": "5.1.1", "dev": true, @@ -21585,14 +24524,18 @@ "license": "MIT" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.2", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -21706,6 +24649,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "license": "MIT", @@ -21796,13 +24749,16 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.2", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -21835,14 +24791,33 @@ "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==" }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-regex-test": { - "version": "1.0.3", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -21923,7 +24898,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -21979,10 +24956,6 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, "node_modules/send/node_modules/statuses": { "version": "2.0.1", "license": "MIT", @@ -22079,6 +25052,7 @@ }, "node_modules/set-function-length": { "version": "1.2.2", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -22106,6 +25080,21 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "license": "ISC" @@ -22158,13 +25147,69 @@ "license": "BSD-2-Clause" }, "node_modules/side-channel": { - "version": "1.0.6", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -22614,6 +25659,13 @@ "dev": true, "license": "ISC" }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, "node_modules/stack-generator": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", @@ -22688,6 +25740,20 @@ "node": ">= 0.6" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "engines": { @@ -22755,25 +25821,84 @@ "version": "3.0.0", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -22783,14 +25908,20 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -22896,6 +26027,66 @@ "node": ">=4" } }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "license": "MIT", @@ -22960,6 +26151,122 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/tar": { "version": "6.1.11", "dev": true, @@ -23199,6 +26506,27 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through": { "version": "2.3.8", "dev": true, @@ -23237,7 +26565,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -23254,7 +26581,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -23272,7 +26598,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -23372,6 +26697,12 @@ } } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, "node_modules/ts-jest": { "version": "29.1.5", "dev": true, @@ -23764,28 +27095,32 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.2", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -23795,16 +27130,19 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -23814,16 +27152,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -23962,14 +27302,19 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.2", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -24149,6 +27494,41 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/upath": { "version": "2.0.1", "dev": true, @@ -24216,6 +27596,49 @@ "version": "10.0.0", "license": "MIT" }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "license": "MIT" @@ -24424,15 +27847,67 @@ } }, "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "license": "MIT", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -24444,14 +27919,18 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.15", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, "engines": { @@ -24762,7 +28241,7 @@ }, "node_modules/yaml": { "version": "2.7.0", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -24856,7 +28335,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -25000,6 +28478,45 @@ "tsyringe": "^4.10.0" } }, + "packages/explorer": { + "name": "@proto-kit/explorer", + "version": "0.1.0", + "dependencies": { + "@hookform/resolvers": "^3.6.0", + "@radix-ui/react-checkbox": "^1.1.0", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-popover": "^1.1.0", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tooltip": "^1.0.7", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "geist": "^1.3.0", + "lucide-react": "^0.395.0", + "next": "14.2.4", + "react": "^18", + "react-dom": "^18", + "react-hook-form": "^7.52.0", + "react-truncate-inside": "^1.0.3", + "tailwind-merge": "^2.3.0", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "14.2.4", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } + }, "packages/indexer": { "name": "@proto-kit/indexer", "version": "0.1.1-develop.267+b252853", diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index a1652efd1..3d9486e05 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -23,8 +23,7 @@ import { sequencerModule, } from "../sequencer/builder/SequencerModule"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; -import { Batch, SettleableBatch } from "../storage/model/Batch"; -import { BlockProofSerializer } from "../protocol/production/tasks/serializers/BlockProofSerializer"; +import { SettleableBatch } from "../storage/model/Batch"; import { Settlement } from "../storage/model/Settlement"; import { SettlementStorage } from "../storage/repositories/SettlementStorage"; From 488b07a49929848b33f6bc70dd13b1f304bd2ea9 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Jan 2026 11:45:38 +0100 Subject: [PATCH 099/155] Added missing TransactionProver config for integration tests --- packages/persistance/test-integration/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/persistance/test-integration/utils.ts b/packages/persistance/test-integration/utils.ts index 7c50982c1..e62050794 100644 --- a/packages/persistance/test-integration/utils.ts +++ b/packages/persistance/test-integration/utils.ts @@ -126,6 +126,7 @@ export function createPrismaAppchain( AccountState: {}, BlockProver: {}, StateTransitionProver: {}, + TransactionProver: {}, BlockHeight: {}, LastStateRoot: {}, }, From f66905f529fac350cc5c8703562b79f479f01372 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Jan 2026 14:57:38 +0100 Subject: [PATCH 100/155] Updated pushUnconstrained --- packages/protocol/src/utils/ProvableHashList.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/protocol/src/utils/ProvableHashList.ts b/packages/protocol/src/utils/ProvableHashList.ts index 1061b167b..777f41f65 100644 --- a/packages/protocol/src/utils/ProvableHashList.ts +++ b/packages/protocol/src/utils/ProvableHashList.ts @@ -40,12 +40,14 @@ export abstract class ProvableHashList { protected abstract hash(elements: Field[]): Field; private pushUnconstrained(preimage: Field, value: Value) { - const valueConstant = this.valueType.fromFields( - this.valueType.toFields(value).map((field) => field.toConstant()) - ); - this.unconstrainedList.get().push({ - preimage: preimage.toConstant(), - value: valueConstant, + this.unconstrainedList.updateAsProver((array) => { + return [ + ...array, + { + preimage: preimage.toConstant(), + value: Provable.toConstant(this.valueType, value), + }, + ]; }); } From 038981b55dc7fb3f78a56a232c1d5c4ddbe8f9fb Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 14 Jan 2026 19:35:35 +0100 Subject: [PATCH 101/155] Removed unnecessary asProver blocks --- packages/protocol/src/utils/ProvableHashList.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/protocol/src/utils/ProvableHashList.ts b/packages/protocol/src/utils/ProvableHashList.ts index 777f41f65..b59da6676 100644 --- a/packages/protocol/src/utils/ProvableHashList.ts +++ b/packages/protocol/src/utils/ProvableHashList.ts @@ -39,8 +39,11 @@ export abstract class ProvableHashList { protected abstract hash(elements: Field[]): Field; - private pushUnconstrained(preimage: Field, value: Value) { + private pushUnconstrained(preimage: Field, value: Value, condition?: Bool) { this.unconstrainedList.updateAsProver((array) => { + if (condition !== undefined && !condition.toBoolean()) { + return array; + } return [ ...array, { @@ -97,9 +100,7 @@ export abstract class ProvableHashList { * @returns Current hash list. */ public push(value: Value) { - Provable.asProver(() => { - this.pushUnconstrained(this.commitment, value); - }); + this.pushUnconstrained(this.commitment, value); this.commitment = this.hash([ this.commitment, @@ -110,11 +111,7 @@ export abstract class ProvableHashList { } public pushIf(value: Value, condition: Bool) { - Provable.asProver(() => { - if (condition.toBoolean()) { - this.pushUnconstrained(this.commitment, value); - } - }); + this.pushUnconstrained(this.commitment, value, condition); const newCommitment = this.hash([ this.commitment, From 4a79498b324a06943aad70abbefc8830970a8245 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 15:23:25 +0100 Subject: [PATCH 102/155] Removed leftover comments --- .../prover/transaction/TransactionProver.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts index 4ca81a089..da319160a 100644 --- a/packages/protocol/src/prover/transaction/TransactionProver.ts +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -342,32 +342,10 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< ) ); - // Check pendingSTBatchesHash - // publicInput.pendingSTBatchesHash.assertEquals( - // proof1.publicInput.pendingSTBatchesHash, - // errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - // ); - // proof1.publicOutput.pendingSTBatchesHash.assertEquals( - // proof2.publicInput.pendingSTBatchesHash, - // errors.transactionsHashNotMatching("proof1.to -> proof2.from") - // ); - // - // // Check witnessedRootsHash - // publicInput.witnessedRootsHash.assertEquals( - // proof1.publicInput.witnessedRootsHash, - // errors.transactionsHashNotMatching("publicInput.from -> proof1.from") - // ); - // proof1.publicOutput.witnessedRootsHash.assertEquals( - // proof2.publicInput.witnessedRootsHash, - // errors.transactionsHashNotMatching("proof1.to -> proof2.from") - // ); - return new TransactionProverPublicOutput({ bundlesHash: proof2.publicOutput.bundlesHash, eternalTransactionsHash: proof2.publicOutput.eternalTransactionsHash, incomingMessagesHash: proof2.publicOutput.incomingMessagesHash, - // pendingSTBatchesHash: proof2.publicOutput.pendingSTBatchesHash, - // witnessedRootsHash: proof2.publicOutput.witnessedRootsHash, }); } From 86cec6f4fde498d20fdf21c5242cf5ffcafc4974 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 14:15:40 +0100 Subject: [PATCH 103/155] Implemented dynamic block building --- packages/indexer/src/IndexerNotifier.ts | 2 +- .../prisma/PrismaTransactionStorage.ts | 7 +- packages/sequencer/src/mempool/Mempool.ts | 4 +- .../src/mempool/private/PrivateMempool.ts | 214 ++---------------- .../production/sequencing/BlockBuilder.ts | 149 ++++++++++++ .../sequencing/BlockProducerModule.ts | 41 +--- .../sequencing/BlockProductionService.ts | 22 +- .../production/sequencing/Ordering.ts | 134 +++++++++++ .../sequencing/TransactionExecutionService.ts | 189 ++++------------ .../production/trigger/TimedBlockTrigger.ts | 2 +- .../inmemory/InMemoryTransactionStorage.ts | 12 +- .../repositories/TransactionStorage.ts | 5 +- 12 files changed, 384 insertions(+), 397 deletions(-) create mode 100644 packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts create mode 100644 packages/sequencer/src/protocol/production/sequencing/Ordering.ts diff --git a/packages/indexer/src/IndexerNotifier.ts b/packages/indexer/src/IndexerNotifier.ts index 5f6816f8b..93a33921c 100644 --- a/packages/indexer/src/IndexerNotifier.ts +++ b/packages/indexer/src/IndexerNotifier.ts @@ -81,7 +81,7 @@ export class IndexerNotifier extends SequencerModule> { await txQueue.addTask(task); } catch (err) { - console.error("Failed to add pending-tx task", err); + log.error("Failed to add pending-tx task", err); } }); this.sequencer.events.on("batch-produced", async (batch) => { diff --git a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts index d4544a7c4..49256eef0 100644 --- a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts @@ -19,7 +19,10 @@ export class PrismaTransactionStorage implements TransactionStorage { ) {} @trace("db.txs.get") - public async getPendingUserTransactions(): Promise { + public async getPendingUserTransactions( + offset: number, + limit?: number + ): Promise { const { prismaClient } = this.connection; const txs = await prismaClient.transaction.findMany({ @@ -31,6 +34,8 @@ export class PrismaTransactionStorage implements TransactionStorage { equals: false, }, }, + skip: offset, + take: limit, }); return txs.map((tx) => this.transactionMapper.mapIn(tx)); } diff --git a/packages/sequencer/src/mempool/Mempool.ts b/packages/sequencer/src/mempool/Mempool.ts index 97401a973..9aca0be96 100644 --- a/packages/sequencer/src/mempool/Mempool.ts +++ b/packages/sequencer/src/mempool/Mempool.ts @@ -17,7 +17,9 @@ export interface Mempool /** * Retrieve all transactions that are currently in the mempool */ - getTxs: (limit?: number) => Promise; + getTxs: (offset: number, limit?: number) => Promise; + + getMandatoryTxs: () => Promise; removeTxs: (included: string[], dropped: string[]) => Promise; } diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index ca82f42cc..71469946b 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -1,21 +1,5 @@ -import { - EventEmitter, - log, - noop, - ModuleContainerLike, -} from "@proto-kit/common"; -import { container, inject } from "tsyringe"; -import { - AccountStateHook, - MandatoryProtocolModulesRecord, - NetworkState, - Protocol, - ProvableHookTransactionState, - RuntimeMethodExecutionContext, - RuntimeMethodExecutionData, - StateServiceProvider, -} from "@proto-kit/protocol"; -import { Field } from "o1js"; +import { EventEmitter, log, noop } from "@proto-kit/common"; +import { inject } from "tsyringe"; import type { Mempool, MempoolEvents } from "../Mempool"; import type { PendingTransaction } from "../PendingTransaction"; @@ -25,50 +9,27 @@ import { } from "../../sequencer/builder/SequencerModule"; import { TransactionStorage } from "../../storage/repositories/TransactionStorage"; import { TransactionValidator } from "../verification/TransactionValidator"; -import { BlockStorage } from "../../storage/repositories/BlockStorage"; -import { CachedStateService } from "../../state/state/CachedStateService"; -import { AsyncStateService } from "../../state/async/AsyncStateService"; -import { distinctByPredicate } from "../../helpers/utils"; import { Tracer } from "../../logging/Tracer"; import { trace } from "../../logging/trace"; - -type MempoolTransactionPaths = { - transaction: PendingTransaction; - paths: Field[]; -}; - -interface PrivateMempoolConfig { - validationEnabled?: boolean; -} +import { IncomingMessagesService } from "../../settlement/messages/IncomingMessagesService"; @sequencerModule() -export class PrivateMempool - extends SequencerModule - implements Mempool -{ +export class PrivateMempool extends SequencerModule implements Mempool { public readonly events = new EventEmitter(); - private readonly accountStateHook: AccountStateHook; - public constructor( private readonly transactionValidator: TransactionValidator, @inject("TransactionStorage") private readonly transactionStorage: TransactionStorage, - @inject("Protocol") - private readonly protocol: Protocol, - @inject("Sequencer") - private readonly sequencer: ModuleContainerLike, - @inject("UnprovenStateService") - private readonly stateService: AsyncStateService, + @inject("IncomingMessagesService", { isOptional: true }) + private readonly messageService: IncomingMessagesService | undefined, @inject("Tracer") public readonly tracer: Tracer ) { super(); - this.accountStateHook = - this.protocol.dependencyContainer.resolve("AccountState"); } public async length(): Promise { - const txs = await this.transactionStorage.getPendingUserTransactions(); + const txs = await this.transactionStorage.getPendingUserTransactions(0); return txs.length; } @@ -98,166 +59,25 @@ export class PrivateMempool ); } - private get unprovenQueue(): BlockStorage { - return this.sequencer.dependencyContainer.resolve( - "BlockStorage" - ); - } - - public async getStagedNetworkState(): Promise { - const result = await this.unprovenQueue.getLatestBlock(); - return result?.result.afterNetworkState; - } - public async removeTxs(included: string[], dropped: string[]) { await this.transactionStorage.removeTx(included, "included"); await this.transactionStorage.removeTx(dropped, "dropped"); } @trace("mempool.get_txs") - public async getTxs(limit?: number): Promise { - // TODO Add limit to the storage (or do something smarter entirely) - const txs = await this.transactionStorage.getPendingUserTransactions(); - - const baseCachedStateService = new CachedStateService(this.stateService); - - const networkState = - (await this.getStagedNetworkState()) ?? NetworkState.empty(); - - const validationEnabled = this.config.validationEnabled ?? false; - const sortedTxs = validationEnabled - ? await this.checkTxValid( - txs, - baseCachedStateService, - this.protocol.stateServiceProvider, - networkState, - limit - ) - : txs.slice(0, limit); - - this.protocol.stateServiceProvider.popCurrentStateService(); - return sortedTxs; - } - - // We iterate through the transactions. For each tx we run the account state hook. - // If the txs succeeds then it can be returned. If it fails then we keep track of it - // in the skipped txs list and when later txs succeed we check to see if any state transition - // paths are shared between the just succeeded tx and any of the skipped txs. This is - // because a failed tx may succeed now if the failure was to do with a nonce issue, say. - // TODO Refactor - @trace("mempool.validate_txs") - // eslint-disable-next-line sonarjs/cognitive-complexity - private async checkTxValid( - transactions: PendingTransaction[], - baseService: CachedStateService, - stateServiceProvider: StateServiceProvider, - networkState: NetworkState, + public async getTxs( + offset: number, limit?: number - ) { - const executionContext = container.resolve( - RuntimeMethodExecutionContext + ): Promise { + return await this.transactionStorage.getPendingUserTransactions( + offset, + limit ); - executionContext.clear(); - - // Initialize starting state - const sortedTransactions: PendingTransaction[] = []; - const skippedTransactions: Record = {}; - - let queue: PendingTransaction[] = [...transactions]; - - const previousBlock = await this.unprovenQueue.getLatestBlock(); - - // TODO This is not sound currently as the prover state changes all the time - // in the actual blockprover. We need to properly simulate that - const proverState: ProvableHookTransactionState = { - eternalTransactionsHash: - previousBlock?.block.toEternalTransactionsHash ?? Field(0), - transactionsHash: previousBlock?.block.transactionsHash ?? Field(0), - incomingMessagesHash: previousBlock?.block.toMessagesHash ?? Field(0), - }; - - while ( - queue.length > 0 && - sortedTransactions.length < (limit ?? Number.MAX_VALUE) - ) { - const [tx] = queue.splice(0, 1); - const txStateService = new CachedStateService(baseService); - stateServiceProvider.setCurrentStateService(txStateService); - const contextInputs: RuntimeMethodExecutionData = { - networkState: networkState, - transaction: tx.toProtocolTransaction().transaction, - }; - executionContext.setup(contextInputs); - - const signedTransaction = tx.toProtocolTransaction(); - - // eslint-disable-next-line no-await-in-loop - await this.accountStateHook.beforeTransaction({ - networkState: networkState, - transaction: signedTransaction.transaction, - signature: signedTransaction.signature, - prover: proverState, - }); - const { status, statusMessage, stateTransitions } = - executionContext.current().result; - - if (status.toBoolean()) { - log.trace(`Accepted tx ${tx.hash().toString()}`); - sortedTransactions.push(tx); - // eslint-disable-next-line no-await-in-loop - await txStateService.applyStateTransitions(stateTransitions); - // eslint-disable-next-line no-await-in-loop - await txStateService.mergeIntoParent(); - delete skippedTransactions[tx.hash().toString()]; - if (Object.entries(skippedTransactions).length > 0) { - // eslint-disable-next-line @typescript-eslint/no-loop-func - stateTransitions.forEach((st) => { - Object.values(skippedTransactions).forEach((value) => { - if (value.paths.some((x) => x.equals(st.path))) { - queue.push(value.transaction); - } - }); - }); - queue = queue.filter(distinctByPredicate((a, b) => a === b)); - } - } else { - // eslint-disable-next-line no-await-in-loop - const removeTxWhen = await this.accountStateHook.removeTransactionWhen({ - networkState: networkState, - transaction: signedTransaction.transaction, - signature: signedTransaction.signature, - prover: proverState, - }); - if (removeTxWhen) { - // eslint-disable-next-line no-await-in-loop - await this.transactionStorage.removeTx( - [tx.hash().toString()], - "dropped" - ); - log.trace( - `Deleting tx ${tx.hash().toString()} from mempool because removeTransactionWhen condition is satisfied` - ); - // eslint-disable-next-line no-continue - continue; - } - - log.trace( - `Skipped tx ${tx.hash().toString()} because ${statusMessage}` - ); - if (!(tx.hash().toString() in skippedTransactions)) { - skippedTransactions[tx.hash().toString()] = { - transaction: tx, - paths: stateTransitions - .map((x) => x.path) - .filter((id, idx, arr) => arr.indexOf(id) === idx), - }; - } - stateServiceProvider.popCurrentStateService(); - } + } - executionContext.clear(); - } - return sortedTransactions; + @trace("mempool.get_mandatory_txs") + public async getMandatoryTxs(): Promise { + return (await this.messageService?.getPendingMessages()) ?? []; } public async start(): Promise { diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts new file mode 100644 index 000000000..f72c251a2 --- /dev/null +++ b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts @@ -0,0 +1,149 @@ +import { inject, injectable } from "tsyringe"; +import { + BeforeTransactionHookArguments, + MandatoryProtocolModulesRecord, + NetworkState, + Protocol, + ProtocolModulesRecord, + ProvableTransactionHook, + StateServiceProvider, + toBeforeTransactionHookArgument, +} from "@proto-kit/protocol"; +import { log, mapSequential } from "@proto-kit/common"; + +import { Mempool } from "../../../mempool/Mempool"; +import { CachedStateService } from "../../../state/state/CachedStateService"; +import { PendingTransaction } from "../../../mempool/PendingTransaction"; +import { Tracer } from "../../../logging/Tracer"; +import { trace } from "../../../logging/trace"; + +import { + BlockTrackers, + TransactionExecutionResultStatus, + TransactionExecutionService, +} from "./TransactionExecutionService"; +import { Ordering } from "./Ordering"; + +// TODO Allow user overriding of the blockbuilder +@injectable() +export class BlockBuilder { + private readonly transactionHooks: ProvableTransactionHook[]; + + public constructor( + private readonly executionService: TransactionExecutionService, + @inject("Mempool") private readonly mempool: Mempool, + @inject("StateServiceProvider") + private readonly stateServiceProvider: StateServiceProvider, + @inject("Protocol") + protocol: Protocol, + @inject("Tracer") public readonly tracer: Tracer + ) { + this.transactionHooks = protocol.dependencyContainer.resolveAll( + "ProvableTransactionHook" + ); + } + + private async shouldRemove( + state: CachedStateService, + args: BeforeTransactionHookArguments + ) { + this.stateServiceProvider.setCurrentStateService(state); + + const returnValues = await mapSequential(this.transactionHooks, (hook) => + hook.removeTransactionWhen(args) + ); + + this.stateServiceProvider.popCurrentStateService(); + return returnValues.some((x) => x); + } + + @trace("block.build") + // eslint-disable-next-line sonarjs/cognitive-complexity + public async buildBlock( + asyncStateService: CachedStateService, + networkState: NetworkState, + state: BlockTrackers, + maximumBlockSize: number + ): Promise<{ + blockState: BlockTrackers; + executionResults: TransactionExecutionResultStatus[]; + }> { + let blockState = state; + const exceptionExecutionResults: TransactionExecutionResultStatus[] = []; + + const networkStateHash = networkState.hash(); + + const ordering = new Ordering(this.mempool, maximumBlockSize); + + let tx: PendingTransaction | undefined; + // eslint-disable-next-line no-await-in-loop,no-cond-assign + while ((tx = await ordering.requestNextTransaction()) !== undefined) { + try { + const newState = this.executionService.addTransactionToBlockProverState( + blockState, + tx + ); + + // TODO Use RecordingStateService -> async asProver needed + const recordingStateService = new CachedStateService(asyncStateService); + + // Create execution trace + const executionTrace = + // eslint-disable-next-line no-await-in-loop + await this.executionService.createExecutionTrace( + recordingStateService, + tx, + { networkState, hash: networkStateHash }, + blockState, + newState + ); + + const transactionIncluded = + executionTrace.hooksStatus.toBoolean() || executionTrace.tx.isMessage; + + let shouldRemove = false; + if (transactionIncluded) { + // eslint-disable-next-line no-await-in-loop + await recordingStateService.mergeIntoParent(); + + // Only for successful hooks, messages will be included but progress thrown away + if (executionTrace.hooksStatus.toBoolean()) { + blockState = newState; + } + } else { + // Execute removeWhen to determine whether it should be dropped + // eslint-disable-next-line no-await-in-loop + shouldRemove = await this.shouldRemove( + asyncStateService, + toBeforeTransactionHookArgument( + tx.toProtocolTransaction(), + networkState, + blockState + ) + ); + + const actionMessage = shouldRemove + ? "removing as to removeWhen hooks" + : "skipping"; + log.error( + `Error in inclusion of tx, ${actionMessage}: Protocol hooks not executable: ${executionTrace.statusMessage ?? "unknown reason"}` + ); + } + + ordering.reportResult({ result: executionTrace, shouldRemove }); + } catch (error) { + if (error instanceof Error) { + log.error("Error in inclusion of tx, dropping", error); + exceptionExecutionResults.push({ tx, status: "shouldRemove" }); + } + } + } + + const orderingResults = ordering.getResults(); + + return { + blockState, + executionResults: orderingResults.concat(...exceptionExecutionResults), + }; + } +} diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 8f035c338..e868058bf 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -14,7 +14,6 @@ import { SequencerModule, } from "../../../sequencer/builder/SequencerModule"; import { BlockQueue } from "../../../storage/repositories/BlockStorage"; -import { PendingTransaction } from "../../../mempool/PendingTransaction"; import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; import { AsyncStateService } from "../../../state/async/AsyncStateService"; import { @@ -23,7 +22,6 @@ import { BlockWithResult, } from "../../../storage/model/Block"; import { Database } from "../../../storage/Database"; -import { IncomingMessagesService } from "../../../settlement/messages/IncomingMessagesService"; import { Tracer } from "../../../logging/Tracer"; import { trace } from "../../../logging/trace"; import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; @@ -42,8 +40,6 @@ export class BlockProducerModule extends SequencerModule { public constructor( @inject("Mempool") private readonly mempool: Mempool, - @inject("IncomingMessagesService", { isOptional: true }) - private readonly messageService: IncomingMessagesService | undefined, @inject("UnprovenStateService") private readonly unprovenStateService: AsyncStateService, @inject("UnprovenLinkedLeafStore") @@ -176,12 +172,7 @@ export class BlockProducerModule extends SequencerModule { // TODO Move to different service, to remove dependency on mempool and messagequeue // Idea: Create a service that aggregates a bunch of different sources @trace("block.collect_inputs") - private async collectProductionData(): Promise<{ - txs: PendingTransaction[]; - metadata: BlockWithResult; - }> { - const txs = await this.mempool.getTxs(this.maximumBlockSize()); - + private async collectProductionData(): Promise { const parentBlock = await this.blockQueue.getLatestBlockAndResult(); let metadata: BlockWithResult; @@ -203,42 +194,30 @@ export class BlockProducerModule extends SequencerModule { }; } - let messages: PendingTransaction[] = []; - if (this.messageService !== undefined) { - messages = await this.messageService.getPendingMessages(); - } - - log.debug( - `Block collected, ${txs.length} txs, ${messages.length} messages` - ); - - return { - txs: messages.concat(txs), - metadata, - }; + return metadata; } @trace("block") private async produceBlock(): Promise { this.productionInProgress = true; - const { txs, metadata } = await this.collectProductionData(); - - // Skip production if no transactions are available for now - if (txs.length === 0 && !this.allowEmptyBlock()) { - return undefined; - } + const metadata = await this.collectProductionData(); const blockResult = await this.productionService.createBlock( this.unprovenStateService, - txs, metadata, - this.allowEmptyBlock() + this.allowEmptyBlock(), + this.maximumBlockSize() ); if (blockResult !== undefined) { const { block, stateChanges } = blockResult; + // Skip production if no transactions are available for now + if (block.transactions.length === 0 && !this.allowEmptyBlock()) { + return undefined; + } + await this.tracer.trace( "block.commit", async () => diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts index f9e57eb16..852b5eb40 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts @@ -33,12 +33,14 @@ import { BlockTrackers, executeWithExecutionContext, TransactionExecutionResultStatus, - TransactionExecutionService, } from "./TransactionExecutionService"; +import { BlockBuilder } from "./BlockBuilder"; -function isIncludedTxs( - x: TransactionExecutionResultStatus -): x is { status: "included"; result: TransactionExecutionResult } { +function isIncludedTxs(x: TransactionExecutionResultStatus): x is { + status: "included"; + tx: PendingTransaction; + result: TransactionExecutionResult; +} { return x.status === "included"; } @@ -52,7 +54,7 @@ export class BlockProductionService { protocol: Protocol, @inject("Tracer") public readonly tracer: Tracer, - private readonly transactionExecutionService: TransactionExecutionService, + private readonly blockBuilder: BlockBuilder, @inject("StateServiceProvider") private readonly stateServiceProvider: StateServiceProvider ) { @@ -98,9 +100,9 @@ export class BlockProductionService { */ public async createBlock( asyncStateService: AsyncStateService, - transactions: PendingTransaction[], lastBlockWithResult: BlockWithResult, - allowEmptyBlocks: boolean + allowEmptyBlocks: boolean, + maximumBlockSize: number ): Promise< | { block: Block; @@ -141,11 +143,11 @@ export class BlockProductionService { ); const { blockState: newBlockState, executionResults } = - await this.transactionExecutionService.createExecutionTraces( + await this.blockBuilder.buildBlock( stateService, - transactions, networkState, - blockState + blockState, + maximumBlockSize ); const previousBlockHash = diff --git a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts new file mode 100644 index 000000000..cb6d20f65 --- /dev/null +++ b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts @@ -0,0 +1,134 @@ +import { Field } from "o1js"; +import { filterNonUndefined } from "@proto-kit/common"; + +import { distinct, distinctByPredicate } from "../../../helpers/utils"; +import { Mempool } from "../../../mempool/Mempool"; +import { PendingTransaction } from "../../../mempool/PendingTransaction"; +import { TransactionExecutionResult } from "../../../storage/model/Block"; + +import { TransactionExecutionResultStatus } from "./TransactionExecutionService"; + +function allKeys(stateTransitions: { path: Field }[]): bigint[] { + // We have to do the distinct with strings because + // array.indexOf() doesn't work with fields + return stateTransitions.map((st) => st.path.toBigInt()).filter(distinct); +} + +export type OrderingReport = { + result: TransactionExecutionResult; + shouldRemove: boolean; +}; + +export class Ordering { + public constructor( + private readonly mempool: Mempool, + private sizeLimit: number + ) {} + + mandatoryTransactionsCompleted = false; + + transactionQueue: PendingTransaction[] = []; + + results: TransactionExecutionResultStatus[] = []; + + ordered = 0; + + userTxOffset = 0; + + // For dependency resolution + failedTxIds = new Map(); + + paths = new Map(); + + public resolvePaths(result: TransactionExecutionResult) { + const keys = allKeys(result.stateTransitions[0].stateTransitions); + + const allSymbols = keys.flatMap((key) => { + const symbols = this.paths.get(key); + if (symbols !== undefined) { + this.paths.delete(key); + } + return symbols ?? []; + }); + + const txs = allSymbols + .map((symbol) => { + const tx = this.failedTxIds.get(symbol); + this.failedTxIds.delete(symbol); + return tx; + }) + .filter(filterNonUndefined); + + this.transactionQueue.push(...txs); + } + + private pushFailed(result: TransactionExecutionResult) { + const symbol = Symbol("tx"); + this.failedTxIds.set(symbol, result.tx); + + const keys = allKeys(result.stateTransitions[0].stateTransitions); + keys.forEach((key) => { + const symbols = this.paths.get(key) ?? []; + symbols.push(symbol); + this.paths.set(key, symbols); + }); + } + + public reportResult({ result, shouldRemove }: OrderingReport) { + if (result.hooksStatus.toBoolean() || result.tx.isMessage) { + // Included + this.ordered += 1; + this.userTxOffset += result.tx.isMessage ? 0 : 1; + this.results.push({ + status: "included", + result, + tx: result.tx, + }); + this.resolvePaths(result); + } else if (shouldRemove) { + // Dropped + this.results.push({ + status: "shouldRemove", + tx: result.tx, + }); + } else { + // Might become valid + this.results.push({ + status: "skipped", + tx: result.tx, + }); + this.pushFailed(result); + } + } + + public async requestNextTransaction() { + if (this.transactionQueue.length === 0) { + // Fetch messages + if (!this.mandatoryTransactionsCompleted) { + const mandos = await this.mempool.getMandatoryTxs(); + this.transactionQueue.push(...mandos); + this.mandatoryTransactionsCompleted = true; + } + + // Fetch as much txs as space is available + const space = this.sizeLimit - this.ordered; + if (space > 0) { + const newTxs = await this.mempool.getTxs(this.userTxOffset, space); + this.transactionQueue.push(...newTxs); + } + } + + return this.transactionQueue.pop(); + } + + public getResults() { + return this.results + .reverse() + .filter( + distinctByPredicate( + (x, y) => x.tx.hash().toBigInt() === y.tx.hash().toBigInt() + ) + ) + .reverse(); + } +} diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index e3dc6ecdb..fe832b1d5 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -25,7 +25,7 @@ import { TransactionProverState, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; -import { AreProofsEnabled, log, mapSequential } from "@proto-kit/common"; +import { log, mapSequential } from "@proto-kit/common"; import { MethodParameterEncoder, Runtime, @@ -64,19 +64,6 @@ export type BlockTrackers = Pick< > & Pick; -function getAreProofsEnabledFromModule( - module: RuntimeModule -): AreProofsEnabled { - if (module.parent === undefined) { - throw new Error("Runtime on RuntimeModule not set"); - } - if (module.parent.areProofsEnabled === undefined) { - throw new Error("AppChain on Runtime not set"); - } - const { areProofsEnabled } = module.parent; - return areProofsEnabled; -} - async function decodeTransaction( tx: PendingTransaction, runtime: Runtime @@ -112,14 +99,14 @@ async function decodeTransaction( } function extractEvents( - runtimeResult: RuntimeContextReducedExecutionResult, + events: RuntimeContextReducedExecutionResult["events"], source: "afterTxHook" | "beforeTxHook" | "runtime" ): { eventName: string; data: Field[]; source: "afterTxHook" | "beforeTxHook" | "runtime"; }[] { - return runtimeResult.events.reduce( + return events.reduce( (acc, event) => { if (event.condition.toBoolean()) { const obj = { @@ -198,6 +185,8 @@ function traceLogSTs(msg: string, stateTransitions: StateTransition[]) { export type TransactionExecutionResultStatus = | { result: TransactionExecutionResult; + // Just for convenience + tx: PendingTransaction; status: "included"; } | { tx: PendingTransaction; status: "skipped" } @@ -208,8 +197,6 @@ export type TransactionExecutionResultStatus = export class TransactionExecutionService { private readonly transactionHooks: ProvableTransactionHook[]; - private readonly txHooks: ProvableTransactionHook[]; - public constructor( @inject("Runtime") private readonly runtime: Runtime, @inject("Protocol") @@ -223,11 +210,6 @@ export class TransactionExecutionService { this.transactionHooks = protocol.dependencyContainer.resolveAll( "ProvableTransactionHook" ); - - this.txHooks = - protocol.dependencyContainer.resolveAll( - "ProvableTransactionHook" - ); } private async executeRuntimeMethod( @@ -321,83 +303,6 @@ export class TransactionExecutionService { ); } - // eslint-disable-next-line sonarjs/cognitive-complexity - public async createExecutionTraces( - asyncStateService: CachedStateService, - transactions: PendingTransaction[], - networkState: NetworkState, - state: BlockTrackers - ): Promise<{ - blockState: BlockTrackers; - executionResults: TransactionExecutionResultStatus[]; - }> { - let blockState = state; - const executionResults: TransactionExecutionResultStatus[] = []; - - const networkStateHash = networkState.hash(); - - for (const tx of transactions) { - try { - const newState = this.addTransactionToBlockProverState(blockState, tx); - - // Create execution trace - const { result: executionTrace, shouldRemove } = - // eslint-disable-next-line no-await-in-loop - await this.createExecutionTrace( - asyncStateService, - tx, - { networkState, hash: networkStateHash }, - blockState, - newState - ); - - // If the hooks fail AND the tx is not a message (in which case we - // have to still execute it), we skip this tx and don't add it to the block - if ( - !executionTrace.hooksStatus.toBoolean() && - !executionTrace.tx.isMessage - ) { - const actionMessage = shouldRemove - ? "removing as to removeWhen hooks" - : "skipping"; - log.error( - `Error in inclusion of tx, ${actionMessage}: Protocol hooks not executable: ${executionTrace.statusMessage ?? "unknown reason"}` - ); - executionResults.push({ - tx, - status: shouldRemove ? "shouldRemove" : "skipped", - }); - } else { - blockState = newState; - - // Push result to results and transaction onto bundle-hash - executionResults.push({ result: executionTrace, status: "included" }); - } - } catch (error) { - if (error instanceof Error) { - log.error("Error in inclusion of tx, dropping", error); - executionResults.push({ tx, status: "shouldRemove" }); - } - } - } - - return { blockState, executionResults }; - } - - private async shouldRemove( - state: CachedStateService, - args: BeforeTransactionHookArguments - ) { - this.stateServiceProvider.setCurrentStateService(state); - - const returnValues = await mapSequential(this.transactionHooks, (hook) => - hook.removeTransactionWhen(args) - ); - - this.stateServiceProvider.popCurrentStateService(); - return returnValues.some((x) => x); - } - @trace("block.transaction", ([, tx, { networkState }]) => ({ height: networkState.block.height.toString(), methodId: tx.methodId.toString(), @@ -412,28 +317,13 @@ export class TransactionExecutionService { }: { networkState: NetworkState; hash: Field }, state: BlockTrackers, newState: BlockTrackers - ): Promise<{ result: TransactionExecutionResult; shouldRemove: boolean }> { - // TODO Use RecordingStateService -> async asProver needed - const recordingStateService = new CachedStateService(asyncStateService); - - const { method, args, module } = await decodeTransaction(tx, this.runtime); - - // Disable proof generation for sequencing the runtime - // TODO Is that even needed? - const appChain = getAreProofsEnabledFromModule(module); - const previousProofsEnabled = appChain.areProofsEnabled; - appChain.setProofsEnabled(false); - + ): Promise { const signedTransaction = tx.toProtocolTransaction(); - const runtimeContextInputs = { - transaction: signedTransaction.transaction, - networkState, - }; // The following steps generate and apply the correct STs with the right values - this.stateServiceProvider.setCurrentStateService(recordingStateService); + this.stateServiceProvider.setCurrentStateService(asyncStateService); - // Execute beforeTransaction hooks + // 1. beforeTransaction hooks const beforeTxArguments = toBeforeTransactionHookArgument( signedTransaction, networkState, @@ -450,12 +340,22 @@ export class TransactionExecutionService { "beforeTx" ) ); - const beforeHookEvents = extractEvents(beforeTxHookResult, "beforeTxHook"); + const beforeHookEvents = extractEvents( + beforeTxHookResult.events, + "beforeTxHook" + ); - await recordingStateService.applyStateTransitions( + await asyncStateService.applyStateTransitions( beforeTxHookResult.stateTransitions ); + // 2. Runtime + const { method, args } = await decodeTransaction(tx, this.runtime); + const runtimeContextInputs = { + transaction: signedTransaction.transaction, + networkState, + }; + const runtimeResult = await this.tracer.trace( "block.transaction.execute", () => this.executeRuntimeMethod(method, args, runtimeContextInputs) @@ -465,7 +365,7 @@ export class TransactionExecutionService { // Apply runtime STs (only if the tx succeeded) if (runtimeResult.status.toBoolean()) { // Apply protocol STs - await recordingStateService.applyStateTransitions( + await asyncStateService.applyStateTransitions( runtimeResult.stateTransitions ); } @@ -475,7 +375,7 @@ export class TransactionExecutionService { runtimeResult.stateTransitions ); - // Execute afterTransaction hook + // 3. afterTransaction hook const afterTxArguments = toAfterTransactionHookArgument( signedTransaction, networkState, @@ -499,33 +399,23 @@ export class TransactionExecutionService { "afterTx" ) ); - const afterHookEvents = extractEvents(afterTxHookResult, "afterTxHook"); - await recordingStateService.applyStateTransitions( + const afterHookEvents = extractEvents( + afterTxHookResult.events, + "afterTxHook" + ); + await asyncStateService.applyStateTransitions( afterTxHookResult.stateTransitions ); const txHooksValid = beforeTxHookResult.status.toBoolean() && afterTxHookResult.status.toBoolean(); - let shouldRemove = false; - if (txHooksValid) { - await recordingStateService.mergeIntoParent(); - } else { - // Execute removeWhen to determine whether it should be dropped - shouldRemove = await this.shouldRemove( - asyncStateService, - beforeTxArguments - ); - } // Reset global stateservice this.stateServiceProvider.popCurrentStateService(); - // Reset proofs enabled - appChain.setProofsEnabled(previousProofsEnabled); - // Extract sequencing results - const runtimeResultEvents = extractEvents(runtimeResult, "runtime"); + const runtimeResultEvents = extractEvents(runtimeResult.events, "runtime"); const stateTransitions = this.buildSTBatches( [ beforeTxHookResult.stateTransitions, @@ -536,19 +426,16 @@ export class TransactionExecutionService { ); return { - result: { - tx, - hooksStatus: Bool(txHooksValid), - status: runtimeResult.status, - statusMessage: - beforeTxHookResult.statusMessage ?? - afterTxHookResult.statusMessage ?? - runtimeResult.statusMessage, - - stateTransitions, - events: beforeHookEvents.concat(runtimeResultEvents, afterHookEvents), - }, - shouldRemove, + tx, + hooksStatus: Bool(txHooksValid), + status: runtimeResult.status, + statusMessage: + beforeTxHookResult.statusMessage ?? + afterTxHookResult.statusMessage ?? + runtimeResult.statusMessage, + + stateTransitions, + events: beforeHookEvents.concat(runtimeResultEvents, afterHookEvents), }; } } diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index 73b630be1..aa7c6fe69 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -143,7 +143,7 @@ export class TimedBlockTrigger private async produceUnprovenBlock() { // TODO Optimize towards mempool.length() - const mempoolTxs = await this.mempool.getTxs(); + const mempoolTxs = await this.mempool.getTxs(0); // Produce a block if either produceEmptyBlocks is true or we have more // than 1 tx in mempool if (mempoolTxs.length > 0 || (this.config.produceEmptyBlocks ?? true)) { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts index df0fe7fd7..10cadc0c9 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts @@ -27,7 +27,10 @@ export class InMemoryTransactionStorage implements TransactionStorage { }); } - public async getPendingUserTransactions(): Promise { + public async getPendingUserTransactions( + offset: number, + limit?: number + ): Promise { const nextHeight = await this.blockStorage.getCurrentBlockHeight(); for ( let height = this.latestScannedBlock + 1; @@ -45,7 +48,10 @@ export class InMemoryTransactionStorage implements TransactionStorage { } this.latestScannedBlock = nextHeight - 1; - return this.queue.slice(); + const from = offset ?? 0; + const to = limit !== undefined ? from + limit : undefined; + + return this.queue.slice(from, to); } public async pushUserTransaction(tx: PendingTransaction): Promise { @@ -83,7 +89,7 @@ export class InMemoryTransactionStorage implements TransactionStorage { } | undefined > { - const pending = await this.getPendingUserTransactions(); + const pending = await this.getPendingUserTransactions(0); const pendingResult = pending.find((tx) => tx.hash().toString() === hash); if (pendingResult !== undefined) { return { diff --git a/packages/sequencer/src/storage/repositories/TransactionStorage.ts b/packages/sequencer/src/storage/repositories/TransactionStorage.ts index ef353ad08..522d63eb5 100644 --- a/packages/sequencer/src/storage/repositories/TransactionStorage.ts +++ b/packages/sequencer/src/storage/repositories/TransactionStorage.ts @@ -3,7 +3,10 @@ import { PendingTransaction } from "../../mempool/PendingTransaction"; export interface TransactionStorage { pushUserTransaction: (tx: PendingTransaction) => Promise; - getPendingUserTransactions: () => Promise; + getPendingUserTransactions: ( + offset: number, + limit?: number + ) => Promise; removeTx: (txHashes: string[], type: "included" | "dropped") => Promise; From b32608422adf99f137cdfefb8e17e91c33fe5105 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 16:04:29 +0100 Subject: [PATCH 104/155] Fixed bug inside block production --- .../production/flow/ReductionTaskFlow.ts | 2 +- .../production/sequencing/BlockBuilder.ts | 10 +++++----- .../production/sequencing/Ordering.ts | 2 +- .../sequencing/TransactionExecutionService.ts | 20 +++++++++++++++++++ .../test/integration/BlockProduction-test.ts | 12 ++++++----- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts index 1ae3ec92b..e4d950ad5 100644 --- a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts @@ -176,7 +176,7 @@ export class ReductionTaskFlow { /** * To be used in conjunction with onCompletion - * It allows errors from this flow to be "defered" to another parent + * It allows errors from this flow to be "deferred" to another parent * flow which might be properly awaited and therefore will throw the * error up to the user * @param flow diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts index f72c251a2..b3f535452 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts @@ -80,7 +80,7 @@ export class BlockBuilder { while ((tx = await ordering.requestNextTransaction()) !== undefined) { try { const newState = this.executionService.addTransactionToBlockProverState( - blockState, + BlockTrackers.clone(blockState), tx ); @@ -103,18 +103,18 @@ export class BlockBuilder { let shouldRemove = false; if (transactionIncluded) { - // eslint-disable-next-line no-await-in-loop - await recordingStateService.mergeIntoParent(); + blockState = newState; // Only for successful hooks, messages will be included but progress thrown away if (executionTrace.hooksStatus.toBoolean()) { - blockState = newState; + // eslint-disable-next-line no-await-in-loop + await recordingStateService.mergeIntoParent(); } } else { // Execute removeWhen to determine whether it should be dropped // eslint-disable-next-line no-await-in-loop shouldRemove = await this.shouldRemove( - asyncStateService, + new CachedStateService(asyncStateService), toBeforeTransactionHookArgument( tx.toProtocolTransaction(), networkState, diff --git a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts index cb6d20f65..3d63ce748 100644 --- a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts +++ b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts @@ -118,7 +118,7 @@ export class Ordering { } } - return this.transactionQueue.pop(); + return this.transactionQueue.shift(); } public getResults() { diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index fe832b1d5..9bd545ac8 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -23,6 +23,8 @@ import { DefaultProvableHashList, addTransactionToBundle, TransactionProverState, + TransactionHashList, + MinaActionsHashList, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { log, mapSequential } from "@proto-kit/common"; @@ -64,6 +66,24 @@ export type BlockTrackers = Pick< > & Pick; +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const BlockTrackers = { + clone: (trackers: BlockTrackers) => { + return { + eternalTransactionsList: new TransactionHashList( + trackers.eternalTransactionsList.commitment + ), + transactionList: new TransactionHashList( + trackers.transactionList.commitment + ), + incomingMessages: new MinaActionsHashList( + trackers.incomingMessages.commitment + ), + blockHashRoot: trackers.blockHashRoot, + } satisfies BlockTrackers; + }, +}; + async function decodeTransaction( tx: PendingTransaction, runtime: Runtime diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 62f29565c..06f6c6885 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -546,13 +546,15 @@ export function testBlockProduction< async (batches, blocksPerBatch, txsPerBlock) => { expect.assertions( 2 * batches + - 1 * batches * blocksPerBatch + - 2 * batches * blocksPerBatch * txsPerBlock + 2 * batches * blocksPerBatch + + 1 * batches * blocksPerBatch * txsPerBlock ); log.setLevel("DEBUG"); - const sender = PrivateKey.random(); + const sender = PrivateKey.fromBase58( + "EKEiL7J4ouZGAz8uHo3oUebfA8zTWYYwLsojTyK9cAafi9sBBRpN" + ); const keys = range(0, batches * blocksPerBatch * txsPerBlock).map(() => PrivateKey.random() @@ -582,9 +584,9 @@ export function testBlockProduction< expect(block).toBeDefined(); + expect(block!.transactions).toHaveLength(txsPerBlock); for (let k = 0; k < txsPerBlock; k++) { - expect(block!.transactions).toHaveLength(txsPerBlock); - expect(block!.transactions[0].status.toBoolean()).toBe(true); + expect(block!.transactions[k].status.toBoolean()).toBe(true); } } From 3801eefb3259a06e7aa4b4d119da4cc737279356 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 16:27:06 +0100 Subject: [PATCH 105/155] Small nit for the BlockProducerModule --- .../sequencing/BlockProducerModule.ts | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index e868058bf..704116e59 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -169,8 +169,6 @@ export class BlockProducerModule extends SequencerModule { return undefined; } - // TODO Move to different service, to remove dependency on mempool and messagequeue - // Idea: Create a service that aggregates a bunch of different sources @trace("block.collect_inputs") private async collectProductionData(): Promise { const parentBlock = await this.blockQueue.getLatestBlockAndResult(); @@ -220,26 +218,27 @@ export class BlockProducerModule extends SequencerModule { await this.tracer.trace( "block.commit", - async () => + async () => { // Push changes to the database atomically await this.database.executeInTransaction(async () => { await stateChanges.mergeIntoParent(); await this.blockQueue.pushBlock(block); - }), + + // Remove included or dropped txs, leave skipped ones alone + await this.mempool.removeTxs( + blockResult.includedTxs + .filter((x) => x.type === "included") + .map((x) => x.hash), + blockResult.includedTxs + .filter((x) => x.type === "shouldRemove") + .map((x) => x.hash) + ); + }); + }, { height: block.height.toString(), } ); - - // Remove included or dropped txs, leave skipped ones alone - await this.mempool.removeTxs( - blockResult.includedTxs - .filter((x) => x.type === "included") - .map((x) => x.hash), - blockResult.includedTxs - .filter((x) => x.type === "shouldRemove") - .map((x) => x.hash) - ); } this.productionInProgress = false; From 5cb8354fd17d307c34125cf255d53e6261c66607 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 16:51:50 +0100 Subject: [PATCH 106/155] Fixed build error in mempool resolver --- packages/api/src/graphql/modules/MempoolResolver.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/api/src/graphql/modules/MempoolResolver.ts b/packages/api/src/graphql/modules/MempoolResolver.ts index 034eee651..0ea9c1a0d 100644 --- a/packages/api/src/graphql/modules/MempoolResolver.ts +++ b/packages/api/src/graphql/modules/MempoolResolver.ts @@ -161,7 +161,10 @@ export class MempoolResolver extends GraphqlModule { "Returns the hashes of all transactions that are currently inside the mempool", }) public async transactions() { - const txs = await this.transactionStorage.getPendingUserTransactions(); + const txs = await this.transactionStorage.getPendingUserTransactions( + 0, + 1000 + ); return txs.map((x) => x.hash().toString()); } } From 931ff0bc4b9e537516db4bf46cf1b1489e908bcc Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 17:56:51 +0100 Subject: [PATCH 107/155] Implemented and integrated MempoolSorting interface --- .../src/mempool/private/PrivateMempool.ts | 27 ++++++++++++++++-- .../src/mempool/sorting/MempoolSorting.ts | 28 +++++++++++++++++++ .../inmemory/InMemoryTransactionStorage.ts | 24 +++++++++++----- .../repositories/TransactionStorage.ts | 5 +++- 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 packages/sequencer/src/mempool/sorting/MempoolSorting.ts diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index 71469946b..448b9bd9e 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -12,20 +12,27 @@ import { TransactionValidator } from "../verification/TransactionValidator"; import { Tracer } from "../../logging/Tracer"; import { trace } from "../../logging/trace"; import { IncomingMessagesService } from "../../settlement/messages/IncomingMessagesService"; +import { MempoolSorting } from "../sorting/MempoolSorting"; +import { DefaultMempoolSorting } from "../sorting/DefaultMempoolSorting"; @sequencerModule() export class PrivateMempool extends SequencerModule implements Mempool { public readonly events = new EventEmitter(); + private readonly mempoolSorting: MempoolSorting; + public constructor( private readonly transactionValidator: TransactionValidator, @inject("TransactionStorage") private readonly transactionStorage: TransactionStorage, @inject("IncomingMessagesService", { isOptional: true }) private readonly messageService: IncomingMessagesService | undefined, - @inject("Tracer") public readonly tracer: Tracer + @inject("Tracer") public readonly tracer: Tracer, + @inject("MempoolSorting", { isOptional: true }) + mempoolSorting: MempoolSorting | undefined ) { super(); + this.mempoolSorting = mempoolSorting ?? new DefaultMempoolSorting(); } public async length(): Promise { @@ -36,7 +43,12 @@ export class PrivateMempool extends SequencerModule implements Mempool { public async add(tx: PendingTransaction): Promise { const [txValid, error] = this.transactionValidator.validateTx(tx); if (txValid) { - const success = await this.transactionStorage.pushUserTransaction(tx); + const sortingValue = this.mempoolSorting!.presortingPriority(tx); + + const success = await this.transactionStorage.pushUserTransaction( + tx, + sortingValue + ); if (success) { this.events.emit("mempool-transaction-added", tx); log.trace(`Transaction added to mempool: ${tx.hash().toString()}`); @@ -69,10 +81,19 @@ export class PrivateMempool extends SequencerModule implements Mempool { offset: number, limit?: number ): Promise { - return await this.transactionStorage.getPendingUserTransactions( + const txs = await this.transactionStorage.getPendingUserTransactions( offset, limit ); + + if (this.mempoolSorting.enablePostSorting()) { + // Sorts in place + txs.sort( + this.mempoolSorting.presortingPriority.bind(this.mempoolSorting) + ); + } + + return txs; } @trace("mempool.get_mandatory_txs") diff --git a/packages/sequencer/src/mempool/sorting/MempoolSorting.ts b/packages/sequencer/src/mempool/sorting/MempoolSorting.ts new file mode 100644 index 000000000..9c74eafb0 --- /dev/null +++ b/packages/sequencer/src/mempool/sorting/MempoolSorting.ts @@ -0,0 +1,28 @@ +import { PendingTransaction } from "../PendingTransaction"; + +export interface MempoolSorting { + /** + * Presorting happens on the backend (i.e. the DB), before the data travels to the sequencer. + * It's very fast, but limited to only integer sorting. + * The value returned here has to be static per transaction, since it will be sorted and + * compared on the DB-side. + * + * @param tx + * @returns Priority of the transaction - larger is better (therefore will be + * put in the block first) + */ + presortingPriority(tx: PendingTransaction): number; + + /** + * Indicate whether to do pre-sorting (as it's expensive depending on your block size) + */ + enablePostSorting(): boolean; + + /** + * Postsorting happens on the sequencer-side. It's less fast but can take in any two + * transactions and directly compare them based on arbitrary logic + * @param a + * @param b + */ + postSorting(a: PendingTransaction, b: PendingTransaction): number; +} diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts index 10cadc0c9..90732770c 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts @@ -9,7 +9,7 @@ import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; @injectable() export class InMemoryTransactionStorage implements TransactionStorage { - private queue: PendingTransaction[] = []; + private queue: { tx: PendingTransaction; sortingValue: number }[] = []; private latestScannedBlock = -1; @@ -21,12 +21,17 @@ export class InMemoryTransactionStorage implements TransactionStorage { public async removeTx(hashes: string[]) { const hashSet = new Set(hashes); - this.queue = this.queue.filter((tx) => { + this.queue = this.queue.filter(({ tx }) => { const hash = tx.hash().toString(); return !hashSet.has(hash); }); } + private sortQueue() { + // Sort in-place and descending + this.queue.sort(({ sortingValue: a }, { sortingValue: b }) => b - a); + } + public async getPendingUserTransactions( offset: number, limit?: number @@ -42,25 +47,30 @@ export class InMemoryTransactionStorage implements TransactionStorage { if (block !== undefined) { const hashes = block.transactions.map((tx) => tx.tx.hash().toString()); this.queue = this.queue.filter( - (tx) => !hashes.includes(tx.hash().toString()) + ({ tx }) => !hashes.includes(tx.hash().toString()) ); } } this.latestScannedBlock = nextHeight - 1; + this.sortQueue(); + const from = offset ?? 0; const to = limit !== undefined ? from + limit : undefined; - return this.queue.slice(from, to); + return this.queue.slice(from, to).map(({ tx }) => tx); } - public async pushUserTransaction(tx: PendingTransaction): Promise { + public async pushUserTransaction( + tx: PendingTransaction, + priority: number + ): Promise { const notInQueue = this.queue.find( - (tx2) => tx2.hash().toString() === tx.hash().toString() + ({ tx: tx2 }) => tx2.hash().toString() === tx.hash().toString() ) === undefined; if (notInQueue) { - this.queue.push(tx); + this.queue.push({ tx, sortingValue: priority }); } return notInQueue; } diff --git a/packages/sequencer/src/storage/repositories/TransactionStorage.ts b/packages/sequencer/src/storage/repositories/TransactionStorage.ts index 522d63eb5..0a8696bcd 100644 --- a/packages/sequencer/src/storage/repositories/TransactionStorage.ts +++ b/packages/sequencer/src/storage/repositories/TransactionStorage.ts @@ -1,7 +1,10 @@ import { PendingTransaction } from "../../mempool/PendingTransaction"; export interface TransactionStorage { - pushUserTransaction: (tx: PendingTransaction) => Promise; + pushUserTransaction: ( + tx: PendingTransaction, + priority: number + ) => Promise; getPendingUserTransactions: ( offset: number, From c55f43e0309288b66eaaa232aa291fa6f21051c9 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 17:57:39 +0100 Subject: [PATCH 108/155] Implemented Default and NonceMempoolSorting --- packages/sequencer/src/index.ts | 3 ++ .../mempool/sorting/DefaultMempoolSorting.ts | 31 +++++++++++++++++++ .../mempool/sorting/NonceMempoolSorting.ts | 30 ++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts create mode 100644 packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index a0fee0179..e4bc3fbd2 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -3,6 +3,9 @@ export * from "./mempool/Mempool"; export * from "./mempool/PendingTransaction"; export * from "./mempool/CompressedSignature"; export * from "./mempool/private/PrivateMempool"; +export * from "./mempool/sorting/MempoolSorting"; +export * from "./mempool/sorting/DefaultMempoolSorting"; +export * from "./mempool/sorting/NonceMempoolSorting"; export * from "./sequencer/executor/Sequencer"; export * from "./sequencer/executor/Sequenceable"; export * from "./sequencer/SequencerIdProvider"; diff --git a/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts b/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts new file mode 100644 index 000000000..eb8b2fa3e --- /dev/null +++ b/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts @@ -0,0 +1,31 @@ +import { noop } from "@proto-kit/common"; + +import { PendingTransaction } from "../PendingTransaction"; +import { + SequencerModule, + sequencerModule, +} from "../../sequencer/builder/SequencerModule"; + +import { MempoolSorting } from "./MempoolSorting"; + +@sequencerModule() +export class DefaultMempoolSorting + extends SequencerModule + implements MempoolSorting +{ + public async start() { + noop(); + } + + public enablePostSorting(): boolean { + return false; + } + + public postSorting(a: PendingTransaction, b: PendingTransaction): number { + return 0; + } + + public presortingPriority(tx: PendingTransaction): number { + return 0; + } +} diff --git a/packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts b/packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts new file mode 100644 index 000000000..a39a7c7d2 --- /dev/null +++ b/packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts @@ -0,0 +1,30 @@ +import { noop } from "@proto-kit/common"; + +import { sequencerModule } from "../../sequencer/builder/SequencerModule"; +import { PendingTransaction } from "../PendingTransaction"; + +import { MempoolSorting } from "./MempoolSorting"; +import { DefaultMempoolSorting } from "./DefaultMempoolSorting"; + +@sequencerModule() +export class NonceMempoolSorting + extends DefaultMempoolSorting + implements MempoolSorting +{ + public enablePostSorting(): boolean { + return true; + } + + public postSorting(a: PendingTransaction, b: PendingTransaction): number { + if (a.sender.equals(b.sender).toBoolean()) { + return Number(a.nonce.toBigInt() - b.nonce.toBigInt()); + } else { + // Return 0, i.e. a should come before b (stuff stays in-order) + return 0; + } + } + + public async start() { + noop(); + } +} From 4aeba4750bdd52c86785545f070f0eb0ae6e332d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 17:57:55 +0100 Subject: [PATCH 109/155] Added priority to Prisma transaction storage --- .../migration.sql | 10 +++++++ packages/persistance/prisma/schema.prisma | 12 ++++++++ .../prisma/PrismaTransactionStorage.ts | 29 +++++++++++++++---- 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 packages/persistance/prisma/migrations/20260116164904_transaction_priority/migration.sql diff --git a/packages/persistance/prisma/migrations/20260116164904_transaction_priority/migration.sql b/packages/persistance/prisma/migrations/20260116164904_transaction_priority/migration.sql new file mode 100644 index 000000000..2d3189245 --- /dev/null +++ b/packages/persistance/prisma/migrations/20260116164904_transaction_priority/migration.sql @@ -0,0 +1,10 @@ +-- CreateTable +CREATE TABLE "TransactionPriority" ( + "transactionHash" TEXT NOT NULL, + "priority" INTEGER NOT NULL, + + CONSTRAINT "TransactionPriority_pkey" PRIMARY KEY ("transactionHash") +); + +-- AddForeignKey +ALTER TABLE "TransactionPriority" ADD CONSTRAINT "TransactionPriority_transactionHash_fkey" FOREIGN KEY ("transactionHash") REFERENCES "Transaction"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index d0ed1472e..043dedeac 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -54,6 +54,18 @@ model Transaction { executionResult TransactionExecutionResult? IncomingMessageBatchTransaction IncomingMessageBatchTransaction[] + + priority TransactionPriority? +} + +model TransactionPriority { + transactionHash String + + priority Int + + Transaction Transaction @relation(fields: [transactionHash], references: [hash]) + + @@id([transactionHash]) } model TransactionExecutionResult { diff --git a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts index 49256eef0..8f49f0384 100644 --- a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts @@ -34,6 +34,11 @@ export class PrismaTransactionStorage implements TransactionStorage { equals: false, }, }, + orderBy: { + priority: { + priority: "desc", + }, + }, skip: offset, take: limit, }); @@ -56,13 +61,27 @@ export class PrismaTransactionStorage implements TransactionStorage { } } - public async pushUserTransaction(tx: PendingTransaction): Promise { + public async pushUserTransaction( + tx: PendingTransaction, + priority: number + ): Promise { const { prismaClient } = this.connection; - const result = await prismaClient.transaction.createMany({ - data: [this.transactionMapper.mapOut(tx)], - skipDuplicates: true, - }); + const transactionData = this.transactionMapper.mapOut(tx); + + const [result] = await prismaClient.$transaction([ + prismaClient.transaction.createMany({ + data: [transactionData], + skipDuplicates: true, + }), + + prismaClient.transactionPriority.create({ + data: { + priority, + transactionHash: transactionData.hash, + }, + }), + ]); return result.count === 1; } From a9eb911d278403860a51d26b4bd90e1149ff3d5b Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 16 Jan 2026 18:13:11 +0100 Subject: [PATCH 110/155] Fixed module declaration race condition and tests --- .../src/protocol/baselayer/BaseLayer.ts | 12 +++++++++- .../src/protocol/baselayer/MinaBaseLayer.ts | 22 ++++++++++++------- .../src/protocol/baselayer/NoopBaseLayer.ts | 8 ++++--- .../test/integration/Mempool.test.ts | 11 +++++----- .../integration/StorageIntegration.test.ts | 10 ++++++--- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/packages/sequencer/src/protocol/baselayer/BaseLayer.ts b/packages/sequencer/src/protocol/baselayer/BaseLayer.ts index d16cbc656..71788a8c8 100644 --- a/packages/sequencer/src/protocol/baselayer/BaseLayer.ts +++ b/packages/sequencer/src/protocol/baselayer/BaseLayer.ts @@ -7,13 +7,23 @@ import { import { IncomingMessageAdapter } from "../../settlement/messages/IncomingMessageAdapter"; import type { OutgoingMessageAdapter } from "../../settlement/messages/outgoing/OutgoingMessageCollector"; -export interface BaseLayerDependencyRecord extends DependencyRecord { +import { MinaNetworkUtils } from "./network-utils/MinaNetworkUtils"; + +export interface StaticBaseLayerDependencyRecord extends DependencyRecord { IncomingMessageAdapter: DependencyDeclaration; OutgoingMessageAdapter: DependencyDeclaration< OutgoingMessageAdapter >; } +export interface StaticBaseLayer extends DependencyFactory { + dependencies: () => StaticBaseLayerDependencyRecord; +} + +export interface BaseLayerDependencyRecord extends DependencyRecord { + NetworkUtils: DependencyDeclaration; +} + export interface BaseLayer extends DependencyFactory { dependencies: () => BaseLayerDependencyRecord; } diff --git a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts index 2681a29aa..f22279d34 100644 --- a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts +++ b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts @@ -15,7 +15,7 @@ import { import { MinaTransactionSender } from "../../settlement/transactions/MinaTransactionSender"; import { DefaultOutgoingMessageAdapter } from "../../settlement/messages/outgoing/DefaultOutgoingMessageAdapter"; -import { BaseLayer } from "./BaseLayer"; +import { BaseLayer, StaticBaseLayer } from "./BaseLayer"; import { LocalBlockchainUtils } from "./network-utils/LocalBlockchainUtils"; import { LightnetUtils } from "./network-utils/LightnetUtils"; import { RemoteNetworkUtils } from "./network-utils/RemoteNetworkUtils"; @@ -62,13 +62,7 @@ export class MinaBaseLayer super(); } - public dependencies() { - const NetworkUtilsClass = match(this.config.network.type) - .with("local", () => LocalBlockchainUtils) - .with("lightnet", () => LightnetUtils) - .with("remote", () => RemoteNetworkUtils) - .exhaustive(); - + public static dependencies() { return { IncomingMessageAdapter: { useClass: MinaIncomingMessageAdapter, @@ -81,7 +75,17 @@ export class MinaBaseLayer OutgoingMessageAdapter: { useClass: DefaultOutgoingMessageAdapter, }, + }; + } + public dependencies() { + const NetworkUtilsClass = match(this.config.network.type) + .with("local", () => LocalBlockchainUtils) + .with("lightnet", () => LightnetUtils) + .with("remote", () => RemoteNetworkUtils) + .exhaustive(); + + return { NetworkUtils: { useClass: NetworkUtilsClass, }, @@ -141,3 +145,5 @@ export class MinaBaseLayer this.network = Network; } } + +MinaBaseLayer satisfies StaticBaseLayer; diff --git a/packages/sequencer/src/protocol/baselayer/NoopBaseLayer.ts b/packages/sequencer/src/protocol/baselayer/NoopBaseLayer.ts index 9e64f6996..430fa3272 100644 --- a/packages/sequencer/src/protocol/baselayer/NoopBaseLayer.ts +++ b/packages/sequencer/src/protocol/baselayer/NoopBaseLayer.ts @@ -11,7 +11,7 @@ import { PendingTransaction } from "../../mempool/PendingTransaction"; import { OutgoingMessageAdapter } from "../../settlement/messages/outgoing/OutgoingMessageCollector"; import { Block } from "../../storage/model/Block"; -import { BaseLayer, BaseLayerDependencyRecord } from "./BaseLayer"; +import { StaticBaseLayer, StaticBaseLayerDependencyRecord } from "./BaseLayer"; class NoopIncomingMessageAdapter implements IncomingMessageAdapter { async fetchPendingMessages( @@ -40,7 +40,7 @@ class NoopMessageAdapter implements OutgoingMessageAdapter { } @sequencerModule() -export class NoopBaseLayer extends SequencerModule implements BaseLayer { +export class NoopBaseLayer extends SequencerModule { public async blockProduced(): Promise { noop(); } @@ -49,7 +49,7 @@ export class NoopBaseLayer extends SequencerModule implements BaseLayer { noop(); } - public dependencies(): BaseLayerDependencyRecord { + public static dependencies(): StaticBaseLayerDependencyRecord { return { OutgoingMessageAdapter: { useClass: NoopMessageAdapter, @@ -60,3 +60,5 @@ export class NoopBaseLayer extends SequencerModule implements BaseLayer { }; } } + +NoopBaseLayer satisfies StaticBaseLayer; diff --git a/packages/sequencer/test/integration/Mempool.test.ts b/packages/sequencer/test/integration/Mempool.test.ts index d5aec4037..2b252f783 100644 --- a/packages/sequencer/test/integration/Mempool.test.ts +++ b/packages/sequencer/test/integration/Mempool.test.ts @@ -23,7 +23,8 @@ import { import { Balance } from "./mocks/Balance"; import { createTransaction } from "./utils"; -describe.each([["InMemory", InMemoryDatabase]])( +// TODO Reenable with next PR +describe.skip.each([["InMemory", InMemoryDatabase]])( "Mempool test", ( testName, @@ -131,7 +132,7 @@ describe.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user2PrivateKey, 1); await mempoolAddTransactions(user3PrivateKey, 1); - const txs = await mempool.getTxs(); + const txs = await mempool.getTxs(0); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); @@ -160,7 +161,7 @@ describe.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user2PrivateKey, 1); await mempoolAddTransactions(user3PrivateKey, 0); - const txs = await mempool.getTxs(); + const txs = await mempool.getTxs(0); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); @@ -187,7 +188,7 @@ describe.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user3PrivateKey, 0); await mempoolAddTransactions(user1PrivateKey, 1); - const txs = await mempool.getTxs(); + const txs = await mempool.getTxs(0); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); @@ -216,7 +217,7 @@ describe.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user3PrivateKey, 0); await mempoolAddTransactions(user1PrivateKey, 1); - const txs = await mempool.getTxs(); + const txs = await mempool.getTxs(0); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index 16f17bbe0..55fb07b8f 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -1,5 +1,5 @@ import "reflect-metadata"; -import { expect } from "@jest/globals"; +import { afterAll, expect } from "@jest/globals"; import { Protocol } from "@proto-kit/protocol"; import { Runtime } from "@proto-kit/module"; import { Bool, Field, PrivateKey, UInt64 } from "o1js"; @@ -113,6 +113,10 @@ describe.each([["InMemory", InMemoryDatabase]])( provenState = sequencer.resolve("AsyncStateService"); }); + afterAll(async () => { + await appChain.close(); + }); + it("test unproven block prod", async () => { await appChain.sequencer.resolve("Mempool").add( createTransaction({ @@ -200,7 +204,7 @@ describe.each([["InMemory", InMemoryDatabase]])( }); await mempool.add(tx); - const txs = await txStorage.getPendingUserTransactions(); + const txs = await txStorage.getPendingUserTransactions(0); expect(txs).toHaveLength(1); expect(txs[0].hash().toString()).toStrictEqual(tx.hash().toString()); @@ -208,7 +212,7 @@ describe.each([["InMemory", InMemoryDatabase]])( await sequencer.resolve("BlockTrigger").produceBlock(); await expect( - txStorage.getPendingUserTransactions() + txStorage.getPendingUserTransactions(0) ).resolves.toHaveLength(0); }, 60_000); } From 178c16367fd23bd550c7a2f72a01cd82e1b00052 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 10:26:18 +0100 Subject: [PATCH 111/155] Fixed compile error --- packages/sequencer/src/mempool/private/PrivateMempool.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index 71469946b..3df1225c6 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -66,11 +66,11 @@ export class PrivateMempool extends SequencerModule implements Mempool { @trace("mempool.get_txs") public async getTxs( - offset: number, + offset?: number, limit?: number ): Promise { return await this.transactionStorage.getPendingUserTransactions( - offset, + offset ?? 0, limit ); } From 63de33ebc62f6cef7411a2c1a5e9e27ec9242086 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 13:23:47 +0100 Subject: [PATCH 112/155] Fixed issue with fee hooks and made SignedTransaction to AuthorizedTransaction to include isMessage --- .../library/src/hooks/TransactionFeeHook.ts | 36 +++++++++---------- .../protocol/src/hooks/AccountStateHook.ts | 4 +-- packages/protocol/src/index.ts | 2 +- ...ransaction.ts => AuthorizedTransaction.ts} | 21 ++++++----- .../src/protocol/ProvableTransactionHook.ts | 33 +++++++---------- .../prover/transaction/TransactionProver.ts | 29 ++++++++------- .../src/mempool/PendingTransaction.ts | 9 ++--- .../sequencing/TransactionExecutionService.ts | 2 +- .../mocks/ProtocolStateTestHook.ts | 2 +- 9 files changed, 68 insertions(+), 70 deletions(-) rename packages/protocol/src/model/transaction/{SignedTransaction.ts => AuthorizedTransaction.ts} (64%) diff --git a/packages/library/src/hooks/TransactionFeeHook.ts b/packages/library/src/hooks/TransactionFeeHook.ts index 974c113f1..a417c553e 100644 --- a/packages/library/src/hooks/TransactionFeeHook.ts +++ b/packages/library/src/hooks/TransactionFeeHook.ts @@ -126,20 +126,15 @@ export class TransactionFeeHook extends ProvableTransactionHook { + public async beforeTransaction({ + transaction: { transaction }, + }: BeforeTransactionHookArguments): Promise { const feeConfig = Provable.witness(MethodFeeConfigData, () => - this.feeAnalyzer.getFeeConfig( - executionData.transaction.methodId.toBigInt() - ) + this.feeAnalyzer.getFeeConfig(transaction.methodId.toBigInt()) ); const witness = Provable.witness( RuntimeFeeAnalyzerService.getWitnessType(), - () => - this.feeAnalyzer.getWitness( - executionData.transaction.methodId.toBigInt() - ) + () => this.feeAnalyzer.getWitness(transaction.methodId.toBigInt()) ); const root = Field(this.feeAnalyzer.getRoot()); @@ -147,14 +142,14 @@ export class TransactionFeeHook extends ProvableTransactionHook { + public async removeTransactionWhen({ + transaction, + }: BeforeTransactionHookArguments): Promise { const feeConfig = this.feeAnalyzer.getFeeConfig( - args.transaction.methodId.toBigInt() + transaction.transaction.methodId.toBigInt() ); const fee = this.getFee(feeConfig); const tokenId = new TokenId(this.config.tokenId); - const feeRecipient = PublicKey.fromBase58(this.config.feeRecipient); const balanceAvailable = await this.balances.balances.get({ tokenId, - address: feeRecipient, + address: transaction.transaction.sender.value, }); - return balanceAvailable.orElse(Balance.from(0)).lessThan(fee).toBoolean(); + return balanceAvailable + .orElse(Balance.from(0)) + .lessThan(fee) + .or(transaction.isMessage) + .toBoolean(); } } diff --git a/packages/protocol/src/hooks/AccountStateHook.ts b/packages/protocol/src/hooks/AccountStateHook.ts index 6e05f4762..cb5029d87 100644 --- a/packages/protocol/src/hooks/AccountStateHook.ts +++ b/packages/protocol/src/hooks/AccountStateHook.ts @@ -22,7 +22,7 @@ export class AccountStateHook extends ProvableTransactionHook { ); public async beforeTransaction({ - transaction, + transaction: { transaction }, }: BeforeTransactionHookArguments) { const sender = transaction.sender.value; @@ -57,7 +57,7 @@ export class AccountStateHook extends ProvableTransactionHook { // Under these conditions we want the tx removed from the mempool. public async removeTransactionWhen({ - transaction, + transaction: { transaction }, }: BeforeTransactionHookArguments): Promise { const sender = transaction.sender.value; diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 34e794b3d..99cf03fdf 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -6,7 +6,7 @@ export * from "./model/StateTransitionProvableBatch"; export * from "./model/Option"; export * from "./model/Path"; export * from "./model/network/NetworkState"; -export * from "./model/transaction/SignedTransaction"; +export * from "./model/transaction/AuthorizedTransaction"; export * from "./model/transaction/RuntimeTransaction"; export * from "./model/transaction/ValueOption"; export * from "./model/MethodPublicOutput"; diff --git a/packages/protocol/src/model/transaction/SignedTransaction.ts b/packages/protocol/src/model/transaction/AuthorizedTransaction.ts similarity index 64% rename from packages/protocol/src/model/transaction/SignedTransaction.ts rename to packages/protocol/src/model/transaction/AuthorizedTransaction.ts index 8f29c7752..a870624bb 100644 --- a/packages/protocol/src/model/transaction/SignedTransaction.ts +++ b/packages/protocol/src/model/transaction/AuthorizedTransaction.ts @@ -2,26 +2,30 @@ import { Bool, Field, Scalar, Signature, Struct, UInt64 } from "o1js"; import { RuntimeTransaction } from "./RuntimeTransaction"; -export class SignedTransaction extends Struct({ +export class AuthorizedTransaction extends Struct({ transaction: RuntimeTransaction, signature: Signature, + isMessage: Bool, }) { public static getSignatureData(args: { methodId: Field; nonce: UInt64; argsHash: Field; }): Field[] { + // No isMessage here - we don't sign that return [args.methodId, ...args.nonce.value.toFields(), args.argsHash]; } - public static dummy(): SignedTransaction { - return new SignedTransaction({ + public static dummy(): AuthorizedTransaction { + return new AuthorizedTransaction({ transaction: RuntimeTransaction.dummyTransaction(), signature: Signature.fromObject({ s: Scalar.from(0), r: Field(0), }), + + isMessage: Bool(false), }); } @@ -31,17 +35,16 @@ export class SignedTransaction extends Struct({ public getSignatureData(): Field[] { const { methodId, argsHash, nonce } = this.transaction; - return SignedTransaction.getSignatureData({ + return AuthorizedTransaction.getSignatureData({ nonce: nonce.value, methodId, argsHash, }); } - public validateSignature(): Bool { - return this.signature.verify( - this.transaction.sender.value, - this.getSignatureData() - ); + public validateAuthorization(): Bool { + return this.signature + .verify(this.transaction.sender.value, this.getSignatureData()) + .or(this.isMessage); } } diff --git a/packages/protocol/src/protocol/ProvableTransactionHook.ts b/packages/protocol/src/protocol/ProvableTransactionHook.ts index 43280ff3c..605dc8814 100644 --- a/packages/protocol/src/protocol/ProvableTransactionHook.ts +++ b/packages/protocol/src/protocol/ProvableTransactionHook.ts @@ -1,13 +1,10 @@ import { NoConfig } from "@proto-kit/common"; -import { Field, Signature } from "o1js"; +import { Field } from "o1js"; -import { RuntimeTransaction } from "../model/transaction/RuntimeTransaction"; import { NetworkState } from "../model/network/NetworkState"; import { MethodPublicOutput } from "../model/MethodPublicOutput"; -import { - TransactionProverState, - TransactionProverTransactionArguments, -} from "../prover/transaction/TransactionProvable"; +import { TransactionProverState } from "../prover/transaction/TransactionProvable"; +import { AuthorizedTransaction } from "../model/transaction/AuthorizedTransaction"; import { TransitioningProtocolModule } from "./TransitioningProtocolModule"; @@ -32,34 +29,29 @@ export function toProvableHookTransactionState( } export function toBeforeTransactionHookArgument( - executionData: Omit< - TransactionProverTransactionArguments, - "verificationKeyAttestation" - >, + authorizedTransaction: AuthorizedTransaction, networkState: NetworkState, state: Parameters[0] ): BeforeTransactionHookArguments { - const { transaction, signature } = executionData; - return { networkState, - transaction, - signature, + transaction: authorizedTransaction, prover: toProvableHookTransactionState(state), }; } export function toAfterTransactionHookArgument( - executionData: Omit< - TransactionProverTransactionArguments, - "verificationKeyAttestation" - >, + authorizedTransaction: AuthorizedTransaction, networkState: NetworkState, state: Parameters[0], runtimeResult: MethodPublicOutput ): AfterTransactionHookArguments { return { - ...toBeforeTransactionHookArgument(executionData, networkState, state), + ...toBeforeTransactionHookArgument( + authorizedTransaction, + networkState, + state + ), runtimeResult, }; } @@ -75,8 +67,7 @@ export type TransactionResult = Omit< >; export interface BeforeTransactionHookArguments { - transaction: RuntimeTransaction; - signature: Signature; + transaction: AuthorizedTransaction; networkState: NetworkState; prover: ProvableHookTransactionState; } diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts index da319160a..c2fb917ae 100644 --- a/packages/protocol/src/prover/transaction/TransactionProver.ts +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -24,7 +24,7 @@ import { import { StateServiceProvider } from "../../state/StateServiceProvider"; import { RuntimeVerificationKeyRootService } from "../block/services/RuntimeVerificationKeyRootService"; import { addTransactionToBundle, executeHooks } from "../utils"; -import { SignedTransaction } from "../../model/transaction/SignedTransaction"; +import { AuthorizedTransaction } from "../../model/transaction/AuthorizedTransaction"; import { MethodVKConfigData, MinimalVKTreeService, @@ -115,8 +115,14 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< const { isMessage } = runtimeOutput; + const authorizedTransaction = new AuthorizedTransaction({ + transaction, + signature, + isMessage, + }); + const beforeTxHookArguments = toBeforeTransactionHookArgument( - executionData, + authorizedTransaction, networkState, state ); @@ -140,7 +146,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< // Apply afterTransaction hook state transitions const afterTxHookArguments = toAfterTransactionHookArgument( - executionData, + authorizedTransaction, networkState, state, runtimeOutput @@ -165,14 +171,10 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< "Transactions provided in AppProof and BlockProof do not match" ); - // Check transaction signature - new SignedTransaction({ - transaction, - signature, - }) - .validateSignature() - .or(isMessage) - .assertTrue("Transaction signature not valid"); + // Check transaction signature or isMessage + authorizedTransaction + .validateAuthorization() + .assertTrue("Transaction authorization not valid"); // Validate layout of transaction witness transaction.assertTransactionType(isMessage); @@ -210,7 +212,10 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< isMessage: Bool ) { const { batch, rawStatus } = await executeHooks( - hookArguments, + { + transaction: hookArguments.transaction.transaction, + networkState: hookArguments.networkState, + }, `${type}Transaction`, async () => { for (const module of this.transactionHooks) { diff --git a/packages/sequencer/src/mempool/PendingTransaction.ts b/packages/sequencer/src/mempool/PendingTransaction.ts index fd1fedfec..3b81066e4 100644 --- a/packages/sequencer/src/mempool/PendingTransaction.ts +++ b/packages/sequencer/src/mempool/PendingTransaction.ts @@ -10,7 +10,7 @@ import { import { PublicKeyOption, RuntimeTransaction, - SignedTransaction, + AuthorizedTransaction, UInt64Option, } from "@proto-kit/protocol"; @@ -82,7 +82,7 @@ export class UnsignedTransaction implements UnsignedTransactionBody { } public getSignatureData(): Field[] { - return SignedTransaction.getSignatureData({ + return AuthorizedTransaction.getSignatureData({ nonce: this.nonce, methodId: this.methodId, argsHash: this.argsHash(), @@ -186,10 +186,11 @@ export class PendingTransaction extends UnsignedTransaction { }; } - public toProtocolTransaction(): SignedTransaction { - return new SignedTransaction({ + public toProtocolTransaction(): AuthorizedTransaction { + return new AuthorizedTransaction({ transaction: this.toRuntimeTransaction(), signature: this.signature, + isMessage: Bool(this.isMessage), }); } } diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index 9bd545ac8..4fffe2859 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -274,7 +274,7 @@ export class TransactionExecutionService { ); }), { - transaction: hookArguments.transaction, + transaction: hookArguments.transaction.transaction, networkState: hookArguments.networkState, }, runSimulated diff --git a/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts b/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts index 6ae5db6bf..00528849a 100644 --- a/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts +++ b/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts @@ -17,7 +17,7 @@ export class ProtocolStateTestHook extends ProvableTransactionHook { public async beforeTransaction( executionData: BeforeTransactionHookArguments ): Promise { - const { methodId } = executionData.transaction; + const { methodId } = executionData.transaction.transaction; const invocations = await this.methodIdInvocations.get(methodId); await this.methodIdInvocations.set( methodId, From dc455a911698fac12f34f9e31abae0de526bd6f8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 13:25:57 +0100 Subject: [PATCH 113/155] Fixed a bunch of tests --- .../src/hooks/RuntimeFeeAnalyzerService.ts | 2 +- .../method/MethodParameterEncoder.test.ts | 1 + .../test/method/runtimeMethod-fail.test.ts | 1 + packages/protocol/test/BlockProver.test.ts | 2 +- .../sdk/test/blockProof/blockProof.test.ts | 18 +++-- packages/sequencer/src/appChain/AppChain.ts | 2 +- .../production/sequencing/Ordering.ts | 2 +- .../test/integration/MempoolTxRemoved.test.ts | 74 ++++++++++++------- .../atomic-block-production.test.ts | 6 +- 9 files changed, 69 insertions(+), 39 deletions(-) diff --git a/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts b/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts index 707d80a4c..1e8d2581c 100644 --- a/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts +++ b/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts @@ -79,8 +79,8 @@ export class RuntimeFeeAnalyzerService extends ConfigurableModule { await mapSequential(txHooks, async (hook) => { await hook.beforeTransaction({ - transaction: RuntimeTransaction.fromTransaction({ - sender: alice, - nonce: O1UInt64.from(0), - methodId: Field(balancesMethodId), - argsHash: Field(0), + transaction: new AuthorizedTransaction({ + transaction: RuntimeTransaction.fromTransaction({ + sender: alice, + nonce: O1UInt64.from(0), + methodId: Field(balancesMethodId), + argsHash: Field(0), + }), + signature: Signature.create(PrivateKey.random(), [Field(0)]), + isMessage: Bool(false), }), networkState: NetworkState.empty(), - signature: Signature.create(PrivateKey.random(), [Field(0)]), prover: { incomingMessagesHash: Field(0), transactionsHash: Field(0), diff --git a/packages/sequencer/src/appChain/AppChain.ts b/packages/sequencer/src/appChain/AppChain.ts index cb2369cf9..0036b4bf2 100644 --- a/packages/sequencer/src/appChain/AppChain.ts +++ b/packages/sequencer/src/appChain/AppChain.ts @@ -61,7 +61,7 @@ export class AppChain< */ public async start( proofsEnabled: boolean = false, - dependencyContainer: DependencyContainer = container + dependencyContainer: DependencyContainer = container.createChildContainer() ) { this.create(() => dependencyContainer); diff --git a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts index 3d63ce748..f9b7f1e73 100644 --- a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts +++ b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts @@ -78,7 +78,6 @@ export class Ordering { if (result.hooksStatus.toBoolean() || result.tx.isMessage) { // Included this.ordered += 1; - this.userTxOffset += result.tx.isMessage ? 0 : 1; this.results.push({ status: "included", result, @@ -114,6 +113,7 @@ export class Ordering { const space = this.sizeLimit - this.ordered; if (space > 0) { const newTxs = await this.mempool.getTxs(this.userTxOffset, space); + this.userTxOffset += space; this.transactionQueue.push(...newTxs); } } diff --git a/packages/sequencer/test/integration/MempoolTxRemoved.test.ts b/packages/sequencer/test/integration/MempoolTxRemoved.test.ts index de598cde9..6d9c4829d 100644 --- a/packages/sequencer/test/integration/MempoolTxRemoved.test.ts +++ b/packages/sequencer/test/integration/MempoolTxRemoved.test.ts @@ -1,12 +1,19 @@ +import "reflect-metadata"; import { Balances } from "@proto-kit/library"; import { Runtime } from "@proto-kit/module"; -import { TestingAppChain } from "@proto-kit/sdk"; import { Bool, PrivateKey, UInt64 } from "o1js"; -import "reflect-metadata"; import { expectDefined, log } from "@proto-kit/common"; import { afterEach, beforeEach, describe, expect } from "@jest/globals"; +import { Protocol } from "@proto-kit/protocol"; -import { PrivateMempool, Sequencer } from "../../src"; +import { + AppChain, + ManualBlockTrigger, + PrivateMempool, + Sequencer, + VanillaTaskWorkerModules, +} from "../../src"; +import { testingSequencerModules } from "../TestingSequencer"; import { createTransaction } from "./utils"; import { Balance } from "./mocks/Balance"; @@ -17,44 +24,61 @@ describe("mempool removal mechanism", () => { let mempool: PrivateMempool; let runtime: Runtime<{ Balances: typeof Balances; Balance: typeof Balance }>; let sequencer: Sequencer; + let trigger: ManualBlockTrigger; + + const createAppChain = async () => { + const app = AppChain.from({ + Sequencer: Sequencer.from(testingSequencerModules({})), + Protocol: Protocol.from(Protocol.defaultModules()), + Runtime: Runtime.from({ + Balances, + Balance, + }), + }); - const createAppChain = async (validationEnabled: boolean) => { - // eslint-disable-next-line @typescript-eslint/no-shadow - const appChain = TestingAppChain.fromRuntime({ Balance }); - - appChain.configurePartial({ + app.configurePartial({ Runtime: { Balance: {}, Balances: {}, }, Protocol: { - ...appChain.config.Protocol!, + ...Protocol.defaultConfig(), }, Sequencer: { - ...appChain.config.Sequencer, - Mempool: { validationEnabled }, + Database: {}, + BlockTrigger: {}, + Mempool: {}, + BatchProducerModule: {}, + BlockProducerModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + BaseLayer: {}, + TaskQueue: {}, + FeeStrategy: {}, + SequencerStartupModule: {}, }, }); - await appChain.start(); - runtime = appChain.runtime; - sequencer = appChain.sequencer; + await app.start(); + runtime = app.runtime; + sequencer = app.sequencer; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment mempool = sequencer.resolve("Mempool"); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + trigger = sequencer.resolve("BlockTrigger"); - return appChain; + return app; }; + beforeEach(async () => { + appChain = await createAppChain(); + }, 60_000); + afterEach(async () => { await appChain.close(); }); describe("block pipeline reaction", () => { - beforeEach(async () => { - appChain = await createAppChain(false); - }, 60_000); - it("check only one is included, other is skipped", async () => { log.setLevel("trace"); @@ -81,7 +105,7 @@ describe("mempool removal mechanism", () => { const txs2 = await mempool.getTxs(); expect(txs2.length).toBe(2); - const block = await appChain.produceBlock(); + const block = await trigger.produceBlock(); expectDefined(block); expect(block.transactions).toHaveLength(1); @@ -113,7 +137,7 @@ describe("mempool removal mechanism", () => { const txs2 = await mempool.getTxs(); expect(txs2.length).toBe(2); - const block = await appChain.produceBlock(); + const block = await trigger.produceBlock(); expectDefined(block); expect(block.transactions).toHaveLength(1); @@ -122,11 +146,7 @@ describe("mempool removal mechanism", () => { }); }); - describe("mempool simulation", () => { - beforeEach(async () => { - appChain = await createAppChain(true); - }, 60_000); - + describe("block production reordering", () => { it("check tx is removed", async () => { await mempool.add( createTransaction({ @@ -151,7 +171,7 @@ describe("mempool removal mechanism", () => { const txs = await mempool.getTxs(); expect(txs.length).toBe(2); - await appChain!.produceBlock(); + await trigger!.produceBlock(); await mempool.add( createTransaction({ diff --git a/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts b/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts index e34b72af6..79dccbcd4 100644 --- a/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts +++ b/packages/sequencer/test/protocol/production/sequencing/atomic-block-production.test.ts @@ -3,7 +3,7 @@ import { Runtime } from "@proto-kit/module"; import { Protocol } from "@proto-kit/protocol"; import { VanillaProtocolModules } from "@proto-kit/library"; import { container } from "tsyringe"; -import { jest } from "@jest/globals"; +import { afterEach, jest } from "@jest/globals"; import { expectDefined } from "@proto-kit/common"; import { @@ -76,6 +76,10 @@ describe("atomic block production", () => { trigger = app.sequencer.resolve("BlockTrigger"); }); + afterEach(async () => { + await appchain.close(); + }); + /** * This test does two passes on block generation. * In the first, the metadata generation function is mocked to throw an error From fa0f74064b58c55838aabd00116e66c442439905 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 13:45:19 +0100 Subject: [PATCH 114/155] Removed NonceSorting, reenabled sorting test --- .../indexer/src/tasks/IndexPendingTxTask.ts | 2 +- packages/sequencer/src/index.ts | 1 - .../mempool/sorting/DefaultMempoolSorting.ts | 3 +- .../mempool/sorting/NonceMempoolSorting.ts | 30 --------------- .../{Mempool.test.ts => Block-order.test.ts} | 38 ++++++++++++------- 5 files changed, 28 insertions(+), 46 deletions(-) delete mode 100644 packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts rename packages/sequencer/test/integration/{Mempool.test.ts => Block-order.test.ts} (88%) diff --git a/packages/indexer/src/tasks/IndexPendingTxTask.ts b/packages/indexer/src/tasks/IndexPendingTxTask.ts index db468660d..9d448c6a7 100644 --- a/packages/indexer/src/tasks/IndexPendingTxTask.ts +++ b/packages/indexer/src/tasks/IndexPendingTxTask.ts @@ -30,7 +30,7 @@ export class IndexPendingTxTask public async compute(input: PendingTransaction): Promise { try { - await this.transactionStorage.pushUserTransaction(input); + await this.transactionStorage.pushUserTransaction(input, 0); return ""; } catch (err) { log.error("Failed to process pending tx task", err); diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index e4bc3fbd2..5821284bf 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -5,7 +5,6 @@ export * from "./mempool/CompressedSignature"; export * from "./mempool/private/PrivateMempool"; export * from "./mempool/sorting/MempoolSorting"; export * from "./mempool/sorting/DefaultMempoolSorting"; -export * from "./mempool/sorting/NonceMempoolSorting"; export * from "./sequencer/executor/Sequencer"; export * from "./sequencer/executor/Sequenceable"; export * from "./sequencer/SequencerIdProvider"; diff --git a/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts b/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts index eb8b2fa3e..88b3500fd 100644 --- a/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts +++ b/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts @@ -26,6 +26,7 @@ export class DefaultMempoolSorting } public presortingPriority(tx: PendingTransaction): number { - return 0; + // This means we order by first in, first out in the db + return -Date.now(); } } diff --git a/packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts b/packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts deleted file mode 100644 index a39a7c7d2..000000000 --- a/packages/sequencer/src/mempool/sorting/NonceMempoolSorting.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { noop } from "@proto-kit/common"; - -import { sequencerModule } from "../../sequencer/builder/SequencerModule"; -import { PendingTransaction } from "../PendingTransaction"; - -import { MempoolSorting } from "./MempoolSorting"; -import { DefaultMempoolSorting } from "./DefaultMempoolSorting"; - -@sequencerModule() -export class NonceMempoolSorting - extends DefaultMempoolSorting - implements MempoolSorting -{ - public enablePostSorting(): boolean { - return true; - } - - public postSorting(a: PendingTransaction, b: PendingTransaction): number { - if (a.sender.equals(b.sender).toBoolean()) { - return Number(a.nonce.toBigInt() - b.nonce.toBigInt()); - } else { - // Return 0, i.e. a should come before b (stuff stays in-order) - return 0; - } - } - - public async start() { - noop(); - } -} diff --git a/packages/sequencer/test/integration/Mempool.test.ts b/packages/sequencer/test/integration/Block-order.test.ts similarity index 88% rename from packages/sequencer/test/integration/Mempool.test.ts rename to packages/sequencer/test/integration/Block-order.test.ts index 2b252f783..0f3ee5649 100644 --- a/packages/sequencer/test/integration/Mempool.test.ts +++ b/packages/sequencer/test/integration/Block-order.test.ts @@ -1,4 +1,4 @@ -import { log, TypedClass } from "@proto-kit/common"; +import { expectDefined, log, TypedClass } from "@proto-kit/common"; import { Runtime } from "@proto-kit/module"; import { Protocol } from "@proto-kit/protocol"; import { Bool, PrivateKey, UInt64 } from "o1js"; @@ -14,6 +14,7 @@ import { StorageDependencyFactory, VanillaTaskWorkerModules, AppChain, + ManualBlockTrigger, } from "../../src"; import { DefaultTestingSequencerModules, @@ -23,19 +24,21 @@ import { import { Balance } from "./mocks/Balance"; import { createTransaction } from "./utils"; -// TODO Reenable with next PR -describe.skip.each([["InMemory", InMemoryDatabase]])( - "Mempool test", +describe.each([["InMemory", InMemoryDatabase]])( + "Block Ordering test: %s", ( testName, Database: TypedClass ) => { let appChain: ReturnType; let sequencer: Sequencer< - DefaultTestingSequencerModules & { Database: typeof Database } + DefaultTestingSequencerModules & { + Database: typeof Database; + } >; let runtime: Runtime<{ Balance: typeof Balance }>; let mempool: PrivateMempool; + let trigger: ManualBlockTrigger; async function mempoolAddTransactions( userPrivateKey: PrivateKey, @@ -116,6 +119,7 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( sequencer = appChain.sequencer; mempool = sequencer.resolve("Mempool"); + trigger = sequencer.resolve("BlockTrigger"); }); afterEach(async () => { @@ -123,7 +127,7 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( }); it("transactions are returned in right order - simple", async () => { - expect.assertions(13); + expect.assertions(14); await mempoolAddTransactions(user1PrivateKey, 0); await mempoolAddTransactions(user2PrivateKey, 0); @@ -132,7 +136,9 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user2PrivateKey, 1); await mempoolAddTransactions(user3PrivateKey, 1); - const txs = await mempool.getTxs(0); + const block = await trigger.produceBlock(); + expectDefined(block); + const txs = block.transactions.map((x) => x.tx); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); @@ -150,7 +156,7 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( }); it("transactions are returned in right order - medium", async () => { - expect.assertions(13); + expect.assertions(14); log.setLevel("TRACE"); @@ -161,7 +167,9 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user2PrivateKey, 1); await mempoolAddTransactions(user3PrivateKey, 0); - const txs = await mempool.getTxs(0); + const block = await trigger.produceBlock(); + expectDefined(block); + const txs = block.transactions.map((x) => x.tx); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); @@ -179,7 +187,7 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( }); it("transactions are returned in right order - harder", async () => { - expect.assertions(13); + expect.assertions(14); await mempoolAddTransactions(user1PrivateKey, 0); await mempoolAddTransactions(user2PrivateKey, 1); @@ -188,7 +196,9 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user3PrivateKey, 0); await mempoolAddTransactions(user1PrivateKey, 1); - const txs = await mempool.getTxs(0); + const block = await trigger.produceBlock(); + expectDefined(block); + const txs = block.transactions.map((x) => x.tx); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); @@ -206,7 +216,7 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( }); it("transactions are returned in right order - hardest", async () => { - expect.assertions(13); + expect.assertions(14); await mempoolAddTransactions(user1PrivateKey, 0); await mempoolAddTransactions(user1PrivateKey, 4); @@ -217,7 +227,9 @@ describe.skip.each([["InMemory", InMemoryDatabase]])( await mempoolAddTransactions(user3PrivateKey, 0); await mempoolAddTransactions(user1PrivateKey, 1); - const txs = await mempool.getTxs(0); + const block = await trigger.produceBlock(); + expectDefined(block); + const txs = block.transactions.map((x) => x.tx); expect(txs).toHaveLength(6); expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); From acae99d92d08f5a5af78d2081e07d4337851af03 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 13:47:54 +0100 Subject: [PATCH 115/155] Added changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2eb240f2..e53a4b6be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added +- Introduced dynamic block building and JIT transaction fetching [#394](https://github.com/proto-kit/framework/pull/394) - Introduced block explorer [#381](https://github.com/proto-kit/framework/pull/381) - Added CircuitAnalysisModule for easy analysis of protocol circuits [#379](https://github.com/proto-kit/framework/pull/379) - Separated settlement and bridging functionally, so now settlement can be used without bridging [#376](https://github.com/proto-kit/framework/pull/376) From b418be8a6384e10a70693a6b5c528a37f9a48f29 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 13:49:08 +0100 Subject: [PATCH 116/155] Added changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2eb240f2..a34052abc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added +- Added Mempool sorting [#395](https://github.com/proto-kit/framework/pull/395) - Introduced block explorer [#381](https://github.com/proto-kit/framework/pull/381) - Added CircuitAnalysisModule for easy analysis of protocol circuits [#379](https://github.com/proto-kit/framework/pull/379) - Separated settlement and bridging functionally, so now settlement can be used without bridging [#376](https://github.com/proto-kit/framework/pull/376) From c73001951ed54fedea27fd3355084d9e60a7b9b8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 14:44:23 +0100 Subject: [PATCH 117/155] Changed priority to bigint in DB --- .../migration.sql | 2 +- packages/persistance/prisma/schema.prisma | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/persistance/prisma/migrations/{20260116164904_transaction_priority => 20260117134313_transaction_priority}/migration.sql (92%) diff --git a/packages/persistance/prisma/migrations/20260116164904_transaction_priority/migration.sql b/packages/persistance/prisma/migrations/20260117134313_transaction_priority/migration.sql similarity index 92% rename from packages/persistance/prisma/migrations/20260116164904_transaction_priority/migration.sql rename to packages/persistance/prisma/migrations/20260117134313_transaction_priority/migration.sql index 2d3189245..e04752925 100644 --- a/packages/persistance/prisma/migrations/20260116164904_transaction_priority/migration.sql +++ b/packages/persistance/prisma/migrations/20260117134313_transaction_priority/migration.sql @@ -1,7 +1,7 @@ -- CreateTable CREATE TABLE "TransactionPriority" ( "transactionHash" TEXT NOT NULL, - "priority" INTEGER NOT NULL, + "priority" BIGINT NOT NULL, CONSTRAINT "TransactionPriority_pkey" PRIMARY KEY ("transactionHash") ); diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 043dedeac..a5abc4338 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -61,7 +61,7 @@ model Transaction { model TransactionPriority { transactionHash String - priority Int + priority BigInt Transaction Transaction @relation(fields: [transactionHash], references: [hash]) From 3f410d748e81990f98c2f6cff57599ce1976c717 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 14:52:35 +0100 Subject: [PATCH 118/155] Changed post sorting interface to full array --- packages/sequencer/src/mempool/private/PrivateMempool.ts | 7 ++----- .../sequencer/src/mempool/sorting/DefaultMempoolSorting.ts | 6 +++--- packages/sequencer/src/mempool/sorting/MempoolSorting.ts | 4 +--- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index e79cd4c7c..5300c50eb 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -81,16 +81,13 @@ export class PrivateMempool extends SequencerModule implements Mempool { offset?: number, limit?: number ): Promise { - const txs = await this.transactionStorage.getPendingUserTransactions( + let txs = await this.transactionStorage.getPendingUserTransactions( offset ?? 0, limit ); if (this.mempoolSorting.enablePostSorting()) { - // Sorts in place - txs.sort( - this.mempoolSorting.presortingPriority.bind(this.mempoolSorting) - ); + txs = this.mempoolSorting.postSorting(txs); } return txs; diff --git a/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts b/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts index 88b3500fd..d8666ad7e 100644 --- a/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts +++ b/packages/sequencer/src/mempool/sorting/DefaultMempoolSorting.ts @@ -21,12 +21,12 @@ export class DefaultMempoolSorting return false; } - public postSorting(a: PendingTransaction, b: PendingTransaction): number { - return 0; + public postSorting(transactions: PendingTransaction[]): PendingTransaction[] { + return transactions; } public presortingPriority(tx: PendingTransaction): number { // This means we order by first in, first out in the db - return -Date.now(); + return Date.UTC(2500, 0) - Date.now(); } } diff --git a/packages/sequencer/src/mempool/sorting/MempoolSorting.ts b/packages/sequencer/src/mempool/sorting/MempoolSorting.ts index 9c74eafb0..fde5370e9 100644 --- a/packages/sequencer/src/mempool/sorting/MempoolSorting.ts +++ b/packages/sequencer/src/mempool/sorting/MempoolSorting.ts @@ -21,8 +21,6 @@ export interface MempoolSorting { /** * Postsorting happens on the sequencer-side. It's less fast but can take in any two * transactions and directly compare them based on arbitrary logic - * @param a - * @param b */ - postSorting(a: PendingTransaction, b: PendingTransaction): number; + postSorting(transactions: PendingTransaction[]): PendingTransaction[]; } From 06825661a83d78467e11b282c745fc1c2262422e Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 15:25:26 +0100 Subject: [PATCH 119/155] Added mempool type configuration --- .../PrismaBlockProduction.test.ts | 2 +- .../src/mempool/private/PrivateMempool.ts | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/persistance/test-integration/PrismaBlockProduction.test.ts b/packages/persistance/test-integration/PrismaBlockProduction.test.ts index f6299f6b1..e9d335156 100644 --- a/packages/persistance/test-integration/PrismaBlockProduction.test.ts +++ b/packages/persistance/test-integration/PrismaBlockProduction.test.ts @@ -251,7 +251,7 @@ describe("prisma integration", () => { PrismaTransactionStorage ); - const txs = await txResolver.getPendingUserTransactions(); + const txs = await txResolver.getPendingUserTransactions(0); expectDefined(transaction.transaction); diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index 5300c50eb..c2e3c4e14 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -15,8 +15,15 @@ import { IncomingMessagesService } from "../../settlement/messages/IncomingMessa import { MempoolSorting } from "../sorting/MempoolSorting"; import { DefaultMempoolSorting } from "../sorting/DefaultMempoolSorting"; +type PrivateMempoolConfig = { + type?: "hybrid" | "private" | "based"; +}; + @sequencerModule() -export class PrivateMempool extends SequencerModule implements Mempool { +export class PrivateMempool + extends SequencerModule + implements Mempool +{ public readonly events = new EventEmitter(); private readonly mempoolSorting: MempoolSorting; @@ -35,6 +42,10 @@ export class PrivateMempool extends SequencerModule implements Mempool { this.mempoolSorting = mempoolSorting ?? new DefaultMempoolSorting(); } + private type() { + return this.config.type ?? "hybrid"; + } + public async length(): Promise { const txs = await this.transactionStorage.getPendingUserTransactions(0); return txs.length; @@ -81,6 +92,10 @@ export class PrivateMempool extends SequencerModule implements Mempool { offset?: number, limit?: number ): Promise { + if (this.type() === "based") { + return []; + } + let txs = await this.transactionStorage.getPendingUserTransactions( offset ?? 0, limit @@ -95,6 +110,9 @@ export class PrivateMempool extends SequencerModule implements Mempool { @trace("mempool.get_mandatory_txs") public async getMandatoryTxs(): Promise { + if (this.type() === "private") { + return []; + } return (await this.messageService?.getPendingMessages()) ?? []; } From 0ee566966a02313079bdb849d8c0b83f95975839 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 17 Jan 2026 16:20:05 +0100 Subject: [PATCH 120/155] Fixed tests --- packages/sdk/test/fees-multi-zkprograms.test.ts | 4 +--- packages/sequencer/test-integration/benchmarks/tps.test.ts | 4 +--- packages/sequencer/test/integration/Block-order.test.ts | 4 +--- .../sequencer/test/integration/BlockProductionSize.test.ts | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/sdk/test/fees-multi-zkprograms.test.ts b/packages/sdk/test/fees-multi-zkprograms.test.ts index 53d4389f9..e6921b708 100644 --- a/packages/sdk/test/fees-multi-zkprograms.test.ts +++ b/packages/sdk/test/fees-multi-zkprograms.test.ts @@ -185,9 +185,7 @@ describe("check fee analyzer", () => { }, }, Sequencer: { - Mempool: { - validationEnabled: true, - }, + Mempool: {}, }, }); diff --git a/packages/sequencer/test-integration/benchmarks/tps.test.ts b/packages/sequencer/test-integration/benchmarks/tps.test.ts index b3eca0093..68e2dd5a0 100644 --- a/packages/sequencer/test-integration/benchmarks/tps.test.ts +++ b/packages/sequencer/test-integration/benchmarks/tps.test.ts @@ -104,9 +104,7 @@ export async function createAppChain() { maximumBlockSize: 100, }, BlockTrigger: {}, - Mempool: { - validationEnabled: false, - }, + Mempool: {}, }, Signer: { signer: PrivateKey.random(), diff --git a/packages/sequencer/test/integration/Block-order.test.ts b/packages/sequencer/test/integration/Block-order.test.ts index 0f3ee5649..e13aaf566 100644 --- a/packages/sequencer/test/integration/Block-order.test.ts +++ b/packages/sequencer/test/integration/Block-order.test.ts @@ -98,9 +98,7 @@ describe.each([["InMemory", InMemoryDatabase]])( Sequencer: { Database: {}, BlockTrigger: {}, - Mempool: { - validationEnabled: true, - }, + Mempool: {}, FeeStrategy: {}, BatchProducerModule: {}, BlockProducerModule: {}, diff --git a/packages/sequencer/test/integration/BlockProductionSize.test.ts b/packages/sequencer/test/integration/BlockProductionSize.test.ts index 109b576c5..67d6d7ca6 100644 --- a/packages/sequencer/test/integration/BlockProductionSize.test.ts +++ b/packages/sequencer/test/integration/BlockProductionSize.test.ts @@ -70,9 +70,7 @@ describe("block limit", () => { Sequencer: { Database: {}, BlockTrigger: {}, - Mempool: { - validationEnabled: true, - }, + Mempool: {}, BatchProducerModule: {}, BlockProducerModule: { maximumBlockSize: maxBlockSize, From 284d6424f3df478c5fa32fe34bf206f2e1d9a1da Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 22 Jan 2026 12:29:59 +0300 Subject: [PATCH 121/155] feat: add a decorator method for checking progress of methods --- packages/sequencer/src/helpers/BusyGuard.ts | 37 +++++++++++++++++++++ packages/sequencer/src/index.ts | 1 + 2 files changed, 38 insertions(+) create mode 100644 packages/sequencer/src/helpers/BusyGuard.ts diff --git a/packages/sequencer/src/helpers/BusyGuard.ts b/packages/sequencer/src/helpers/BusyGuard.ts new file mode 100644 index 000000000..0f117b697 --- /dev/null +++ b/packages/sequencer/src/helpers/BusyGuard.ts @@ -0,0 +1,37 @@ +import { log } from "@proto-kit/common"; +/** + * Decorator that ensures a function/method is not currently in use. + * Mostly useful for production of blocks, batches and tasks. + */ +export function ensureNotBusy() { + return function InnerFunction( + target: object, + methodName: string, + descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise> + ) { + const originalMethod = descriptor.value!; + + descriptor.value = async function decorator( + this: { inProgress: boolean }, + ...args: any[] + ) { + if (this.inProgress === true) { + log.info(`${methodName.toString()} is in use at the moment.`); + return undefined; + } + + this.inProgress = true; + try { + return await originalMethod.apply(this, args); + } catch (error: unknown) { + if (error instanceof Error) { + throw error; + } else { + log.error(error); + } + } finally { + this.inProgress = false; + } + }; + }; +} diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index a0fee0179..813bc66d5 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -1,4 +1,5 @@ export * from "./helpers/utils"; +export * from "./helpers/BusyGuard"; export * from "./mempool/Mempool"; export * from "./mempool/PendingTransaction"; export * from "./mempool/CompressedSignature"; From 3c995ccb8c2d3c8b848fe389b5c7dad2b051d2c7 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 22 Jan 2026 12:30:59 +0300 Subject: [PATCH 122/155] refactor: replace use of inProgress flag with decorator method --- .../production/BatchProducerModule.ts | 37 +------- .../sequencing/BlockProducerModule.ts | 49 ++++------- .../production/trigger/TimedBlockTrigger.ts | 22 ++--- .../src/worker/queue/LocalTaskQueue.ts | 85 +++++++++---------- 4 files changed, 70 insertions(+), 123 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index 545190624..87eb550b6 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -17,6 +17,7 @@ import { BlockWithResult } from "../../storage/model/Block"; import type { Database } from "../../storage/Database"; import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; import { CachedLinkedLeafStore } from "../../state/lmt/CachedLinkedLeafStore"; +import { ensureNotBusy } from "../../helpers/BusyGuard"; import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer"; import { BatchTracingService } from "./tracing/BatchTracingService"; @@ -44,8 +45,6 @@ const errors = { */ @sequencerModule() export class BatchProducerModule extends SequencerModule { - private productionInProgress = false; - public constructor( @inject("AsyncLinkedLeafStore") private readonly merkleStore: AsyncLinkedLeafStore, @@ -64,41 +63,11 @@ export class BatchProducerModule extends SequencerModule { * transactions that are present in the mempool. This function should also * be the one called by BlockTriggerss */ + @ensureNotBusy() public async createBatch( blocks: BlockWithResult[] ): Promise { - if (!this.productionInProgress) { - try { - this.productionInProgress = true; - - const batch = await this.tryProduceBatch(blocks); - - this.productionInProgress = false; - - return batch; - } catch (error: unknown) { - this.productionInProgress = false; - // TODO Check if that still makes sense - if (error instanceof Error) { - if ( - !error.message.includes( - "Can't create a block with zero transactions" - ) - ) { - log.error(error); - } - - throw error; - } else { - log.error(error); - } - } - } else { - log.debug( - "Skipping new block production because production is still in progress" - ); - } - return undefined; + return await this.tryProduceBatch(blocks); } private async tryProduceBatch( diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 8f035c338..2e1f8858b 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -27,6 +27,7 @@ import { IncomingMessagesService } from "../../../settlement/messages/IncomingMe import { Tracer } from "../../../logging/Tracer"; import { trace } from "../../../logging/trace"; import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; +import { ensureNotBusy } from "../../../helpers/BusyGuard"; import { BlockProductionService } from "./BlockProductionService"; import { BlockResultService } from "./BlockResultService"; @@ -38,8 +39,6 @@ export interface BlockConfig { @sequencerModule() export class BlockProducerModule extends SequencerModule { - private productionInProgress = false; - public constructor( @inject("Mempool") private readonly mempool: Mempool, @inject("IncomingMessagesService", { isOptional: true }) @@ -140,37 +139,25 @@ export class BlockProducerModule extends SequencerModule { return result; } + @ensureNotBusy() public async tryProduceBlock(): Promise { - if (!this.productionInProgress) { - try { - const block = await this.produceBlock(); - - if (block === undefined) { - if (!this.allowEmptyBlock()) { - log.info("No transactions in mempool, skipping production"); - } else { - log.error("Something wrong happened, skipping block"); - } - return undefined; - } + const block = await this.produceBlock(); - log.info( - `Produced block #${block.height.toBigInt()} (${block.transactions.length} txs)` - ); - this.prettyPrintBlockContents(block); - - return block; - } catch (error: unknown) { - if (error instanceof Error) { - throw error; - } else { - log.error(error); - } - } finally { - this.productionInProgress = false; + if (block === undefined) { + if (!this.allowEmptyBlock()) { + log.info("No transactions in mempool, skipping production"); + } else { + log.error("Something wrong happened, skipping block"); } + return undefined; } - return undefined; + + log.info( + `Produced block #${block.height.toBigInt()} (${block.transactions.length} txs)` + ); + this.prettyPrintBlockContents(block); + + return block; } // TODO Move to different service, to remove dependency on mempool and messagequeue @@ -220,8 +207,6 @@ export class BlockProducerModule extends SequencerModule { @trace("block") private async produceBlock(): Promise { - this.productionInProgress = true; - const { txs, metadata } = await this.collectProductionData(); // Skip production if no transactions are available for now @@ -263,8 +248,6 @@ export class BlockProducerModule extends SequencerModule { ); } - this.productionInProgress = false; - return blockResult?.block; } diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index 73b630be1..9818e3277 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -12,6 +12,7 @@ import { BridgingModule, SettlementTokenConfig, } from "../../../settlement/BridgingModule"; +import { ensureNotBusy } from "../../../helpers/BusyGuard"; import { BlockEvents, BlockTriggerBase } from "./BlockTrigger"; @@ -45,9 +46,6 @@ export class TimedBlockTrigger private interval?: any; - // TODO Move that logic to somewhere proper - private settlementInProgress = false; - public constructor( @inject("BatchProducerModule", { isOptional: true }) batchProducerModule: BatchProducerModule | undefined, @@ -123,15 +121,9 @@ export class TimedBlockTrigger // otherwise treat as unproven-only if ( settlementInterval !== undefined && - totalTime % settlementInterval === 0 && - !this.settlementInProgress + totalTime % settlementInterval === 0 ) { - this.settlementInProgress = true; - const batch = await this.produceBatch(); - if (batch !== undefined) { - await this.settle(batch, this.config.settlementTokenConfig); - } - this.settlementInProgress = false; + await this.tryProduceSettlement(); } } catch (error) { log.error(error); @@ -151,6 +143,14 @@ export class TimedBlockTrigger } } + @ensureNotBusy() + private async tryProduceSettlement(): Promise { + const batch = await this.produceBatch(); + if (batch !== undefined) { + await this.settle(batch, this.config.settlementTokenConfig); + } + } + public async close(): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument clearInterval(this.interval); diff --git a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts index e325508ca..70daf9521 100644 --- a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts +++ b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts @@ -3,6 +3,7 @@ import { log, mapSequential, noop, sleep } from "@proto-kit/common"; import { sequencerModule } from "../../sequencer/builder/SequencerModule"; import { TaskPayload } from "../flow/Task"; import { Closeable } from "../../sequencer/builder/Closeable"; +import { ensureNotBusy } from "../../helpers/BusyGuard"; import { InstantiatedQueue, TaskQueue } from "./TaskQueue"; import { ListenerList } from "./ListenerList"; @@ -91,56 +92,50 @@ export class LocalTaskQueue [key: string]: QueueListener[] | undefined; } = {}; - private taskInProgress = false; - + @ensureNotBusy() public async workNextTasks() { - if (this.taskInProgress) { - return; - } - this.taskInProgress = true; - - // Collect all tasks - const tasksToExecute = Object.entries(this.queuedTasks).flatMap( - ([queueName, tasks]) => { - if (tasks.length > 0 && this.workers[queueName]) { - const functions = tasks.map((task) => async () => { - // Execute task in worker - - log.trace(`Working ${task.payload.name} with id ${task.taskId}`); - - const payload = await this.workers[queueName]?.handler( - task.payload - ); - - if (payload === "closed" || payload === undefined) { - return; - } - log.trace("LocalTaskQueue got", JSON.stringify(payload)); - - // Notify listeners about result - const listenerPromises = this.listeners[queueName]?.map( - async (listener) => { - await listener(payload); - } - ); - await Promise.all(listenerPromises || []); - }); - this.queuedTasks[queueName] = []; - return functions; - } + let hasMoreTasks = true; - return []; - } - ); + while (hasMoreTasks) { + // Collect all tasks + const tasksToExecute = Object.entries(this.queuedTasks).flatMap( + ([queueName, tasks]) => { + if (tasks.length > 0 && this.workers[queueName]) { + const functions = tasks.map((task) => async () => { + // Execute task in worker - // Execute all tasks - await mapSequential(tasksToExecute, async (task) => await task()); + log.trace(`Working ${task.payload.name} with id ${task.taskId}`); + + const payload = await this.workers[queueName]?.handler( + task.payload + ); + + if (payload === "closed" || payload === undefined) { + return; + } + log.trace("LocalTaskQueue got", JSON.stringify(payload)); + + // Notify listeners about result + const listenerPromises = this.listeners[queueName]?.map( + async (listener) => { + await listener(payload); + } + ); + await Promise.all(listenerPromises || []); + }); + this.queuedTasks[queueName] = []; + return functions; + } + + return []; + } + ); - this.taskInProgress = false; + // Execute all tasks + await mapSequential(tasksToExecute, async (task) => await task()); - // In case new tasks came up in the meantime, execute them as well - if (tasksToExecute.length > 0) { - await this.workNextTasks(); + // Continue loop only if we processed tasks (more may have arrived) + hasMoreTasks = tasksToExecute.length > 0; } } From ab05c0c203f3f5e9785f84f6d740b9ab0c281847 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 22 Jan 2026 16:00:55 +0300 Subject: [PATCH 123/155] style: fix lint errors --- packages/sequencer/src/helpers/BusyGuard.ts | 3 ++- packages/sequencer/src/worker/queue/LocalTaskQueue.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/helpers/BusyGuard.ts b/packages/sequencer/src/helpers/BusyGuard.ts index 0f117b697..530a1bbc4 100644 --- a/packages/sequencer/src/helpers/BusyGuard.ts +++ b/packages/sequencer/src/helpers/BusyGuard.ts @@ -11,7 +11,8 @@ export function ensureNotBusy() { ) { const originalMethod = descriptor.value!; - descriptor.value = async function decorator( + // eslint-disable-next-line consistent-return + descriptor.value = async function value( this: { inProgress: boolean }, ...args: any[] ) { diff --git a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts index 70daf9521..7b1e5eb9f 100644 --- a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts +++ b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts @@ -97,7 +97,6 @@ export class LocalTaskQueue let hasMoreTasks = true; while (hasMoreTasks) { - // Collect all tasks const tasksToExecute = Object.entries(this.queuedTasks).flatMap( ([queueName, tasks]) => { if (tasks.length > 0 && this.workers[queueName]) { @@ -118,6 +117,7 @@ export class LocalTaskQueue // Notify listeners about result const listenerPromises = this.listeners[queueName]?.map( async (listener) => { + // eslint-disable-next-line no-await-in-loop await listener(payload); } ); From ea7809a55034d2834e7cc008d28e2e8e9ebf1176 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 22 Jan 2026 23:29:20 +0300 Subject: [PATCH 124/155] refactor: revert applying ensureNotBusy decorator on LocalTaskQueue --- .../src/worker/queue/LocalTaskQueue.ts | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts index 7b1e5eb9f..eaad97c58 100644 --- a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts +++ b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts @@ -92,50 +92,56 @@ export class LocalTaskQueue [key: string]: QueueListener[] | undefined; } = {}; - @ensureNotBusy() - public async workNextTasks() { - let hasMoreTasks = true; + private taskInProgress = false; - while (hasMoreTasks) { - const tasksToExecute = Object.entries(this.queuedTasks).flatMap( - ([queueName, tasks]) => { - if (tasks.length > 0 && this.workers[queueName]) { - const functions = tasks.map((task) => async () => { - // Execute task in worker - - log.trace(`Working ${task.payload.name} with id ${task.taskId}`); - - const payload = await this.workers[queueName]?.handler( - task.payload - ); - - if (payload === "closed" || payload === undefined) { - return; +public async workNextTasks() { + if (this.taskInProgress) { + return; + } + this.taskInProgress = true; + + // Collect all tasks + const tasksToExecute = Object.entries(this.queuedTasks).flatMap( + ([queueName, tasks]) => { + if (tasks.length > 0 && this.workers[queueName]) { + const functions = tasks.map((task) => async () => { + // Execute task in worker + + log.trace(`Working ${task.payload.name} with id ${task.taskId}`); + + const payload = await this.workers[queueName]?.handler( + task.payload + ); + + if (payload === "closed" || payload === undefined) { + return; + } + log.trace("LocalTaskQueue got", JSON.stringify(payload)); + + // Notify listeners about result + const listenerPromises = this.listeners[queueName]?.map( + async (listener) => { + await listener(payload); } - log.trace("LocalTaskQueue got", JSON.stringify(payload)); - - // Notify listeners about result - const listenerPromises = this.listeners[queueName]?.map( - async (listener) => { - // eslint-disable-next-line no-await-in-loop - await listener(payload); - } - ); - await Promise.all(listenerPromises || []); - }); - this.queuedTasks[queueName] = []; - return functions; - } - - return []; + ); + await Promise.all(listenerPromises || []); + }); + this.queuedTasks[queueName] = []; + return functions; } - ); - // Execute all tasks - await mapSequential(tasksToExecute, async (task) => await task()); + return []; + } + ); + + // Execute all tasks + await mapSequential(tasksToExecute, async (task) => await task()); + + this.taskInProgress = false; - // Continue loop only if we processed tasks (more may have arrived) - hasMoreTasks = tasksToExecute.length > 0; + // In case new tasks came up in the meantime, execute them as well + if (tasksToExecute.length > 0) { + await this.workNextTasks(); } } From 602a492bf71720b61d995f12302a30be7c97dabc Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 22 Jan 2026 23:29:59 +0300 Subject: [PATCH 125/155] refactor: update use of ensureNotBusy decorator --- packages/sequencer/src/helpers/BusyGuard.ts | 34 ++++++++----------- .../production/BatchProducerModule.ts | 4 +-- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/sequencer/src/helpers/BusyGuard.ts b/packages/sequencer/src/helpers/BusyGuard.ts index 530a1bbc4..00c043b6b 100644 --- a/packages/sequencer/src/helpers/BusyGuard.ts +++ b/packages/sequencer/src/helpers/BusyGuard.ts @@ -3,36 +3,32 @@ import { log } from "@proto-kit/common"; * Decorator that ensures a function/method is not currently in use. * Mostly useful for production of blocks, batches and tasks. */ -export function ensureNotBusy() { - return function InnerFunction( - target: object, +export function ensureNotBusy() { + + let inProgress = false; + + return function innerFunction( + _target: T, methodName: string, descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise> - ) { + ): void { const originalMethod = descriptor.value!; - // eslint-disable-next-line consistent-return - descriptor.value = async function value( - this: { inProgress: boolean }, - ...args: any[] + descriptor.value = async function wrapped( + this: T, + ...args: unknown[] ) { - if (this.inProgress === true) { - log.info(`${methodName.toString()} is in use at the moment.`); + if (inProgress) { + log.trace(`${methodName} is in use at the moment.`); return undefined; } - this.inProgress = true; + inProgress = true; try { return await originalMethod.apply(this, args); - } catch (error: unknown) { - if (error instanceof Error) { - throw error; - } else { - log.error(error); - } } finally { - this.inProgress = false; + inProgress = false; } }; }; -} +} \ No newline at end of file diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index 87eb550b6..a27409f88 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -63,13 +63,13 @@ export class BatchProducerModule extends SequencerModule { * transactions that are present in the mempool. This function should also * be the one called by BlockTriggerss */ - @ensureNotBusy() public async createBatch( blocks: BlockWithResult[] ): Promise { return await this.tryProduceBatch(blocks); } - + + @ensureNotBusy() private async tryProduceBatch( blocks: BlockWithResult[] ): Promise { From 7205261be2a79c5f6a49366296b9431fac653c46 Mon Sep 17 00:00:00 2001 From: saitunc Date: Thu, 22 Jan 2026 23:31:24 +0300 Subject: [PATCH 126/155] style: run lint fix --- packages/sequencer/src/helpers/BusyGuard.ts | 8 ++------ .../src/protocol/production/BatchProducerModule.ts | 2 +- packages/sequencer/src/worker/queue/LocalTaskQueue.ts | 3 +-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/sequencer/src/helpers/BusyGuard.ts b/packages/sequencer/src/helpers/BusyGuard.ts index 00c043b6b..f38dc77a1 100644 --- a/packages/sequencer/src/helpers/BusyGuard.ts +++ b/packages/sequencer/src/helpers/BusyGuard.ts @@ -4,7 +4,6 @@ import { log } from "@proto-kit/common"; * Mostly useful for production of blocks, batches and tasks. */ export function ensureNotBusy() { - let inProgress = false; return function innerFunction( @@ -14,10 +13,7 @@ export function ensureNotBusy() { ): void { const originalMethod = descriptor.value!; - descriptor.value = async function wrapped( - this: T, - ...args: unknown[] - ) { + descriptor.value = async function wrapped(this: T, ...args: unknown[]) { if (inProgress) { log.trace(`${methodName} is in use at the moment.`); return undefined; @@ -31,4 +27,4 @@ export function ensureNotBusy() { } }; }; -} \ No newline at end of file +} diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index a27409f88..ac38af420 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -68,7 +68,7 @@ export class BatchProducerModule extends SequencerModule { ): Promise { return await this.tryProduceBatch(blocks); } - + @ensureNotBusy() private async tryProduceBatch( blocks: BlockWithResult[] diff --git a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts index eaad97c58..e325508ca 100644 --- a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts +++ b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts @@ -3,7 +3,6 @@ import { log, mapSequential, noop, sleep } from "@proto-kit/common"; import { sequencerModule } from "../../sequencer/builder/SequencerModule"; import { TaskPayload } from "../flow/Task"; import { Closeable } from "../../sequencer/builder/Closeable"; -import { ensureNotBusy } from "../../helpers/BusyGuard"; import { InstantiatedQueue, TaskQueue } from "./TaskQueue"; import { ListenerList } from "./ListenerList"; @@ -94,7 +93,7 @@ export class LocalTaskQueue private taskInProgress = false; -public async workNextTasks() { + public async workNextTasks() { if (this.taskInProgress) { return; } From 63456143bebd6bf4651d628c3a83d3771b5ab3d4 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 22 Jan 2026 22:03:34 +0100 Subject: [PATCH 127/155] Fixed Ordering and added path persistence --- .../production/sequencing/BlockBuilder.ts | 7 +- .../sequencing/BlockProducerModule.ts | 12 +- .../sequencing/BlockProductionService.ts | 20 ++- .../production/sequencing/Ordering.ts | 139 +++++++++++++----- .../storage/adapters/PostgresStateModule.ts | 11 -- .../inmemory/InMemoryTransactionStorage.ts | 41 +++++- packages/sequencer/src/storage/model/Batch.ts | 8 - packages/sequencer/src/storage/model/Block.ts | 5 - .../repositories/TransactionStorage.ts | 7 + .../test/integration/Block-order.test.ts | 51 ++++++- 10 files changed, 226 insertions(+), 75 deletions(-) delete mode 100644 packages/sequencer/src/storage/adapters/PostgresStateModule.ts diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts index b3f535452..6ebe0b43c 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts @@ -22,7 +22,7 @@ import { TransactionExecutionResultStatus, TransactionExecutionService, } from "./TransactionExecutionService"; -import { Ordering } from "./Ordering"; +import { Ordering, OrderingMetadata } from "./Ordering"; // TODO Allow user overriding of the blockbuilder @injectable() @@ -67,6 +67,7 @@ export class BlockBuilder { ): Promise<{ blockState: BlockTrackers; executionResults: TransactionExecutionResultStatus[]; + orderingMetadata: OrderingMetadata; }> { let blockState = state; const exceptionExecutionResults: TransactionExecutionResultStatus[] = []; @@ -139,11 +140,13 @@ export class BlockBuilder { } } - const orderingResults = ordering.getResults(); + const { results: orderingResults, orderingMetadata } = + ordering.getResults(); return { blockState, executionResults: orderingResults.concat(...exceptionExecutionResults), + orderingMetadata, }; } } diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 704116e59..617bb8802 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -25,6 +25,7 @@ import { Database } from "../../../storage/Database"; import { Tracer } from "../../../logging/Tracer"; import { trace } from "../../../logging/trace"; import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; +import { TransactionStorage } from "../../../storage/repositories/TransactionStorage"; import { BlockProductionService } from "./BlockProductionService"; import { BlockResultService } from "./BlockResultService"; @@ -46,6 +47,8 @@ export class BlockProducerModule extends SequencerModule { private readonly unprovenLinkedLeafStore: AsyncLinkedLeafStore, @inject("BlockQueue") private readonly blockQueue: BlockQueue, + @inject("TransactionStorage") + private readonly transactionStorage: TransactionStorage, @inject("BlockTreeStore") private readonly blockTreeStore: AsyncMerkleTreeStore, private readonly productionService: BlockProductionService, @@ -209,7 +212,7 @@ export class BlockProducerModule extends SequencerModule { ); if (blockResult !== undefined) { - const { block, stateChanges } = blockResult; + const { block, stateChanges, orderingMetadata } = blockResult; // Skip production if no transactions are available for now if (block.transactions.length === 0 && !this.allowEmptyBlock()) { @@ -233,6 +236,13 @@ export class BlockProducerModule extends SequencerModule { .filter((x) => x.type === "shouldRemove") .map((x) => x.hash) ); + + await this.transactionStorage.reportChangedPaths( + orderingMetadata.allChangedPaths + ); + await this.transactionStorage.reportSkippedTransactions( + orderingMetadata.skippedPaths + ); }); }, { diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts index 852b5eb40..95ad398a4 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts @@ -35,6 +35,7 @@ import { TransactionExecutionResultStatus, } from "./TransactionExecutionService"; import { BlockBuilder } from "./BlockBuilder"; +import { OrderingMetadata } from "./Ordering"; function isIncludedTxs(x: TransactionExecutionResultStatus): x is { status: "included"; @@ -111,6 +112,7 @@ export class BlockProductionService { hash: string; type: "included" | "skipped" | "shouldRemove"; }[]; + orderingMetadata: OrderingMetadata; } | undefined > { @@ -142,13 +144,16 @@ export class BlockProductionService { UntypedStateTransition.fromStateTransition(transition) ); - const { blockState: newBlockState, executionResults } = - await this.blockBuilder.buildBlock( - stateService, - networkState, - blockState, - maximumBlockSize - ); + const { + blockState: newBlockState, + executionResults, + orderingMetadata, + } = await this.blockBuilder.buildBlock( + stateService, + networkState, + blockState, + maximumBlockSize + ); const previousBlockHash = lastResult.blockHash === 0n ? undefined : Field(lastResult.blockHash); @@ -206,6 +211,7 @@ export class BlockProductionService { }, stateChanges: stateService, includedTxs, + orderingMetadata, }; } } diff --git a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts index f9b7f1e73..9cc8132ae 100644 --- a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts +++ b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts @@ -19,6 +19,61 @@ export type OrderingReport = { shouldRemove: boolean; }; +export type OrderingMetadata = { + skippedPaths: { [p: string]: bigint[] }; + allChangedPaths: bigint[]; +}; + +export class PathResolution { + failedTxIds = new Map(); + + paths = new Map(); + + public resolvePaths(paths: bigint[]) { + const allSymbols = paths.flatMap((key) => { + const symbols = this.paths.get(key); + if (symbols !== undefined) { + this.paths.delete(key); + } + return symbols ?? []; + }); + + return allSymbols + .map((symbol) => { + const tx = this.failedTxIds.get(symbol); + this.failedTxIds.delete(symbol); + return tx; + }) + .filter(filterNonUndefined); + } + + public pushPaths(object: Object, paths: bigint[]) { + const symbol = Symbol("tx"); + this.failedTxIds.set(symbol, object); + + paths.forEach((path) => { + const symbols = this.paths.get(path) ?? []; + symbols.push(symbol); + this.paths.set(path, symbols); + }); + } + + // TODO I hate how inefficient this function is - the tradeoff here is + // lookup performance during block production vs. after + public retrieveUnresolved(key: (o: Object) => string) { + const paths = new Map(); + for (const [path, txs] of this.paths.entries()) { + txs.forEach((tx) => { + const hash = key(this.failedTxIds.get(tx)!); + const thisPaths = paths.get(hash) ?? []; + thisPaths.push(path); + paths.set(hash, thisPaths); + }); + } + return Object.fromEntries(paths.entries()); + } +} + export class Ordering { public constructor( private readonly mempool: Mempool, @@ -36,42 +91,26 @@ export class Ordering { userTxOffset = 0; // For dependency resolution - failedTxIds = new Map(); + pathResolution = new PathResolution(); - paths = new Map(); + allChangedPaths = new Set(); public resolvePaths(result: TransactionExecutionResult) { - const keys = allKeys(result.stateTransitions[0].stateTransitions); - - const allSymbols = keys.flatMap((key) => { - const symbols = this.paths.get(key); - if (symbols !== undefined) { - this.paths.delete(key); - } - return symbols ?? []; - }); + const paths = allKeys( + result.stateTransitions.flatMap((x) => x.stateTransitions) + ); - const txs = allSymbols - .map((symbol) => { - const tx = this.failedTxIds.get(symbol); - this.failedTxIds.delete(symbol); - return tx; - }) - .filter(filterNonUndefined); + const txs = this.pathResolution.resolvePaths(paths); this.transactionQueue.push(...txs); + + paths.forEach((path) => this.allChangedPaths.add(path)); } private pushFailed(result: TransactionExecutionResult) { - const symbol = Symbol("tx"); - this.failedTxIds.set(symbol, result.tx); - const keys = allKeys(result.stateTransitions[0].stateTransitions); - keys.forEach((key) => { - const symbols = this.paths.get(key) ?? []; - symbols.push(symbol); - this.paths.set(key, symbols); - }); + + this.pathResolution.pushPaths(result.tx, keys); } public reportResult({ result, shouldRemove }: OrderingReport) { @@ -100,29 +139,41 @@ export class Ordering { } } + private space() { + return this.sizeLimit - this.ordered; + } + + private mandoQueue: PendingTransaction[] = []; + public async requestNextTransaction() { - if (this.transactionQueue.length === 0) { - // Fetch messages - if (!this.mandatoryTransactionsCompleted) { - const mandos = await this.mempool.getMandatoryTxs(); - this.transactionQueue.push(...mandos); - this.mandatoryTransactionsCompleted = true; - } + // Fetch messages + if (!this.mandatoryTransactionsCompleted) { + const mandos = await this.mempool.getMandatoryTxs(); + this.mandoQueue.push(...mandos); + this.mandatoryTransactionsCompleted = true; + } + + if (this.mandoQueue.length > 0) { + return this.mandoQueue.shift(); + } - // Fetch as much txs as space is available - const space = this.sizeLimit - this.ordered; - if (space > 0) { + const space = this.space(); + if (space > 0) { + if (this.transactionQueue.length === 0) { + // Fetch as many txs as space is availabe const newTxs = await this.mempool.getTxs(this.userTxOffset, space); this.userTxOffset += space; this.transactionQueue.push(...newTxs); } - } - return this.transactionQueue.shift(); + return this.transactionQueue.shift(); + } else { + return undefined; + } } public getResults() { - return this.results + const results = this.results .reverse() .filter( distinctByPredicate( @@ -130,5 +181,15 @@ export class Ordering { ) ) .reverse(); + + return { + results, + orderingMetadata: { + skippedPaths: this.pathResolution.retrieveUnresolved((tx) => + tx.hash().toString() + ), + allChangedPaths: Array.from(this.allChangedPaths), + }, + }; } } diff --git a/packages/sequencer/src/storage/adapters/PostgresStateModule.ts b/packages/sequencer/src/storage/adapters/PostgresStateModule.ts deleted file mode 100644 index 7e9c22e24..000000000 --- a/packages/sequencer/src/storage/adapters/PostgresStateModule.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { - sequencerModule, - SequencerModule, -} from "../../sequencer/builder/SequencerModule"; - -@sequencerModule() -export class PostgresStateModule extends SequencerModule { - public async start(): Promise { - return undefined; - } -} diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts index 90732770c..09e493dfa 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts @@ -1,9 +1,11 @@ import { inject, injectable } from "tsyringe"; import { Field } from "o1js"; +import { splitArray } from "@proto-kit/common"; import { TransactionStorage } from "../repositories/TransactionStorage"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { BlockStorage } from "../repositories/BlockStorage"; +import { PathResolution } from "../../protocol/production/sequencing/Ordering"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; @@ -56,7 +58,10 @@ export class InMemoryTransactionStorage implements TransactionStorage { this.sortQueue(); const from = offset ?? 0; - const to = limit !== undefined ? from + limit : undefined; + const to = + limit !== undefined + ? Math.min(from + limit, this.queue.length) + : undefined; return this.queue.slice(from, to).map(({ tx }) => tx); } @@ -131,4 +136,38 @@ export class InMemoryTransactionStorage implements TransactionStorage { } return undefined; } + + private pathResolution = new PathResolution(); + + private unresolvedSet: { tx: PendingTransaction; sortingValue: number }[] = + []; + + public async reportSkippedTransactions( + paths: Record + ): Promise { + Object.entries(paths).forEach(([txHash, paths]) => { + this.pathResolution.pushPaths(txHash, paths); + }); + + // Remove all unresolved txs from queue and append them to the unresolvedSet + const unresolvedHashes = Object.keys(paths); + const split = splitArray(this.queue, (x) => + unresolvedHashes.includes(x.tx.hash().toString()) ? "unresolved" : "queue" + ); + this.queue = split.queue ?? []; + this.unresolvedSet.push(...(split.unresolved ?? [])); + } + + public async reportChangedPaths(paths: bigint[]): Promise { + const resolved = this.pathResolution.resolvePaths(paths); + + // Move resolved from unresolvedSet to queue, then sort queue + const resolvedSplit = splitArray(this.unresolvedSet, (x) => + resolved.includes(x.tx.hash().toString()) ? "resolved" : "unresolved" + ); + this.queue.push(...(resolvedSplit.resolved ?? [])); + this.unresolvedSet = resolvedSplit.unresolved ?? []; + + this.sortQueue(); + } } diff --git a/packages/sequencer/src/storage/model/Batch.ts b/packages/sequencer/src/storage/model/Batch.ts index 731b14718..65e325a5b 100644 --- a/packages/sequencer/src/storage/model/Batch.ts +++ b/packages/sequencer/src/storage/model/Batch.ts @@ -1,14 +1,6 @@ import { JsonProof } from "o1js"; import { NetworkState } from "@proto-kit/protocol"; -import { PendingTransaction } from "../../mempool/PendingTransaction"; - -export interface BatchTransaction { - tx: PendingTransaction; - status: boolean; - statusMessage?: string; -} - export interface Batch { proof: JsonProof; blockHashes: string[]; diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index da2e44802..2a9ea57d5 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -85,11 +85,6 @@ export interface BlockWithMaybeResult { // eslint-disable-next-line @typescript-eslint/no-redeclare export const BlockWithResult = { - // toBlockProverState: ({ block, result }: BlockWithResult) => ({ - // stateRoot: result.stateRoot, - // - // } satisfies BlockProverStateCommitments), - createEmpty: () => ({ block: { diff --git a/packages/sequencer/src/storage/repositories/TransactionStorage.ts b/packages/sequencer/src/storage/repositories/TransactionStorage.ts index 0a8696bcd..e738660f0 100644 --- a/packages/sequencer/src/storage/repositories/TransactionStorage.ts +++ b/packages/sequencer/src/storage/repositories/TransactionStorage.ts @@ -28,4 +28,11 @@ export interface TransactionStorage { } | undefined >; + + /** + * Mapping hash => path[] + */ + reportSkippedTransactions: (paths: Record) => Promise; + + reportChangedPaths: (paths: bigint[]) => Promise; } diff --git a/packages/sequencer/test/integration/Block-order.test.ts b/packages/sequencer/test/integration/Block-order.test.ts index e13aaf566..6d7a1819f 100644 --- a/packages/sequencer/test/integration/Block-order.test.ts +++ b/packages/sequencer/test/integration/Block-order.test.ts @@ -4,7 +4,7 @@ import { Protocol } from "@proto-kit/protocol"; import { Bool, PrivateKey, UInt64 } from "o1js"; import "reflect-metadata"; import { container } from "tsyringe"; -import { afterEach } from "@jest/globals"; +import { afterEach, expect, jest } from "@jest/globals"; import { InMemoryDatabase, @@ -122,6 +122,8 @@ describe.each([["InMemory", InMemoryDatabase]])( afterEach(async () => { await appChain.close(); + + jest.restoreAllMocks(); }); it("transactions are returned in right order - simple", async () => { @@ -243,5 +245,52 @@ describe.each([["InMemory", InMemoryDatabase]])( expect(txs[5].nonce.toBigInt()).toStrictEqual(1n); expect(txs[5].sender).toStrictEqual(user3PublicKey); }); + + it("transactions are returned in right order in multiple distinct blocks - hardest", async () => { + expect.assertions(18); + + sequencer.resolve("BlockProducerModule").config.maximumBlockSize = 3; + const txStorage = sequencer.resolve("TransactionStorage"); + const getTxsSpy = jest.spyOn(txStorage, "getPendingUserTransactions"); + + await mempoolAddTransactions(user1PrivateKey, 0); + await mempoolAddTransactions(user1PrivateKey, 4); + await mempoolAddTransactions(user1PrivateKey, 5); + await mempoolAddTransactions(user2PrivateKey, 1); + await mempoolAddTransactions(user3PrivateKey, 1); + await mempoolAddTransactions(user2PrivateKey, 0); + await mempoolAddTransactions(user3PrivateKey, 0); + await mempoolAddTransactions(user1PrivateKey, 1); + + let block = await trigger.produceBlock(); + expectDefined(block); + + let txs = block.transactions.map((x) => x.tx); + expect(txs).toHaveLength(3); + + expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); + expect(txs[0].sender).toStrictEqual(user1PublicKey); + expect(txs[1].nonce.toBigInt()).toStrictEqual(0n); + expect(txs[1].sender).toStrictEqual(user2PublicKey); + expect(txs[2].nonce.toBigInt()).toStrictEqual(0n); + expect(txs[2].sender).toStrictEqual(user3PublicKey); + + expect(getTxsSpy).toHaveBeenCalledTimes(3); + + block = await trigger.produceBlock(); + expectDefined(block); + + txs = block.transactions.map((x) => x.tx); + expect(txs).toHaveLength(3); + + expect(txs[0].nonce.toBigInt()).toStrictEqual(1n); + expect(txs[0].sender).toStrictEqual(user2PublicKey); + expect(txs[1].nonce.toBigInt()).toStrictEqual(1n); + expect(txs[1].sender).toStrictEqual(user3PublicKey); + expect(txs[2].nonce.toBigInt()).toStrictEqual(1n); + expect(txs[2].sender).toStrictEqual(user1PublicKey); + + expect(getTxsSpy).toHaveBeenCalledTimes(4); + }); } ); From daf9244f4a256584b064fa0da07a251def335eed Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 22 Jan 2026 22:03:56 +0100 Subject: [PATCH 128/155] Added maximumNonceLookahead to AccountStateHook --- packages/protocol/src/hooks/AccountStateHook.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/protocol/src/hooks/AccountStateHook.ts b/packages/protocol/src/hooks/AccountStateHook.ts index cb5029d87..021b67999 100644 --- a/packages/protocol/src/hooks/AccountStateHook.ts +++ b/packages/protocol/src/hooks/AccountStateHook.ts @@ -14,8 +14,12 @@ export class AccountState extends Struct({ nonce: UInt64, }) {} +export type AccountStateHookConfig = { + maximumNonceLookahead?: number; +}; + @injectable() -export class AccountStateHook extends ProvableTransactionHook { +export class AccountStateHook extends ProvableTransactionHook { @state() public accountState = StateMap.from( PublicKey, AccountState @@ -67,6 +71,10 @@ export class AccountStateHook extends ProvableTransactionHook { const currentNonce = accountState.nonce; - return transaction.nonce.value.lessThan(currentNonce).toBoolean(); + const exceedsMaximumLookahead = transaction.nonce.value.greaterThan( + currentNonce.add(this.config.maximumNonceLookahead ?? 10) + ); + const nonceIsInPast = transaction.nonce.value.lessThan(currentNonce); + return nonceIsInPast.or(exceedsMaximumLookahead).toBoolean(); } } From c66227033e766485fd9261e73afd673e8d7bc3c7 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 22 Jan 2026 22:06:54 +0100 Subject: [PATCH 129/155] Implemented input paths for prisma --- .../migration.sql | 10 +++++++ packages/persistance/prisma/schema.prisma | 10 +++++++ .../prisma/PrismaTransactionStorage.ts | 26 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 packages/persistance/prisma/migrations/20260122210623_transaction_input_paths/migration.sql diff --git a/packages/persistance/prisma/migrations/20260122210623_transaction_input_paths/migration.sql b/packages/persistance/prisma/migrations/20260122210623_transaction_input_paths/migration.sql new file mode 100644 index 000000000..8f9d6cd06 --- /dev/null +++ b/packages/persistance/prisma/migrations/20260122210623_transaction_input_paths/migration.sql @@ -0,0 +1,10 @@ +-- CreateTable +CREATE TABLE "SkippedTransactionInputPaths" ( + "transactionHash" TEXT NOT NULL, + "paths" DECIMAL(78,0)[], + + CONSTRAINT "SkippedTransactionInputPaths_pkey" PRIMARY KEY ("transactionHash") +); + +-- AddForeignKey +ALTER TABLE "SkippedTransactionInputPaths" ADD CONSTRAINT "SkippedTransactionInputPaths_transactionHash_fkey" FOREIGN KEY ("transactionHash") REFERENCES "Transaction"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index a5abc4338..7b3494d20 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -56,6 +56,8 @@ model Transaction { IncomingMessageBatchTransaction IncomingMessageBatchTransaction[] priority TransactionPriority? + + inputPaths SkippedTransactionInputPaths[] } model TransactionPriority { @@ -68,6 +70,14 @@ model TransactionPriority { @@id([transactionHash]) } +model SkippedTransactionInputPaths { + transactionHash String @id + + paths Decimal[] @db.Decimal(78, 0) + + transaction Transaction @relation(fields: [transactionHash], references: [hash]) +} + model TransactionExecutionResult { // TODO Make StateTransitionBatch and StateTransition Table stateTransitions Json @db.Json diff --git a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts index 8f49f0384..297aff552 100644 --- a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts @@ -9,6 +9,7 @@ import { import type { PrismaConnection } from "../../PrismaDatabaseConnection"; import { TransactionMapper } from "./mappers/TransactionMapper"; +import { Decimal } from "./PrismaStateService"; @injectable() export class PrismaTransactionStorage implements TransactionStorage { @@ -127,4 +128,29 @@ export class PrismaTransactionStorage implements TransactionStorage { batch, }; } + + public async reportSkippedTransactions( + paths: Record + ): Promise { + const { prismaClient } = this.connection; + + await prismaClient.skippedTransactionInputPaths.createMany({ + data: Object.entries(paths).map(([transactionHash, pathArray]) => ({ + transactionHash, + paths: pathArray.map((path) => new Decimal(path.toString())), + })), + }); + } + + public async reportChangedPaths(paths: bigint[]): Promise { + const { prismaClient } = this.connection; + + await prismaClient.skippedTransactionInputPaths.deleteMany({ + where: { + paths: { + hasSome: paths.map((path) => new Decimal(path.toString())), + }, + }, + }); + } } From faba128450fcebebd66da0b5cbc13fca9c7643f2 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 22 Jan 2026 22:22:41 +0100 Subject: [PATCH 130/155] Fixed compile error in API --- packages/api/src/graphql/modules/BlockResolver.ts | 12 ++++++------ ...onModel.ts => TransactionExecutionResultModel.ts} | 12 +++++++----- .../storage/inmemory/InMemoryTransactionStorage.ts | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) rename packages/api/src/graphql/modules/model/{BatchTransactionModel.ts => TransactionExecutionResultModel.ts} (69%) diff --git a/packages/api/src/graphql/modules/BlockResolver.ts b/packages/api/src/graphql/modules/BlockResolver.ts index 036184873..0acb876ca 100644 --- a/packages/api/src/graphql/modules/BlockResolver.ts +++ b/packages/api/src/graphql/modules/BlockResolver.ts @@ -4,7 +4,7 @@ import { Arg, Field, ObjectType, Query } from "type-graphql"; import { GraphqlModule, graphqlModule } from "../GraphqlModule"; -import { BatchTransactionModel } from "./model/BatchTransactionModel"; +import { TransactionExecutionResultModel } from "./model/TransactionExecutionResultModel"; @ObjectType() export class BlockModel { @@ -12,9 +12,9 @@ export class BlockModel { return new BlockModel( Number(block.networkState.during.block.height.toBigInt()), block.transactions.map((tx) => - BatchTransactionModel.fromServiceLayerModel({ + TransactionExecutionResultModel.fromServiceLayerModel({ tx: tx.tx, - status: tx.status.toBoolean(), + status: tx.status, statusMessage: tx.statusMessage, }) ), @@ -33,15 +33,15 @@ export class BlockModel { @Field() height: number; - @Field(() => [BatchTransactionModel]) - txs: BatchTransactionModel[]; + @Field(() => [TransactionExecutionResultModel]) + txs: TransactionExecutionResultModel[]; @Field() transactionsHash: string; private constructor( height: number, - txs: BatchTransactionModel[], + txs: TransactionExecutionResultModel[], transactionsHash: string, hash: string, previousBlockHash: string | undefined diff --git a/packages/api/src/graphql/modules/model/BatchTransactionModel.ts b/packages/api/src/graphql/modules/model/TransactionExecutionResultModel.ts similarity index 69% rename from packages/api/src/graphql/modules/model/BatchTransactionModel.ts rename to packages/api/src/graphql/modules/model/TransactionExecutionResultModel.ts index 316218778..0998e59c2 100644 --- a/packages/api/src/graphql/modules/model/BatchTransactionModel.ts +++ b/packages/api/src/graphql/modules/model/TransactionExecutionResultModel.ts @@ -1,16 +1,18 @@ import { ObjectType, Field } from "type-graphql"; -import { BatchTransaction } from "@proto-kit/sequencer"; import { IsBoolean } from "class-validator"; +import { TransactionExecutionResult } from "@proto-kit/sequencer"; import { TransactionObject } from "../MempoolResolver"; @ObjectType() -export class BatchTransactionModel { - public static fromServiceLayerModel(cbt: BatchTransaction) { +export class TransactionExecutionResultModel { + public static fromServiceLayerModel( + cbt: Pick + ) { const { tx, status, statusMessage } = cbt; - return new BatchTransactionModel( + return new TransactionExecutionResultModel( TransactionObject.fromServiceLayerModel(tx), - status, + status.toBoolean(), statusMessage ); } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts index 09e493dfa..0d672d6bc 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts @@ -145,8 +145,8 @@ export class InMemoryTransactionStorage implements TransactionStorage { public async reportSkippedTransactions( paths: Record ): Promise { - Object.entries(paths).forEach(([txHash, paths]) => { - this.pathResolution.pushPaths(txHash, paths); + Object.entries(paths).forEach(([txHash, transactionPaths]) => { + this.pathResolution.pushPaths(txHash, transactionPaths); }); // Remove all unresolved txs from queue and append them to the unresolvedSet From 4cd45a6f968d70021c9f7b5a336e98435a4f0d05 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 23 Jan 2026 00:25:08 +0300 Subject: [PATCH 131/155] refactor: replace unecessary wrapper function --- .../src/protocol/production/BatchProducerModule.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index ac38af420..81ee673be 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -61,16 +61,10 @@ export class BatchProducerModule extends SequencerModule { /** * Main function to call when wanting to create a new block based on the * transactions that are present in the mempool. This function should also - * be the one called by BlockTriggerss + * be the one called by BlockTriggers. */ - public async createBatch( - blocks: BlockWithResult[] - ): Promise { - return await this.tryProduceBatch(blocks); - } - @ensureNotBusy() - private async tryProduceBatch( + public async createBatch( blocks: BlockWithResult[] ): Promise { log.info("Producing batch..."); From 4e6d7fd0c26a0ad133af34e0196bf11b932c4ac8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 22 Jan 2026 23:36:48 +0100 Subject: [PATCH 132/155] Fixed test --- packages/sequencer/test/integration/MempoolTxRemoved.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sequencer/test/integration/MempoolTxRemoved.test.ts b/packages/sequencer/test/integration/MempoolTxRemoved.test.ts index 6d9c4829d..02fae71f5 100644 --- a/packages/sequencer/test/integration/MempoolTxRemoved.test.ts +++ b/packages/sequencer/test/integration/MempoolTxRemoved.test.ts @@ -110,7 +110,7 @@ describe("mempool removal mechanism", () => { expectDefined(block); expect(block.transactions).toHaveLength(1); - await expect(mempool.getTxs()).resolves.toHaveLength(1); + await expect(mempool.getTxs()).resolves.toHaveLength(0); }); it("check only one is included, other is removed", async () => { From 14e84ab0267f151dc0ecf420a6837a8f88766fe3 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 22 Jan 2026 23:37:06 +0100 Subject: [PATCH 133/155] Fixed missing filter clause in PrismaTransactionStorage --- packages/persistance/prisma/schema.prisma | 2 +- .../src/services/prisma/PrismaTransactionStorage.ts | 3 +++ .../sequencer/src/storage/repositories/TransactionStorage.ts | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 7b3494d20..3613a00a6 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -57,7 +57,7 @@ model Transaction { priority TransactionPriority? - inputPaths SkippedTransactionInputPaths[] + inputPaths SkippedTransactionInputPaths? } model TransactionPriority { diff --git a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts index 297aff552..5722421ed 100644 --- a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts @@ -34,6 +34,9 @@ export class PrismaTransactionStorage implements TransactionStorage { isMessage: { equals: false, }, + inputPaths: { + is: null, + }, }, orderBy: { priority: { diff --git a/packages/sequencer/src/storage/repositories/TransactionStorage.ts b/packages/sequencer/src/storage/repositories/TransactionStorage.ts index e738660f0..9106b72d2 100644 --- a/packages/sequencer/src/storage/repositories/TransactionStorage.ts +++ b/packages/sequencer/src/storage/repositories/TransactionStorage.ts @@ -35,4 +35,6 @@ export interface TransactionStorage { reportSkippedTransactions: (paths: Record) => Promise; reportChangedPaths: (paths: bigint[]) => Promise; + + // TODO Add a method to retrieve all conflict transactions and expose it through the APIs } From b9dd7cf8b0dab0007bfa9bb6f7384d405837d8a7 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 24 Jan 2026 18:21:50 +0100 Subject: [PATCH 134/155] Fix state carrying issue with block prover --- .../src/prover/block/BlockProvable.ts | 142 ++++++++++++------ .../protocol/src/prover/block/BlockProver.ts | 91 ++++++----- .../contracts/settlement/SettlementBase.ts | 12 +- .../protocol/src/utils/ProvableHashList.ts | 10 +- .../src/protocol/production/flow/BatchFlow.ts | 5 + .../protocol/production/tasks/NewBlockTask.ts | 4 + .../NewBlockProvingParametersSerializer.ts | 12 +- .../production/tracing/BatchTracingService.ts | 26 +++- .../production/tracing/BlockTracingService.ts | 31 +++- .../test/integration/BlockProduction-test.ts | 1 + 10 files changed, 227 insertions(+), 107 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index c0396b177..04b506b64 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -1,4 +1,4 @@ -import { Bool, Field, Proof, Provable, Struct } from "o1js"; +import { Bool, Field, Poseidon, Proof, Provable, Struct } from "o1js"; import { CompilableModule, WithZkProgrammable } from "@proto-kit/common"; import { StateTransitionProof } from "../statetransition/StateTransitionProvable"; @@ -52,6 +52,90 @@ export class BlockArgumentsBatch extends Struct({ batch: Provable.Array(BlockArguments, BLOCK_ARGUMENT_BATCH_SIZE), }) {} +const BlockProverStateBaseFields = { + eternalTransactionsHash: Field, + incomingMessagesHash: Field, + stateRoot: Field, + blockHashRoot: Field, + blockNumber: Field, + networkStateHash: Field, +}; + +export class BlockProverPublicInput extends Struct({ + // Tracker of the current block prover state + proverStateRemainder: Field, + ...BlockProverStateBaseFields, +}) { + public equals(input: BlockProverPublicInput): Bool { + const output2 = BlockProverPublicInput.toFields(input); + const output1 = BlockProverPublicInput.toFields(this); + return output1 + .map((value1, index) => value1.equals(output2[index])) + .reduce((a, b) => a.and(b)); + } + + public clone() { + return new BlockProverPublicInput( + BlockProverPublicInput.fromFields(BlockProverPublicInput.toFields(this)) + ); + } +} + +export const BlockProverStateCommitments = { + remainders: { + // Commitment to the list of unprocessed (pending) batches of STs that need to be proven + pendingSTBatchesHash: Field, + witnessedRootsHash: Field, + bundlesHash: Field, + }, + ...BlockProverStateBaseFields, +}; + +export class BlockProverStateInput extends Struct(BlockProverStateCommitments) { + public hash() { + return Poseidon.hash(BlockProverStateInput.toFields(this)); + } + + public static fromPublicInput(input: BlockProverPublicInput) { + return new BlockProverStateInput({ + remainders: { + bundlesHash: Field(0), + pendingSTBatchesHash: Field(0), + witnessedRootsHash: Field(0), + }, + eternalTransactionsHash: input.eternalTransactionsHash, + incomingMessagesHash: input.incomingMessagesHash, + stateRoot: input.stateRoot, + blockHashRoot: input.blockHashRoot, + blockNumber: input.blockNumber, + networkStateHash: input.networkStateHash, + }); + } + + public finalize(condition: Bool) { + condition + .implies( + this.remainders.bundlesHash + .equals(0) + .and(this.remainders.pendingSTBatchesHash.equals(0)) + .and(this.remainders.witnessedRootsHash.equals(0)) + ) + .assertTrue("Remainers not fully removed"); + + return new BlockProverPublicInput({ + proverStateRemainder: Field(0), + eternalTransactionsHash: this.eternalTransactionsHash, + incomingMessagesHash: this.incomingMessagesHash, + stateRoot: this.stateRoot, + blockHashRoot: this.blockHashRoot, + blockNumber: this.blockNumber, + networkStateHash: this.networkStateHash, + }); + } +} + +export class BlockProverPublicOutput extends BlockProverPublicInput {} + export class BlockProverState { /** * The network state which gives access to values such as blockHeight @@ -113,8 +197,8 @@ export class BlockProverState { this.incomingMessages = args.incomingMessages; } - public toCommitments(): BlockProverPublicInput { - return { + public toCommitments(): BlockProverStateInput { + return new BlockProverStateInput({ remainders: { bundlesHash: this.bundleList.commitment, pendingSTBatchesHash: this.pendingSTBatches.commitment, @@ -126,31 +210,31 @@ export class BlockProverState { blockHashRoot: this.blockHashRoot, blockNumber: this.blockNumber, networkStateHash: this.networkState.hash(), - }; + }); } public static blockProverFromCommitments( - publicInput: BlockProverPublicInput, + stateInput: NonMethods, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness ): BlockProverState { return new BlockProverState({ - bundleList: new BundleHashList(publicInput.remainders.bundlesHash), + bundleList: new BundleHashList(stateInput.remainders.bundlesHash), eternalTransactionsList: new TransactionHashList( - publicInput.eternalTransactionsHash + stateInput.eternalTransactionsHash ), incomingMessages: new MinaActionsHashList( - publicInput.incomingMessagesHash + stateInput.incomingMessagesHash ), pendingSTBatches: new AppliedBatchHashList( - publicInput.remainders.pendingSTBatchesHash + stateInput.remainders.pendingSTBatchesHash ), witnessedRoots: new WitnessedRootHashList( - publicInput.remainders.witnessedRootsHash + stateInput.remainders.witnessedRootsHash ), - stateRoot: publicInput.stateRoot, - blockHashRoot: publicInput.blockHashRoot, - blockNumber: publicInput.blockNumber, + stateRoot: stateInput.stateRoot, + blockHashRoot: stateInput.blockHashRoot, + blockNumber: stateInput.blockNumber, networkState, blockWitness, }); @@ -250,37 +334,6 @@ export class BlockProverState { } } -export const BlockProverStateCommitments = { - remainders: { - // Commitment to the list of unprocessed (pending) batches of STs that need to be proven - pendingSTBatchesHash: Field, - witnessedRootsHash: Field, - bundlesHash: Field, - }, - eternalTransactionsHash: Field, - incomingMessagesHash: Field, - stateRoot: Field, - blockHashRoot: Field, - blockNumber: Field, - networkStateHash: Field, -}; - -export class BlockProverPublicInput extends Struct( - BlockProverStateCommitments -) {} - -export class BlockProverPublicOutput extends Struct({ - ...BlockProverStateCommitments, -}) { - public equals(input: BlockProverPublicInput): Bool { - const output2 = BlockProverPublicOutput.toFields(input); - const output1 = BlockProverPublicOutput.toFields(this); - return output1 - .map((value1, index) => value1.equals(output2[index])) - .reduce((a, b) => a.and(b)); - } -} - export type BlockProof = Proof; export interface BlockProvable @@ -288,6 +341,7 @@ export interface BlockProvable CompilableModule { proveBlockBatch: ( publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 83485f3be..c821ac0d5 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -6,6 +6,7 @@ import { CompileArtifact, CompileRegistry, log, + NonMethods, PlainZkProgram, provableMethod, reduceSequential, @@ -42,13 +43,14 @@ import { import { Bundle } from "../accumulators/BlockHashList"; import { - BlockProvable, + BlockArguments, + BlockArgumentsBatch, BlockProof, + BlockProvable, BlockProverPublicInput, BlockProverPublicOutput, BlockProverState, - BlockArgumentsBatch, - BlockArguments, + BlockProverStateInput, } from "./BlockProvable"; import { BlockHashMerkleTreeWitness, @@ -223,6 +225,7 @@ export class BlockProverProgrammable extends ZkProgrammable< @provableMethod() public async proveBlockBatch( publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, @@ -231,29 +234,45 @@ export class BlockProverProgrammable extends ZkProgrammable< deferTransactionProof: Bool, batch: BlockArgumentsBatch ): Promise { - publicInput.networkStateHash.assertEquals( + const hasNoStateRemained = publicInput.proverStateRemainder.equals(0); + + // If the state is supplied as a witness, we check that it is equals the PI's stateHash + stateWitness + .hash() + .equals(publicInput.proverStateRemainder) + .or(hasNoStateRemained) + .assertTrue("Input state witness is invalid"); + + const stateInputs = Provable.if( + hasNoStateRemained, + BlockProverStateInput, + BlockProverStateInput.fromPublicInput(publicInput), + stateWitness + ); + + stateInputs.networkStateHash.assertEquals( networkState.hash(), "Network state not valid" ); + let state = BlockProverState.blockProverFromCommitments( + stateInputs, + networkState, + blockWitness + ); + // Calculate the new block tree hash const blockIndex = blockWitness.calculateIndex(); - blockIndex.assertEquals(publicInput.blockNumber); + blockIndex.assertEquals(stateInputs.blockNumber); blockWitness .calculateRoot(Field(0)) .assertEquals( - publicInput.blockHashRoot, + stateInputs.blockHashRoot, "Supplied block hash witness not matching state root" ); - let state = BlockProverState.blockProverFromCommitments( - publicInput, - networkState, - blockWitness - ); - // Prove blocks iteratively state = await reduceSequential( batch.batch, @@ -333,7 +352,21 @@ export class BlockProverProgrammable extends ZkProgrammable< state.pendingSTBatches.commitment = stateProofResult.pendingSTBatchesHash; state.witnessedRoots.commitment = stateProofResult.witnessedRootsHash; - return new BlockProverPublicOutput(state.toCommitments()); + const finalizedOutput = state.toCommitments(); + + const deferredOutput = { + ...publicInput, + blockProverStateHashRemainder: finalizedOutput.hash(), + }; + + return new BlockProverPublicOutput( + Provable.if( + verifyTransactionProof, + BlockProverPublicOutput, + finalizedOutput.finalize(verifyTransactionProof), + deferredOutput + ) + ); } private async proveBlock( @@ -456,13 +489,7 @@ export class BlockProverProgrammable extends ZkProgrammable< proof2.verify(); function checkProperty< - Key extends - | "stateRoot" - | "networkStateHash" - | "blockHashRoot" - | "eternalTransactionsHash" - | "incomingMessagesHash" - | "blockNumber", + Key extends keyof NonMethods, >(key: Key) { // Check state publicInput[key].assertEquals( @@ -475,29 +502,12 @@ export class BlockProverProgrammable extends ZkProgrammable< ); } - function checkRemainderProperty< - Key extends "pendingSTBatchesHash" | "witnessedRootsHash" | "bundlesHash", - >(key: Key) { - // Check state - publicInput.remainders[key].assertEquals( - proof1.publicInput.remainders[key], - errors.propertyNotMatchingStep(key, "publicInput.from -> proof1.from") - ); - proof1.publicOutput.remainders[key].assertEquals( - proof2.publicInput.remainders[key], - errors.propertyNotMatchingStep(key, "proof1.to -> proof2.from") - ); - } - checkProperty("stateRoot"); checkProperty("networkStateHash"); checkProperty("blockHashRoot"); checkProperty("eternalTransactionsHash"); checkProperty("incomingMessagesHash"); - - checkRemainderProperty("bundlesHash"); - checkRemainderProperty("pendingSTBatchesHash"); - checkRemainderProperty("witnessedRootsHash"); + checkProperty("proverStateRemainder"); return proof2.publicOutput; } @@ -525,6 +535,7 @@ export class BlockProverProgrammable extends ZkProgrammable< methods: { proveBlockBatch: { privateInputs: [ + BlockProverStateInput, NetworkState, BlockHashMerkleTreeWitness, StateTransitionProofClass, @@ -535,6 +546,7 @@ export class BlockProverProgrammable extends ZkProgrammable< ], async method( publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, @@ -546,6 +558,7 @@ export class BlockProverProgrammable extends ZkProgrammable< return { publicOutput: await proveBlockBatch( publicInput, + stateWitness, networkState, blockWitness, stateTransitionProof, @@ -647,6 +660,7 @@ export class BlockProver public proveBlockBatch( publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, stateTransitionProof: StateTransitionProof, @@ -657,6 +671,7 @@ export class BlockProver ): Promise { return this.zkProgrammable.proveBlockBatch( publicInput, + stateWitness, networkState, blockWitness, stateTransitionProof, diff --git a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts index e37e35a60..050543b5f 100644 --- a/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts +++ b/packages/protocol/src/settlement/contracts/settlement/SettlementBase.ts @@ -199,17 +199,9 @@ export abstract class SettlementBase ); // Check remainders are zero - blockProof.publicOutput.remainders.bundlesHash.assertEquals( + blockProof.publicOutput.proverStateRemainder.assertEquals( Field(0), - "Bundles list has not been fully proven" - ); - blockProof.publicOutput.remainders.pendingSTBatchesHash.assertEquals( - Field(0), - "Supplied proof is has outstanding STs to be proven" - ); - blockProof.publicOutput.remainders.witnessedRootsHash.assertEquals( - Field(0), - "Supplied proof is has outstanding witnessed roots hashes to be proven" + "Supplied proof is has outstanding block prover state to be proven" ); // Execute onSettlementHooks for additional checks diff --git a/packages/protocol/src/utils/ProvableHashList.ts b/packages/protocol/src/utils/ProvableHashList.ts index b59da6676..da96f7a2a 100644 --- a/packages/protocol/src/utils/ProvableHashList.ts +++ b/packages/protocol/src/utils/ProvableHashList.ts @@ -79,9 +79,13 @@ export abstract class ProvableHashList { ) { const { from, to } = transition; - condition - .implies(from.equals(this.commitment)) - .assertTrue(`From-commitment for ${message} not matching`); + // Equal to condition -> (from == this.commitment) + from + .mul(condition.toField()) + .assertEquals( + this.commitment.mul(condition.toField()), + `From-commitment for ${message} not matching` + ); this.commitment = Provable.if(condition, to, this.commitment); } diff --git a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts index 824970891..e752f9223 100644 --- a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts @@ -48,6 +48,11 @@ export class BatchFlow { b.publicInput.eternalTransactionsHash ) ) + .and( + a.publicOutput.proverStateRemainder.equals( + b.publicInput.proverStateRemainder + ) + ) .toBoolean(); } diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index de5c15015..5b883da5d 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -13,6 +13,7 @@ import { TransactionProvable, BlockArguments, BlockArgumentsBatch, + BlockProverStateInput, } from "@proto-kit/protocol"; import { Bool } from "o1js"; import { @@ -37,6 +38,7 @@ export type NewBlockArguments = { export interface NewBlockProverParameters { publicInput: BlockProverPublicInput; + stateWitness: BlockProverStateInput; networkState: NetworkState; blockWitness: BlockHashMerkleTreeWitness; deferSTProof: Bool; @@ -103,6 +105,7 @@ export class NewBlockTask networkState, blockWitness, publicInput, + stateWitness, deferSTProof, deferTransactionProof, blocks, @@ -123,6 +126,7 @@ export class NewBlockTask async () => { await this.blockProver.proveBlockBatch( publicInput, + stateWitness, networkState, blockWitness, input1, diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts index d0f8bc122..3c59f730d 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts @@ -2,6 +2,7 @@ import { BlockArguments, BlockHashMerkleTreeWitness, BlockProverPublicInput, + BlockProverStateInput, NetworkState, ReturnType, StateTransitionProof, @@ -28,6 +29,7 @@ interface JsonType { input2: string; params: { publicInput: ReturnType; + stateWitness: ReturnType; networkState: ReturnType; blockWitness: ReturnType; deferSTProof: boolean; @@ -68,6 +70,8 @@ export class NewBlockProvingParametersSerializer params: { publicInput: BlockProverPublicInput.toJSON(input.params.publicInput), + stateWitness: BlockProverStateInput.toJSON(input.params.stateWitness), + networkState: NetworkState.toJSON(input.params.networkState), blockWitness: BlockHashMerkleTreeWitness.toJSON( @@ -102,8 +106,12 @@ export class NewBlockProvingParametersSerializer input2: await this.transactionProofSerializer.fromJSON(jsonObject.input2), params: { - publicInput: BlockProverPublicInput.fromJSON( - jsonObject.params.publicInput + publicInput: new BlockProverPublicInput( + BlockProverPublicInput.fromJSON(jsonObject.params.publicInput) + ), + + stateWitness: new BlockProverStateInput( + BlockProverStateInput.fromJSON(jsonObject.params.stateWitness) ), networkState: new NetworkState( diff --git a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts index f09e20710..5a2dd464c 100644 --- a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts @@ -72,6 +72,11 @@ export class BatchTracingService { const batchState = this.createBatchState(blocks[0]); + const publicInput = this.blockTracingService.openBatch( + batchState, + blocks[0] + ); + // Trace blocks const numBlocks = blocks.length; const numBatches = Math.ceil(numBlocks / BLOCK_ARGUMENT_BATCH_SIZE); @@ -80,9 +85,21 @@ export class BatchTracingService { chunk(blocks, BLOCK_ARGUMENT_BATCH_SIZE), async (state, batch, index) => { // Trace batch of blocks fitting in single proof - const batchTrace = this.blockTracingService.openBlock(state, batch[0]); + const partialBlockTrace = this.blockTracingService.openBlock( + state, + batch[0], + publicInput + ); const start = state.blockNumber.toString(); + // PI is taken for first chunk of batch, all others use the stateWitness + // Copy here because we need a fresh instance + const currentPublicInput = publicInput.clone(); + if (index > 0) { + currentPublicInput.proverStateRemainder = + partialBlockTrace.stateWitness.hash(); + } + const [newState, combinedTraces] = await yieldSequential( batch, async (state2, block, jndex) => { @@ -118,10 +135,11 @@ export class BatchTracingService { const blockTrace: BlockTrace = { block: { - ...batchTrace, + ...partialBlockTrace, + publicInput: currentPublicInput, blocks: blockArgumentBatch.concat(dummies), - deferTransactionProof: Bool(numBatches - 1 < index), - deferSTProof: Bool(numBatches - 1 < index), + deferTransactionProof: Bool(numBatches - 1 !== index), + deferSTProof: Bool(numBatches - 1 !== index), }, heights: [start, newState.blockNumber.toString()], }; diff --git a/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts index e0e8867c8..a326ea898 100644 --- a/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts @@ -8,6 +8,7 @@ import { WitnessedRootWitness, BundleHashList, BundlePreimage, + BlockProverStateInput, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { toStateTransitionsHash } from "@proto-kit/module"; @@ -42,20 +43,38 @@ export class BlockTracingService { public readonly tracer: Tracer ) {} - public openBlock( + public openBatch( state: BlockTracingState, { block: firstBlock, result: firstResult }: BlockWithResult + ) { + return new BlockProverPublicInput({ + stateRoot: state.stateRoot, + blockNumber: firstBlock.height, + blockHashRoot: firstBlock.fromBlockHashRoot, + eternalTransactionsHash: firstBlock.fromEternalTransactionsHash, + incomingMessagesHash: firstBlock.fromMessagesHash, + networkStateHash: firstBlock.networkState.before.hash(), + proverStateRemainder: Field(0), + }); + } + + public openBlock( + state: BlockTracingState, + { block: firstBlock, result: firstResult }: BlockWithResult, + batchInput: BlockProverPublicInput ): Pick< NewBlockProverParameters, - "publicInput" | "networkState" | "blockWitness" + "stateWitness" | "networkState" | "blockWitness" > { - const publicInput: BlockProverPublicInput = new BlockProverPublicInput({ + const stateWitness = new BlockProverStateInput({ stateRoot: state.stateRoot, blockNumber: firstBlock.height, blockHashRoot: firstBlock.fromBlockHashRoot, - eternalTransactionsHash: firstBlock.fromEternalTransactionsHash, - incomingMessagesHash: firstBlock.fromMessagesHash, networkStateHash: firstBlock.networkState.before.hash(), + // The next two are properties that we fast-forward only after tx proofs are verified + // Therefore those don't change over multiple block batches + eternalTransactionsHash: batchInput.eternalTransactionsHash, + incomingMessagesHash: batchInput.incomingMessagesHash, remainders: { witnessedRootsHash: state.witnessedRoots.commitment, pendingSTBatchesHash: state.pendingSTBatches.commitment, @@ -64,7 +83,7 @@ export class BlockTracingService { }); return { - publicInput, + stateWitness, networkState: firstBlock.networkState.before, blockWitness: firstResult.blockHashWitness, }; diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 06f6c6885..fe0bb6657 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -539,6 +539,7 @@ export function testBlockProduction< [2, 1, 1], [1, 2, 1], [1, 1, 2], + [1, 5, 1], [2, 2, 2], [1, 14, 0], ])( From 4db36dae1007da4ab5f9d5f404b499c46c023201 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 24 Jan 2026 19:09:18 +0100 Subject: [PATCH 135/155] Fixed issue with empty transaction proofs not triggering batch finalization --- packages/common/src/trees/sparse/RollupMerkleTree.ts | 2 ++ packages/protocol/src/prover/block/BlockProver.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/common/src/trees/sparse/RollupMerkleTree.ts b/packages/common/src/trees/sparse/RollupMerkleTree.ts index c701a091d..3cd90301b 100644 --- a/packages/common/src/trees/sparse/RollupMerkleTree.ts +++ b/packages/common/src/trees/sparse/RollupMerkleTree.ts @@ -189,6 +189,8 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { return hash; } + // TODO Make sure this implementation is as efficient as it gets. + // Especially compared to doing calculateRoot + witness new witness + check index public calculateRootIncrement( leafIndex: Field, leaf: Field diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index c821ac0d5..415ed19a8 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -287,9 +287,10 @@ export class BlockProverProgrammable extends ZkProgrammable< ); // Verify Transaction proof if it has at least 1 tx and it isn't deferred - const verifyTransactionProof = deferTransactionProof - .not() - .and(state.bundleList.isEmpty().not()); + const finalizeBlockProof = deferTransactionProof.not(); + const verifyTransactionProof = finalizeBlockProof.and( + state.bundleList.isEmpty().not() + ); transactionProof.verifyIf(verifyTransactionProof); @@ -361,9 +362,9 @@ export class BlockProverProgrammable extends ZkProgrammable< return new BlockProverPublicOutput( Provable.if( - verifyTransactionProof, + finalizeBlockProof, BlockProverPublicOutput, - finalizedOutput.finalize(verifyTransactionProof), + finalizedOutput.finalize(finalizeBlockProof), deferredOutput ) ); From b9904a56eb425d09c162410034ca52a497927846 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 24 Jan 2026 19:34:31 +0100 Subject: [PATCH 136/155] Fixed wrong public output due to missed renaming --- packages/protocol/src/prover/block/BlockProver.ts | 2 +- packages/sequencer/test/integration/BlockProduction-test.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 415ed19a8..cc64531fe 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -357,8 +357,8 @@ export class BlockProverProgrammable extends ZkProgrammable< const deferredOutput = { ...publicInput, - blockProverStateHashRemainder: finalizedOutput.hash(), }; + deferredOutput.proverStateRemainder = finalizedOutput.hash(); return new BlockProverPublicOutput( Provable.if( diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index fe0bb6657..bb20c9c02 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -537,11 +537,10 @@ export function testBlockProduction< it.each([ [2, 1, 1], - [1, 2, 1], - [1, 1, 2], [1, 5, 1], [2, 2, 2], [1, 14, 0], + [1, 6, 5], ])( "should produce multiple blocks with multiple batches with multiple transactions", async (batches, blocksPerBatch, txsPerBlock) => { From 78c354dc4152d379c2222de9e7c7021dc3a669fd Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 24 Jan 2026 21:11:35 +0100 Subject: [PATCH 137/155] Improved efficiency of calculateRootAndIncrement --- .../src/trees/sparse/RollupMerkleTree.ts | 89 ++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/packages/common/src/trees/sparse/RollupMerkleTree.ts b/packages/common/src/trees/sparse/RollupMerkleTree.ts index 3cd90301b..e7503df46 100644 --- a/packages/common/src/trees/sparse/RollupMerkleTree.ts +++ b/packages/common/src/trees/sparse/RollupMerkleTree.ts @@ -189,66 +189,71 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { return hash; } - // TODO Make sure this implementation is as efficient as it gets. - // Especially compared to doing calculateRoot + witness new witness + check index public calculateRootIncrement( leafIndex: Field, leaf: Field ): [Field, RollupMerkleWitness] { - // This won't generate any constraints, since it's purely a computation on constants - const zero = getZeroes(); + const root = this.calculateRoot(leaf); - if (zero.length === 0) { - throw new Error("Zeroes not initialized"); - } - const zeroes = zero.map((x) => Field(x)); + const newWitness = Provable.witness(RollupMerkleWitness, () => { + const zero = getZeroes(); - let hash = leaf; - const n = this.height(); + if (zero.length === 0) { + throw new Error("Zeroes not initialized"); + } + const zeroes = zero.map((x) => Field(x)); - let notDiverged = Bool(true); - const newPath = leafIndex.add(1).toBits(); - newPath.push(Bool(false)); + let hash = leaf; + const n = this.height(); - const newSiblings: Field[] = []; - const newIsLefts: Bool[] = []; + let notDiverged = true; + const newPath = leafIndex.add(1).toBits(); + newPath.push(Bool(false)); - for (let index = 0; index < n - 1; ++index) { - const isLeft = this.isLeft[index]; - const sibling = this.path[index]; + const newSiblings: Field[] = []; + const newIsLefts: Bool[] = []; - const newIsLeft = newPath[index].not(); + for (let index = 0; index < n - 1; ++index) { + const isLeft = this.isLeft[index]; + const sibling = this.path[index]; - // Bool(true) default for root level - let convergesNextLevel = Bool(true); - if (index < n - 2) { - convergesNextLevel = newPath[index + 1] - .equals(this.isLeft[index + 1]) - .not(); - } + const newIsLeft = newPath[index].not(); - const nextSibling = Provable.if( - convergesNextLevel.and(notDiverged), - hash, - Provable.if(notDiverged, zeroes[index], sibling) - ); + // Bool(true) default for root level + let convergesNextLevel = true; + if (index < n - 2) { + convergesNextLevel = newPath[index + 1] + .equals(this.isLeft[index + 1]) + .not() + .toBoolean(); + } - notDiverged = notDiverged.and(convergesNextLevel.not()); + const nextSibling = + // eslint-disable-next-line no-nested-ternary + convergesNextLevel && notDiverged + ? hash + : notDiverged + ? zeroes[index] + : sibling; - newSiblings.push(nextSibling); - newIsLefts.push(newIsLeft); + notDiverged = notDiverged && !convergesNextLevel; - const [left, right] = maybeSwap(isLeft, hash, sibling); - hash = Poseidon.hash([left, right]); - } + newSiblings.push(nextSibling); + newIsLefts.push(newIsLeft); + + const [left, right] = maybeSwap(isLeft, hash, sibling); + hash = Poseidon.hash([left, right]); + } - return [ - hash, - new RollupMerkleWitness({ + return new RollupMerkleWitness({ isLeft: newIsLefts, path: newSiblings, - }), - ]; + }); + }); + + newWitness.calculateIndex().assertEquals(leafIndex.add(1)); + + return [root, newWitness]; } /** From ddd651ad0d3fd4f5dec1b10db72576e49b4d7bcc Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 25 Jan 2026 16:59:38 +0100 Subject: [PATCH 138/155] Fixed bug in block batch sizing --- .../src/protocol/production/tracing/BatchTracingService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts index 5a2dd464c..3df227201 100644 --- a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts @@ -117,13 +117,15 @@ export class BatchTracingService { state ); + const [blockArgumentBatch, transactionTraces] = unzip(combinedTraces); + // Fill up with dummies const dummyBlockArgs = BlockArguments.noop( newState, Field(blocks.at(-1)!.result.stateRoot) ); const dummies = range( - blocks.length, + blockArgumentBatch.length, BLOCK_ARGUMENT_BATCH_SIZE ).map(() => ({ args: dummyBlockArgs, @@ -131,8 +133,6 @@ export class BatchTracingService { startingStateBeforeHook: {}, })); - const [blockArgumentBatch, transactionTraces] = unzip(combinedTraces); - const blockTrace: BlockTrace = { block: { ...partialBlockTrace, From c7e81e530103ec21dc68601c8b0dedd65f761d0d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 25 Jan 2026 16:55:13 +0100 Subject: [PATCH 139/155] Added sanity check to block task --- .../sequencer/src/protocol/production/tasks/NewBlockTask.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 5b883da5d..427ee0ca1 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -14,6 +14,7 @@ import { BlockArguments, BlockArgumentsBatch, BlockProverStateInput, + BLOCK_ARGUMENT_BATCH_SIZE, } from "@proto-kit/protocol"; import { Bool } from "o1js"; import { @@ -111,6 +112,10 @@ export class NewBlockTask blocks, } = parameters; + if (blocks.length !== BLOCK_ARGUMENT_BATCH_SIZE) { + throw new Error("Given block argument length not exactly batch size"); + } + const blockArgumentBatch = new BlockArgumentsBatch({ batch: blocks.map((block) => block.args), }); From eb1fa512e666201d871752b0f5d234df979b5cff Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 26 Jan 2026 16:31:10 +0100 Subject: [PATCH 140/155] Fixed handling of witnessed root preimage --- .../accumulators/WitnessedRootHashList.ts | 28 ++++++++++++------- .../src/prover/block/BlockProvable.ts | 20 +++++++------ .../protocol/src/prover/block/BlockProver.ts | 12 ++++---- .../production/tasks/StateTransitionTask.ts | 6 +--- .../production/tracing/BlockTracingService.ts | 10 ++----- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts index 7226492da..220ed53a3 100644 --- a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts +++ b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts @@ -1,4 +1,4 @@ -import { Bool, Field, Struct } from "o1js"; +import { Bool, Field, Provable, Struct } from "o1js"; import { DefaultProvableHashList } from "../../utils/ProvableHashList"; @@ -13,7 +13,7 @@ export class WitnessedRoot extends Struct({ export class WitnessedRootWitness extends Struct({ witnessedRoot: Field, - preimage: Field, + // preimage: Field, }) {} /** @@ -21,7 +21,10 @@ export class WitnessedRootWitness extends Struct({ */ export class WitnessedRootHashList extends DefaultProvableHashList { - public constructor(commitment: Field = Field(0)) { + public constructor( + commitment: Field = Field(0), + public preimage: Field = Field(0) + ) { super(WitnessedRoot, commitment); } @@ -37,17 +40,13 @@ export class WitnessedRootHashList extends DefaultProvableHashList { - const output = await this.stateTransitionProver.proveBatch( + await this.stateTransitionProver.proveBatch( input.publicInput, input.batch, new MerkleWitnessBatch({ witnesses: input.merkleWitnesses.slice() }), input.batchState ); - log.debug("STTask public io:", { - input: StateTransitionProverPublicInput.toJSON(input.publicInput), - output: StateTransitionProverPublicOutput.toJSON(output), - }); return await this.executionContext .current() diff --git a/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts index a326ea898..98c02358f 100644 --- a/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BlockTracingService.ts @@ -76,9 +76,10 @@ export class BlockTracingService { eternalTransactionsHash: batchInput.eternalTransactionsHash, incomingMessagesHash: batchInput.incomingMessagesHash, remainders: { - witnessedRootsHash: state.witnessedRoots.commitment, pendingSTBatchesHash: state.pendingSTBatches.commitment, bundlesHash: state.bundleList.commitment, + witnessedRootsHash: state.witnessedRoots.commitment, + witnessedRootsPreimage: state.witnessedRoots.preimage, }, }); @@ -173,14 +174,8 @@ export class BlockTracingService { state.incomingMessages = afterState.incomingMessages; state.eternalTransactionsList = afterState.eternalTransactionsList; - const preimage = afterState.witnessedRoots - .getUnconstrainedValues() - .get() - .at(-2)?.preimage; - const afterBlockRootWitness: WitnessedRootWitness = { witnessedRoot: Field(block.result.witnessedRoots[0]), - preimage: preimage ?? Field(0), }; // We create the batch here, because we need the afterBlockRootWitness, @@ -206,7 +201,6 @@ export class BlockTracingService { appliedBatchListState: afterState.pendingSTBatches.commitment, root: afterBlockRootWitness.witnessedRoot, }, - afterBlockRootWitness.preimage, state.pendingSTBatches.commitment.equals(0).not() ); } From 3e5ffc371c886828102b01407505f8273ac6989d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 26 Jan 2026 16:45:32 +0100 Subject: [PATCH 141/155] Added regression test for filled + empty blocks --- .../accumulators/WitnessedRootHashList.ts | 7 ---- .../test/integration/BlockProduction-test.ts | 40 +++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts index 220ed53a3..9671c6582 100644 --- a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts +++ b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts @@ -13,7 +13,6 @@ export class WitnessedRoot extends Struct({ export class WitnessedRootWitness extends Struct({ witnessedRoot: Field, - // preimage: Field, }) {} /** @@ -37,8 +36,6 @@ export class WitnessedRootHashList extends DefaultProvableHashList { + log.setLevel("INFO"); + + (sequencer.resolve("BlockProducerModule") as BlockProducerModule).config = { + maximumBlockSize: 5, + }; + + const privateKey = PrivateKey.random(); + + for (const i of range(0, 7)) { + await test.addTransaction({ + method: ["Balance", "addBalance"], + privateKey, + args: [PrivateKey.random().toPublicKey(), UInt64.from(100)], + }); + } + + // Produce 6 blocks, 5 txs each into 1 batch + const block = await test.produceBlock(); + + expectDefined(block); + expect(block.transactions).toHaveLength(5); + expect(block.transactions[0].status.toBoolean()).toBe(true); + + await test.produceBlock(); + await test.produceBlock(); + await test.produceBlock(); + await test.produceBlock(); + await test.produceBlock(); + const batch = await test.produceBatch(); + + expectDefined(batch); + + console.log(batch.proof); + + expect(batch.blockHashes).toHaveLength(6); + expect(batch.proof.proof.length).toBeGreaterThan(50); + }, 30000); + it("events - should produce block with the right events", async () => { log.setLevel("TRACE"); From ad8e8996a922aa74a45d1211635c4dea972843d6 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 26 Jan 2026 17:47:18 +0100 Subject: [PATCH 142/155] Fixed halting issue in block flow --- .../src/prover/block/BlockProvable.ts | 5 ++ .../test/integration/BlockProduction-test.ts | 67 ++++++++++--------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index eab36dc1b..400971ae1 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -318,6 +318,11 @@ export class BlockProverState { condition, a.witnessedRoots.commitment, b.witnessedRoots.commitment + ), + Provable.if( + condition, + a.witnessedRoots.preimage, + b.witnessedRoots.preimage ) ), stateRoot: Provable.if(condition, a.stateRoot, b.stateRoot), diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 57ed164a4..328b2a66d 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -685,44 +685,47 @@ export function testBlockProduction< expect(batch!.proof.proof).toBe(MOCK_PROOF); }, 30000); - it("should produce some filled blocks and some empty blocks", async () => { - log.setLevel("INFO"); - - (sequencer.resolve("BlockProducerModule") as BlockProducerModule).config = { - maximumBlockSize: 5, - }; - - const privateKey = PrivateKey.random(); - - for (const i of range(0, 7)) { - await test.addTransaction({ - method: ["Balance", "addBalance"], - privateKey, - args: [PrivateKey.random().toPublicKey(), UInt64.from(100)], - }); - } + it.each([4, 6, 9])( + "should produce some filled blocks and some empty blocks", + async (numBlocks) => { + log.setLevel("INFO"); + + (sequencer.resolve("BlockProducerModule") as BlockProducerModule).config = + { + maximumBlockSize: 5, + }; + + const privateKey = PrivateKey.random(); + + for (const i of range(0, 7)) { + await test.addTransaction({ + method: ["Balance", "addBalance"], + privateKey, + args: [PrivateKey.random().toPublicKey(), UInt64.from(100)], + }); + } - // Produce 6 blocks, 5 txs each into 1 batch - const block = await test.produceBlock(); + // Produce 6 blocks, 5 txs each into 1 batch + const block = await test.produceBlock(); - expectDefined(block); - expect(block.transactions).toHaveLength(5); - expect(block.transactions[0].status.toBoolean()).toBe(true); + expectDefined(block); + expect(block.transactions).toHaveLength(5); + expect(block.transactions[0].status.toBoolean()).toBe(true); - await test.produceBlock(); - await test.produceBlock(); - await test.produceBlock(); - await test.produceBlock(); - await test.produceBlock(); - const batch = await test.produceBatch(); + await mapSequential( + range(0, numBlocks - 1), + async () => await test.produceBlock() + ); + const batch = await test.produceBatch(); - expectDefined(batch); + expectDefined(batch); - console.log(batch.proof); + console.log(batch.proof); - expect(batch.blockHashes).toHaveLength(6); - expect(batch.proof.proof.length).toBeGreaterThan(50); - }, 30000); + expect(batch.blockHashes).toHaveLength(numBlocks); + }, + 30000 + ); it("events - should produce block with the right events", async () => { log.setLevel("TRACE"); From c434f839a85a3456441c00f7935ad32e2e79909c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 26 Jan 2026 17:52:10 +0100 Subject: [PATCH 143/155] Fixed linting --- .../src/protocol/production/tasks/StateTransitionTask.ts | 2 -- packages/sequencer/test/integration/BlockProduction-test.ts | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts index f68b0b52d..532a0d5d4 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts @@ -9,10 +9,8 @@ import { StateTransitionProvable, StateTransitionProvableBatch, StateTransitionProverPublicInput, - StateTransitionProverPublicOutput, } from "@proto-kit/protocol"; import { - log, ProvableMethodExecutionContext, CompileRegistry, LinkedMerkleTreeWitness, diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 328b2a66d..58f1b0c94 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -697,6 +697,7 @@ export function testBlockProduction< const privateKey = PrivateKey.random(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const i of range(0, 7)) { await test.addTransaction({ method: ["Balance", "addBalance"], From c2920e5b0295514fb30d49ed96c7b9b35f3add45 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 27 Jan 2026 15:06:52 +0100 Subject: [PATCH 144/155] Added proofs/no-proofs variants for blockprover --- .../src/prover/block/BlockProvable.ts | 17 +- .../protocol/src/prover/block/BlockProver.ts | 329 ++++++++++++------ .../src/protocol/production/flow/BatchFlow.ts | 2 + .../protocol/production/tasks/NewBlockTask.ts | 42 ++- 4 files changed, 275 insertions(+), 115 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index 400971ae1..fc2a15052 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -348,16 +348,25 @@ export type BlockProof = Proof; export interface BlockProvable extends WithZkProgrammable, CompilableModule { - proveBlockBatch: ( + proveBlockBatchNoProofs: ( publicInput: BlockProverPublicInput, stateWitness: BlockProverStateInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, - stateTransitionProof: StateTransitionProof, + batch: BlockArgumentsBatch, + finalize: Bool + ) => Promise; + + proveBlockBatchWithProofs: ( + publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness, + batch: BlockArgumentsBatch, deferSTProof: Bool, - transactionProof: TransactionProof, deferTransactionProof: Bool, - batch: BlockArgumentsBatch + stateTransitionProof: StateTransitionProof, + transactionProof: TransactionProof ) => Promise; merge: ( diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 740d42e3f..4bb709c2b 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -64,6 +64,8 @@ const errors = { propertyNotMatching: (propertyName: string) => `${propertyName} not matching`, }; +type Tail = T extends [infer A, ...infer Rest] ? Rest : never; + type BlockHookArgument = T extends "before" ? BeforeBlockHookArguments : AfterBlockHookArguments; @@ -222,70 +224,37 @@ export class BlockProverProgrammable extends ZkProgrammable< }; } - @provableMethod() - public async proveBlockBatch( - publicInput: BlockProverPublicInput, - stateWitness: BlockProverStateInput, - networkState: NetworkState, - blockWitness: BlockHashMerkleTreeWitness, + private verifySTProof( + state: BlockProverState, stateTransitionProof: StateTransitionProof, - deferSTProof: Bool, - transactionProof: TransactionProof, - deferTransactionProof: Bool, - batch: BlockArgumentsBatch - ): Promise { - const hasNoStateRemained = publicInput.proverStateRemainder.equals(0); - - // If the state is supplied as a witness, we check that it is equals the PI's stateHash - stateWitness - .hash() - .equals(publicInput.proverStateRemainder) - .or(hasNoStateRemained) - .assertTrue("Input state witness is invalid"); - - const stateInputs = Provable.if( - hasNoStateRemained, - BlockProverStateInput, - BlockProverStateInput.fromPublicInput(publicInput), - stateWitness - ); - - stateInputs.networkStateHash.assertEquals( - networkState.hash(), - "Network state not valid" - ); - - let state = BlockProverState.blockProverFromCommitments( - stateInputs, - networkState, - blockWitness - ); - - // Calculate the new block tree hash - const blockIndex = blockWitness.calculateIndex(); - - blockIndex.assertEquals(stateInputs.blockNumber); - - blockWitness - .calculateRoot(Field(0)) - .assertEquals( - stateInputs.blockHashRoot, - "Supplied block hash witness not matching state root" - ); - - // Prove blocks iteratively - state = await reduceSequential( - batch.batch, - async (current, block) => { - const result = await this.proveBlock(current.copy(), block); - - this.stateServiceProvider.popCurrentStateService(); + deferSTProof: Bool + ) { + // Verify ST Proof only if STs have been emitted, + // and we don't defer the verification of the STs + // otherwise we can input a dummy proof + const batchesEmpty = state.pendingSTBatches.commitment.equals(Field(0)); + const verifyStProof = deferSTProof.not().and(batchesEmpty.not()); + log.provable.debug("Verify STProof", verifyStProof); + stateTransitionProof.verifyIf(verifyStProof); - return BlockProverState.choose(block.isDummy, current, result); - }, - state + // Apply STProof if not deferred + const stateProofResult = this.includeSTProof( + stateTransitionProof, + verifyStProof, + state.stateRoot, + state.pendingSTBatches.commitment, + state.witnessedRoots.commitment ); + state.stateRoot = stateProofResult.stateRoot; + state.pendingSTBatches.commitment = stateProofResult.pendingSTBatchesHash; + state.witnessedRoots.commitment = stateProofResult.witnessedRootsHash; + } + private verifyTransactionProof( + state: BlockProverState, + transactionProof: TransactionProof, + deferTransactionProof: Bool + ) { // Verify Transaction proof if it has at least 1 tx and it isn't deferred const finalizeBlockProof = deferTransactionProof.not(); const verifyTransactionProof = finalizeBlockProof.and( @@ -332,27 +301,61 @@ export class BlockProverProgrammable extends ZkProgrammable< verifyTransactionProof, "bundles hash" ); + } - // Verify ST Proof only if STs have been emitted, - // and we don't defer the verification of the STs - // otherwise we can input a dummy proof - const batchesEmpty = state.pendingSTBatches.commitment.equals(Field(0)); - const verifyStProof = deferSTProof.not().and(batchesEmpty.not()); - log.provable.debug("Verify STProof", verifyStProof); - stateTransitionProof.verifyIf(verifyStProof); + private parseState( + publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness + ) { + const hasNoStateRemained = publicInput.proverStateRemainder.equals(0); - // Apply STProof if not deferred - const stateProofResult = this.includeSTProof( - stateTransitionProof, - verifyStProof, - state.stateRoot, - state.pendingSTBatches.commitment, - state.witnessedRoots.commitment + // If the state is supplied as a witness, we check that it is equals the PI's stateHash + stateWitness + .hash() + .equals(publicInput.proverStateRemainder) + .or(hasNoStateRemained) + .assertTrue("Input state witness is invalid"); + + const stateInputs = Provable.if( + hasNoStateRemained, + BlockProverStateInput, + BlockProverStateInput.fromPublicInput(publicInput), + stateWitness ); - state.stateRoot = stateProofResult.stateRoot; - state.pendingSTBatches.commitment = stateProofResult.pendingSTBatchesHash; - state.witnessedRoots.commitment = stateProofResult.witnessedRootsHash; + stateInputs.networkStateHash.assertEquals( + networkState.hash(), + "Network state not valid" + ); + + const state = BlockProverState.blockProverFromCommitments( + stateInputs, + networkState, + blockWitness + ); + + // Verify block witness validity + const blockIndex = blockWitness.calculateIndex(); + + blockIndex.assertEquals(stateInputs.blockNumber); + + blockWitness + .calculateRoot(Field(0)) + .assertEquals( + stateInputs.blockHashRoot, + "Supplied block hash witness not matching state root" + ); + + return state; + } + + private computeOutput( + publicInput: BlockProverPublicInput, + state: BlockProverState, + finalizeBlockProof: Bool + ) { const finalizedOutput = state.toCommitments(); const deferredOutput = { @@ -370,6 +373,84 @@ export class BlockProverProgrammable extends ZkProgrammable< ); } + @provableMethod() + public async proveBlockBatchNoProofs( + publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness, + batch: BlockArgumentsBatch, + finalize: Bool + ) { + return await this.proveBlockBatch( + false, + publicInput, + stateWitness, + networkState, + blockWitness, + batch, + // TODO Don't do this -> very confusing + finalize, + finalize + ); + } + + @provableMethod() + public async proveBlockBatchWithProofs( + ...args: Required>> + ) { + return await this.proveBlockBatch(true, ...args); + } + + public async proveBlockBatch( + doProofVerification: boolean, + publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness, + batch: BlockArgumentsBatch, + deferSTProof: Bool, + deferTransactionProof: Bool, + stateTransitionProof?: StateTransitionProof, + transactionProof?: TransactionProof + ): Promise { + let state = this.parseState( + publicInput, + stateWitness, + networkState, + blockWitness + ); + + // Prove blocks iteratively + state = await reduceSequential( + batch.batch, + async (current, block) => { + const result = await this.proveBlock(current.copy(), block); + + this.stateServiceProvider.popCurrentStateService(); + + return BlockProverState.choose(block.isDummy, current, result); + }, + state + ); + + if (doProofVerification) { + this.verifyTransactionProof( + state, + transactionProof!, + deferTransactionProof + ); + this.verifySTProof(state, stateTransitionProof!, deferSTProof); + } + + const finalizeBlockProof = deferTransactionProof.or(deferSTProof).not(); + // .or() + // .or(state.bundleList.isEmpty().and(state.pendingSTBatches.isEmpty())); + // TODO This finalizes immediately if nothing happened - which we don't account for in tracer currently + + return this.computeOutput(publicInput, state, finalizeBlockProof); + } + private async proveBlock( state: BlockProverState, args: BlockArguments @@ -445,6 +526,8 @@ export class BlockProverProgrammable extends ZkProgrammable< startingPendingStBatches ); + // TODO Cover case when we witness root but pendingSTBatches is completely empty + state.witnessedRoots.witnessRoot( { appliedBatchListState: state.pendingSTBatches.commitment, @@ -525,7 +608,9 @@ export class BlockProverProgrammable extends ZkProgrammable< const { prover, stateTransitionProver, transactionProver } = this; const StateTransitionProofClass = stateTransitionProver.zkProgram[0].Proof; const TransactionProofClass = transactionProver.zkProgram[0].Proof; - const proveBlockBatch = prover.proveBlockBatch.bind(prover); + const proveBlockBatchWithProofs = + prover.proveBlockBatchWithProofs.bind(prover); + const proveBlockBatchNoProofs = prover.proveBlockBatchNoProofs.bind(prover); const merge = prover.merge.bind(prover); const program = ZkProgram({ @@ -534,39 +619,68 @@ export class BlockProverProgrammable extends ZkProgrammable< publicOutput: BlockProverPublicOutput, methods: { - proveBlockBatch: { + proveBlockBatchWithProofs: { privateInputs: [ BlockProverStateInput, NetworkState, BlockHashMerkleTreeWitness, - StateTransitionProofClass, + BlockArgumentsBatch, Bool, - TransactionProofClass, Bool, - BlockArgumentsBatch, + StateTransitionProofClass, + TransactionProofClass, ], async method( publicInput: BlockProverPublicInput, stateWitness: BlockProverStateInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, - stateTransitionProof: StateTransitionProof, + batch: BlockArgumentsBatch, deferSTProof: Bool, - transactionProof: TransactionProof, deferTransactionProof: Bool, - batch: BlockArgumentsBatch + stateTransitionProof: StateTransitionProof, + transactionProof: TransactionProof ) { return { - publicOutput: await proveBlockBatch( + publicOutput: await proveBlockBatchWithProofs( publicInput, stateWitness, networkState, blockWitness, - stateTransitionProof, + batch, deferSTProof, - transactionProof, deferTransactionProof, - batch + stateTransitionProof, + transactionProof + ), + }; + }, + }, + + proveBlockBatchNoProofs: { + privateInputs: [ + BlockProverStateInput, + NetworkState, + BlockHashMerkleTreeWitness, + BlockArgumentsBatch, + Bool, + ], + async method( + publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness, + batch: BlockArgumentsBatch, + finalize: Bool + ) { + return { + publicOutput: await proveBlockBatchNoProofs( + publicInput, + stateWitness, + networkState, + blockWitness, + batch, + finalize ), }; }, @@ -590,7 +704,8 @@ export class BlockProverProgrammable extends ZkProgrammable< }); const methods = { - proveBlockBatch: program.proveBlockBatch, + proveBlockBatchWithProofs: program.proveBlockBatchWithProofs, + proveBlockBatchNoProofs: program.proveBlockBatchNoProofs, merge: program.merge, }; @@ -659,27 +774,45 @@ export class BlockProver }); } - public proveBlockBatch( + public proveBlockBatchNoProofs( publicInput: BlockProverPublicInput, stateWitness: BlockProverStateInput, networkState: NetworkState, blockWitness: BlockHashMerkleTreeWitness, - stateTransitionProof: StateTransitionProof, + batch: BlockArgumentsBatch, + finalize: Bool + ): Promise { + return this.zkProgrammable.proveBlockBatchNoProofs( + publicInput, + stateWitness, + networkState, + blockWitness, + batch, + finalize + ); + } + + public proveBlockBatchWithProofs( + publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness, + batch: BlockArgumentsBatch, deferSTProof: Bool, - transactionProof: TransactionProof, deferTransactionProof: Bool, - batch: BlockArgumentsBatch + stateTransitionProof: StateTransitionProof, + transactionProof: TransactionProof ): Promise { - return this.zkProgrammable.proveBlockBatch( + return this.zkProgrammable.proveBlockBatchWithProofs( publicInput, stateWitness, networkState, blockWitness, - stateTransitionProof, + batch, deferSTProof, - transactionProof, deferTransactionProof, - batch + stateTransitionProof, + transactionProof ); } diff --git a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts index e752f9223..e2267ec60 100644 --- a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts @@ -125,6 +125,8 @@ export class BatchFlow { } ); + // TODO Cover case where either 0 STs or 0 Transactions are in a batch + // Push all blocks except the last one with dummy proofs // except the last one, which will wait on the two proofs to complete await mapSequential( diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 427ee0ca1..1b25581cc 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -16,7 +16,7 @@ import { BlockProverStateInput, BLOCK_ARGUMENT_BATCH_SIZE, } from "@proto-kit/protocol"; -import { Bool } from "o1js"; +import { Bool, Provable } from "o1js"; import { ProvableMethodExecutionContext, CompileRegistry, @@ -129,26 +129,42 @@ export class NewBlockTask this.protocol.stateServiceProvider, stateRecords, async () => { - await this.blockProver.proveBlockBatch( - publicInput, - stateWitness, - networkState, - blockWitness, - input1, - deferSTProof, - input2, - deferTransactionProof, - blockArgumentBatch - ); + if (deferSTProof.toBoolean() && deferTransactionProof.toBoolean()) { + await this.blockProver.proveBlockBatchNoProofs( + publicInput, + stateWitness, + networkState, + blockWitness, + blockArgumentBatch, + deferSTProof.or(deferTransactionProof) + ); + } else { + await this.blockProver.proveBlockBatchWithProofs( + publicInput, + stateWitness, + networkState, + blockWitness, + blockArgumentBatch, + deferSTProof, + deferTransactionProof, + input1, + input2 + ); + } } ); - return await executeWithPrefilledStateService( + const proof = await executeWithPrefilledStateService( this.protocol.stateServiceProvider, stateRecords, async () => await this.executionContext.current().result.prove() ); + + Provable.log("Input", proof.publicInput); + Provable.log("Output", proof.publicOutput); + + return proof; } public async prepare(): Promise { From d4099670e96168ea01e7d44cb5286a78026d35b6 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 28 Jan 2026 15:11:33 +0100 Subject: [PATCH 145/155] Added dummy transaction proof for empty batches --- .../protocol/src/prover/block/BlockProver.ts | 38 ++++++++++++++----- .../prover/transaction/TransactionProvable.ts | 4 ++ .../prover/transaction/TransactionProver.ts | 20 ++++++++++ .../src/protocol/production/flow/BlockFlow.ts | 26 ++++++------- .../protocol/production/tasks/NewBlockTask.ts | 3 +- .../tasks/TransactionProvingTask.ts | 24 ++++++++++++ ...ansactionProvingTaskParameterSerializer.ts | 8 ++++ .../types/TransactionProvingTypes.ts | 10 +++-- 8 files changed, 104 insertions(+), 29 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 4bb709c2b..f959f0355 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -389,17 +389,39 @@ export class BlockProverProgrammable extends ZkProgrammable< networkState, blockWitness, batch, - // TODO Don't do this -> very confusing - finalize, + Bool(true), + Bool(true), finalize ); } @provableMethod() public async proveBlockBatchWithProofs( - ...args: Required>> + publicInput: BlockProverPublicInput, + stateWitness: BlockProverStateInput, + networkState: NetworkState, + blockWitness: BlockHashMerkleTreeWitness, + batch: BlockArgumentsBatch, + deferSTProof: Bool, + deferTransactionProof: Bool, + stateTransitionProof: StateTransitionProof, + transactionProof: TransactionProof ) { - return await this.proveBlockBatch(true, ...args); + const finalize = deferTransactionProof.or(deferSTProof).not(); + + return await this.proveBlockBatch( + true, + publicInput, + stateWitness, + networkState, + blockWitness, + batch, + deferSTProof, + deferTransactionProof, + finalize, + stateTransitionProof, + transactionProof + ); } public async proveBlockBatch( @@ -411,6 +433,7 @@ export class BlockProverProgrammable extends ZkProgrammable< batch: BlockArgumentsBatch, deferSTProof: Bool, deferTransactionProof: Bool, + finalize: Bool, stateTransitionProof?: StateTransitionProof, transactionProof?: TransactionProof ): Promise { @@ -443,12 +466,7 @@ export class BlockProverProgrammable extends ZkProgrammable< this.verifySTProof(state, stateTransitionProof!, deferSTProof); } - const finalizeBlockProof = deferTransactionProof.or(deferSTProof).not(); - // .or() - // .or(state.bundleList.isEmpty().and(state.pendingSTBatches.isEmpty())); - // TODO This finalizes immediately if nothing happened - which we don't account for in tracer currently - - return this.computeOutput(publicInput, state, finalizeBlockProof); + return this.computeOutput(publicInput, state, finalize); } private async proveBlock( diff --git a/packages/protocol/src/prover/transaction/TransactionProvable.ts b/packages/protocol/src/prover/transaction/TransactionProvable.ts index bb99ab0cc..0803946a5 100644 --- a/packages/protocol/src/prover/transaction/TransactionProvable.ts +++ b/packages/protocol/src/prover/transaction/TransactionProvable.ts @@ -171,6 +171,10 @@ export interface TransactionProvable executionData2: TransactionProverExecutionData ) => Promise; + dummy: ( + publicInput: TransactionProverPublicInput + ) => Promise; + merge: ( publicInput: TransactionProverPublicInput, proof1: TransactionProof, diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts index c2fb917ae..7d8291cf4 100644 --- a/packages/protocol/src/prover/transaction/TransactionProver.ts +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -302,6 +302,13 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< ); } + @provableMethod() + public async dummy( + publicInput: TransactionProverPublicInput + ): Promise { + return publicInput; + } + @provableMethod() public async merge( publicInput: TransactionProverPublicInput, @@ -367,6 +374,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< const proveTransaction = prover.proveTransaction.bind(prover); const proveTransactions = prover.proveTransactions.bind(prover); const merge = prover.merge.bind(prover); + const dummy = prover.dummy.bind(prover); const program = ZkProgram({ name: "TransactionProver", @@ -419,6 +427,13 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< }, }, + dummy: { + privateInputs: [], + async method(publicInput: TransactionProverPublicInput) { + return { publicOutput: await dummy(publicInput) }; + }, + }, + merge: { privateInputs: [ SelfProof< @@ -445,6 +460,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< const methods = { proveTransaction: program.proveTransaction, proveTransactions: program.proveTransactions, + dummy: program.dummy, merge: program.merge, }; @@ -531,6 +547,10 @@ export class TransactionProver ); } + public dummy(publicInput: TransactionProverPublicInput) { + return this.zkProgrammable.dummy(publicInput); + } + public merge( publicInput: TransactionProverPublicInput, proof1: TransactionProof, diff --git a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts index 527dd0833..e1531fdcb 100644 --- a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts @@ -32,23 +32,21 @@ export class BlockFlow { private readonly transactionMergeTask: TransactionReductionTask ) {} + private dummyProof: TransactionProof | undefined = undefined; + private async dummyTransactionProof() { - const publicInput = { - bundlesHash: Field(0), - eternalTransactionsHash: Field(0), - incomingMessagesHash: Field(0), - } satisfies TransactionProverPublicInput; + if (this.dummyProof !== undefined) { + return this.dummyProof; + } - // TODO Set publicInput.stateRoot to result after block hooks! - const publicOutput = new TransactionProverPublicOutput({ - ...publicInput, + const flow = this.flowCreator.createFlow("transaction-dummy", undefined); + const dummy = await flow.withFlow(async (resolve) => { + await flow.pushTask(this.transactionTask, "dummy", async (result) => { + resolve(result); + }); }); - - return await this.protocol.transactionProver.zkProgrammable.zkProgram[0].Proof.dummy( - publicInput, - publicOutput, - 2 - ); + this.dummyProof = dummy; + return dummy; } private async proveTransactions(height: string, traces: TransactionTrace[]) { diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 1b25581cc..dfa151eaf 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -136,7 +136,8 @@ export class NewBlockTask networkState, blockWitness, blockArgumentBatch, - deferSTProof.or(deferTransactionProof) + Bool(false) + // deferSTProof.or(deferTransactionProof) ); } else { await this.blockProver.proveBlockBatchWithProofs( diff --git a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts index 88c77d4db..5f6896b99 100644 --- a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts @@ -6,6 +6,7 @@ import { DynamicRuntimeProof, TransactionProvable, TransactionProof, + TransactionProverPublicInput, } from "@proto-kit/protocol"; import { Runtime } from "@proto-kit/module"; import { inject, injectable, Lifecycle, scoped } from "tsyringe"; @@ -87,9 +88,32 @@ export class TransactionProvingTask ); } + private async computeDummy(): Promise { + await executeWithPrefilledStateService( + this.protocol.stateServiceProvider, + [{}, {}], + async () => { + await this.transactionProver.dummy( + TransactionProverPublicInput.empty() + ); + } + ); + + return await executeWithPrefilledStateService( + this.protocol.stateServiceProvider, + [{}, {}], + async () => + await this.executionContext.current().result.prove() + ); + } + public async compute( input: TransactionProvingTaskParameters ): Promise { + if (input === "dummy") { + return await this.computeDummy(); + } + const startingState = input.flatMap((i) => i.parameters.startingState); await executeWithPrefilledStateService( diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts index 2cb30a520..0f34205ad 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts @@ -84,6 +84,10 @@ export class TransactionProvingTaskParameterSerializer } public toJSON(inputs: TransactionProvingTaskParameters): string { + if (inputs === "dummy") { + return "dummy"; + } + const taskParamsJson: TransactionProvingTaskParametersJSON = inputs.map( (input) => { const { parameters, proof } = input; @@ -119,6 +123,10 @@ export class TransactionProvingTaskParameterSerializer public async fromJSON( json: string ): Promise { + if (json === "dummy") { + return "dummy"; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const jsonReadyObject: TransactionProvingTaskParametersJSON = JSON.parse(json); diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts b/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts index 0db92f4b7..ae4a2ac27 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/types/TransactionProvingTypes.ts @@ -17,7 +17,9 @@ export interface TransactionProverTaskParameters { export type OneOrTwo = [Type] | [Type, Type]; -export type TransactionProvingTaskParameters = OneOrTwo<{ - parameters: TransactionProverTaskParameters; - proof: RuntimeProof; -}>; +export type TransactionProvingTaskParameters = + | "dummy" + | OneOrTwo<{ + parameters: TransactionProverTaskParameters; + proof: RuntimeProof; + }>; From 4e7fd90ed8d1022b6ae24183a182bfc00bbde5bb Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 29 Jan 2026 14:13:26 +0100 Subject: [PATCH 146/155] Fixed linting --- packages/protocol/src/prover/block/BlockProver.ts | 2 -- packages/sequencer/src/protocol/production/flow/BlockFlow.ts | 3 --- 2 files changed, 5 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index f959f0355..f03430c2e 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -64,8 +64,6 @@ const errors = { propertyNotMatching: (propertyName: string) => `${propertyName} not matching`, }; -type Tail = T extends [infer A, ...infer Rest] ? Rest : never; - type BlockHookArgument = T extends "before" ? BeforeBlockHookArguments : AfterBlockHookArguments; diff --git a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts index e1531fdcb..874a4f03d 100644 --- a/packages/sequencer/src/protocol/production/flow/BlockFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BlockFlow.ts @@ -3,10 +3,7 @@ import { MandatoryProtocolModulesRecord, Protocol, TransactionProof, - TransactionProverPublicInput, - TransactionProverPublicOutput, } from "@proto-kit/protocol"; -import { Field } from "o1js"; import { mapSequential } from "@proto-kit/common"; // eslint-disable-next-line import/no-extraneous-dependencies import chunk from "lodash/chunk"; From 19fe01a9f8639f85d539f8fa0f9f029d1c5196b0 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 29 Jan 2026 14:48:48 +0100 Subject: [PATCH 147/155] Fixed integration test --- .../persistance/test-integration/PrismaBlockProduction.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/persistance/test-integration/PrismaBlockProduction.test.ts b/packages/persistance/test-integration/PrismaBlockProduction.test.ts index f6299f6b1..e9d335156 100644 --- a/packages/persistance/test-integration/PrismaBlockProduction.test.ts +++ b/packages/persistance/test-integration/PrismaBlockProduction.test.ts @@ -251,7 +251,7 @@ describe("prisma integration", () => { PrismaTransactionStorage ); - const txs = await txResolver.getPendingUserTransactions(); + const txs = await txResolver.getPendingUserTransactions(0); expectDefined(transaction.transaction); From f8b8fbfebf13392a403e27e06d23944aab63ab78 Mon Sep 17 00:00:00 2001 From: stanlou Date: Tue, 20 Jan 2026 03:25:43 +0100 Subject: [PATCH 148/155] feat: add default modules and configurations for sequencer presets --- package-lock.json | 4 + packages/stack/package.json | 6 +- packages/stack/src/index.ts | 4 + packages/stack/src/presets/app-chain/index.ts | 122 ++++ packages/stack/src/presets/config.ts | 193 ++++++ packages/stack/src/presets/modules/index.ts | 619 ++++++++++++++++++ packages/stack/src/presets/modules/types.ts | 86 +++ packages/stack/src/presets/modules/utils.ts | 381 +++++++++++ packages/stack/src/presets/sequencer/index.ts | 136 ++++ 9 files changed, 1550 insertions(+), 1 deletion(-) create mode 100644 packages/stack/src/presets/app-chain/index.ts create mode 100644 packages/stack/src/presets/config.ts create mode 100644 packages/stack/src/presets/modules/index.ts create mode 100644 packages/stack/src/presets/modules/types.ts create mode 100644 packages/stack/src/presets/modules/utils.ts create mode 100644 packages/stack/src/presets/sequencer/index.ts diff --git a/package-lock.json b/package-lock.json index f5626d39d..5c83cdcdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28777,6 +28777,8 @@ "version": "0.1.1-develop.833+397881ed", "license": "MIT", "dependencies": { + "@prisma/client": "^5.19.1", + "mina-fungible-token": "^1.1.0", "reflect-metadata": "^0.1.13" }, "devDependencies": { @@ -28786,9 +28788,11 @@ "@proto-kit/api": "*", "@proto-kit/common": "*", "@proto-kit/deployment": "*", + "@proto-kit/indexer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/persistance": "*", + "@proto-kit/processor": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", diff --git a/packages/stack/package.json b/packages/stack/package.json index 2851234e1..e983b9fe9 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -28,6 +28,8 @@ "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", + "@proto-kit/indexer": "*", + "@proto-kit/processor": "*", "o1js": "^2.10.0", "tsyringe": "^4.10.0" }, @@ -35,7 +37,9 @@ "@jest/globals": "^29.5.0" }, "dependencies": { - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "@prisma/client": "^5.19.1", + "mina-fungible-token": "^1.1.0" }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } diff --git a/packages/stack/src/index.ts b/packages/stack/src/index.ts index 35d49ee83..3c5da028c 100644 --- a/packages/stack/src/index.ts +++ b/packages/stack/src/index.ts @@ -1 +1,5 @@ export * from "./scripts/graphql/server"; +export * from "./presets/app-chain"; +export * from "./presets/sequencer"; +export * from "./presets/config"; +export * from "./presets/modules"; \ No newline at end of file diff --git a/packages/stack/src/presets/app-chain/index.ts b/packages/stack/src/presets/app-chain/index.ts new file mode 100644 index 000000000..48db076cc --- /dev/null +++ b/packages/stack/src/presets/app-chain/index.ts @@ -0,0 +1,122 @@ +import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; +import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; +import { + MandatoryProtocolModulesRecord, + Protocol, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; +import { + AppChain, + AppChainModulesRecord, + SequencerModulesRecord, +} from "@proto-kit/sequencer"; +import { DefaultModules, DefaultConfigs } from "../modules"; +import { DefaultSequencer, DefaultSequencerConfig } from "../sequencer"; + +export class DefaultAppChain { + static inmemory( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.inmemory({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } + static development( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.development({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } + static sovereign( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.sovereign({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } +} + +export class DefaultAppChainConfig { + static inmemory(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.inmemory({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } + static development(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.development({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } + static sovereign(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.sovereign({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } +} diff --git a/packages/stack/src/presets/config.ts b/packages/stack/src/presets/config.ts new file mode 100644 index 000000000..835402150 --- /dev/null +++ b/packages/stack/src/presets/config.ts @@ -0,0 +1,193 @@ +export const inmemoryConfig = { + PROTOKIT_BLOCK_INTERVAL: 5000, + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHQL_HOST: "localhost", + PROTOKIT_GRAPHIQL_ENABLED: true, +}; + +export const developmentConfig = { + PROTOKIT_PROOFS_ENABLED: false, + PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + PROTOKIT_PRUNE_ON_STARTUP: false, + PROTOKIT_LOG_LEVEL: "INFO", + + PROTOKIT_BLOCK_INTERVAL: 30000, + PROTOKIT_SETTLEMENT_INTERVAL: 60000, + PROTOKIT_SETTLEMENT_ENABLED: true, + + REDIS_HOST: "localhost", + REDIS_PORT: 6379, + REDIS_PASSWORD: "password", + + DATABASE_URL: + "postgresql://admin:password@localhost:5432/protokit?schema=public", + + INDEXER_DATABASE_URL: + "postgresql://admin:password@localhost:5433/protokit-indexer?schema=public", + + PROCESSOR_DATABASE_URL: + "postgresql://admin:password@localhost:5434/protokit-processor?schema=public", + + PROTOKIT_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHIQL_ENABLED: true, + + PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + + PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "0.0.0.0", + + MINA_NETWORK: "lightnet", + MINA_NODE_GRAPHQL_HOST: "http://localhost", + MINA_NODE_GRAPHQL_PORT: 8083, + + MINA_ARCHIVE_GRAPHQL_HOST: "http://localhost", + MINA_ARCHIVE_GRAPHQL_PORT: 8085, + + MINA_ACCOUNT_MANAGER_HOST: "http://localhost", + MINA_ACCOUNT_MANAGER_PORT: 8084, + MINA_EXPLORER_PORT: 3001, + + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", + + PROTOKIT_SEQUENCER_PRIVATE_KEY: + "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + PROTOKIT_SEQUENCER_PUBLIC_KEY: + "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", + PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", + + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", + PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", + + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", + PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", + + PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY: + "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", + + PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY: + "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", + + PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY: + "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", + + TEST_ACCOUNT_1_PRIVATE_KEY: + "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", + TEST_ACCOUNT_1_PUBLIC_KEY: + "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", + + OPEN_TELEMETRY_TRACING_ENABLED: true, + OPEN_TELEMETRY_TRACING_URL: "http://localhost:4318", + + OPEN_TELEMETRY_METRICS_ENABLED: true, + OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", + OPEN_TELEMETRY_METRICS_PORT: 4320, + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + +}; + +export const sovereignConfig = { + PROTOKIT_BLOCK_INTERVAL: 10000, + PROTOKIT_SETTLEMENT_INTERVAL: 30000, + PROTOKIT_SETTLEMENT_ENABLED: true, + + PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + + PROTOKIT_PRUNE_ON_STARTUP: false, + PROTOKIT_LOG_LEVEL: "INFO", + + REDIS_HOST: "redis", + REDIS_PORT: 6379, + REDIS_PASSWORD: "password", + + DATABASE_URL: + "postgresql://admin:password@postgres:5432/protokit?schema=public", + + INDEXER_DATABASE_URL: + "postgresql://admin:password@indexer-postgres:5432/protokit-indexer?schema=public", + + PROCESSOR_DATABASE_URL: + "postgresql://admin:password@processor-postgres:5432/protokit-processor?schema=public", + + PROTOKIT_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHIQL_ENABLED: true, + + PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + + PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "indexer", + + MINA_NETWORK: "lightnet", + MINA_NODE_GRAPHQL_HOST: "http://lightnet", + MINA_NODE_GRAPHQL_PORT: 8080, + + MINA_ARCHIVE_GRAPHQL_HOST: "http://lightnet", + MINA_ARCHIVE_GRAPHQL_PORT: 8282, + + MINA_ACCOUNT_MANAGER_HOST: "http://lightnet", + MINA_ACCOUNT_MANAGER_PORT: 8084, + MINA_EXPLORER_PORT: 3001, + + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", + + PROTOKIT_SEQUENCER_PRIVATE_KEY: + "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + PROTOKIT_SEQUENCER_PUBLIC_KEY: + "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", + PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", + + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", + PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", + + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", + PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", + + TEST_ACCOUNT_1_PRIVATE_KEY: + "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", + TEST_ACCOUNT_1_PUBLIC_KEY: + "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", + + OPEN_TELEMETRY_TRACING_ENABLED: true, + OPEN_TELEMETRY_TRACING_URL: "http://otel-collector:4317", + + OPEN_TELEMETRY_METRICS_ENABLED: true, + OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", + OPEN_TELEMETRY_METRICS_PORT: 4320, + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + +}; \ No newline at end of file diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts new file mode 100644 index 000000000..4561e14e1 --- /dev/null +++ b/packages/stack/src/presets/modules/index.ts @@ -0,0 +1,619 @@ +import { + VanillaGraphqlModules, + GraphqlSequencerModule, + GraphqlServer, + OpenTelemetryServer, +} from "@proto-kit/api"; +import { + PrivateMempool, + SequencerModulesRecord, + TimedBlockTrigger, + BlockProducerModule, + SequencerStartupModule, + LocalTaskWorkerModule, + VanillaTaskWorkerModules, + MinaBaseLayer, + ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + DatabasePruneModule, + InMemoryDatabase, + LocalTaskQueue, +} from "@proto-kit/sequencer"; +import { + IndexerNotifier, + GeneratedResolverFactoryGraphqlModule, + IndexBlockTask, +} from "@proto-kit/indexer"; +import { PrivateKey } from "o1js"; +import { PrismaRedisDatabase } from "@proto-kit/persistance"; +import { BullQueue } from "@proto-kit/deployment"; +import { + TimedProcessorTrigger, + BlockFetching, + HandlersExecutor, + ResolverFactoryGraphqlModule, + HandlersRecord, + BasePrismaClient, +} from "@proto-kit/processor"; +import { + BlockStorageNetworkStateModule, + InMemoryTransactionSender, + StateServiceQueryModule, +} from "@proto-kit/sdk"; +import { AppChainModulesRecord } from "@proto-kit/sequencer"; +import { + buildCustomTokenConfig, + buildSettlementTokenConfig, + definePreset, + orderModulesByDependencies, + parseApiEnv, + parseCoreEnv, + parseMetricsEnv, + parseSettlementEnv, + parseIndexerEnv, + parseProcessorEnv, + parseDatabaseEnv, + parseDatabasePruneEnv, + parseGraphqlServerEnv, + parseRedisEnv, + resolveEnv, +} from "./utils"; +import { NonEmptyArray } from "type-graphql"; +import { + Environment, + ModuleOverrides, + ApiEnv, + ConfigOverrides, + CoreEnv, + MetricsEnv, + IndexerEnv, + ProcessorEnv, + SettlementEnv, + DatabaseEnv, + TaskQueueEnv, + DatabasePruneEnv, + GraphqlServerEnv, + RedisEnv, +} from "./types"; + +export class DefaultModules { + static api(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + GraphqlServer, + Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), + }, + options?.overrides + ); + } + static core(options?: { + overrides?: ModuleOverrides; + settlementEnabled?: boolean; + }): SequencerModulesRecord { + return definePreset( + { + ...DefaultModules.api(), + Mempool: PrivateMempool, + BlockProducerModule, + BlockTrigger: TimedBlockTrigger, + SequencerStartupModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.withoutSettlement() + ), + ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), + }, + options?.overrides + ); + } + static metrics(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + OpenTelemetryServer, + }, + options?.overrides + ); + } + static settlement(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + BaseLayer: MinaBaseLayer, + FeeStrategy: ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + }, + options?.overrides + ); + } + static sequencerIndexer(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + IndexerNotifier, + }, + options?.overrides + ); + } + static indexer(options?: { overrides?: ModuleOverrides }) { + return definePreset( + { + Database: PrismaRedisDatabase, + TaskQueue: BullQueue, + TaskWorker: LocalTaskWorkerModule.from({ + IndexBlockTask, + }), + GraphqlServer, + Graphql: GraphqlSequencerModule.from({ + GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, + }), + }, + options?.overrides + ); + } + static processor( + resolvers: NonEmptyArray, + handlers: HandlersRecord, + options?: { overrides?: ModuleOverrides } + ) { + return definePreset( + { + GraphqlServer, + GraphqlSequencerModule: GraphqlSequencerModule.from({ + ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), + }), + HandlersExecutor: HandlersExecutor.from(handlers), + BlockFetching, + Trigger: TimedProcessorTrigger, + }, + options?.overrides + ); + } + static database(options?: { + overrides?: ModuleOverrides; + preset?: Environment; + }): SequencerModulesRecord { + const preset = options?.preset ?? "inmemory"; + + return definePreset( + { + Database: + preset === "inmemory" ? InMemoryDatabase : PrismaRedisDatabase, + }, + options?.overrides + ); + } + static taskQueue(options?: { + overrides?: ModuleOverrides; + preset?: Environment; + }): SequencerModulesRecord { + const preset = options?.preset ?? "inmemory"; + return definePreset( + { + TaskQueue: preset === "inmemory" ? LocalTaskQueue : BullQueue, + }, + options?.overrides + ); + } + static databasePrune(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + DatabasePruneModule, + }, + options?.overrides + ); + } + static worker(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + TaskQueue: BullQueue, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + }, + options?.overrides + ); + } + static appChainBase(options?: { + overrides?: Partial; + }): AppChainModulesRecord { + return definePreset( + { + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + }, + options?.overrides + ) as AppChainModulesRecord; + } + static settlementScript(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + ...DefaultModules.settlement(), + Mempool: PrivateMempool, + TaskQueue: LocalTaskQueue, + SequencerStartupModule, + }, + options?.overrides + ); + } + static ordered(modules: any) { + return orderModulesByDependencies(modules); + } +} +export class DefaultConfigs { + static api(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset( + { + Graphql: VanillaGraphqlModules.defaultConfig(), + GraphqlServer: DefaultConfigs.graphqlServer({ + type: "protokit", + preset: options?.preset, + envs: options?.envs, + }), + }, + options?.overrides + ); + } + static core(options?: { + preset?: Environment; + envs?: Partial & Partial & Partial; + overrides?: ConfigOverrides; + settlementEnabled?: boolean; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseCoreEnv( + { ...config, ...options?.envs }, + options?.settlementEnabled + ); + const apiConfig = DefaultConfigs.api({ + preset: options?.preset, + envs: options?.envs, + }); + const settlementConfig = options?.settlementEnabled + ? DefaultConfigs.settlement({ + preset: options?.preset, + envs: options?.envs, + }) + : {}; + const blockTriggerConfig = { + blockInterval: parsed.blockInterval, + produceEmptyBlocks: true, + ...(options?.settlementEnabled + ? { + settlementInterval: parsed.settlementInterval, + settlementTokenConfig: buildSettlementTokenConfig( + parsed.minaBridgeKey!, + buildCustomTokenConfig( + parsed.customTokenKey, + parsed.customTokenBridgeKey + ) + ), + } + : { settlementTokenConfig: {} }), + }; + + return definePreset( + { + ...apiConfig, + Mempool: {}, + BlockProducerModule: {}, + BlockTrigger: blockTriggerConfig, + SequencerStartupModule: {}, + LocalTaskWorkerModule: VanillaGraphqlModules.defaultConfig(), + ...settlementConfig, + }, + options?.overrides + ); + } + static metrics(options?: { + preset?: Environment; + envs?: MetricsEnv; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const configs = resolveEnv(options?.preset, options?.envs); + const parsed = parseMetricsEnv(configs); + return definePreset( + { + OpenTelemetryServer: { + metrics: { + enabled: parsed.metricsEnabled, + prometheus: { + host: parsed.metricsHost, + port: parsed.metricsPort, + appendTimestamp: true, + }, + nodeScrapeInterval: parsed.metricsScrapingFrequency, + }, + tracing: { + enabled: parsed.tracingEnabled, + otlp: { + url: parsed.tracingUrl, + }, + }, + }, + }, + options?.overrides + ); + } + static sequencerIndexer(options?: { + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset({ IndexerNotifier: {} }, options?.overrides); + } + static indexer(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseIndexerEnv(config); + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + const databaseConfig = DefaultConfigs.database({ + preset: options?.preset, + envs: options?.envs, + }); + const graphqlServerConfig = DefaultConfigs.graphqlServer({ + type: "indexer", + preset: options?.preset, + envs: options?.envs, + }); + + return definePreset( + { + ...databaseConfig, + TaskQueue: redisConfig.TaskQueue, + TaskWorker: { + IndexBlockTask: {}, + }, + ...graphqlServerConfig, + Graphql: { + GeneratedResolverFactory: {}, + }, + }, + options?.overrides + ); + } + static processor(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseProcessorEnv(config); + const graphqlServerConfig = DefaultConfigs.graphqlServer({ + type: "processor", + preset: options?.preset, + envs: options?.envs, + }); + return definePreset( + { + HandlersExecutor: {}, + BlockFetching: { + url: `http://${parsed.processorIndexerGraphqlHost}:${parsed.indexerGraphqlPort}`, + }, + Trigger: { + interval: (parsed.blockInterval ?? 5000) / 5, + }, + ...graphqlServerConfig, + GraphqlSequencerModule: { + ResolverFactory: {}, + }, + }, + options?.overrides + ); + } + static settlement(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseSettlementEnv(config); + + return definePreset( + { + BaseLayer: { + network: { + type: parsed.network, + graphql: parsed.graphql, + archive: parsed.archive, + accountManager: parsed.accountManager, + }, + }, + SettlementModule: { + feepayer: PrivateKey.fromBase58(parsed.sequencerPrivateKey), + keys: { + settlement: PrivateKey.fromBase58( + parsed.settlementContractPrivateKey + ), + dispatch: PrivateKey.fromBase58( + parsed.dispatcherContractPrivateKey + ), + minaBridge: PrivateKey.fromBase58( + parsed.minaBridgeContractPrivateKey + ), + }, + }, + FeeStrategy: {}, + BatchProducerModule: {}, + }, + options?.overrides + ); + } + static database(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const preset = options?.preset ?? "inmemory"; + if (preset === "inmemory") { + return { Database: definePreset({}, options?.overrides) }; + } + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseDatabaseEnv(config); + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + return { + Database: definePreset( + { + ...redisConfig, + prisma: { + connection: parsed.databaseUrl, + }, + }, + options?.overrides + ), + }; + } + static taskQueue(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const preset = options?.preset ?? "inmemory"; + if (preset === "inmemory") { + return { + TaskQueue: definePreset({}, options?.overrides), + }; + } + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + + return { TaskQueue: definePreset(redisConfig, options?.overrides) }; + } + static databasePrune(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseDatabasePruneEnv(config); + + return { + DatabasePruneModule: definePreset( + { + pruneOnStartup: parsed.pruneOnStartup, + }, + options?.overrides + ), + }; + } + static graphqlServer(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + type?: "indexer" | "processor" | "protokit"; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseGraphqlServerEnv(config, options?.type); + + return definePreset( + { + port: parsed.graphqlPort, + host: parsed.graphqlHost, + graphiql: parsed.graphiqlEnabled, + }, + options?.overrides + ); + } + static redis(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseRedisEnv(config); + + return { + redis: definePreset( + { + host: parsed.redisHost, + port: parsed.redisPort, + password: parsed.redisPassword, + }, + options?.overrides + ), + }; + } + static appChainBase(options?: { + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset( + { + QueryTransportModule: {}, + NetworkStateTransportModule: {}, + TransactionSender: {}, + }, + options?.overrides + ); + } + static worker(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + overrides: { + db: 1, + }, + }); + + return definePreset( + { + TaskQueue: redisConfig, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }, + options?.overrides + ); + } + static settlementScript(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const settlementConfig = DefaultConfigs.settlement({ + preset: options?.preset, + envs: options?.envs, + }); + return definePreset( + { + ...settlementConfig, + SequencerStartupModule: {}, + TaskQueue: { + simulatedDuration: 0, + }, + Mempool: {}, + }, + options?.overrides + ); + } +} diff --git a/packages/stack/src/presets/modules/types.ts b/packages/stack/src/presets/modules/types.ts new file mode 100644 index 000000000..712511168 --- /dev/null +++ b/packages/stack/src/presets/modules/types.ts @@ -0,0 +1,86 @@ +import { RecursivePartial, ModulesConfig } from "@proto-kit/common"; +import { SequencerModulesRecord } from "@proto-kit/sequencer"; + +export type ModuleOverrides = Partial; +export type ConfigOverrides = RecursivePartial>; +export type Environment = "inmemory" | "development" | "sovereign"; +export type ApiEnv = { + PROTOKIT_GRAPHQL_PORT: number | string; + PROTOKIT_GRAPHQL_HOST: string; + PROTOKIT_GRAPHIQL_ENABLED: boolean | string; +}; +export type CoreEnv = { + PROTOKIT_BLOCK_INTERVAL: number | string; + PROTOKIT_SETTLEMENT_INTERVAL?: number | string; + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY?: string; + PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY?: string; + PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY?: string; +}; +export type MetricsEnv = { + OPEN_TELEMETRY_METRICS_ENABLED: boolean | string; + OPEN_TELEMETRY_METRICS_HOST: string; + OPEN_TELEMETRY_METRICS_PORT: number | string; + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: number | string; + OPEN_TELEMETRY_TRACING_ENABLED: boolean | string; + OPEN_TELEMETRY_TRACING_URL: string; +}; +export type SettlementEnv = { + MINA_NETWORK: string; + MINA_NODE_GRAPHQL_HOST: string; + MINA_NODE_GRAPHQL_PORT: number | string; + MINA_ARCHIVE_GRAPHQL_HOST: string; + MINA_ARCHIVE_GRAPHQL_PORT: number | string; + MINA_ACCOUNT_MANAGER_HOST: string; + MINA_ACCOUNT_MANAGER_PORT: number | string; + PROTOKIT_SEQUENCER_PRIVATE_KEY: string; + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: string; + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: string; + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: string; +}; +export type IndexerEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; + INDEXER_DATABASE_URL: string; + PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; + PROTOKIT_INDEXER_GRAPHQL_HOST: string; + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: boolean | string; +}; +export type ProcessorEnv = { + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: string; + PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; + PROTOKIT_BLOCK_INTERVAL: number | string; + PROTOKIT_PROCESSOR_GRAPHQL_HOST: string; + PROTOKIT_PROCESSOR_GRAPHQL_PORT: number | string; + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: boolean | string; +}; +export type DatabaseEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; + DATABASE_URL: string; +}; +export type TaskQueueEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; +}; +export type DatabasePruneEnv = { + PRUNE_ON_STARTUP?: boolean | string; +}; +export type GraphqlServerEnv = { + PROTOKIT_GRAPHQL_PORT?: number | string; + PROTOKIT_GRAPHQL_HOST?: string; + PROTOKIT_GRAPHIQL_ENABLED?: boolean | string; + PROTOKIT_INDEXER_GRAPHQL_HOST?: string; + PROTOKIT_INDEXER_GRAPHQL_PORT?: number | string; + PROTOKIT_INDEXER_GRAPHIQL_ENABLED?: boolean | string; + PROTOKIT_PROCESSOR_GRAPHQL_HOST?: string; + PROTOKIT_PROCESSOR_GRAPHQL_PORT?: number | string; + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED?: boolean | string; +}; +export type RedisEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; +}; diff --git a/packages/stack/src/presets/modules/utils.ts b/packages/stack/src/presets/modules/utils.ts new file mode 100644 index 000000000..679b35165 --- /dev/null +++ b/packages/stack/src/presets/modules/utils.ts @@ -0,0 +1,381 @@ +import { PrivateKey, TokenId } from "o1js"; +import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; +import { FungibleToken } from "mina-fungible-token"; +import { assertDefined } from "@proto-kit/common"; +import { + ApiEnv, + CoreEnv, + DatabaseEnv, + DatabasePruneEnv, + Environment, + GraphqlServerEnv, + IndexerEnv, + MetricsEnv, + ProcessorEnv, + RedisEnv, + SettlementEnv, + TaskQueueEnv, +} from "./types"; + +function ensureDefined(obj: Record, keys: string[]) { + keys.forEach((k) => assertDefined(obj[k], `${k} is required`)); +} +export function definePreset( + base: T, + overrides?: Partial +): T { + return { + ...base, + ...overrides, + }; +} +const MODULE_DEPENDENCIES: Record = { + Database: [], + TaskQueue: [], + OpenTelemetryServer: [], + BaseLayer: [], + FeeStrategy: [], + + Protocol: [], + Mempool: ["Database"], + BlockProducerModule: ["Database"], + + LocalTaskWorkerModule: ["TaskQueue"], + TaskWorker: ["TaskQueue"], + SequencerStartupModule: ["Database"], + BatchProducerModule: ["Database"], + BlockTrigger: ["BlockProducerModule", "BatchProducerModule"], + + SettlementModule: ["Database", "BaseLayer", "FeeStrategy"], + + DatabasePruneModule: ["Database"], + + Graphql: [], + GraphqlServer: [], + + IndexBlockTask: ["Database", "TaskQueue"], + IndexerNotifier: ["Database", "TaskQueue"], + GeneratedResolverFactory: [], + + BlockFetching: [], + HandlersExecutor: [], + Trigger: ["BlockFetching", "HandlersExecutor"], + ResolverFactory: [], +}; +export function orderModulesByDependencies( + modules: Record +): Record { + const moduleSet = new Set(Object.keys(modules)); + const ordered: Record = {}; + const visited = new Set(); + + function visit(name: string) { + if (!moduleSet.has(name) || visited.has(name)) return; + + const deps = MODULE_DEPENDENCIES[name] ?? []; + for (const dep of deps) { + visit(dep); + } + + visited.add(name); + ordered[name] = modules[name]; + } + + for (const name of moduleSet) { + visit(name); + } + + return ordered; +} +export function resolveEnv( + preset: Environment = "inmemory", + envs?: Partial +): T { + return { + ...getConfigs(preset), + ...envs, + } as T; +} +export function buildCustomTokenConfig( + customTokenPrivateKey?: string, + customTokenBridgePrivateKey?: string +): Record { + if (!customTokenPrivateKey || !customTokenBridgePrivateKey) { + return {}; + } + const pk = PrivateKey.fromBase58(customTokenPrivateKey); + const tokenId = TokenId.derive(pk.toPublicKey()).toString(); + return { + [tokenId]: { + bridgingContractPrivateKey: PrivateKey.fromBase58( + customTokenBridgePrivateKey + ), + tokenOwner: FungibleToken, + tokenOwnerPrivateKey: customTokenPrivateKey, + }, + }; +} +export function buildSettlementTokenConfig( + bridgePrivateKey: string, + customTokens: Record = {} +): Record { + return { + "1": { + bridgingContractPrivateKey: PrivateKey.fromBase58(bridgePrivateKey), + }, + ...customTokens, + }; +} +export function getConfigs(preset: Environment) { + switch (preset) { + case "development": + return developmentConfig; + case "sovereign": + return sovereignConfig; + case "inmemory": + default: + return inmemoryConfig; + } +} + +export function parseApiEnv(envs: ApiEnv): { + graphqlPort: number; + graphqlHost: string; + graphiqlEnabled: boolean; +} { + ensureDefined(envs, [ + "PROTOKIT_GRAPHIQL_ENABLED", + "PROTOKIT_GRAPHQL_HOST", + "PROTOKIT_GRAPHQL_PORT", + ]); + return { + graphqlPort: Number(envs.PROTOKIT_GRAPHQL_PORT), + graphqlHost: envs.PROTOKIT_GRAPHQL_HOST, + graphiqlEnabled: Boolean(envs.PROTOKIT_GRAPHIQL_ENABLED), + }; +} +export function parseCoreEnv( + envs: CoreEnv, + settlementEnabled?: boolean +): { + blockInterval: number; + settlementInterval?: number; + minaBridgeKey?: string; + customTokenKey?: string; + customTokenBridgeKey?: string; +} { + ensureDefined(envs, ["PROTOKIT_BLOCK_INTERVAL"]); + if (settlementEnabled) { + ensureDefined(envs, [ + "PROTOKIT_SETTLEMENT_INTERVAL", + "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + ]); + if (envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY) { + ensureDefined(envs, ["PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY"]); + } + } + return { + blockInterval: Number(envs.PROTOKIT_BLOCK_INTERVAL), + settlementInterval: Number(envs.PROTOKIT_SETTLEMENT_INTERVAL), + minaBridgeKey: envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY, + customTokenKey: envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, + customTokenBridgeKey: envs.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, + }; +} +export function parseMetricsEnv(envs: MetricsEnv): { + metricsEnabled: boolean; + metricsHost?: string; + metricsPort?: number; + metricsScrapingFrequency?: number; + tracingEnabled: boolean; + tracingUrl?: string; +} { + ensureDefined(envs as Record, [ + "OPEN_TELEMETRY_METRICS_ENABLED", + "OPEN_TELEMETRY_TRACING_ENABLED", + "OPEN_TELEMETRY_TRACING_URL", + "OPEN_TELEMETRY_METRICS_HOST", + "OPEN_TELEMETRY_METRICS_PORT", + "OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY", + ]); + return { + metricsEnabled: Boolean(envs.OPEN_TELEMETRY_METRICS_ENABLED), + metricsHost: envs.OPEN_TELEMETRY_METRICS_HOST, + metricsPort: Number(envs.OPEN_TELEMETRY_METRICS_PORT), + metricsScrapingFrequency: Number( + envs.OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY + ), + tracingEnabled: Boolean(envs.OPEN_TELEMETRY_TRACING_ENABLED), + tracingUrl: envs.OPEN_TELEMETRY_TRACING_URL, + }; +} +export function parseSettlementEnv(envs: SettlementEnv): { + network: string; + graphql: string; + archive: string; + accountManager: string; + sequencerPrivateKey: string; + settlementContractPrivateKey: string; + dispatcherContractPrivateKey: string; + minaBridgeContractPrivateKey: string; +} { + ensureDefined(envs as Record, [ + "MINA_ACCOUNT_MANAGER_HOST", + "MINA_ACCOUNT_MANAGER_PORT", + "MINA_ARCHIVE_GRAPHQL_HOST", + "MINA_ARCHIVE_GRAPHQL_PORT", + "MINA_NODE_GRAPHQL_HOST", + "MINA_NODE_GRAPHQL_PORT", + "MINA_NETWORK", + "PROTOKIT_SEQUENCER_PRIVATE_KEY", + "PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY", + "PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY", + "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + ]); + + return { + network: envs.MINA_NETWORK!, + graphql: `${envs.MINA_NODE_GRAPHQL_HOST}:${envs.MINA_NODE_GRAPHQL_PORT}/graphql`, + archive: `${envs.MINA_ARCHIVE_GRAPHQL_HOST}:${envs.MINA_ARCHIVE_GRAPHQL_PORT}`, + accountManager: `${envs.MINA_ACCOUNT_MANAGER_HOST}:${envs.MINA_ACCOUNT_MANAGER_PORT}`, + sequencerPrivateKey: envs.PROTOKIT_SEQUENCER_PRIVATE_KEY!, + settlementContractPrivateKey: + envs.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY!, + dispatcherContractPrivateKey: + envs.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY!, + minaBridgeContractPrivateKey: + envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY!, + }; +} +export function parseIndexerEnv(envs: IndexerEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; + indexerDatabaseUrl?: string; + graphqlPort?: number; + graphqlHost?: string; + graphiqlEnabled?: boolean; +} { + ensureDefined(envs as Record, [ + "REDIS_HOST", + "REDIS_PORT", + "REDIS_PASSWORD", + "INDEXER_DATABASE_URL", + "PROTOKIT_INDEXER_GRAPHQL_PORT", + "PROTOKIT_INDEXER_GRAPHQL_HOST", + "PROTOKIT_INDEXER_GRAPHIQL_ENABLED", + ]); + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + indexerDatabaseUrl: envs.INDEXER_DATABASE_URL, + graphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT + ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) + : undefined, + graphqlHost: envs.PROTOKIT_INDEXER_GRAPHQL_HOST, + graphiqlEnabled: envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED + ? Boolean(envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED) + : undefined, + }; +} +export function parseProcessorEnv(envs: ProcessorEnv): { + processorIndexerGraphqlHost?: string; + indexerGraphqlPort?: number; + blockInterval?: number; + processorGraphqlHost?: string; + processorGraphqlPort?: number; + processorGraphiqlEnabled?: boolean; +} { + ensureDefined(envs as Record, [ + "PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST", + "PROTOKIT_INDEXER_GRAPHQL_PORT", + "PROTOKIT_BLOCK_INTERVAL", + "PROTOKIT_PROCESSOR_GRAPHQL_HOST", + "PROTOKIT_PROCESSOR_GRAPHQL_PORT", + "PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED", + ]); + return { + processorIndexerGraphqlHost: envs.PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST, + indexerGraphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT + ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) + : undefined, + blockInterval: envs.PROTOKIT_BLOCK_INTERVAL + ? Number(envs.PROTOKIT_BLOCK_INTERVAL) + : undefined, + processorGraphqlHost: envs.PROTOKIT_PROCESSOR_GRAPHQL_HOST, + processorGraphqlPort: envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT + ? Number(envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT) + : undefined, + processorGraphiqlEnabled: envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED + ? Boolean(envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED) + : undefined, + }; +} +export function parseDatabaseEnv(envs: DatabaseEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; + databaseUrl?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + databaseUrl: envs.DATABASE_URL, + }; +} +export function parseTaskQueueEnv(envs: TaskQueueEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + }; +} +export function parseDatabasePruneEnv(envs: DatabasePruneEnv): { + pruneOnStartup: boolean; +} { + return { + pruneOnStartup: + envs.PRUNE_ON_STARTUP === "true" || envs.PRUNE_ON_STARTUP === true, + }; +} +export function parseGraphqlServerEnv( + envs: GraphqlServerEnv, + type: "protokit" | "indexer" | "processor" = "protokit" +): { + graphqlPort?: number; + graphqlHost?: string; + graphiqlEnabled?: boolean; +} { + const prefix = + type === "indexer" + ? "PROTOKIT_INDEXER" + : type === "processor" + ? "PROTOKIT_PROCESSOR" + : "PROTOKIT"; + return { + graphqlPort: envs[`${prefix}_GRAPHQL_PORT`] + ? Number(envs[`${prefix}_GRAPHQL_PORT`]) + : undefined, + graphqlHost: envs[`${prefix}_GRAPHQL_HOST`], + graphiqlEnabled: envs[`${prefix}_GRAPHIQL_ENABLED`] + ? Boolean(envs[`${prefix}_GRAPHIQL_ENABLED`]) + : undefined, + }; +} +export function parseRedisEnv(envs: RedisEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + }; +} diff --git a/packages/stack/src/presets/sequencer/index.ts b/packages/stack/src/presets/sequencer/index.ts new file mode 100644 index 000000000..1fd6919fb --- /dev/null +++ b/packages/stack/src/presets/sequencer/index.ts @@ -0,0 +1,136 @@ +import { SequencerModulesRecord, Sequencer } from "@proto-kit/sequencer"; +import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; +import { DefaultConfigs, DefaultModules } from "../modules"; +import { definePreset } from "../modules/utils"; + +export class DefaultSequencer { + static inmemory(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + ...DefaultModules.database(), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.taskQueue(), + ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } + static development(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + // ordering of the modules matters due to dependency resolution + ...DefaultModules.database({ preset: "development" }), + ...DefaultModules.databasePrune(), + ...DefaultModules.metrics(), + ...DefaultModules.taskQueue({ preset: "development" }), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.sequencerIndexer(), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } + static sovereign(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + // ordering of the modules matters due to dependency resolution + ...DefaultModules.database(), + ...DefaultModules.taskQueue(), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.sequencerIndexer(), + ...DefaultModules.metrics(), + ...DefaultModules.databasePrune(), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } +} +export class DefaultSequencerConfig { + static inmemory(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ settlementEnabled: options?.settlementEnabled }), + ...DefaultConfigs.database({ preset: "inmemory" }), + ...DefaultConfigs.taskQueue({ preset: "inmemory" }), + ...options?.overrideConfig, + }; + } + static development(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ + settlementEnabled: options?.settlementEnabled, + preset: "development", + }), + ...DefaultConfigs.sequencerIndexer(), + ...DefaultConfigs.metrics({ preset: "development" }), + ...DefaultConfigs.databasePrune({ preset: "development" }), + ...DefaultConfigs.taskQueue({ + preset: "development", + overrides: { + ...DefaultConfigs.redis({ + preset: "development", + overrides: { db: 1 }, + }), + }, + }), + ...DefaultConfigs.database({ preset: "development" }), + ...options?.overrideConfig, + }; + } + static sovereign(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ + settlementEnabled: options?.settlementEnabled, + preset: "sovereign", + }), + ...DefaultConfigs.sequencerIndexer(), + ...DefaultConfigs.metrics({ preset: "sovereign" }), + ...DefaultConfigs.databasePrune({ preset: "sovereign" }), + ...DefaultConfigs.taskQueue({ + preset: "sovereign", + overrides: { + ...DefaultConfigs.redis({ + preset: "sovereign", + overrides: { db: 1 }, + }), + }, + }), + ...DefaultConfigs.database({ preset: "sovereign" }), + ...options?.overrideConfig, + }; + } +} From bd8862197f4b5760db90b8865835c0b3514a3edc Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 05:44:12 +0100 Subject: [PATCH 149/155] use default modules --- packages/processor/src/index.ts | 1 + packages/stack/package.json | 4 +- packages/stack/src/index.ts | 6 +- packages/stack/src/presets/app-chain/index.ts | 122 --- packages/stack/src/presets/config.ts | 235 +++--- packages/stack/src/presets/modules/index.ts | 779 ++++++++---------- packages/stack/src/presets/modules/types.ts | 123 ++- packages/stack/src/presets/modules/utils.ts | 369 +-------- packages/stack/src/presets/sequencer/index.ts | 136 --- 9 files changed, 533 insertions(+), 1242 deletions(-) delete mode 100644 packages/stack/src/presets/app-chain/index.ts delete mode 100644 packages/stack/src/presets/sequencer/index.ts diff --git a/packages/processor/src/index.ts b/packages/processor/src/index.ts index b6bc05ff7..918e46e03 100644 --- a/packages/processor/src/index.ts +++ b/packages/processor/src/index.ts @@ -1,6 +1,7 @@ export * from "./Processor"; export * from "./ProcessorModule"; export * from "./handlers/HandlersExecutor"; +export * from "./handlers/BasePrismaClient"; export * from "./storage/Database"; export * from "./triggers/TimedProcessorTrigger"; export * from "./indexer/BlockFetching"; diff --git a/packages/stack/package.json b/packages/stack/package.json index e983b9fe9..dc3c50ad3 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -39,7 +39,9 @@ "dependencies": { "reflect-metadata": "^0.1.13", "@prisma/client": "^5.19.1", - "mina-fungible-token": "^1.1.0" + "mina-fungible-token": "^1.1.0", + "type-graphql": "2.0.0-rc.2" + }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } diff --git a/packages/stack/src/index.ts b/packages/stack/src/index.ts index 3c5da028c..fb31453cc 100644 --- a/packages/stack/src/index.ts +++ b/packages/stack/src/index.ts @@ -1,5 +1,5 @@ export * from "./scripts/graphql/server"; -export * from "./presets/app-chain"; -export * from "./presets/sequencer"; export * from "./presets/config"; -export * from "./presets/modules"; \ No newline at end of file +export * from "./presets/modules/types"; +export * from "./presets/modules/utils"; +export * from "./presets/modules"; diff --git a/packages/stack/src/presets/app-chain/index.ts b/packages/stack/src/presets/app-chain/index.ts deleted file mode 100644 index 48db076cc..000000000 --- a/packages/stack/src/presets/app-chain/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; -import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; -import { - MandatoryProtocolModulesRecord, - Protocol, - ProtocolModulesRecord, -} from "@proto-kit/protocol"; -import { - AppChain, - AppChainModulesRecord, - SequencerModulesRecord, -} from "@proto-kit/sequencer"; -import { DefaultModules, DefaultConfigs } from "../modules"; -import { DefaultSequencer, DefaultSequencerConfig } from "../sequencer"; - -export class DefaultAppChain { - static inmemory( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.inmemory({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } - static development( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.development({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } - static sovereign( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.sovereign({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } -} - -export class DefaultAppChainConfig { - static inmemory(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.inmemory({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } - static development(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.development({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } - static sovereign(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.sovereign({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } -} diff --git a/packages/stack/src/presets/config.ts b/packages/stack/src/presets/config.ts index 835402150..040b14f42 100644 --- a/packages/stack/src/presets/config.ts +++ b/packages/stack/src/presets/config.ts @@ -1,193 +1,184 @@ export const inmemoryConfig = { - PROTOKIT_BLOCK_INTERVAL: 5000, - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHQL_HOST: "localhost", - PROTOKIT_GRAPHIQL_ENABLED: true, + blockInterval: 5000, + graphqlHost: "localhost", + graphqlPort: 8080, + graphiqlEnabled: true, }; - export const developmentConfig = { - PROTOKIT_PROOFS_ENABLED: false, - PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, - PROTOKIT_PRUNE_ON_STARTUP: false, - PROTOKIT_LOG_LEVEL: "INFO", - - PROTOKIT_BLOCK_INTERVAL: 30000, - PROTOKIT_SETTLEMENT_INTERVAL: 60000, - PROTOKIT_SETTLEMENT_ENABLED: true, - - REDIS_HOST: "localhost", - REDIS_PORT: 6379, - REDIS_PASSWORD: "password", - - DATABASE_URL: + proofsEnabled: false, + + shouldAttemptDbMigration: true, + shouldAttemptIndexerDbMigration: true, + shouldAttemptProcessorDbMigration: true, + + pruneOnStartup: false, + + blockInterval: 30000, + settlementInterval: 60000, + settlementEnabled: true, + + redisHost: "localhost", + redisPort: 6379, + redisPassword: "password", + + databaseUrl: "postgresql://admin:password@localhost:5432/protokit?schema=public", - INDEXER_DATABASE_URL: + indexerDatabaseUrl: "postgresql://admin:password@localhost:5433/protokit-indexer?schema=public", - PROCESSOR_DATABASE_URL: + processorDatabaseUrl: "postgresql://admin:password@localhost:5434/protokit-processor?schema=public", - PROTOKIT_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHIQL_ENABLED: true, + graphqlHost: "0.0.0.0", + graphqlPort: 8080, + graphiqlEnabled: true, - PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + indexerGraphqlHost: "0.0.0.0", + indexerGraphqlPort: 8081, + indexerGraphqlEnabled: true, - PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "0.0.0.0", + processorGraphqlHost: "0.0.0.0", + processorGraphqlPort: 8082, + processorGraphqlEnabled: true, - MINA_NETWORK: "lightnet", - MINA_NODE_GRAPHQL_HOST: "http://localhost", - MINA_NODE_GRAPHQL_PORT: 8083, + processorIndexerGraphqlHost: "0.0.0.0", - MINA_ARCHIVE_GRAPHQL_HOST: "http://localhost", - MINA_ARCHIVE_GRAPHQL_PORT: 8085, + minaNetwork: "lightnet", + minaNodeGraphqlHost: "http://localhost", + minaNodeGraphqlPort: 8083, - MINA_ACCOUNT_MANAGER_HOST: "http://localhost", - MINA_ACCOUNT_MANAGER_PORT: 8084, - MINA_EXPLORER_PORT: 3001, + minaArchiveGraphqlHost: "http://localhost", + minaArchiveGraphqlPort: 8085, - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + minaAccountManagerHost: "http://localhost", + minaAccountManagerPort: 8084, + minaExplorerPort: 3001, + + transactionFeeRecipientPrivateKey: "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + transactionFeeRecipientPublicKey: "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", - PROTOKIT_SEQUENCER_PRIVATE_KEY: - "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", - PROTOKIT_SEQUENCER_PUBLIC_KEY: - "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + sequencerPrivateKey: "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + sequencerPublicKey: "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + settlementContractPrivateKey: "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", - PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + settlementContractPublicKey: "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + dispatcherContractPrivateKey: "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", - PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + dispatcherContractPublicKey: "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + minaBridgeContractPrivateKey: "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", - PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + minaBridgeContractPublicKey: "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", - PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY: - "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", + customTokenPrivateKey: "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", - PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY: + customTokenAdminPrivateKey: "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", - PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY: + customTokenBridgePrivateKey: "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", - TEST_ACCOUNT_1_PRIVATE_KEY: + testAccount1PrivateKey: "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", - TEST_ACCOUNT_1_PUBLIC_KEY: + testAccount1PublicKey: "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", - OPEN_TELEMETRY_TRACING_ENABLED: true, - OPEN_TELEMETRY_TRACING_URL: "http://localhost:4318", - - OPEN_TELEMETRY_METRICS_ENABLED: true, - OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", - OPEN_TELEMETRY_METRICS_PORT: 4320, - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + openTelemetryTracingEnabled: true, + openTelemetryTracingUrl: "http://localhost:4318", + openTelemetryMetricsEnabled: true, + openTelemetryMetricsHost: "0.0.0.0", + openTelemetryMetricsPort: 4320, + openTelemetryMetricsScrapingFrequency: 10, }; - export const sovereignConfig = { - PROTOKIT_BLOCK_INTERVAL: 10000, - PROTOKIT_SETTLEMENT_INTERVAL: 30000, - PROTOKIT_SETTLEMENT_ENABLED: true, + blockInterval: 10000, + settlementInterval: 30000, + settlementEnabled: true, - PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + shouldAttemptDbMigration: true, + shouldAttemptIndexerDbMigration: true, + shouldAttemptProcessorDbMigration: true, - PROTOKIT_PRUNE_ON_STARTUP: false, - PROTOKIT_LOG_LEVEL: "INFO", + pruneOnStartup: false, - REDIS_HOST: "redis", - REDIS_PORT: 6379, - REDIS_PASSWORD: "password", + redisHost: "redis", + redisPort: 6379, + redisPassword: "password", - DATABASE_URL: + databaseUrl: "postgresql://admin:password@postgres:5432/protokit?schema=public", - INDEXER_DATABASE_URL: + indexerDatabaseUrl: "postgresql://admin:password@indexer-postgres:5432/protokit-indexer?schema=public", - PROCESSOR_DATABASE_URL: + processorDatabaseUrl: "postgresql://admin:password@processor-postgres:5432/protokit-processor?schema=public", - PROTOKIT_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHIQL_ENABLED: true, + graphqlHost: "0.0.0.0", + graphqlPort: 8080, + graphiqlEnabled: true, - PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + indexerGraphqlHost: "0.0.0.0", + indexerGraphqlPort: 8081, + indexerGraphqlEnabled: true, - PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "indexer", + processorGraphqlHost: "0.0.0.0", + processorGraphqlPort: 8082, + processorGraphqlEnabled: true, + processorIndexerGraphqlHost: "indexer", - MINA_NETWORK: "lightnet", - MINA_NODE_GRAPHQL_HOST: "http://lightnet", - MINA_NODE_GRAPHQL_PORT: 8080, + minaNetwork: "lightnet", + minaNodeGraphqlHost: "http://lightnet", + minaNodeGraphqlPort: 8080, - MINA_ARCHIVE_GRAPHQL_HOST: "http://lightnet", - MINA_ARCHIVE_GRAPHQL_PORT: 8282, + minaArchiveGraphqlHost: "http://lightnet", + minaArchiveGraphqlPort: 8282, - MINA_ACCOUNT_MANAGER_HOST: "http://lightnet", - MINA_ACCOUNT_MANAGER_PORT: 8084, - MINA_EXPLORER_PORT: 3001, - - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + minaAccountManagerHost: "http://lightnet", + minaAccountManagerPort: 8084, + minaExplorerPort: 3001, + transactionFeeRecipientPrivateKey: "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + transactionFeeRecipientPublicKey: "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", - PROTOKIT_SEQUENCER_PRIVATE_KEY: - "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", - PROTOKIT_SEQUENCER_PUBLIC_KEY: - "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + sequencerPrivateKey: "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + sequencerPublicKey: "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + settlementContractPrivateKey: "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", - PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + settlementContractPublicKey: "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + dispatcherContractPrivateKey: "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", - PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + dispatcherContractPublicKey: "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + minaBridgeContractPrivateKey: "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", - PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + minaBridgeContractPublicKey: "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", - TEST_ACCOUNT_1_PRIVATE_KEY: + testAccount1PrivateKey: "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", - TEST_ACCOUNT_1_PUBLIC_KEY: + testAccount1PublicKey: "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", - - OPEN_TELEMETRY_TRACING_ENABLED: true, - OPEN_TELEMETRY_TRACING_URL: "http://otel-collector:4317", - OPEN_TELEMETRY_METRICS_ENABLED: true, - OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", - OPEN_TELEMETRY_METRICS_PORT: 4320, - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + openTelemetryTracingEnabled: true, + openTelemetryTracingUrl: "http://otel-collector:4317", -}; \ No newline at end of file + openTelemetryMetricsEnabled: true, + openTelemetryMetricsHost: "0.0.0.0", + openTelemetryMetricsPort: 4320, + openTelemetryMetricsScrapingFrequency: 10, +}; diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index 4561e14e1..d1067aadd 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -19,13 +19,13 @@ import { DatabasePruneModule, InMemoryDatabase, LocalTaskQueue, + AppChainModulesRecord, } from "@proto-kit/sequencer"; import { IndexerNotifier, GeneratedResolverFactoryGraphqlModule, IndexBlockTask, } from "@proto-kit/indexer"; -import { PrivateKey } from "o1js"; import { PrismaRedisDatabase } from "@proto-kit/persistance"; import { BullQueue } from "@proto-kit/deployment"; import { @@ -41,579 +41,476 @@ import { InMemoryTransactionSender, StateServiceQueryModule, } from "@proto-kit/sdk"; -import { AppChainModulesRecord } from "@proto-kit/sequencer"; +import { PrivateKey } from "o1js"; +import { NonEmptyArray } from "type-graphql"; + import { buildCustomTokenConfig, buildSettlementTokenConfig, - definePreset, - orderModulesByDependencies, - parseApiEnv, - parseCoreEnv, - parseMetricsEnv, - parseSettlementEnv, - parseIndexerEnv, - parseProcessorEnv, - parseDatabaseEnv, - parseDatabasePruneEnv, - parseGraphqlServerEnv, - parseRedisEnv, resolveEnv, } from "./utils"; -import { NonEmptyArray } from "type-graphql"; import { Environment, - ModuleOverrides, - ApiEnv, - ConfigOverrides, CoreEnv, MetricsEnv, IndexerEnv, ProcessorEnv, SettlementEnv, + RedisEnv, DatabaseEnv, - TaskQueueEnv, - DatabasePruneEnv, + RedisTaskQueueEnv, GraphqlServerEnv, - RedisEnv, } from "./types"; export class DefaultModules { - static api(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - GraphqlServer, - Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), - }, - options?.overrides - ); + static api() { + return { + GraphqlServer, + Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), + } satisfies SequencerModulesRecord; } - static core(options?: { - overrides?: ModuleOverrides; - settlementEnabled?: boolean; - }): SequencerModulesRecord { - return definePreset( - { - ...DefaultModules.api(), - Mempool: PrivateMempool, - BlockProducerModule, - BlockTrigger: TimedBlockTrigger, - SequencerStartupModule, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.withoutSettlement() - ), - ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), - }, - options?.overrides - ); + + static core(options?: { settlementEnabled?: boolean }) { + const settlementEnabled = options?.settlementEnabled ?? false; + return { + ...(settlementEnabled ? DefaultModules.settlement() : {}), + ...DefaultModules.api(), + Mempool: PrivateMempool, + BlockProducerModule, + BlockTrigger: TimedBlockTrigger, + SequencerStartupModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.withoutSettlement() + ), + } satisfies SequencerModulesRecord; } - static metrics(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - OpenTelemetryServer, - }, - options?.overrides - ); + + static metrics() { + return { + OpenTelemetryServer, + } satisfies SequencerModulesRecord; } - static settlement(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - BaseLayer: MinaBaseLayer, - FeeStrategy: ConstantFeeStrategy, - BatchProducerModule, - SettlementModule, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.allTasks() - ), - }, - options?.overrides - ); + + static settlement() { + return { + BaseLayer: MinaBaseLayer, + FeeStrategy: ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + } satisfies SequencerModulesRecord; } - static sequencerIndexer(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - IndexerNotifier, - }, - options?.overrides - ); + + static sequencerIndexer() { + return { + IndexerNotifier, + } satisfies SequencerModulesRecord; } - static indexer(options?: { overrides?: ModuleOverrides }) { - return definePreset( - { - Database: PrismaRedisDatabase, - TaskQueue: BullQueue, - TaskWorker: LocalTaskWorkerModule.from({ - IndexBlockTask, - }), - GraphqlServer, - Graphql: GraphqlSequencerModule.from({ - GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, - }), - }, - options?.overrides - ); + + static indexer() { + return { + Database: PrismaRedisDatabase, + TaskQueue: BullQueue, + TaskWorker: LocalTaskWorkerModule.from({ + IndexBlockTask, + }), + GraphqlServer, + Graphql: GraphqlSequencerModule.from({ + GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, + }), + } satisfies SequencerModulesRecord; } + static processor( resolvers: NonEmptyArray, - handlers: HandlersRecord, - options?: { overrides?: ModuleOverrides } + handlers: HandlersRecord ) { - return definePreset( - { - GraphqlServer, - GraphqlSequencerModule: GraphqlSequencerModule.from({ - ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), - }), - HandlersExecutor: HandlersExecutor.from(handlers), - BlockFetching, - Trigger: TimedProcessorTrigger, - }, - options?.overrides - ); + return { + GraphqlServer, + GraphqlSequencerModule: GraphqlSequencerModule.from({ + ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), + }), + HandlersExecutor: HandlersExecutor.from(handlers), + BlockFetching, + Trigger: TimedProcessorTrigger, + } satisfies SequencerModulesRecord; } - static database(options?: { - overrides?: ModuleOverrides; - preset?: Environment; - }): SequencerModulesRecord { - const preset = options?.preset ?? "inmemory"; - return definePreset( - { - Database: - preset === "inmemory" ? InMemoryDatabase : PrismaRedisDatabase, - }, - options?.overrides - ); + static inMemoryDatabase() { + return { + Database: InMemoryDatabase, + } satisfies SequencerModulesRecord; } - static taskQueue(options?: { - overrides?: ModuleOverrides; - preset?: Environment; - }): SequencerModulesRecord { - const preset = options?.preset ?? "inmemory"; - return definePreset( - { - TaskQueue: preset === "inmemory" ? LocalTaskQueue : BullQueue, - }, - options?.overrides - ); + + static PrismaRedisDatabase() { + return { + Database: PrismaRedisDatabase, + DatabasePruneModule, + } satisfies SequencerModulesRecord; } - static databasePrune(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - DatabasePruneModule, - }, - options?.overrides - ); + + static localTaskQueue() { + return { + TaskQueue: LocalTaskQueue, + } satisfies SequencerModulesRecord; } - static worker(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - TaskQueue: BullQueue, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.allTasks() - ), - }, - options?.overrides - ); + + static RedisTaskQueue() { + return { + TaskQueue: BullQueue, + } satisfies SequencerModulesRecord; } - static appChainBase(options?: { - overrides?: Partial; - }): AppChainModulesRecord { - return definePreset( - { - TransactionSender: InMemoryTransactionSender, - QueryTransportModule: StateServiceQueryModule, - NetworkStateTransportModule: BlockStorageNetworkStateModule, - }, - options?.overrides - ) as AppChainModulesRecord; + + static worker() { + return { + TaskQueue: BullQueue, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + } satisfies SequencerModulesRecord; } - static settlementScript(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - ...DefaultModules.settlement(), - Mempool: PrivateMempool, - TaskQueue: LocalTaskQueue, - SequencerStartupModule, - }, - options?.overrides - ); + + static appChainBase() { + return { + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + } satisfies AppChainModulesRecord; } - static ordered(modules: any) { - return orderModulesByDependencies(modules); + + static settlementScript() { + return { + ...DefaultModules.settlement(), + Mempool: PrivateMempool, + TaskQueue: LocalTaskQueue, + SequencerStartupModule, + } satisfies SequencerModulesRecord; } } export class DefaultConfigs { static api(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset( - { - Graphql: VanillaGraphqlModules.defaultConfig(), - GraphqlServer: DefaultConfigs.graphqlServer({ - type: "protokit", - preset: options?.preset, - envs: options?.envs, - }), - }, - options?.overrides - ); + overrides?: Partial; + }) { + return { + Graphql: VanillaGraphqlModules.defaultConfig(), + GraphqlServer: DefaultConfigs.graphqlServer({ + preset: options?.preset, + overrides: options?.overrides, + }), + }; } + static core(options?: { preset?: Environment; - envs?: Partial & Partial & Partial; - overrides?: ConfigOverrides; + overrides?: Partial & + Partial & + Partial; settlementEnabled?: boolean; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseCoreEnv( - { ...config, ...options?.envs }, - options?.settlementEnabled - ); + }) { + const settlementEnabled = options?.settlementEnabled ?? false; + const config = resolveEnv(options?.preset, options?.overrides); const apiConfig = DefaultConfigs.api({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - const settlementConfig = options?.settlementEnabled + const settlementConfig = settlementEnabled ? DefaultConfigs.settlement({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }) : {}; const blockTriggerConfig = { - blockInterval: parsed.blockInterval, + blockInterval: config.blockInterval, produceEmptyBlocks: true, - ...(options?.settlementEnabled + ...(settlementEnabled ? { - settlementInterval: parsed.settlementInterval, + settlementInterval: config.settlementInterval, settlementTokenConfig: buildSettlementTokenConfig( - parsed.minaBridgeKey!, + config.minaBridgeContractPrivateKey!, buildCustomTokenConfig( - parsed.customTokenKey, - parsed.customTokenBridgeKey + config.customTokenPrivateKey, + config.customTokenBridgePrivateKey ) ), } : { settlementTokenConfig: {} }), }; - return definePreset( - { - ...apiConfig, - Mempool: {}, - BlockProducerModule: {}, - BlockTrigger: blockTriggerConfig, - SequencerStartupModule: {}, - LocalTaskWorkerModule: VanillaGraphqlModules.defaultConfig(), - ...settlementConfig, - }, - options?.overrides - ); + return { + ...apiConfig, + Mempool: {}, + BlockProducerModule: {}, + BlockTrigger: blockTriggerConfig, + SequencerStartupModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + ...settlementConfig, + }; } + static metrics(options?: { preset?: Environment; - envs?: MetricsEnv; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const configs = resolveEnv(options?.preset, options?.envs); - const parsed = parseMetricsEnv(configs); - return definePreset( - { - OpenTelemetryServer: { - metrics: { - enabled: parsed.metricsEnabled, - prometheus: { - host: parsed.metricsHost, - port: parsed.metricsPort, - appendTimestamp: true, - }, - nodeScrapeInterval: parsed.metricsScrapingFrequency, + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); + return { + OpenTelemetryServer: { + metrics: { + enabled: config.metricsEnabled, + prometheus: { + host: config.metricsHost, + port: config.metricsPort, + appendTimestamp: true, }, - tracing: { - enabled: parsed.tracingEnabled, - otlp: { - url: parsed.tracingUrl, - }, + nodeScrapeInterval: config.metricsScrapingFrequency, + }, + tracing: { + enabled: config.tracingEnabled, + otlp: { + url: config.tracingUrl, }, }, }, - options?.overrides - ); + }; } - static sequencerIndexer(options?: { - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset({ IndexerNotifier: {} }, options?.overrides); + + static sequencerIndexer() { + return { IndexerNotifier: {} }; } + static indexer(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseIndexerEnv(config); - const redisConfig = DefaultConfigs.redis({ + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); + const taskQueueConfig = DefaultConfigs.redisTaskQueue({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - const databaseConfig = DefaultConfigs.database({ + const databaseConfig = DefaultConfigs.prismaRedisDatabase({ preset: options?.preset, - envs: options?.envs, + overrides: { + databaseUrl: config.indexerDatabaseUrl, + ...options?.overrides, + }, }); const graphqlServerConfig = DefaultConfigs.graphqlServer({ - type: "indexer", preset: options?.preset, - envs: options?.envs, + overrides: { + graphqlHost: config.indexerGraphqlHost, + graphqlPort: config.indexerGraphqlPort, + graphiqlEnabled: config.indexerGraphqlEnabled, + ...options?.overrides, + }, }); - return definePreset( - { - ...databaseConfig, - TaskQueue: redisConfig.TaskQueue, - TaskWorker: { - IndexBlockTask: {}, - }, - ...graphqlServerConfig, - Graphql: { - GeneratedResolverFactory: {}, - }, + return { + ...databaseConfig, + ...taskQueueConfig, + TaskWorker: { + IndexBlockTask: {}, }, - options?.overrides - ); + ...graphqlServerConfig, + Graphql: { + GeneratedResolverFactory: {}, + }, + }; } + static processor(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseProcessorEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); const graphqlServerConfig = DefaultConfigs.graphqlServer({ - type: "processor", preset: options?.preset, - envs: options?.envs, + overrides: { + graphqlHost: config.processorGraphqlHost, + graphqlPort: config.processorGraphqlPort, + graphiqlEnabled: config.processorGraphqlEnabled, + ...options?.overrides, + }, }); - return definePreset( - { - HandlersExecutor: {}, - BlockFetching: { - url: `http://${parsed.processorIndexerGraphqlHost}:${parsed.indexerGraphqlPort}`, - }, - Trigger: { - interval: (parsed.blockInterval ?? 5000) / 5, - }, - ...graphqlServerConfig, - GraphqlSequencerModule: { - ResolverFactory: {}, - }, + return { + HandlersExecutor: {}, + BlockFetching: { + url: `http://${config.processorIndexerGraphqlHost}:${config.indexerGraphqlPort}`, }, - options?.overrides - ); + Trigger: { + interval: Number(config.blockInterval) / 5, + }, + ...graphqlServerConfig, + GraphqlSequencerModule: { + ResolverFactory: {}, + }, + }; } + static settlement(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseSettlementEnv(config); - - return definePreset( - { - BaseLayer: { - network: { - type: parsed.network, - graphql: parsed.graphql, - archive: parsed.archive, - accountManager: parsed.accountManager, - }, + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); + + return { + BaseLayer: { + network: { + type: "lightnet" as const, + graphql: config.minaNodeGraphqlHost, + archive: config.minaArchiveGraphqlHost, + accountManager: config.minaAccountManagerHost, }, - SettlementModule: { - feepayer: PrivateKey.fromBase58(parsed.sequencerPrivateKey), - keys: { - settlement: PrivateKey.fromBase58( - parsed.settlementContractPrivateKey - ), - dispatch: PrivateKey.fromBase58( - parsed.dispatcherContractPrivateKey - ), - minaBridge: PrivateKey.fromBase58( - parsed.minaBridgeContractPrivateKey - ), - }, + }, + SettlementModule: { + feepayer: PrivateKey.fromBase58(config.sequencerPrivateKey), + keys: { + settlement: PrivateKey.fromBase58( + config.settlementContractPrivateKey + ), + dispatch: PrivateKey.fromBase58(config.dispatcherContractPrivateKey), + minaBridge: PrivateKey.fromBase58( + config.minaBridgeContractPrivateKey + ), }, - FeeStrategy: {}, - BatchProducerModule: {}, }, - options?.overrides - ); + FeeStrategy: {}, + BatchProducerModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }; + } + + static inMemoryDatabase() { + return { Database: {} }; } - static database(options?: { + + static prismaRedisDatabase(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const preset = options?.preset ?? "inmemory"; - if (preset === "inmemory") { - return { Database: definePreset({}, options?.overrides) }; - } - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseDatabaseEnv(config); + overrides?: Partial; + }) { + const preset = options?.preset ?? "development"; + const config = resolveEnv(preset, options?.overrides); const redisConfig = DefaultConfigs.redis({ - preset: options?.preset, - envs: options?.envs, + preset, + overrides: options?.overrides, }); return { - Database: definePreset( - { - ...redisConfig, - prisma: { - connection: parsed.databaseUrl, - }, + Database: { + ...redisConfig, + prisma: { + connection: config.databaseUrl, }, - options?.overrides - ), + }, + DatabasePruneModule: { + pruneOnStartup: config.pruneOnStartup, + }, }; } - static taskQueue(options?: { - preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const preset = options?.preset ?? "inmemory"; - if (preset === "inmemory") { - return { - TaskQueue: definePreset({}, options?.overrides), - }; - } - const redisConfig = DefaultConfigs.redis({ - preset: options?.preset, - envs: options?.envs, - }); - return { TaskQueue: definePreset(redisConfig, options?.overrides) }; + static localTaskQueue() { + return { + TaskQueue: {}, + }; } - static databasePrune(options?: { + + static redisTaskQueue(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseDatabasePruneEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); return { - DatabasePruneModule: definePreset( - { - pruneOnStartup: parsed.pruneOnStartup, + TaskQueue: { + redis: { + host: config.redisHost, + port: config.redisPort, + password: config.redisPassword, + db: config.redisDb, }, - options?.overrides - ), + retryAttempts: config.retryAttempts, + }, }; } + static graphqlServer(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - type?: "indexer" | "processor" | "protokit"; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseGraphqlServerEnv(config, options?.type); - - return definePreset( - { - port: parsed.graphqlPort, - host: parsed.graphqlHost, - graphiql: parsed.graphiqlEnabled, - }, + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, options?.overrides ); + + return { + port: config.graphqlPort, + host: config.graphqlHost, + graphiql: config.graphiqlEnabled, + }; } + static redis(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseRedisEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); return { - redis: definePreset( - { - host: parsed.redisHost, - port: parsed.redisPort, - password: parsed.redisPassword, - }, - options?.overrides - ), + redis: { + host: config.redisHost, + port: config.redisPort, + password: config.redisPassword, + }, }; } - static appChainBase(options?: { - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset( - { - QueryTransportModule: {}, - NetworkStateTransportModule: {}, - TransactionSender: {}, - }, - options?.overrides - ); + + static appChainBase() { + return { + QueryTransportModule: {}, + NetworkStateTransportModule: {}, + TransactionSender: {}, + }; } + static worker(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const redisConfig = DefaultConfigs.redis({ + overrides?: Partial; + }) { + const taskQueueConfig = DefaultConfigs.redisTaskQueue({ preset: options?.preset, - envs: options?.envs, - overrides: { - db: 1, - }, + overrides: options?.overrides, }); - return definePreset( - { - TaskQueue: redisConfig, - LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), - }, - options?.overrides - ); + return { + ...taskQueueConfig, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }; } + static settlementScript(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { + overrides?: Partial; + }) { const settlementConfig = DefaultConfigs.settlement({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - return definePreset( - { - ...settlementConfig, - SequencerStartupModule: {}, - TaskQueue: { - simulatedDuration: 0, - }, - Mempool: {}, + return { + ...settlementConfig, + SequencerStartupModule: {}, + TaskQueue: { + simulatedDuration: 0, }, - options?.overrides - ); + Mempool: {}, + }; } } diff --git a/packages/stack/src/presets/modules/types.ts b/packages/stack/src/presets/modules/types.ts index 712511168..65efe1603 100644 --- a/packages/stack/src/presets/modules/types.ts +++ b/packages/stack/src/presets/modules/types.ts @@ -1,86 +1,63 @@ -import { RecursivePartial, ModulesConfig } from "@proto-kit/common"; -import { SequencerModulesRecord } from "@proto-kit/sequencer"; - -export type ModuleOverrides = Partial; -export type ConfigOverrides = RecursivePartial>; export type Environment = "inmemory" | "development" | "sovereign"; -export type ApiEnv = { - PROTOKIT_GRAPHQL_PORT: number | string; - PROTOKIT_GRAPHQL_HOST: string; - PROTOKIT_GRAPHIQL_ENABLED: boolean | string; + +export type GraphqlServerEnv = { + graphqlPort: number; + graphqlHost: string; + graphiqlEnabled: boolean; }; export type CoreEnv = { - PROTOKIT_BLOCK_INTERVAL: number | string; - PROTOKIT_SETTLEMENT_INTERVAL?: number | string; - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY?: string; - PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY?: string; - PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY?: string; + blockInterval: number; + settlementInterval?: number; + minaBridgeContractPrivateKey?: string; + customTokenPrivateKey?: string; + customTokenBridgePrivateKey?: string; }; export type MetricsEnv = { - OPEN_TELEMETRY_METRICS_ENABLED: boolean | string; - OPEN_TELEMETRY_METRICS_HOST: string; - OPEN_TELEMETRY_METRICS_PORT: number | string; - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: number | string; - OPEN_TELEMETRY_TRACING_ENABLED: boolean | string; - OPEN_TELEMETRY_TRACING_URL: string; + metricsEnabled: boolean; + metricsHost: string; + metricsPort: number; + metricsScrapingFrequency: number; + tracingEnabled: boolean; + tracingUrl: string; }; export type SettlementEnv = { - MINA_NETWORK: string; - MINA_NODE_GRAPHQL_HOST: string; - MINA_NODE_GRAPHQL_PORT: number | string; - MINA_ARCHIVE_GRAPHQL_HOST: string; - MINA_ARCHIVE_GRAPHQL_PORT: number | string; - MINA_ACCOUNT_MANAGER_HOST: string; - MINA_ACCOUNT_MANAGER_PORT: number | string; - PROTOKIT_SEQUENCER_PRIVATE_KEY: string; - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: string; - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: string; - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: string; -}; -export type IndexerEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; - INDEXER_DATABASE_URL: string; - PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; - PROTOKIT_INDEXER_GRAPHQL_HOST: string; - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: boolean | string; + minaNetwork: string; + minaNodeGraphqlHost: string; + minaNodeGraphqlPort: number; + minaArchiveGraphqlHost: string; + minaArchiveGraphqlPort: number; + minaAccountManagerHost: string; + minaAccountManagerPort: number; + sequencerPrivateKey: string; + settlementContractPrivateKey: string; + dispatcherContractPrivateKey: string; + minaBridgeContractPrivateKey: string; +}; +export type IndexerEnv = RedisTaskQueueEnv & { + indexerDatabaseUrl: string; + indexerGraphqlHost: string; + indexerGraphqlPort: number; + indexerGraphqlEnabled: boolean; + pruneOnStartup?: boolean; }; export type ProcessorEnv = { - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: string; - PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; - PROTOKIT_BLOCK_INTERVAL: number | string; - PROTOKIT_PROCESSOR_GRAPHQL_HOST: string; - PROTOKIT_PROCESSOR_GRAPHQL_PORT: number | string; - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: boolean | string; -}; -export type DatabaseEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; - DATABASE_URL: string; + processorIndexerGraphqlHost: string; + indexerGraphqlPort: number; + blockInterval: number; + processorGraphqlHost: string; + processorGraphqlPort: number; + processorGraphqlEnabled: boolean; }; -export type TaskQueueEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; -}; -export type DatabasePruneEnv = { - PRUNE_ON_STARTUP?: boolean | string; -}; -export type GraphqlServerEnv = { - PROTOKIT_GRAPHQL_PORT?: number | string; - PROTOKIT_GRAPHQL_HOST?: string; - PROTOKIT_GRAPHIQL_ENABLED?: boolean | string; - PROTOKIT_INDEXER_GRAPHQL_HOST?: string; - PROTOKIT_INDEXER_GRAPHQL_PORT?: number | string; - PROTOKIT_INDEXER_GRAPHIQL_ENABLED?: boolean | string; - PROTOKIT_PROCESSOR_GRAPHQL_HOST?: string; - PROTOKIT_PROCESSOR_GRAPHQL_PORT?: number | string; - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED?: boolean | string; +export type DatabaseEnv = RedisEnv & { + databaseUrl: string; + pruneOnStartup?: boolean; }; export type RedisEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; + redisHost: string; + redisPort: number; + redisPassword: string; +}; +export type RedisTaskQueueEnv = RedisEnv & { + redisDb?: number; + retryAttempts?: number; }; diff --git a/packages/stack/src/presets/modules/utils.ts b/packages/stack/src/presets/modules/utils.ts index 679b35165..ebe16616f 100644 --- a/packages/stack/src/presets/modules/utils.ts +++ b/packages/stack/src/presets/modules/utils.ts @@ -1,106 +1,40 @@ import { PrivateKey, TokenId } from "o1js"; -import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; import { FungibleToken } from "mina-fungible-token"; -import { assertDefined } from "@proto-kit/common"; -import { - ApiEnv, - CoreEnv, - DatabaseEnv, - DatabasePruneEnv, - Environment, - GraphqlServerEnv, - IndexerEnv, - MetricsEnv, - ProcessorEnv, - RedisEnv, - SettlementEnv, - TaskQueueEnv, -} from "./types"; - -function ensureDefined(obj: Record, keys: string[]) { - keys.forEach((k) => assertDefined(obj[k], `${k} is required`)); -} -export function definePreset( - base: T, - overrides?: Partial -): T { - return { - ...base, - ...overrides, - }; -} -const MODULE_DEPENDENCIES: Record = { - Database: [], - TaskQueue: [], - OpenTelemetryServer: [], - BaseLayer: [], - FeeStrategy: [], - - Protocol: [], - Mempool: ["Database"], - BlockProducerModule: ["Database"], - - LocalTaskWorkerModule: ["TaskQueue"], - TaskWorker: ["TaskQueue"], - SequencerStartupModule: ["Database"], - BatchProducerModule: ["Database"], - BlockTrigger: ["BlockProducerModule", "BatchProducerModule"], - - SettlementModule: ["Database", "BaseLayer", "FeeStrategy"], - DatabasePruneModule: ["Database"], - - Graphql: [], - GraphqlServer: [], - - IndexBlockTask: ["Database", "TaskQueue"], - IndexerNotifier: ["Database", "TaskQueue"], - GeneratedResolverFactory: [], - - BlockFetching: [], - HandlersExecutor: [], - Trigger: ["BlockFetching", "HandlersExecutor"], - ResolverFactory: [], -}; -export function orderModulesByDependencies( - modules: Record -): Record { - const moduleSet = new Set(Object.keys(modules)); - const ordered: Record = {}; - const visited = new Set(); - - function visit(name: string) { - if (!moduleSet.has(name) || visited.has(name)) return; - - const deps = MODULE_DEPENDENCIES[name] ?? []; - for (const dep of deps) { - visit(dep); - } +import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; - visited.add(name); - ordered[name] = modules[name]; - } +import { Environment } from "./types"; - for (const name of moduleSet) { - visit(name); +export function getConfigs(preset: Environment) { + switch (preset) { + case "development": + return developmentConfig; + case "sovereign": + return sovereignConfig; + case "inmemory": + default: + return inmemoryConfig; } - - return ordered; } export function resolveEnv( preset: Environment = "inmemory", - envs?: Partial + envs?: Partial | undefined ): T { - return { - ...getConfigs(preset), - ...envs, - } as T; + const config = getConfigs(preset); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + if (!envs) return config as T; + const resolved = { ...config, ...envs } satisfies Partial; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return resolved as T; } export function buildCustomTokenConfig( customTokenPrivateKey?: string, customTokenBridgePrivateKey?: string -): Record { - if (!customTokenPrivateKey || !customTokenBridgePrivateKey) { +) { + if ( + customTokenPrivateKey === undefined || + customTokenBridgePrivateKey === undefined + ) { return {}; } const pk = PrivateKey.fromBase58(customTokenPrivateKey); @@ -117,8 +51,8 @@ export function buildCustomTokenConfig( } export function buildSettlementTokenConfig( bridgePrivateKey: string, - customTokens: Record = {} -): Record { + customTokens: Record = {} +) { return { "1": { bridgingContractPrivateKey: PrivateKey.fromBase58(bridgePrivateKey), @@ -126,256 +60,3 @@ export function buildSettlementTokenConfig( ...customTokens, }; } -export function getConfigs(preset: Environment) { - switch (preset) { - case "development": - return developmentConfig; - case "sovereign": - return sovereignConfig; - case "inmemory": - default: - return inmemoryConfig; - } -} - -export function parseApiEnv(envs: ApiEnv): { - graphqlPort: number; - graphqlHost: string; - graphiqlEnabled: boolean; -} { - ensureDefined(envs, [ - "PROTOKIT_GRAPHIQL_ENABLED", - "PROTOKIT_GRAPHQL_HOST", - "PROTOKIT_GRAPHQL_PORT", - ]); - return { - graphqlPort: Number(envs.PROTOKIT_GRAPHQL_PORT), - graphqlHost: envs.PROTOKIT_GRAPHQL_HOST, - graphiqlEnabled: Boolean(envs.PROTOKIT_GRAPHIQL_ENABLED), - }; -} -export function parseCoreEnv( - envs: CoreEnv, - settlementEnabled?: boolean -): { - blockInterval: number; - settlementInterval?: number; - minaBridgeKey?: string; - customTokenKey?: string; - customTokenBridgeKey?: string; -} { - ensureDefined(envs, ["PROTOKIT_BLOCK_INTERVAL"]); - if (settlementEnabled) { - ensureDefined(envs, [ - "PROTOKIT_SETTLEMENT_INTERVAL", - "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", - ]); - if (envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY) { - ensureDefined(envs, ["PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY"]); - } - } - return { - blockInterval: Number(envs.PROTOKIT_BLOCK_INTERVAL), - settlementInterval: Number(envs.PROTOKIT_SETTLEMENT_INTERVAL), - minaBridgeKey: envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY, - customTokenKey: envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, - customTokenBridgeKey: envs.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, - }; -} -export function parseMetricsEnv(envs: MetricsEnv): { - metricsEnabled: boolean; - metricsHost?: string; - metricsPort?: number; - metricsScrapingFrequency?: number; - tracingEnabled: boolean; - tracingUrl?: string; -} { - ensureDefined(envs as Record, [ - "OPEN_TELEMETRY_METRICS_ENABLED", - "OPEN_TELEMETRY_TRACING_ENABLED", - "OPEN_TELEMETRY_TRACING_URL", - "OPEN_TELEMETRY_METRICS_HOST", - "OPEN_TELEMETRY_METRICS_PORT", - "OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY", - ]); - return { - metricsEnabled: Boolean(envs.OPEN_TELEMETRY_METRICS_ENABLED), - metricsHost: envs.OPEN_TELEMETRY_METRICS_HOST, - metricsPort: Number(envs.OPEN_TELEMETRY_METRICS_PORT), - metricsScrapingFrequency: Number( - envs.OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY - ), - tracingEnabled: Boolean(envs.OPEN_TELEMETRY_TRACING_ENABLED), - tracingUrl: envs.OPEN_TELEMETRY_TRACING_URL, - }; -} -export function parseSettlementEnv(envs: SettlementEnv): { - network: string; - graphql: string; - archive: string; - accountManager: string; - sequencerPrivateKey: string; - settlementContractPrivateKey: string; - dispatcherContractPrivateKey: string; - minaBridgeContractPrivateKey: string; -} { - ensureDefined(envs as Record, [ - "MINA_ACCOUNT_MANAGER_HOST", - "MINA_ACCOUNT_MANAGER_PORT", - "MINA_ARCHIVE_GRAPHQL_HOST", - "MINA_ARCHIVE_GRAPHQL_PORT", - "MINA_NODE_GRAPHQL_HOST", - "MINA_NODE_GRAPHQL_PORT", - "MINA_NETWORK", - "PROTOKIT_SEQUENCER_PRIVATE_KEY", - "PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY", - "PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY", - "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", - ]); - - return { - network: envs.MINA_NETWORK!, - graphql: `${envs.MINA_NODE_GRAPHQL_HOST}:${envs.MINA_NODE_GRAPHQL_PORT}/graphql`, - archive: `${envs.MINA_ARCHIVE_GRAPHQL_HOST}:${envs.MINA_ARCHIVE_GRAPHQL_PORT}`, - accountManager: `${envs.MINA_ACCOUNT_MANAGER_HOST}:${envs.MINA_ACCOUNT_MANAGER_PORT}`, - sequencerPrivateKey: envs.PROTOKIT_SEQUENCER_PRIVATE_KEY!, - settlementContractPrivateKey: - envs.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY!, - dispatcherContractPrivateKey: - envs.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY!, - minaBridgeContractPrivateKey: - envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY!, - }; -} -export function parseIndexerEnv(envs: IndexerEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; - indexerDatabaseUrl?: string; - graphqlPort?: number; - graphqlHost?: string; - graphiqlEnabled?: boolean; -} { - ensureDefined(envs as Record, [ - "REDIS_HOST", - "REDIS_PORT", - "REDIS_PASSWORD", - "INDEXER_DATABASE_URL", - "PROTOKIT_INDEXER_GRAPHQL_PORT", - "PROTOKIT_INDEXER_GRAPHQL_HOST", - "PROTOKIT_INDEXER_GRAPHIQL_ENABLED", - ]); - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - indexerDatabaseUrl: envs.INDEXER_DATABASE_URL, - graphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT - ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) - : undefined, - graphqlHost: envs.PROTOKIT_INDEXER_GRAPHQL_HOST, - graphiqlEnabled: envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED - ? Boolean(envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED) - : undefined, - }; -} -export function parseProcessorEnv(envs: ProcessorEnv): { - processorIndexerGraphqlHost?: string; - indexerGraphqlPort?: number; - blockInterval?: number; - processorGraphqlHost?: string; - processorGraphqlPort?: number; - processorGraphiqlEnabled?: boolean; -} { - ensureDefined(envs as Record, [ - "PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST", - "PROTOKIT_INDEXER_GRAPHQL_PORT", - "PROTOKIT_BLOCK_INTERVAL", - "PROTOKIT_PROCESSOR_GRAPHQL_HOST", - "PROTOKIT_PROCESSOR_GRAPHQL_PORT", - "PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED", - ]); - return { - processorIndexerGraphqlHost: envs.PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST, - indexerGraphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT - ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) - : undefined, - blockInterval: envs.PROTOKIT_BLOCK_INTERVAL - ? Number(envs.PROTOKIT_BLOCK_INTERVAL) - : undefined, - processorGraphqlHost: envs.PROTOKIT_PROCESSOR_GRAPHQL_HOST, - processorGraphqlPort: envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT - ? Number(envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT) - : undefined, - processorGraphiqlEnabled: envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED - ? Boolean(envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED) - : undefined, - }; -} -export function parseDatabaseEnv(envs: DatabaseEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; - databaseUrl?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - databaseUrl: envs.DATABASE_URL, - }; -} -export function parseTaskQueueEnv(envs: TaskQueueEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - }; -} -export function parseDatabasePruneEnv(envs: DatabasePruneEnv): { - pruneOnStartup: boolean; -} { - return { - pruneOnStartup: - envs.PRUNE_ON_STARTUP === "true" || envs.PRUNE_ON_STARTUP === true, - }; -} -export function parseGraphqlServerEnv( - envs: GraphqlServerEnv, - type: "protokit" | "indexer" | "processor" = "protokit" -): { - graphqlPort?: number; - graphqlHost?: string; - graphiqlEnabled?: boolean; -} { - const prefix = - type === "indexer" - ? "PROTOKIT_INDEXER" - : type === "processor" - ? "PROTOKIT_PROCESSOR" - : "PROTOKIT"; - return { - graphqlPort: envs[`${prefix}_GRAPHQL_PORT`] - ? Number(envs[`${prefix}_GRAPHQL_PORT`]) - : undefined, - graphqlHost: envs[`${prefix}_GRAPHQL_HOST`], - graphiqlEnabled: envs[`${prefix}_GRAPHIQL_ENABLED`] - ? Boolean(envs[`${prefix}_GRAPHIQL_ENABLED`]) - : undefined, - }; -} -export function parseRedisEnv(envs: RedisEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - }; -} diff --git a/packages/stack/src/presets/sequencer/index.ts b/packages/stack/src/presets/sequencer/index.ts deleted file mode 100644 index 1fd6919fb..000000000 --- a/packages/stack/src/presets/sequencer/index.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { SequencerModulesRecord, Sequencer } from "@proto-kit/sequencer"; -import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; -import { DefaultConfigs, DefaultModules } from "../modules"; -import { definePreset } from "../modules/utils"; - -export class DefaultSequencer { - static inmemory(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - ...DefaultModules.database(), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.taskQueue(), - ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } - static development(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - // ordering of the modules matters due to dependency resolution - ...DefaultModules.database({ preset: "development" }), - ...DefaultModules.databasePrune(), - ...DefaultModules.metrics(), - ...DefaultModules.taskQueue({ preset: "development" }), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.sequencerIndexer(), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } - static sovereign(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - // ordering of the modules matters due to dependency resolution - ...DefaultModules.database(), - ...DefaultModules.taskQueue(), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.sequencerIndexer(), - ...DefaultModules.metrics(), - ...DefaultModules.databasePrune(), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } -} -export class DefaultSequencerConfig { - static inmemory(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ settlementEnabled: options?.settlementEnabled }), - ...DefaultConfigs.database({ preset: "inmemory" }), - ...DefaultConfigs.taskQueue({ preset: "inmemory" }), - ...options?.overrideConfig, - }; - } - static development(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ - settlementEnabled: options?.settlementEnabled, - preset: "development", - }), - ...DefaultConfigs.sequencerIndexer(), - ...DefaultConfigs.metrics({ preset: "development" }), - ...DefaultConfigs.databasePrune({ preset: "development" }), - ...DefaultConfigs.taskQueue({ - preset: "development", - overrides: { - ...DefaultConfigs.redis({ - preset: "development", - overrides: { db: 1 }, - }), - }, - }), - ...DefaultConfigs.database({ preset: "development" }), - ...options?.overrideConfig, - }; - } - static sovereign(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ - settlementEnabled: options?.settlementEnabled, - preset: "sovereign", - }), - ...DefaultConfigs.sequencerIndexer(), - ...DefaultConfigs.metrics({ preset: "sovereign" }), - ...DefaultConfigs.databasePrune({ preset: "sovereign" }), - ...DefaultConfigs.taskQueue({ - preset: "sovereign", - overrides: { - ...DefaultConfigs.redis({ - preset: "sovereign", - overrides: { db: 1 }, - }), - }, - }), - ...DefaultConfigs.database({ preset: "sovereign" }), - ...options?.overrideConfig, - }; - } -} From 4f0a0b8c221dd5e7be17d460fe8d230b551d8cce Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 11:25:45 +0100 Subject: [PATCH 150/155] fix code format --- packages/stack/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stack/package.json b/packages/stack/package.json index dc3c50ad3..9258a54cc 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -41,7 +41,6 @@ "@prisma/client": "^5.19.1", "mina-fungible-token": "^1.1.0", "type-graphql": "2.0.0-rc.2" - }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } From 19ad5b9f9929cb9113d2fc7045fcf6ce2bd373f1 Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 12:49:56 +0100 Subject: [PATCH 151/155] fix graphql server config --- packages/stack/src/presets/modules/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index d1067aadd..9c61fbdaf 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -196,7 +196,7 @@ export class DefaultConfigs { }) { return { Graphql: VanillaGraphqlModules.defaultConfig(), - GraphqlServer: DefaultConfigs.graphqlServer({ + ...DefaultConfigs.graphqlServer({ preset: options?.preset, overrides: options?.overrides, }), @@ -452,9 +452,11 @@ export class DefaultConfigs { ); return { - port: config.graphqlPort, - host: config.graphqlHost, - graphiql: config.graphiqlEnabled, + GraphqlServer: { + port: config.graphqlPort, + host: config.graphqlHost, + graphiql: config.graphiqlEnabled, + }, }; } From cfafd175c308e3112249b375d42f64ce029d7296 Mon Sep 17 00:00:00 2001 From: stanlou Date: Wed, 28 Jan 2026 16:55:36 +0300 Subject: [PATCH 152/155] extend CLI with interactive setup and operational commands --- package-lock.json | 195 +++++++-- packages/cli/package.json | 16 +- packages/cli/src/index.ts | 358 ++++++++++++++++- packages/cli/src/scripts/bridge/deposit.ts | 183 +++++++++ packages/cli/src/scripts/bridge/redeem.ts | 151 +++++++ packages/cli/src/scripts/bridge/withdraw.ts | 78 ++++ .../cli/src/scripts/env/create-environment.ts | 102 +++++ packages/cli/src/scripts/explorer/start.ts | 57 +++ packages/cli/src/scripts/generateKeys.ts | 21 + .../graphqlDocs}/generateGqlDocs.ts | 4 +- packages/cli/src/scripts/lightnet/faucet.ts | 79 ++++ .../src/scripts/lightnet/wait-for-network.ts | 41 ++ .../cli/src/scripts/lightnetInitialize.ts | 32 ++ .../src/scripts/settlement/deploy-token.ts | 248 ++++++++++++ packages/cli/src/scripts/settlement/deploy.ts | 86 ++++ packages/cli/src/utils/create-environment.ts | 372 ++++++++++++++++++ .../src/{utils.ts => utils/graphqlDocs.ts} | 3 + packages/cli/src/utils/loadEnv.ts | 65 +++ packages/cli/src/utils/loadUserModules.ts | 60 +++ packages/explorer/src/config.ts | 3 +- packages/stack/src/presets/modules/index.ts | 9 + 21 files changed, 2122 insertions(+), 41 deletions(-) create mode 100644 packages/cli/src/scripts/bridge/deposit.ts create mode 100644 packages/cli/src/scripts/bridge/redeem.ts create mode 100644 packages/cli/src/scripts/bridge/withdraw.ts create mode 100644 packages/cli/src/scripts/env/create-environment.ts create mode 100644 packages/cli/src/scripts/explorer/start.ts create mode 100644 packages/cli/src/scripts/generateKeys.ts rename packages/cli/src/{commands => scripts/graphqlDocs}/generateGqlDocs.ts (94%) create mode 100644 packages/cli/src/scripts/lightnet/faucet.ts create mode 100644 packages/cli/src/scripts/lightnet/wait-for-network.ts create mode 100644 packages/cli/src/scripts/lightnetInitialize.ts create mode 100644 packages/cli/src/scripts/settlement/deploy-token.ts create mode 100644 packages/cli/src/scripts/settlement/deploy.ts create mode 100644 packages/cli/src/utils/create-environment.ts rename packages/cli/src/{utils.ts => utils/graphqlDocs.ts} (98%) create mode 100644 packages/cli/src/utils/loadEnv.ts create mode 100644 packages/cli/src/utils/loadUserModules.ts diff --git a/package-lock.json b/package-lock.json index 5c83cdcdb..61f19dbb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7144,6 +7144,16 @@ "ink": "*" } }, + "node_modules/@types/inquirer": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz", + "integrity": "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "license": "MIT" @@ -7351,6 +7361,15 @@ "@types/node": "*" } }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "dev": true, @@ -8808,7 +8827,6 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "dev": true, "funding": [ { "type": "github", @@ -8929,7 +8947,6 @@ }, "node_modules/bl": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -9101,7 +9118,6 @@ }, "node_modules/buffer": { "version": "5.7.1", - "dev": true, "funding": [ { "type": "github", @@ -9507,7 +9523,6 @@ }, "node_modules/chardet": { "version": "0.7.0", - "dev": true, "license": "MIT" }, "node_modules/cheerio": { @@ -9665,7 +9680,6 @@ }, "node_modules/cli-spinners": { "version": "2.6.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9756,7 +9770,6 @@ }, "node_modules/clone": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -10752,7 +10765,6 @@ }, "node_modules/defaults": { "version": "1.0.4", - "dev": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -12467,7 +12479,6 @@ }, "node_modules/external-editor": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "chardet": "^0.7.0", @@ -14930,7 +14941,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "dev": true, "funding": [ { "type": "github", @@ -16129,7 +16139,6 @@ }, "node_modules/is-interactive": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16400,7 +16409,6 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -19555,6 +19563,11 @@ "license": "MIT", "optional": true }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "license": "MIT" @@ -19572,7 +19585,6 @@ }, "node_modules/log-symbols": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -19587,7 +19599,6 @@ }, "node_modules/log-symbols/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -19601,7 +19612,6 @@ }, "node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -19616,7 +19626,6 @@ }, "node_modules/log-symbols/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -19627,12 +19636,10 @@ }, "node_modules/log-symbols/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/log-symbols/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -19640,7 +19647,6 @@ }, "node_modules/log-symbols/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -22451,7 +22457,6 @@ }, "node_modules/ora": { "version": "5.4.1", - "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -22473,7 +22478,6 @@ }, "node_modules/ora/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -22487,7 +22491,6 @@ }, "node_modules/ora/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -22502,7 +22505,6 @@ }, "node_modules/ora/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -22513,12 +22515,10 @@ }, "node_modules/ora/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/ora/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -22526,7 +22526,6 @@ }, "node_modules/ora/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -24394,7 +24393,6 @@ }, "node_modules/readable-stream": { "version": "3.6.2", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -24742,7 +24740,6 @@ }, "node_modules/rxjs": { "version": "7.8.1", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -25762,7 +25759,6 @@ }, "node_modules/string_decoder": { "version": "1.3.0", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -26608,7 +26604,6 @@ }, "node_modules/tmp": { "version": "0.0.33", - "dev": true, "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" @@ -27794,7 +27789,6 @@ }, "node_modules/wcwidth": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" @@ -28413,6 +28407,12 @@ "version": "0.1.0", "license": "ISC", "dependencies": { + "@inquirer/figures": "^2.0.3", + "dotenv": "^17.2.3", + "inquirer": "^9.3.0", + "kleur": "^4.1.5", + "mina-fungible-token": "^1.1.0", + "reflect-metadata": "^0.1.13", "spectaql": "3.0.5", "ts-node": "^10.9.1", "yargs": "17.7.2" @@ -28421,18 +28421,148 @@ "proto-kit": "bin/protokit-cli.js" }, "devDependencies": { + "@types/inquirer": "^9.0.9", "@types/node": "^20.19.24", "@types/yargs": "17.0.32" }, "peerDependencies": { "@proto-kit/api": "*", "@proto-kit/common": "*", + "@proto-kit/explorer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", - "o1js": "^2.10.0" + "@proto-kit/stack": "*", + "o1js": "^2.10.0", + "tsyringe": "^4.10.0" + } + }, + "packages/cli/node_modules/@inquirer/figures": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.3.tgz", + "integrity": "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "packages/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/cli/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "packages/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "packages/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "packages/cli/node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "packages/cli/node_modules/inquirer": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.0.tgz", + "integrity": "sha512-zdopqPUKWmnOcaBJYMMtjqWCB2HHXrteAou9tCYgkTJu01QheLfYOrkzigDfidPBtCizmkdpSU0fp2DKaMdFPA==", + "dependencies": { + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "picocolors": "^1.0.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/cli/node_modules/inquirer/node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "engines": { + "node": ">=18" + } + }, + "packages/cli/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "packages/cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "packages/cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" + } + }, + "packages/cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, "packages/common": { @@ -28779,7 +28909,8 @@ "dependencies": { "@prisma/client": "^5.19.1", "mina-fungible-token": "^1.1.0", - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "type-graphql": "2.0.0-rc.2" }, "devDependencies": { "@jest/globals": "^29.5.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 7fbd14143..a37868ff3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -21,21 +21,31 @@ "author": "", "license": "ISC", "dependencies": { - "yargs": "17.7.2", + "@inquirer/figures": "^2.0.3", + "dotenv": "^17.2.3", + "inquirer": "^9.3.0", + "kleur": "^4.1.5", + "mina-fungible-token": "^1.1.0", + "reflect-metadata": "^0.1.13", + "spectaql": "3.0.5", "ts-node": "^10.9.1", - "spectaql": "3.0.5" + "yargs": "17.7.2" }, "peerDependencies": { "@proto-kit/api": "*", "@proto-kit/common": "*", + "@proto-kit/explorer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", - "o1js": "^2.10.0" + "@proto-kit/stack": "*", + "o1js": "^2.10.0", + "tsyringe": "^4.10.0" }, "devDependencies": { + "@types/inquirer": "^9.0.9", "@types/node": "^20.19.24", "@types/yargs": "17.0.32" } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index fbd97cd2c..d8f53fad7 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node + +/* eslint-disable no-console */ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; -import { generateGqlDocsCommand } from "./commands/generateGqlDocs"; +import { parseEnvArgs } from "./utils/loadEnv"; process.removeAllListeners("warning"); process.env.NODE_NO_WARNINGS = "1"; await yargs(hideBin(process.argv)) + .scriptName("proto-kit") + .usage("$0 [options]") + .strict() .command( "generate-gql-docs", - "generate GraphQL docs", + "Generate GraphQL docs", (yarg) => yarg .option("port", { @@ -33,6 +38,9 @@ await yargs(hideBin(process.argv)) }), async (args) => { try { + const { generateGqlDocsCommand } = await import( + "./scripts/graphqlDocs/generateGqlDocs" + ); await generateGqlDocsCommand(args); process.exit(0); } catch (error) { @@ -41,7 +49,349 @@ await yargs(hideBin(process.argv)) } } ) - .demandCommand() - .help() + .command( + "generate-keys [count]", + "Generate private/public key pairs for development", + (yarg) => + yarg.positional("count", { + type: "number", + default: 1, + describe: "number of keys to generate", + }), + async (args) => { + try { + const { generateKeysCommand } = await import("./scripts/generateKeys"); + await generateKeysCommand({ count: args.count }); + process.exit(0); + } catch (error) { + console.error("Failed to generate keys:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:wait-for-network", + "Wait for lightnet network to be ready\n\nRequires: MINA_NODE_GRAPHQL_HOST, MINA_NODE_GRAPHQL_PORT", + (yarg) => + yarg + .option("env-path", { + type: "string", + describe: "path to .env file", + }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: lightnetWaitForNetworkScript } = await import( + "./scripts/lightnet/wait-for-network" + ); + await lightnetWaitForNetworkScript({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to wait for network:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:faucet ", + "Send MINA to an account from the lightnet faucet", + (yarg) => + yarg + .positional("publicKey", { + type: "string", + describe: "public key to send MINA to", + demandOption: true, + }) + .option("env-path", { + type: "string", + describe: "path to .env file", + }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: lightnetFaucetScript } = await import( + "./scripts/lightnet/faucet" + ); + await lightnetFaucetScript(args.publicKey); + process.exit(0); + } catch (error) { + console.error("Failed to send funds from faucet:", error); + process.exit(1); + } + } + ) + .command( + "settlement:deploy", + "Deploy settlement contracts\n\nRequires: PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: settlementDeployScript } = await import( + "./scripts/settlement/deploy" + ); + await settlementDeployScript({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to deploy settlement:", error); + process.exit(1); + } + } + ) + .command( + "settlement:token:deploy [mintAmount]", + "Deploy custom fungible token for settlement\n\nRequires: PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenSymbol", { type: "string", demandOption: true }) + .positional("feepayerKey", { type: "string", demandOption: true }) + .positional("receiverPublicKey", { + type: "string", + demandOption: true, + }) + .positional("mintAmount", { type: "number", default: 0 }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: settlementTokenDeployScript } = await import( + "./scripts/settlement/deploy-token" + ); + await settlementTokenDeployScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenSymbol: args.tokenSymbol, + feepayerKey: args.feepayerKey, + receiverPublicKey: args.receiverPublicKey, + mintAmount: args.mintAmount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to deploy settlement token:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:initialize", + "Initialize lightnet: wait for network, fund accounts, and deploy settlement\n\nRequires: MINA_NODE_GRAPHQL_HOST, MINA_NODE_GRAPHQL_PORT, MINA_ARCHIVE_GRAPHQL_HOST, MINA_ARCHIVE_GRAPHQL_PORT, MINA_ACCOUNT_MANAGER_HOST, MINA_ACCOUNT_MANAGER_PORT, PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { lightnetInitializeCommand } = await import( + "./scripts/lightnetInitialize" + ); + await lightnetInitializeCommand({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to initialize lightnet:", error); + process.exit(1); + } + } + ) + .command( + "bridge:deposit ", + "Deposit tokens to the bridge\n\nRequires: PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY (for custom tokens), PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("fromKey", { type: "string", demandOption: true }) + .positional("toKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeDepositScript } = await import( + "./scripts/bridge/deposit" + ); + await bridgeDepositScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + fromKey: args.fromKey, + toKey: args.toKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to deposit to bridge:", error); + process.exit(1); + } + } + ) + .command( + "bridge:redeem ", + "Redeem tokens from the bridge\n\nRequires: PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("toKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeRedeemScript } = await import( + "./scripts/bridge/redeem" + ); + await bridgeRedeemScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + toKey: args.toKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to redeem from bridge:", error); + process.exit(1); + } + } + ) + .command( + "bridge:withdraw ", + "Withdraw tokens\n\nRequires: NEXT_PUBLIC_PROTOKIT_GRAPHQL_URL", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("senderKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeWithdrawScript } = await import( + "./scripts/bridge/withdraw" + ); + await bridgeWithdrawScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + senderKey: args.senderKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to withdraw from bridge:", error); + process.exit(1); + } + } + ) + .command( + "env:create", + "Create a new environment configuration with guided wizard", + () => ({}), + async () => { + try { + const { default: createEnvironmentScript } = await import( + "./scripts/env/create-environment" + ); + await createEnvironmentScript(); + process.exit(0); + } catch (error) { + console.error("Failed to create environment:", error); + process.exit(1); + } + } + ) + .command( + "explorer:start", + "Start the explorer UI", + (yarg) => + yarg + .option("port", { + alias: "p", + type: "number", + default: 5003, + describe: "port to run the explorer on", + }) + .option("indexer-url", { + type: "string", + describe: "GraphQL endpoint URL for the indexer", + }), + async (args) => { + try { + const { default: explorerStartScript } = await import( + "./scripts/explorer/start" + ); + await explorerStartScript(args.port, args["indexer-url"]); + process.exit(0); + } catch (error) { + console.error("Failed to start explorer:", error); + process.exit(1); + } + } + ) + .demandCommand( + 1, + "You must specify a command. Use --help to see available commands." + ) + .help("help") + .alias("help", "h") + .option("help", { describe: "Show help" }) .strict() .parse(); +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/bridge/deposit.ts b/packages/cli/src/scripts/bridge/deposit.ts new file mode 100644 index 000000000..0f222b80a --- /dev/null +++ b/packages/cli/src/scripts/bridge/deposit.ts @@ -0,0 +1,183 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ + +import { + BridgingModule, + MinaTransactionSender, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; +import { + AccountUpdate, + fetchAccount, + Field, + Mina, + PrivateKey, + Provable, + PublicKey, + UInt64, +} from "o1js"; +import { FungibleToken } from "mina-fungible-token"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeDepositArgs { + tokenId: string; + fromKey: string; + toKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeDepositArgs +) { + if (!bridgeArgs) { + throw new Error( + "Bridge deposit arguments required: tokenId, fromKey, toKey, amount" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const fromPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.fromKey] ?? bridgeArgs.fromKey + ); + const toPublicKey = PublicKey.fromBase58( + process.env[bridgeArgs.toKey] ?? bridgeArgs.toKey + ); + const amount = bridgeArgs.amount * 1e9; + const fee = 0.1 * 1e9; + + const isCustomToken = tokenId.toBigInt() !== 1n; + const tokenOwnerPrivateKey = isCustomToken + ? PrivateKey.fromBase58(getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY")) + : PrivateKey.random(); + const bridgeContractKey = isCustomToken + ? PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY") + ) + : PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") + ); + + Provable.log("Preparing to deposit", { + tokenId, + fromPrivateKey, + toPublicKey, + amount, + fee, + }); + + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.inMemoryDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + + const { settlement, dispatch } = settlementModule.getContracts(); + + await fetchAccount({ publicKey: fromPrivateKey.toPublicKey() }); + await fetchAccount({ publicKey: settlement.address }); + await fetchAccount({ publicKey: dispatch.address }); + const bridgeAddress = await bridgingModule.getBridgeAddress(tokenId); + await fetchAccount({ publicKey: bridgeAddress!, tokenId: tokenId }); + await fetchAccount({ publicKey: bridgeAddress!, tokenId: tokenId }); + + const attestation = + await bridgingModule.getDepositContractAttestation(tokenId); + + console.log("Forging transaction..."); + const tx = await Mina.transaction( + { + memo: "User deposit", + sender: fromPrivateKey.toPublicKey(), + fee, + }, + async () => { + const au = AccountUpdate.createSigned( + fromPrivateKey.toPublicKey(), + tokenId + ); + au.balance.subInPlace(UInt64.from(amount)); + + await dispatch.deposit( + UInt64.from(amount), + tokenId, + bridgeContractKey.toPublicKey(), + attestation, + toPublicKey + ); + + if (isCustomToken) { + await new FungibleToken( + tokenOwnerPrivateKey.toPublicKey() + )!.approveAccountUpdates([au, dispatch.self]); + } + } + ); + console.log(tx.toPretty()); + + settlementModule.signTransaction( + tx, + [fromPrivateKey], + [tokenOwnerPrivateKey], + [dispatch.address] + ); + + console.log("Sending..."); + console.log(tx.toPretty()); + + const { hash } = await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log(`Deposit transaction included in a block: ${hash}`); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/bridge/redeem.ts b/packages/cli/src/scripts/bridge/redeem.ts new file mode 100644 index 000000000..35bc126d5 --- /dev/null +++ b/packages/cli/src/scripts/bridge/redeem.ts @@ -0,0 +1,151 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { + BridgingModule, + MinaTransactionSender, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + AccountUpdate, + fetchAccount, + Field, + Mina, + PrivateKey, + Provable, + UInt64, +} from "o1js"; +import { FungibleToken } from "mina-fungible-token"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeRedeemArgs { + tokenId: string; + toKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeRedeemArgs +) { + if (!bridgeArgs) { + throw new Error("Bridge redeem arguments required: tokenId, toKey, amount"); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const toPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.toKey] ?? bridgeArgs.toKey + ); + const amount = bridgeArgs.amount * 1e9; + const fee = 0.1 * 1e9; + + const isCustomToken = tokenId.toBigInt() !== 1n; + const tokenOwnerPrivateKey = isCustomToken + ? PrivateKey.fromBase58(getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY")) + : PrivateKey.random(); + + Provable.log("Preparing to redeem", { + tokenId, + to: toPrivateKey.toPublicKey(), + amount, + fee, + }); + + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.inMemoryDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled); + + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + + const bridgeContract = await bridgingModule.getBridgeContract(tokenId); + + const customAcc = await fetchAccount({ + publicKey: toPrivateKey.toPublicKey(), + tokenId: bridgeContract.deriveTokenId(), + }); + + Provable.log("Custom account", customAcc.account?.balance); + + console.log("Forging transaction..."); + const tx = await Mina.transaction( + { + sender: toPrivateKey.toPublicKey(), + fee, + }, + async () => { + const au = AccountUpdate.createSigned( + toPrivateKey.toPublicKey(), + tokenId + ); + au.balance.addInPlace(UInt64.from(amount)); + + await bridgeContract.redeem(au); + + if (isCustomToken) { + await new FungibleToken( + tokenOwnerPrivateKey.toPublicKey() + )!.approveAccountUpdate(bridgeContract.self); + } + } + ); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + settlementModule.signTransaction(tx, [toPrivateKey], [tokenOwnerPrivateKey]); + + console.log("Sending..."); + + const { hash } = await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log(`Redeem transaction included in a block: ${hash}`); + console.log(tx.toPretty()); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/bridge/withdraw.ts b/packages/cli/src/scripts/bridge/withdraw.ts new file mode 100644 index 000000000..346576db2 --- /dev/null +++ b/packages/cli/src/scripts/bridge/withdraw.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { ClientAppChain, InMemorySigner } from "@proto-kit/sdk"; +import { Field, PrivateKey, Provable } from "o1js"; +import { UInt64 } from "@proto-kit/library"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; + +import { loadEnvironmentVariables, LoadEnvOptions } from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeWithdrawArgs { + tokenId: string; + senderKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeWithdrawArgs +) { + if (!bridgeArgs) { + throw new Error( + "Bridge withdraw arguments required: tokenId, senderKey, amount" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const amount = UInt64.from(bridgeArgs.amount * 1e9); + const appChain = ClientAppChain.fromRemoteEndpoint( + Runtime.from(runtime.modules), + Protocol.from({ ...protocol.modules, ...protocol.settlementModules }), + InMemorySigner + ); + + appChain.configurePartial({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + GraphqlClient: { + url: process.env.NEXT_PUBLIC_PROTOKIT_GRAPHQL_URL, + }, + }); + + await appChain.start(); + + const senderPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.senderKey] ?? bridgeArgs.senderKey + ); + const senderPublicKey = senderPrivateKey.toPublicKey(); + const signer = appChain.resolve("Signer"); + signer.config.signer = senderPrivateKey; + + Provable.log("debug", { + senderPrivateKey, + senderPublicKey, + amount, + tokenId, + }); + + const withdrawals = appChain.runtime.resolve("Withdrawals"); + const tx = await appChain.transaction(senderPublicKey, async () => { + await withdrawals.withdraw(senderPublicKey, amount, tokenId); + }); + + await tx.sign(); + await tx.send(); + + console.log("withdrawal tx sent"); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/env/create-environment.ts b/packages/cli/src/scripts/env/create-environment.ts new file mode 100644 index 000000000..ea3c3d576 --- /dev/null +++ b/packages/cli/src/scripts/env/create-environment.ts @@ -0,0 +1,102 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import * as fs from "fs"; +import * as path from "path"; + +import { cyan, green, red, bold } from "kleur/colors"; + +import { + copyAndUpdateEnvFile, + generateChainConfig, + generateIndexerConfig, + generateProcessorConfig, + icons, + promptUser, +} from "../../utils/create-environment"; + +export default async function () { + try { + const answers = await promptUser(); + + const cwd = process.cwd(); + const envDir = path.join( + cwd, + "src/core/environments", + answers.environmentName + ); + + if (!fs.existsSync(envDir)) { + fs.mkdirSync(envDir, { recursive: true }); + } + + const chainConfigPath = path.join(envDir, "chain.config.ts"); + const indexerConfigPath = path.join(envDir, "indexer.config.ts"); + const processorConfigPath = path.join(envDir, "processor.config.ts"); + + if (fs.existsSync(chainConfigPath)) { + console.log(`\nEnvironment already exists at ${envDir}`); + return; + } + + copyAndUpdateEnvFile(answers, cwd, envDir); + const chainConfig = generateChainConfig(answers); + fs.writeFileSync(chainConfigPath, chainConfig); + + if (answers.includeIndexer) { + const indexerConfig = generateIndexerConfig(answers); + if (indexerConfig) { + fs.writeFileSync(indexerConfigPath, indexerConfig); + } + } + + if (answers.includeProcessor && answers.includeIndexer) { + const processorConfig = generateProcessorConfig(answers); + if (processorConfig) { + fs.writeFileSync(processorConfigPath, processorConfig); + } + } + + console.log( + `\n${bold(green(" ╔════════════════════════════════════════╗"))}` + ); + console.log( + `${bold(green(" ║ ✓ Environment Created Successfully ║"))}` + ); + console.log( + `${bold(green(" ╚════════════════════════════════════════╝"))}` + ); + console.log(""); + + console.log(`${bold("Location:")}`); + console.log(` ${cyan(envDir)}\n`); + + console.log(`${bold("Generated Files:")}`); + console.log(` ${green(icons.checkmark)} .env`); + console.log(` ${green(icons.checkmark)} chain.config.ts`); + if (answers.includeIndexer) { + console.log(` ${green(icons.checkmark)} indexer.config.ts`); + } + if (answers.includeProcessor && answers.includeIndexer) { + console.log(` ${green(icons.checkmark)} processor.config.ts`); + } + + console.log(`\n${bold("Next Steps:")}`); + const cdCommand = `cd ${path.relative(process.cwd(), envDir)}`; + console.log(` 1. ${cyan(cdCommand)}`); + console.log(` 2. Update environment variables in ${cyan(".env")}`); + console.log( + ` 3. Add the following script to your root ${cyan("package.json")}:` + ); + const scriptCommand = `"env:${answers.environmentName}": "dotenv -e ./packages/chain/src/core/environments/${answers.environmentName}/.env -- pnpm"`; + console.log(`${cyan(scriptCommand)}`); + console.log(" 4. Start your application\n"); + } catch (error) { + console.log(`\n${bold(red("✗ Error"))}`); + console.log(`${red("-".repeat(50))}`); + console.error(` ${error}`); + console.log(`${red("-".repeat(50))}\n`); + process.exit(1); + } +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/explorer/start.ts b/packages/cli/src/scripts/explorer/start.ts new file mode 100644 index 000000000..4ce12db6f --- /dev/null +++ b/packages/cli/src/scripts/explorer/start.ts @@ -0,0 +1,57 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { spawn } from "child_process"; +import path from "path"; +import { fileURLToPath } from "url"; + +export default async function (port?: number, indexerUrl?: string) { + let explorerDir: string; + + try { + const pkgUrl = await import.meta.resolve( + "@proto-kit/explorer/package.json" + ); + const pkgPath = fileURLToPath(pkgUrl); + explorerDir = path.dirname(pkgPath); + } catch (error) { + console.error("Failed to find @proto-kit/explorer package."); + throw error; + } + + return await new Promise((resolve, reject) => { + const child = spawn("npm", ["run", "dev", "--", "-p", String(port)], { + cwd: explorerDir, + stdio: "inherit", + env: { + ...process.env, + NODE_OPTIONS: "", + NEXT_PUBLIC_INDEXER_URL: indexerUrl, + }, + }); + + child.on("error", (error) => { + console.error("Failed to start explorer:", error); + reject(error); + }); + + child.on("exit", (code) => { + if (code !== null && code !== 0 && code !== 143) { + reject(new Error(`Explorer process exited with code ${code}`)); + } else { + resolve(); + } + }); + + process.on("SIGINT", () => { + child.kill(); + resolve(); + }); + + process.on("SIGTERM", () => { + child.kill(); + resolve(); + }); + }); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/generateKeys.ts b/packages/cli/src/scripts/generateKeys.ts new file mode 100644 index 000000000..d4d5fd663 --- /dev/null +++ b/packages/cli/src/scripts/generateKeys.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-console */ + +import { PrivateKey } from "o1js"; + +type GenerateKeysArgs = { + count?: number; +}; + +export async function generateKeysCommand(args: GenerateKeysArgs) { + const count = args.count ?? 1; + console.log(`Generated ${count} keys for development purposes:`); + console.log("-".repeat(70)); + for (let i = 0; i < count; i++) { + const privateKey = PrivateKey.random(); + const publicKey = privateKey.toPublicKey(); + console.log("Private key:", privateKey.toBase58()); + console.log("Public key:", publicKey.toBase58()); + console.log("-".repeat(70)); + } +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/commands/generateGqlDocs.ts b/packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts similarity index 94% rename from packages/cli/src/commands/generateGqlDocs.ts rename to packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts index f9fcedc17..2ab3c680d 100644 --- a/packages/cli/src/commands/generateGqlDocs.ts +++ b/packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { BlockStorageNetworkStateModule, InMemoryTransactionSender, @@ -21,7 +22,7 @@ import { } from "@proto-kit/api"; import { Runtime } from "@proto-kit/module"; -import { generateGqlDocs } from "../utils"; +import { generateGqlDocs } from "../../utils/graphqlDocs"; export async function generateGqlDocsCommand(args: { empty: boolean; @@ -76,3 +77,4 @@ export async function generateGqlDocsCommand(args: { await generateGqlDocs(args.url); } } +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/lightnet/faucet.ts b/packages/cli/src/scripts/lightnet/faucet.ts new file mode 100644 index 000000000..b3af98c63 --- /dev/null +++ b/packages/cli/src/scripts/lightnet/faucet.ts @@ -0,0 +1,79 @@ +/* eslint-disable func-names */ +import { + AccountUpdate, + fetchAccount, + Lightnet, + Mina, + Provable, + PublicKey, +} from "o1js"; + +import "reflect-metadata"; +import { getRequiredEnv } from "../../utils/loadEnv"; + +export default async function (publicKey: string) { + // configuration + const fee = 0.1 * 1e9; + const fundingAmount = 1000 * 1e9; + + const net = Mina.Network({ + mina: `${getRequiredEnv("MINA_NODE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_NODE_GRAPHQL_PORT")}/graphql`, + archive: `${getRequiredEnv("MINA_ARCHIVE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_ARCHIVE_GRAPHQL_PORT")}/graphql`, + lightnetAccountManager: `${getRequiredEnv("MINA_ACCOUNT_MANAGER_HOST")}:${getRequiredEnv("MINA_ACCOUNT_MANAGER_PORT")}`, + }); + + Mina.setActiveInstance(net); + + // get the source account from the account manager + const pair = await Lightnet.acquireKeyPair({ + isRegularAccount: true, + }); + + // which account to drip to + const keyArg = process.env[publicKey] ?? publicKey; + + if (keyArg?.length === 0) { + throw new Error("No key provided"); + } + + const key = PublicKey.fromBase58(keyArg); + + await fetchAccount({ publicKey: pair.publicKey }); + + Provable.log( + `Dripping ${fundingAmount / 1e9} MINA from ${pair.publicKey.toBase58()} to ${key.toBase58()}` + ); + + const tx = await Mina.transaction( + { + sender: pair.publicKey, + fee, + }, + async () => { + const account = await fetchAccount({ publicKey: key }); + // if the destination account does not exist yet, pay the creation fee for it + if (account.error) { + AccountUpdate.fundNewAccount(pair.publicKey); + } + + AccountUpdate.createSigned(pair.publicKey).balance.subInPlace( + fundingAmount + ); + AccountUpdate.create(key).balance.addInPlace(fundingAmount); + } + ); + + tx.sign([pair.privateKey]); + + const sentTx = await tx.send(); + await sentTx.wait(); + + Provable.log( + `Funded account ${key.toBase58()} with ${fundingAmount / 1e9} MINA` + ); + + await Lightnet.releaseKeyPair({ + publicKey: pair.publicKey.toBase58(), + }); +} +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/lightnet/wait-for-network.ts b/packages/cli/src/scripts/lightnet/wait-for-network.ts new file mode 100644 index 000000000..9f7ccbae6 --- /dev/null +++ b/packages/cli/src/scripts/lightnet/wait-for-network.ts @@ -0,0 +1,41 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { sleep } from "@proto-kit/common"; +import { fetchLastBlock, Provable } from "o1js"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; + +const maxAttempts = 24; +const delay = 5000; + +export default async function (options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + const graphqlEndpoint = `${getRequiredEnv("MINA_NODE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_NODE_GRAPHQL_PORT")}/graphql`; + let lastBlock; + let attempt = 0; + console.log("Waiting for network to be ready..."); + while (!lastBlock) { + attempt += 1; + if (attempt > maxAttempts) { + throw new Error( + `Network was still not ready after ${(delay / 1000) * (attempt - 1)}s` + ); + } + try { + // eslint-disable-next-line no-await-in-loop + lastBlock = await fetchLastBlock(graphqlEndpoint); + } catch (e) { + // continue + } + // eslint-disable-next-line no-await-in-loop + await sleep(delay); + } + + Provable.log("Network is ready", lastBlock); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/lightnetInitialize.ts b/packages/cli/src/scripts/lightnetInitialize.ts new file mode 100644 index 000000000..ab1f97d2f --- /dev/null +++ b/packages/cli/src/scripts/lightnetInitialize.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-console */ + +import { + LoadEnvOptions, + getRequiredEnv, + loadEnvironmentVariables, +} from "../utils/loadEnv"; + +import lightnetWaitForNetworkScript from "./lightnet/wait-for-network"; +import lightnetFaucetScript from "./lightnet/faucet"; +import settlementDeployScript from "./settlement/deploy"; + +export async function lightnetInitializeCommand(options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + + console.log("Step 1: Waiting for network to be ready..."); + await lightnetWaitForNetworkScript(options); + + console.log("Step 2: Funding PROTOKIT_SEQUENCER_PUBLIC_KEY from faucet..."); + await lightnetFaucetScript(getRequiredEnv("PROTOKIT_SEQUENCER_PUBLIC_KEY")); + + console.log("Step 3: Funding TEST_ACCOUNT_1_PUBLIC_KEY from faucet..."); + await lightnetFaucetScript(getRequiredEnv("TEST_ACCOUNT_1_PUBLIC_KEY")); + + console.log("Step 4: Deploying settlement contracts..."); + await settlementDeployScript(options); + + console.log( + "Lightnet initialization complete! Settlement contracts are deployed." + ); +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/settlement/deploy-token.ts b/packages/cli/src/scripts/settlement/deploy-token.ts new file mode 100644 index 000000000..28c707158 --- /dev/null +++ b/packages/cli/src/scripts/settlement/deploy-token.ts @@ -0,0 +1,248 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + ArchiveNode, + MinaTransactionSender, + ProvenSettlementPermissions, + Sequencer, + SettlementModule, + SignedSettlementPermissions, + AppChain, +} from "@proto-kit/sequencer"; +import { + AccountUpdate, + Bool, + fetchAccount, + Mina, + PrivateKey, + Provable, + PublicKey, + UInt64, + UInt8, +} from "o1js"; +import "reflect-metadata"; +import { container } from "tsyringe"; +import { FungibleToken, FungibleTokenAdmin } from "mina-fungible-token"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { loadEnvironmentVariables, LoadEnvOptions } from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface TokenDeployArgs { + tokenSymbol: string; + feepayerKey: string; + receiverPublicKey: string; + mintAmount: number; +} + +export default async function ( + options?: LoadEnvOptions, + tokenArgs?: TokenDeployArgs +) { + if (!tokenArgs) { + throw new Error( + "Token deployment arguments required: tokenSymbol, feepayerKey, receiverPublicKey, [mintAmount]" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.PrismaRedisDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.prismaRedisDatabase({ + preset: "development", + overrides: { + pruneOnStartup: false, + }, + }), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const chainContainer = container.createChildContainer(); + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled, chainContainer); + const { tokenSymbol } = tokenArgs; + const feepayerPrivateKey = PrivateKey.fromBase58( + process.env[tokenArgs.feepayerKey] ?? tokenArgs.feepayerKey + ); + const receiverPublicKey = PublicKey.fromBase58( + process.env[tokenArgs.receiverPublicKey] ?? tokenArgs.receiverPublicKey + ); + const mintAmount = tokenArgs.mintAmount * 1e9; + const fee = 0.1 * 1e9; + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + const isSignedSettlement = settlementModule.utils.isSignedSettlement(); + + const tokenOwnerKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + const tokenAdminKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + const tokenBridgeKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + + await ArchiveNode.waitOnSync(appChain.sequencer.resolve("BaseLayer").config); + + async function deployTokenContracts() { + const permissions = isSignedSettlement + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions(); + + const tx = await Mina.transaction( + { + sender: feepayerPrivateKey.toPublicKey(), + memo: "Deploy custom token", + fee, + }, + async () => { + AccountUpdate.fundNewAccount(feepayerPrivateKey.toPublicKey(), 3); + + const admin = new FungibleTokenAdmin(tokenAdminKey.toPublicKey()); + await admin.deploy({ + adminPublicKey: feepayerPrivateKey.toPublicKey(), + }); + admin.self.account.permissions.set(permissions.bridgeContractToken()); + + const fungibleToken = new FungibleToken(tokenOwnerKey.toPublicKey()); + await fungibleToken.deploy({ + src: "", + symbol: tokenSymbol, + allowUpdates: false, + }); + fungibleToken!.self.account.permissions.set( + permissions.bridgeContractToken() + ); + + await fungibleToken.initialize( + tokenAdminKey.toPublicKey(), + UInt8.from(9), + Bool(false) + ); + } + ); + console.log("Sending deploy transaction..."); + console.log(tx.toPretty()); + + settlementModule.signTransaction( + tx, + [feepayerPrivateKey, tokenOwnerKey, tokenAdminKey], + [tokenOwnerKey, tokenAdminKey] + ); + + await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log("Deploy transaction included"); + } + + async function mint() { + const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); + await settlementModule.utils.fetchContractAccounts( + { + address: tokenOwner!.address, + tokenId: tokenOwner!.tokenId, + }, + { + address: tokenOwner!.address, + tokenId: tokenOwner!.deriveTokenId(), + } + ); + + const tx = await Mina.transaction( + { + sender: feepayerPrivateKey.toPublicKey(), + memo: "Mint custom token", + fee, + }, + async () => { + AccountUpdate.fundNewAccount(feepayerPrivateKey.toPublicKey(), 1); + + await tokenOwner!.mint(receiverPublicKey, UInt64.from(mintAmount)); + } + ); + settlementModule.utils.signTransaction( + tx, + [feepayerPrivateKey], + [tokenOwnerKey, tokenAdminKey] + ); + + await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + } + + async function deployBridge() { + const { settlement, dispatch } = settlementModule.getAddresses(); + await fetchAccount({ + publicKey: settlementModule.config.feepayer.toPublicKey(), + }); + await fetchAccount({ publicKey: settlement }); + await fetchAccount({ publicKey: dispatch }); + + const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); + // SetAdminEvent. + await settlementModule.deployTokenBridge( + tokenOwner, + tokenOwnerKey, + tokenBridgeKey, + {} + ); + console.log( + `Token bridge address: ${tokenBridgeKey.toPublicKey().toBase58()} @ ${tokenOwner.deriveTokenId().toString()}` + ); + } + + await deployTokenContracts(); + await mint(); + await deployBridge(); + + console.log( + `Deployed custom token with id ${new FungibleToken(tokenOwnerKey.toPublicKey())!.deriveTokenId()}` + ); + + Provable.log("Deployed and initialized settlement contracts", { + settlement: PrivateKey.fromBase58( + process.env.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY! + ).toPublicKey(), + dispatcher: PrivateKey.fromBase58( + process.env.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY! + ).toPublicKey(), + }); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/settlement/deploy.ts b/packages/cli/src/scripts/settlement/deploy.ts new file mode 100644 index 000000000..9e0530fb0 --- /dev/null +++ b/packages/cli/src/scripts/settlement/deploy.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ + +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + InMemoryDatabase, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { PrivateKey, Provable } from "o1js"; +import "reflect-metadata"; +import { container } from "tsyringe"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export default async function (options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + Database: InMemoryDatabase, + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ preset: "development" }), + }, + }); + + const chainContainer = container.createChildContainer(); + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled, chainContainer); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + console.log("Deploying settlement contracts..."); + + await settlementModule.deploy( + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + ), + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + ), + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") + ) + ); + + Provable.log("Deployed and initialized settlement contracts", { + settlement: PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + ).toPublicKey(), + dispatcher: PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + ).toPublicKey(), + }); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/utils/create-environment.ts b/packages/cli/src/utils/create-environment.ts new file mode 100644 index 000000000..6f4b5baad --- /dev/null +++ b/packages/cli/src/utils/create-environment.ts @@ -0,0 +1,372 @@ +import * as fs from "fs"; +import * as path from "path"; + +import inquirer from "inquirer"; +import figuresLib from "@inquirer/figures"; +import { cyan, green, blue, gray, bold } from "kleur/colors"; + +/* eslint-disable no-console */ + +export const icons = { + checkmark: figuresLib.tick, + cross: figuresLib.cross, + arrow: figuresLib.pointerSmall, + circle: figuresLib.bullet, + square: figuresLib.square, +}; + +export type PresetType = "inmemory" | "development" | "sovereign"; + +export interface WizardAnswers { + environmentName: string; + preset: PresetType; + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; +} + +export const PRESET_ENV_NAMES: Record = { + inmemory: "inmemory", + development: "development", + sovereign: "sovereign", +}; + +export const PRESET_DESCRIPTIONS: Record = { + inmemory: "Fast testing and development environment", + development: "Local development environment", + sovereign: "Production-ready environment", +}; + +export const PRESET_LABELS: Record = { + inmemory: "In-Memory", + development: "Development", + sovereign: "Sovereign", +}; + +export function printHeader(): void { + console.log(bold(cyan(" ╔════════════════════════════════════════╗"))); + console.log(bold(cyan(" ║ 🚀 Proto-Kit Environment Wizard ║"))); + console.log(bold(cyan(" ╚════════════════════════════════════════╝"))); + console.log(""); +} + +export function printSection(title: string): void { + const section = `${icons.square} ${title}`; + console.log(`\n${bold(blue(section))}`); + console.log(gray("-".repeat(50))); + console.log(""); +} + +export async function selectPreset(): Promise { + const presetTypes: PresetType[] = ["inmemory", "development", "sovereign"]; + const answer = await inquirer.prompt<{ preset: PresetType }>([ + { + type: "list", + name: "preset", + message: "Select Environment Preset", + choices: presetTypes.map((type) => { + const label = PRESET_LABELS[type]; + const description = PRESET_DESCRIPTIONS[type]; + return { + name: `${label} - ${description}`, + value: type, + }; + }), + }, + ]); + + return answer.preset; +} + +export async function selectModules(preset: PresetType): Promise<{ + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; +}> { + const isInMemory = preset === "inmemory"; + + const answers = await inquirer.prompt<{ + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; + }>([ + { + type: "confirm", + name: "includeIndexer", + message: "Include Indexer Module?", + default: false, + when: !isInMemory, + }, + { + type: "confirm", + name: "includeProcessor", + message: "Include Processor Module? (requires Indexer)", + default: false, + when: (ans: WizardAnswers) => ans.includeIndexer === true, + }, + { + type: "confirm", + name: "includeMetrics", + message: "Include OpenTelemetry Metrics?", + default: false, + }, + { + type: "confirm", + name: "settlementEnabled", + message: "Enable Settlement Module?", + default: false, + }, + ]); + if (isInMemory && answers.includeIndexer === false) { + answers.includeIndexer = false; + } + if (answers.includeProcessor === false) { + answers.includeProcessor = false; + } + + return answers; +} + +export async function promptUser(): Promise { + printHeader(); + + printSection("Environment Configuration"); + + const answers = await inquirer.prompt<{ environmentName: string }>([ + { + type: "input", + name: "environmentName", + message: "Environment name (e.g 'production')", + validate: (input: string) => { + if (!input.trim()) { + return "Environment name is required"; + } + return true; + }, + }, + ]); + + const environmentName = (answers.environmentName ?? "").trim(); + const confirmationMessage = `${icons.checkmark} Environment: ${environmentName}`; + console.log(`${green(confirmationMessage)}\n`); + + const preset = await selectPreset(); + + printSection("Configure Modules"); + const modules = await selectModules(preset); + + return { + environmentName, + preset, + ...modules, + }; +} + +export function generateChainConfig(answers: WizardAnswers): string { + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + const isInMemory = answers.preset === "inmemory"; + + const moduleParts: string[] = []; + + if (answers.includeMetrics) { + moduleParts.push(" ...DefaultModules.metrics(),"); + } + if (isInMemory) { + moduleParts.push(" ...DefaultModules.inMemoryDatabase(),"); + } else { + moduleParts.push(" ...DefaultModules.PrismaRedisDatabase(),"); + } + moduleParts.push( + ` ...DefaultModules.core({ settlementEnabled: ${answers.settlementEnabled} }),` + ); + if (isInMemory) { + moduleParts.push(" ...DefaultModules.localTaskQueue(),"); + } else { + moduleParts.push(" ...DefaultModules.RedisTaskQueue(),"); + } + if (answers.includeIndexer) { + moduleParts.push(" ...DefaultModules.sequencerIndexer(),"); + } + if (answers.settlementEnabled) { + moduleParts.push(" ...DefaultModules.settlement(),"); + } + const modulesString = moduleParts.join("\n"); + const configParts: string[] = []; + const coreConfig = ` ...DefaultConfigs.core({ settlementEnabled: ${answers.settlementEnabled}, preset: "${presetEnv}" }),`; + configParts.push(coreConfig); + if (answers.includeIndexer) { + configParts.push(" ...DefaultConfigs.sequencerIndexer(),"); + } + if (answers.includeMetrics) { + configParts.push( + ` ...DefaultConfigs.metrics({ preset: "${presetEnv}" }),` + ); + } + if (isInMemory) { + configParts.push(" ...DefaultConfigs.localTaskQueue(),"); + configParts.push(" ...DefaultConfigs.inMemoryDatabase(),"); + } else { + configParts.push( + ` ...DefaultConfigs.redisTaskQueue({ + preset: "${presetEnv}", + }),` + ); + configParts.push( + ` ...DefaultConfigs.prismaRedisDatabase({ + preset: "${presetEnv}", + }),` + ); + } + if (answers.settlementEnabled) { + configParts.push( + ` ...DefaultConfigs.settlement({ preset: "${presetEnv}" }),` + ); + } + const configString = configParts.join("\n"); + return `import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { AppChain, Sequencer } from "@proto-kit/sequencer"; +import runtime from "../../../runtime"; +import * as protocol from "../../../protocol"; + +import { Arguments } from "../../../start"; +import { Startable } from "@proto-kit/common"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +const settlementEnabled = process.env.PROTOKIT_SETTLEMENT_ENABLED! === "true"; + +const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...(settlementEnabled ? protocol.settlementModules : {}), + }), + Sequencer: Sequencer.from({ + // ordering of the modules matters due to dependency resolution +${modulesString} + }), + ...DefaultModules.appChainBase(), +}); + +export default async (args: Arguments): Promise => { + appChain.configurePartial({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...(settlementEnabled ? protocol.settlementModulesConfig : {}), + }, + Sequencer: { +${configString} + }, + ...DefaultConfigs.appChainBase(), + }); + + return appChain; +};`; +} + +export function generateIndexerConfig(answers: WizardAnswers): string { + if (!answers.includeIndexer) { + return ""; + } + + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + + return `import { Indexer } from "@proto-kit/indexer"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +const indexer = Indexer.from({ + ...DefaultModules.indexer(), +}); + +export default async (): Promise => { + indexer.configurePartial({ + ...DefaultConfigs.indexer({ + preset: "${presetEnv}", + }), + }); + + return indexer; +};`; +} + +export function generateProcessorConfig(answers: WizardAnswers): string { + if (!answers.includeProcessor || !answers.includeIndexer) { + return ""; + } + + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + + return `import { Processor } from "@proto-kit/processor"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { handlers } from "../../processor/handlers"; +import { resolvers } from "../../processor/api/resolvers"; + +const processor = Processor.from({ + ...DefaultModules.processor(resolvers, handlers), +}); + +export default async (): Promise => { + processor.configurePartial({ + ...DefaultConfigs.processor({ + preset: "${presetEnv}", + }), + }); + + return processor; +};`; +} + +export function copyAndUpdateEnvFile( + answers: WizardAnswers, + cwd: string, + envDir: string +): boolean { + const presetEnvPath = path.join( + cwd, + "src/core/environments", + answers.preset, + ".env" + ); + + if (!fs.existsSync(presetEnvPath)) { + console.warn(`Could not find .env file at ${presetEnvPath}`); + return false; + } + + try { + let envContent = fs.readFileSync(presetEnvPath, "utf-8"); + + if (envContent.includes("PROTOKIT_ENV_FOLDER=")) { + envContent = envContent.replace( + /PROTOKIT_ENV_FOLDER=.*/g, + `PROTOKIT_ENV_FOLDER=${answers.environmentName}` + ); + } else { + const envFolder = `PROTOKIT_ENV_FOLDER=${answers.environmentName}`; + envContent = `${envFolder}\n${envContent}`; + } + + if (envContent.includes("PROTOKIT_SETTLEMENT_ENABLED=")) { + envContent = envContent.replace( + /PROTOKIT_SETTLEMENT_ENABLED=.*/g, + `PROTOKIT_SETTLEMENT_ENABLED=${answers.settlementEnabled}` + ); + } else { + envContent += `\nPROTOKIT_SETTLEMENT_ENABLED=${answers.settlementEnabled}\n`; + } + + const envFilePath = path.join(envDir, ".env"); + fs.writeFileSync(envFilePath, envContent); + + return true; + } catch (error) { + console.error(`Error copying .env file: ${error}}`); + return false; + } +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils/graphqlDocs.ts similarity index 98% rename from packages/cli/src/utils.ts rename to packages/cli/src/utils/graphqlDocs.ts index fca7a9956..cd896cd13 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils/graphqlDocs.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { spawn } from "child_process"; import fs from "fs"; import path from "path"; @@ -110,3 +112,4 @@ export async function generateGqlDocs(gqlUrl: string) { console.log("Docs generated successfully!"); cleanUp(generatedPath); } +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils/loadEnv.ts b/packages/cli/src/utils/loadEnv.ts new file mode 100644 index 000000000..3601c6cf7 --- /dev/null +++ b/packages/cli/src/utils/loadEnv.ts @@ -0,0 +1,65 @@ +/* eslint-disable no-console */ + +import path from "path"; +import fs from "fs"; + +import dotenv from "dotenv"; + +export type LoadEnvOptions = { + envPath?: string; + envVars?: Record; +}; + +export function loadEnvironmentVariables(options?: LoadEnvOptions) { + const cwd = process.cwd(); + + if (options?.envPath !== undefined) { + if (fs.existsSync(options.envPath)) { + dotenv.config({ path: options.envPath }); + console.log(`Loaded environment from ${options.envPath}`); + } else { + throw new Error(`Environment file not found at ${options.envPath}`); + } + } else { + const envPath = path.join(cwd, "./src/core/environments/development/.env"); + + if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); + console.log(`Loaded environment from ${envPath}`); + } else { + console.warn(`.env file not found at ${envPath}`); + } + } + if (options?.envVars !== undefined) { + Object.entries(options.envVars).forEach(([key, value]) => { + process.env[key] = value; + }); + console.log( + `Loaded ${Object.keys(options.envVars).length} environment variables from arguments` + ); + } +} + +export function getRequiredEnv(key: string): string { + const value = process.env[key]; + if (value === undefined) { + throw new Error( + `Required environment variable "${key}" is not defined. Please check your .env file or pass it as an argument.` + ); + } + return value; +} + +export function parseEnvArgs(args: string[]): Record { + const envVars: Record = {}; + + for (const arg of args) { + if (arg.includes("=")) { + const [key, value] = arg.split("=", 2); + envVars[key.trim()] = value.trim(); + } + } + + return envVars; +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils/loadUserModules.ts b/packages/cli/src/utils/loadUserModules.ts new file mode 100644 index 000000000..3ac32d5f1 --- /dev/null +++ b/packages/cli/src/utils/loadUserModules.ts @@ -0,0 +1,60 @@ +import path from "path"; + +import { + MandatoryProtocolModulesRecord, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; +import { RuntimeModulesRecord } from "@proto-kit/module"; +import { ModulesConfig } from "@proto-kit/common"; +import { Withdrawals } from "@proto-kit/library"; + +/* eslint-disable no-console */ + +type AppRuntimeModules = RuntimeModulesRecord & { + Withdrawals: typeof Withdrawals; +}; + +interface RuntimeModule { + modules: AppRuntimeModules; + config: ModulesConfig; +} + +interface ProtocolModule { + modules: ProtocolModulesRecord & MandatoryProtocolModulesRecord; + + config: ModulesConfig; + + settlementModules?: ProtocolModulesRecord; + + settlementModulesConfig?: ModulesConfig; +} + +interface LoadedModules { + runtime: RuntimeModule; + protocol: ProtocolModule; +} + +export async function loadUserModules(): Promise { + const cwd = process.cwd(); + + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const runtimeImport: { default: RuntimeModule } = await import( + path.join(cwd, "src/runtime") + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const protocolImport: { default: ProtocolModule } = await import( + path.join(cwd, "src/protocol") + ); + + return { + runtime: runtimeImport.default, + protocol: protocolImport.default, + }; + } catch (error) { + console.error("Failed to load runtime or protocol modules."); + throw error; + } +} + +/* eslint-enable no-console */ diff --git a/packages/explorer/src/config.ts b/packages/explorer/src/config.ts index 97529e1ab..47c6a2c6f 100644 --- a/packages/explorer/src/config.ts +++ b/packages/explorer/src/config.ts @@ -1,5 +1,6 @@ const config = { - INDEXER_URL: "http://localhost:8081/graphql", + INDEXER_URL: + process.env.NEXT_PUBLIC_INDEXER_URL ?? "http://localhost:8081/graphql", }; export default config; diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index 9c61fbdaf..bdb606004 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -25,6 +25,9 @@ import { IndexerNotifier, GeneratedResolverFactoryGraphqlModule, IndexBlockTask, + IndexBatchTask, + IndexPendingTxTask, + IndexSettlementTask, } from "@proto-kit/indexer"; import { PrismaRedisDatabase } from "@proto-kit/persistance"; import { BullQueue } from "@proto-kit/deployment"; @@ -115,6 +118,9 @@ export class DefaultModules { TaskQueue: BullQueue, TaskWorker: LocalTaskWorkerModule.from({ IndexBlockTask, + IndexPendingTxTask, + IndexBatchTask, + IndexSettlementTask, }), GraphqlServer, Graphql: GraphqlSequencerModule.from({ @@ -311,6 +317,9 @@ export class DefaultConfigs { ...taskQueueConfig, TaskWorker: { IndexBlockTask: {}, + IndexBatchTask: {}, + IndexPendingTxTask: {}, + IndexSettlementTask: {}, }, ...graphqlServerConfig, Graphql: { From c3e481baae652f01bb20633b8754a2f8f467e8a5 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 30 Jan 2026 16:52:55 +0300 Subject: [PATCH 153/155] feat: expose isSginedSettlement to SettlemntUtils --- .../sequencer/src/settlement/utils/SettlementUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 9639a2fe5..9c9f2bba8 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -109,6 +109,14 @@ export class SettlementUtils { return this.signer.registerKey(privateKey); } + public getSigner(): PublicKey { + return this.signer.getFeepayerKey(); + } + + public isSignedSettlement(): boolean { + return this.baseLayer.isSignedSettlement(); + } + /** * Fetch a set of accounts (and there update internally) with respect to what network is set */ From 709adcb8aa824991ad63d8326ff83388284af14d Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 30 Jan 2026 16:53:12 +0300 Subject: [PATCH 154/155] refactor: update scripts and configs with new SettlementModule API --- packages/cli/src/scripts/bridge/deposit.ts | 17 ++++--- packages/cli/src/scripts/bridge/redeem.ts | 5 +- .../src/scripts/settlement/deploy-token.ts | 49 ++++++++++++------- packages/cli/src/scripts/settlement/deploy.ts | 29 +++++------ packages/stack/src/presets/modules/index.ts | 21 +++++--- 5 files changed, 70 insertions(+), 51 deletions(-) diff --git a/packages/cli/src/scripts/bridge/deposit.ts b/packages/cli/src/scripts/bridge/deposit.ts index 0f222b80a..628d2c5ca 100644 --- a/packages/cli/src/scripts/bridge/deposit.ts +++ b/packages/cli/src/scripts/bridge/deposit.ts @@ -9,7 +9,7 @@ import { AppChain, } from "@proto-kit/sequencer"; import { Runtime } from "@proto-kit/module"; -import { Protocol } from "@proto-kit/protocol"; +import { DispatchSmartContract, Protocol } from "@proto-kit/protocol"; import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; import { AccountUpdate, @@ -118,7 +118,9 @@ export default async function ( BridgingModule ); - const { settlement, dispatch } = settlementModule.getContracts(); + const settlement = settlementModule.getSettlementContract(); + const dispatch = + bridgingModule.getDispatchContract() as DispatchSmartContract; await fetchAccount({ publicKey: fromPrivateKey.toPublicKey() }); await fetchAccount({ publicKey: settlement.address }); @@ -161,12 +163,11 @@ export default async function ( ); console.log(tx.toPretty()); - settlementModule.signTransaction( - tx, - [fromPrivateKey], - [tokenOwnerPrivateKey], - [dispatch.address] - ); + settlementModule.utils.signTransaction(tx, { + signingPublicKeys: [fromPrivateKey.toPublicKey()], + preventNoncePreconditionFor: [dispatch.address], + signingWithSignatureCheck: [tokenOwnerPrivateKey.toPublicKey()], + }); console.log("Sending..."); console.log(tx.toPretty()); diff --git a/packages/cli/src/scripts/bridge/redeem.ts b/packages/cli/src/scripts/bridge/redeem.ts index 35bc126d5..7a3b2c039 100644 --- a/packages/cli/src/scripts/bridge/redeem.ts +++ b/packages/cli/src/scripts/bridge/redeem.ts @@ -134,7 +134,10 @@ export default async function ( SettlementModule ); - settlementModule.signTransaction(tx, [toPrivateKey], [tokenOwnerPrivateKey]); + settlementModule.utils.signTransaction(tx, { + signingPublicKeys: [toPrivateKey.toPublicKey()], + signingWithSignatureCheck: [tokenOwnerPrivateKey.toPublicKey()], + }); console.log("Sending..."); diff --git a/packages/cli/src/scripts/settlement/deploy-token.ts b/packages/cli/src/scripts/settlement/deploy-token.ts index 28c707158..12f1828aa 100644 --- a/packages/cli/src/scripts/settlement/deploy-token.ts +++ b/packages/cli/src/scripts/settlement/deploy-token.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ /* eslint-disable func-names */ import { Runtime } from "@proto-kit/module"; -import { Protocol } from "@proto-kit/protocol"; +import { DispatchSmartContract, Protocol } from "@proto-kit/protocol"; import { ArchiveNode, MinaTransactionSender, @@ -10,6 +10,7 @@ import { SettlementModule, SignedSettlementPermissions, AppChain, + BridgingModule, } from "@proto-kit/sequencer"; import { AccountUpdate, @@ -98,6 +99,11 @@ export default async function ( SettlementModule ); + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + const isSignedSettlement = settlementModule.utils.isSignedSettlement(); const tokenOwnerKey = PrivateKey.fromBase58( @@ -155,11 +161,13 @@ export default async function ( console.log("Sending deploy transaction..."); console.log(tx.toPretty()); - settlementModule.signTransaction( - tx, - [feepayerPrivateKey, tokenOwnerKey, tokenAdminKey], - [tokenOwnerKey, tokenAdminKey] - ); + settlementModule.utils.signTransaction(tx, { + signingWithSignatureCheck: [ + tokenOwnerKey.toPublicKey(), + tokenAdminKey.toPublicKey(), + ], + signingPublicKeys: [feepayerPrivateKey.toPublicKey()], + }); await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -193,11 +201,14 @@ export default async function ( await tokenOwner!.mint(receiverPublicKey, UInt64.from(mintAmount)); } ); - settlementModule.utils.signTransaction( - tx, - [feepayerPrivateKey], - [tokenOwnerKey, tokenAdminKey] - ); + + settlementModule.utils.signTransaction(tx, { + signingPublicKeys: [feepayerPrivateKey.toPublicKey()], + signingWithSignatureCheck: [ + tokenOwnerKey.toPublicKey(), + tokenAdminKey.toPublicKey(), + ], + }); await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -205,19 +216,21 @@ export default async function ( } async function deployBridge() { - const { settlement, dispatch } = settlementModule.getAddresses(); + const settlement = settlementModule.getSettlementContract(); + const dispatch = + bridgingModule.getDispatchContract() as DispatchSmartContract; + await fetchAccount({ - publicKey: settlementModule.config.feepayer.toPublicKey(), + publicKey: settlementModule.utils.getSigner(), }); - await fetchAccount({ publicKey: settlement }); - await fetchAccount({ publicKey: dispatch }); + await fetchAccount({ publicKey: settlement.address }); + await fetchAccount({ publicKey: dispatch.address }); const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); // SetAdminEvent. - await settlementModule.deployTokenBridge( + await bridgingModule.deployTokenBridge( tokenOwner, - tokenOwnerKey, - tokenBridgeKey, + tokenBridgeKey.toPublicKey(), {} ); console.log( diff --git a/packages/cli/src/scripts/settlement/deploy.ts b/packages/cli/src/scripts/settlement/deploy.ts index 9e0530fb0..c36f836fe 100644 --- a/packages/cli/src/scripts/settlement/deploy.ts +++ b/packages/cli/src/scripts/settlement/deploy.ts @@ -9,7 +9,7 @@ import { SettlementModule, AppChain, } from "@proto-kit/sequencer"; -import { PrivateKey, Provable } from "o1js"; +import { Provable, PublicKey } from "o1js"; import "reflect-metadata"; import { container } from "tsyringe"; import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; @@ -59,25 +59,22 @@ export default async function (options?: LoadEnvOptions) { console.log("Deploying settlement contracts..."); - await settlementModule.deploy( - PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + await settlementModule.deploy({ + settlementContract: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY") ), - PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + dispatchContract: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY") ), - PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") - ) - ); + }); Provable.log("Deployed and initialized settlement contracts", { - settlement: PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") - ).toPublicKey(), - dispatcher: PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") - ).toPublicKey(), + settlement: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY") + ), + dispatcher: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY") + ), }); await appChain.close(); diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index bdb606004..bf1d0a4d6 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -20,6 +20,7 @@ import { InMemoryDatabase, LocalTaskQueue, AppChainModulesRecord, + InMemoryMinaSigner, } from "@proto-kit/sequencer"; import { IndexerNotifier, @@ -100,6 +101,7 @@ export class DefaultModules { FeeStrategy: ConstantFeeStrategy, BatchProducerModule, SettlementModule, + SettlementSigner: InMemoryMinaSigner, LocalTaskWorkerModule: LocalTaskWorkerModule.from( VanillaTaskWorkerModules.allTasks() ), @@ -379,17 +381,20 @@ export class DefaultConfigs { }, }, SettlementModule: { - feepayer: PrivateKey.fromBase58(config.sequencerPrivateKey), - keys: { - settlement: PrivateKey.fromBase58( + addresses: { + SettlementContract: PrivateKey.fromBase58( config.settlementContractPrivateKey - ), - dispatch: PrivateKey.fromBase58(config.dispatcherContractPrivateKey), - minaBridge: PrivateKey.fromBase58( - config.minaBridgeContractPrivateKey - ), + ).toPublicKey(), }, }, + SettlementSigner: { + feepayer: PrivateKey.fromBase58(config.sequencerPrivateKey), + contractKeys: [ + PrivateKey.fromBase58(config.settlementContractPrivateKey), + PrivateKey.fromBase58(config.dispatcherContractPrivateKey), + PrivateKey.fromBase58(config.minaBridgeContractPrivateKey), + ], + }, FeeStrategy: {}, BatchProducerModule: {}, LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), From 1c9b74547e4cd16a89a16467b81c74097f23760a Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 30 Jan 2026 17:09:32 +0300 Subject: [PATCH 155/155] fix: run lint fix --- packages/cli/src/scripts/bridge/deposit.ts | 1 + packages/cli/src/scripts/settlement/deploy-token.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/cli/src/scripts/bridge/deposit.ts b/packages/cli/src/scripts/bridge/deposit.ts index 628d2c5ca..cfb4ad492 100644 --- a/packages/cli/src/scripts/bridge/deposit.ts +++ b/packages/cli/src/scripts/bridge/deposit.ts @@ -120,6 +120,7 @@ export default async function ( const settlement = settlementModule.getSettlementContract(); const dispatch = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions bridgingModule.getDispatchContract() as DispatchSmartContract; await fetchAccount({ publicKey: fromPrivateKey.toPublicKey() }); diff --git a/packages/cli/src/scripts/settlement/deploy-token.ts b/packages/cli/src/scripts/settlement/deploy-token.ts index 12f1828aa..96d06a92d 100644 --- a/packages/cli/src/scripts/settlement/deploy-token.ts +++ b/packages/cli/src/scripts/settlement/deploy-token.ts @@ -217,7 +217,9 @@ export default async function ( async function deployBridge() { const settlement = settlementModule.getSettlementContract(); + const dispatch = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions bridgingModule.getDispatchContract() as DispatchSmartContract; await fetchAccount({