mirror of
https://github.com/vacp2p/nim-libp2p-experimental.git
synced 2025-02-20 22:08:09 +00:00
1 line
45 KiB
JSON
1 line
45 KiB
JSON
{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"nim-libp2p documentation Welcome to the nim-libp2p documentation! Here, you'll find tutorials to help you get started, as well as the full reference .","title":"Introduction"},{"location":"#nim-libp2p-documentation","text":"Welcome to the nim-libp2p documentation! Here, you'll find tutorials to help you get started, as well as the full reference .","title":"nim-libp2p documentation"},{"location":"circuitrelay/","text":"Circuit Relay example Circuit Relay can be used when a node cannot reach another node directly, but can reach it through a another node (the Relay). That may happen because of NAT, Firewalls, or incompatible transports. More informations here . import chronos , stew / byteutils import libp2p , libp2p / protocols / connectivity / relay /[ relay , client ] # Helper to create a circuit relay node proc createCircuitRelaySwitch ( r : Relay ): Switch = SwitchBuilder . new () . withRng ( newRng ()) . withAddresses ( @[ MultiAddress . init ( \"/ip4/0.0.0.0/tcp/0\" ). tryGet () ] ) . withTcpTransport () . withMplex () . withNoise () . withCircuitRelay ( r ) . build () proc main () {. async .} = # Create a custom protocol let customProtoCodec = \"/test\" var proto = new LPProtocol proto . codec = customProtoCodec proto . handler = proc ( conn : Connection , proto : string ) {. async .} = var msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"1 - Dst Received: \" , msg assert \"test1\" == msg await conn . writeLp ( \"test2\" ) msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"2 - Dst Received: \" , msg assert \"test3\" == msg await conn . writeLp ( \"test4\" ) let relay = Relay . new () clSrc = RelayClient . new () clDst = RelayClient . new () # Create three hosts, enable relay client on two of them. # The third one can relay connections for other peers. # RelayClient can use a relay, Relay is a relay. swRel = createCircuitRelaySwitch ( relay ) swSrc = createCircuitRelaySwitch ( clSrc ) swDst = createCircuitRelaySwitch ( clDst ) swDst . mount ( proto ) await swRel . start () await swSrc . start () await swDst . start () let # Create a relay address to swDst using swRel as the relay addrs = MultiAddress . init ( $ swRel . peerInfo . addrs [ 0 ] & \"/p2p/\" & $ swRel . peerInfo . peerId & \"/p2p-circuit/p2p/\" & $ swDst . peerInfo . peerId ). get () # Connect Dst to the relay await swDst . connect ( swRel . peerInfo . peerId , swRel . peerInfo . addrs ) # Dst reserve a slot on the relay. let rsvp = await clDst . reserve ( swRel . peerInfo . peerId , swRel . peerInfo . addrs ) # Src dial Dst using the relay let conn = await swSrc . dial ( swDst . peerInfo . peerId , @[ addrs ] , customProtoCodec ) await conn . writeLp ( \"test1\" ) var msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"1 - Src Received: \" , msg assert \"test2\" == msg await conn . writeLp ( \"test3\" ) msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"2 - Src Received: \" , msg assert \"test4\" == msg await relay . stop () await allFutures ( swSrc . stop (), swDst . stop (), swRel . stop ()) waitFor ( main ())","title":"Circuit Relay"},{"location":"circuitrelay/#circuit-relay-example","text":"Circuit Relay can be used when a node cannot reach another node directly, but can reach it through a another node (the Relay). That may happen because of NAT, Firewalls, or incompatible transports. More informations here . import chronos , stew / byteutils import libp2p , libp2p / protocols / connectivity / relay /[ relay , client ] # Helper to create a circuit relay node proc createCircuitRelaySwitch ( r : Relay ): Switch = SwitchBuilder . new () . withRng ( newRng ()) . withAddresses ( @[ MultiAddress . init ( \"/ip4/0.0.0.0/tcp/0\" ). tryGet () ] ) . withTcpTransport () . withMplex () . withNoise () . withCircuitRelay ( r ) . build () proc main () {. async .} = # Create a custom protocol let customProtoCodec = \"/test\" var proto = new LPProtocol proto . codec = customProtoCodec proto . handler = proc ( conn : Connection , proto : string ) {. async .} = var msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"1 - Dst Received: \" , msg assert \"test1\" == msg await conn . writeLp ( \"test2\" ) msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"2 - Dst Received: \" , msg assert \"test3\" == msg await conn . writeLp ( \"test4\" ) let relay = Relay . new () clSrc = RelayClient . new () clDst = RelayClient . new () # Create three hosts, enable relay client on two of them. # The third one can relay connections for other peers. # RelayClient can use a relay, Relay is a relay. swRel = createCircuitRelaySwitch ( relay ) swSrc = createCircuitRelaySwitch ( clSrc ) swDst = createCircuitRelaySwitch ( clDst ) swDst . mount ( proto ) await swRel . start () await swSrc . start () await swDst . start () let # Create a relay address to swDst using swRel as the relay addrs = MultiAddress . init ( $ swRel . peerInfo . addrs [ 0 ] & \"/p2p/\" & $ swRel . peerInfo . peerId & \"/p2p-circuit/p2p/\" & $ swDst . peerInfo . peerId ). get () # Connect Dst to the relay await swDst . connect ( swRel . peerInfo . peerId , swRel . peerInfo . addrs ) # Dst reserve a slot on the relay. let rsvp = await clDst . reserve ( swRel . peerInfo . peerId , swRel . peerInfo . addrs ) # Src dial Dst using the relay let conn = await swSrc . dial ( swDst . peerInfo . peerId , @[ addrs ] , customProtoCodec ) await conn . writeLp ( \"test1\" ) var msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"1 - Src Received: \" , msg assert \"test2\" == msg await conn . writeLp ( \"test3\" ) msg = string . fromBytes ( await conn . readLp ( 1024 )) echo \"2 - Src Received: \" , msg assert \"test4\" == msg await relay . stop () await allFutures ( swSrc . stop (), swDst . stop (), swRel . stop ()) waitFor ( main ())","title":"Circuit Relay example"},{"location":"tutorial_1_connect/","text":"Simple ping tutorial Hi all, welcome to the first nim-libp2p tutorial! This tutorial is for everyone who is interested in building peer-to-peer applications. No Nim programming experience is needed. To give you a quick overview, Nim is the programming language we are using and nim-libp2p is the Nim implementation of libp2p , a modular library that enables the development of peer-to-peer network applications. Hope you'll find it helpful in your journey of learning. Happy coding! ;) Before you start The only prerequisite here is Nim , the programming language with a Python-like syntax and a performance similar to C. Detailed information can be found here . Install Nim via their official website . Check Nim's installation via nim --version and its package manager Nimble via nimble --version . You can now install the latest version of nim-libp2p : nimble install libp2p@#master A simple ping application We'll start by creating a simple application, which is starting two libp2p switch , and pinging each other using the Ping protocol. You can find the source of this tutorial (and other tutorials) in the libp2p/examples folder! Let's create a part1.nim , and import our dependencies: import chronos import libp2p import libp2p / protocols / ping chronos the asynchronous framework used by nim-libp2p Next, we'll create an helper procedure to create our switches. A switch needs a bit of configuration, and it will be easier to do this configuration only once: proc createSwitch ( ma : MultiAddress , rng : ref HmacDrbgContext ): Switch = var switch = SwitchBuilder . new () . withRng ( rng ) # Give the application RNG . withAddress ( ma ) # Our local address(es) . withTcpTransport () # Use TCP as transport . withMplex () # Use Mplex as muxer . withNoise () # Use Noise as secure manager . build () return switch This will create a switch using Mplex as a multiplexer, Noise to secure the communication, and TCP as an underlying transport. You can of course tweak this, to use a different or multiple transport, or tweak the configuration of Mplex and Noise, but this is some sane defaults that we'll use going forward. Let's now start to create our main procedure: proc main () {. async , gcsafe .} = let rng = newRng () localAddress = MultiAddress . init ( \"/ip4/0.0.0.0/tcp/0\" ). tryGet () pingProtocol = Ping . new ( rng = rng ) We created some variables that we'll need for the rest of the application: the global rng instance, our localAddress , and an instance of the Ping protocol. The address is in the MultiAddress format. The port 0 means \"take any port available\". tryGet is procedure which is part of nim-result , that will throw an exception if the supplied MultiAddress is invalid. We can now create our two switches: let switch1 = createSwitch ( localAddress , rng ) switch2 = createSwitch ( localAddress , rng ) switch1 . mount ( pingProtocol ) await switch1 . start () await switch2 . start () We've mounted the pingProtocol on our first switch. This means that the first switch will actually listen for any ping requests coming in, and handle them accordingly. Now that we've started the nodes, they are listening for incoming peers. We can find out which port was attributed, and the resulting local addresses, by using switch1.peerInfo.addrs . We'll dial the first switch from the second one, by specifying it's Peer ID , it's MultiAddress and the Ping protocol codec : let conn = await switch2 . dial ( switch1 . peerInfo . peerId , switch1 . peerInfo . addrs , PingCodec ) We now have a Ping connection setup between the second and the first switch, we can use it to actually ping the node: # ping the other node and echo the ping duration echo \"ping: \" , await pingProtocol . ping ( conn ) # We must close the connection ourselves when we're done with it await conn . close () And that's it! Just a little bit of cleanup: shutting down the switches, waiting for them to stop, and we'll call our main procedure: await allFutures ( switch1 . stop (), switch2 . stop ()) # close connections and shutdown all transports waitFor ( main ()) You can now run this program using nim c -r part1.nim , and you should see the dialing sequence, ending with a ping output. In the next tutorial , we'll look at how to create our own custom protocol.","title":"Simple connection"},{"location":"tutorial_1_connect/#simple-ping-tutorial","text":"Hi all, welcome to the first nim-libp2p tutorial! This tutorial is for everyone who is interested in building peer-to-peer applications. No Nim programming experience is needed. To give you a quick overview, Nim is the programming language we are using and nim-libp2p is the Nim implementation of libp2p , a modular library that enables the development of peer-to-peer network applications. Hope you'll find it helpful in your journey of learning. Happy coding! ;)","title":"Simple ping tutorial"},{"location":"tutorial_1_connect/#before-you-start","text":"The only prerequisite here is Nim , the programming language with a Python-like syntax and a performance similar to C. Detailed information can be found here . Install Nim via their official website . Check Nim's installation via nim --version and its package manager Nimble via nimble --version . You can now install the latest version of nim-libp2p : nimble install libp2p@#master","title":"Before you start"},{"location":"tutorial_1_connect/#a-simple-ping-application","text":"We'll start by creating a simple application, which is starting two libp2p switch , and pinging each other using the Ping protocol. You can find the source of this tutorial (and other tutorials) in the libp2p/examples folder! Let's create a part1.nim , and import our dependencies: import chronos import libp2p import libp2p / protocols / ping chronos the asynchronous framework used by nim-libp2p Next, we'll create an helper procedure to create our switches. A switch needs a bit of configuration, and it will be easier to do this configuration only once: proc createSwitch ( ma : MultiAddress , rng : ref HmacDrbgContext ): Switch = var switch = SwitchBuilder . new () . withRng ( rng ) # Give the application RNG . withAddress ( ma ) # Our local address(es) . withTcpTransport () # Use TCP as transport . withMplex () # Use Mplex as muxer . withNoise () # Use Noise as secure manager . build () return switch This will create a switch using Mplex as a multiplexer, Noise to secure the communication, and TCP as an underlying transport. You can of course tweak this, to use a different or multiple transport, or tweak the configuration of Mplex and Noise, but this is some sane defaults that we'll use going forward. Let's now start to create our main procedure: proc main () {. async , gcsafe .} = let rng = newRng () localAddress = MultiAddress . init ( \"/ip4/0.0.0.0/tcp/0\" ). tryGet () pingProtocol = Ping . new ( rng = rng ) We created some variables that we'll need for the rest of the application: the global rng instance, our localAddress , and an instance of the Ping protocol. The address is in the MultiAddress format. The port 0 means \"take any port available\". tryGet is procedure which is part of nim-result , that will throw an exception if the supplied MultiAddress is invalid. We can now create our two switches: let switch1 = createSwitch ( localAddress , rng ) switch2 = createSwitch ( localAddress , rng ) switch1 . mount ( pingProtocol ) await switch1 . start () await switch2 . start () We've mounted the pingProtocol on our first switch. This means that the first switch will actually listen for any ping requests coming in, and handle them accordingly. Now that we've started the nodes, they are listening for incoming peers. We can find out which port was attributed, and the resulting local addresses, by using switch1.peerInfo.addrs . We'll dial the first switch from the second one, by specifying it's Peer ID , it's MultiAddress and the Ping protocol codec : let conn = await switch2 . dial ( switch1 . peerInfo . peerId , switch1 . peerInfo . addrs , PingCodec ) We now have a Ping connection setup between the second and the first switch, we can use it to actually ping the node: # ping the other node and echo the ping duration echo \"ping: \" , await pingProtocol . ping ( conn ) # We must close the connection ourselves when we're done with it await conn . close () And that's it! Just a little bit of cleanup: shutting down the switches, waiting for them to stop, and we'll call our main procedure: await allFutures ( switch1 . stop (), switch2 . stop ()) # close connections and shutdown all transports waitFor ( main ()) You can now run this program using nim c -r part1.nim , and you should see the dialing sequence, ending with a ping output. In the next tutorial , we'll look at how to create our own custom protocol.","title":"A simple ping application"},{"location":"tutorial_2_customproto/","text":"Custom protocol in libp2p In the previous tutorial , we've looked at how to create a simple ping program using the nim-libp2p . We'll now look at how to create a custom protocol inside the libp2p Let's create a part2.nim , and import our dependencies: import chronos import stew / byteutils import libp2p This is similar to the first tutorial, except we don't need to import the Ping protocol. Next, we'll declare our custom protocol const TestCodec = \"/test/proto/1.0.0\" type TestProto = ref object of LPProtocol We've set a protocol ID , and created a custom LPProtocol . In a more complex protocol, we could use this structure to store interesting variables. A protocol generally has two part: and handling/server part, and a dialing/client part. Theses two parts can be identical, but in our trivial protocol, the server will wait for a message from the client, and the client will send a message, so we have to handle the two cases separately. Let's start with the server part: proc new ( T : typedesc [ TestProto ] ): T = # every incoming connections will in be handled in this closure proc handle ( conn : Connection , proto : string ) {. async , gcsafe .} = # Read up to 1024 bytes from this connection, and transform them into # a string echo \"Got from remote - \" , string . fromBytes ( await conn . readLp ( 1024 )) # We must close the connections ourselves when we're done with it await conn . close () return T ( codecs : @[ TestCodec ] , handler : handle ) This is a constructor for our TestProto , that will specify our codecs and a handler , which will be called for each incoming peer asking for this protocol. In our handle, we simply read a message from the connection and echo it. We can now create our client part: proc hello ( p : TestProto , conn : Connection ) {. async .} = await conn . writeLp ( \"Hello p2p!\" ) Again, pretty straight-forward, we just send a message on the connection. We can now create our main procedure: proc main () {. async , gcsafe .} = let rng = newRng () testProto = TestProto . new () switch1 = newStandardSwitch ( rng = rng ) switch2 = newStandardSwitch ( rng = rng ) switch1 . mount ( testProto ) await switch1 . start () await switch2 . start () let conn = await switch2 . dial ( switch1 . peerInfo . peerId , switch1 . peerInfo . addrs , TestCodec ) await testProto . hello ( conn ) # We must close the connection ourselves when we're done with it await conn . close () await allFutures ( switch1 . stop (), switch2 . stop ()) # close connections and shutdown all transports This is very similar to the first tutorial's main , the only noteworthy difference is that we use newStandardSwitch , which is similar to the createSwitch of the first tutorial, but is bundled directly in libp2p We can now wrap our program by calling our main proc: waitFor ( main ()) And that's it! In the next tutorial , we'll create a more complex protocol using Protobuf.","title":"Create a custom protocol"},{"location":"tutorial_2_customproto/#custom-protocol-in-libp2p","text":"In the previous tutorial , we've looked at how to create a simple ping program using the nim-libp2p . We'll now look at how to create a custom protocol inside the libp2p Let's create a part2.nim , and import our dependencies: import chronos import stew / byteutils import libp2p This is similar to the first tutorial, except we don't need to import the Ping protocol. Next, we'll declare our custom protocol const TestCodec = \"/test/proto/1.0.0\" type TestProto = ref object of LPProtocol We've set a protocol ID , and created a custom LPProtocol . In a more complex protocol, we could use this structure to store interesting variables. A protocol generally has two part: and handling/server part, and a dialing/client part. Theses two parts can be identical, but in our trivial protocol, the server will wait for a message from the client, and the client will send a message, so we have to handle the two cases separately. Let's start with the server part: proc new ( T : typedesc [ TestProto ] ): T = # every incoming connections will in be handled in this closure proc handle ( conn : Connection , proto : string ) {. async , gcsafe .} = # Read up to 1024 bytes from this connection, and transform them into # a string echo \"Got from remote - \" , string . fromBytes ( await conn . readLp ( 1024 )) # We must close the connections ourselves when we're done with it await conn . close () return T ( codecs : @[ TestCodec ] , handler : handle ) This is a constructor for our TestProto , that will specify our codecs and a handler , which will be called for each incoming peer asking for this protocol. In our handle, we simply read a message from the connection and echo it. We can now create our client part: proc hello ( p : TestProto , conn : Connection ) {. async .} = await conn . writeLp ( \"Hello p2p!\" ) Again, pretty straight-forward, we just send a message on the connection. We can now create our main procedure: proc main () {. async , gcsafe .} = let rng = newRng () testProto = TestProto . new () switch1 = newStandardSwitch ( rng = rng ) switch2 = newStandardSwitch ( rng = rng ) switch1 . mount ( testProto ) await switch1 . start () await switch2 . start () let conn = await switch2 . dial ( switch1 . peerInfo . peerId , switch1 . peerInfo . addrs , TestCodec ) await testProto . hello ( conn ) # We must close the connection ourselves when we're done with it await conn . close () await allFutures ( switch1 . stop (), switch2 . stop ()) # close connections and shutdown all transports This is very similar to the first tutorial's main , the only noteworthy difference is that we use newStandardSwitch , which is similar to the createSwitch of the first tutorial, but is bundled directly in libp2p We can now wrap our program by calling our main proc: waitFor ( main ()) And that's it! In the next tutorial , we'll create a more complex protocol using Protobuf.","title":"Custom protocol in libp2p"},{"location":"tutorial_3_protobuf/","text":"Protobuf usage In the previous tutorial , we created a simple \"ping\" protocol. Most real protocol want their messages to be structured and extensible, which is why most real protocols use protobuf to define their message structures. Here, we'll create a slightly more complex protocol, which parses & generate protobuf messages. Let's start by importing our dependencies, as usual: import chronos import stew / results # for Opt[T] import libp2p Protobuf encoding & decoding This will be the structure of our messages: message MetricList { message Metric { string name = 1 ; float value = 2 ; } repeated Metric metrics = 2 ; } We'll create our protobuf types, encoders & decoders, according to this format. To create the encoders & decoders, we are going to use minprotobuf (included in libp2p). While more modern technics (such as nim-protobuf-serialization ) exists, minprotobuf is currently the recommended method to handle protobuf, since it has been used in production extensively, and audited. type Metric = object name : string value : float MetricList = object metrics : seq [ Metric ] {. push raises : [] .} proc encode ( m : Metric ): ProtoBuffer = result = initProtoBuffer () result . write ( 1 , m . name ) result . write ( 2 , m . value ) result . finish () proc decode ( _: type Metric, buf: seq[byte]): Result[Metric, ProtoError] = var res : Metric let pb = initProtoBuffer ( buf ) # \"getField\" will return a Result[bool, ProtoError]. # The Result will hold an error if the protobuf is invalid. # The Result will hold \"false\" if the field is missing # # We are just checking the error, and ignoring whether the value # is present or not (default values are valid). discard ? pb . getField ( 1 , res . name ) discard ? pb . getField ( 2 , res . value ) ok ( res ) proc encode ( m : MetricList ): ProtoBuffer = result = initProtoBuffer () for metric in m . metrics : result . write ( 1 , metric . encode ()) result . finish () proc decode ( _: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] = var res : MetricList metrics : seq [ seq [ byte ]] let pb = initProtoBuffer ( buf ) discard ? pb . getRepeatedField ( 1 , metrics ) for metric in metrics : res . metrics &= ? Metric . decode ( metric ) ok ( res ) Results instead of exceptions As you can see, this part of the program also uses Results instead of exceptions for error handling. We start by {.push raises: [].} , which will prevent every non-async function from raising exceptions. Then, we use nim-result to convey errors to function callers. A Result[T, E] will either hold a valid result of type T, or an error of type E. You can check if the call succeeded by using res.isOk , and then get the value using res.value or the error by using res.error . Another useful tool is ? , which will unpack a Result if it succeeded, or if it failed, exit the current procedure returning the error. nim-result is packed with other functionalities that you'll find in the nim-result repository. Results and exception are generally interchangeable, but have different semantics that you may or may not prefer. Creating the protocol We'll next create a protocol, like in the last tutorial, to request these metrics from our host type MetricCallback = proc : Future [ MetricList ] {. raises : [] , gcsafe .} MetricProto = ref object of LPProtocol metricGetter : MetricCallback proc new ( _: typedesc[MetricProto], cb: MetricCallback): MetricProto = let res = MetricProto ( metricGetter : cb ) proc handle ( conn : Connection , proto : string ) {. async , gcsafe .} = let metrics = await res . metricGetter () asProtobuf = metrics . encode () await conn . writeLp ( asProtobuf . buffer ) await conn . close () res . codecs = @[ \"/metric-getter/1.0.0\" ] res . handler = handle return res proc fetch ( p : MetricProto , conn : Connection ): Future [ MetricList ] {. async .} = let protobuf = await conn . readLp ( 2048 ) # tryGet will raise an exception if the Result contains an error. # It's useful to bridge between exception-world and result-world return MetricList . decode ( protobuf ). tryGet () We can now create our main procedure: proc main () {. async , gcsafe .} = let rng = newRng () proc randomMetricGenerator : Future [ MetricList ] {. async .} = let metricCount = rng [] . generate ( uint32 ) mod 16 for i in 0 .. < metricCount + 1 : result . metrics . add ( Metric ( name : \"metric_\" & $ i , value : float ( rng [] . generate ( uint16 )) / 1000.0 )) return result let metricProto1 = MetricProto . new ( randomMetricGenerator ) metricProto2 = MetricProto . new ( randomMetricGenerator ) switch1 = newStandardSwitch ( rng = rng ) switch2 = newStandardSwitch ( rng = rng ) switch1 . mount ( metricProto1 ) await switch1 . start () await switch2 . start () let conn = await switch2 . dial ( switch1 . peerInfo . peerId , switch1 . peerInfo . addrs , metricProto2 . codecs ) metrics = await metricProto2 . fetch ( conn ) await conn . close () for metric in metrics . metrics : echo metric . name , \" = \" , metric . value await allFutures ( switch1 . stop (), switch2 . stop ()) # close connections and shutdown all transports waitFor ( main ()) If you run this program, you should see random metrics being sent from the switch1 to the switch2.","title":"Protobuf"},{"location":"tutorial_3_protobuf/#protobuf-usage","text":"In the previous tutorial , we created a simple \"ping\" protocol. Most real protocol want their messages to be structured and extensible, which is why most real protocols use protobuf to define their message structures. Here, we'll create a slightly more complex protocol, which parses & generate protobuf messages. Let's start by importing our dependencies, as usual: import chronos import stew / results # for Opt[T] import libp2p","title":"Protobuf usage"},{"location":"tutorial_3_protobuf/#protobuf-encoding-decoding","text":"This will be the structure of our messages: message MetricList { message Metric { string name = 1 ; float value = 2 ; } repeated Metric metrics = 2 ; } We'll create our protobuf types, encoders & decoders, according to this format. To create the encoders & decoders, we are going to use minprotobuf (included in libp2p). While more modern technics (such as nim-protobuf-serialization ) exists, minprotobuf is currently the recommended method to handle protobuf, since it has been used in production extensively, and audited. type Metric = object name : string value : float MetricList = object metrics : seq [ Metric ] {. push raises : [] .} proc encode ( m : Metric ): ProtoBuffer = result = initProtoBuffer () result . write ( 1 , m . name ) result . write ( 2 , m . value ) result . finish () proc decode ( _: type Metric, buf: seq[byte]): Result[Metric, ProtoError] = var res : Metric let pb = initProtoBuffer ( buf ) # \"getField\" will return a Result[bool, ProtoError]. # The Result will hold an error if the protobuf is invalid. # The Result will hold \"false\" if the field is missing # # We are just checking the error, and ignoring whether the value # is present or not (default values are valid). discard ? pb . getField ( 1 , res . name ) discard ? pb . getField ( 2 , res . value ) ok ( res ) proc encode ( m : MetricList ): ProtoBuffer = result = initProtoBuffer () for metric in m . metrics : result . write ( 1 , metric . encode ()) result . finish () proc decode ( _: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] = var res : MetricList metrics : seq [ seq [ byte ]] let pb = initProtoBuffer ( buf ) discard ? pb . getRepeatedField ( 1 , metrics ) for metric in metrics : res . metrics &= ? Metric . decode ( metric ) ok ( res )","title":"Protobuf encoding & decoding"},{"location":"tutorial_3_protobuf/#results-instead-of-exceptions","text":"As you can see, this part of the program also uses Results instead of exceptions for error handling. We start by {.push raises: [].} , which will prevent every non-async function from raising exceptions. Then, we use nim-result to convey errors to function callers. A Result[T, E] will either hold a valid result of type T, or an error of type E. You can check if the call succeeded by using res.isOk , and then get the value using res.value or the error by using res.error . Another useful tool is ? , which will unpack a Result if it succeeded, or if it failed, exit the current procedure returning the error. nim-result is packed with other functionalities that you'll find in the nim-result repository. Results and exception are generally interchangeable, but have different semantics that you may or may not prefer.","title":"Results instead of exceptions"},{"location":"tutorial_3_protobuf/#creating-the-protocol","text":"We'll next create a protocol, like in the last tutorial, to request these metrics from our host type MetricCallback = proc : Future [ MetricList ] {. raises : [] , gcsafe .} MetricProto = ref object of LPProtocol metricGetter : MetricCallback proc new ( _: typedesc[MetricProto], cb: MetricCallback): MetricProto = let res = MetricProto ( metricGetter : cb ) proc handle ( conn : Connection , proto : string ) {. async , gcsafe .} = let metrics = await res . metricGetter () asProtobuf = metrics . encode () await conn . writeLp ( asProtobuf . buffer ) await conn . close () res . codecs = @[ \"/metric-getter/1.0.0\" ] res . handler = handle return res proc fetch ( p : MetricProto , conn : Connection ): Future [ MetricList ] {. async .} = let protobuf = await conn . readLp ( 2048 ) # tryGet will raise an exception if the Result contains an error. # It's useful to bridge between exception-world and result-world return MetricList . decode ( protobuf ). tryGet () We can now create our main procedure: proc main () {. async , gcsafe .} = let rng = newRng () proc randomMetricGenerator : Future [ MetricList ] {. async .} = let metricCount = rng [] . generate ( uint32 ) mod 16 for i in 0 .. < metricCount + 1 : result . metrics . add ( Metric ( name : \"metric_\" & $ i , value : float ( rng [] . generate ( uint16 )) / 1000.0 )) return result let metricProto1 = MetricProto . new ( randomMetricGenerator ) metricProto2 = MetricProto . new ( randomMetricGenerator ) switch1 = newStandardSwitch ( rng = rng ) switch2 = newStandardSwitch ( rng = rng ) switch1 . mount ( metricProto1 ) await switch1 . start () await switch2 . start () let conn = await switch2 . dial ( switch1 . peerInfo . peerId , switch1 . peerInfo . addrs , metricProto2 . codecs ) metrics = await metricProto2 . fetch ( conn ) await conn . close () for metric in metrics . metrics : echo metric . name , \" = \" , metric . value await allFutures ( switch1 . stop (), switch2 . stop ()) # close connections and shutdown all transports waitFor ( main ()) If you run this program, you should see random metrics being sent from the switch1 to the switch2.","title":"Creating the protocol"},{"location":"tutorial_4_gossipsub/","text":"GossipSub In this tutorial, we'll build a simple GossipSub network to broadcast the metrics we built in the previous tutorial. GossipSub is used to broadcast some messages in a network, and allows to balance between latency, bandwidth usage, privacy and attack resistance. You'll find a good explanation on how GossipSub works here. There are a lot of parameters you can tweak to adjust how GossipSub behaves but here we'll use the sane defaults shipped with libp2p. We'll start by creating our metric structure like previously import chronos import stew / results import libp2p import libp2p / protocols / pubsub / rpc / messages type Metric = object name : string value : float MetricList = object hostname : string metrics : seq [ Metric ] {. push raises : [] .} proc encode ( m : Metric ): ProtoBuffer = result = initProtoBuffer () result . write ( 1 , m . name ) result . write ( 2 , m . value ) result . finish () proc decode ( _: type Metric, buf: seq[byte]): Result[Metric, ProtoError] = var res : Metric let pb = initProtoBuffer ( buf ) discard ? pb . getField ( 1 , res . name ) discard ? pb . getField ( 2 , res . value ) ok ( res ) proc encode ( m : MetricList ): ProtoBuffer = result = initProtoBuffer () for metric in m . metrics : result . write ( 1 , metric . encode ()) result . write ( 2 , m . hostname ) result . finish () proc decode ( _: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] = var res : MetricList metrics : seq [ seq [ byte ]] let pb = initProtoBuffer ( buf ) discard ? pb . getRepeatedField ( 1 , metrics ) for metric in metrics : res . metrics &= ? Metric . decode ( metric ) ? pb . getRequiredField ( 2 , res . hostname ) ok ( res ) This is exactly like the previous structure, except that we added a hostname to distinguish where the metric is coming from. Now we'll create a small GossipSub network to broadcast the metrics, and collect them on one of the node. type Node = tuple [ switch : Switch , gossip : GossipSub , hostname : string ] proc oneNode ( node : Node , rng : ref HmacDrbgContext ) {. async .} = # This procedure will handle one of the node of the network node . gossip . addValidator ( [ \"metrics\" ] , proc ( topic : string , message : Message ): Future [ ValidationResult ] {. async .} = let decoded = MetricList . decode ( message . data ) if decoded . isErr : return ValidationResult . Reject return ValidationResult . Accept ) # This \"validator\" will attach to the `metrics` topic and make sure # that every message in this topic is valid. This allows us to stop # propagation of invalid messages quickly in the network, and punish # peers sending them. # `John` will be responsible to log the metrics, the rest of the nodes # will just forward them in the network if node . hostname == \"John\" : node . gossip . subscribe ( \"metrics\" , proc ( topic : string , data : seq [ byte ] ) {. async .} = echo MetricList . decode ( data ). tryGet () ) else : node . gossip . subscribe ( \"metrics\" , nil ) # Create random metrics 10 times and broadcast them for _ in 0..<10: await sleepAsync ( 500. milliseconds ) var metricList = MetricList ( hostname : node . hostname ) let metricCount = rng [] . generate ( uint32 ) mod 4 for i in 0 .. < metricCount + 1 : metricList . metrics . add ( Metric ( name : \"metric_\" & $ i , value : float ( rng [] . generate ( uint16 )) / 1000.0 )) discard await node . gossip . publish ( \"metrics\" , encode ( metricList ). buffer ) await node . switch . stop () For our main procedure, we'll create a few nodes, and connect them together. Note that they are not all interconnected, but GossipSub will take care of broadcasting to the full network nonetheless. proc main {. async .} = let rng = newRng () var nodes : seq [ Node ] for hostname in [ \"John\" , \"Walter\" , \"David\" , \"Thuy\" , \"Amy\" ] : let switch = newStandardSwitch ( rng = rng ) gossip = GossipSub . init ( switch = switch , triggerSelf = true ) switch . mount ( gossip ) await switch . start () nodes . add (( switch , gossip , hostname )) for index , node in nodes : # Connect to a few neighbors for otherNodeIdx in index - 1 .. index + 2 : if otherNodeIdx notin 0 .. < nodes . len or otherNodeIdx == index : continue let otherNode = nodes [ otherNodeIdx ] await node . switch . connect ( otherNode . switch . peerInfo . peerId , otherNode . switch . peerInfo . addrs ) var allFuts : seq [ Future [ void ]] for node in nodes : allFuts . add ( oneNode ( node , rng )) await allFutures ( allFuts ) waitFor ( main ()) If you run this program, you should see something like: (hostname: \"John\", metrics: @[(name: \"metric_0\", value: 42.097), (name: \"metric_1\", value: 50.99), (name: \"metric_2\", value: 47.86), (name: \"metric_3\", value: 5.368)]) (hostname: \"Walter\", metrics: @[(name: \"metric_0\", value: 39.452), (name: \"metric_1\", value: 15.606), (name: \"metric_2\", value: 14.059), (name: \"metric_3\", value: 6.68)]) (hostname: \"David\", metrics: @[(name: \"metric_0\", value: 9.82), (name: \"metric_1\", value: 2.862), (name: \"metric_2\", value: 15.514)]) (hostname: \"Thuy\", metrics: @[(name: \"metric_0\", value: 59.038)]) (hostname: \"Amy\", metrics: @[(name: \"metric_0\", value: 55.616), (name: \"metric_1\", value: 23.52), (name: \"metric_2\", value: 59.081), (name: \"metric_3\", value: 2.516)]) This is John receiving & logging everyone's metrics. Going further Building efficient & safe GossipSub networks is a tricky subject. By tweaking the gossip params and topic params , you can achieve very different properties. Also see reports for GossipSub v1.1 If you are interested in broadcasting for your application, you may want to use Waku , which builds on top of GossipSub, and adds features such as history, spam protection, and light node friendliness.","title":"GossipSub"},{"location":"tutorial_4_gossipsub/#gossipsub","text":"In this tutorial, we'll build a simple GossipSub network to broadcast the metrics we built in the previous tutorial. GossipSub is used to broadcast some messages in a network, and allows to balance between latency, bandwidth usage, privacy and attack resistance. You'll find a good explanation on how GossipSub works here. There are a lot of parameters you can tweak to adjust how GossipSub behaves but here we'll use the sane defaults shipped with libp2p. We'll start by creating our metric structure like previously import chronos import stew / results import libp2p import libp2p / protocols / pubsub / rpc / messages type Metric = object name : string value : float MetricList = object hostname : string metrics : seq [ Metric ] {. push raises : [] .} proc encode ( m : Metric ): ProtoBuffer = result = initProtoBuffer () result . write ( 1 , m . name ) result . write ( 2 , m . value ) result . finish () proc decode ( _: type Metric, buf: seq[byte]): Result[Metric, ProtoError] = var res : Metric let pb = initProtoBuffer ( buf ) discard ? pb . getField ( 1 , res . name ) discard ? pb . getField ( 2 , res . value ) ok ( res ) proc encode ( m : MetricList ): ProtoBuffer = result = initProtoBuffer () for metric in m . metrics : result . write ( 1 , metric . encode ()) result . write ( 2 , m . hostname ) result . finish () proc decode ( _: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] = var res : MetricList metrics : seq [ seq [ byte ]] let pb = initProtoBuffer ( buf ) discard ? pb . getRepeatedField ( 1 , metrics ) for metric in metrics : res . metrics &= ? Metric . decode ( metric ) ? pb . getRequiredField ( 2 , res . hostname ) ok ( res ) This is exactly like the previous structure, except that we added a hostname to distinguish where the metric is coming from. Now we'll create a small GossipSub network to broadcast the metrics, and collect them on one of the node. type Node = tuple [ switch : Switch , gossip : GossipSub , hostname : string ] proc oneNode ( node : Node , rng : ref HmacDrbgContext ) {. async .} = # This procedure will handle one of the node of the network node . gossip . addValidator ( [ \"metrics\" ] , proc ( topic : string , message : Message ): Future [ ValidationResult ] {. async .} = let decoded = MetricList . decode ( message . data ) if decoded . isErr : return ValidationResult . Reject return ValidationResult . Accept ) # This \"validator\" will attach to the `metrics` topic and make sure # that every message in this topic is valid. This allows us to stop # propagation of invalid messages quickly in the network, and punish # peers sending them. # `John` will be responsible to log the metrics, the rest of the nodes # will just forward them in the network if node . hostname == \"John\" : node . gossip . subscribe ( \"metrics\" , proc ( topic : string , data : seq [ byte ] ) {. async .} = echo MetricList . decode ( data ). tryGet () ) else : node . gossip . subscribe ( \"metrics\" , nil ) # Create random metrics 10 times and broadcast them for _ in 0..<10: await sleepAsync ( 500. milliseconds ) var metricList = MetricList ( hostname : node . hostname ) let metricCount = rng [] . generate ( uint32 ) mod 4 for i in 0 .. < metricCount + 1 : metricList . metrics . add ( Metric ( name : \"metric_\" & $ i , value : float ( rng [] . generate ( uint16 )) / 1000.0 )) discard await node . gossip . publish ( \"metrics\" , encode ( metricList ). buffer ) await node . switch . stop () For our main procedure, we'll create a few nodes, and connect them together. Note that they are not all interconnected, but GossipSub will take care of broadcasting to the full network nonetheless. proc main {. async .} = let rng = newRng () var nodes : seq [ Node ] for hostname in [ \"John\" , \"Walter\" , \"David\" , \"Thuy\" , \"Amy\" ] : let switch = newStandardSwitch ( rng = rng ) gossip = GossipSub . init ( switch = switch , triggerSelf = true ) switch . mount ( gossip ) await switch . start () nodes . add (( switch , gossip , hostname )) for index , node in nodes : # Connect to a few neighbors for otherNodeIdx in index - 1 .. index + 2 : if otherNodeIdx notin 0 .. < nodes . len or otherNodeIdx == index : continue let otherNode = nodes [ otherNodeIdx ] await node . switch . connect ( otherNode . switch . peerInfo . peerId , otherNode . switch . peerInfo . addrs ) var allFuts : seq [ Future [ void ]] for node in nodes : allFuts . add ( oneNode ( node , rng )) await allFutures ( allFuts ) waitFor ( main ()) If you run this program, you should see something like: (hostname: \"John\", metrics: @[(name: \"metric_0\", value: 42.097), (name: \"metric_1\", value: 50.99), (name: \"metric_2\", value: 47.86), (name: \"metric_3\", value: 5.368)]) (hostname: \"Walter\", metrics: @[(name: \"metric_0\", value: 39.452), (name: \"metric_1\", value: 15.606), (name: \"metric_2\", value: 14.059), (name: \"metric_3\", value: 6.68)]) (hostname: \"David\", metrics: @[(name: \"metric_0\", value: 9.82), (name: \"metric_1\", value: 2.862), (name: \"metric_2\", value: 15.514)]) (hostname: \"Thuy\", metrics: @[(name: \"metric_0\", value: 59.038)]) (hostname: \"Amy\", metrics: @[(name: \"metric_0\", value: 55.616), (name: \"metric_1\", value: 23.52), (name: \"metric_2\", value: 59.081), (name: \"metric_3\", value: 2.516)]) This is John receiving & logging everyone's metrics.","title":"GossipSub"},{"location":"tutorial_4_gossipsub/#going-further","text":"Building efficient & safe GossipSub networks is a tricky subject. By tweaking the gossip params and topic params , you can achieve very different properties. Also see reports for GossipSub v1.1 If you are interested in broadcasting for your application, you may want to use Waku , which builds on top of GossipSub, and adds features such as history, spam protection, and light node friendliness.","title":"Going further"},{"location":"go-daemon/daemonapi/","text":"Table of Contents Introduction Installation Usage Example Getting Started Introduction This is a libp2p-backed daemon wrapping the functionalities of go-libp2p for use in Nim. For more information about the go daemon, check out this repository . Installation # clone and install dependencies git clone https://github.com/status-im/nim-libp2p cd nim-libp2p nimble install # perform unit tests nimble test # update the git submodule to install the go daemon git submodule update --init --recursive go version git clone https://github.com/libp2p/go-libp2p-daemon cd go-libp2p-daemon git checkout v0.0.1 go install ./... cd .. Usage Example Examples can be found in the examples folder Getting Started Try out the chat example. Full code can be found here : nim c -r --threads:on examples/directchat.nim This will output a peer ID such as QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu which you can use in another instance to connect to it. ./examples/directchat /connect QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu You can now chat between the instances!","title":"Table of Contents"},{"location":"go-daemon/daemonapi/#table-of-contents","text":"Introduction Installation Usage Example Getting Started","title":"Table of Contents"},{"location":"go-daemon/daemonapi/#introduction","text":"This is a libp2p-backed daemon wrapping the functionalities of go-libp2p for use in Nim. For more information about the go daemon, check out this repository .","title":"Introduction"},{"location":"go-daemon/daemonapi/#installation","text":"# clone and install dependencies git clone https://github.com/status-im/nim-libp2p cd nim-libp2p nimble install # perform unit tests nimble test # update the git submodule to install the go daemon git submodule update --init --recursive go version git clone https://github.com/libp2p/go-libp2p-daemon cd go-libp2p-daemon git checkout v0.0.1 go install ./... cd ..","title":"Installation"},{"location":"go-daemon/daemonapi/#usage","text":"","title":"Usage"},{"location":"go-daemon/daemonapi/#example","text":"Examples can be found in the examples folder","title":"Example"},{"location":"go-daemon/daemonapi/#getting-started","text":"Try out the chat example. Full code can be found here : nim c -r --threads:on examples/directchat.nim This will output a peer ID such as QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu which you can use in another instance to connect to it. ./examples/directchat /connect QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu You can now chat between the instances!","title":"Getting Started"}]} |