Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/filler/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,19 +446,20 @@ export class ContractDB {
}
}

async getReaderPosition(): Promise<{ live: boolean, block_num: number, updated: number }> {
const query = await this.connection.database.query('SELECT live, block_num, updated FROM contract_readers WHERE name = $1', [this.name]);
// The live column is deliberately absent here. It is a one-way latch and the
// reader no longer derives its processing state from it, so returning it
// only invites that back. reconcile reads the column with its own query.
async getReaderPosition(): Promise<{ block_num: number, updated: number }> {
const query = await this.connection.database.query('SELECT block_num, updated FROM contract_readers WHERE name = $1', [this.name]);

if (query.rows.length === 0) {
return {
live: false,
block_num: 0,
updated: 0
};
}

return {
live: query.rows[0].live,
block_num: parseInt(query.rows[0].block_num, 10),
updated: parseInt(query.rows[0].updated, 10)
};
Expand Down
11 changes: 11 additions & 0 deletions src/filler/receiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,17 @@ describe('StateReceiver', () => {
return receiver;
}

it('starts in catchup even when the stored live flag is set', async () => {
// live is a one-way latch: it stays true from the first time a reader
// reached head, so a reader restarting far behind must ignore it and
// let the per-block head-distance check promote instead.
const receiver = createStartedReceiver(6_000_000);
await receiver.startProcessing();

const setState = (receiver as any).processor.setState;
expect(setState.calledOnceWith(ProcessingState.CATCHUP)).to.equal(true);
});

it('arms the irreversible floor from the checkpoint before the first block', async () => {
const receiver = createStartedReceiver(6_000_000);
await receiver.startProcessing();
Expand Down
10 changes: 9 additions & 1 deletion src/filler/receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,15 @@ export default class StateReceiver implements IShipConsumer {
position = await this.database.getReaderPosition();
}

this.processor.setState(position.live ? ProcessingState.HEAD : ProcessingState.CATCHUP);
// Deliberately not seeded from position.live. That column is a one-way
// latch: it goes true the first time a reader reaches head and nothing
// ever writes it back, so seeding from it puts a reader that once
// reached head into head mode however far behind it restarts. Starting
// in catchup costs nothing, because process() re-evaluates the same
// head-distance predicate on every block and promotes on the first one
// that qualifies, before that block's commit or its notifications. The
// column is still written, since reconcile reads it as a liveness hint.
this.processor.setState(ProcessingState.CATCHUP);

let startBlock = position.block_num + 1;

Expand Down