@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
95 lines • 3.98 kB
JavaScript
import { digest } from "@chainsafe/as-sha256";
import { ForkName } from "@lodestar/params";
import { intToBytes } from "@lodestar/utils";
import { compress, uncompress } from "snappyjs";
import xxhashFactory from "xxhash-wasm";
import { MESSAGE_DOMAIN_VALID_SNAPPY } from "./constants.js";
import { getGossipSSZType } from "./topic.js";
// Load WASM
const xxhash = await xxhashFactory();
// Use salt to prevent msgId from being mined for collisions
const h64Seed = BigInt(Math.floor(Math.random() * 1e9));
// Shared buffer to convert msgId to string
const sharedMsgIdBuf = Buffer.alloc(20);
/**
* The function used to generate a gossipsub message id
* We use the first 8 bytes of SHA256(data) for content addressing
*/
export function fastMsgIdFn(rpcMsg) {
if (rpcMsg.data) {
return xxhash.h64Raw(rpcMsg.data, h64Seed).toString(16);
}
return "0000000000000000";
}
export function msgIdToStrFn(msgId) {
// this is the same logic to `toHex(msgId)` with better performance
sharedMsgIdBuf.set(msgId);
return `0x${sharedMsgIdBuf.toString("hex")}`;
}
/**
* Only valid msgId. Messages that fail to snappy_decompress() are not tracked
*/
export function msgIdFn(gossipTopicCache, msg) {
const topic = gossipTopicCache.getTopic(msg.topic);
let vec;
if (topic.boundary.fork === ForkName.phase0) {
// message id for phase0.
// ```
// SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]
// ```
vec = [MESSAGE_DOMAIN_VALID_SNAPPY, msg.data];
}
else {
// message id for altair and subsequent future forks.
// ```
// SHA256(
// MESSAGE_DOMAIN_VALID_SNAPPY +
// uint_to_bytes(uint64(len(message.topic))) +
// message.topic +
// snappy_decompress(message.data)
// )[:20]
// ```
// https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/p2p-interface.md#topics-and-messages
vec = [MESSAGE_DOMAIN_VALID_SNAPPY, intToBytes(msg.topic.length, 8), Buffer.from(msg.topic), msg.data];
}
return digest(Buffer.concat(vec)).subarray(0, 20);
}
export class DataTransformSnappy {
constructor(gossipTopicCache, maxSizePerMessage) {
this.gossipTopicCache = gossipTopicCache;
this.maxSizePerMessage = maxSizePerMessage;
}
/**
* Takes the data published by peers on a topic and transforms the data.
* Should be the reverse of outboundTransform(). Example:
* - `inboundTransform()`: decompress snappy payload
* - `outboundTransform()`: compress snappy payload
*/
inboundTransform(topicStr, data) {
const uncompressedData = uncompress(data, this.maxSizePerMessage);
// check uncompressed data length before we extract beacon block root, slot or
// attestation data at later steps
const uncompressedDataLength = uncompressedData.length;
const topic = this.gossipTopicCache.getTopic(topicStr);
const sszType = getGossipSSZType(topic);
if (uncompressedDataLength < sszType.minSize) {
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} < ${sszType.minSize}`);
}
if (uncompressedDataLength > sszType.maxSize) {
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${sszType.maxSize}`);
}
return uncompressedData;
}
/**
* Takes the data to be published (a topic and associated data) transforms the data. The
* transformed data will then be used to create a `RawGossipsubMessage` to be sent to peers.
*/
outboundTransform(_topicStr, data) {
if (data.length > this.maxSizePerMessage) {
throw Error(`ssz_snappy encoded data length ${data.length} > ${this.maxSizePerMessage}`);
}
// No need to parse topic, everything is snappy compressed
return compress(data);
}
}
//# sourceMappingURL=encoding.js.map