@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
186 lines • 7.75 kB
JavaScript
import { LodestarError } from "@lodestar/utils";
import { BlockError, BlockErrorCode } from "../../chain/errors/index.js";
import { MAX_BATCH_DOWNLOAD_ATTEMPTS, MAX_BATCH_PROCESSING_ATTEMPTS } from "../constants.js";
import { getBatchSlotRange, hashBlocks } from "./utils/index.js";
/**
* Current state of a batch
*/
export var BatchStatus;
(function (BatchStatus) {
/** The batch has failed either downloading or processing, but can be requested again. */
BatchStatus["AwaitingDownload"] = "AwaitingDownload";
/** The batch is being downloaded. */
BatchStatus["Downloading"] = "Downloading";
/** The batch has been completely downloaded and is ready for processing. */
BatchStatus["AwaitingProcessing"] = "AwaitingProcessing";
/** The batch is being processed. */
BatchStatus["Processing"] = "Processing";
/**
* The batch was successfully processed and is waiting to be validated.
*
* It is not sufficient to process a batch successfully to consider it correct. This is
* because batches could be erroneously empty, or incomplete. Therefore, a batch is considered
* valid, only if the next sequential batch imports at least a block.
*/
BatchStatus["AwaitingValidation"] = "AwaitingValidation";
})(BatchStatus || (BatchStatus = {}));
/**
* Batches are downloaded at the first block of the epoch.
*
* For example:
*
* Epoch boundary | |
* ... | 30 | 31 | 32 | 33 | 34 | ... | 61 | 62 | 63 | 64 | 65 |
* Batch 1 | Batch 2 | Batch 3
*
* Jul2022: Offset changed from 1 to 0, see rationale in {@link BATCH_SLOT_OFFSET}
*/
export class Batch {
constructor(startEpoch, config) {
/** State of the batch. */
this.state = { status: BatchStatus.AwaitingDownload };
/** The `Attempts` that have been made and failed to send us this batch. */
this.failedProcessingAttempts = [];
/** The `Attempts` that have been made and failed because of execution malfunction. */
this.executionErrorAttempts = [];
/** The number of download retries this batch has undergone due to a failed request. */
this.failedDownloadAttempts = [];
const { startSlot, count } = getBatchSlotRange(startEpoch);
this.config = config;
this.startEpoch = startEpoch;
this.request = {
startSlot,
count,
step: 1,
};
}
/**
* Gives a list of peers from which this batch has had a failed download or processing attempt.
*/
getFailedPeers() {
return [...this.failedDownloadAttempts, ...this.failedProcessingAttempts.map((a) => a.peer)];
}
getMetadata() {
return { startEpoch: this.startEpoch, status: this.state.status };
}
/**
* AwaitingDownload -> Downloading
*/
startDownloading(peer) {
if (this.state.status !== BatchStatus.AwaitingDownload) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingDownload));
}
this.state = { status: BatchStatus.Downloading, peer };
}
/**
* Downloading -> AwaitingProcessing
*/
downloadingSuccess(blocks) {
if (this.state.status !== BatchStatus.Downloading) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading));
}
this.state = { status: BatchStatus.AwaitingProcessing, peer: this.state.peer, blocks };
}
/**
* Downloading -> AwaitingDownload
*/
downloadingError() {
if (this.state.status !== BatchStatus.Downloading) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading));
}
this.failedDownloadAttempts.push(this.state.peer);
if (this.failedDownloadAttempts.length > MAX_BATCH_DOWNLOAD_ATTEMPTS) {
throw new BatchError(this.errorType({ code: BatchErrorCode.MAX_DOWNLOAD_ATTEMPTS }));
}
this.state = { status: BatchStatus.AwaitingDownload };
}
/**
* AwaitingProcessing -> Processing
*/
startProcessing() {
if (this.state.status !== BatchStatus.AwaitingProcessing) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingProcessing));
}
const blocks = this.state.blocks;
const hash = hashBlocks(blocks, this.config); // tracks blocks to report peer on processing error
this.state = { status: BatchStatus.Processing, attempt: { peer: this.state.peer, hash } };
return blocks;
}
/**
* Processing -> AwaitingValidation
*/
processingSuccess() {
if (this.state.status !== BatchStatus.Processing) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.Processing));
}
this.state = { status: BatchStatus.AwaitingValidation, attempt: this.state.attempt };
}
/**
* Processing -> AwaitingDownload
*/
processingError(err) {
if (this.state.status !== BatchStatus.Processing) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.Processing));
}
if (err instanceof BlockError && err.type.code === BlockErrorCode.EXECUTION_ENGINE_ERROR) {
this.onExecutionEngineError(this.state.attempt);
}
else {
this.onProcessingError(this.state.attempt);
}
}
/**
* AwaitingValidation -> AwaitingDownload
*/
validationError(err) {
if (this.state.status !== BatchStatus.AwaitingValidation) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingValidation));
}
if (err instanceof BlockError && err.type.code === BlockErrorCode.EXECUTION_ENGINE_ERROR) {
this.onExecutionEngineError(this.state.attempt);
}
else {
this.onProcessingError(this.state.attempt);
}
}
/**
* AwaitingValidation -> Done
*/
validationSuccess() {
if (this.state.status !== BatchStatus.AwaitingValidation) {
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingValidation));
}
return this.state.attempt;
}
onExecutionEngineError(attempt) {
this.executionErrorAttempts.push(attempt);
if (this.executionErrorAttempts.length > MAX_BATCH_PROCESSING_ATTEMPTS) {
throw new BatchError(this.errorType({ code: BatchErrorCode.MAX_EXECUTION_ENGINE_ERROR_ATTEMPTS }));
}
this.state = { status: BatchStatus.AwaitingDownload };
}
onProcessingError(attempt) {
this.failedProcessingAttempts.push(attempt);
if (this.failedProcessingAttempts.length > MAX_BATCH_PROCESSING_ATTEMPTS) {
throw new BatchError(this.errorType({ code: BatchErrorCode.MAX_PROCESSING_ATTEMPTS }));
}
this.state = { status: BatchStatus.AwaitingDownload };
}
/** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
errorType(type) {
return { ...type, ...this.getMetadata() };
}
wrongStatusErrorType(expectedStatus) {
return this.errorType({ code: BatchErrorCode.WRONG_STATUS, expectedStatus });
}
}
export var BatchErrorCode;
(function (BatchErrorCode) {
BatchErrorCode["WRONG_STATUS"] = "BATCH_ERROR_WRONG_STATUS";
BatchErrorCode["MAX_DOWNLOAD_ATTEMPTS"] = "BATCH_ERROR_MAX_DOWNLOAD_ATTEMPTS";
BatchErrorCode["MAX_PROCESSING_ATTEMPTS"] = "BATCH_ERROR_MAX_PROCESSING_ATTEMPTS";
BatchErrorCode["MAX_EXECUTION_ENGINE_ERROR_ATTEMPTS"] = "MAX_EXECUTION_ENGINE_ERROR_ATTEMPTS";
})(BatchErrorCode || (BatchErrorCode = {}));
export class BatchError extends LodestarError {
}
//# sourceMappingURL=batch.js.map