Skip to content

Commit

Permalink
Don't error on LightClientServer finalizedHeader not available (#3771)
Browse files Browse the repository at this point in the history
* Don't error on LightClientServer finalizedHeader not available

* Add metrics
  • Loading branch information
dapplion authored Feb 28, 2022
1 parent a904f9c commit 07fbc13
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 32 deletions.
5 changes: 1 addition & 4 deletions packages/lodestar/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,7 @@ export class BeaconChain implements IBeaconChain {
signal,
});

const lightClientServer = new LightClientServer(
{config, db, emitter, logger},
{genesisTime: this.genesisTime, genesisValidatorsRoot: this.genesisValidatorsRoot as Uint8Array}
);
const lightClientServer = new LightClientServer({config, db, metrics, emitter, logger});

this.reprocessController = new ReprocessController(this.metrics);

Expand Down
66 changes: 42 additions & 24 deletions packages/lodestar/src/chain/lightClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {ILogger} from "@chainsafe/lodestar-utils";
import {routes} from "@chainsafe/lodestar-api";
import {BitVector, toHexString} from "@chainsafe/ssz";
import {IBeaconDb} from "../../db";
import {IMetrics} from "../../metrics";
import {MapDef, pruneSetToMax} from "../../util/map";
import {ChainEvent, ChainEventEmitter} from "../emitter";
import {
Expand Down Expand Up @@ -40,17 +41,13 @@ type SyncAttestedData = {
}
);

type GenesisData = {
genesisTime: number;
genesisValidatorsRoot: Uint8Array;
};

interface ILightClientIniterModules {
type LightClientServerModules = {
config: IChainForkConfig;
db: IBeaconDb;
metrics: IMetrics | null;
emitter: ChainEventEmitter;
logger: ILogger;
}
};

const MAX_CACHED_FINALIZED_HEADERS = 3;
const MAX_PREV_HEAD_DATA = 32;
Expand Down Expand Up @@ -158,6 +155,7 @@ const MAX_PREV_HEAD_DATA = 32;
export class LightClientServer {
private readonly db: IBeaconDb;
private readonly config: IChainForkConfig;
private readonly metrics: IMetrics | null;
private readonly emitter: ChainEventEmitter;
private readonly logger: ILogger;
private readonly knownSyncCommittee = new MapDef<SyncPeriod, Set<DependantRootHex>>(() => new Set());
Expand All @@ -172,11 +170,13 @@ export class LightClientServer {

private readonly zero: Pick<altair.LightClientUpdate, "finalityBranch" | "finalizedHeader">;

constructor(modules: ILightClientIniterModules, private readonly genesisData: GenesisData) {
this.config = modules.config;
this.db = modules.db;
this.emitter = modules.emitter;
this.logger = modules.logger;
constructor(modules: LightClientServerModules) {
const {config, db, metrics, emitter, logger} = modules;
this.config = config;
this.db = db;
this.metrics = metrics;
this.emitter = emitter;
this.logger = logger;

this.zero = {
finalizedHeader: ssz.phase0.BeaconBlockHeader.defaultValue(),
Expand Down Expand Up @@ -468,23 +468,36 @@ export class LightClientServer {
return;
}

const newPartialUpdate: PartialLightClientUpdate = attestedData.isFinalized
? {
...attestedData,
finalizedHeader: await this.getFinalizedHeader(attestedData.finalizedCheckpoint.root as Uint8Array),
syncAggregate,
}
: {
...attestedData,
syncAggregate,
};
let newPartialUpdate: PartialLightClientUpdate;

if (attestedData.isFinalized) {
// If update if finalized retrieve the previously stored header from DB.
// Only checkpoint candidates are stored, and not all headers are guaranteed to be available
const finalizedCheckpointRoot = attestedData.finalizedCheckpoint.root as Uint8Array;
const finalizedHeader = await this.getFinalizedHeader(finalizedCheckpointRoot);
if (finalizedHeader) {
// If finalizedHeader is available (should be most times) create a finalized update
newPartialUpdate = {...attestedData, finalizedHeader, syncAggregate};
} else {
// If finalizedHeader is not available (happens on startup) create a non-finalized update
newPartialUpdate = {...attestedData, isFinalized: false, syncAggregate};
}
} else {
newPartialUpdate = {...attestedData, syncAggregate};
}

await this.db.bestPartialLightClientUpdate.put(period, newPartialUpdate);
this.logger.debug("Stored new PartialLightClientUpdate", {
period,
isFinalized: attestedData.isFinalized,
participation: sumBits(syncAggregate.syncCommitteeBits) / SYNC_COMMITTEE_SIZE,
});

// Count total persisted updates per type. DB metrics don't diff between each type.
// The frequency of finalized vs non-finalized is critical to debug if finalizedHeader is not available
this.metrics?.lightclientServer.persistedUpdates.inc({
type: newPartialUpdate.isFinalized ? "finalized" : "non-finalized",
});
}

private async storeSyncCommittee(syncCommittee: altair.SyncCommittee, syncCommitteeRoot: Uint8Array): Promise<void> {
Expand All @@ -497,7 +510,7 @@ export class LightClientServer {
/**
* Get finalized header from db. Keeps a small in-memory cache to speed up most of the lookups
*/
private async getFinalizedHeader(finalizedBlockRoot: Uint8Array): Promise<phase0.BeaconBlockHeader> {
private async getFinalizedHeader(finalizedBlockRoot: Uint8Array): Promise<phase0.BeaconBlockHeader | null> {
const finalizedBlockRootHex = toHexString(finalizedBlockRoot);
const cachedFinalizedHeader = this.checkpointHeaders.get(finalizedBlockRootHex);
if (cachedFinalizedHeader) {
Expand All @@ -506,7 +519,12 @@ export class LightClientServer {

const finalizedHeader = await this.db.checkpointHeader.get(finalizedBlockRoot);
if (!finalizedHeader) {
throw Error(`finalityHeader not available ${toHexString(finalizedBlockRoot)}`);
// finalityHeader is not available during sync, since started after the finalized checkpoint.
// See https://github.com/ChainSafe/lodestar/issues/3495
// To prevent excesive logging this condition is not considered an error, but the lightclient updater
// will just create a non-finalized update.
this.logger.debug("finalizedHeader not available", {root: finalizedBlockRootHex});
return null;
}

this.checkpointHeaders.set(finalizedBlockRootHex, finalizedHeader);
Expand Down
8 changes: 8 additions & 0 deletions packages/lodestar/src/metrics/metrics/lodestar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,5 +735,13 @@ export function createLodestarMetrics(
help: "Time to wait for unknown block before being rejected",
}),
},

lightclientServer: {
persistedUpdates: register.gauge<"type">({
name: "lodestar_lightclient_server_persisted_updates_total",
help: "Total number of persisted updates by finalized type",
labelNames: ["type"],
}),
},
};
}
11 changes: 7 additions & 4 deletions packages/lodestar/test/utils/mocks/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,13 @@ export class MockBeaconChain implements IBeaconChain {
metrics: null,
emitter: this.emitter,
});
this.lightClientServer = new LightClientServer(
{config: this.config, emitter: this.emitter, logger, db: db},
{genesisTime: this.genesisTime, genesisValidatorsRoot: this.genesisValidatorsRoot as Uint8Array}
);
this.lightClientServer = new LightClientServer({
config: this.config,
db: db,
metrics: null,
emitter: this.emitter,
logger,
});
this.reprocessController = new ReprocessController(null);
}

Expand Down

0 comments on commit 07fbc13

Please sign in to comment.