Skip to content

Commit 35d68a7

Browse files
committed
test: move self-anchor integration test
1 parent e881c0a commit 35d68a7

3 files changed

Lines changed: 272 additions & 261 deletions

File tree

tests/networks/basic-rust.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ spec:
1616
CERAMIC_ONE_EVM_RPC_URL: "http://ganache:8545"
1717
CERAMIC_ONE_EVM_PRIVATE_KEY: "0000000000000000000000000000000000000000000000000000000000000001"
1818
CERAMIC_ONE_EVM_CHAIN_ID: "1337"
19+
CERAMIC_ONE_ANCHOR_INTERVAL: "20"
1920
CERAMIC_ONE_EVM_CONTRACT_ADDRESS: "0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC"
2021
CERAMIC_ONE_FLIGHT_SQL_BIND_ADDRESS: "0.0.0.0:5102"
2122
resourceLimits:
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import { beforeAll, describe, expect, test, jest } from '@jest/globals'
2+
import {
3+
type ClientOptions,
4+
type FlightSqlClient,
5+
createFlightSqlClient,
6+
} from '@ceramic-sdk/flight-sql-client'
7+
import { CeramicClient } from '@ceramic-sdk/http-client'
8+
import type { StreamID } from '@ceramic-sdk/identifiers'
9+
import { ModelClient } from '@ceramic-sdk/model-client'
10+
import { ModelInstanceClient } from '@ceramic-sdk/model-instance-client'
11+
import type { ModelDefinition } from '@ceramic-sdk/model-protocol'
12+
import { tableFromIPC } from 'apache-arrow'
13+
import { randomDID } from '../../../utils/didHelper'
14+
import { waitForEventState } from '../../../utils/rustCeramicHelpers'
15+
import { urlsToEndpoint, utilities } from '../../../utils/common'
16+
17+
const delayMs = utilities.delayMs
18+
19+
const CeramicUrls = String(process.env.CERAMIC_URLS).split(',')
20+
const CeramicFlightUrls = String(process.env.CERAMIC_FLIGHT_URLS).split(',')
21+
const CeramicFlightEndpoints = urlsToEndpoint(CeramicFlightUrls)
22+
23+
const FLIGHT_OPTIONS: ClientOptions = {
24+
headers: [],
25+
username: undefined,
26+
password: undefined,
27+
token: undefined,
28+
tls: false,
29+
host: CeramicFlightEndpoints[0].host,
30+
port: CeramicFlightEndpoints[0].port,
31+
}
32+
33+
// Self-anchoring should complete within 2 minutes (anchor interval + processing time)
34+
const ANCHOR_TIMEOUT_MS = 2 * 60 * 1000
35+
const POLL_INTERVAL_MS = 5000
36+
37+
// Expected chain ID for local Ganache network configured in basic-rust.yaml
38+
const EXPECTED_CHAIN_ID = 'eip155:1337'
39+
40+
const testModel: ModelDefinition = {
41+
version: '2.0',
42+
name: 'SelfAnchorTestModel',
43+
description: 'Model for testing self-anchoring',
44+
accountRelation: { type: 'list' },
45+
interface: false,
46+
implements: [],
47+
schema: {
48+
type: 'object',
49+
properties: {
50+
value: { type: 'integer' },
51+
},
52+
additionalProperties: false,
53+
},
54+
}
55+
56+
/**
57+
* Wait for an event to be anchored (chain_id populated) for a given stream.
58+
* Polls the event_states table for events that have chain_id set.
59+
*/
60+
async function waitForAnchoredEvent(
61+
flightClient: FlightSqlClient,
62+
streamCid: string,
63+
timeoutMs: number = ANCHOR_TIMEOUT_MS,
64+
): Promise<{ anchored: boolean; chainId: string | null }> {
65+
const startTime = Date.now()
66+
while (Date.now() - startTime < timeoutMs) {
67+
try {
68+
// Query for events that have been anchored (chain_id is not null)
69+
const buffer = await flightClient.query(
70+
`SELECT chain_id FROM event_states
71+
WHERE cid_string(stream_cid) = '${streamCid.toString()}'
72+
AND chain_id IS NOT NULL
73+
LIMIT 1`,
74+
)
75+
76+
const table = tableFromIPC(buffer)
77+
if (table.numRows > 0) {
78+
const row = table.get(0)
79+
const chainId = row?.chain_id as string | null
80+
console.log(`Found anchored event for stream with chain_id: ${chainId}`)
81+
return { anchored: true, chainId }
82+
}
83+
} catch (error) {
84+
console.log(`Query error (retrying): ${error}`)
85+
}
86+
87+
await delayMs(POLL_INTERVAL_MS)
88+
}
89+
90+
return { anchored: false, chainId: null }
91+
}
92+
93+
/**
94+
* Count anchored events for a given stream.
95+
*/
96+
async function countAnchoredEvents(
97+
flightClient: FlightSqlClient,
98+
streamCid: string,
99+
): Promise<number> {
100+
const buffer = await flightClient.query(
101+
`SELECT COUNT(*) as count FROM event_states
102+
WHERE cid_string(stream_cid) = '${streamCid}'
103+
AND chain_id IS NOT NULL`,
104+
)
105+
106+
const table = tableFromIPC(buffer)
107+
const row = table.get(0)
108+
return row ? Number(row.count) : 0
109+
}
110+
111+
describe('self-anchoring integration test', () => {
112+
jest.setTimeout(1000 * 60 * 10) // 10 minutes total test timeout
113+
114+
let flightClient: FlightSqlClient
115+
let client: CeramicClient
116+
let modelClient: ModelClient
117+
let modelInstanceClient: ModelInstanceClient
118+
let modelStream: StreamID
119+
120+
beforeAll(async () => {
121+
flightClient = await createFlightSqlClient(FLIGHT_OPTIONS)
122+
123+
client = new CeramicClient({
124+
url: CeramicUrls[0],
125+
})
126+
127+
modelClient = new ModelClient({
128+
ceramic: client,
129+
did: await randomDID(),
130+
})
131+
132+
modelInstanceClient = new ModelInstanceClient({
133+
ceramic: client,
134+
did: await randomDID(),
135+
})
136+
137+
// Create test model
138+
modelStream = await modelClient.createDefinition(testModel)
139+
await waitForEventState(flightClient, modelStream.cid)
140+
console.log(`Created test model: ${modelStream.toString()}`)
141+
}, 30000)
142+
143+
test(
144+
'document creation triggers self-anchoring',
145+
async () => {
146+
// Create a document
147+
console.log('Creating document...')
148+
const documentStream = await modelInstanceClient.createInstance({
149+
model: modelStream,
150+
content: { value: 42 },
151+
shouldIndex: true,
152+
})
153+
154+
// Wait for the init event to be processed
155+
await waitForEventState(flightClient, documentStream.commit)
156+
console.log(`Document created: ${documentStream.baseID.toString()}`)
157+
158+
// Get the stream CID for querying
159+
const streamCid = documentStream.baseID.cid.toString()
160+
console.log(`Waiting for anchored event for stream CID: ${streamCid}`)
161+
162+
// Wait for anchoring to complete
163+
const result = await waitForAnchoredEvent(flightClient, streamCid, ANCHOR_TIMEOUT_MS)
164+
165+
expect(result.anchored).toBe(true)
166+
expect(result.chainId).toBe(EXPECTED_CHAIN_ID)
167+
console.log(
168+
`Document successfully anchored via self-anchoring with chain_id: ${result.chainId}`,
169+
)
170+
},
171+
ANCHOR_TIMEOUT_MS + 30000,
172+
)
173+
174+
test(
175+
'document update triggers self-anchoring',
176+
async () => {
177+
// Create a document first
178+
console.log('Creating document...')
179+
const documentStream = await modelInstanceClient.createInstance({
180+
model: modelStream,
181+
content: { value: 1 },
182+
shouldIndex: true,
183+
})
184+
185+
await waitForEventState(flightClient, documentStream.commit)
186+
const streamCid = documentStream.baseID.cid.toString()
187+
188+
// Wait for initial anchor
189+
console.log('Waiting for initial anchor...')
190+
const initialResult = await waitForAnchoredEvent(flightClient, streamCid, ANCHOR_TIMEOUT_MS)
191+
expect(initialResult.anchored).toBe(true)
192+
expect(initialResult.chainId).toBe(EXPECTED_CHAIN_ID)
193+
194+
const initialCount = await countAnchoredEvents(flightClient, streamCid)
195+
console.log(`Initial anchored events count: ${initialCount}`)
196+
197+
// Update the document
198+
console.log('Updating document...')
199+
const updatedState = await modelInstanceClient.updateDocument({
200+
streamID: documentStream.baseID.toString(),
201+
newContent: { value: 2 },
202+
shouldIndex: true,
203+
})
204+
205+
await waitForEventState(flightClient, updatedState.commitID.commit)
206+
console.log('Document updated, waiting for anchor...')
207+
208+
// Wait for the update to be anchored (should result in more anchored events)
209+
const startTime = Date.now()
210+
let newCount = initialCount
211+
while (Date.now() - startTime < ANCHOR_TIMEOUT_MS) {
212+
newCount = await countAnchoredEvents(flightClient, streamCid)
213+
if (newCount > initialCount) {
214+
break
215+
}
216+
await delayMs(POLL_INTERVAL_MS)
217+
}
218+
219+
expect(newCount).toBeGreaterThan(initialCount)
220+
console.log(`Update anchored. Anchored events count: ${newCount}`)
221+
},
222+
ANCHOR_TIMEOUT_MS * 2 + 60000,
223+
)
224+
225+
test(
226+
'multiple documents are anchored',
227+
async () => {
228+
// Create multiple documents
229+
console.log('Creating 3 documents...')
230+
const docs = await Promise.all([
231+
modelInstanceClient.createInstance({
232+
model: modelStream,
233+
content: { value: 10 },
234+
shouldIndex: true,
235+
}),
236+
modelInstanceClient.createInstance({
237+
model: modelStream,
238+
content: { value: 20 },
239+
shouldIndex: true,
240+
}),
241+
modelInstanceClient.createInstance({
242+
model: modelStream,
243+
content: { value: 30 },
244+
shouldIndex: true,
245+
}),
246+
])
247+
248+
// Wait for all init events to be processed
249+
await Promise.all(docs.map((doc) => waitForEventState(flightClient, doc.commit)))
250+
console.log('All documents created')
251+
252+
// Wait for all to be anchored
253+
const streamCids = docs.map((doc) => doc.baseID.cid.toString())
254+
255+
console.log('Waiting for all documents to be anchored...')
256+
const anchorResults = await Promise.all(
257+
streamCids.map((cid) => waitForAnchoredEvent(flightClient, cid, ANCHOR_TIMEOUT_MS)),
258+
)
259+
260+
// Verify all were anchored with correct chain ID
261+
for (let i = 0; i < anchorResults.length; i++) {
262+
expect(anchorResults[i].anchored).toBe(true)
263+
expect(anchorResults[i].chainId).toBe(EXPECTED_CHAIN_ID)
264+
console.log(
265+
`Document ${i + 1} anchored: ${docs[i].baseID.toString()} with chain_id: ${anchorResults[i].chainId}`,
266+
)
267+
}
268+
},
269+
ANCHOR_TIMEOUT_MS + 60000,
270+
)
271+
})

0 commit comments

Comments
 (0)