diff --git a/.changeset/rare-lizards-sleep.md b/.changeset/rare-lizards-sleep.md new file mode 100644 index 0000000000..ac297a57bf --- /dev/null +++ b/.changeset/rare-lizards-sleep.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/store-sync": patch +--- + +Catch errors when parsing logs to tables and storage operations, log and skip diff --git a/packages/store-sync/src/blockLogsToStorage.ts b/packages/store-sync/src/blockLogsToStorage.ts index 814fbda4c7..70f07276a6 100644 --- a/packages/store-sync/src/blockLogsToStorage.ts +++ b/packages/store-sync/src/blockLogsToStorage.ts @@ -34,39 +34,43 @@ export function blockLogsToStorage({ // Find table schema registration events const newTables = block.logs .map((log) => { - if (log.eventName !== "StoreSetRecord") return; - if (log.args.table !== schemasTableId) return; + try { + if (log.eventName !== "StoreSetRecord") return; + if (log.args.table !== schemasTableId) return; - // TODO: refactor encode/decode to use Record schemas - // TODO: refactor to decode key with protocol-parser utils + // TODO: refactor encode/decode to use Record schemas + // TODO: refactor to decode key with protocol-parser utils - const [tableId, ...otherKeys] = log.args.key; - if (otherKeys.length) { - console.warn("registerSchema event is expected to have only one key in key tuple, but got multiple", log); - } + const [tableId, ...otherKeys] = log.args.key; + if (otherKeys.length) { + console.warn("registerSchema event is expected to have only one key in key tuple, but got multiple", log); + } - const table = hexToTableId(tableId); + const table = hexToTableId(tableId); - const valueTuple = decodeRecord(abiTypesToSchema(Object.values(schemasTable.schema)), log.args.data); - const value = Object.fromEntries( - Object.keys(schemasTable.schema).map((name, i) => [name, valueTuple[i]]) - ) as ConfigToValuePrimitives; + const valueTuple = decodeRecord(abiTypesToSchema(Object.values(schemasTable.schema)), log.args.data); + const value = Object.fromEntries( + Object.keys(schemasTable.schema).map((name, i) => [name, valueTuple[i]]) + ) as ConfigToValuePrimitives; - const keySchema = hexToSchema(value.keySchema); - const valueSchema = hexToSchema(value.valueSchema); - const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedKeyNames)[0]; - const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedFieldNames)[0]; + const keySchema = hexToSchema(value.keySchema); + const valueSchema = hexToSchema(value.valueSchema); + const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedKeyNames)[0]; + const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedFieldNames)[0]; - const valueAbiTypes = [...valueSchema.staticFields, ...valueSchema.dynamicFields]; + const valueAbiTypes = [...valueSchema.staticFields, ...valueSchema.dynamicFields]; - return { - address: log.address, - tableId, - namespace: table.namespace, - name: table.name, - keySchema: Object.fromEntries(keySchema.staticFields.map((abiType, i) => [keyNames[i], abiType])), - valueSchema: Object.fromEntries(valueAbiTypes.map((abiType, i) => [fieldNames[i], abiType])), - }; + return { + address: log.address, + tableId, + namespace: table.namespace, + name: table.name, + keySchema: Object.fromEntries(keySchema.staticFields.map((abiType, i) => [keyNames[i], abiType])), + valueSchema: Object.fromEntries(valueAbiTypes.map((abiType, i) => [fieldNames[i], abiType])), + }; + } catch (error: unknown) { + console.error("Failed to get table from log", log, error); + } }) .filter(isDefined); @@ -101,75 +105,80 @@ export function blockLogsToStorage({ const operations = block.logs .map((log): StorageOperation | undefined => { - const table = tables[`${getAddress(log.address)}:${log.args.table}`]; - if (!table) { - debug("no table found for event, skipping", hexToTableId(log.args.table), log); - return; - } - - const keyNames = Object.keys(table.keySchema); - const keyValues = decodeKeyTuple( - { staticFields: Object.values(table.keySchema), dynamicFields: [] }, - log.args.key - ); - const key = Object.fromEntries(keyValues.map((value, i) => [keyNames[i], value])) as Key< - TConfig, - keyof TConfig["tables"] - >; - - const valueAbiTypes = Object.values(table.valueSchema); - const valueSchema = abiTypesToSchema(valueAbiTypes); - const fieldNames = Object.keys(table.valueSchema); - - // TODO: decide if we should split these up into distinct operations so the storage adapter can decide whether to combine or not - if (log.eventName === "StoreSetRecord" || log.eventName === "StoreEphemeralRecord") { - const valueTuple = decodeRecord(valueSchema, log.args.data); - const value = Object.fromEntries(fieldNames.map((name, i) => [name, valueTuple[i]])) as Value< + try { + const table = tables[`${getAddress(log.address)}:${log.args.table}`]; + if (!table) { + debug("no table found for event, skipping", hexToTableId(log.args.table), log); + return; + } + + const keyNames = Object.keys(table.keySchema); + const keyValues = decodeKeyTuple( + { staticFields: Object.values(table.keySchema), dynamicFields: [] }, + log.args.key + ); + const key = Object.fromEntries(keyValues.map((value, i) => [keyNames[i], value])) as Key< TConfig, keyof TConfig["tables"] >; - return { - log, - address: getAddress(log.address), - namespace: table.namespace, - name: table.name, - type: "SetRecord", - key, - value, - }; - } - if (log.eventName === "StoreSetField") { - const fieldName = fieldNames[log.args.schemaIndex] as string & keyof Value; - const fieldValue = decodeField(valueAbiTypes[log.args.schemaIndex], log.args.data) as Value< - TConfig, - keyof TConfig["tables"] - >[typeof fieldName]; - return { - log, - address: getAddress(log.address), - namespace: table.namespace, - name: table.name, - type: "SetField", - key, - fieldName, - fieldValue, - }; - } - - if (log.eventName === "StoreDeleteRecord") { - return { - log, - address: getAddress(log.address), - namespace: table.namespace, - name: table.name, - type: "DeleteRecord", - key, - }; + const valueAbiTypes = Object.values(table.valueSchema); + const valueSchema = abiTypesToSchema(valueAbiTypes); + const fieldNames = Object.keys(table.valueSchema); + + // TODO: decide if we should split these up into distinct operations so the storage adapter can decide whether to combine or not + if (log.eventName === "StoreSetRecord" || log.eventName === "StoreEphemeralRecord") { + const valueTuple = decodeRecord(valueSchema, log.args.data); + const value = Object.fromEntries(fieldNames.map((name, i) => [name, valueTuple[i]])) as Value< + TConfig, + keyof TConfig["tables"] + >; + return { + log, + address: getAddress(log.address), + namespace: table.namespace, + name: table.name, + type: "SetRecord", + key, + value, + }; + } + + if (log.eventName === "StoreSetField") { + const fieldName = fieldNames[log.args.schemaIndex] as string & + keyof Value; + const fieldValue = decodeField(valueAbiTypes[log.args.schemaIndex], log.args.data) as Value< + TConfig, + keyof TConfig["tables"] + >[typeof fieldName]; + return { + log, + address: getAddress(log.address), + namespace: table.namespace, + name: table.name, + type: "SetField", + key, + fieldName, + fieldValue, + }; + } + + if (log.eventName === "StoreDeleteRecord") { + return { + log, + address: getAddress(log.address), + namespace: table.namespace, + name: table.name, + type: "DeleteRecord", + key, + }; + } + + debug("unknown store event or log, skipping", log); + return; + } catch (error: unknown) { + console.error("Failed to translate log to storage operation", log, error); } - - debug("unknown store event or log, skipping", log); - return; }) .filter(isDefined);