Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(store-indexer): catch errors when parsing logs to tables and storage operations #1488

Merged
merged 2 commits into from
Sep 14, 2023
Merged
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
5 changes: 5 additions & 0 deletions .changeset/rare-lizards-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/store-sync": patch
---

Catch errors when parsing logs to tables and storage operations, log and skip
189 changes: 99 additions & 90 deletions packages/store-sync/src/blockLogsToStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,39 +34,43 @@ export function blockLogsToStorage<TConfig extends StoreConfig = StoreConfig>({
// 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<string, SchemaAbiType> schemas
// TODO: refactor to decode key with protocol-parser utils
// TODO: refactor encode/decode to use Record<string, SchemaAbiType> 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<typeof storeConfig, typeof schemasTable.name>;
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<typeof storeConfig, typeof schemasTable.name>;

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);

Expand Down Expand Up @@ -101,75 +105,80 @@ export function blockLogsToStorage<TConfig extends StoreConfig = StoreConfig>({

const operations = block.logs
.map((log): StorageOperation<TConfig> | 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<TConfig, keyof TConfig["tables"]>;
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<TConfig, keyof TConfig["tables"]>;
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);

Expand Down