Skip to content

Commit 5692c89

Browse files
feat(subgraph): add PiecesAddedV2 event handler (#685)
feat: add PiecesAddedV2 event handler
1 parent 24ed2b6 commit 5692c89

6 files changed

Lines changed: 277 additions & 32 deletions

File tree

apps/subgraph/scripts/sync-abis.mjs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,36 @@ import { fwss, pdp } from "@filoz/synapse-core/abis";
1313
const here = dirname(fileURLToPath(import.meta.url));
1414
const abisDir = join(here, "..", "abis");
1515

16+
// TODO(https://github.com/FilOzone/dealbot/issues/683): PDPVerifier PR
17+
// https://github.com/FilOzone/pdp/pull/300 adds a compact PiecesAddedV2 event
18+
// but hasn't been merged/deployed/picked up by synapse-core yet, so it's
19+
// still missing from the fetched ABI. Inject it by hand so codegen produces
20+
// the handlePiecesAddedV2 bindings; drop this once synapse-core ships it
21+
// natively (`pdp.some(e => e.name === "PiecesAddedV2")` will start passing).
22+
const PIECES_ADDED_V2_EVENT = {
23+
type: "event",
24+
name: "PiecesAddedV2",
25+
inputs: [
26+
{ name: "setId", type: "uint256", indexed: true, internalType: "uint256" },
27+
{ name: "firstPieceId", type: "uint256", indexed: false, internalType: "uint256" },
28+
{
29+
name: "pieceCids",
30+
type: "tuple[]",
31+
indexed: false,
32+
internalType: "struct Cids.PackedCid[]",
33+
components: [
34+
{ name: "header", type: "bytes32", internalType: "bytes32" },
35+
{ name: "root", type: "bytes32", internalType: "bytes32" },
36+
],
37+
},
38+
],
39+
anonymous: false,
40+
};
41+
42+
const pdpPatched = pdp.some((entry) => entry.name === "PiecesAddedV2") ? pdp : [...pdp, PIECES_ADDED_V2_EVENT];
43+
1644
const targets = [
17-
{ file: "PDPVerifier.json", abi: pdp },
45+
{ file: "PDPVerifier.json", abi: pdpPatched },
1846
{ file: "FilecoinWarmStorageService.json", abi: fwss },
1947
];
2048

apps/subgraph/src/helpers.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,22 @@ export function validateCommPv2(cidData: Bytes): CommPv2ValidationResult {
160160
return new CommPv2ValidationResult(true, padding, height, offset);
161161
}
162162

163+
// Reconstructs a full CID byte string from a Cids.PackedCid (header, root), as emitted by
164+
// PDPVerifier.PiecesAddedV2. `header` is right-aligned in its 32-byte word with zero padding on
165+
// the left; the CID is recovered by dropping only those leading zero bytes and appending `root`.
166+
export function reconstructCidFromPackedCid(header: Bytes, root: Bytes): Bytes {
167+
let firstNonZero = header.length;
168+
for (let i = 0; i < header.length; i++) {
169+
if (header[i] != 0) {
170+
firstNonZero = i;
171+
break;
172+
}
173+
}
174+
175+
const headerBytes = Bytes.fromUint8Array(header.slice(firstNonZero));
176+
return headerBytes.concat(root);
177+
}
178+
163179
export function unpaddedSize(padding: BigInt, height: u8): BigInt {
164180
if (height > 58) {
165181
return BigInt.zero();

apps/subgraph/src/pdp-verifier.ts

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { BigInt, log } from "@graphprotocol/graph-ts";
1+
import { BigInt, Bytes, log } from "@graphprotocol/graph-ts";
22
import {
33
DataSetCreated as DataSetCreatedEvent,
44
DataSetDeleted as DataSetDeletedEvent,
55
DataSetEmpty as DataSetEmptyEvent,
66
NextProvingPeriod as NextProvingPeriodEvent,
77
PiecesAdded as PiecesAddedEvent,
8+
PiecesAddedV2 as PiecesAddedV2Event,
89
PiecesRemoved as PiecesRemovedEvent,
910
PossessionProven as PossessionProvenEvent,
1011
StorageProviderChanged as StorageProviderChangedEvent,
@@ -15,6 +16,7 @@ import {
1516
getRootEntityId,
1617
getRootSampleKey,
1718
maxProvingPeriodFor,
19+
reconstructCidFromPackedCid,
1820
unpaddedSize,
1921
validateCommPv2,
2022
} from "./helpers";
@@ -163,12 +165,50 @@ export function handleNextProvingPeriod(event: NextProvingPeriodEvent): void {
163165
}
164166
}
165167

168+
// Shared by handlePiecesAdded and handlePiecesAddedV2: creates the Root entity for one added
169+
// piece. Returns false (and logs) if the Root already exists, so the caller can skip counting it
170+
// toward the EMPTY -> READY transition.
171+
function createRootFromPieceCid(
172+
setId: BigInt,
173+
rootId: BigInt,
174+
pieceCidBytes: Bytes,
175+
proofSetEntityId: Bytes,
176+
blockTimestamp: BigInt,
177+
): boolean {
178+
const commPData = validateCommPv2(pieceCidBytes);
179+
const rawSize = commPData.isValid ? unpaddedSize(commPData.padding, commPData.height) : BigInt.zero();
180+
181+
const rootEntityId = getRootEntityId(setId, rootId);
182+
if (Root.load(rootEntityId) != null) {
183+
log.warning("createRootFromPieceCid: Root {} for Set {} already exists; skipping", [
184+
rootId.toString(),
185+
setId.toString(),
186+
]);
187+
return false;
188+
}
189+
190+
const root = new Root(rootEntityId);
191+
root.setId = setId;
192+
root.rootId = rootId;
193+
root.rawSize = rawSize;
194+
root.cid = pieceCidBytes;
195+
root.removed = false;
196+
root.createdAt = blockTimestamp;
197+
root.proofSet = proofSetEntityId;
198+
root.sampleKey = getRootSampleKey(rootEntityId);
199+
// ipfsRootCID: patched in FWSS handler if applicable.
200+
root.save();
201+
202+
return true;
203+
}
204+
166205
export function handlePiecesAdded(event: PiecesAddedEvent): void {
167206
const setId = event.params.setId;
168207
const rootIdsFromEvent = event.params.pieceIds;
169208
const pieceCidsFromEvent = event.params.pieceCids;
170209

171-
const proofSet = DataSet.load(getProofSetEntityId(setId));
210+
const proofSetEntityId = getProofSetEntityId(setId);
211+
const proofSet = DataSet.load(proofSetEntityId);
172212
if (proofSet == null) {
173213
log.warning("handlePiecesAdded: DataSet {} not found", [setId.toString()]);
174214
return;
@@ -177,35 +217,48 @@ export function handlePiecesAdded(event: PiecesAddedEvent): void {
177217
let addedAny = false;
178218

179219
for (let i = 0; i < rootIdsFromEvent.length; i++) {
180-
const rootId = rootIdsFromEvent[i];
181-
const pieceCid = pieceCidsFromEvent[i];
182-
183-
const pieceBytes = pieceCid.data;
184-
const commPData = validateCommPv2(pieceBytes);
185-
const rawSize = commPData.isValid ? unpaddedSize(commPData.padding, commPData.height) : BigInt.zero();
186-
187-
const rootEntityId = getRootEntityId(setId, rootId);
188-
if (Root.load(rootEntityId) != null) {
189-
log.warning("handlePiecesAdded: Root {} for Set {} already exists; skipping", [
190-
rootId.toString(),
191-
setId.toString(),
192-
]);
193-
continue;
194-
}
220+
const created = createRootFromPieceCid(
221+
setId,
222+
rootIdsFromEvent[i],
223+
pieceCidsFromEvent[i].data,
224+
proofSetEntityId,
225+
event.block.timestamp,
226+
);
227+
if (created) addedAny = true;
228+
}
195229

196-
const root = new Root(rootEntityId);
197-
root.setId = setId;
198-
root.rootId = rootId;
199-
root.rawSize = rawSize;
200-
root.cid = pieceBytes;
201-
root.removed = false;
202-
root.createdAt = event.block.timestamp;
203-
root.proofSet = getProofSetEntityId(setId);
204-
root.sampleKey = getRootSampleKey(rootEntityId);
205-
// ipfsRootCID: patched in FWSS handler if applicable.
206-
root.save();
230+
// First non-empty add transitions the DataSet to READY. NextProvingPeriod
231+
// will then promote it to PROVING.
232+
if (addedAny && proofSet.status == DataSetStatus.EMPTY) {
233+
proofSet.status = DataSetStatus.READY;
234+
proofSet.save();
235+
}
236+
}
237+
238+
// Handles the compact PiecesAddedV2 event (PDPVerifier PR #300), which replaces the legacy
239+
// PiecesAdded for all new piece additions (PiecesAdded is kept only for historical logs). Piece
240+
// IDs are contiguous from firstPieceId, and each CID is packed as (header, root) instead of raw
241+
// bytes; a single addPieces call can emit several of these events once pieces are batched.
242+
export function handlePiecesAddedV2(event: PiecesAddedV2Event): void {
243+
const setId = event.params.setId;
244+
const firstPieceId = event.params.firstPieceId;
245+
const pieceCidsFromEvent = event.params.pieceCids;
246+
247+
const proofSetEntityId = getProofSetEntityId(setId);
248+
const proofSet = DataSet.load(proofSetEntityId);
249+
if (proofSet == null) {
250+
log.warning("handlePiecesAddedV2: DataSet {} not found", [setId.toString()]);
251+
return;
252+
}
253+
254+
let addedAny = false;
255+
256+
for (let i = 0; i < pieceCidsFromEvent.length; i++) {
257+
const rootId = firstPieceId.plus(BigInt.fromI32(i));
258+
const pieceCidBytes = reconstructCidFromPackedCid(pieceCidsFromEvent[i].header, pieceCidsFromEvent[i].root);
207259

208-
addedAny = true;
260+
const created = createRootFromPieceCid(setId, rootId, pieceCidBytes, proofSetEntityId, event.block.timestamp);
261+
if (created) addedAny = true;
209262
}
210263

211264
// First non-empty add transitions the DataSet to READY. NextProvingPeriod

apps/subgraph/subgraph.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ dataSources:
3232
handler: handleDataSetDeleted
3333
- event: PiecesAdded(indexed uint256,uint256[],(bytes)[])
3434
handler: handlePiecesAdded
35+
- event: PiecesAddedV2(indexed uint256,uint256,(bytes32,bytes32)[])
36+
handler: handlePiecesAddedV2
3537
- event: PiecesRemoved(indexed uint256,uint256[])
3638
handler: handlePiecesRemoved
3739
- event: DataSetEmpty(indexed uint256)

apps/subgraph/tests/pdp-verifier-utils.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
DataSetEmpty,
77
NextProvingPeriod,
88
PiecesAdded,
9+
PiecesAddedV2,
910
PossessionProven,
1011
} from "../generated/PDPVerifier/PDPVerifier";
1112

@@ -94,6 +95,55 @@ export function createRootsAddedEvent(
9495
return rootsAddedEvent;
9596
}
9697

98+
// header/root packing of the same CID used by createRootsAddedEvent
99+
// (0x01559120258ff7f7021387dcea7164b7d1c4a98bd6f8d3c187e3114795efa391df307c8aa9d5d5cbac03):
100+
// the header is right-aligned/zero-padded to 32 bytes, the trailing 32 bytes are the digest ("root").
101+
export const PACKED_CID_HEADER = "0x0000000000000000000000000000000000000000000001559120258ff7f70213";
102+
export const PACKED_CID_ROOT = "0x87dcea7164b7d1c4a98bd6f8d3c187e3114795efa391df307c8aa9d5d5cbac03";
103+
104+
export function createPiecesAddedV2Event(
105+
setId: BigInt,
106+
firstPieceId: BigInt,
107+
pieceCount: i32,
108+
sender: Address,
109+
contractAddress: Address,
110+
blockNumber: BigInt = BigInt.fromI32(1),
111+
timestamp: BigInt = BigInt.fromI32(1),
112+
txHash: Bytes = generateTxHash(6),
113+
logIndex: BigInt = BigInt.fromI32(0),
114+
): PiecesAddedV2 {
115+
const piecesAddedV2Event = changetype<PiecesAddedV2>(newMockEvent());
116+
117+
piecesAddedV2Event.parameters = [];
118+
piecesAddedV2Event.address = contractAddress;
119+
piecesAddedV2Event.transaction.from = sender;
120+
piecesAddedV2Event.transaction.to = contractAddress;
121+
122+
const setIdParam = new ethereum.EventParam("setId", ethereum.Value.fromUnsignedBigInt(setId));
123+
const firstPieceIdParam = new ethereum.EventParam("firstPieceId", ethereum.Value.fromUnsignedBigInt(firstPieceId));
124+
125+
const pieceCids: Array<ethereum.Tuple> = [];
126+
for (let i = 0; i < pieceCount; i++) {
127+
const packedCidTuple = new ethereum.Tuple();
128+
packedCidTuple.push(ethereum.Value.fromFixedBytes(Bytes.fromHexString(PACKED_CID_HEADER)));
129+
packedCidTuple.push(ethereum.Value.fromFixedBytes(Bytes.fromHexString(PACKED_CID_ROOT)));
130+
pieceCids.push(packedCidTuple);
131+
}
132+
133+
const pieceCidsParam = new ethereum.EventParam("pieceCids", ethereum.Value.fromTupleArray(pieceCids));
134+
135+
piecesAddedV2Event.parameters.push(setIdParam);
136+
piecesAddedV2Event.parameters.push(firstPieceIdParam);
137+
piecesAddedV2Event.parameters.push(pieceCidsParam);
138+
139+
piecesAddedV2Event.block.number = blockNumber;
140+
piecesAddedV2Event.block.timestamp = timestamp;
141+
piecesAddedV2Event.transaction.hash = txHash;
142+
piecesAddedV2Event.logIndex = logIndex;
143+
144+
return piecesAddedV2Event;
145+
}
146+
97147
export function createNextProvingPeriodEvent(
98148
setId: BigInt,
99149
challengeEpoch: BigInt,

0 commit comments

Comments
 (0)