Skip to content

Commit

Permalink
fix(swing-store): Delete transcript spans in stopUsingTranscript as i…
Browse files Browse the repository at this point in the history
…n rollover (#10060)

Fixes #10054

## Description
The first commits from #10055, [by request](#10055 (review)), culminating in a fix of #10054 by introducing a `closeSpan` helper to support both span rollover and `stopUsingTranscript`.

### Security Considerations
None known.

### Scaling Considerations
If anything, a negligible reduction in transcriptStore size.

### Documentation Considerations
None known.

### Testing Considerations
Covered by unit tests.

### Upgrade Considerations
This is kernel code that can be used at any node restart (i.e., because the configuration is consensus-independent, it doesn't even need to wait for a chain software upgrade).
  • Loading branch information
mergify[bot] authored Sep 11, 2024
2 parents bf1e645 + 2d1e478 commit 914bee2
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 164 deletions.
26 changes: 26 additions & 0 deletions packages/internal/tools/ava-assertions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Assert that the contents of `array` are
* [like]{@link https://github.com/avajs/ava/blob/main/docs/03-assertions.md#likeactual-selector-message}
* those of `expected`, including having matching lengths, with pretty diffs in
* case of mismatch.
*
* @param {import('ava').ExecutionContext} t
* @param {unknown[]} array
* @param {unknown[]} expected
* @param {string} [message]
*/
export const arrayIsLike = (t, array, expected, message) => {
const actualLength = array.length;
const expectedLength = expected.length;
const actualExcess = actualLength - expectedLength;
const comparable =
actualExcess > 0
? [...expected, ...Array.from({ length: actualExcess })]
: expected;
t.like(array, comparable, message);

if (actualLength === expectedLength) return;

const extended = [...array, ...Array.from({ length: -actualExcess })];
t.deepEqual(extended, array, message);
};
93 changes: 54 additions & 39 deletions packages/swing-store/src/transcriptStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,12 @@ export function makeTranscriptStore(
*/
function initTranscript(vatID) {
ensureTxn();
const initialIncarnation = 0;
sqlWriteSpan.run(vatID, 0, 0, initialHash, 1, initialIncarnation);
const newRec = spanRec(vatID, 0, 0, initialHash, true, 0);
noteExport(spanMetadataKey(newRec), JSON.stringify(newRec));
const pos = 0;
const isCurrent = 1;
const incarnation = 0;
sqlWriteSpan.run(vatID, pos, pos, initialHash, isCurrent, incarnation);
const rec = spanRec(vatID, pos, pos, initialHash, isCurrent, incarnation);
noteExport(spanMetadataKey(rec), JSON.stringify(rec));
}

const sqlGetCurrentSpanBounds = db.prepare(`
Expand Down Expand Up @@ -255,32 +257,27 @@ export function makeTranscriptStore(
WHERE vatID = ? AND position >= ? AND position < ?
`);

function doSpanRollover(vatID, isNewIncarnation) {
/**
* Finalize a span, setting isCurrent to null, marking the resulting record
* for export, and effecting disk archival and database cleanup as
* configured.
* Note that creation of a new DB row and removal/replacement of the
* transcript.${vatID}.current export record are responsibility of the caller.
*
* @param {string} vatID
* @param {ReturnType<getCurrentSpanBounds>} bounds
*/
function closeSpan(vatID, bounds) {
ensureTxn();
const { hash, startPos, endPos, incarnation } = getCurrentSpanBounds(vatID);
const { startPos, endPos, hash, incarnation } = bounds;
const rec = spanRec(vatID, startPos, endPos, hash, false, incarnation);

// add a new record for the now-old span
// add a new export record for the now-old span
noteExport(spanMetadataKey(rec), JSON.stringify(rec));

// and change its DB row to isCurrent=0
// and change its DB row to isCurrent=null
sqlEndCurrentSpan.run(vatID);

// create a new (empty) row, with isCurrent=1
const incarnationToUse = isNewIncarnation ? incarnation + 1 : incarnation;
sqlWriteSpan.run(vatID, endPos, endPos, initialHash, 1, incarnationToUse);

// overwrite the transcript.${vatID}.current record with new span
const newRec = spanRec(
vatID,
endPos,
endPos,
initialHash,
true,
incarnationToUse,
);
noteExport(spanMetadataKey(newRec), JSON.stringify(newRec));

if (!keepTranscripts) {
// Delete items of the previously-current span.
// There may still be items associated with even older spans, but we leave
Expand All @@ -290,7 +287,32 @@ export function makeTranscriptStore(
// that doesn't include them.
sqlDeleteOldItems.run(vatID, startPos, endPos);
}
return incarnationToUse;
}

function doSpanRollover(vatID, isNewIncarnation) {
ensureTxn();
const bounds = getCurrentSpanBounds(vatID);
const { endPos, incarnation } = bounds;

// deal with the now-old span
closeSpan(vatID, bounds);

// create a new (empty) DB row, with isCurrent=1
const newSpanIncarnation = isNewIncarnation ? incarnation + 1 : incarnation;
sqlWriteSpan.run(vatID, endPos, endPos, initialHash, 1, newSpanIncarnation);

// overwrite the transcript.${vatID}.current record with new span
const rec = spanRec(
vatID,
endPos,
endPos,
initialHash,
true,
newSpanIncarnation,
);
noteExport(spanMetadataKey(rec), JSON.stringify(rec));

return newSpanIncarnation;
}

/**
Expand Down Expand Up @@ -360,28 +382,21 @@ export function makeTranscriptStore(
`);

/**
* Prepare for vat deletion by marking the isCurrent span as not
* current. Idempotent.
* Prepare for vat deletion by marking the isCurrent=1 span as not current.
* Idempotent.
*
* @param {string} vatID The vat being terminated/deleted.
*/
function stopUsingTranscript(vatID) {
ensureTxn();
// this transforms the current span into a (short) historical one
// (basically doSpanRollover without adding replacement data)
const bounds = sqlGetCurrentSpanBounds.get(vatID);
if (bounds) {
// add a new record for the now-old span
const { startPos, endPos, hash, incarnation } = bounds;
const rec = spanRec(vatID, startPos, endPos, hash, false, incarnation);
noteExport(spanMetadataKey(rec), JSON.stringify(rec));
if (!bounds) return;

// and change its DB row to isCurrent=0
sqlEndCurrentSpan.run(vatID);
// deal with the now-old span
closeSpan(vatID, bounds);

// remove the transcript.${vatID}.current record
noteExport(spanMetadataKey({ vatID, isCurrent: true }), undefined);
}
// remove the transcript.${vatID}.current record
noteExport(spanMetadataKey({ vatID, isCurrent: true }), undefined);
}

/**
Expand Down Expand Up @@ -542,7 +557,7 @@ export function makeTranscriptStore(
}
}
} else if (artifactMode === 'archival') {
// every span for all vatIDs that have an isCurrent span (to
// every span for all vatIDs that have an isCurrent=1 span (to
// ignore terminated/partially-deleted vats)
const vatIDs = new Set();
for (const { vatID } of sqlGetCurrentSpanMetadata.iterate()) {
Expand Down
Loading

0 comments on commit 914bee2

Please sign in to comment.