Proficient in IPFS | IPFS startup start function

In the system startup, a total of two start functions are executed, one is the preStart function, and the other is the start function I studied today. This function is located in the core / components / start.js file, its main role is to actually start the system, its main body is a series, the old rules we directly analyze several of its functions.

  1. Execute the first function, check if the repository is closed, and if so open the repository, otherwise, call the next function. The specific code is as follows:
     (cb) => {    self._repo.closed ? : cb() } 
  2. Execute the second function, generate a libp2p object based on the repository's configuration file, and call its startup method, and then set the IPFS object's libp2p to the generated libp2p object. The specific code is as follows:
     (cb) => {    self._repo.config.get((err, config) => {      if (err) return cb(err) 
       const libp2p = createLibp2pBundle(self, config) 

Libp2p.start(err => {
If (err) return cb(err)
Self.libp2p = libp2p


createLibp2pBundle function is located in the libp2p.js file in the current directory. The execution flow of this function is as follows:

  • Generate configuration objects and option objects, the former is passed in by parameters, and the latter is obtained through IPFS objects.
     const options = self._options || {} config = config || {} 
  • Determine how to create a libp2p object. If the creation method is specified in the options object, the specified creation method is used, otherwise the default creation method is used. By default, the user does not specify a creation method, so the default creation method is used here.
     const createBundle = typeof options.libp2p === 'function'    ? options.libp2p    : defaultBundle 
  • Get the information needed to create a libp2p object from an IPFS object.
     const { datastore } = self._repo const peerInfo = self._peerInfo const peerBook = self._peerInfoBook 
  • Call the create method to create a libp2p object. The default creation method is as follows:
    • First, set the default options.
       const libp2pDefaults = {    datastore,    peerInfo,    peerBook,    config: {      peerDiscovery: {        mdns: {          enabled: get(options, 'config.Discovery.MDNS.Enabled',            get(config, 'Discovery.MDNS.Enabled', true))        },        webRTCStar: {          enabled: get(options, 'config.Discovery.webRTCStar.Enabled',            get(config, 'Discovery.webRTCStar.Enabled', true))        },        bootstrap: {          list: get(options, 'config.Bootstrap',            get(config, 'Bootstrap', []))        }      },      relay: {        enabled: get(options, 'relay.enabled',          get(config, 'relay.enabled', true)),        hop: {          enabled: get(options, 'relay.hop.enabled',            get(config, 'relay.hop.enabled', false)),          active: get(options, '',            get(config, '', false))        }      },      dht: {        kBucketSize: get(options, 'dht.kBucketSize', 20),        enabled: false,        randomWalk: {          enabled: false // disabled waiting for        },        validators: {          ipns: ipnsUtils.validator        },        selectors: {          ipns: ipnsUtils.selector        }      },      EXPERIMENTAL: {        pubsub: get(options, 'EXPERIMENTAL.pubsub', false)      }    },    connectionManager: get(options, 'connectionManager',      {        maxPeers: get(config, 'Swarm.ConnMgr.HighWater'),        minPeers: get(config, 'Swarm.ConnMgr.LowWater')    }) } 

      Among them, datastore , peerInfo , peerBook , options and other related private attributes from IPFS objects, config comes from the configuration file of the final generated warehouse and the user-specified configuration.

    • Then, call the mergeOptions method to merge the default options with the user-specified options.
       const libp2pOptions = mergeOptions(libp2pDefaults, get(options, 'libp2p', {})) 
    • Finally, load the Node object defined in the core/runtime/libp2p-nodejs.js file (inherited from the object defined by the libp2p library) and call its constructor to generate the libp2p object.
       const Node = require('../runtime/libp2p-nodejs') return new Node(libp2pOptions) 

      libp2p-nodejs.js file mainly defines the default options for creating libp2p objects, and merges the previously generated options with the default options, and then calls the constructor of the parent class to create the object. The specific default options are:

       {  switch: {    blacklistTTL: 2 * 60 * 1e3, // 2 minute base    blackListAttempts: 5, // back off 5 times    maxParallelDials: 150,    maxColdCalls: 50,    dialTimeout: 10e3 // Be strict with dial time  },  modules: {    transport: [      TCP,      WS,      wsstar    ],    streamMuxer: [      Multiplex    ],    connEncryption: [      SECIO    ],    peerDiscovery: [      MulticastDNS,      Bootstrap,      wsstar.discovery    ],    dht: KadDHT  },  config: {    peerDiscovery: {      autoDial: true,      mdns: {        enabled: true      },      bootstrap: {        enabled: true      },      websocketStar: {        enabled: true      }    },    dht: {      kBucketSize: 20,      enabled: false,      randomWalk: {        enabled: false      }    },    EXPERIMENTAL: {      pubsub: false    }  } } 

    From the above code, we can find that the process of creating libp2p is more complicated. The actual type of libp2p object is the object defined in the libp2p library.

Because libp2p is a very, very important component/library that can be used in IPFS/Filecoin, it can be used independently, or in other projects. Since it is so important, we will specifically explain it in the future. This is just a simple matter.

The libp2p object inherits from the EventEmitter class, so it can trigger an event, and it also has a state variable of type fsm-event internally, so it can also be considered a state machine object.

  • Call the start method of the libp2p object to start the libp2p object. When the libp2p object starts successfully, save it in the same name property of the IPFS object. The specific code is as follows:
     libp2p.start(err => {    if (err) return cb(err)    self.libp2p = libp2p    cb() } 

    The start method of the libp2p object sets the internal state to start , which causes libp2p to call its _onStarting method and start the startup process. The specific processing is as follows:

    • Check if there is a specific transmission method configured, such as TCP. If no transfer method is specified, an exception is thrown. By default, three transmission methods such as TCP, WS, and wsstar are configured.
    • Check all multiaddr addresses of the node. If no node ID is specified, the address is appended with /p2p/node ID. The peerInfo of the libp2p object is derived from the peerInfo of the IPFS object, which is generated and initialized in the preStart function. The included multiaddr is from the Addresses.Swarm array of the configuration file, /p2p-websocket-star generated by libp2p-nodejs.js , The /p2p-circuit/ipfs/节点ID generated by the previous constructor. The last address is only when the modules.streamMuxer and relay.enabled are configured, that is, when stream multiplexing and circuit relay are enabled, when the circuit relay object is generated when the enableCircuitRelay method of the switch.connection object is called in the constructor. Will be generated and added to the address of the node information object. After this step, the multiaddrs final node information object becomes "/ip4/节点ID , /ip4/节点ID , /p2p-websocket-star/ipfs/节点ID , /p2p-circuit/ipfs/节点ID form.
    • Traverse all the transport methods specified by the peer node. If a transport method can process the address specified by the node, it is saved to the switch.transport object (of type TransportManager). The purpose of this step is to filter the transmission method with the address of the node. Only the transmission method that can handle an address is saved to the switch.transport object. However, each transfer method is saved to the _transport array of the libp2p object itself.
    • Start all services serially. For example: switch service, DHT service, node discovery service, etc., if these services are configured. The switch object manages all network communication related services. It is also a state machine. It usually changes the state to execute different methods. When it starts its service, it will finally execute the _onStarting method. This method will make all available (ie, there is an address. The transport object of the listener starts to listen, for example, the TCP transport method listens on port 4002. After the node discovery service finds the node, it will trigger the event peer:discovery , and will save the discovered node to the peerBook . If the current number of connections is less than the specified number, the connection will be made.
    • After all services are started, it traverses all the addresses saved in the peerBook , triggers the event peer:discovery , and connects if the current number of connections is less than the specified number.
  • Executing the third function, the content of this function is also more, we will slowly look.
    • First, generate an IPNS object and set the _ipns of the IPFS object to generate an IPNS object.
       const ipnsRouting = routingConfig(self) self._ipns = new IPNS(ipnsRouting, self._repo.datastore, self._peerInfo, self._keychain, self._options) 
    • Then, generate a Bitswap object and set the _bitswap of the IPFS object to the generated Bitswap object, and call the latter's startup method;
       self._bitswap = new Bitswap(  self.libp2p,  self._repo.blocks,  { statsEnabled: true } ) self._bitswap.start() 

      The Bitswap object is another very object in the IPFS/libp2p architecture. It determines whether to load a block from a local repository or request a block from another node in the network. It also determines whether another node requests a block request. Its start method in turn starts the WantManager object (an object that periodically sends request messages to other nodes), Network object (how a specified libp2p/switch object handles the bitswap protocol, and listens for libp2p object node connection/disconnection events) Object), DecisionEngine object (an object that determines whether to respond to requests from other nodes).

    • Next, call the setExchange method of the blockService object to set the object of the former swap block to the newly generated Bitswap object.

      Before the method is executed, when the block service object request block is called, the block is loaded from the local warehouse; when the method is executed, the block service object needs to determine the block through the bitswap object when requesting the block. Where did you get it?

    • Next, call the start method of several objects to start.
       self._preload.start() self._ipns.republisher.start() self._mfsPreload.start(cb) 

      The start method of the preloaded object simply sets the internal variable start to false; the IPNS starts, and the analysis of the IPNS will be analyzed later, skipped here. The startup method of _mfsPreload is also relatively simple, just call the stat method of the files object of the IPFS object and load the root directory. The root directory object is initialized during the initialization function, saving init-files/init-docs/ .

  • When several functions of the series method are executed, the system basically starts up. The last action to be executed is to call the done function defined in the start function and set the state to running.

    When the done function is completed, the IPFS system is started.