mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo-db.git
synced 2026-09-21 16:52:02 -07:00
115 lines
3.0 KiB
JavaScript
115 lines
3.0 KiB
JavaScript
/*
|
|
Utility: backfill profile `seen` timestamps from on-chain block header times.
|
|
|
|
Reads each profile's txid, looks up blockHeight in ptxs, fetches block header
|
|
time via BCH RPC, and writes seen = block.time * 1000 (Unix ms).
|
|
|
|
Run from the psf-memo-db repo root (memo-db must be stopped to avoid lock conflicts):
|
|
node util/profiles/backfill_seen.js
|
|
|
|
Environment (same as psf-memo-indexer):
|
|
RPC_IP, RPC_PORT, RPC_USER, RPC_PASS
|
|
*/
|
|
|
|
import 'dotenv/config'
|
|
import level from 'level'
|
|
import * as url from 'url'
|
|
|
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
|
|
|
const rpcIp = process.env.RPC_IP || '172.17.0.1'
|
|
const rpcPort = process.env.RPC_PORT || '8332'
|
|
const rpcUser = process.env.RPC_USER || 'bitcoin'
|
|
const rpcPass = process.env.RPC_PASS || 'password'
|
|
|
|
const profilesDb = level(`${__dirname}/../../leveldb/current/profiles`, {
|
|
valueEncoding: 'json'
|
|
})
|
|
|
|
const ptxsDb = level(`${__dirname}/../../leveldb/current/ptxs`, {
|
|
valueEncoding: 'json'
|
|
})
|
|
|
|
const blockTimeCache = new Map()
|
|
|
|
async function rpcCall (method, params = []) {
|
|
const response = await fetch(`http://${rpcIp}:${rpcPort}/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Basic ${Buffer.from(`${rpcUser}:${rpcPass}`).toString('base64')}`
|
|
},
|
|
body: JSON.stringify({
|
|
jsonrpc: '1.0',
|
|
id: method,
|
|
method,
|
|
params
|
|
})
|
|
})
|
|
|
|
const body = await response.json()
|
|
if (body.error) {
|
|
throw new Error(body.error.message || `RPC ${method} failed`)
|
|
}
|
|
return body.result
|
|
}
|
|
|
|
async function getBlockTimeMs (blockHeight) {
|
|
if (blockTimeCache.has(blockHeight)) {
|
|
return blockTimeCache.get(blockHeight)
|
|
}
|
|
|
|
const hash = await rpcCall('getblockhash', [blockHeight])
|
|
const header = await rpcCall('getblockheader', [hash, true])
|
|
const seen = header.time * 1000
|
|
blockTimeCache.set(blockHeight, seen)
|
|
return seen
|
|
}
|
|
|
|
async function backfillProfiles () {
|
|
let updated = 0
|
|
let skipped = 0
|
|
let errors = 0
|
|
|
|
try {
|
|
for await (const [addr, profile] of profilesDb.iterator()) {
|
|
try {
|
|
if (!profile.txid) {
|
|
skipped++
|
|
continue
|
|
}
|
|
|
|
const ptx = await ptxsDb.get(profile.txid)
|
|
if (!ptx || ptx.blockHeight === undefined) {
|
|
console.warn(`No ptx blockHeight for ${addr} txid ${profile.txid}`)
|
|
skipped++
|
|
continue
|
|
}
|
|
|
|
const seen = await getBlockTimeMs(ptx.blockHeight)
|
|
if (profile.seen === seen) {
|
|
skipped++
|
|
continue
|
|
}
|
|
|
|
await profilesDb.put(addr, { ...profile, seen })
|
|
updated++
|
|
console.log(`${addr}: seen ${profile.seen} -> ${seen} (block ${ptx.blockHeight})`)
|
|
} catch (err) {
|
|
errors++
|
|
console.error(`Error updating ${addr}:`, err.message)
|
|
}
|
|
}
|
|
|
|
console.log(`\nBackfill complete. Updated: ${updated}, skipped: ${skipped}, errors: ${errors}`)
|
|
} finally {
|
|
await profilesDb.close()
|
|
await ptxsDb.close()
|
|
}
|
|
}
|
|
|
|
backfillProfiles().catch((err) => {
|
|
console.error('Backfill failed:', err.message)
|
|
process.exit(1)
|
|
})
|