diff --git a/packages/network/src/createSyncWorker.ts b/packages/network/src/createSyncWorker.ts index ec616fa9ba..d05675d475 100644 --- a/packages/network/src/createSyncWorker.ts +++ b/packages/network/src/createSyncWorker.ts @@ -1,8 +1,7 @@ import { Components } from "@latticexyz/recs"; import { fromWorker } from "@latticexyz/utils"; import { Subject } from "rxjs"; -import { NetworkComponentUpdate, SyncWorkerConfig } from "./types"; -import { Output } from "./workers/SyncWorker"; +import { NetworkEvent, SyncWorkerConfig } from "./types"; /** * Create a new SyncWorker ({@link Sync.worker.ts}) to performn contract/client state sync. @@ -14,13 +13,13 @@ import { Output } from "./workers/SyncWorker"; * dispose: function to dispose of the sync worker * } */ -export function createSyncWorker() { +export function createSyncWorker() { const config$ = new Subject(); const worker = new Worker(new URL("./workers/Sync.worker.ts", import.meta.url), { type: "module" }); - const ecsEvent$ = new Subject>(); + const ecsEvent$ = new Subject>(); // Pass in a "config stream", receive a stream of ECS events - const subscription = fromWorker>(worker, config$).subscribe(ecsEvent$); + const subscription = fromWorker>(worker, config$).subscribe(ecsEvent$); const dispose = () => { worker.terminate(); subscription?.unsubscribe(); diff --git a/packages/network/src/types.ts b/packages/network/src/types.ts index 3e58716468..71c1cbefd1 100644 --- a/packages/network/src/types.ts +++ b/packages/network/src/types.ts @@ -1,7 +1,7 @@ import { Result } from "@ethersproject/abi"; import { Components, ComponentValue, EntityID, SchemaOf } from "@latticexyz/recs"; import { Cached } from "@latticexyz/utils"; -import { BaseContract, ContractInterface } from "ethers"; +import { BaseContract, BigNumber, ContractInterface } from "ethers"; import { Observable } from "rxjs"; export interface NetworkConfig { @@ -69,6 +69,7 @@ export type Mappings = { export type NetworkComponentUpdate = { [key in keyof C]: { + type: NetworkEvents.NetworkComponentUpdate; component: key & string; value: ComponentValue> | undefined; }; @@ -79,6 +80,36 @@ export type NetworkComponentUpdate = { blockNumber: number; }; +export type SystemCallTransaction = { + hash: string; + to: string; + data: string; + value: BigNumber; +}; + +export type SystemCall = { + type: NetworkEvents.SystemCall; + tx: SystemCallTransaction; + updates: NetworkComponentUpdate[]; +}; + +export enum NetworkEvents { + SystemCall = "SystemCall", + NetworkComponentUpdate = "NetworkComponentUpdate", +} + +export type NetworkEvent = NetworkComponentUpdate | SystemCall; + +export function isSystemCallEvent(e: NetworkEvent): e is SystemCall { + return e.type === NetworkEvents.SystemCall; +} + +export function isNetworkComponentUpdateEvent( + e: NetworkEvent +): e is NetworkComponentUpdate { + return e.type === NetworkEvents.NetworkComponentUpdate; +} + export type SyncWorkerConfig = { provider: ProviderConfig; initialBlockNumber: number; @@ -87,6 +118,7 @@ export type SyncWorkerConfig = { chainId: number; checkpointServiceUrl?: string; streamServiceUrl?: string; + fetchSystemCalls?: boolean; }; export enum ContractSchemaValue { diff --git a/packages/network/src/workers/CacheStore.spec.ts b/packages/network/src/workers/CacheStore.spec.ts index e811ca7182..6e5dc345ed 100644 --- a/packages/network/src/workers/CacheStore.spec.ts +++ b/packages/network/src/workers/CacheStore.spec.ts @@ -1,6 +1,6 @@ import { EntityID } from "@latticexyz/recs"; import { packTuple } from "@latticexyz/utils"; -import { NetworkComponentUpdate } from "../types"; +import { NetworkComponentUpdate, NetworkEvents } from "../types"; import { createCacheStore, getCacheStoreEntries, @@ -29,6 +29,7 @@ describe("CacheStore", () => { describe("storeEvent", () => { it("should store events to the cacheStore", () => { const event: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 1, y: 2 }, @@ -49,6 +50,7 @@ describe("CacheStore", () => { expect([...cacheStore.state.entries()]).toEqual([[packTuple([0, 0]), { x: 1, y: 2 }]]); const event2: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x01" as EntityID, component: "Position", value: { x: 1, y: 2 }, @@ -73,6 +75,7 @@ describe("CacheStore", () => { it("should normalize hex entity ids to the same padding", () => { const event1: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00000000000000000000000001" as EntityID, component: "Position", value: { x: 1, y: 2 }, @@ -82,6 +85,7 @@ describe("CacheStore", () => { }; const event2: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00001" as EntityID, component: "Position", value: { x: 1, y: 3 }, @@ -97,6 +101,7 @@ describe("CacheStore", () => { const events = [...getCacheStoreEntries(cacheStore)]; expect(events.length).toBe(1); expect(events[0]).toEqual({ + type: NetworkEvents.NetworkComponentUpdate, entity: "0x01" as EntityID, component: "Position", value: { x: 1, y: 3 }, @@ -108,6 +113,7 @@ describe("CacheStore", () => { it("should set block number to one less than the last received event", () => { const event: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 1, y: 2 }, @@ -130,6 +136,7 @@ describe("CacheStore", () => { const cacheStore = createCacheStore(); const event: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 1, y: 2 }, @@ -142,6 +149,7 @@ describe("CacheStore", () => { expect([...getCacheStoreEntries(cacheStore)]).toEqual([ { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00", component: "Position", value: { x: 1, y: 2 }, @@ -152,6 +160,7 @@ describe("CacheStore", () => { ]); const event2: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 2, y: 2 }, @@ -164,6 +173,7 @@ describe("CacheStore", () => { expect([...getCacheStoreEntries(cacheStore)]).toEqual([ { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00", component: "Position", value: { x: 2, y: 2 }, @@ -174,6 +184,7 @@ describe("CacheStore", () => { ]); const event3: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x01" as EntityID, component: "Position", value: { x: -1, y: 2 }, @@ -186,6 +197,7 @@ describe("CacheStore", () => { expect([...getCacheStoreEntries(cacheStore)]).toEqual([ { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00", component: "Position", value: { x: 2, y: 2 }, @@ -194,6 +206,7 @@ describe("CacheStore", () => { txHash: "cache", }, { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x01", component: "Position", value: { x: -1, y: 2 }, @@ -211,6 +224,7 @@ describe("CacheStore", () => { const cacheStore2 = createCacheStore(); const event1: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 1, y: 2 }, @@ -220,6 +234,7 @@ describe("CacheStore", () => { }; const event2: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x01" as EntityID, component: "Health", value: { value: 1 }, @@ -232,6 +247,7 @@ describe("CacheStore", () => { storeEvent(cacheStore1, event2); const event3: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 3, y: 2 }, @@ -241,6 +257,7 @@ describe("CacheStore", () => { }; const event4: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Speed", value: { value: 10 }, @@ -258,6 +275,7 @@ describe("CacheStore", () => { expect(entries).toEqual([ { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00", component: "Position", value: { x: 3, y: 2 }, @@ -266,6 +284,7 @@ describe("CacheStore", () => { txHash: "cache", }, { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x01", component: "Health", value: { value: 1 }, @@ -274,6 +293,7 @@ describe("CacheStore", () => { txHash: "cache", }, { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00", component: "Speed", value: { value: 10 }, @@ -292,6 +312,7 @@ describe("CacheStore", () => { const cacheStore = createCacheStore(); storeEvent(cacheStore, { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 1, y: 2 }, @@ -299,6 +320,7 @@ describe("CacheStore", () => { }); storeEvent(cacheStore, { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x01" as EntityID, component: "Health", value: { value: 1 }, @@ -306,6 +328,7 @@ describe("CacheStore", () => { }); storeEvent(cacheStore, { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Position", value: { x: 3, y: 2 }, @@ -313,6 +336,7 @@ describe("CacheStore", () => { }); storeEvent(cacheStore, { + type: NetworkEvents.NetworkComponentUpdate, entity: "0x00" as EntityID, component: "Speed", value: { value: 10 }, diff --git a/packages/network/src/workers/CacheStore.ts b/packages/network/src/workers/CacheStore.ts index e616c49470..a2ed9caab4 100644 --- a/packages/network/src/workers/CacheStore.ts +++ b/packages/network/src/workers/CacheStore.ts @@ -2,7 +2,7 @@ import { Components, ComponentValue, EntityID, SchemaOf } from "@latticexyz/recs import { packTuple, transformIterator, unpackTuple } from "@latticexyz/utils"; import { initCache } from "../initCache"; import { ECSStateReply } from "@latticexyz/services/protobuf/ts/ecs-snapshot/ecs-snapshot"; -import { NetworkComponentUpdate } from "../types"; +import { NetworkComponentUpdate, NetworkEvents } from "../types"; import { BigNumber } from "ethers"; export type State = Map; @@ -83,6 +83,7 @@ export function getCacheStoreEntries({ } const ecsEvent: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component, entity: entity as EntityID, value: value as ComponentValue>, diff --git a/packages/network/src/workers/SyncWorker.spec.ts b/packages/network/src/workers/SyncWorker.spec.ts index 244977894a..72706418bc 100644 --- a/packages/network/src/workers/SyncWorker.spec.ts +++ b/packages/network/src/workers/SyncWorker.spec.ts @@ -1,9 +1,9 @@ import { JsonRpcProvider } from "@ethersproject/providers"; import { keccak256, sleep } from "@latticexyz/utils"; import { computed } from "mobx"; -import { Output, SyncWorker } from "./SyncWorker"; +import { SyncWorker } from "./SyncWorker"; import { Subject, Subscription } from "rxjs"; -import { NetworkComponentUpdate, SyncWorkerConfig } from "../types"; +import { isNetworkComponentUpdateEvent, NetworkComponentUpdate, NetworkEvents, SyncWorkerConfig } from "../types"; import { Components, EntityID } from "@latticexyz/recs"; import { createCacheStore, storeEvent } from "./CacheStore"; import * as syncUtils from "./syncUtils"; @@ -14,16 +14,18 @@ import { createLatestEventStreamRPC, createLatestEventStreamService } from "./sy // Test constants const cacheBlockNumber = 99; const cacheEvent = { + type: NetworkEvents.NetworkComponentUpdate, component: "0x10", entity: "0x11" as EntityID, value: {}, txHash: "0x12", lastEventInTx: true, blockNumber: cacheBlockNumber + 1, -}; +} as NetworkComponentUpdate; const snapshotBlockNumber = 9999; const snapshotEvents = [ { + type: NetworkEvents.NetworkComponentUpdate, component: "0x42", entity: "0x11" as EntityID, value: {}, @@ -31,12 +33,13 @@ const snapshotEvents = [ lastEventInTx: true, blockNumber: snapshotBlockNumber + 1, }, -]; +] as NetworkComponentUpdate[]; const blockNumber$ = new Subject(); const latestEvent$ = new Subject(); const lastGapStateEventBlockNumber = 999; const gapStateEvents = [ { + type: NetworkEvents.NetworkComponentUpdate, component: "0x20", entity: "0x21" as EntityID, value: {}, @@ -44,7 +47,7 @@ const gapStateEvents = [ lastEventInTx: true, blockNumber: lastGapStateEventBlockNumber, }, -]; +] as NetworkComponentUpdate[]; // Mocks jest.mock("../createProvider", () => ({ @@ -117,7 +120,7 @@ describe("Sync.worker", () => { output = jest.fn(); subscription = worker.work(input$).subscribe((e) => { - if (e.component !== keccak256("component.LoadingState")) { + if (isNetworkComponentUpdateEvent(e) && e.component !== keccak256("component.LoadingState")) { console.log("Called with", e); output(e); } @@ -144,7 +147,8 @@ describe("Sync.worker", () => { initialBlockNumber: 0, }); - const finalUpdate: Output = { + const finalUpdate: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: keccak256("component.LoadingState"), value: { state: SyncState.LIVE, msg: "Streaming live events", percentage: 100 }, entity: GodID, @@ -176,6 +180,7 @@ describe("Sync.worker", () => { await sleep(0); const event: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: "0x00", entity: "0x01" as EntityID, value: {}, @@ -314,6 +319,7 @@ describe("Sync.worker", () => { const secondLiveBlockNumber = 1002; const event1: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: "0x99", entity: "0x01" as EntityID, value: {}, @@ -323,6 +329,7 @@ describe("Sync.worker", () => { }; const event2: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: "0x0999", entity: "0x01" as EntityID, value: {}, @@ -332,6 +339,7 @@ describe("Sync.worker", () => { }; const event3: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: "0x9999", entity: "0x01" as EntityID, value: {}, diff --git a/packages/network/src/workers/SyncWorker.ts b/packages/network/src/workers/SyncWorker.ts index 2126d5c92d..e87b94f4e5 100644 --- a/packages/network/src/workers/SyncWorker.ts +++ b/packages/network/src/workers/SyncWorker.ts @@ -1,6 +1,12 @@ import { awaitStreamValue, DoWork, keccak256, streamToDefinedComputed } from "@latticexyz/utils"; import { Observable, Subject } from "rxjs"; -import { NetworkComponentUpdate, SyncWorkerConfig } from "../types"; +import { + isNetworkComponentUpdateEvent, + NetworkComponentUpdate, + NetworkEvent, + NetworkEvents, + SyncWorkerConfig, +} from "../types"; import { Components, ComponentValue, SchemaOf } from "@latticexyz/recs"; import { createCacheStore, @@ -23,15 +29,15 @@ import { createLatestEventStreamRPC, createLatestEventStreamService, createTransformWorldEventsFromStream, + createFetchSystemCallsFromEvents, fetchEventsInBlockRangeChunked, } from "./syncUtils"; import { createBlockNumberStream } from "../createBlockNumberStream"; import { GodID, SyncState } from "./constants"; -export type Output = NetworkComponentUpdate; -export class SyncWorker implements DoWork> { +export class SyncWorker implements DoWork> { private input$ = new Subject(); - private output$ = new Subject>(); + private output$ = new Subject>(); constructor() { this.init(); @@ -46,9 +52,10 @@ export class SyncWorker implements DoWork = { + const update: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: keccak256("component.LoadingState"), - value: { state, msg, percentage } as unknown as ComponentValue>, + value: { state, msg, percentage } as unknown as ComponentValue>, entity: GodID, txHash: "worker", lastEventInTx: false, @@ -83,6 +90,7 @@ export class SyncWorker implements DoWork implements DoWork[] = []; + : createLatestEventStreamRPC( + blockNumber$, + fetchWorldEvents, + fetchSystemCalls ? createFetchSystemCallsFromEvents(provider) : undefined + ); + const initialLiveEvents: NetworkComponentUpdate[] = []; latestEvent$.subscribe((event) => { // If initial sync is in progress, temporary store the events to apply later - if (!passLiveEventsToOutput) return initialLiveEvents.push(event); + // Ignore system calls during initial sync + if (!passLiveEventsToOutput) { + if (isNetworkComponentUpdateEvent(event)) initialLiveEvents.push(event); + return; + } + + if (isNetworkComponentUpdateEvent(event)) { + // Store cache to indexdb every block + if (event.blockNumber > cacheStore.current.blockNumber + 1) + saveCacheStoreToIndexDb(indexDbCache, cacheStore.current); - // Store cache to indexdb every block - if (event.blockNumber > cacheStore.current.blockNumber + 1) - saveCacheStoreToIndexDb(indexDbCache, cacheStore.current); + storeEvent(cacheStore.current, event); + } - storeEvent(cacheStore.current, event); - this.output$.next(event as Output); + this.output$.next(event as NetworkEvent); }); const streamStartBlockNumberPromise = awaitStreamValue(blockNumber$); @@ -177,7 +196,7 @@ export class SyncWorker implements DoWork); + this.output$.next(update as NetworkEvent); } // Save initial state to cache @@ -188,7 +207,7 @@ export class SyncWorker implements DoWork): Observable> { + public work(input$: Observable): Observable> { input$.subscribe(this.input$); return this.output$.asObservable(); } diff --git a/packages/network/src/workers/syncUtils.spec.ts b/packages/network/src/workers/syncUtils.spec.ts index 7184cdbc77..edc10096a7 100644 --- a/packages/network/src/workers/syncUtils.spec.ts +++ b/packages/network/src/workers/syncUtils.spec.ts @@ -1,7 +1,7 @@ import { EntityID } from "@latticexyz/recs"; import { sleep } from "@latticexyz/utils"; import { Subject } from "rxjs"; -import { NetworkComponentUpdate } from "../types"; +import { NetworkComponentUpdate, NetworkEvents } from "../types"; import { getCacheStoreEntries } from "./CacheStore"; import { createLatestEventStreamRPC, @@ -30,6 +30,7 @@ describe("syncUtils", () => { it("should fetch world events for new block range when a new block number arrives", async () => { const blockNumber$ = new Subject(); const event: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: "0x1", entity: "0x0" as EntityID, value: {}, @@ -70,6 +71,7 @@ describe("syncUtils", () => { describe("fetchStateInBlockRange", () => { it("should fetch world events in the given range with the given interval", async () => { const event: NetworkComponentUpdate = { + type: NetworkEvents.NetworkComponentUpdate, component: "0x01", entity: "0x00" as EntityID, value: {}, @@ -90,6 +92,7 @@ describe("syncUtils", () => { expect(state.blockNumber).toBe(4241); expect([...getCacheStoreEntries(state)]).toEqual([ { + type: NetworkEvents.NetworkComponentUpdate, component: "0x01", entity: "0x00" as EntityID, value: {}, diff --git a/packages/network/src/workers/syncUtils.ts b/packages/network/src/workers/syncUtils.ts index c70d14604d..2a1c8e3252 100644 --- a/packages/network/src/workers/syncUtils.ts +++ b/packages/network/src/workers/syncUtils.ts @@ -10,7 +10,14 @@ import { fetchEventsInBlockRange } from "../networkUtils"; import { ECSStateReply } from "@latticexyz/services/protobuf/ts/ecs-snapshot/ecs-snapshot"; import { ECSStateSnapshotServiceClient } from "@latticexyz/services/protobuf/ts/ecs-snapshot/ecs-snapshot.client"; import { ECSStreamServiceClient } from "@latticexyz/services/protobuf/ts/ecs-stream/ecs-stream.client"; -import { NetworkComponentUpdate, ContractConfig } from "../types"; +import { + NetworkComponentUpdate, + ContractConfig, + SystemCallTransaction, + NetworkEvents, + SystemCall, + NetworkEvent, +} from "../types"; import { CacheStore, createCacheStore, storeEvent, storeEvents } from "./CacheStore"; import { abi as ComponentAbi } from "@latticexyz/solecs/abi/Component.json"; import { abi as WorldAbi } from "@latticexyz/solecs/abi/World.json"; @@ -131,7 +138,7 @@ export async function reduceFetchedState( const component = to256BitString(stateComponents[componentIdIdx]); const entity = stateEntities[entityIdIdx] as EntityID; const value = await decode(component, rawValue); - storeEvent(cacheStore, { component, entity, value, blockNumber }); + storeEvent(cacheStore, { type: NetworkEvents.NetworkComponentUpdate, component, entity, value, blockNumber }); } } @@ -184,8 +191,9 @@ export function createLatestEventStreamService( */ export function createLatestEventStreamRPC( blockNumber$: Observable, - fetchWorldEvents: ReturnType -): Observable { + fetchWorldEvents: ReturnType, + fetchSystemCallsFromEvents?: ReturnType +): Observable { let lastSyncedBlockNumber: number | undefined; return blockNumber$.pipe( @@ -196,6 +204,12 @@ export function createLatestEventStreamRPC( lastSyncedBlockNumber = to; const events = await fetchWorldEvents(from, to); console.log(`[SyncWorker] fetched ${events.length} events from block range ${from} -> ${to}`); + + if (fetchSystemCallsFromEvents && events.length > 0) { + const systemCalls = await fetchSystemCallsFromEvents(events, blockNumber); + return [...events, ...systemCalls]; + } + return events; }), awaitPromise(), @@ -324,7 +338,7 @@ export function createWorldTopics() { * @param decode Function to decode raw component values ({@link createDecode}) * @returns Function to fetch World contract events in a given block range. */ -export function createFetchWorldEventsInBlockRange( +export function createFetchWorldEventsInBlockRange( provider: JsonRpcProvider, worldConfig: ContractConfig, batch: boolean | undefined, @@ -335,7 +349,7 @@ export function createFetchWorldEventsInBlockRange( // Fetches World events in the provided block range (including from and to) return async (from: number, to: number) => { const contractsEvents = await fetchEventsInBlockRange(provider, topics, from, to, { World: worldConfig }, batch); - const ecsEvents: NetworkComponentUpdate[] = []; + const ecsEvents: NetworkComponentUpdate[] = []; for (const event of contractsEvents) { const { lastEventInTx, txHash, args } = event; @@ -356,13 +370,14 @@ export function createFetchWorldEventsInBlockRange( const blockNumber = to; const ecsEvent = { + type: NetworkEvents.NetworkComponentUpdate, component, entity, value: undefined, blockNumber, lastEventInTx, txHash, - }; + } as NetworkComponentUpdate; if (event.eventKey === "ComponentValueRemoved") { ecsEvents.push(ecsEvent); @@ -407,6 +422,7 @@ export function createTransformWorldEventsFromStream(decode: ReturnType { + const systemCalls: SystemCall[] = []; + const transactionHashToEvents = events.reduce((acc, event) => { + if (["worker", "cache"].includes(event.txHash)) return acc; + + if (!acc[event.txHash]) acc[event.txHash] = []; + + acc[event.txHash].push(event); + + return acc; + }, {} as { [key: string]: NetworkComponentUpdate[] }); + + const txData = await Promise.all( + Object.keys(transactionHashToEvents).map((hash) => fetchSystemCallData(hash, blockNumber)) + ); + clearBlock(blockNumber); + + for (const tx of txData) { + if (!tx) continue; + + systemCalls.push({ + type: NetworkEvents.SystemCall, + tx, + updates: transactionHashToEvents[tx.hash], + }); + } + + return systemCalls; + }; +} + +function createFetchSystemCallData(fetchBlock: ReturnType["fetchBlock"]) { + return async (txHash: string, blockNumber: number) => { + const block = await fetchBlock(blockNumber); + const tx = block.transactions.find((tx) => tx.hash === txHash); + + if (!tx) return; + + return { + to: tx.to, + data: tx.data, + value: tx.value, + hash: tx.hash, + } as SystemCallTransaction; + }; +} + +function createBlockCache(provider: JsonRpcProvider) { + const blocks: Record>> = {}; + + return { + fetchBlock: async (blockNumber: number) => { + if (blocks[blockNumber]) return blocks[blockNumber]; + + const block = await provider.getBlockWithTransactions(blockNumber); + blocks[blockNumber] = block; + + return block; + }, + clearBlock: (blockNumber: number) => delete blocks[blockNumber], + }; +} diff --git a/packages/std-client/src/setup/setupContracts.ts b/packages/std-client/src/setup/setupContracts.ts index 66999a164e..5bab9e8b73 100644 --- a/packages/std-client/src/setup/setupContracts.ts +++ b/packages/std-client/src/setup/setupContracts.ts @@ -9,6 +9,7 @@ import { createSystemExecutor, NetworkConfig, SyncWorkerConfig, + isNetworkComponentUpdateEvent, } from "@latticexyz/network"; import { bufferTime, filter, Observable, Subject } from "rxjs"; import { @@ -105,7 +106,12 @@ export async function setupContracts