Skip to content

Commit

Permalink
Replace Action | Session | Snapshot with a shared MessageType (#442)
Browse files Browse the repository at this point in the history
* add MessageType

* replace action session snapshot with messagetype

* more replacement

* more replacements
  • Loading branch information
rjwebb authored Feb 12, 2025
1 parent da06e3f commit 33ff05f
Show file tree
Hide file tree
Showing 14 changed files with 83 additions and 60 deletions.
21 changes: 15 additions & 6 deletions packages/chain-ethereum/src/eip712/Secp256k1DelegateSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ import {

import { AbiCoder } from "ethers/abi"

import type { Action, Message, Session, Snapshot, Signature, SignatureScheme, Signer } from "@canvas-js/interfaces"
import type {
Action,
Message,
Session,
Snapshot,
Signature,
SignatureScheme,
Signer,
MessageType,
} from "@canvas-js/interfaces"
import { decodeURI, encodeURI } from "@canvas-js/signatures"
import { assert, prepareMessage, signalInvalidType } from "@canvas-js/utils"

Expand All @@ -28,7 +37,7 @@ export const codecs = {
* - canvas-action-eip712
* - canvas-session-eip712
*/
export class Secp256k1DelegateSigner implements Signer<Action | Session<Eip712SessionData> | Snapshot> {
export class Secp256k1DelegateSigner implements Signer<MessageType<Eip712SessionData>> {
public static eip712ActionTypes = {
Message: [
{ name: "topic", type: "string" },
Expand Down Expand Up @@ -63,7 +72,7 @@ export class Secp256k1DelegateSigner implements Signer<Action | Session<Eip712Se
AuthorizationData: [{ name: "signature", type: "bytes" }],
} satisfies Record<string, TypedDataField[]>

public readonly scheme: SignatureScheme<Action | Session<Eip712SessionData> | Snapshot> = Secp256k1SignatureScheme
public readonly scheme: SignatureScheme<MessageType<Eip712SessionData>> = Secp256k1SignatureScheme
public readonly publicKey: string

readonly #wallet: BaseWallet
Expand All @@ -80,7 +89,7 @@ export class Secp256k1DelegateSigner implements Signer<Action | Session<Eip712Se
this.publicKey = encodeURI(Secp256k1SignatureScheme.type, publicKey)
}

public async sign(message: Message<Action | Session<Eip712SessionData> | Snapshot>): Promise<Signature> {
public async sign(message: Message<MessageType<Eip712SessionData>>): Promise<Signature> {
const { topic, clock, parents, payload } = prepareMessage(message)

if (payload.type === "action") {
Expand Down Expand Up @@ -182,10 +191,10 @@ function getAbiTypeForValue(value: any) {
throw new TypeError(`invalid type ${typeof value}: ${JSON.stringify(value)}`)
}

export const Secp256k1SignatureScheme: SignatureScheme<Action | Session<Eip712SessionData> | Snapshot> = {
export const Secp256k1SignatureScheme: SignatureScheme<MessageType<Eip712SessionData>> = {
type: "secp256k1",
codecs: [codecs.action, codecs.session],
verify(signature: Signature, message: Message<Action | Session<Eip712SessionData> | Snapshot>) {
verify(signature: Signature, message: Message<MessageType<Eip712SessionData>>) {
const { type, publicKey } = decodeURI(signature.publicKey)
assert(type === Secp256k1SignatureScheme.type)

Expand Down
12 changes: 10 additions & 2 deletions packages/chain-ethereum/src/siwf/SIWFSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { verifyMessage, hexlify, getBytes } from "ethers"
import * as siwe from "siwe"
import * as json from "@ipld/dag-json"

import type { Action, Session, Snapshot, AbstractSessionData, DidIdentifier, Signer } from "@canvas-js/interfaces"
import type {
Action,
Session,
Snapshot,
AbstractSessionData,
DidIdentifier,
Signer,
MessageType,
} from "@canvas-js/interfaces"
import { AbstractSessionSigner, ed25519 } from "@canvas-js/signatures"
import { assert, DAYS } from "@canvas-js/utils"

Expand Down Expand Up @@ -75,7 +83,7 @@ export class SIWFSigner extends AbstractSessionSigner<SIWFSessionData> {
authorizationData: SIWFSessionData,
timestamp: number,
privateKey: Uint8Array,
): Promise<{ payload: Session<SIWFSessionData>; signer: Signer<Action | Session<SIWFSessionData> | Snapshot> }> {
): Promise<{ payload: Session<SIWFSessionData>; signer: Signer<MessageType<SIWFSessionData>> }> {
const signer = this.scheme.create({ type: ed25519.type, privateKey })
const did = await this.getDid()

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Argv } from "yargs"
import chalk from "chalk"
import * as json from "@ipld/dag-json"

import type { Action, Message, Session, Signature, Snapshot } from "@canvas-js/interfaces"
import type { Action, Message, MessageType, Session, Signature, Snapshot } from "@canvas-js/interfaces"
import { Canvas } from "@canvas-js/core"

import { getContractLocation } from "../utils.js"
Expand Down Expand Up @@ -52,7 +52,7 @@ export async function handler(args: Args) {
const { id, signature, message } = json.parse<{
id: string
signature: Signature
message: Message<Action | Session | Snapshot>
message: Message<MessageType>
}>(line)

try {
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as json from "@ipld/dag-json"
import { StatusCodes } from "http-status-codes"

import type { Action, Message, Session, SessionSigner, Signer } from "@canvas-js/interfaces"
import type { Action, Message, MessageType, Session, SessionSigner, Signer } from "@canvas-js/interfaces"
import { assert } from "@canvas-js/utils"

export class Client {
Expand Down Expand Up @@ -60,7 +60,7 @@ export class Client {
})
}

private async insert(delegateSigner: Signer<Action | Session>, message: Message<Action | Session>): Promise<string> {
private async insert(delegateSigner: Signer<MessageType>, message: Message<MessageType>): Promise<string> {
const signature = await delegateSigner.sign(message)

const res = await fetch(`${this.host}/api/messages`, {
Expand Down
31 changes: 20 additions & 11 deletions packages/core/src/Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ import { logger } from "@libp2p/logger"
import type pg from "pg"
import type { SqlStorage } from "@cloudflare/workers-types"

import { Signature, Action, Session, Message, Snapshot, SessionSigner, SignerCache } from "@canvas-js/interfaces"
import {
Signature,
Action,
Session,
Message,
Snapshot,
SessionSigner,
SignerCache,
MessageType,
} from "@canvas-js/interfaces"
import { AbstractModelDB, Model, ModelSchema, Effect } from "@canvas-js/modeldb"
import { SIWESigner } from "@canvas-js/chain-ethereum"
import { AbstractGossipLog, GossipLogEvents, SignedMessage } from "@canvas-js/gossiplog"
Expand Down Expand Up @@ -49,14 +58,14 @@ export type ActionResult<Result = any> = { id: string; signature: Signature; mes

export type ActionAPI<Args extends Array<any> = any, Result = any> = (...args: Args) => Promise<ActionResult<Result>>

export interface CanvasEvents extends GossipLogEvents<Action | Session | Snapshot> {
export interface CanvasEvents extends GossipLogEvents<MessageType> {
stop: Event
}

export type CanvasLogEvent = CustomEvent<{
id: string
signature: unknown
message: Message<Action | Session | Snapshot>
message: Message<MessageType>
}>

export type ApplicationData = {
Expand Down Expand Up @@ -88,7 +97,7 @@ export class Canvas<

const signers = new SignerCache(initSigners.length === 0 ? [new SIWESigner()] : initSigners)

const verifySignature = (signature: Signature, message: Message<Action | Session | Snapshot>) => {
const verifySignature = (signature: Signature, message: Message<MessageType>) => {
const signer = signers.getAll().find((signer) => signer.scheme.codecs.includes(signature.codec))
assert(signer !== undefined, "no matching signer found")
return signer.scheme.verify(signature, message)
Expand Down Expand Up @@ -149,9 +158,9 @@ export class Canvas<
let resultCount: number
let start: string | undefined = undefined
do {
const results: { id: string; message: Message<Action | Session> }[] = await db.query<{
const results: { id: string; message: Message<MessageType> }[] = await db.query<{
id: string
message: Message<Action | Session>
message: Message<MessageType>
}>("$messages", {
limit,
select: { id: true, message: true },
Expand Down Expand Up @@ -216,7 +225,7 @@ export class Canvas<

private constructor(
public readonly signers: SignerCache,
public readonly messageLog: AbstractGossipLog<Action | Session | Snapshot>,
public readonly messageLog: AbstractGossipLog<MessageType>,
private readonly runtime: Runtime,
) {
super()
Expand Down Expand Up @@ -303,7 +312,7 @@ export class Canvas<
await target.listen(this, port, options)
}

public async startLibp2p(config: NetworkConfig): Promise<Libp2p<ServiceMap<Action | Session | Snapshot>>> {
public async startLibp2p(config: NetworkConfig): Promise<Libp2p<ServiceMap<MessageType>>> {
this.networkConfig = config
return await this.messageLog.startLibp2p(config)
}
Expand Down Expand Up @@ -383,23 +392,23 @@ export class Canvas<
* Low-level utility method for internal and debugging use.
* The normal way to apply actions is to use the `Canvas.actions[name](...)` functions.
*/
public async insert(signature: Signature, message: Message<Session | Action | Snapshot>): Promise<{ id: string }> {
public async insert(signature: Signature, message: Message<MessageType>): Promise<{ id: string }> {
assert(message.topic === this.topic, "invalid message topic")

const signedMessage = this.messageLog.encode(signature, message)
await this.messageLog.insert(signedMessage)
return { id: signedMessage.id }
}

public async getMessage(id: string): Promise<SignedMessage<Action | Session | Snapshot> | null> {
public async getMessage(id: string): Promise<SignedMessage<MessageType> | null> {
return await this.messageLog.get(id)
}

public async *getMessages(
lowerBound: { id: string; inclusive: boolean } | null = null,
upperBound: { id: string; inclusive: boolean } | null = null,
options: { reverse?: boolean } = {},
): AsyncIterable<SignedMessage<Action | Session | Snapshot>> {
): AsyncIterable<SignedMessage<MessageType>> {
const range: { lt?: string; lte?: string; gt?: string; gte?: string; reverse?: boolean; limit?: number } = {}
if (lowerBound) {
if (lowerBound.inclusive) range.gte = lowerBound.id
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/ExecutionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as cbor from "@ipld/dag-cbor"
import { blake3 } from "@noble/hashes/blake3"
import { bytesToHex } from "@noble/hashes/utils"

import type { Action, Session, Snapshot } from "@canvas-js/interfaces"
import type { Action, MessageType } from "@canvas-js/interfaces"

import { ModelValue, PropertyValue, validateModelValue, updateModelValues, mergeModelValues } from "@canvas-js/modeldb"
import { AbstractGossipLog, SignedMessage, MessageId } from "@canvas-js/gossiplog"
Expand All @@ -21,7 +21,7 @@ export class ExecutionContext {
public readonly root: MessageId[]

constructor(
public readonly messageLog: AbstractGossipLog<Action | Session | Snapshot>,
public readonly messageLog: AbstractGossipLog<MessageType>,
public readonly signedMessage: SignedMessage<Action>,
public readonly address: string,
) {
Expand Down
16 changes: 5 additions & 11 deletions packages/core/src/runtime/AbstractRuntime.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as cbor from "@ipld/dag-cbor"
import { logger } from "@libp2p/logger"

import type { Action, Session, Snapshot, SignerCache, Awaitable } from "@canvas-js/interfaces"
import type { Action, Session, Snapshot, SignerCache, Awaitable, MessageType } from "@canvas-js/interfaces"

import { AbstractModelDB, Effect, ModelSchema } from "@canvas-js/modeldb"
import { GossipLogConsumer, MAX_MESSAGE_ID, AbstractGossipLog, SignedMessage } from "@canvas-js/gossiplog"
Expand Down Expand Up @@ -95,12 +95,12 @@ export abstract class AbstractRuntime {
this.#db = db
}

public getConsumer(): GossipLogConsumer<Action | Session | Snapshot> {
public getConsumer(): GossipLogConsumer<MessageType> {
const handleSession = this.handleSession.bind(this)
const handleAction = this.handleAction.bind(this)
const handleSnapshot = this.handleSnapshot.bind(this)

return async function (this: AbstractGossipLog<Action | Session | Snapshot>, signedMessage) {
return async function (this: AbstractGossipLog<MessageType>, signedMessage) {
if (isSession(signedMessage)) {
return await handleSession(signedMessage)
} else if (isAction(signedMessage)) {
Expand All @@ -113,10 +113,7 @@ export abstract class AbstractRuntime {
}
}

private async handleSnapshot(
signedMessage: SignedMessage<Snapshot>,
messageLog: AbstractGossipLog<Action | Session | Snapshot>,
) {
private async handleSnapshot(signedMessage: SignedMessage<Snapshot>, messageLog: AbstractGossipLog<MessageType>) {
const { models, effects } = signedMessage.message.payload

const messages = await messageLog.getMessages()
Expand Down Expand Up @@ -167,10 +164,7 @@ export abstract class AbstractRuntime {
await this.db.apply(effects)
}

private async handleAction(
signedMessage: SignedMessage<Action>,
messageLog: AbstractGossipLog<Action | Session | Snapshot>,
) {
private async handleAction(signedMessage: SignedMessage<Action>, messageLog: AbstractGossipLog<MessageType>) {
const { id, signature, message } = signedMessage
const { did, name, context } = message.payload

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { fromDSL } from "@ipld/schema/from-dsl.js"
import { create } from "@ipld/schema/typed.js"

import type { Action, Session, Snapshot } from "@canvas-js/interfaces"
import type { Action, MessageType, Session } from "@canvas-js/interfaces"

const schema = `
type ActionContext struct {
Expand Down Expand Up @@ -50,7 +50,7 @@ type Payload union {

const { toTyped } = create(fromDSL(schema), "Payload")

export function validatePayload(payload: unknown): payload is Action | Session | Snapshot {
export function validatePayload(payload: unknown): payload is MessageType {
const result = toTyped(payload) as { Action: Omit<Action, "type"> } | { Session: Omit<Session, "type"> } | undefined
return result !== undefined
}
6 changes: 3 additions & 3 deletions packages/core/src/targets/interface.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type pg from "pg"

import type { Action, Session, Snapshot } from "@canvas-js/interfaces"
import type { Action, MessageType, Session, Snapshot } from "@canvas-js/interfaces"
import type { AbstractGossipLog, GossipLogInit } from "@canvas-js/gossiplog"
import type { Canvas } from "@canvas-js/core"
import type { SqlStorage } from "@cloudflare/workers-types"

export interface PlatformTarget {
openGossipLog: (
location: { path: string | pg.ConnectionConfig | SqlStorage | null; topic: string; clear?: boolean },
init: GossipLogInit<Action | Session | Snapshot>,
) => Promise<AbstractGossipLog<Action | Session | Snapshot>>
init: GossipLogInit<MessageType>,
) => Promise<AbstractGossipLog<MessageType>>

listen: (app: Canvas, port: number, options?: { signal?: AbortSignal }) => Promise<void>
}
17 changes: 7 additions & 10 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@ import { blake3 } from "@noble/hashes/blake3"
import { utf8ToBytes } from "@noble/hashes/utils"
import { base64 } from "multiformats/bases/base64"

import { Action, Session, Snapshot } from "@canvas-js/interfaces"
import { Action, MessageType, Session, Snapshot } from "@canvas-js/interfaces"
import { SignedMessage } from "@canvas-js/gossiplog"
import { PrimaryKeyValue } from "@canvas-js/modeldb"

export const isAction = (
signedMessage: SignedMessage<Action | Session | Snapshot>,
): signedMessage is SignedMessage<Action> => signedMessage.message.payload.type === "action"
export const isAction = (signedMessage: SignedMessage<MessageType>): signedMessage is SignedMessage<Action> =>
signedMessage.message.payload.type === "action"

export const isSession = (
signedMessage: SignedMessage<Action | Session | Snapshot>,
): signedMessage is SignedMessage<Session> => signedMessage.message.payload.type === "session"
export const isSession = (signedMessage: SignedMessage<MessageType>): signedMessage is SignedMessage<Session> =>
signedMessage.message.payload.type === "session"

export const isSnapshot = (
signedMessage: SignedMessage<Action | Session | Snapshot>,
): signedMessage is SignedMessage<Snapshot> => signedMessage.message.payload.type === "snapshot"
export const isSnapshot = (signedMessage: SignedMessage<MessageType>): signedMessage is SignedMessage<Snapshot> =>
signedMessage.message.payload.type === "snapshot"

export const topicPattern = /^[a-zA-Z0-9.-]+$/

Expand Down
5 changes: 5 additions & 0 deletions packages/interfaces/src/MessageType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Action } from "./Action.js"
import { Session } from "./Session.js"
import { Snapshot } from "./Snapshot.js"

export type MessageType<AuthorizationData = any> = Action | Session<AuthorizationData> | Snapshot
7 changes: 4 additions & 3 deletions packages/interfaces/src/SessionSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Session } from "./Session.js"
import type { Action } from "./Action.js"
import type { Snapshot } from "./Snapshot.js"
import type { Awaitable } from "./Awaitable.js"
import { MessageType } from "./MessageType.js"

export type DidIdentifier = `did:${string}`

Expand All @@ -17,7 +18,7 @@ export interface AbstractSessionData {
}

export interface SessionSigner<AuthorizationData = any> {
scheme: SignatureScheme<Action | Session<AuthorizationData> | Snapshot>
scheme: SignatureScheme<MessageType<AuthorizationData>>
match: (did: DidIdentifier) => boolean

getDid: () => Awaitable<DidIdentifier>
Expand All @@ -31,11 +32,11 @@ export interface SessionSigner<AuthorizationData = any> {
options?: { did?: DidIdentifier } | { address: string },
) => Awaitable<{
payload: Session<AuthorizationData>
signer: Signer<Action | Session<AuthorizationData> | Snapshot>
signer: Signer<MessageType<AuthorizationData>>
} | null>
newSession: (topic: string) => Awaitable<{
payload: Session<AuthorizationData>
signer: Signer<Action | Session<AuthorizationData> | Snapshot>
signer: Signer<MessageType<AuthorizationData>>
}>

/**
Expand Down
1 change: 1 addition & 0 deletions packages/interfaces/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./Message.js"
export * from "./Session.js"
export * from "./Snapshot.js"
export * from "./Awaitable.js"
export * from "./MessageType.js"
Loading

0 comments on commit 33ff05f

Please sign in to comment.