diff --git a/packages/module/src/runtime/Runtime.ts b/packages/module/src/runtime/Runtime.ts index e38b8f59a..bae879cc8 100644 --- a/packages/module/src/runtime/Runtime.ts +++ b/packages/module/src/runtime/Runtime.ts @@ -311,9 +311,7 @@ export class Runtime } public get stateServiceProvider(): StateServiceProvider { - return this.dependencyContainer.resolve( - "StateServiceProvider" - ); + return this.container.resolve("StateServiceProvider"); } public get stateService(): SimpleAsyncStateService { diff --git a/packages/module/test/modules/Balances.test.ts b/packages/module/test/modules/Balances.test.ts index 6fbf0eb9d..d61f6a3d9 100644 --- a/packages/module/test/modules/Balances.test.ts +++ b/packages/module/test/modules/Balances.test.ts @@ -9,6 +9,7 @@ import { RuntimeMethodExecutionContext, RuntimeTransaction, NetworkState, + PROTOKIT_PREFIXES, } from "@proto-kit/protocol"; import { Runtime } from "../../src"; @@ -132,7 +133,11 @@ describe("balances", () => { it("should have a state transition for the correct path", () => { expect.assertions(1); - const path = Path.fromProperty("Balances", "totalSupply"); + const path = Path.fromProperty( + "Balances", + "totalSupply", + PROTOKIT_PREFIXES.STATE_RUNTIME + ); expect(stateTransitions[0].path.toString()).toStrictEqual( path.toString() @@ -192,7 +197,11 @@ describe("balances", () => { it("should have a state transition for the correct path", () => { expect.assertions(1); - const path = Path.fromProperty("Balances", "totalSupply"); + const path = Path.fromProperty( + "Balances", + "totalSupply", + PROTOKIT_PREFIXES.STATE_RUNTIME + ); expect(stateTransitions[0].path.toString()).toStrictEqual( path.toString() @@ -247,7 +256,11 @@ describe("balances", () => { it("should have a state transition for the correct path", () => { expect.assertions(1); - const path = Path.fromProperty("Balances", "totalSupply"); + const path = Path.fromProperty( + "Balances", + "totalSupply", + PROTOKIT_PREFIXES.STATE_RUNTIME + ); expect(stateTransitions[0].path.toString()).toStrictEqual( path.toString() @@ -313,7 +326,11 @@ describe("balances", () => { expect.assertions(1); const path = Path.fromKey( - Path.fromProperty("Balances", "balances"), + Path.fromProperty( + "Balances", + "balances", + PROTOKIT_PREFIXES.STATE_RUNTIME + ), PublicKey, address ); diff --git a/packages/module/test/modules/Balances.ts b/packages/module/test/modules/Balances.ts index 679165924..d97453954 100644 --- a/packages/module/test/modules/Balances.ts +++ b/packages/module/test/modules/Balances.ts @@ -1,8 +1,8 @@ import { PublicKey, UInt64 } from "o1js"; -import { State, StateMap } from "@proto-kit/protocol"; +import { State, StateMap, state } from "@proto-kit/protocol"; import { Presets } from "@proto-kit/common"; -import { RuntimeModule, runtimeMethod, runtimeModule, state } from "../../src"; +import { RuntimeModule, runtimeMethod, runtimeModule } from "../../src"; import { Admin } from "./Admin.js"; diff --git a/packages/protocol/src/state/protocol/ProtocolState.ts b/packages/protocol/src/state/protocol/ProtocolState.ts index 0e0c24cca..9ca034d16 100644 --- a/packages/protocol/src/state/protocol/ProtocolState.ts +++ b/packages/protocol/src/state/protocol/ProtocolState.ts @@ -27,41 +27,6 @@ export interface StatefulModule { }; } -export function createStateGetter( - target: TargetModule, - propertyKey: string, - valueReference: Reference | undefined>, - prefix: string, - debugInfo: { parentName: string; baseModuleNames: string } -) { - return () => { - const { value } = valueReference; - // Short-circuit this to return the state in case its already initialized - if (value !== undefined && value.path !== undefined) { - return value; - } - - if (target.name === undefined) { - throw errors.missingName(target.constructor.name); - } - - if (!target.parent) { - throw errors.missingParent( - target.constructor.name, - debugInfo.parentName, - debugInfo.baseModuleNames - ); - } - - const path = Path.fromProperty(target.name, propertyKey, prefix); - if (value) { - value.path = path; - value.stateServiceProvider = target.parent.stateServiceProvider; - } - return value; - }; -} - /** * Decorates a runtime module property as state, passing down some * underlying values to improve developer experience. @@ -71,31 +36,62 @@ export function state() { target: TargetTransitioningModule, propertyKey: string ) => { - const stateReference = createReference | undefined>( - undefined - ); - - const isProtocol = target instanceof TransitioningProtocolModule; - const statePrefix = isProtocol - ? PROTOKIT_PREFIXES.STATE_PROTOCOL - : PROTOKIT_PREFIXES.STATE_RUNTIME; - const debugInfo = isProtocol - ? { parentName: "protocol", baseModuleNames: "...Hook" } - : { parentName: "runtime", baseModuleNames: "RuntimeModule" }; - Object.defineProperty(target, propertyKey, { enumerable: true, - get: createStateGetter( - target, - propertyKey, - stateReference, - statePrefix, - debugInfo - ), + get: function get(this: TargetTransitioningModule) { + // The reason for why we store the state value in this weird way is that + // in the decorator on the prototype of the class. This means that if there + // are multiple instances of this class, any closure that this getter shares + // will be the same for all instances. + // Therefore, we need to somehow save the set instance on the instance itself + + // eslint-disable-next-line max-len + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions,@typescript-eslint/no-unsafe-assignment + const reference: Reference> | undefined = (this as any)[ + `protokit_state_cache_${propertyKey}` + ]; + + // Short-circuit this to return the state in case its already initialized + if (reference !== undefined && reference.value.path !== undefined) { + return reference.value; + } + + if (this.name === undefined) { + throw errors.missingName(this.constructor.name); + } + + const isProtocol = target instanceof TransitioningProtocolModule; + + if (!this.parent) { + const debugInfo = isProtocol + ? { parentName: "protocol", baseModuleNames: "...Hook" } + : { parentName: "runtime", baseModuleNames: "RuntimeModule" }; + + throw errors.missingParent( + this.constructor.name, + debugInfo.parentName, + debugInfo.baseModuleNames + ); + } + + const statePrefix = isProtocol + ? PROTOKIT_PREFIXES.STATE_PROTOCOL + : PROTOKIT_PREFIXES.STATE_RUNTIME; + const path = Path.fromProperty(this.name, propertyKey, statePrefix); + if (reference) { + const { value } = reference; + value.path = path; + value.stateServiceProvider = this.parent.stateServiceProvider; + } + return reference?.value; + }, - set: (newValue: State) => { - stateReference.value = newValue; + set: function set( + this: TargetTransitioningModule & any, + newValue: State + ) { + this[`protokit_state_cache_${propertyKey}`] = createReference(newValue); }, }); }; diff --git a/packages/sdk/test/TestingAppChain.test.ts b/packages/sdk/test/TestingAppChain.test.ts index 9caf0b198..f07a6c21c 100644 --- a/packages/sdk/test/TestingAppChain.test.ts +++ b/packages/sdk/test/TestingAppChain.test.ts @@ -2,14 +2,9 @@ import "reflect-metadata"; import { randomUUID } from "crypto"; import { inject } from "tsyringe"; -import { - runtimeMethod, - RuntimeModule, - runtimeModule, - state, -} from "@proto-kit/module"; +import { runtimeMethod, RuntimeModule, runtimeModule } from "@proto-kit/module"; import { PrivateKey, Provable, PublicKey } from "o1js"; -import { assert, State } from "@proto-kit/protocol"; +import { assert, State, state } from "@proto-kit/protocol"; import { Balances, BalancesKey, TokenId, UInt64 } from "@proto-kit/library"; import { log } from "@proto-kit/common"; diff --git a/packages/sdk/test/XYK/XYK.ts b/packages/sdk/test/XYK/XYK.ts index 1ee48d76e..4c848edcf 100644 --- a/packages/sdk/test/XYK/XYK.ts +++ b/packages/sdk/test/XYK/XYK.ts @@ -1,11 +1,6 @@ import "reflect-metadata"; -import { - RuntimeModule, - runtimeMethod, - state, - runtimeModule, -} from "@proto-kit/module"; -import { StateMap, assert } from "@proto-kit/protocol"; +import { RuntimeModule, runtimeMethod, runtimeModule } from "@proto-kit/module"; +import { StateMap, assert, state } from "@proto-kit/protocol"; import { Field, Poseidon, PublicKey, Provable, Struct } from "o1js"; import { inject } from "tsyringe"; import { Balance, Balances, TokenId } from "@proto-kit/library"; diff --git a/packages/sdk/test/blockProof/TestBalances.ts b/packages/sdk/test/blockProof/TestBalances.ts index e502abf69..81f7faf4c 100644 --- a/packages/sdk/test/blockProof/TestBalances.ts +++ b/packages/sdk/test/blockProof/TestBalances.ts @@ -1,6 +1,6 @@ -import { runtimeModule, state, runtimeMethod } from "@proto-kit/module"; +import { runtimeModule, runtimeMethod } from "@proto-kit/module"; import { PublicKey } from "o1js"; -import { State } from "@proto-kit/protocol"; +import { State, state } from "@proto-kit/protocol"; import { Balance, Balances, TokenId, UInt64 } from "@proto-kit/library"; interface BalancesConfig { diff --git a/packages/sdk/test/fee-hook-sts-regression.test.ts b/packages/sdk/test/fee-hook-sts-regression.test.ts index cd27a0321..69a47d39d 100644 --- a/packages/sdk/test/fee-hook-sts-regression.test.ts +++ b/packages/sdk/test/fee-hook-sts-regression.test.ts @@ -2,14 +2,9 @@ import "reflect-metadata"; import { randomUUID } from "crypto"; import { inject } from "tsyringe"; -import { - runtimeMethod, - RuntimeModule, - runtimeModule, - state, -} from "@proto-kit/module"; +import { runtimeMethod, RuntimeModule, runtimeModule } from "@proto-kit/module"; import { PrivateKey, Provable, PublicKey } from "o1js"; -import { assert, State } from "@proto-kit/protocol"; +import { assert, State, state } from "@proto-kit/protocol"; import { Balances, BalancesKey, TokenId, UInt64 } from "@proto-kit/library"; import { log, expectDefined, sleep } from "@proto-kit/common"; diff --git a/packages/sdk/test/networkstate/Balance.ts b/packages/sdk/test/networkstate/Balance.ts index 1e1977d47..9bc285295 100644 --- a/packages/sdk/test/networkstate/Balance.ts +++ b/packages/sdk/test/networkstate/Balance.ts @@ -5,11 +5,11 @@ import { TokenId, UInt64, } from "@proto-kit/library"; -import { runtimeMethod, runtimeModule, state } from "@proto-kit/module"; +import { runtimeMethod, runtimeModule } from "@proto-kit/module"; import { log, Presets, range, mapSequential } from "@proto-kit/common"; import { Bool, Field, PublicKey } from "o1js"; import { Admin } from "@proto-kit/module/test/modules/Admin"; -import { State, assert } from "@proto-kit/protocol"; +import { State, assert, state } from "@proto-kit/protocol"; @runtimeModule() export class BalanceChild extends Balances { diff --git a/packages/sdk/test/parameters.test.ts b/packages/sdk/test/parameters.test.ts index 0b7c4c198..02693ed1c 100644 --- a/packages/sdk/test/parameters.test.ts +++ b/packages/sdk/test/parameters.test.ts @@ -11,13 +11,8 @@ import { PublicKey, ZkProgram, } from "o1js"; -import { - runtimeMethod, - RuntimeModule, - runtimeModule, - state, -} from "@proto-kit/module"; -import { assert, State, StateMap } from "@proto-kit/protocol"; +import { runtimeMethod, RuntimeModule, runtimeModule } from "@proto-kit/module"; +import { assert, State, StateMap, state } from "@proto-kit/protocol"; import { expectDefined } from "@proto-kit/common"; import { TestingAppChain } from "../src/index"; diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 3637619c2..462da091d 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -22,6 +22,10 @@ export * from "./worker/worker/WorkerReadyModule"; export * from "./protocol/baselayer/BaseLayer"; export * from "./protocol/baselayer/MinaBaseLayer"; export * from "./protocol/baselayer/NoopBaseLayer"; +export * from "./protocol/baselayer/network-utils/MinaNetworkUtils"; +export * from "./protocol/baselayer/network-utils/RemoteNetworkUtils"; +export * from "./protocol/baselayer/network-utils/LightnetUtils"; +export * from "./protocol/baselayer/network-utils/LocalBlockchainUtils"; export * from "./protocol/production/helpers/UntypedOption"; export * from "./protocol/production/helpers/UntypedStateTransition"; export * from "./protocol/production/tasks/TransactionProvingTask"; diff --git a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts index 8d60cbbd7..9b99b6a08 100644 --- a/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts +++ b/packages/sequencer/src/protocol/baselayer/MinaBaseLayer.ts @@ -10,25 +10,38 @@ import { } from "../../sequencer/builder/SequencerModule"; import { MinaTransactionSender } from "../../settlement/transactions/MinaTransactionSender"; import { WithdrawalQueue } from "../../settlement/messages/WithdrawalQueue"; +import { + Sequencer, + SequencerModulesRecord, +} from "../../sequencer/executor/Sequencer"; import { BaseLayer } from "./BaseLayer"; +import { LocalBlockchainUtils } from "./network-utils/LocalBlockchainUtils"; +import { LightnetUtils } from "./network-utils/LightnetUtils"; +import { RemoteNetworkUtils } from "./network-utils/RemoteNetworkUtils"; + +export type LocalMinaBaseLayerConfig = { + type: "local"; +}; + +export type LightnetMinaBaseLayerConfig = { + type: "lightnet"; + graphql: string; + archive: string; + accountManager?: string; +}; + +export type RemoteMinaBaseLayerConfig = { + type: "remote"; + graphql: string; + archive: string; +}; export interface MinaBaseLayerConfig { network: - | { - type: "local"; - } - | { - type: "lightnet"; - graphql: string; - archive: string; - accountManager?: string; - } - | { - type: "remote"; - graphql: string; - archive: string; - }; + | LocalMinaBaseLayerConfig + | LightnetMinaBaseLayerConfig + | RemoteMinaBaseLayerConfig; } @sequencerModule() @@ -42,12 +55,20 @@ export class MinaBaseLayer public constructor( @inject("AreProofsEnabled") - private readonly areProofsEnabled: AreProofsEnabled + private readonly areProofsEnabled: AreProofsEnabled, + @inject("Sequencer") + private readonly sequencer: Sequencer ) { super(); } public dependencies() { + const NetworkUtilsClass = match(this.config.network.type) + .with("local", () => LocalBlockchainUtils) + .with("lightnet", () => LightnetUtils) + .with("remote", () => RemoteNetworkUtils) + .exhaustive(); + return { IncomingMessageAdapter: { useClass: MinaIncomingMessageAdapter, @@ -60,9 +81,20 @@ export class MinaBaseLayer OutgoingMessageQueue: { useClass: WithdrawalQueue, }, + + NetworkUtils: { + useClass: NetworkUtilsClass, + }, }; } + public get networkUtils() { + if (this.config.network.type === "remote") { + throw new Error("NetworkUtils not available for remote networks"); + } + return this.sequencer.dependencyContainer.resolve("NetworkUtils"); + } + public isLocalBlockChain(): boolean { return this.config.network.type === "local"; } diff --git a/packages/sequencer/src/protocol/baselayer/accounts/MinaBlockchainAccounts.ts b/packages/sequencer/src/protocol/baselayer/accounts/MinaBlockchainAccounts.ts deleted file mode 100644 index afd603737..000000000 --- a/packages/sequencer/src/protocol/baselayer/accounts/MinaBlockchainAccounts.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { injectable, inject } from "tsyringe"; -import { range } from "@proto-kit/common"; -import { PrivateKey, Mina, Lightnet, PublicKey, AccountUpdate } from "o1js"; - -import { MinaTransactionSender } from "../../../settlement/transactions/MinaTransactionSender"; -import { BaseLayer } from "../BaseLayer"; -import { MinaBaseLayer } from "../MinaBaseLayer"; -import { FeeStrategy } from "../fees/FeeStrategy"; - -type LocalBlockchain = Awaited>; - -@injectable() -export class MinaBlockchainAccounts { - public constructor( - @inject("BaseLayer") - private readonly baseLayer: BaseLayer, - @inject("TransactionSender") - private readonly transactionSender: MinaTransactionSender, - @inject("FeeStrategy") - private readonly feeStrategy: FeeStrategy - ) {} - - private keysRetrieved = 0; - - private isMinaBaseLayer( - baseLayer: BaseLayer | undefined - ): baseLayer is MinaBaseLayer { - return baseLayer !== undefined && baseLayer instanceof MinaBaseLayer; - } - - public async getFundedAccounts(num: number = 1): Promise { - const { baseLayer } = this; - if (!this.isMinaBaseLayer(baseLayer)) { - throw new Error("Baselayer not defined or not subclass of MinaBaseLayer"); - } - if (baseLayer.config.network.type === "local") { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const local = baseLayer.network! as LocalBlockchain; - const accounts = local.testAccounts.slice( - this.keysRetrieved, - this.keysRetrieved + num - ); - this.keysRetrieved += num; - return accounts.map((acc) => acc.key); - } - if (baseLayer.config.network.type === "lightnet") { - return await Promise.all( - range(num).map(async (i) => { - const pair = await Lightnet.acquireKeyPair({ - isRegularAccount: true, - }); - return pair.privateKey; - }) - ); - } - throw new Error("Can't acquire keys for remote non-lighnet network"); - } - - public async fundAccountFrom( - sender: PrivateKey, - receiver: PublicKey, - amount: number - ) { - const { baseLayer } = this; - if (!this.isMinaBaseLayer(baseLayer)) { - throw new Error("Baselayer not defined or not subclass of MinaBaseLayer"); - } - if (baseLayer.config.network.type === "local") { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - (baseLayer.network as LocalBlockchain).addAccount( - receiver, - amount.toString() - ); - } else { - const tx = await Mina.transaction( - { sender: sender.toPublicKey(), fee: this.feeStrategy.getFee() }, - async () => { - AccountUpdate.fundNewAccount(sender.toPublicKey()); - AccountUpdate.createSigned(sender.toPublicKey()).send({ - to: receiver, - amount, - }); - } - ); - await this.transactionSender.proveAndSendTransaction( - tx.sign([sender]), - "included" - ); - } - } -} diff --git a/packages/sequencer/src/protocol/baselayer/network-utils/LightnetUtils.ts b/packages/sequencer/src/protocol/baselayer/network-utils/LightnetUtils.ts new file mode 100644 index 000000000..3e3c704b6 --- /dev/null +++ b/packages/sequencer/src/protocol/baselayer/network-utils/LightnetUtils.ts @@ -0,0 +1,149 @@ +import { inject, injectable } from "tsyringe"; +import { + AccountUpdate, + fetchAccount, + fetchLastBlock, + Lightnet, + Mina, + PrivateKey, + PublicKey, +} from "o1js"; +import { log, noop, range, sleep } from "@proto-kit/common"; + +import type { + LightnetMinaBaseLayerConfig, + MinaBaseLayer, + MinaBaseLayerConfig, +} from "../MinaBaseLayer"; +import { MinaTransactionSender } from "../../../settlement/transactions/MinaTransactionSender"; + +import { MinaNetworkUtils } from "./MinaNetworkUtils"; + +@injectable() +export class LightnetUtils implements MinaNetworkUtils { + public constructor( + @inject("BaseLayer") + private readonly baseLayer: MinaBaseLayer, + @inject("TransactionSender") + private readonly transactionSender: MinaTransactionSender + ) {} + + public async waitForNetwork(): Promise { + const maxAttempts = 24; + const delay = 5000; + + let lastBlock; + let attempt = 0; + + const { config } = this.baseLayer; + this.assertConfigLightnet(config); + const graphqlEndpoint = config.network.graphql; + + log.info("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) { + // Ignore errors + noop(); + } + // eslint-disable-next-line no-await-in-loop + await sleep(delay); + } + + log.provable.info("Network is ready", lastBlock); + } + + private assertConfigLightnet( + config: MinaBaseLayerConfig + ): asserts config is { network: LightnetMinaBaseLayerConfig } { + if (config.network.type !== "lightnet") { + throw new Error("Config provided is not of type 'lightnet'"); + } + } + + public async faucet( + receiver: PublicKey, + fundingAmount = 1000 * 1e9, + fee = 0.1 * 1e9 + ) { + const [faucetDonor] = await this.getFundedAccounts(1); + + const account = await fetchAccount({ publicKey: receiver }); + + log.provable.info( + `Dripping ${fundingAmount / 1e9} MINA from ${faucetDonor.toBase58()} to ${receiver.toBase58()}` + ); + + const faucetDonorPublicKey = faucetDonor.toPublicKey(); + + const tx = await Mina.transaction( + { + sender: faucetDonorPublicKey, + fee, + }, + async () => { + // if the destination account does not exist yet, pay the creation fee for it + if (account.error) { + AccountUpdate.fundNewAccount(faucetDonorPublicKey); + } + + AccountUpdate.createSigned(faucetDonorPublicKey).balance.subInPlace( + fundingAmount + ); + AccountUpdate.create(receiver).balance.addInPlace(fundingAmount); + } + ); + + tx.sign([faucetDonor]); + + await this.transactionSender.proveAndSendTransaction(tx, "included"); + + log.provable.info( + `Funded account ${receiver.toBase58()} with ${fundingAmount / 1e9} MINA` + ); + + await Lightnet.releaseKeyPair({ + publicKey: faucetDonor.toPublicKey().toBase58(), + lightnetAccountManagerEndpoint: this.getAccountManagerEndpoint(), + }); + } + + private getAccountManagerEndpoint() { + const { + baseLayer: { config }, + } = this; + + this.assertConfigLightnet(config); + + if (config.network.accountManager === undefined) { + throw new Error( + "Wanted to retrieve funded keypairs, but accountManager endpoint is missing in config" + ); + } + + return config.network.accountManager; + } + + public async getFundedAccounts(num: number = 1): Promise { + const lightnetAccountManagerEndpoint = this.getAccountManagerEndpoint(); + + return await Promise.all( + range(num).map(async (i) => { + const pair = await Lightnet.acquireKeyPair({ + isRegularAccount: true, + lightnetAccountManagerEndpoint, + }); + return pair.privateKey; + }) + ); + } +} diff --git a/packages/sequencer/src/protocol/baselayer/network-utils/LocalBlockchainUtils.ts b/packages/sequencer/src/protocol/baselayer/network-utils/LocalBlockchainUtils.ts new file mode 100644 index 000000000..9433d7b89 --- /dev/null +++ b/packages/sequencer/src/protocol/baselayer/network-utils/LocalBlockchainUtils.ts @@ -0,0 +1,100 @@ +import { inject, injectable } from "tsyringe"; +import { AccountUpdate, Mina, PrivateKey, PublicKey } from "o1js"; +import { log, noop } from "@proto-kit/common"; + +import { MinaTransactionSender } from "../../../settlement/transactions/MinaTransactionSender"; +import type { + LocalMinaBaseLayerConfig, + MinaBaseLayer, + MinaBaseLayerConfig, +} from "../MinaBaseLayer"; + +import { MinaNetworkUtils } from "./MinaNetworkUtils"; + +type LocalBlockchain = Awaited>; + +@injectable() +export class LocalBlockchainUtils implements MinaNetworkUtils { + public constructor( + @inject("BaseLayer") private readonly baseLayer: MinaBaseLayer, + @inject("TransactionSender") + private readonly transactionSender: MinaTransactionSender + ) {} + + private keysRetrieved = 0; + + private faucetDonor?: PrivateKey = undefined; + + private assertConfigLocal( + config: MinaBaseLayerConfig + ): asserts config is { network: LocalMinaBaseLayerConfig } { + if (config.network.type !== "local") { + throw new Error("Config provided is not of type 'local'"); + } + } + + public async getFundedAccounts(num: number = 1): Promise { + this.assertConfigLocal(this.baseLayer.config); + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const local = this.baseLayer.network! as LocalBlockchain; + const accounts = local.testAccounts.slice( + this.keysRetrieved, + this.keysRetrieved + num + ); + this.keysRetrieved += num; + return accounts.map((acc) => acc.key); + } + + public async faucet( + receiver: PublicKey, + fundingAmount = 1000 * 1e9, + fee = 0.1 * 1e9 + ) { + this.assertConfigLocal(this.baseLayer.config); + + let { faucetDonor } = this; + if (faucetDonor === undefined) { + [faucetDonor] = await this.getFundedAccounts(1); + this.faucetDonor = faucetDonor; + } + + const accountExists = Mina.hasAccount(receiver); + + const faucetDonorPublicKey = faucetDonor.toPublicKey(); + + log.provable.info( + `Dripping ${fundingAmount / 1e9} MINA from ${faucetDonor.toBase58()} to ${receiver.toBase58()}` + ); + + const tx = await Mina.transaction( + { + sender: faucetDonorPublicKey, + fee, + }, + async () => { + // if the destination account does not exist yet, pay the creation fee for it + if (!accountExists) { + AccountUpdate.fundNewAccount(faucetDonorPublicKey); + } + + AccountUpdate.createSigned(faucetDonorPublicKey).balance.subInPlace( + fundingAmount + ); + AccountUpdate.create(receiver).balance.addInPlace(fundingAmount); + } + ); + + tx.sign([faucetDonor]); + + await this.transactionSender.proveAndSendTransaction(tx, "included"); + + log.provable.info( + `Funded account ${receiver.toBase58()} with ${fundingAmount / 1e9} MINA` + ); + } + + public async waitForNetwork() { + noop(); + } +} diff --git a/packages/sequencer/src/protocol/baselayer/network-utils/MinaNetworkUtils.ts b/packages/sequencer/src/protocol/baselayer/network-utils/MinaNetworkUtils.ts new file mode 100644 index 000000000..70f5298ff --- /dev/null +++ b/packages/sequencer/src/protocol/baselayer/network-utils/MinaNetworkUtils.ts @@ -0,0 +1,14 @@ +import { PrivateKey, PublicKey } from "o1js"; + +export interface MinaNetworkUtils { + getFundedAccounts(num?: number): Promise; + + faucet( + receiver: PublicKey, + fundingAmount?: number, + // TODO Should we use the FeeStrategy here? + fee?: number + ): Promise; + + waitForNetwork(): Promise; +} diff --git a/packages/sequencer/src/protocol/baselayer/network-utils/RemoteNetworkUtils.ts b/packages/sequencer/src/protocol/baselayer/network-utils/RemoteNetworkUtils.ts new file mode 100644 index 000000000..b400f2a44 --- /dev/null +++ b/packages/sequencer/src/protocol/baselayer/network-utils/RemoteNetworkUtils.ts @@ -0,0 +1,26 @@ +import { injectable } from "tsyringe"; +import { PrivateKey, PublicKey } from "o1js"; +import { noop } from "@proto-kit/common"; + +import { MinaNetworkUtils } from "./MinaNetworkUtils"; + +@injectable() +export class RemoteNetworkUtils implements MinaNetworkUtils { + public async waitForNetwork(): Promise { + noop(); + } + + public async getFundedAccounts( + num?: number | undefined + ): Promise { + throw new Error("Method not implemented."); + } + + public async faucet( + receiver: PublicKey, + fundingAmount?: number | undefined, + fee?: number | undefined + ): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/packages/sequencer/src/sequencer/executor/Sequencer.ts b/packages/sequencer/src/sequencer/executor/Sequencer.ts index b17f75d92..bcd1b7ef7 100644 --- a/packages/sequencer/src/sequencer/executor/Sequencer.ts +++ b/packages/sequencer/src/sequencer/executor/Sequencer.ts @@ -120,6 +120,8 @@ export class Sequencer // eslint-disable-next-line no-await-in-loop await sequencerModule.start(); } + + // TODO This currently also warns for client appchains if (!moduleClassNames.includes("SequencerStartupModule")) { log.warn("SequencerStartupModule is not defined."); } diff --git a/packages/sequencer/src/settlement/transactions/MinaTransactionSimulator.ts b/packages/sequencer/src/settlement/transactions/MinaTransactionSimulator.ts index 00fdee20f..785259715 100644 --- a/packages/sequencer/src/settlement/transactions/MinaTransactionSimulator.ts +++ b/packages/sequencer/src/settlement/transactions/MinaTransactionSimulator.ts @@ -13,7 +13,7 @@ import { } from "o1js"; import { ACTIONS_EMPTY_HASH, - MINA_EVENT_PREFIXES, + MINA_PREFIXES, ReturnType, } from "@proto-kit/protocol"; import { match } from "ts-pattern"; @@ -301,10 +301,10 @@ export class MinaTransactionSimulator { // populate the full array with the current value const previousActionState = account.zkapp.actionState.at(0) ?? ACTIONS_EMPTY_HASH; - const newActionsHash = hashWithPrefix( - MINA_EVENT_PREFIXES.sequenceEvents, - [previousActionState, actions.hash] - ); + const newActionsHash = hashWithPrefix(MINA_PREFIXES.sequenceEvents, [ + previousActionState, + actions.hash, + ]); account.zkapp.actionState = range(0, 5).map(() => newActionsHash); } } diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 3dea83e6e..644427239 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -18,6 +18,7 @@ import { MandatoryProtocolModulesRecord, Path, Protocol, + PROTOKIT_PREFIXES, } from "@proto-kit/protocol"; import { AppChain } from "@proto-kit/sdk"; import { Bool, Field, PrivateKey, PublicKey, Struct, UInt64 } from "o1js"; @@ -595,7 +596,11 @@ describe("block production", () => { expect(batch!.blockHashes).toHaveLength(1); expect(batch!.proof.proof).toBe(MOCK_PROOF); - const supplyPath = Path.fromProperty("Balance", "totalSupply"); + const supplyPath = Path.fromProperty( + "Balance", + "totalSupply", + PROTOKIT_PREFIXES.STATE_RUNTIME + ); const newState = await test.getState(supplyPath, "block"); expect(newState).toBeDefined(); diff --git a/packages/sequencer/test/integration/mocks/Balance.ts b/packages/sequencer/test/integration/mocks/Balance.ts index 1fe0c945b..7f66cf63f 100644 --- a/packages/sequencer/test/integration/mocks/Balance.ts +++ b/packages/sequencer/test/integration/mocks/Balance.ts @@ -3,12 +3,18 @@ import { runtimeMethod, runtimeModule, RuntimeModule, - state, } from "@proto-kit/module"; import { log, Presets, range, mapSequential } from "@proto-kit/common"; import { Bool, Field, PublicKey, UInt64 } from "o1js"; import { Admin } from "@proto-kit/module/test/modules/Admin"; -import { Option, State, StateMap, assert, Deposit } from "@proto-kit/protocol"; +import { + Option, + State, + StateMap, + assert, + Deposit, + state, +} from "@proto-kit/protocol"; @runtimeModule() export class Balance extends RuntimeModule { diff --git a/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts b/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts index b47b625bb..6ae5db6bf 100644 --- a/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts +++ b/packages/sequencer/test/integration/mocks/ProtocolStateTestHook.ts @@ -1,9 +1,9 @@ import { AfterTransactionHookArguments, BeforeTransactionHookArguments, - protocolState, ProvableTransactionHook, StateMap, + state, } from "@proto-kit/protocol"; import { Field } from "o1js"; import { noop } from "@proto-kit/common"; @@ -12,7 +12,7 @@ import { noop } from "@proto-kit/common"; * A hook used to test protocolstate inside the blockproduction tests */ export class ProtocolStateTestHook extends ProvableTransactionHook { - @protocolState() methodIdInvocations = StateMap.from(Field, Field); + @state() methodIdInvocations = StateMap.from(Field, Field); public async beforeTransaction( executionData: BeforeTransactionHookArguments diff --git a/packages/sequencer/test/integration/mocks/ProvenBalance.ts b/packages/sequencer/test/integration/mocks/ProvenBalance.ts index f579993ff..97d981a48 100644 --- a/packages/sequencer/test/integration/mocks/ProvenBalance.ts +++ b/packages/sequencer/test/integration/mocks/ProvenBalance.ts @@ -3,12 +3,11 @@ import { runtimeMethod, runtimeModule, RuntimeModule, - state, } from "@proto-kit/module"; import { log, Presets } from "@proto-kit/common"; import { PublicKey, UInt64 } from "o1js"; import { Admin } from "@proto-kit/module/test/modules/Admin"; -import { Deposit, State, StateMap } from "@proto-kit/protocol"; +import { Deposit, State, StateMap, state } from "@proto-kit/protocol"; @runtimeModule() export class ProvenBalance extends RuntimeModule { diff --git a/packages/sequencer/test/merkle/MerkleTree.test.ts b/packages/sequencer/test/merkle/MerkleTree.test.ts index 27eed6179..5825ec730 100644 --- a/packages/sequencer/test/merkle/MerkleTree.test.ts +++ b/packages/sequencer/test/merkle/MerkleTree.test.ts @@ -1,3 +1,4 @@ +import "reflect-metadata"; import { Field } from "o1js"; import { RollupMerkleTree, log } from "@proto-kit/common"; diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 2b1fba2e6..17a3966a0 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -3,7 +3,6 @@ import { mapSequential, TypedClass, RollupMerkleTree, - sleep, } from "@proto-kit/common"; import { VanillaProtocolModules } from "@proto-kit/library"; import { Runtime } from "@proto-kit/module"; @@ -59,12 +58,12 @@ import { import { BlockProofSerializer } from "../../src/protocol/production/tasks/serializers/BlockProofSerializer"; import { testingSequencerFromModules } from "../TestingSequencer"; import { createTransaction } from "../integration/utils"; -import { MinaBlockchainAccounts } from "../../src/protocol/baselayer/accounts/MinaBlockchainAccounts"; 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"; import { Balances, BalancesKey } from "./mocks/Balances"; import { Withdrawals } from "./mocks/Withdrawals"; @@ -277,22 +276,20 @@ export const settlementTestFn = ( blockSerializer = appChain.sequencer.dependencyContainer.resolve(BlockProofSerializer); - const accountService = appChain.sequencer.dependencyContainer.resolve( - MinaBlockchainAccounts - ); - const accs = await accountService.getFundedAccounts(3); + 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 sleep(100); - await accountService.fundAccountFrom( - accs[0], - sequencerKey.toPublicKey(), - 20 * 1e9 - ); + await networkUtils.faucet(sequencerKey.toPublicKey(), 20 * 1e9); }, timeout * 3); afterAll(async () => { diff --git a/packages/sequencer/test/settlement/mocks/Balances.ts b/packages/sequencer/test/settlement/mocks/Balances.ts index e2778f800..983e2b6a6 100644 --- a/packages/sequencer/test/settlement/mocks/Balances.ts +++ b/packages/sequencer/test/settlement/mocks/Balances.ts @@ -2,11 +2,10 @@ import { NoConfig } from "@proto-kit/common"; import { RuntimeModule, runtimeMethod, - state, runtimeModule, runtimeMessage, } from "@proto-kit/module"; -import { StateMap, assert, Deposit } from "@proto-kit/protocol"; +import { StateMap, assert, Deposit, state } from "@proto-kit/protocol"; import { Field, PublicKey, Struct, Provable, UInt64 } from "o1js"; export const errors = { diff --git a/packages/sequencer/test/settlement/mocks/Withdrawals.ts b/packages/sequencer/test/settlement/mocks/Withdrawals.ts index cbe42d7bd..cc2290f55 100644 --- a/packages/sequencer/test/settlement/mocks/Withdrawals.ts +++ b/packages/sequencer/test/settlement/mocks/Withdrawals.ts @@ -3,9 +3,8 @@ import { runtimeMethod, runtimeModule, RuntimeModule, - state, } from "@proto-kit/module"; -import { StateMap, Withdrawal } from "@proto-kit/protocol"; +import { StateMap, Withdrawal, state } from "@proto-kit/protocol"; import { Field, PublicKey, Struct, UInt64 } from "o1js"; import { inject } from "tsyringe"; diff --git a/packages/stack/test/graphql/Post.ts b/packages/stack/test/graphql/Post.ts index 868afd695..80857b94f 100644 --- a/packages/stack/test/graphql/Post.ts +++ b/packages/stack/test/graphql/Post.ts @@ -1,10 +1,5 @@ -import { - RuntimeModule, - runtimeMethod, - runtimeModule, - state, -} from "@proto-kit/module"; -import { StateMap } from "@proto-kit/protocol"; +import { RuntimeModule, runtimeMethod, runtimeModule } from "@proto-kit/module"; +import { StateMap, state } from "@proto-kit/protocol"; import { CircuitString, Field, diff --git a/packages/stack/test/graphql/graphql.test.ts b/packages/stack/test/graphql/graphql.test.ts index b79fcd0fb..d5d5c4ae1 100644 --- a/packages/stack/test/graphql/graphql.test.ts +++ b/packages/stack/test/graphql/graphql.test.ts @@ -20,6 +20,7 @@ import { GraphqlNetworkStateTransportModule, } from "@proto-kit/sdk"; import { beforeAll } from "@jest/globals"; +import { container } from "tsyringe"; import { startServer, TestBalances } from "../../src/scripts/graphql/server"; @@ -103,7 +104,7 @@ describe("graphql client test", () => { appChain = prepareClient(); - await appChain.start(); + await appChain.start(false, container.createChildContainer()); trigger = server.sequencer.resolveOrFail( "BlockTrigger",