OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / test / overlay-blob-store.test.mjs

106 lines · javascript ref:
// OFFLINE PROOF (bare) — pillar 1b: atproto blobs/images as REAL content-addressed FILES, deduped +
// distributed by hyper-overlay, instead of a Hyperbee-of-bytes balloon.
//
// Proves: (1) put stores the bytes as a FILE on disk (the bee holds only tiny metadata, not the
// bytes); (2) get(cid) returns identical bytes + mime (drop-in for BlobStore); (3) content-addressed
// idempotence (same bytes -> same cid -> one file); (4) cross-node byte transfer — a second overlay
// fetches the blob bytes by oid over a SecretStream pair (the distribution OverlayBlobStore.get uses
// when the local file is absent). hyper-overlay is bare-only, and the testnet DHT does not connect
// under this bare, so cross-node uses a direct SecretStream pair (proven to carry replication here).
//
// Run: bare test/overlay-blob-store.test.mjs
import '../lib/bare-globals.js'
import process from 'bare-process'
import fs from 'bare-fs'
import os from 'bare-os'
import path from 'bare-path'
import crypto from 'hypercore-crypto'
import Protomux from 'protomux'
import SecretStream from '@hyperswarm/secret-stream'
import { HyperOverlay } from 'hyper-overlay'
import { OverlayBlobStore } from 'atproto-store/lib/overlay-blob-store.js'
import { openCorestore } from 'atproto-store/lib/corestore.js' // atproto-store's OWN corestore copy — instanceof-safe

let failed = 0
const dirs = []
const ok = (c, m) => { if (c) console.log('  ok   -', m); else { console.error('  FAIL -', m); failed++ } }
const tmp = (l) => { const d = path.join(os.tmpdir(), l + '-' + crypto.randomBytes(6).toString('hex')); fs.mkdirSync(d, { recursive: true }); return d }
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const eq = (a, b) => a.length === b.length && b4aEqual(a, b)
function b4aEqual (a, b) { for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true }

// ---- (1)-(3) LOCAL: storage as files, dedup, drop-in API ----
{
  const storeDir = tmp('blob-store'); dirs.push(storeDir)
  const blobRoot = tmp('blob-files'); dirs.push(blobRoot)
  const store = openCorestore(storeDir)
  const overlay = new HyperOverlay(store, { namespace: 'blob-ov', destDir: tmp('blob-dest') })
  await overlay.ready()
  const blobs = await new OverlayBlobStore(blobRoot, { overlay, indexStore: store }).ready()

  const img = crypto.randomBytes(200000) // 200KB "image"
  const put = await blobs.put(img, 'image/png')
  ok(put.cid && put.size === 200000 && put.mimeType === 'image/png', 'put returns { cid, mimeType, size } (drop-in shape)')

  const cidStr = put.cid.toString()
  // (1) bytes are a FILE on disk, NOT bee-of-bytes
  const onDisk = fs.existsSync(path.join(blobRoot, cidStr))
  ok(onDisk, 'blob bytes stored as a real FILE on disk (blobRoot/<cid>)')
  const beeVal = (await blobs.bee.get(cidStr)).value
  ok(beeVal.length < 200, `bee holds only tiny metadata (${beeVal.length} bytes), not the 200KB blob — no balloon`)

  // (2) get roundtrip
  const got = await blobs.get(cidStr)
  ok(got && got.mimeType === 'image/png' && got.size === 200000 && eq(got.bytes, img), 'get(cid) returns identical bytes + mime')
  ok(await blobs.has(cidStr), 'has(cid) is true')
  ok((await blobs.get('bafk=nonexistent')) === null || !(await blobs.has('bafkreinope')), 'get/has of an unknown cid is null/false')

  // (3) content-addressed idempotence
  const put2 = await blobs.put(img, 'image/png')
  ok(put2.cid.toString() === cidStr, 'same bytes -> same cid (content-addressed, idempotent)')

  await blobs.close(); await overlay.close(); await store.close()
}

// ---- (4) CROSS-NODE: a second overlay fetches the blob bytes by oid over a SecretStream pair ----
{
  const storeA = openCorestore(tmp('xn-A')); const storeB = openCorestore(tmp('xn-B'))
  const ovA = new HyperOverlay(storeA, { namespace: 'blob-ov', destDir: tmp('xn-A-dest') })
  const ovB = new HyperOverlay(storeB, { namespace: 'blob-ov', destDir: tmp('xn-B-dest') })
  await ovA.ready(); await ovB.ready()

  // A stores a blob as a file + registers it (gets the overlay oid)
  const blobRootA = tmp('xn-A-blobs')
  const img = crypto.randomBytes(300000)
  const cid = 'bafkreicrossnodeblob'
  const diskA = path.join(blobRootA, cid)
  fs.writeFileSync(diskA, img)
  const reg = await ovA.registerFile('/blobs/' + cid, diskA, { size: img.length })
  const oid = reg && reg.contentHash
  ok(!!oid, 'A registered the blob file -> overlay oid (blake2b content hash)')

  // Connect A<->B over a direct SecretStream pair; both attach the overlay content protocol
  const sa = new SecretStream(true); const sb = new SecretStream(false)
  sa.rawStream.pipe(sb.rawStream).pipe(sa.rawStream)
  ovA.attachProtocol(Protomux.from(sa))
  ovB.attachProtocol(Protomux.from(sb))
  await sleep(500)

  // B fetches the bytes by oid (the distribution OverlayBlobStore.get uses when local file is absent)
  const destB = path.join(tmp('xn-B-fetch'), cid)
  let fetched = null
  for (let i = 0; i < 20; i++) {
    const r = await ovB.fetchFile(oid, { destPath: destB }).catch(() => null)
    if (r) { fetched = fs.readFileSync(destB); break }
    await sleep(500)
  }
  ok(fetched && eq(fetched, img), 'B fetched the blob bytes by oid over the connection (cross-node distribution)')

  try { sa.destroy(); sb.destroy() } catch {}
  await ovA.close(); await ovB.close(); await storeA.close(); await storeB.close()
}

for (const d of dirs) { try { fs.rmSync(d, { recursive: true, force: true }) } catch {} }
console.log(failed ? `\nOVERLAY-BLOB: ${failed} FAILED` : '\nOVERLAY-BLOB OK: blobs are deduped files (no balloon); get-by-cid roundtrips; bytes distribute cross-node by oid')
process.exit(failed ? 1 : 0)