import { logger } from '../common/RadixLogger' ; import promiseRetry from 'promise-retry' ; import { radixTokenManager , shuffleArray , RadixNode , RadixUniverseConfig , RadixNodeDiscoveryHardcoded , RadixNodeDiscovery , RadixNodeConnection , RadixLedger , RadixAtomStore , RadixAtomNodeStatus , RadixBootstrapConfig , } from '../..' ; import { RRI , RadixFixedSupplyTokenDefinitionParticle , RadixMutableSupplyTokenDefinitionParticle } from '../atommodel' ; import ipaddr from 'ipaddr.js' ; import { RadixNEDBAtomStore } from '../ledger/RadixNEDBAtomStore' ; import { RadixPartialBootstrapConfig } from './RadixBootstrapConfig' ; import axios from 'axios' ; process . on ( 'unhandledRejection' , ( reason , promise ) => { } ) ; export default class RadixUniverse { public static LOCAL_SINGLE_NODE : RadixBootstrapConfig = { universeConfig : RadixUniverseConfig . LOCAL , nodeDiscovery : new RadixNodeDiscoveryHardcoded ( [ 'localhost:8080' ] , false ) , finalityTime : 0 , } ; public initialized = false ; public universeConfig : RadixUniverseConfig ; public nodeDiscovery : RadixNodeDiscovery ; public ledger : RadixLedger ; public nativeToken : RRI ; private liveNodes : RadixNode [ ] = [ ] ; private connectedNodes : RadixNodeConnection [ ] = [ ] ; private lastNetworkUpdate = 0 ; private networkUpdateInterval = 1000 * 60 * 10 ; param config public async bootstrap ( config , atomStore ? ) { await this . closeAllConnections ( ) ; this . connectedNodes = [ ] ; this . liveNodes = [ ] ; this . lastNetworkUpdate = 0 ; this . universeConfig = config . universeConfig ; this . nodeDiscovery = config . nodeDiscovery ; this . universeConfig . initialize ( ) ; for ( const atom of this . universeConfig . genesis ) { const tokenClasses = [ ] . concat ( atom . getParticlesOfType ( RadixFixedSupplyTokenDefinitionParticle ) ) . concat ( atom . getParticlesOfType ( RadixMutableSupplyTokenDefinitionParticle ) ) ; if ( tokenClasses . length === 0 ) { logger . error ( `Couldn't find native token in genesis` ) ; throw new Error ( `Couldn't find native token in genesis` ) ; } else { if ( tokenClasses . length > 1 ) { logger . error ( 'More than 1 tokens defined in genesis, using the first' ) ; } this . nativeToken = tokenClasses [ 0 ] . getRRI ( ) ; } } if ( ! atomStore ) { atomStore = RadixNEDBAtomStore . createInMemoryStore ( ) ; } for ( const atom of this . universeConfig . genesis ) { await atomStore . insert ( atom , { status : RadixAtomNodeStatus . STORED_FINAL } ) ; } this . ledger = new RadixLedger ( this , atomStore , config . finalityTime ) ; this . initialized = true ; radixTokenManager . initialize ( this . nativeToken ) ; } /**
     * Bootstraps the universe using the universe config of connected nodes.
     */ public async bootstrapTrustedNode ( partialBootstrapConfig , atomStore ? , useSSL = false ) { const nodes = await partialBootstrapConfig . nodeDiscovery . loadNodes ( ) ; if ( ! nodes [ 0 ] ) { logger . error ( 'ERROR: No nodes found.' ) ; throw new Error ( 'ERROR: No nodes found.' ) ; } const nodeUrl = new URL ( nodes [ 0 ] . httpAddress ) ; const universeConfigData = ( await axios . get ( `${ useSSL ? 'https' : 'http' }://${ nodeUrl . host }/api/universe` ) ) . data ; const nodeUniverseConfig = new RadixUniverseConfig ( universeConfigData ) ; nodeUniverseConfig . initialize ( ) ; const bootstrapConfig = { ... partialBootstrapConfig , universeConfig : nodeUniverseConfig , } ; await this . bootstrap ( bootstrapConfig , atomStore ) ; } returns public getMagicByte ( ) { this . isInitialized ( ) ; return this . universeConfig . getMagicByte ( ) ; } private loadPeersFromBootstrap ( ) { return promiseRetry ( async ( retry , attempt ) => { try { this . liveNodes = await this . nodeDiscovery . loadNodes ( ) ; this . lastNetworkUpdate = Date . now ( ) ; return this . liveNodes ; } catch ( error ) { logger . error ( error ) ; retry ( error ) ; } } , { retries : 5 , maxtimeout : 10000 , } ) ; } returns public getNodeConnection ( ) { this . isInitialized ( ) ; return new Promise < RadixNodeConnection > ( ( resolve , reject ) => { for ( const node of this . connectedNodes ) { if ( node . isReady ( ) ) { logger . info ( 'Got an active connection' ) ; return resolve ( node ) ; } } for ( const nodeConnection of this . connectedNodes ) { logger . info ( 'Got a pending connection' ) ; nodeConnection . on ( 'open' , ( ) => { resolve ( nodeConnection ) ; } ) ; nodeConnection . on ( 'closed' , ( ) => { resolve ( this . getNodeConnection ( ) ) ; } ) ; return ; } logger . info ( 'Opening a new connection' ) ; this . openNodeConnection ( ) . then ( ( connection ) => { if ( connection ) { resolve ( connection ) ; } else { reject ( new Error ( `Couldn't find a node to connect to` ) ) ; } } ) . catch ( e => { reject ( e ) ; } ) ; } ) ; } private async openNodeConnection ( ) { if ( Date . now ( ) - this . lastNetworkUpdate > this . networkUpdateInterval ) { await this . loadPeersFromBootstrap ( ) ; } this . liveNodes = shuffleArray ( this . liveNodes ) ; for ( const node of this . liveNodes ) { const connection = new RadixNodeConnection ( node ) ; this . connectedNodes . push ( connection ) ; connection . on ( 'closed' , ( ) => { const nodeIndex = this . connectedNodes . indexOf ( connection ) ; if ( nodeIndex > - 1 ) { this . connectedNodes . splice ( nodeIndex , 1 ) ; } } ) ; try { await connection . openConnection ( ) ; } catch ( error ) { logger . error ( error ) ; return null ; } return connection ; } return null ; } /**
     * Close all open connections
     * Recommended to call this before quitting the application, so that nodes can close the corresponding open connections as well
     */ public closeAllConnections = async ( ) => { const tasks = [ ] ; for ( const connection of this . connectedNodes ) { tasks . push ( connection . close ( ) ) ; } await Promise . all ( tasks ) ; } ; /**
     * Get the latest known list of the live nodes in the network
     * Updated 60 seconds after the previous update whenever a new connection is requested
     */ public getLiveNodes ( ) { return this . liveNodes ; } private isInitialized ( ) { if ( ! this . initialized ) { throw new Error ( 'Universe needs to be initialized before using the library, please call "radixUniverse.bootstrap" with a universe configuration' ) ; } } param address public static resolveNodeName ( address ) { try { const ipbytes = ipaddr . parse ( address ) . toByteArray ( ) ; if ( ipbytes . length === 4 ) { const shl = ( base , exp ) => base * Math . pow ( 2 , exp ) ; const ip = ipbytes [ 3 ] + shl ( ipbytes [ 2 ] , 8 ) + shl ( ipbytes [ 1 ] , 16 ) + shl ( ipbytes [ 0 ] , 24 ) ; return `a${ ip . toString ( 36 ) }.radixnode.net` ; } logger . warn ( 'No base36 encoder for IPv6 yet' ) ; return `[${ address }]` ; } catch ( err ) { } return address ; } } export const radixUniverse = new RadixUniverse ( ) ;