Skip to content

Commit 9c29967

Browse files
NikolasHaimerljuliangruberNikolas Haimerl
authored
feat: add index provider library (#129)
* add library * add library * fmt * Update lib/miner-info.js Co-authored-by: Julian Gruber <julian@juliangruber.com> * changed naming * changed import naming * remove smart contract client files * print error message * lint * add abort signal * removed error message --------- Co-authored-by: Julian Gruber <julian@juliangruber.com> Co-authored-by: Nikolas Haimerl <nhaimerl@Nikolass-MacBook-Pro.local>
1 parent b98b08c commit 9c29967

File tree

7 files changed

+3035
-2206
lines changed

7 files changed

+3035
-2206
lines changed

deps.ts

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export { retry } from 'https://deno.land/std@0.203.0/async/retry.ts'
1212

1313
// Deno Bundle does not support npm dependencies, we have to load them via CDN
1414
export { ethers } from 'https://cdn.jsdelivr.net/npm/ethers@6.13.5/dist/ethers.min.js'
15+
export {
16+
getIndexProviderPeerId,
17+
MINER_TO_PEERID_CONTRACT_ADDRESS,
18+
MINER_TO_PEERID_CONTRACT_ABI,
19+
} from 'https://cdn.jsdelivr.net/npm/index-provider-peer-id@1.0.1/index.js/+esm'
1520
export { CarBlockIterator } from 'https://cdn.skypack.dev/@ipld/car@5.3.2/?dts'
1621
export {
1722
UnsupportedHashError,

lib/miner-info.js

+28-129
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,39 @@
1-
import { retry } from '../vendor/deno-deps.js'
21
import { RPC_URL, RPC_AUTH } from './constants.js'
3-
import { getIndexProviderPeerIdFromSmartContract } from './smart-contract-client.js'
2+
import {
3+
getIndexProviderPeerId as getPeerId,
4+
MINER_TO_PEERID_CONTRACT_ADDRESS,
5+
MINER_TO_PEERID_CONTRACT_ABI,
6+
ethers,
7+
} from '../vendor/deno-deps.js'
8+
9+
// Initialize contract
10+
const fetchRequest = new ethers.FetchRequest(RPC_URL)
11+
fetchRequest.setHeader('Authorization', `Bearer ${RPC_AUTH}`)
12+
const provider = new ethers.JsonRpcProvider(fetchRequest)
13+
const smartContractClient = new ethers.Contract(
14+
MINER_TO_PEERID_CONTRACT_ADDRESS,
15+
MINER_TO_PEERID_CONTRACT_ABI,
16+
provider,
17+
)
418

519
/**
6-
* @param {object} options
7-
* @param {number} [options.maxAttempts]
8-
* @param {function} [options.rpcFn]
9-
* @returns {Promise<string>} The chain head Cid
20+
* @param {string} minerId - The ID of the miner.
21+
* @param {object} options - Options for the function.
22+
* @param {number} options.maxAttempts - The maximum number of attempts to fetch the peer ID.
23+
* @returns {Promise<string>} The peer ID of the miner.
1024
*/
11-
async function getChainHead({ maxAttempts = 5, rpcFn } = {}) {
25+
export async function getIndexProviderPeerId(minerId, { maxAttempts = 5 } = {}) {
1226
try {
13-
const res = await retry(() => (rpcFn ?? rpc)('Filecoin.ChainHead'), {
14-
// The maximum amount of attempts until failure.
27+
const { peerId, source } = await getPeerId(minerId, smartContractClient, {
28+
rpcUrl: RPC_URL,
29+
rpcAuth: RPC_AUTH,
1530
maxAttempts,
16-
// The initial and minimum amount of milliseconds between attempts.
17-
minTimeout: 5_000,
18-
// How much to backoff after each retry.
19-
multiplier: 1.5,
31+
signal: AbortSignal.timeout(60_000),
2032
})
21-
return res.Cids
33+
console.log(`Peer ID fetched from ${source}.`)
34+
return peerId
2235
} catch (err) {
23-
if (err.name === 'RetryError' && err.cause) {
24-
// eslint-disable-next-line no-ex-assign
25-
err = err.cause
26-
}
27-
err.message = `Cannot obtain chain head: ${err.message}`
36+
console.error(err)
2837
throw err
2938
}
3039
}
31-
32-
/**
33-
* @param {string} minerId A miner actor id, e.g. `f0142637`
34-
* @param {object} options
35-
* @param {number} [options.maxAttempts]
36-
* @returns {Promise<string>} Miner's PeerId, e.g. `12D3KooWMsPmAA65yHAHgbxgh7CPkEctJHZMeM3rAvoW8CZKxtpG`
37-
*/
38-
export async function getIndexProviderPeerId(
39-
minerId,
40-
{ maxAttempts = 5, smartContract, rpcFn } = {},
41-
) {
42-
try {
43-
// Make a concurrent request to both sources: FilecoinMinerInfo and smart contract
44-
const [minerInfoResult, contractResult] = await Promise.all([
45-
getIndexProviderPeerIdFromFilecoinMinerInfo(minerId, { maxAttempts, rpcFn }),
46-
getIndexProviderPeerIdFromSmartContract(minerId, { smartContract }),
47-
])
48-
// Check contract result first
49-
if (contractResult) {
50-
console.log('Using PeerID from the smart contract.')
51-
return contractResult
52-
}
53-
54-
// Fall back to FilecoinMinerInfo result
55-
if (minerInfoResult) {
56-
console.log('Using PeerID from FilecoinMinerInfo.')
57-
return minerInfoResult
58-
}
59-
60-
// Handle the case where both failed
61-
throw new Error(
62-
`Failed to obtain Miner's Index Provider PeerID.\nSmartContract query result: ${contractResult}\nStateMinerInfo query result: ${minerInfoResult}`,
63-
)
64-
} catch (error) {
65-
console.error('Error fetching PeerID:', error)
66-
throw Error(`Error fetching PeerID for miner ${minerId}.`, {
67-
cause: error,
68-
})
69-
}
70-
}
71-
72-
/**
73-
* @param {string} minerId A miner actor id, e.g. `f0142637`
74-
* @param {object} options
75-
* @param {number} [options.maxAttempts]
76-
* @param {function} [options.rpcFn]
77-
* @returns {Promise<string>} Miner's PeerId, e.g. `12D3KooWMsPmAA65yHAHgbxgh7CPkEctJHZMeM3rAvoW8CZKxtpG`
78-
*/
79-
export async function getIndexProviderPeerIdFromFilecoinMinerInfo(
80-
minerId,
81-
{ maxAttempts = 5, rpcFn } = {},
82-
) {
83-
const chainHead = await getChainHead({ maxAttempts, rpcFn })
84-
try {
85-
const res = await retry(() => (rpcFn ?? rpc)('Filecoin.StateMinerInfo', minerId, chainHead), {
86-
// The maximum amount of attempts until failure.
87-
maxAttempts,
88-
// The initial and minimum amount of milliseconds between attempts.
89-
minTimeout: 5_000,
90-
// How much to backoff after each retry.
91-
multiplier: 1.5,
92-
})
93-
return res.PeerId
94-
} catch (err) {
95-
if (err.name === 'RetryError' && err.cause) {
96-
// eslint-disable-next-line no-ex-assign
97-
err = err.cause
98-
}
99-
err.message = `Cannot obtain miner info for ${minerId}: ${err.message}`
100-
throw err
101-
}
102-
}
103-
104-
/**
105-
* @param {string} method
106-
* @param {unknown[]} params
107-
*/
108-
async function rpc(method, ...params) {
109-
const req = new Request(RPC_URL, {
110-
method: 'POST',
111-
headers: {
112-
'content-type': 'application/json',
113-
accepts: 'application/json',
114-
authorization: `Bearer ${RPC_AUTH}`,
115-
},
116-
body: JSON.stringify({
117-
jsonrpc: '2.0',
118-
id: 1,
119-
method,
120-
params,
121-
}),
122-
})
123-
const res = await fetch(req, {
124-
signal: AbortSignal.timeout(60_000),
125-
})
126-
127-
if (!res.ok) {
128-
throw new Error(`JSON RPC failed with ${res.code}: ${(await res.text()).trimEnd()}`)
129-
}
130-
131-
const body = await res.json()
132-
if (body.error) {
133-
const err = new Error(body.error.message)
134-
err.name = 'FilecoinRpcError'
135-
err.code = body.code
136-
throw err
137-
}
138-
139-
return body.result
140-
}

lib/smart-contract-client.js

-69
This file was deleted.

test.js

-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ import './test/multiaddr.test.js'
55

66
import './test/integration.js'
77
import './test/spark.js'
8-
import './test/smart-contract-client.test.js'

test/miner-info.test.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { test } from 'zinnia:test'
2-
import { assertMatch, AssertionError } from 'zinnia:assert'
2+
import { assertMatch, AssertionError, assert, assertEquals } from 'zinnia:assert'
33
import { getIndexProviderPeerId } from '../lib/miner-info.js'
44

55
const KNOWN_MINER_ID = 'f0142637'
@@ -16,6 +16,15 @@ test('get peer id of a miner that does not exist', async () => {
1616
`Expected "getIndexProviderPeerId()" to fail, but it resolved with "${result}" instead.`,
1717
)
1818
} catch (err) {
19-
assertMatch(err.cause.toString(), /\bf010\b.*\bactor code is not miner/)
19+
assert(err instanceof Error, 'Expected error to be an instance of Error')
20+
assert(err.message.toString().includes('Error fetching index provider PeerID for miner f010'))
21+
assert(err.cause.toString().includes('Error fetching PeerID for miner f010'))
2022
}
2123
})
24+
25+
test('getIndexProviderPeerId returns correct peer id for miner f03303347', async () => {
26+
const peerId = await getIndexProviderPeerId('f03303347')
27+
28+
assert(typeof peerId === 'string', 'Expected peerId to be a string')
29+
assertEquals(peerId, '12D3KooWJ91c6xQshrNe7QAXPFAaeRrHWq2UrgXGPf8UmMZMwyZ5')
30+
})

0 commit comments

Comments
 (0)