From c64539dc884062c5f42a2ae21069bf39aa7ae2c5 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 17:39:31 -0600 Subject: [PATCH 1/8] portalnetwork: create HeadStateNetwork class --- .../networks/headState/headStateNetwork.ts | 10 ++++++ packages/portalnetwork/src/networks/types.ts | 32 +++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 packages/portalnetwork/src/networks/headState/headStateNetwork.ts diff --git a/packages/portalnetwork/src/networks/headState/headStateNetwork.ts b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts new file mode 100644 index 000000000..d65f31532 --- /dev/null +++ b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts @@ -0,0 +1,10 @@ +import { BaseNetwork, NetworkId, type NetworkConfig } from '../../index.js' + +export class HeadStateNetwork extends BaseNetwork { + networkName = 'HeadStateNetwork' + networkId = NetworkId.HeadStateNetwork + + constructor(config: NetworkConfig) { + super(config) + } +} diff --git a/packages/portalnetwork/src/networks/types.ts b/packages/portalnetwork/src/networks/types.ts index 9bdcb371e..d9b5bc1a5 100644 --- a/packages/portalnetwork/src/networks/types.ts +++ b/packages/portalnetwork/src/networks/types.ts @@ -24,12 +24,14 @@ const BYTE_SIZE = 256 // subnetwork IDs export enum NetworkId { + HeadStateNetwork = '0x5009', StateNetwork = '0x500a', HistoryNetwork = '0x500b', BeaconChainNetwork = '0x500c', CanonicalTxIndexNetwork = '0x500d', VerkleStateNetwork = '0x500e', TransactionGossipNetwork = '0x500f', + Angelfood_HeadStateNetwork = '0x5049', Angelfood_StateNetwork = '0x504a', Angelfood_HistoryNetwork = '0x504b', Angelfood_BeaconChainNetwork = '0x504c', @@ -39,19 +41,23 @@ export enum NetworkId { UTPNetwork = '0x757470', } -export type SubNetwork = T extends '0x500a' - ? HistoryNetwork - : T extends '0x504a' - ? HistoryNetwork - : T extends '0x500b' - ? StateNetwork - : T extends '0x504b' - ? StateNetwork - : T extends '0x500c' - ? BeaconNetwork - : T extends '0x504c' - ? BeaconNetwork - : never +export type SubNetwork = T extends '0x5009' + ? HeadStateNetwork + : T extends '0x5049' + ? HeadStateNetwork + : T extends '0x500a' + ? HistoryNetwork + : T extends '0x504a' + ? HistoryNetwork + : T extends '0x500b' + ? StateNetwork + : T extends '0x504b' + ? StateNetwork + : T extends '0x500c' + ? BeaconNetwork + : T extends '0x504c' + ? BeaconNetwork + : never export class Bloom { bitvector: Uint8Array From 7d52dc8b70c348076108e3b07e826053645522df Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 17:45:43 -0600 Subject: [PATCH 2/8] stub out class methods and constructor --- .../networks/headState/headStateNetwork.ts | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/portalnetwork/src/networks/headState/headStateNetwork.ts b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts index d65f31532..d5789ae94 100644 --- a/packages/portalnetwork/src/networks/headState/headStateNetwork.ts +++ b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts @@ -1,10 +1,29 @@ -import { BaseNetwork, NetworkId, type NetworkConfig } from '../../index.js' +import type { ENR } from '@chainsafe/enr' +import { + BaseNetwork, + type ContentLookupResponse, + NetworkId, + type BaseNetworkConfig, +} from '../../index.js' export class HeadStateNetwork extends BaseNetwork { networkName = 'HeadStateNetwork' networkId = NetworkId.HeadStateNetwork - constructor(config: NetworkConfig) { - super(config) + constructor({ client, db, radius, maxStorage }: BaseNetworkConfig) { + super({ client, networkId: NetworkId.HeadStateNetwork, db, radius, maxStorage }) + } + + store(contentKey: Uint8Array, value: Uint8Array): Promise { + throw new Error('Method not implemented.') + } + public findContentLocally = async (contentKey: Uint8Array): Promise => { + throw new Error('Method not implemented') + } + public sendFindContent = async ( + enr: ENR, + key: Uint8Array, + ): Promise => { + throw new Error('Method not implemented') } } From a4156fa8c8b4e16562c8d42689bfe7b9429d448c Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 17:47:02 -0600 Subject: [PATCH 3/8] add logger to network class --- .../portalnetwork/src/networks/headState/headStateNetwork.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/portalnetwork/src/networks/headState/headStateNetwork.ts b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts index d5789ae94..2982b3b9e 100644 --- a/packages/portalnetwork/src/networks/headState/headStateNetwork.ts +++ b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts @@ -5,6 +5,7 @@ import { NetworkId, type BaseNetworkConfig, } from '../../index.js' +import debug from 'debug' export class HeadStateNetwork extends BaseNetwork { networkName = 'HeadStateNetwork' @@ -12,6 +13,8 @@ export class HeadStateNetwork extends BaseNetwork { constructor({ client, db, radius, maxStorage }: BaseNetworkConfig) { super({ client, networkId: NetworkId.HeadStateNetwork, db, radius, maxStorage }) + this.logger = debug(this.enr.nodeId.slice(0, 5)).extend('Portal').extend('HeadStateNetwork') + this.routingTable.setLogger(this.logger) } store(contentKey: Uint8Array, value: Uint8Array): Promise { From 2de047c77820811a532582385b17e7f7a871b274 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 18:24:59 -0600 Subject: [PATCH 4/8] HeadState: "interested" method --- .../src/networks/headState/headStateNetwork.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/portalnetwork/src/networks/headState/headStateNetwork.ts b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts index 2982b3b9e..a80a97259 100644 --- a/packages/portalnetwork/src/networks/headState/headStateNetwork.ts +++ b/packages/portalnetwork/src/networks/headState/headStateNetwork.ts @@ -6,6 +6,8 @@ import { type BaseNetworkConfig, } from '../../index.js' import debug from 'debug' +import { bytesToUnprefixedHex } from '@ethereumjs/util' +import { distance } from '@chainsafe/discv5' export class HeadStateNetwork extends BaseNetwork { networkName = 'HeadStateNetwork' @@ -17,6 +19,13 @@ export class HeadStateNetwork extends BaseNetwork { this.routingTable.setLogger(this.logger) } + public interested = (contentId: Uint8Array) => { + const bits = contentId.length + const compareId = this.enr.nodeId.slice(0, bits) + const d = distance(compareId, bytesToUnprefixedHex(contentId)) + return d <= this.nodeRadius + } + store(contentKey: Uint8Array, value: Uint8Array): Promise { throw new Error('Method not implemented.') } From 53b2f030c25bf502f6db9e467454662e5efcf33f Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 18:25:21 -0600 Subject: [PATCH 5/8] HeadState: define content types + SSZ --- .../src/networks/headState/types.ts | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 packages/portalnetwork/src/networks/headState/types.ts diff --git a/packages/portalnetwork/src/networks/headState/types.ts b/packages/portalnetwork/src/networks/headState/types.ts new file mode 100644 index 000000000..8198e8e5a --- /dev/null +++ b/packages/portalnetwork/src/networks/headState/types.ts @@ -0,0 +1,63 @@ +import { ByteVectorType, ContainerType, ListCompositeType } from '@chainsafe/ssz' +import { Bytes32Type } from '../types.js' +import { Nibbles, TrieNode, type AddressHash, type TNibbles } from '../state/types.js' + +export enum HeadStateNetworkContentType { + AccountTrieDiff = 0x30, + AccountTrieNode = 0x31, + ContractTrieDiff = 0x32, + ContractTrieNode = 0x33, +} + +type BlockHash = Uint8Array + +export type AccountTrieNode = { + path: TNibbles + blockHash: BlockHash +} + +export const AccountTrieNodeType = new ContainerType({ + path: Nibbles, + blockHash: Bytes32Type, +}) + +type Path = Uint8Array +const PathType = new ByteVectorType(1) + +export type AccountTrieDiff = { + path: Path + blockHash: BlockHash +} + +const AccountTrieDiffType = new ContainerType({ + path: PathType, + blockHash: Bytes32Type, +}) + +export type ContractTrieNode = { + path: TNibbles + addressHash: AddressHash + blockHash: BlockHash +} + +export const ContractTrieNodeType = new ContainerType({ + path: Nibbles, + addressHash: Bytes32Type, + blockHash: Bytes32Type, +}) + +export type ContractTrieDiff = { + path: Path + blockHash: BlockHash +} + +export const ContractTrieDiffType = new ContainerType({ + path: PathType, + blockHash: Bytes32Type, +}) + +export const TrieNodeListType = new ListCompositeType(TrieNode, 65536) +export const TrieDiffType = new ContainerType({ + before: TrieNodeListType, + after: TrieNodeListType, +}) From 1c30d187bca4ce7cba6ee6bb2e4cdd6dcb46f6da Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 19:18:40 -0600 Subject: [PATCH 6/8] HeadStateNetwork: content key generator --- .../src/networks/headState/types.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/portalnetwork/src/networks/headState/types.ts b/packages/portalnetwork/src/networks/headState/types.ts index 8198e8e5a..f805124d7 100644 --- a/packages/portalnetwork/src/networks/headState/types.ts +++ b/packages/portalnetwork/src/networks/headState/types.ts @@ -61,3 +61,29 @@ export const TrieDiffType = new ContainerType({ before: TrieNodeListType, after: TrieNodeListType, }) + +export type HeadStateNetworkContent = + T extends HeadStateNetworkContentType.AccountTrieDiff + ? AccountTrieDiff + : T extends HeadStateNetworkContentType.AccountTrieNode + ? AccountTrieNode + : T extends HeadStateNetworkContentType.ContractTrieDiff + ? ContractTrieDiff + : T extends HeadStateNetworkContentType.ContractTrieNode + ? ContractTrieNode + : never + +const sszSerialize: { + [T in HeadStateNetworkContentType]: { + serialize: (content: HeadStateNetworkContent) => Uint8Array + } +} = { + [HeadStateNetworkContentType.AccountTrieDiff]: AccountTrieDiffType, + [HeadStateNetworkContentType.AccountTrieNode]: AccountTrieNodeType, + [HeadStateNetworkContentType.ContractTrieDiff]: ContractTrieDiffType, + [HeadStateNetworkContentType.ContractTrieNode]: ContractTrieNodeType, +} + +export function getContentKey(type: T, content: HeadStateNetworkContent) { + return Uint8Array.from([type, ...sszSerialize[type].serialize(content)]) +} \ No newline at end of file From 05eb25b50da01bfe3598c4536f3a1a3e85cd7174 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 19:48:15 -0600 Subject: [PATCH 7/8] HeadStateNetwork: define ContentValues and write serialization function --- .../src/networks/headState/types.ts | 88 +++++++++++++++++-- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/packages/portalnetwork/src/networks/headState/types.ts b/packages/portalnetwork/src/networks/headState/types.ts index f805124d7..8e50da186 100644 --- a/packages/portalnetwork/src/networks/headState/types.ts +++ b/packages/portalnetwork/src/networks/headState/types.ts @@ -1,6 +1,6 @@ import { ByteVectorType, ContainerType, ListCompositeType } from '@chainsafe/ssz' import { Bytes32Type } from '../types.js' -import { Nibbles, TrieNode, type AddressHash, type TNibbles } from '../state/types.js' +import { Nibbles, TrieNode, TrieProof, type AddressHash, type TNibbles, type TTrieProof } from '../state/types.js' export enum HeadStateNetworkContentType { AccountTrieDiff = 0x30, @@ -16,11 +16,25 @@ export type AccountTrieNode = { blockHash: BlockHash } +export const TrieNodeListType = new ListCompositeType(TrieNode, 65536) +export const TrieDiffType = new ContainerType({ + before: TrieNodeListType, + after: TrieNodeListType, +}) + export const AccountTrieNodeType = new ContainerType({ path: Nibbles, blockHash: Bytes32Type, }) +export type TAccountTrieNodeValue = { + proof: TTrieProof +} + +export const AccountTrieNodeValue = new ContainerType({ + proof: TrieProof, +}) + type Path = Uint8Array const PathType = new ByteVectorType(1) @@ -34,6 +48,17 @@ const AccountTrieDiffType = new ContainerType({ blockHash: Bytes32Type, }) +export type TAccountTrieDiffValue = { + subtrieDiff: { + before: TTrieProof + after: TTrieProof + } +} + +const AccountTrieDiffValue = new ContainerType({ + subtrieDiff: TrieDiffType, +}) + export type ContractTrieNode = { path: TNibbles addressHash: AddressHash @@ -46,6 +71,16 @@ export const ContractTrieNodeType = new ContainerType({ blockHash: Bytes32Type, }) +export type TContractTrieNodeValue = { + storageProof: TTrieProof + accountProof: TTrieProof +} + +export const ContractTrieNodeValue = new ContainerType({ + storageProof: TrieProof, + accountProof: TrieProof, +}) + export type ContractTrieDiff = { path: Path blockHash: BlockHash @@ -56,10 +91,17 @@ export const ContractTrieDiffType = new ContainerType({ blockHash: Bytes32Type, }) -export const TrieNodeListType = new ListCompositeType(TrieNode, 65536) -export const TrieDiffType = new ContainerType({ - before: TrieNodeListType, - after: TrieNodeListType, +export type TContractTrieDiffValue = { + subtrieDiff: { + before: TTrieProof + after: TTrieProof + } + accountProof: TTrieProof +} + +const ContractTrieDiffValue = new ContainerType({ + subtrieDiff: TrieDiffType, + accountProof: TrieProof, }) export type HeadStateNetworkContent = @@ -84,6 +126,38 @@ const sszSerialize: { [HeadStateNetworkContentType.ContractTrieNode]: ContractTrieNodeType, } -export function getContentKey(type: T, content: HeadStateNetworkContent) { - return Uint8Array.from([type, ...sszSerialize[type].serialize(content)]) +export function getContentKey( + type: T, + content: HeadStateNetworkContent, +) { + return Uint8Array.from([type, ...sszSerialize[type].serialize(content)]) +} + +type TContentValue = + T extends HeadStateNetworkContentType.AccountTrieDiff + ? TAccountTrieDiffValue + : T extends HeadStateNetworkContentType.AccountTrieNode + ? TAccountTrieNodeValue + : T extends HeadStateNetworkContentType.ContractTrieDiff + ? TContractTrieDiffValue + : T extends HeadStateNetworkContentType.ContractTrieNode + ? TContractTrieNodeValue + : never + +export const serializeContentValue: { + [T in HeadStateNetworkContentType]: { + serialize: (content: TContentValue) => Uint8Array + } +} = { + [HeadStateNetworkContentType.AccountTrieDiff]: AccountTrieDiffValue, + [HeadStateNetworkContentType.AccountTrieNode]: AccountTrieNodeValue, + [HeadStateNetworkContentType.ContractTrieDiff]: ContractTrieDiffValue, + [HeadStateNetworkContentType.ContractTrieNode]: ContractTrieNodeValue, +} + +export function getContentValue( + type: T, + content: TContentValue, +) { + return serializeContentValue[type].serialize(content) } \ No newline at end of file From 3b9392ce4799d51bc3b19f8d992795bf3de047ea Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 6 May 2025 20:06:46 -0600 Subject: [PATCH 8/8] HeadStateNetwork: organize types file and make naming consistent --- .../src/networks/headState/types.ts | 160 +++++++++--------- 1 file changed, 84 insertions(+), 76 deletions(-) diff --git a/packages/portalnetwork/src/networks/headState/types.ts b/packages/portalnetwork/src/networks/headState/types.ts index 8e50da186..6c885c6d9 100644 --- a/packages/portalnetwork/src/networks/headState/types.ts +++ b/packages/portalnetwork/src/networks/headState/types.ts @@ -2,6 +2,7 @@ import { ByteVectorType, ContainerType, ListCompositeType } from '@chainsafe/ssz import { Bytes32Type } from '../types.js' import { Nibbles, TrieNode, TrieProof, type AddressHash, type TNibbles, type TTrieProof } from '../state/types.js' +// Network content type definitions export enum HeadStateNetworkContentType { AccountTrieDiff = 0x30, AccountTrieNode = 0x31, @@ -9,89 +10,57 @@ export enum HeadStateNetworkContentType { ContractTrieNode = 0x33, } +// Common type aliases type BlockHash = Uint8Array +type Path = Uint8Array -export type AccountTrieNode = { - path: TNibbles - blockHash: BlockHash -} - +// Trie node and diff base types export const TrieNodeListType = new ListCompositeType(TrieNode, 65536) export const TrieDiffType = new ContainerType({ before: TrieNodeListType, after: TrieNodeListType, }) -export const AccountTrieNodeType = new ContainerType({ - path: Nibbles, - blockHash: Bytes32Type, -}) +// Account Trie types +export type AccountTrieNode = { + path: TNibbles + blockHash: BlockHash +} -export type TAccountTrieNodeValue = { +export type AccountTrieNodeValue = { proof: TTrieProof } -export const AccountTrieNodeValue = new ContainerType({ - proof: TrieProof, -}) - -type Path = Uint8Array -const PathType = new ByteVectorType(1) - export type AccountTrieDiff = { path: Path blockHash: BlockHash } -const AccountTrieDiffType = new ContainerType({ - path: PathType, - blockHash: Bytes32Type, -}) - -export type TAccountTrieDiffValue = { +export type AccountTrieDiffValue = { subtrieDiff: { before: TTrieProof after: TTrieProof } } -const AccountTrieDiffValue = new ContainerType({ - subtrieDiff: TrieDiffType, -}) - +// Contract Trie types export type ContractTrieNode = { path: TNibbles addressHash: AddressHash blockHash: BlockHash } -export const ContractTrieNodeType = new ContainerType({ - path: Nibbles, - addressHash: Bytes32Type, - blockHash: Bytes32Type, -}) - -export type TContractTrieNodeValue = { +export type ContractTrieNodeValue = { storageProof: TTrieProof accountProof: TTrieProof } -export const ContractTrieNodeValue = new ContainerType({ - storageProof: TrieProof, - accountProof: TrieProof, -}) - export type ContractTrieDiff = { path: Path blockHash: BlockHash } -export const ContractTrieDiffType = new ContainerType({ - path: PathType, - blockHash: Bytes32Type, -}) - -export type TContractTrieDiffValue = { +export type ContractTrieDiffValue = { subtrieDiff: { before: TTrieProof after: TTrieProof @@ -99,11 +68,49 @@ export type TContractTrieDiffValue = { accountProof: TTrieProof } -const ContractTrieDiffValue = new ContainerType({ +// SSZ serialization types +const PathType = new ByteVectorType(1) + +export const AccountTrieNodeSszType = new ContainerType({ + path: Nibbles, + blockHash: Bytes32Type, +}) + +export const AccountTrieNodeValueSszType = new ContainerType({ + proof: TrieProof, +}) + +export const AccountTrieDiffSszType = new ContainerType({ + path: PathType, + blockHash: Bytes32Type, +}) + +export const AccountTrieDiffValueSszType = new ContainerType({ + subtrieDiff: TrieDiffType, +}) + +export const ContractTrieNodeSszType = new ContainerType({ + path: Nibbles, + addressHash: Bytes32Type, + blockHash: Bytes32Type, +}) + +export const ContractTrieNodeValueSszType = new ContainerType({ + storageProof: TrieProof, + accountProof: TrieProof, +}) + +export const ContractTrieDiffSszType = new ContainerType({ + path: PathType, + blockHash: Bytes32Type, +}) + +export const ContractTrieDiffValueSszType = new ContainerType({ subtrieDiff: TrieDiffType, accountProof: TrieProof, }) +// Content type mapping export type HeadStateNetworkContent = T extends HeadStateNetworkContentType.AccountTrieDiff ? AccountTrieDiff @@ -115,49 +122,50 @@ export type HeadStateNetworkContent = ? ContractTrieNode : never -const sszSerialize: { +export type HeadStateNetworkContentValue = + T extends HeadStateNetworkContentType.AccountTrieDiff + ? AccountTrieDiffValue + : T extends HeadStateNetworkContentType.AccountTrieNode + ? AccountTrieNodeValue + : T extends HeadStateNetworkContentType.ContractTrieDiff + ? ContractTrieDiffValue + : T extends HeadStateNetworkContentType.ContractTrieNode + ? ContractTrieNodeValue + : never + +// Serialization helpers +const contentKeySerializers: { [T in HeadStateNetworkContentType]: { serialize: (content: HeadStateNetworkContent) => Uint8Array } } = { - [HeadStateNetworkContentType.AccountTrieDiff]: AccountTrieDiffType, - [HeadStateNetworkContentType.AccountTrieNode]: AccountTrieNodeType, - [HeadStateNetworkContentType.ContractTrieDiff]: ContractTrieDiffType, - [HeadStateNetworkContentType.ContractTrieNode]: ContractTrieNodeType, + [HeadStateNetworkContentType.AccountTrieDiff]: AccountTrieDiffSszType, + [HeadStateNetworkContentType.AccountTrieNode]: AccountTrieNodeSszType, + [HeadStateNetworkContentType.ContractTrieDiff]: ContractTrieDiffSszType, + [HeadStateNetworkContentType.ContractTrieNode]: ContractTrieNodeSszType, +} + +const contentValueSerializers: { + [T in HeadStateNetworkContentType]: { + serialize: (content: HeadStateNetworkContentValue) => Uint8Array + } +} = { + [HeadStateNetworkContentType.AccountTrieDiff]: AccountTrieDiffValueSszType, + [HeadStateNetworkContentType.AccountTrieNode]: AccountTrieNodeValueSszType, + [HeadStateNetworkContentType.ContractTrieDiff]: ContractTrieDiffValueSszType, + [HeadStateNetworkContentType.ContractTrieNode]: ContractTrieNodeValueSszType, } export function getContentKey( type: T, content: HeadStateNetworkContent, ) { - return Uint8Array.from([type, ...sszSerialize[type].serialize(content)]) -} - -type TContentValue = - T extends HeadStateNetworkContentType.AccountTrieDiff - ? TAccountTrieDiffValue - : T extends HeadStateNetworkContentType.AccountTrieNode - ? TAccountTrieNodeValue - : T extends HeadStateNetworkContentType.ContractTrieDiff - ? TContractTrieDiffValue - : T extends HeadStateNetworkContentType.ContractTrieNode - ? TContractTrieNodeValue - : never - -export const serializeContentValue: { - [T in HeadStateNetworkContentType]: { - serialize: (content: TContentValue) => Uint8Array - } -} = { - [HeadStateNetworkContentType.AccountTrieDiff]: AccountTrieDiffValue, - [HeadStateNetworkContentType.AccountTrieNode]: AccountTrieNodeValue, - [HeadStateNetworkContentType.ContractTrieDiff]: ContractTrieDiffValue, - [HeadStateNetworkContentType.ContractTrieNode]: ContractTrieNodeValue, + return Uint8Array.from([type, ...contentKeySerializers[type].serialize(content)]) } export function getContentValue( type: T, - content: TContentValue, + content: HeadStateNetworkContentValue, ) { - return serializeContentValue[type].serialize(content) + return contentValueSerializers[type].serialize(content) } \ No newline at end of file