Per-order terminal reorg linkage
This commit is contained in:
@@ -22,7 +22,7 @@ import { CUSTODY_POLICY, custodyMessage, type CustodyAction, type CustodyCommand
|
||||
type CustodyListing, type CustodyOrder, type CustodyWithdrawal } from '../../shared/custody.ts';
|
||||
import { NETWORK } from '../../shared/network.ts';
|
||||
import { CHAIN_ID, GENESIS_HASH, address, sha256 } from '../../server/testnet/consensus.ts';
|
||||
import { saleContext, termsForCustodyOrder, saleId } from '../../server/testnet/sale-candidate.ts';
|
||||
import { saleContext, termsForCustodyOrder, saleCandidateTransactionId, saleId } from '../../server/testnet/sale-candidate.ts';
|
||||
import { SALE_CANDIDATE, saleTermsMessage, saleCompletionMessage, saleCancellationMessage,
|
||||
type SaleTransaction } from '../../shared/sale-candidate.ts';
|
||||
import { replayReservedSale, type SaleRehearsal } from '../../server/market/sale-rehearsal.ts';
|
||||
@@ -78,7 +78,7 @@ function canonicalPage(streamId: string, all: CanonicalInboxPage['events'], star
|
||||
const anchor = events.at(-1)?.eventHash ?? previousAnchor;
|
||||
return { streamId, previousAnchor, anchor, events, cursor, head: all.length, caughtUp: cursor === all.length };
|
||||
}
|
||||
function canonicalEvents(streamId: string) {
|
||||
function canonicalEvents(streamId: string, linkedTxId = '1'.repeat(64)) {
|
||||
const rows: CanonicalInboxPage['events'] = [];
|
||||
let anchor = GENESIS_HASH;
|
||||
const add = (type: 'connect' | 'disconnect', height: number, hash: string, previousHash: string, transactionIds: string[] = []) => {
|
||||
@@ -88,8 +88,8 @@ function canonicalEvents(streamId: string) {
|
||||
rows.push({ sequence, type, height, hash, previousHash, transactionIds, eventHash }); anchor = eventHash;
|
||||
};
|
||||
const a = 'a'.repeat(64), b = 'b'.repeat(64), c = 'c'.repeat(64);
|
||||
add('connect', 1, a, GENESIS_HASH, ['1'.repeat(64)]);
|
||||
add('disconnect', 1, a, GENESIS_HASH, ['1'.repeat(64)]);
|
||||
add('connect', 1, a, GENESIS_HASH, [linkedTxId]);
|
||||
add('disconnect', 1, a, GENESIS_HASH, [linkedTxId]);
|
||||
add('connect', 1, b, GENESIS_HASH, ['2'.repeat(64)]);
|
||||
add('connect', 2, c, b, ['3'.repeat(64)]);
|
||||
return rows;
|
||||
@@ -631,6 +631,32 @@ test('signed candidate completion settles actual PostgreSQL balances once across
|
||||
} finally { await a.close(); await b.close(); }
|
||||
});
|
||||
|
||||
test('disconnect of the exact posted candidate terminal transaction commits an order-linked review hold', async t => {
|
||||
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
|
||||
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '100000000000', feeBps: 250 }) as CustodyListing;
|
||||
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
|
||||
const s = saleTranscript(o, f.market.id), transcript = { ...s.input, blocks: [[s.input.lock], [], [s.complete], [], []] };
|
||||
const terminalTxId = saleCandidateTransactionId(s.complete);
|
||||
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
|
||||
const observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
|
||||
const streamId = randomUUID(), events = canonicalEvents(streamId, terminalTxId);
|
||||
try {
|
||||
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'settle');
|
||||
const credited = await f.activity(seller);
|
||||
assert.equal(credited.availableAtomicXmr, '96500000000');
|
||||
const first = await observer.observeCanonicalEvents(canonicalPage(streamId, events, 0, 1));
|
||||
assert.equal(first.cursor, 1);
|
||||
await assert.rejects(observer.observeCanonicalEvents(canonicalPage(streamId, events, 1, 1)), /posted-sale-terminal-disconnected/);
|
||||
await assert.rejects(observer.observeCanonicalEvents(canonicalPage(streamId, events, 1, 1)), /hold|incident unresolved/);
|
||||
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, 1);
|
||||
assert.equal((await f.activity(seller)).availableAtomicXmr, credited.availableAtomicXmr);
|
||||
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.journals WHERE id=$1", [`settle:${o.id}`])).rows[0].n, 1);
|
||||
const incident = await f.admin.query("SELECT value FROM wk_custody.custody_meta WHERE key='integrity_incident_v1'");
|
||||
assert.match(incident.rows[0].value, /posted-sale-terminal-disconnected/);
|
||||
await assert.rejects(f.execute(other, { type: 'mock-credit' }), /incident is unresolved/);
|
||||
} finally { await observer.close(); await rehearsal.close(); }
|
||||
});
|
||||
|
||||
test('candidate cancellation and withheld-lock expiry release actual reservations only after depth', async t => {
|
||||
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
|
||||
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
|
||||
|
||||
Reference in New Issue
Block a user