2023-11-09 16:37:18 -08:00
import { sha256 } from "@noble/hashes/sha256" ;
2023-11-28 15:57:18 +05:30
import type { PubsubTopic , ShardInfo , SingleShardInfo } from "@waku/interfaces" ;
2023-10-10 20:18:02 +05:30
2023-11-09 16:37:18 -08:00
import { concat , utf8ToBytes } from "../bytes/index.js" ;
2023-11-28 15:57:18 +05:30
export const singleShardInfoToPubsubTopic = (
shardInfo : SingleShardInfo
) : PubsubTopic = > {
if ( shardInfo . cluster === undefined || shardInfo . index === undefined )
throw new Error ( "Invalid shard" ) ;
return ` /waku/2/rs/ ${ shardInfo . cluster } / ${ shardInfo . index } ` ;
} ;
2023-11-14 21:22:52 +05:30
export const shardInfoToPubsubTopics = (
2023-10-10 20:18:02 +05:30
shardInfo : ShardInfo
2023-11-14 21:22:52 +05:30
) : PubsubTopic [ ] = > {
2023-11-28 15:57:18 +05:30
if ( shardInfo . cluster === undefined || shardInfo . indexList === undefined )
throw new Error ( "Invalid shard" ) ;
2023-10-10 20:18:02 +05:30
return shardInfo . indexList . map (
( index ) = > ` /waku/2/rs/ ${ shardInfo . cluster } / ${ index } `
) ;
} ;
2023-09-27 15:28:07 +05:30
2023-11-28 15:57:18 +05:30
export const pubsubTopicToSingleShardInfo = (
pubsubTopics : PubsubTopic
) : SingleShardInfo = > {
const parts = pubsubTopics . split ( "/" ) ;
if ( parts . length != 6 ) throw new Error ( "Invalid pubsub topic" ) ;
const cluster = parseInt ( parts [ 4 ] ) ;
const index = parseInt ( parts [ 5 ] ) ;
if ( isNaN ( cluster ) || isNaN ( index ) ) throw new Error ( "Invalid pubsub topic" ) ;
return { cluster , index } ;
} ;
2023-09-27 15:28:07 +05:30
export function ensurePubsubTopicIsConfigured (
2023-11-14 21:22:52 +05:30
pubsubTopic : PubsubTopic ,
configuredTopics : PubsubTopic [ ]
2023-09-27 15:28:07 +05:30
) : void {
if ( ! configuredTopics . includes ( pubsubTopic ) ) {
throw new Error (
2023-11-14 21:22:52 +05:30
` Pubsub topic ${ pubsubTopic } has not been configured on this instance. Configured topics are: ${ configuredTopics } . Please update your configuration by passing in the topic during Waku node instantiation. `
2023-09-27 15:28:07 +05:30
) ;
}
}
2023-11-07 20:06:44 -08:00
2023-11-09 16:37:18 -08:00
interface ContentTopic {
generation : number ;
application : string ;
version : string ;
topicName : string ;
encoding : string ;
}
2023-11-07 20:06:44 -08:00
/ * *
* Given a string , will throw an error if it is not formatted as a valid content topic for autosharding based on https : //rfc.vac.dev/spec/51/
* @param contentTopic String to validate
2023-11-09 16:37:18 -08:00
* @returns Object with each content topic field as an attribute
2023-11-07 20:06:44 -08:00
* /
2023-11-09 16:37:18 -08:00
export function ensureValidContentTopic ( contentTopic : string ) : ContentTopic {
2023-11-07 20:06:44 -08:00
const parts = contentTopic . split ( "/" ) ;
if ( parts . length < 5 || parts . length > 6 ) {
throw Error ( "Content topic format is invalid" ) ;
}
// Validate generation field if present
2023-11-09 16:37:18 -08:00
let generation = 0 ;
2023-11-07 20:06:44 -08:00
if ( parts . length == 6 ) {
2023-11-09 16:37:18 -08:00
generation = parseInt ( parts [ 1 ] ) ;
2023-11-07 20:06:44 -08:00
if ( isNaN ( generation ) ) {
throw new Error ( "Invalid generation field in content topic" ) ;
}
if ( generation > 0 ) {
throw new Error ( "Generation greater than 0 is not supported" ) ;
}
}
// Validate remaining fields
const fields = parts . splice ( - 4 ) ;
// Validate application field
if ( fields [ 0 ] . length == 0 ) {
throw new Error ( "Application field cannot be empty" ) ;
}
// Validate version field
if ( fields [ 1 ] . length == 0 ) {
throw new Error ( "Version field cannot be empty" ) ;
}
// Validate topic name field
if ( fields [ 2 ] . length == 0 ) {
throw new Error ( "Topic name field cannot be empty" ) ;
}
// Validate encoding field
if ( fields [ 3 ] . length == 0 ) {
throw new Error ( "Encoding field cannot be empty" ) ;
}
2023-11-09 16:37:18 -08:00
return {
generation ,
application : fields [ 0 ] ,
version : fields [ 1 ] ,
topicName : fields [ 2 ] ,
encoding : fields [ 3 ]
} ;
}
/ * *
* Given a string , determines which autoshard index to use for its pubsub topic .
* Based on the algorithm described in the RFC : https : //rfc.vac.dev/spec/51//#algorithm
* /
export function contentTopicToShardIndex (
contentTopic : string ,
networkShards : number = 8
) : number {
const { application , version } = ensureValidContentTopic ( contentTopic ) ;
const digest = sha256 (
concat ( [ utf8ToBytes ( application ) , utf8ToBytes ( version ) ] )
) ;
const dataview = new DataView ( digest . buffer . slice ( - 8 ) ) ;
return Number ( dataview . getBigUint64 ( 0 , false ) % BigInt ( networkShards ) ) ;
}
export function contentTopicToPubsubTopic (
contentTopic : string ,
clusterId : number = 1 ,
networkShards : number = 8
) : string {
const shardIndex = contentTopicToShardIndex ( contentTopic , networkShards ) ;
return ` /waku/2/rs/ ${ clusterId } / ${ shardIndex } ` ;
2023-11-07 20:06:44 -08:00
}