Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"@proto-kit/protocol": "*",
"@proto-kit/sequencer": "*",
"o1js": "2.10.0-dev.6d3a3",
"@o1js/native": "2.10.0-dev.6d3a3",
"tsyringe": "^4.10.0"
},
"devDependencies": {
Expand Down
162 changes: 144 additions & 18 deletions packages/api/src/graphql/GraphqlSequencerModule.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import assert from "node:assert";

import { buildSchemaSync, NonEmptyArray } from "type-graphql";
import { Closeable, closeable, SequencerModule } from "@proto-kit/sequencer";
import {
ChildContainerProvider,
Configurable,
CombinedModuleContainerConfig,
log,
ModuleContainer,
ModulesRecord,
TypedClass,
} from "@proto-kit/common";
import { GraphQLSchema } from "graphql/type";
import { stitchSchemas } from "@graphql-tools/stitch";
import { createYoga } from "graphql-yoga";
import Koa from "koa";

import { GraphqlServer } from "./GraphqlServer";
import {
GraphqlModule,
ResolverFactoryGraphqlModule,
Expand All @@ -21,11 +24,50 @@ export type GraphqlModulesRecord = ModulesRecord<
TypedClass<GraphqlModule<unknown>>
>;

export interface GraphqlServerConfig {
host: string;
port: number;
graphiql: boolean;
}

export type GraphqlSequencerModuleConfig<
GraphQLModules extends GraphqlModulesRecord,
> = CombinedModuleContainerConfig<GraphQLModules, GraphqlServerConfig>;

type Server = ReturnType<Koa["listen"]>;

function assertArrayIsNotEmpty<T>(
array: readonly T[],
errorMessage: string
): asserts array is NonEmptyArray<T> {
if (array.length === 0) {
throw new Error(errorMessage);
}
}

@closeable()
export class GraphqlSequencerModule<GraphQLModules extends GraphqlModulesRecord>
extends ModuleContainer<GraphQLModules>
implements Configurable<unknown>, SequencerModule<unknown>, Closeable
extends ModuleContainer<GraphQLModules, GraphqlServerConfig>
implements
SequencerModule<
CombinedModuleContainerConfig<GraphQLModules, GraphqlServerConfig>
>,
Closeable
{
private readonly modules: TypedClass<GraphqlModule<unknown>>[] = [];

private readonly schemas: GraphQLSchema[] = [];

private resolvers: NonEmptyArray<Function> | undefined;

private server?: Server;

private context: {} = {};

public get serverConfig(): GraphqlServerConfig {
return this.containerConfig;
}

public static from<GraphQLModules extends GraphqlModulesRecord>(
definition: GraphQLModules
): TypedClass<GraphqlSequencerModule<GraphQLModules>> {
Expand All @@ -36,19 +78,28 @@ export class GraphqlSequencerModule<GraphQLModules extends GraphqlModulesRecord>
};
}

private graphqlServer?: GraphqlServer;
public constructor(definition: GraphQLModules) {
super(definition);
}

public setContext(newContext: {}) {
this.context = newContext;
}

public registerResolvers(resolvers: NonEmptyArray<Function>) {
if (this.resolvers === undefined) {
this.resolvers = resolvers;
} else {
this.resolvers = [...this.resolvers, ...resolvers];
}
}

public create(childContainerProvider: ChildContainerProvider) {
super.create(childContainerProvider);

this.graphqlServer = this.container.resolve("GraphqlServer");
this.container.register("GraphqlServer", { useValue: this });
}

public async start(): Promise<void> {
assert(this.graphqlServer !== undefined);

this.graphqlServer.setContainer(this.container);

// eslint-disable-next-line guard-for-in
for (const moduleName in this.definition) {
const moduleClass = this.definition[moduleName];
Expand All @@ -65,9 +116,9 @@ export class GraphqlSequencerModule<GraphQLModules extends GraphqlModulesRecord>
moduleName
) as ResolverFactoryGraphqlModule<unknown>;
// eslint-disable-next-line no-await-in-loop
this.graphqlServer.registerResolvers(await module.resolvers());
this.registerResolvers(await module.resolvers());
} else {
this.graphqlServer.registerModule(moduleClass);
this.modules.push(moduleClass);

if (
Object.prototype.isPrototypeOf.call(
Expand All @@ -80,16 +131,91 @@ export class GraphqlSequencerModule<GraphQLModules extends GraphqlModulesRecord>
const module = this.resolve(
moduleName
) as SchemaGeneratingGraphqlModule<unknown>;
this.graphqlServer.registerSchema(module.generateSchema());
this.schemas.push(module.generateSchema());
}
}
}
await this.graphqlServer.startServer();
await this.startServer();
}

// Server logic

private async startServer() {
const { modules, container: dependencyContainer } = this;

const resolvers = [...modules, ...(this.resolvers || [])];

assertArrayIsNotEmpty(
resolvers,
"At least one module has to be provided to GraphqlServer"
);

// Building schema
const resolverSchema = buildSchemaSync({
resolvers,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
container: { get: (cls) => dependencyContainer.resolve(cls) },
validate: {
enableDebugMessages: true,
},
});

// Instantiate all modules at startup
modules.forEach((module) => {
dependencyContainer.resolve(module);
});

const schema = [resolverSchema, ...this.schemas].reduce(
(schema1, schema2) =>
stitchSchemas({
subschemas: [{ schema: schema1 }, { schema: schema2 }],
})
);

const app = new Koa();

const { graphiql, port, host } = this.serverConfig;

const yoga = createYoga<Koa.ParameterizedContext>({
schema,
graphiql,
context: this.context,
});

// Bind GraphQL Yoga to `/graphql` endpoint
app.use(async (ctx) => {
// Second parameter adds Koa's context into GraphQL Context
const response = await yoga.handleNodeRequest(ctx.req, ctx);

// Set status code
ctx.status = response.status;

// Set headers
response.headers.forEach((value, key) => {
ctx.append(key, value);
});

// Converts ReadableStream to a NodeJS Stream
ctx.body = response.body;
});

this.server = app.listen({ port, host }, () => {
log.info(`GraphQL Server listening on ${host}:${port}`);
});
}

public async close() {
if (this.graphqlServer !== undefined) {
await this.graphqlServer.close();
if (this.server !== undefined) {
const { server } = this;

await new Promise<void>((res) => {
server.close((error) => {
if (error !== undefined) {
log.error(error);
}
res();
});
});
}
}
}
1 change: 0 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"@proto-kit/stack": "*",
"@proto-kit/indexer": "*",
"o1js": "2.10.0-dev.6d3a3",
"@o1js/native": "2.10.0-dev.6d3a3",
"tsyringe": "^4.10.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/lightnet/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface InitializeArgs {
export const initializeCommand: CommandModule<{}, InitializeArgs> = {
command: "initialize",
describe:
"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",
"Initialize lightnet: wait for network, fund accounts, and deploy settlement\n\nRequires: MINA_NODE_GRAPHQL, MINA_ARCHIVE_GRAPHQL, MINA_ACCOUNT_MANAGER_URL, PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY",
builder: (yarg) => addEnvironmentOptions(yarg),
handler: async (args) => {
try {
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/commands/lightnet/waitForNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ interface WaitForNetworkArgs {

export const waitForNetworkCommand: CommandModule<{}, WaitForNetworkArgs> = {
command: "wait",
describe:
"Wait for network to be ready\n\nRequires: MINA_NODE_GRAPHQL_HOST, MINA_NODE_GRAPHQL_PORT",
describe: "Wait for network to be ready\n\nRequires: MINA_NODE_GRAPHQL",
builder: (yarg) => addEnvironmentOptions(yarg),
handler: async (args) => {
try {
Expand Down
15 changes: 10 additions & 5 deletions packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default async function (args: {
VanillaProtocolModules,
VanillaRuntimeModules,
} = await import("@proto-kit/library");
const { GraphqlSequencerModule, GraphqlServer, VanillaGraphqlModules } =
const { GraphqlSequencerModule, VanillaGraphqlModules } =
await import("@proto-kit/api");
const { Runtime } = await import("@proto-kit/module");
const { port } = args;
Expand All @@ -30,7 +30,6 @@ export default async function (args: {
Protocol: Protocol.from(VanillaProtocolModules.with({})),
Sequencer: Sequencer.from(
InMemorySequencerModules.with({
GraphqlServer: GraphqlServer,
Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})),
})
),
Expand All @@ -45,16 +44,22 @@ export default async function (args: {
Sequencer: {
Database: {},
TaskQueue: {},
LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(),
WorkerModule: VanillaTaskWorkerModules.defaultConfig(),
Mempool: {},
BlockProducerModule: {},
SequencerStartupModule: {},
BlockTrigger: { blockInterval: 5000, produceEmptyBlocks: true },
FeeStrategy: {},
BaseLayer: {},
BatchProducerModule: {},
Graphql: VanillaGraphqlModules.defaultConfig(),
GraphqlServer: { port, host: "localhost", graphiql: true },
Graphql: {
...VanillaGraphqlModules.defaultConfig(),
containerConfig: {
port,
host: "localhost",
graphiql: true,
},
},
},
});

Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/scripts/lightnet/faucet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export default async function (publicKey: string) {
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: getRequiredEnv("MINA_NODE_GRAPHQL"),
archive: getRequiredEnv("MINA_ARCHIVE_GRAPHQL"),
lightnetAccountManager: getRequiredEnv("MINA_ACCOUNT_MANAGER_URL"),
});

Mina.setActiveInstance(net);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/scripts/lightnet/wait-for-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default async function (options: LoadEnvOptions) {
const { sleep } = await import("@proto-kit/common");
const { fetchLastBlock, Provable } = await import("o1js");
loadEnvironmentVariables(options);
const graphqlEndpoint = `${getRequiredEnv("MINA_NODE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_NODE_GRAPHQL_PORT")}/graphql`;
const graphqlEndpoint = getRequiredEnv("MINA_NODE_GRAPHQL");
let lastBlock;
let attempt = 0;
console.log("Waiting for network to be ready...");
Expand Down
10 changes: 4 additions & 6 deletions packages/cli/src/scripts/settlement/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default async function (options: LoadEnvOptions) {
MinaBaseLayer,
PrivateMempool,
LocalTaskQueue,
LocalTaskWorkerModule,
WorkerModule,
VanillaTaskWorkerModules,
SequencerStartupModule,
} = await import("@proto-kit/sequencer");
Expand All @@ -49,9 +49,7 @@ export default async function (options: LoadEnvOptions) {
BridgingModule,
Mempool: PrivateMempool,
TaskQueue: LocalTaskQueue,
LocalTaskWorker: LocalTaskWorkerModule.from(
VanillaTaskWorkerModules.allTasks()
),
WorkerModule: WorkerModule.from(VanillaTaskWorkerModules.allTasks()),
SequencerStartupModule,
}),
});
Expand All @@ -71,7 +69,7 @@ export default async function (options: LoadEnvOptions) {
type: process.env.MINA_NETWORK as any,
graphql: process.env.MINA_NODE_GRAPHQL!,
archive: process.env.MINA_ARCHIVE_GRAPHQL!,
accountManager: process.env.MINA_ACCOUNT_MANAGER!,
accountManager: process.env.MINA_ACCOUNT_MANAGER_URL!,
},
},
SettlementSigner: {
Expand Down Expand Up @@ -102,7 +100,7 @@ export default async function (options: LoadEnvOptions) {
TaskQueue: {
simulatedDuration: 0,
},
LocalTaskWorker: VanillaTaskWorkerModules.defaultConfig(),
WorkerModule: VanillaTaskWorkerModules.defaultConfig(),
Mempool: {},
},
});
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/utils/create-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,10 @@ export function generateWorkerConfig(answers: WizardAnswers): string {
const presetEnv = PRESET_ENV_NAMES[answers.preset];
const taskWorkerImports = answers.settlementEnabled
? ""
: " LocalTaskWorkerModule, VanillaTaskWorkerModules";
: " WorkerModule, VanillaTaskWorkerModules";
const withoutSettlementTask = answers.settlementEnabled
? ""
: `LocalTaskWorkerModule: LocalTaskWorkerModule.from(
: `WorkerModule: WorkerModule.from(
VanillaTaskWorkerModules.withoutSettlement()
),
`;
Expand Down
1 change: 0 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
},
"peerDependencies": {
"o1js": "2.10.0-dev.6d3a3",
"@o1js/native": "2.10.0-dev.6d3a3",
"tsyringe": "^4.10.0"
},
"devDependencies": {
Expand Down
Loading
Loading