Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beacon chain network scaffolding #426

Merged
merged 13 commits into from
Aug 1, 2023
126 changes: 126 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/portalnetwork/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"@libp2p/interface-peer-id": "^2.0.1",
"@libp2p/peer-id": "^2.0.1",
"@libp2p/peer-id-factory": "^2.0.3",
"@lodestar/config": "^1.9.2",
"@lodestar/params": "^1.9.2",
"@lodestar/types": "^1.9.2",
"@multiformats/multiaddr": "^11.0.0",
"@thi.ng/leb128": "^2.1.11",
"@waku/core": "0.0.8",
Expand Down
116 changes: 116 additions & 0 deletions packages/portalnetwork/src/subprotocols/beacon/beacon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Debugger } from 'debug'
import { BaseProtocol } from '../protocol.js'
import { ProtocolId } from '../types.js'
import { PortalNetwork } from '../../client/client.js'
import debug from 'debug'
import { Union } from '@chainsafe/ssz/lib/interface.js'
import { fromHexString, toHexString } from '@chainsafe/ssz'
import { shortId } from '../../util/util.js'
import { createBeaconConfig, defaultChainConfig, BeaconConfig } from '@lodestar/config'
import {
Forks,
MainnetGenesisValidatorsRoot,
BeaconLightClientNetworkContentType,
} from './types.js'
import {
ContentMessageType,
FindContentMessage,
MessageCodes,
PortalWireMessageType,
} from '../../wire/types.js'
import { ssz } from '@lodestar/types'

export class BeaconLightClientNetwork extends BaseProtocol {
protocolId: ProtocolId.BeaconLightClientNetwork
beaconConfig: BeaconConfig
protocolName = 'BeaconLightClientNetwork'
logger: Debugger
constructor(client: PortalNetwork, nodeRadius?: bigint) {
super(client, nodeRadius)

const genesisRoot = fromHexString(MainnetGenesisValidatorsRoot)
this.beaconConfig = createBeaconConfig(defaultChainConfig, genesisRoot)
this.protocolId = ProtocolId.BeaconLightClientNetwork
this.logger = debug(this.enr.nodeId.slice(0, 5))
.extend('Portal')
.extend('BeaconLightClientNetwork')
this.routingTable.setLogger(this.logger)
}

public findContentLocally = async (contentKey: Uint8Array): Promise<Uint8Array | undefined> => {
const value = await this.retrieve(toHexString(contentKey))
return value ? fromHexString(value) : fromHexString('0x')
}

public async retrieve(contentKey: string): Promise<string | undefined> {
try {
const content = await this.get(this.protocolId, contentKey)
return content
} catch {
this.logger('Error retrieving content from DB')
}
}

public sendFindContent = async (
dstId: string,
key: Uint8Array
): Promise<Union<Uint8Array | Uint8Array[]> | undefined> => {
const enr = this.routingTable.getValue(dstId)
if (!enr) {
this.logger(`No ENR found for ${shortId(dstId)}. FINDCONTENT aborted.`)
return
}
this.metrics?.findContentMessagesSent.inc()
const findContentMsg: FindContentMessage = { contentKey: key }
const payload = PortalWireMessageType.serialize({
selector: MessageCodes.FINDCONTENT,
value: findContentMsg,
})
const res = await this.sendMessage(enr, Buffer.from(payload), this.protocolId)
if (res.length === 0) {
return undefined
}
try {
if (parseInt(res.subarray(0, 1).toString('hex')) === MessageCodes.CONTENT) {
this.metrics?.contentMessagesReceived.inc()
this.logger.extend('FOUNDCONTENT')(`Received from ${shortId(dstId)}`)
const decoded = ContentMessageType.deserialize(res.subarray(1))
const contentHash = toHexString(key)
const forkhash = decoded.value.slice(0, 4) as Uint8Array
const forkname = this.beaconConfig.forkDigest2ForkName(forkhash) as any
switch (decoded.selector) {
case BeaconLightClientNetworkContentType.LightClientOptimisticUpdate:
try {
// TODO: Figure out how to use Forks type to limit selector in ssz[forkname] below and make typescript happy
;(ssz as any)[forkname].LightClientOptimisticUpdate.deserialize(
decoded.value as Uint8Array
)
} catch (err) {
this.logger(`received invalid content from ${shortId(dstId)}`)
break
}
this.logger(
`received ${
BeaconLightClientNetworkContentType[decoded.selector]
} content corresponding to ${contentHash}`
)
await this.store(decoded.selector, contentHash, decoded.value as Uint8Array)
break
default:
this.logger(
`received ${
BeaconLightClientNetworkContentType[decoded.selector]
} content corresponding to ${contentHash}`
)
}
return decoded
}
} catch (err: any) {
this.logger(`Error sending FINDCONTENT to ${shortId(dstId)} - ${err.message}`)
}
}

public store = async (contentType: any, hashKey: string, value: Uint8Array): Promise<void> => {
await this.put(this.protocolId, hashKey, toHexString(value))
}
}
30 changes: 30 additions & 0 deletions packages/portalnetwork/src/subprotocols/beacon/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BitArray, fromHexString } from '@chainsafe/ssz'

export const attestedHeaderFromJson = (data: any) => {
return {
slot: BigInt(data.beacon.slot),
proposerIndex: BigInt(data.beacon.proposer_index),
parentRoot: fromHexString(data.beacon.parent_root),
stateRoot: fromHexString(data.beacon.state_root),
bodyRoot: fromHexString(data.beacon.body_root),
}
}

export const syncAggregateFromJson = (data: any) => {
return {
syncCommitteeBits: new BitArray(
new Uint8Array(
Array.from(BigInt(data.sync_committee_bits).toString(2)).map((el) => parseInt(el))
),
256 // TODO: Fix this so Bitlength is equal to SYNC_COMMITTEE_SIZE - 512
),
syncCommitteeSignature: fromHexString(data.sync_committee_signature),
}
}

export const lightClientOptimisticUpdateFromJson = (data: any) => {
return {
attestedHeader: attestedHeaderFromJson(data.attested_header),
syncAggregate: syncAggregateFromJson(data.sync_aggregate),
}
}
3 changes: 3 additions & 0 deletions packages/portalnetwork/src/subprotocols/beacon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './beacon.js'
export * from './types.js'
export * from './helpers.js'
23 changes: 23 additions & 0 deletions packages/portalnetwork/src/subprotocols/beacon/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ByteListType, ListCompositeType } from '@chainsafe/ssz'
import { ForkName } from '@lodestar/params'

export const MainnetGenesisValidatorsRoot =
'0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95'

export const MAX_REQUEST_LIGHT_CLIENT_UPDATES = 128

export enum BeaconLightClientNetworkContentType {
LightClientBootstrap = 0,
LightClientUpdatesByRange = 1,
LightClientFinalityUpdate = 2,
LightClientOptimisticUpdate = 3,
}

export enum Forks {
altair = ForkName.altair,
capella = ForkName.capella,
deneb = ForkName.deneb,
}

// TODO - figure out what a theoretical maximum byte size for a LightClientUpdate is (the `ByteListType`) in the below ssz list
export const LightClientUpdatesByRange = new ListCompositeType(new ByteListType(2 ** 18), 128)
Loading