OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / seed-ack-order.test.js

158 lines · javascript ref:
import test from 'brittle'
import b4a from 'b4a'
import { RelayNode } from '../../packages/core/core/relay-node/index.js'
import { BareRelay } from '../../packages/core/core/relay-node/bare-relay.js'

const STORAGE_BOUND = 1024 * 1024

function deferred () {
  const state = {}
  const promise = new Promise((resolve, reject) => {
    state.resolve = resolve
    state.reject = reject
  })
  return { promise, ...state }
}

async function turn () {
  await Promise.resolve()
  await Promise.resolve()
}

function message ({ discoveryKeys = [] } = {}) {
  return {
    appKey: b4a.alloc(32, 1),
    publisherPubkey: b4a.alloc(32, 2),
    discoveryKeys,
    replicationFactor: 1,
    maxStorageBytes: STORAGE_BOUND,
    ttlSeconds: 60,
    bountyRate: 0,
    revocable: true,
    unseedFreezeMs: 0,
    durability: 0
  }
}

function protocol (events) {
  return {
    acceptSeedRequest () { events.push('accept') },
    denySeedRequest (channel, appKey, reason) { events.push('deny:' + reason) }
  }
}

function nodeHarness ({ appPromise, corePromise, events }) {
  return {
    config: { regions: ['test'], acceptMode: 'open' },
    seeder: {
      cores: new Map(),
      async seedCore () {
        events.push('core-start')
        return corePromise
      },
      async unseedCore () { events.push('core-rollback') }
    },
    swarm: { keyPair: { publicKey: b4a.alloc(32, 3) } },
    _seedProtocol: protocol(events),
    _resolveAcceptMode: () => 'open',
    _decideAcceptance: () => 'accept',
    _storageAdmission: () => ({ allowed: true, availableBytes: STORAGE_BOUND }),
    leaseManager: null,
    seedingRegistry: null,
    async seedApp () {
      events.push('app-start')
      return appPromise
    },
    async unseedApp () { events.push('app-rollback') },
    emit (name) { events.push('emit:' + name) }
  }
}

function bareHarness ({ appPromise, corePromise, events }) {
  return {
    config: { enableSeeding: true, regions: ['test'], acceptMode: 'open', acceptAllowlist: [] },
    seeder: {
      cores: new Map(),
      async seedCore () {
        events.push('core-start')
        return corePromise
      },
      async unseedCore () { events.push('core-rollback') }
    },
    appLifecycle: {
      async seedApp () {
        events.push('app-start')
        return appPromise
      },
      async unseedApp () { events.push('app-rollback') }
    },
    swarm: { keyPair: { publicKey: b4a.alloc(32, 3) } },
    _seedProtocol: protocol(events),
    _storageAdmission: () => ({ allowed: true, availableBytes: STORAGE_BOUND }),
    emit (name) { events.push('emit:' + name) }
  }
}

for (const runtime of [
  { name: 'Node', handler: RelayNode.prototype._onSeedRequest, harness: nodeHarness },
  { name: 'Bare', handler: BareRelay.prototype._onSeedRequest, harness: bareHarness }
]) {
  test(`${runtime.name} seed ACK follows durable drive and extra-core completion`, async (t) => {
    const events = []
    const app = deferred()
    const core = deferred()
    const extraCore = b4a.alloc(32, 4)
    const relay = runtime.harness({ appPromise: app.promise, corePromise: core.promise, events })

    const pending = runtime.handler.call(relay, message({ discoveryKeys: [extraCore] }), {})
    t.alike(events, ['app-start'])

    app.resolve({ alreadySeeded: false })
    await turn()
    t.alike(events, ['app-start', 'core-start'])
    t.absent(events.find(event => event === 'accept'), 'drive persistence alone cannot ACK the full request')

    core.resolve({})
    await pending
    const acceptIndex = events.indexOf('accept')
    t.ok(acceptIndex > events.indexOf('core-start'))
    t.is(events[acceptIndex + 1], 'emit:seed-accepted', 'success is emitted only after the wire ACK')
  })

  test(`${runtime.name} seed durability failure denies without a false ACK`, async (t) => {
    const events = []
    const app = Promise.resolve({ alreadySeeded: false })
    const failure = new Error('injected durable core persistence failure')
    const core = Promise.reject(failure)
    // The handler attaches its rejection path in the same turn.
    core.catch(() => {})
    const relay = runtime.harness({ appPromise: app, corePromise: core, events })

    await runtime.handler.call(relay, message({ discoveryKeys: [b4a.alloc(32, 5)] }), {})

    t.absent(events.find(event => event === 'accept'))
    t.ok(events.includes('app-rollback'), 'partially accepted drive is durably rolled back')
    t.ok(events.includes('deny:storage-admission-blocked'))
    t.ok(events.indexOf('app-rollback') < events.indexOf('deny:storage-admission-blocked'), 'rollback finishes before denial')
  })

  test(`${runtime.name} post-ACK observer failure cannot revoke durable acceptance`, async (t) => {
    const events = []
    const relay = runtime.harness({
      appPromise: Promise.resolve({ alreadySeeded: false }),
      corePromise: Promise.resolve({}),
      events
    })
    relay.emit = (name) => {
      events.push('emit:' + name)
      if (name === 'seed-accepted') throw new Error('injected observer failure')
    }

    await runtime.handler.call(relay, message(), {})

    t.ok(events.includes('accept'))
    t.absent(events.find(event => event.endsWith('-rollback')))
    t.absent(events.find(event => event.startsWith('deny:')))
  })
}