@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
515 lines • 26.5 kB
JavaScript
import { peerIdFromPrivateKey } from "@libp2p/peer-id";
import { routes } from "@lodestar/api";
import { ForkSeq } from "@lodestar/params";
import { computeEpochAtSlot } from "@lodestar/state-transition";
import { isGloasDataColumnSidecar, } from "@lodestar/types";
import { prettyPrintIndices, sleep } from "@lodestar/utils";
import { ChainEvent } from "../chain/index.js";
import { computeSubnetForDataColumnSidecar } from "../chain/validation/dataColumnSidecar.js";
import { RegistryMetricCreator } from "../metrics/index.js";
import { peerIdToString } from "../util/peerId.js";
import { promiseAllMaybeAsync } from "../util/promises.js";
import { NetworkCore, WorkerNetworkCore } from "./core/index.js";
import { NetworkEvent, NetworkEventBus } from "./events.js";
import { getActiveForkBoundaries } from "./forks.js";
import { GossipType } from "./gossip/index.js";
import { getGossipSSZType, gossipTopicIgnoreDuplicatePublishError, stringifyGossipTopic } from "./gossip/topic.js";
import { AggregatorTracker } from "./processor/aggregatorTracker.js";
import { NetworkProcessor } from "./processor/index.js";
import { ReqRespMethod } from "./reqresp/index.js";
import { Version, requestSszTypeByMethod, responseSszTypeByMethod } from "./reqresp/types.js";
import { collectExactOneTyped, collectMaxResponseTyped, collectMaxResponseTypedWithBytes, } from "./reqresp/utils/collect.js";
import { collectSequentialBlocksInRange } from "./reqresp/utils/collectSequentialBlocksInRange.js";
import { isPublishToZeroPeersError, prettyPrintPeerIdStr } from "./util.js";
/**
* Must support running both on worker and on main thread.
*
* Exists a front class that's what consumers interact with.
* This class will multiplex between:
* - libp2p in worker
* - libp2p in main thread
*/
export class Network {
peerId;
custodyConfig;
// TODO: Make private
events;
logger;
config;
clock;
chain;
// Used only for sleep() statements
controller;
// TODO: Review
networkProcessor;
core;
aggregatorTracker;
subscribedToCoreTopics = false;
connectedPeersSyncMeta = new Map();
constructor(modules) {
this.peerId = peerIdFromPrivateKey(modules.privateKey);
this.config = modules.config;
this.custodyConfig = modules.chain.custodyConfig;
this.logger = modules.logger;
this.chain = modules.chain;
this.clock = modules.chain.clock;
this.controller = new AbortController();
this.events = modules.networkEventBus;
this.networkProcessor = modules.networkProcessor;
this.core = modules.core;
this.aggregatorTracker = modules.aggregatorTracker;
this.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
this.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
this.chain.emitter.on(routes.events.EventType.head, this.onHead);
this.chain.emitter.on(routes.events.EventType.lightClientFinalityUpdate, ({ data }) => this.onLightClientFinalityUpdate(data));
this.chain.emitter.on(routes.events.EventType.lightClientOptimisticUpdate, ({ data }) => this.onLightClientOptimisticUpdate(data));
this.chain.emitter.on(ChainEvent.updateTargetCustodyGroupCount, this.onTargetGroupCountUpdated);
this.chain.emitter.on(ChainEvent.publishDataColumns, this.onPublishDataColumns);
this.chain.emitter.on(ChainEvent.publishBlobSidecars, this.onPublishBlobSidecars);
this.chain.emitter.on(ChainEvent.updateStatus, this.onUpdateStatus);
}
static async init({ opts, config, logger, metrics, chain, db, gossipHandlers, privateKey, peerStoreDir, getReqRespHandler, }) {
const events = new NetworkEventBus();
const aggregatorTracker = new AggregatorTracker();
const activeValidatorCount = chain.getHeadState().activeValidatorCount;
const initialStatus = chain.getStatus();
const initialCustodyGroupCount = chain.custodyConfig.targetCustodyGroupCount;
if (opts.useWorker) {
logger.info("running libp2p instance in worker thread");
}
const core = opts.useWorker
? await WorkerNetworkCore.init({
opts: {
...opts,
peerStoreDir,
metricsEnabled: Boolean(metrics),
activeValidatorCount,
genesisTime: chain.genesisTime,
initialStatus,
initialCustodyGroupCount,
},
config,
privateKey,
logger,
events,
metrics,
getReqRespHandler,
})
: await NetworkCore.init({
opts,
config,
privateKey,
peerStoreDir,
logger,
clock: chain.clock,
events,
getReqRespHandler,
metricsRegistry: metrics ? new RegistryMetricCreator() : null,
initialStatus,
initialCustodyGroupCount,
activeValidatorCount,
});
const networkProcessor = new NetworkProcessor({ chain, db, config, logger, metrics, events, gossipHandlers, core, aggregatorTracker }, opts);
const multiaddresses = opts.localMultiaddrs?.join(",");
const peerId = peerIdFromPrivateKey(privateKey);
logger.info(`PeerId ${peerIdToString(peerId)}, Multiaddrs ${multiaddresses}`);
return new Network({
opts,
privateKey,
config,
logger,
chain,
networkEventBus: events,
aggregatorTracker,
networkProcessor,
core,
});
}
get closed() {
return this.controller.signal.aborted;
}
/** Destroy this instance. Can only be called once. */
async close() {
if (this.closed)
return;
this.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
this.events.off(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
this.chain.emitter.off(routes.events.EventType.head, this.onHead);
this.chain.emitter.off(routes.events.EventType.lightClientFinalityUpdate, this.onLightClientFinalityUpdate);
this.chain.emitter.off(routes.events.EventType.lightClientOptimisticUpdate, this.onLightClientOptimisticUpdate);
this.chain.emitter.off(ChainEvent.updateTargetCustodyGroupCount, this.onTargetGroupCountUpdated);
this.chain.emitter.off(ChainEvent.publishDataColumns, this.onPublishDataColumns);
this.chain.emitter.off(ChainEvent.publishBlobSidecars, this.onPublishBlobSidecars);
this.chain.emitter.off(ChainEvent.updateStatus, this.onUpdateStatus);
await this.core.close();
// Used only for sleep() statements
this.controller.abort();
this.logger.debug("network core closed");
}
async scrapeMetrics() {
return this.core.scrapeMetrics();
}
/**
* Request att subnets up `toSlot`. Network will ensure to mantain some peers for each
*/
async prepareBeaconCommitteeSubnets(subscriptions) {
for (const subscription of subscriptions) {
if (subscription.isAggregator) {
this.aggregatorTracker.addAggregator(subscription.subnet, subscription.slot);
}
}
this.aggregatorTracker.prune();
return this.core.prepareBeaconCommitteeSubnets(subscriptions);
}
async prepareSyncCommitteeSubnets(subscriptions) {
return this.core.prepareSyncCommitteeSubnets(subscriptions);
}
/**
* The app layer needs to refresh the status of some peers. The sync have reached a target
*/
async reStatusPeers(peers) {
return this.core.reStatusPeers(peers);
}
searchUnknownBlock(slotRoot, source, peer) {
this.networkProcessor.searchUnknownBlock(slotRoot, source, peer);
}
searchUnknownEnvelope(slotRoot, source, peer) {
this.networkProcessor.searchUnknownEnvelope(slotRoot, source, peer);
}
async reportPeer(peer, action, actionName) {
return this.core.reportPeer(peer, action, actionName);
}
// REST API queries
getConnectedPeers() {
return Array.from(this.connectedPeersSyncMeta.keys());
}
getConnectedPeerSyncMeta(peerId) {
const syncMeta = this.connectedPeersSyncMeta.get(peerId);
if (!syncMeta) {
throw new Error(`peerId=${prettyPrintPeerIdStr(peerId)} not in connectedPeerSyncMeta`);
}
return { peerId, ...syncMeta };
}
getConnectedPeerCount() {
return this.connectedPeersSyncMeta.size;
}
async getNetworkIdentity() {
return this.core.getNetworkIdentity();
}
/**
* Subscribe to all gossip events. Safe to call multiple times
*/
async subscribeGossipCoreTopics() {
if (!this.subscribedToCoreTopics) {
await this.core.subscribeGossipCoreTopics();
// Only mark subscribedToCoreTopics if worker resolved this call
this.subscribedToCoreTopics = true;
}
}
/**
* Unsubscribe from all gossip events. Safe to call multiple times
*/
async unsubscribeGossipCoreTopics() {
// Drop all the gossip validation queues
this.networkProcessor.dropAllJobs();
await this.core.unsubscribeGossipCoreTopics();
this.subscribedToCoreTopics = false;
}
isSubscribedToGossipCoreTopics() {
return this.subscribedToCoreTopics;
}
shouldAggregate(subnet, slot) {
return this.aggregatorTracker.shouldAggregate(subnet, slot);
}
// Gossip
async publishBeaconBlock(signedBlock) {
const epoch = computeEpochAtSlot(signedBlock.message.slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.beacon_block, boundary }, signedBlock, {
ignoreDuplicatePublishError: true,
});
}
async publishBlobSidecar(blobSidecar) {
const epoch = computeEpochAtSlot(blobSidecar.signedBlockHeader.message.slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
const subnet = blobSidecar.index;
return this.publishGossip({ type: GossipType.blob_sidecar, boundary, subnet }, blobSidecar, {
ignoreDuplicatePublishError: true,
});
}
async publishDataColumnSidecar(dataColumnSidecar) {
const slot = isGloasDataColumnSidecar(dataColumnSidecar)
? dataColumnSidecar.slot
: dataColumnSidecar.signedBlockHeader.message.slot;
const epoch = computeEpochAtSlot(slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
const subnet = computeSubnetForDataColumnSidecar(this.config, dataColumnSidecar);
return this.publishGossip({ type: GossipType.data_column_sidecar, boundary, subnet }, dataColumnSidecar, {
ignoreDuplicatePublishError: true,
// we ensure having all topic peers via prioritizePeers() function
// in the worse case, if there is 0 peer on the topic, the overall publish operation could be still a success
// because supernode will rebuild and publish missing data column sidecars for us
// hence we want to track sent peers as 0 instead of an error
allowPublishToZeroTopicPeers: true,
});
}
async publishBeaconAggregateAndProof(aggregateAndProof) {
const epoch = computeEpochAtSlot(aggregateAndProof.message.aggregate.data.slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.beacon_aggregate_and_proof, boundary }, aggregateAndProof, { ignoreDuplicatePublishError: true });
}
async publishBeaconAttestation(attestation, subnet) {
const epoch = computeEpochAtSlot(attestation.data.slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.beacon_attestation, boundary, subnet }, attestation, { ignoreDuplicatePublishError: true });
}
async publishVoluntaryExit(voluntaryExit) {
const epoch = voluntaryExit.message.epoch;
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.voluntary_exit, boundary }, voluntaryExit, {
ignoreDuplicatePublishError: true,
});
}
async publishBlsToExecutionChange(blsToExecutionChange) {
const publishChanges = [];
for (const boundary of getActiveForkBoundaries(this.config, this.clock.currentEpoch)) {
const fork = ForkSeq[boundary.fork];
if (fork >= ForkSeq.capella) {
const publishPromise = this.publishGossip({ type: GossipType.bls_to_execution_change, boundary }, blsToExecutionChange, { ignoreDuplicatePublishError: true });
publishChanges.push(publishPromise);
}
}
if (publishChanges.length === 0) {
throw Error("No capella+ fork active yet to publish blsToExecutionChange");
}
return Promise.any(publishChanges);
}
async publishProposerSlashing(proposerSlashing) {
const epoch = computeEpochAtSlot(Number(proposerSlashing.signedHeader1.message.slot));
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.proposer_slashing, boundary }, proposerSlashing);
}
async publishAttesterSlashing(attesterSlashing) {
const epoch = computeEpochAtSlot(Number(attesterSlashing.attestation1.data.slot));
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.attester_slashing, boundary }, attesterSlashing);
}
async publishSyncCommitteeSignature(signature, subnet) {
const epoch = computeEpochAtSlot(signature.slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.sync_committee, boundary, subnet }, signature, {
ignoreDuplicatePublishError: true,
});
}
async publishContributionAndProof(contributionAndProof) {
const epoch = computeEpochAtSlot(contributionAndProof.message.contribution.slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.sync_committee_contribution_and_proof, boundary }, contributionAndProof, { ignoreDuplicatePublishError: true });
}
async publishLightClientFinalityUpdate(update) {
const epoch = computeEpochAtSlot(update.signatureSlot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.light_client_finality_update, boundary }, update);
}
async publishLightClientOptimisticUpdate(update) {
const epoch = computeEpochAtSlot(update.signatureSlot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.light_client_optimistic_update, boundary }, update);
}
async publishSignedExecutionPayloadEnvelope(signedEnvelope) {
const epoch = computeEpochAtSlot(signedEnvelope.message.payload.slotNumber);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.execution_payload, boundary }, signedEnvelope, { ignoreDuplicatePublishError: true });
}
async publishPayloadAttestationMessage(payloadAttestationMessage) {
const epoch = computeEpochAtSlot(payloadAttestationMessage.data.slot);
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
return this.publishGossip({ type: GossipType.payload_attestation_message, boundary }, payloadAttestationMessage, { ignoreDuplicatePublishError: true });
}
async publishGossip(topic, object, opts) {
const topicStr = stringifyGossipTopic(this.config, topic);
const sszType = getGossipSSZType(topic);
const messageData = sszType.serialize(object);
opts = {
...opts,
ignoreDuplicatePublishError: gossipTopicIgnoreDuplicatePublishError[topic.type],
};
const sentPeers = await this.core.publishGossip(topicStr, messageData, opts);
this.logger.verbose("Publish to topic", { topic: topicStr, sentPeers, currentSlot: this.clock.currentSlot });
return sentPeers;
}
// ReqResp
async sendBeaconBlocksByRange(peerId, request) {
return collectSequentialBlocksInRange(this.sendReqRespRequest(peerId, ReqRespMethod.BeaconBlocksByRange,
// Before altair, prioritize V2. After altair only request V2
this.config.getForkSeq(this.clock.currentSlot) >= ForkSeq.altair ? [Version.V2] : [Version.V2, Version.V1], request), request, this.chain.serializedCache);
}
async sendBeaconBlocksByRoot(peerId, request) {
return collectMaxResponseTypedWithBytes(this.sendReqRespRequest(peerId, ReqRespMethod.BeaconBlocksByRoot,
// Before altair, prioritize V2. After altair only request V2
this.config.getForkSeq(this.clock.currentSlot) >= ForkSeq.altair ? [Version.V2] : [Version.V2, Version.V1], request), request.length, responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByRoot], this.chain.serializedCache);
}
async sendBeaconBlocksByHead(peerId, request) {
return collectMaxResponseTypedWithBytes(this.sendReqRespRequest(peerId, ReqRespMethod.BeaconBlocksByHead, [Version.V1], request), Math.min(request.count, this.config.MAX_REQUEST_BLOCKS_DENEB), responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByHead], this.chain.serializedCache);
}
async sendLightClientBootstrap(peerId, request) {
return collectExactOneTyped(this.sendReqRespRequest(peerId, ReqRespMethod.LightClientBootstrap, [Version.V1], request), responseSszTypeByMethod[ReqRespMethod.LightClientBootstrap]);
}
async sendLightClientOptimisticUpdate(peerId) {
return collectExactOneTyped(this.sendReqRespRequest(peerId, ReqRespMethod.LightClientOptimisticUpdate, [Version.V1], null), responseSszTypeByMethod[ReqRespMethod.LightClientOptimisticUpdate]);
}
async sendLightClientFinalityUpdate(peerId) {
return collectExactOneTyped(this.sendReqRespRequest(peerId, ReqRespMethod.LightClientFinalityUpdate, [Version.V1], null), responseSszTypeByMethod[ReqRespMethod.LightClientFinalityUpdate]);
}
async sendLightClientUpdatesByRange(peerId, request) {
return collectMaxResponseTyped(this.sendReqRespRequest(peerId, ReqRespMethod.LightClientUpdatesByRange, [Version.V1], request), request.count, responseSszTypeByMethod[ReqRespMethod.LightClientUpdatesByRange]);
}
async sendBlobSidecarsByRange(peerId, request) {
const epoch = computeEpochAtSlot(request.startSlot);
return collectMaxResponseTyped(this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRange, [Version.V1], request),
// request's count represent the slots, so the actual max count received could be slots * blobs per slot
request.count * this.config.getMaxBlobsPerBlock(epoch), responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRange]);
}
async sendBlobSidecarsByRoot(peerId, request) {
return collectMaxResponseTyped(this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRoot, [Version.V1], request), request.length, responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRoot], this.chain.serializedCache);
}
async sendDataColumnSidecarsByRange(peerId, request) {
return collectMaxResponseTyped(this.sendReqRespRequest(peerId, ReqRespMethod.DataColumnSidecarsByRange, [Version.V1], request), request.count * request.columns.length, responseSszTypeByMethod[ReqRespMethod.DataColumnSidecarsByRange]);
}
async sendDataColumnSidecarsByRoot(peerId, request) {
return collectMaxResponseTyped(this.sendReqRespRequest(peerId, ReqRespMethod.DataColumnSidecarsByRoot, [Version.V1], request), request.reduce((total, { columns }) => total + columns.length, 0), responseSszTypeByMethod[ReqRespMethod.DataColumnSidecarsByRoot], this.chain.serializedCache);
}
async sendExecutionPayloadEnvelopesByRange(peerId, request) {
return collectMaxResponseTyped(this.sendReqRespRequest(peerId, ReqRespMethod.ExecutionPayloadEnvelopesByRange, [Version.V1], request), request.count, responseSszTypeByMethod[ReqRespMethod.ExecutionPayloadEnvelopesByRange]);
}
async sendExecutionPayloadEnvelopesByRoot(peerId, request) {
return collectMaxResponseTyped(this.sendReqRespRequest(peerId, ReqRespMethod.ExecutionPayloadEnvelopesByRoot, [Version.V1], request), request.length, responseSszTypeByMethod[ReqRespMethod.ExecutionPayloadEnvelopesByRoot], this.chain.serializedCache);
}
sendReqRespRequest(peerId, method, versions, request) {
const fork = this.config.getForkName(this.clock.currentSlot);
const requestType = requestSszTypeByMethod(fork, this.config)[method];
const requestData = requestType ? requestType.serialize(request) : new Uint8Array();
// ReqResp outgoing request, emit from main thread to worker
return this.core.sendReqRespRequest({ peerId, method, versions, requestData });
}
// Debug
connectToPeer(peer, multiaddr) {
return this.core.connectToPeer(peer, multiaddr);
}
disconnectPeer(peer) {
return this.core.disconnectPeer(peer);
}
addDirectPeer(peer) {
return this.core.addDirectPeer(peer);
}
removeDirectPeer(peerId) {
return this.core.removeDirectPeer(peerId);
}
getDirectPeers() {
return this.core.getDirectPeers();
}
dumpPeer(peerIdStr) {
return this.core.dumpPeer(peerIdStr);
}
dumpPeers() {
return this.core.dumpPeers();
}
dumpPeerScoreStats() {
return this.core.dumpPeerScoreStats();
}
dumpGossipPeerScoreStats() {
return this.core.dumpGossipPeerScoreStats();
}
dumpDiscv5KadValues() {
return this.core.dumpDiscv5KadValues();
}
dumpMeshPeers() {
return this.core.dumpMeshPeers();
}
async dumpGossipQueue(gossipType) {
return this.networkProcessor.dumpGossipQueue(gossipType);
}
async writeNetworkThreadProfile(durationMs, dirpath) {
return this.core.writeNetworkThreadProfile(durationMs, dirpath);
}
async writeDiscv5Profile(durationMs, dirpath) {
return this.core.writeDiscv5Profile(durationMs, dirpath);
}
async writeNetworkHeapSnapshot(prefix, dirpath) {
return this.core.writeNetworkHeapSnapshot(prefix, dirpath);
}
async writeDiscv5HeapSnapshot(prefix, dirpath) {
return this.core.writeDiscv5HeapSnapshot(prefix, dirpath);
}
onLightClientFinalityUpdate = async (finalityUpdate) => {
// TODO: Review is OK to remove if (this.hasAttachedSyncCommitteeMember())
try {
// messages SHOULD be broadcast after SYNC_MESSAGE_DUE_BPS of slot has transpired
// https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/altair/light-client/p2p-interface.md#sync-committee
await this.waitForSyncMessageCutoff(finalityUpdate.signatureSlot);
await this.publishLightClientFinalityUpdate(finalityUpdate);
}
catch (e) {
// Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
// Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
if (!isPublishToZeroPeersError(e)) {
this.logger.debug("Error on BeaconGossipHandler.onLightclientFinalityUpdate", {}, e);
}
}
};
onLightClientOptimisticUpdate = async (optimisticUpdate) => {
// TODO: Review is OK to remove if (this.hasAttachedSyncCommitteeMember())
try {
// messages SHOULD be broadcast after SYNC_MESSAGE_DUE_BPS of slot has transpired
// https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/altair/light-client/p2p-interface.md#sync-committee
await this.waitForSyncMessageCutoff(optimisticUpdate.signatureSlot);
await this.publishLightClientOptimisticUpdate(optimisticUpdate);
}
catch (e) {
// Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
// Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
if (!isPublishToZeroPeersError(e)) {
this.logger.debug("Error on BeaconGossipHandler.onLightclientOptimisticUpdate", {}, e);
}
}
};
waitForSyncMessageCutoff = async (slot) => {
const fork = this.config.getForkName(slot);
const msToCutoffTime = this.config.getSyncMessageDueMs(fork) - this.chain.clock.msFromSlot(slot);
await sleep(msToCutoffTime, this.controller.signal);
};
onHead = async () => {
await this.onUpdateStatus();
};
onPeerConnected = (data) => {
const { peer, clientAgent, custodyColumns, status } = data;
const earliestAvailableSlot = status.earliestAvailableSlot;
this.logger.verbose("onPeerConnected", {
peer,
clientAgent,
custodyColumns: prettyPrintIndices(custodyColumns),
earliestAvailableSlot: earliestAvailableSlot ?? "pre-fulu",
});
this.connectedPeersSyncMeta.set(peer, {
client: clientAgent,
custodyColumns,
earliestAvailableSlot, // can be undefined pre-fulu
});
};
onPeerDisconnected = (data) => {
this.connectedPeersSyncMeta.delete(data.peer);
};
onTargetGroupCountUpdated = (count) => {
this.core.setTargetGroupCount(count);
};
onPublishDataColumns = (sidecars) => {
return promiseAllMaybeAsync(sidecars.map((sidecar) => () => this.publishDataColumnSidecar(sidecar)));
};
onPublishBlobSidecars = (sidecars) => {
return promiseAllMaybeAsync(sidecars.map((sidecar) => () => this.publishBlobSidecar(sidecar)));
};
onUpdateStatus = async () => {
await this.core.updateStatus(this.chain.getStatus());
};
}
//# sourceMappingURL=network.js.map