-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(store-sync): add syncToStash util (#3192)
Co-authored-by: Kevin Ingersoll <[email protected]>
- Loading branch information
Showing
13 changed files
with
287 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
"@latticexyz/store-sync": patch | ||
--- | ||
|
||
Added a `syncToStash` util to hydrate a `stash` client store from MUD contract state. This is currently exported from `@latticexyz/store-sync/internal` while Stash package is unstable/experimental. | ||
|
||
```ts | ||
import { createClient, http } from "viem"; | ||
import { anvil } from "viem/chains"; | ||
import { createStash } from "@latticexyz/stash/internal"; | ||
import { syncToStash } from "@latticexyz/store-sync/internal"; | ||
import config from "../mud.config"; | ||
|
||
const client = createClient({ | ||
chain: anvil, | ||
transport: http(), | ||
}); | ||
|
||
const address = "0x..."; | ||
|
||
const stash = createStash(config); | ||
const sync = await syncToStash({ stash, client, address }); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from "../sql"; | ||
export * from "../stash"; |
92 changes: 92 additions & 0 deletions
92
packages/store-sync/src/stash/createStorageAdapter.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { beforeAll, describe, expect, it } from "vitest"; | ||
import { storeEventsAbi } from "@latticexyz/store"; | ||
import { createStorageAdapter } from "./createStorageAdapter"; | ||
import { config, deployMockGame } from "../../test/mockGame"; | ||
import { fetchAndStoreLogs } from "../fetchAndStoreLogs"; | ||
import { testClient } from "../../test/common"; | ||
import { getBlockNumber } from "viem/actions"; | ||
import { createStash } from "@latticexyz/stash/internal"; | ||
|
||
describe("createStorageAdapter", async () => { | ||
beforeAll(async () => { | ||
await deployMockGame(); | ||
}); | ||
|
||
it("sets component values from logs", async () => { | ||
const stash = createStash(config); | ||
const storageAdapter = createStorageAdapter({ stash }); | ||
|
||
console.log("fetching blocks"); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
for await (const block of fetchAndStoreLogs({ | ||
storageAdapter, | ||
publicClient: testClient, | ||
events: storeEventsAbi, | ||
fromBlock: 0n, | ||
toBlock: await getBlockNumber(testClient), | ||
})) { | ||
// | ||
} | ||
|
||
expect(stash.get().records).toMatchInlineSnapshot(` | ||
{ | ||
"": { | ||
"Health": { | ||
"0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb": { | ||
"health": 0n, | ||
"player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", | ||
}, | ||
"0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e": { | ||
"health": 5n, | ||
"player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", | ||
}, | ||
"0x328809Bc894f92807417D2dAD6b7C998c1aFdac6": { | ||
"health": 5n, | ||
"player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", | ||
}, | ||
}, | ||
"Inventory": {}, | ||
"Position": { | ||
"0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb": { | ||
"player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", | ||
"x": 3, | ||
"y": 5, | ||
}, | ||
"0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e": { | ||
"player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", | ||
"x": 1, | ||
"y": -1, | ||
}, | ||
"0x328809Bc894f92807417D2dAD6b7C998c1aFdac6": { | ||
"player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", | ||
"x": 3, | ||
"y": 5, | ||
}, | ||
"0xdBa86119a787422C593ceF119E40887f396024E2": { | ||
"player": "0xdBa86119a787422C593ceF119E40887f396024E2", | ||
"x": 100, | ||
"y": 100, | ||
}, | ||
}, | ||
"Score": {}, | ||
"Terrain": { | ||
"3|5": { | ||
"terrainType": 2, | ||
"x": 3, | ||
"y": 5, | ||
}, | ||
}, | ||
"Winner": {}, | ||
}, | ||
} | ||
`); | ||
|
||
expect(stash.getRecord({ table: config.tables.Terrain, key: { x: 3, y: 5 } })).toMatchInlineSnapshot(` | ||
{ | ||
"terrainType": 2, | ||
"x": 3, | ||
"y": 5, | ||
} | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { Stash, deleteRecord, getRecord, setRecord } from "@latticexyz/stash/internal"; | ||
import { | ||
decodeKey, | ||
decodeValueArgs, | ||
encodeValueArgs, | ||
getKeySchema, | ||
getSchemaTypes, | ||
getValueSchema, | ||
} from "@latticexyz/protocol-parser/internal"; | ||
import { spliceHex } from "@latticexyz/common"; | ||
import { size } from "viem"; | ||
import { Table } from "@latticexyz/config"; | ||
import { StorageAdapter, StorageAdapterBlock, emptyValueArgs } from "../common"; | ||
|
||
export type CreateStorageAdapter = { | ||
stash: Stash; | ||
}; | ||
|
||
export function createStorageAdapter({ stash }: CreateStorageAdapter): StorageAdapter { | ||
const tablesById = Object.fromEntries( | ||
Object.values(stash.get().config) | ||
.flatMap((namespace) => Object.values(namespace) as readonly Table[]) | ||
.map((table) => [table.tableId, table]), | ||
); | ||
|
||
return async function storageAdapter({ logs }: StorageAdapterBlock): Promise<void> { | ||
for (const log of logs) { | ||
const table = tablesById[log.args.tableId]; | ||
if (!table) continue; | ||
|
||
const valueSchema = getSchemaTypes(getValueSchema(table)); | ||
const keySchema = getSchemaTypes(getKeySchema(table)); | ||
const key = decodeKey(keySchema, log.args.keyTuple); | ||
|
||
if (log.eventName === "Store_SetRecord") { | ||
const value = decodeValueArgs(valueSchema, log.args); | ||
setRecord({ stash, table, key, value }); | ||
} else if (log.eventName === "Store_SpliceStaticData") { | ||
const previousValue = getRecord({ stash, table, key }); | ||
|
||
const { | ||
staticData: previousStaticData, | ||
encodedLengths, | ||
dynamicData, | ||
} = previousValue ? encodeValueArgs(valueSchema, previousValue) : emptyValueArgs; | ||
|
||
const staticData = spliceHex(previousStaticData, log.args.start, size(log.args.data), log.args.data); | ||
const value = decodeValueArgs(valueSchema, { | ||
staticData, | ||
encodedLengths, | ||
dynamicData, | ||
}); | ||
|
||
setRecord({ stash, table, key, value }); | ||
} else if (log.eventName === "Store_SpliceDynamicData") { | ||
const previousValue = getRecord({ stash, table, key }); | ||
|
||
const { staticData, dynamicData: previousDynamicData } = previousValue | ||
? encodeValueArgs(valueSchema, previousValue) | ||
: emptyValueArgs; | ||
|
||
const dynamicData = spliceHex(previousDynamicData, log.args.start, log.args.deleteCount, log.args.data); | ||
const value = decodeValueArgs(valueSchema, { | ||
staticData, | ||
encodedLengths: log.args.encodedLengths, | ||
dynamicData, | ||
}); | ||
|
||
setRecord({ stash, table, key, value }); | ||
} else if (log.eventName === "Store_DeleteRecord") { | ||
deleteRecord({ stash, table, key }); | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./createStorageAdapter"; | ||
export * from "./syncToStash"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { getRecord, setRecord, registerTable, Stash } from "@latticexyz/stash/internal"; | ||
import { Address, Client, publicActions } from "viem"; | ||
import { createStorageAdapter } from "./createStorageAdapter"; | ||
import { defineTable } from "@latticexyz/store/config/v2"; | ||
import { SyncStep } from "../SyncStep"; | ||
import { SyncResult } from "../common"; | ||
import { createStoreSync } from "../createStoreSync"; | ||
import { getSchemaPrimitives, getValueSchema } from "@latticexyz/protocol-parser/internal"; | ||
|
||
export const SyncProgress = defineTable({ | ||
namespaceLabel: "syncToStash", | ||
label: "SyncProgress", | ||
schema: { | ||
step: "string", | ||
percentage: "uint32", | ||
latestBlockNumber: "uint256", | ||
lastBlockNumberProcessed: "uint256", | ||
message: "string", | ||
}, | ||
key: [], | ||
}); | ||
|
||
export const initialProgress = { | ||
step: SyncStep.INITIALIZE, | ||
percentage: 0, | ||
latestBlockNumber: 0n, | ||
lastBlockNumberProcessed: 0n, | ||
message: "Connecting", | ||
} satisfies getSchemaPrimitives<getValueSchema<typeof SyncProgress>>; | ||
|
||
export type SyncToStashOptions = { | ||
stash: Stash; | ||
client: Client; | ||
address: Address; | ||
startSync?: boolean; | ||
}; | ||
|
||
export type SyncToStashResult = Omit<SyncResult, "waitForTransaction"> & { | ||
waitForStateChange: SyncResult["waitForTransaction"]; | ||
stopSync: () => void; | ||
}; | ||
|
||
export async function syncToStash({ | ||
stash, | ||
client, | ||
address, | ||
startSync = true, | ||
}: SyncToStashOptions): Promise<SyncToStashResult> { | ||
registerTable({ stash, table: SyncProgress }); | ||
|
||
const storageAdapter = createStorageAdapter({ stash }); | ||
|
||
const { waitForTransaction: waitForStateChange, ...sync } = await createStoreSync({ | ||
storageAdapter, | ||
publicClient: client.extend(publicActions) as never, | ||
address, | ||
onProgress: (nextValue) => { | ||
const currentValue = getRecord({ stash, table: SyncProgress, key: {} }); | ||
// update sync progress until we're caught up and live | ||
if (currentValue?.step !== SyncStep.LIVE) { | ||
setRecord({ stash, table: SyncProgress, key: {}, value: nextValue }); | ||
} | ||
}, | ||
}); | ||
|
||
const sub = startSync ? sync.storedBlockLogs$.subscribe() : null; | ||
function stopSync(): void { | ||
sub?.unsubscribe(); | ||
} | ||
|
||
return { | ||
...sync, | ||
waitForStateChange, | ||
stopSync, | ||
}; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters