Skip to content

Commit

Permalink
fix(prover): allow payload provider to work with no finalized block (#…
Browse files Browse the repository at this point in the history
…5820)

* Fix some issues in the prover

* Remove unused file

* Fix browser tests

* Add comment for buffer
  • Loading branch information
nazarhussain authored Aug 1, 2023
1 parent 4468054 commit 549aa03
Show file tree
Hide file tree
Showing 6 changed files with 486 additions and 17 deletions.
12 changes: 6 additions & 6 deletions packages/prover/src/proof_provider/ordered_map.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
export class OrderedMap<T> extends Map<number, T> {
private _min = 0;
private _max = 0;
private _min?: number;
private _max?: number;

get min(): number {
get min(): number | undefined {
return this._min;
}

get max(): number {
get max(): number | undefined {
return this._max;
}

set(key: number, value: T): this {
if (key < this._min) {
if (this._min === undefined || key < this._min) {
this._min = key;
}

if (key > this._max) {
if (this._max === undefined || key > this._max) {
this._max = key;
}

Expand Down
41 changes: 31 additions & 10 deletions packages/prover/src/proof_provider/payload_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ type BlockCLRoot = string;
* The in-memory store for the execution payloads to be used to verify the proofs
*/
export class PayloadStore {
// We store the block numbers only for finalized blocks
// We store the block root from execution for finalized blocks
// As these blocks are finalized, so not to be worried about conflicting roots
private finalizedRoots = new OrderedMap<BlockELRoot>();

// Unfinalized blocks are stored by the roots of the beacon chain
// Unfinalized blocks may change over time and may have conflicting roots
// We can receive multiple light-client headers for the same block of execution
// So we why store unfinalized payloads by their CL root, which is only used
// in processing the light-client headers
private unfinalizedRoots = new Map<BlockCLRoot, BlockELRoot>();

// Payloads store with BlockELRoot as key
Expand All @@ -27,7 +31,13 @@ export class PayloadStore {
constructor(private opts: {api: Api; logger: Logger}) {}

get finalized(): allForks.ExecutionPayload | undefined {
const finalizedMaxRoot = this.finalizedRoots.get(this.finalizedRoots.max);
const maxBlockNumberForFinalized = this.finalizedRoots.max;

if (maxBlockNumberForFinalized === undefined) {
return;
}

const finalizedMaxRoot = this.finalizedRoots.get(maxBlockNumberForFinalized);
if (finalizedMaxRoot) {
return this.payloads.get(finalizedMaxRoot);
}
Expand Down Expand Up @@ -67,8 +77,15 @@ export class PayloadStore {
return undefined;
}

async getOrFetchFinalizedPayload(blockNumber: number): Promise<allForks.ExecutionPayload | undefined> {
if (blockNumber > this.finalizedRoots.max) {
protected async getOrFetchFinalizedPayload(blockNumber: number): Promise<allForks.ExecutionPayload | undefined> {
const maxBlockNumberForFinalized = this.finalizedRoots.max;
const minBlockNumberForFinalized = this.finalizedRoots.min;

if (maxBlockNumberForFinalized === undefined || minBlockNumberForFinalized === undefined) {
return;
}

if (blockNumber > maxBlockNumberForFinalized) {
throw new Error(
`Block number ${blockNumber} is higher than the latest finalized block number. We recommend to use block hash for unfinalized blocks.`
);
Expand All @@ -77,7 +94,7 @@ export class PayloadStore {
let blockELRoot = this.finalizedRoots.get(blockNumber);
// check if we have payload cached locally else fetch from api
if (!blockELRoot) {
const payloads = await getExecutionPayloadForBlockNumber(this.opts.api, this.finalizedRoots.min, blockNumber);
const payloads = await getExecutionPayloadForBlockNumber(this.opts.api, minBlockNumberForFinalized, blockNumber);
for (const payload of Object.values(payloads)) {
this.set(payload, true);
}
Expand Down Expand Up @@ -129,7 +146,7 @@ export class PayloadStore {

// If the block is finalized and we do not have the payload
// We need to fetch and set the payload
else if (finalized && !existingELRoot) {
else {
this.payloads.set(
bufferToHex(header.execution.blockHash),
(
Expand All @@ -155,9 +172,11 @@ export class PayloadStore {
// Re-org happened, we need to update the payload
if (existingELRoot && existingELRoot !== blockELRoot) {
this.payloads.delete(existingELRoot);
this.unfinalizedRoots.set(blockCLRoot, blockELRoot);
}

// This is unfinalized header we need to store it's root related to cl root
this.unfinalizedRoots.set(blockCLRoot, blockELRoot);

// We do not have the payload for this block, we need to fetch it
const payload = (
await getExecutionPayloads({
Expand All @@ -171,12 +190,14 @@ export class PayloadStore {
this.prune();
}

private prune(): void {
prune(): void {
if (this.finalizedRoots.size <= MAX_PAYLOAD_HISTORY) return;
// Store doe not have any finalized blocks means it's recently initialized
if (this.finalizedRoots.max === undefined || this.finalizedRoots.min === undefined) return;

for (
let blockNumber = this.finalizedRoots.max - MAX_PAYLOAD_HISTORY;
blockNumber > this.finalizedRoots.min;
blockNumber >= this.finalizedRoots.min;
blockNumber--
) {
const blockELRoot = this.finalizedRoots.get(blockNumber);
Expand Down
2 changes: 2 additions & 0 deletions packages/prover/src/utils/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export async function getExecutionPayloads({
let slot = endSlot;
let block = await fetchNearestBlock(api, slot, "down");
payloads[block.message.slot] = block.message.body.executionPayload;
slot = block.message.slot - 1;

while (slot >= startSlot) {
const previousBlock = await fetchNearestBlock(api, block.message.slot - 1, "down");
Expand Down Expand Up @@ -84,6 +85,7 @@ export async function getExecutionPayloadForBlockNumber(
while (payloads[block.message.slot].blockNumber !== blockNumber) {
const previousBlock = await fetchNearestBlock(api, block.message.slot - 1, "down");
block = previousBlock;
payloads[block.message.slot] = block.message.body.executionPayload;
}

return payloads;
Expand Down
4 changes: 3 additions & 1 deletion packages/prover/src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,11 @@ export async function isValidStorageKeys({
sp.proof.map(hexToBuffer)
);

// buffer.equals is not compatible with Uint8Array for browser
// so we need to convert the output of RLP.encode to Buffer first
const isStorageValid =
(!expectedStorageRLP && sp.value === "0x0") ||
(!!expectedStorageRLP && expectedStorageRLP.equals(RLP.encode(sp.value)));
(!!expectedStorageRLP && expectedStorageRLP.equals(Buffer.from(RLP.encode(sp.value))));
if (!isStorageValid) return false;
} catch (err) {
logger.error("Error verifying storage keys", undefined, err as Error);
Expand Down
40 changes: 40 additions & 0 deletions packages/prover/test/unit/proof_provider/orderd_map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {expect} from "chai";
import {OrderedMap} from "../../../src/proof_provider/ordered_map.js";

describe("proof_provider/ordered_map", () => {
it("should initialize the min with undefined", () => {
const omap = new OrderedMap<string>();

expect(omap.min).to.undefined;
});

it("should initialize the max with undefined", () => {
const omap = new OrderedMap<string>();

expect(omap.max).to.undefined;
});

it("should set the min and max to the first value ", () => {
const omap = new OrderedMap<string>();
omap.set(11, "value");

expect(omap.min).eql(11);
expect(omap.max).eql(11);
});

it("should set the max value", () => {
const omap = new OrderedMap<string>();
omap.set(10, "value");
omap.set(11, "value");

expect(omap.max).eql(11);
});

it("should set the min value", () => {
const omap = new OrderedMap<string>();
omap.set(10, "value");
omap.set(11, "value");

expect(omap.min).eql(10);
});
});
Loading

0 comments on commit 549aa03

Please sign in to comment.