From 149e554e293296b7b5ca27adee65a468d2e1f5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Fri, 13 Dec 2024 10:43:38 +0100 Subject: [PATCH 1/8] DAL/Node: Filter peers that we already know --- src/lib_gossipsub/gossipsub_worker.ml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib_gossipsub/gossipsub_worker.ml b/src/lib_gossipsub/gossipsub_worker.ml index c659644c4d91..7eeb7415e96e 100644 --- a/src/lib_gossipsub/gossipsub_worker.ml +++ b/src/lib_gossipsub/gossipsub_worker.ml @@ -589,6 +589,18 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : state | state, GS.PX peers -> Introspection.update_count_recv_prunes state.stats `Incr ; + let peers = + let current_connections = + let GS.Introspection.{connections; _} = + GS.Introspection.(view state.gossip_state) + in + connections + in + Peer.Set.filter + (fun peer -> + not (GS.Introspection.Connections.mem peer current_connections)) + peers + in emit_p2p_output state ~mk_output:(fun to_peer -> -- GitLab From 0ab31b3824afa63eeb53ecf4132b1d78213611f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Fri, 13 Dec 2024 10:58:42 +0100 Subject: [PATCH 2/8] DAL/Node: The gossipsub worker knows about itself --- src/bin_dal_node/daemon.ml | 11 +++++++++-- src/lib_gossipsub/gossipsub_intf.ml | 1 + src/lib_gossipsub/gossipsub_worker.ml | 11 ++++++++--- src/lib_gossipsub/test/test_integration_worker.ml | 12 ++++++++---- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/bin_dal_node/daemon.ml b/src/bin_dal_node/daemon.ml index 9e50d202604d..aadf959e352d 100644 --- a/src/bin_dal_node/daemon.ml +++ b/src/bin_dal_node/daemon.ml @@ -1207,6 +1207,8 @@ let run ~data_dir ~configuration_override = in return (fun () -> !bootstrap_points) in + let* p2p_config = Transport_layer_parameters.p2p_config config in + let p2p_limits = Transport_layer_parameters.p2p_limits in (* Create and start a GS worker *) let gs_worker = let rng = @@ -1246,11 +1248,18 @@ let run ~data_dir ~configuration_override = } else limits in + let identity = p2p_config.P2p.identity in + let self = + (* What matters is the identity, the reachable point is more like a placeholder here. *) + Types.Peer. + {peer_id = identity.peer_id; maybe_reachable_point = public_addr} + in let gs_worker = Gossipsub.Worker.( make ~bootstrap_points:get_bootstrap_points ~events_logging:Logging.event + ~self rng limits peer_filter_parameters) @@ -1261,8 +1270,6 @@ let run ~data_dir ~configuration_override = let points = get_bootstrap_points () in (* Create a transport (P2P) layer instance. *) let* transport_layer = - let open Transport_layer_parameters in - let* p2p_config = p2p_config config in Gossipsub.Transport_layer.create ~public_addr ~is_bootstrap_peer:(profile = Profile_manager.bootstrap) diff --git a/src/lib_gossipsub/gossipsub_intf.ml b/src/lib_gossipsub/gossipsub_intf.ml index 7459a5bf3c3c..76675d82c7d9 100644 --- a/src/lib_gossipsub/gossipsub_intf.ml +++ b/src/lib_gossipsub/gossipsub_intf.ml @@ -1181,6 +1181,7 @@ module type WORKER = sig val make : ?events_logging:(event -> unit Monad.t) -> ?bootstrap_points:(unit -> Point.t list) -> + self:GS.Peer.t -> Random.State.t -> (GS.Topic.t, GS.Peer.t, GS.Message_id.t, GS.span) limits -> (GS.Peer.t, GS.Message_id.t) parameters -> diff --git a/src/lib_gossipsub/gossipsub_worker.ml b/src/lib_gossipsub/gossipsub_worker.ml index 7eeb7415e96e..a3ff26c9e3c2 100644 --- a/src/lib_gossipsub/gossipsub_worker.ml +++ b/src/lib_gossipsub/gossipsub_worker.ml @@ -283,7 +283,11 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : } (** A worker instance is made of its status and state. *) - type t = {mutable status : worker_status; mutable state : worker_state} + type t = { + mutable status : worker_status; + mutable state : worker_state; + self : Peer.t; + } let state {state; _} = GS.Introspection.view state.gossip_state @@ -799,7 +803,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : | Check_unknown_messages -> check_unknown_messages_id state (** A helper function that pushes events in the state *) - let push e {status = _; state} = Stream.push e state.events_stream + let push e {status = _; state; self = _} = Stream.push e state.events_stream let app_input t input = push (App_input input) t @@ -891,8 +895,9 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : event_loop_promise let make ?(events_logging = fun _event -> Monad.return ()) - ?(bootstrap_points = fun () -> []) rng limits parameters = + ?(bootstrap_points = fun () -> []) ~self rng limits parameters = { + self; status = Starting; state = { diff --git a/src/lib_gossipsub/test/test_integration_worker.ml b/src/lib_gossipsub/test/test_integration_worker.ml index 5f06cb5b397d..2ce350e82b54 100644 --- a/src/lib_gossipsub/test/test_integration_worker.ml +++ b/src/lib_gossipsub/test/test_integration_worker.ml @@ -146,7 +146,8 @@ let test_worker_start_and_stop rng limits parameters = the expected outputs. *) let expected_p2p_output = Queue.create () in let expected_app_output = Queue.create () in - let worker = Worker.make rng limits parameters in + let self = -1 in + let worker = Worker.make ~self rng limits parameters in let heartbeat_span = limits.heartbeat_interval in let () = Worker.start ["1"; "2"; "3"] worker in let context = @@ -187,7 +188,8 @@ let test_worker_connect_and_graft rng limits parameters = the expected outputs. *) let expected_p2p_output = Queue.create () in let expected_app_output = Queue.create () in - let worker = Worker.make rng limits parameters in + let self = -1 in + let worker = Worker.make ~self rng limits parameters in let heartbeat_span = limits.heartbeat_interval in (* 1. The worker joins topic "1" at startup. *) let () = Worker.start [topic] worker in @@ -277,7 +279,8 @@ let test_worker_filter_messages_for_app rng limits parameters = the expected outputs. *) let expected_p2p_output = Queue.create () in let expected_app_output = Queue.create () in - let worker = Worker.make rng limits parameters in + let self = -1 in + let worker = Worker.make ~self rng limits parameters in (* 1. The worker joins topic "1" at startup. *) let () = Worker.start [topic] worker in let heartbeat_span = limits.heartbeat_interval in @@ -365,7 +368,8 @@ let test_worker_join rng limits parameters = ~title:"GS worker: Join topic" ~tags:["gossipsub"; "worker"; "join"] @@ fun () -> - let worker = Worker.make rng limits parameters in + let self = -1 in + let worker = Worker.make ~self rng limits parameters in let () = Worker.start [] worker in let () = Worker.app_input worker (Join "topic") in Check.( -- GitLab From 66e0f2547b8ce7ab164a0ed73bf56185be346f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Fri, 13 Dec 2024 11:01:27 +0100 Subject: [PATCH 3/8] DAL/Node: Avoid trying to connect to itself --- src/lib_gossipsub/gossipsub_worker.ml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib_gossipsub/gossipsub_worker.ml b/src/lib_gossipsub/gossipsub_worker.ml index a3ff26c9e3c2..602c3234da7c 100644 --- a/src/lib_gossipsub/gossipsub_worker.ml +++ b/src/lib_gossipsub/gossipsub_worker.ml @@ -576,7 +576,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : automaton removes that peer from the given topic's mesh. It also filters the given collection of alternative peers to connect to. The worker then asks the P2P part to connect to those peeers. *) - let handle_prune ~from_peer input_px = + let handle_prune ~self ~from_peer input_px = let forget_all state = emit_p2p_output state @@ -602,7 +602,8 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : in Peer.Set.filter (fun peer -> - not (GS.Introspection.Connections.mem peer current_connections)) + (not (GS.Introspection.Connections.mem peer current_connections)) + && not (Peer.equal peer self)) peers in emit_p2p_output @@ -697,7 +698,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : |> update_gossip_state state |> handle_leave topic (** Handling messages received from the P2P network. *) - let apply_p2p_message ({gossip_state; _} as state) from_peer = function + let apply_p2p_message ~self ({gossip_state; _} as state) from_peer = function | Message_with_header {message; topic; message_id} -> let receive_message = {GS.sender = from_peer; topic; message_id; message} @@ -731,10 +732,11 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : | Prune {topic; px; backoff} -> let prune : GS.prune = {peer = from_peer; topic; px; backoff} in GS.handle_prune prune gossip_state - |> update_gossip_state state |> handle_prune ~from_peer px + |> update_gossip_state state + |> handle_prune ~self ~from_peer px (** Handling events received from P2P layer. *) - let apply_p2p_event ({gossip_state; _} as state) = function + let apply_p2p_event ~self ({gossip_state; _} as state) = function | New_connection {peer; direct; trusted; bootstrap} -> GS.add_peer {direct; outbound = trusted; peer; bootstrap} gossip_state |> update_gossip_state state @@ -743,7 +745,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : GS.remove_peer {peer} gossip_state |> update_gossip_state state |> handle_disconnection peer | In_message {from_peer; p2p_message} -> - apply_p2p_message state from_peer p2p_message + apply_p2p_message ~self state from_peer p2p_message let rec check_unknown_messages_id state = match Bounded_message_map.remove_min state.unknown_validity_messages with @@ -787,7 +789,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : (** This is the main function of the worker. It interacts with the Gossipsub automaton given an event. The function possibly sends messages to the P2P and application layers and returns the new worker's state. *) - let apply_event ({gossip_state; _} as state) = function + let apply_event ~self ({gossip_state; _} as state) = function (* FIXME: https://gitlab.com/tezos/tezos/-/issues/5326 Notify the GS worker about the status of messages sent to peers. *) @@ -798,7 +800,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : would be handled (e.g. because the first one is late)? *) GS.heartbeat gossip_state |> update_gossip_state state |> handle_heartheat - | P2P_input event -> apply_p2p_event state event + | P2P_input event -> apply_p2p_event ~self state event | App_input event -> apply_app_event state event | Check_unknown_messages -> check_unknown_messages_id state @@ -847,7 +849,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : if !shutdown then return () else let* () = events_logging event in - t.state <- apply_event t.state event ; + t.state <- apply_event ~self:t.self t.state event ; loop t in let promise = loop t in -- GitLab From 332d96db6e70796db762597d872f6e08ea841df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Fri, 13 Dec 2024 15:17:10 +0100 Subject: [PATCH 4/8] Gossipsub: Enable to set unreachable points --- src/lib_gossipsub/gossipsub_intf.ml | 3 +++ src/lib_gossipsub/gossipsub_worker.ml | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/lib_gossipsub/gossipsub_intf.ml b/src/lib_gossipsub/gossipsub_intf.ml index 76675d82c7d9..7e1342fe7ae7 100644 --- a/src/lib_gossipsub/gossipsub_intf.ml +++ b/src/lib_gossipsub/gossipsub_intf.ml @@ -1198,6 +1198,9 @@ module type WORKER = sig to the worker's input stream. *) val app_input : t -> app_input -> unit + (** [set_unreachable_point state point] declares this point as unreachable. *) + val set_unreachable_point : t -> Point.t -> unit + (** [p2p_input state p2p_input] adds the given P2P input [p2p_input] to the worker's input stream. *) val p2p_input : t -> p2p_input -> unit diff --git a/src/lib_gossipsub/gossipsub_worker.ml b/src/lib_gossipsub/gossipsub_worker.ml index 602c3234da7c..7f952df78737 100644 --- a/src/lib_gossipsub/gossipsub_worker.ml +++ b/src/lib_gossipsub/gossipsub_worker.ml @@ -280,6 +280,8 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : app_output_stream : app_output Stream.t; events_logging : event -> unit Monad.t; unknown_validity_messages : Bounded_message_map.t; + unreachable_points : int64 Point.Map.t; + (* For each point, stores the next heartbeat tick when we can try to recontact this point again. *) } (** A worker instance is made of its status and state. *) @@ -913,6 +915,7 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : app_output_stream = Stream.empty (); events_logging; unknown_validity_messages = Bounded_message_map.make ~capacity:10_000; + unreachable_points = Point.Map.empty; }; } @@ -927,6 +930,22 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : let is_subscribed t topic = GS.Introspection.(has_joined topic (view t.state.gossip_state)) + let set_unreachable_point t point = + let GS.Introspection.{heartbeat_ticks; _} = + GS.Introspection.(view t.state.gossip_state) + in + let unreachable_points = + Point.Map.update + point + (function + | None -> Some (Int64.add 5L heartbeat_ticks) + | Some x -> + Some + (Int64.mul x 2L |> Int64.max 300L |> Int64.add heartbeat_ticks)) + t.state.unreachable_points + in + t.state <- {t.state with unreachable_points} + let pp_list pp_elt = Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt "; ") pp_elt -- GitLab From b5541e79698fa758650f3576edf67a1fa8b7662b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Fri, 13 Dec 2024 15:27:47 +0100 Subject: [PATCH 5/8] DAL/Node: Register unreachable points --- src/lib_dal_node/gossipsub/gs_transport_connection.ml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib_dal_node/gossipsub/gs_transport_connection.ml b/src/lib_dal_node/gossipsub/gs_transport_connection.ml index bce3ae0c6206..0846dfcf0af0 100644 --- a/src/lib_dal_node/gossipsub/gs_transport_connection.ml +++ b/src/lib_dal_node/gossipsub/gs_transport_connection.ml @@ -175,7 +175,7 @@ let disconnections_handler gs_worker peer_id = | None -> (* Something is off, we should log something probably. *) () | Some (peer, _) -> Worker.(Disconnection {peer} |> p2p_input gs_worker) -let try_connect ?expected_peer_id p2p_layer ~trusted point = +let try_connect ?expected_peer_id gs_worker p2p_layer ~trusted point = let open Lwt_syntax in (* We don't wait for the promise to resolve here, because if the advertised peer is not reachable or is not responding, we might block @@ -194,9 +194,12 @@ let try_connect ?expected_peer_id p2p_layer ~trusted point = Implement a better PX exchange. *) ignore expected_peer_id ; - let* (_ : _ P2p.connection tzresult) = + let* (result : _ P2p.connection tzresult) = P2p.connect ~trusted p2p_layer point in + Result.iter_error + (fun _ -> Worker.set_unreachable_point gs_worker point) + result ; return_unit) (fun exn -> Format.eprintf @@ -256,9 +259,11 @@ let gs_worker_p2p_output_handler gs_worker p2p_layer = try_connect ~trusted ~expected_peer_id:peer_id + gs_worker p2p_layer maybe_reachable_point - | Connect_point {point} -> try_connect p2p_layer point ~trusted:false + | Connect_point {point} -> + try_connect gs_worker p2p_layer point ~trusted:false | Forget _ -> return_unit | Kick {peer} -> P2p.pool p2p_layer -- GitLab From a3006cb080f46ff1b683b79e93fbdacbd5338857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Fri, 13 Dec 2024 15:37:04 +0100 Subject: [PATCH 6/8] Gossipsub/Worker: Add a `maybe_reachable_point` function --- src/lib_dal_node/gossipsub/gs_interface.ml | 3 +++ src/lib_gossipsub/gossipsub_intf.ml | 2 ++ src/lib_gossipsub/gossipsub_worker.ml | 2 ++ src/lib_gossipsub/test/test_gossipsub_shared.ml | 3 +++ 4 files changed, 10 insertions(+) diff --git a/src/lib_dal_node/gossipsub/gs_interface.ml b/src/lib_dal_node/gossipsub/gs_interface.ml index b150dd904bba..6990c98154ff 100644 --- a/src/lib_dal_node/gossipsub/gs_interface.ml +++ b/src/lib_dal_node/gossipsub/gs_interface.ml @@ -105,6 +105,9 @@ module Worker_config : module Monad = Monad module Point = Types.Point + let maybe_reachable_point Types.Peer.{maybe_reachable_point; _} = + maybe_reachable_point + (* TODO: https://gitlab.com/tezos/tezos/-/issues/5596 Use Seq_s instead of Lwt_stream to implement module Stream. *) diff --git a/src/lib_gossipsub/gossipsub_intf.ml b/src/lib_gossipsub/gossipsub_intf.ml index 7e1342fe7ae7..ca9a3cf25815 100644 --- a/src/lib_gossipsub/gossipsub_intf.ml +++ b/src/lib_gossipsub/gossipsub_intf.ml @@ -1051,6 +1051,8 @@ module type WORKER_CONFIGURATION = sig module Point : ITERABLE + val maybe_reachable_point : GS.Peer.t -> Point.t + (** Abstraction of the IO monad used by the worker. *) module Monad : sig (** The monad type. *) diff --git a/src/lib_gossipsub/gossipsub_worker.ml b/src/lib_gossipsub/gossipsub_worker.ml index 7f952df78737..2ed1f639b7d1 100644 --- a/src/lib_gossipsub/gossipsub_worker.ml +++ b/src/lib_gossipsub/gossipsub_worker.ml @@ -291,6 +291,8 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : self : Peer.t; } + let maybe_reachable_point = C.maybe_reachable_point + let state {state; _} = GS.Introspection.view state.gossip_state let emit_app_output state e = Stream.push e state.app_output_stream diff --git a/src/lib_gossipsub/test/test_gossipsub_shared.ml b/src/lib_gossipsub/test/test_gossipsub_shared.ml index 00a71ff65a0d..ddb9f11948ad 100644 --- a/src/lib_gossipsub/test/test_gossipsub_shared.ml +++ b/src/lib_gossipsub/test/test_gossipsub_shared.ml @@ -334,6 +334,9 @@ module Worker_config = struct module GS = GS module Point = Int_iterable + (* This should be modified in the future. *) + let maybe_reachable_point _ = -2 + module Monad = struct type 'a t = 'a Lwt.t -- GitLab From 36821bea3c650af4ac7780e63f0d3146628226ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Fri, 13 Dec 2024 15:41:28 +0100 Subject: [PATCH 7/8] Gossipsub/Worker: Memorize unreachable peers --- src/lib_gossipsub/gossipsub_worker.ml | 33 +++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/lib_gossipsub/gossipsub_worker.ml b/src/lib_gossipsub/gossipsub_worker.ml index 2ed1f639b7d1..92da341f63d3 100644 --- a/src/lib_gossipsub/gossipsub_worker.ml +++ b/src/lib_gossipsub/gossipsub_worker.ml @@ -598,16 +598,22 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : | state, GS.PX peers -> Introspection.update_count_recv_prunes state.stats `Incr ; let peers = - let current_connections = - let GS.Introspection.{connections; _} = - GS.Introspection.(view state.gossip_state) - in - connections + let GS.Introspection. + {connections = current_connections; heartbeat_ticks; _} = + GS.Introspection.(view state.gossip_state) + in + let can_be_contacted peer = + let point = maybe_reachable_point peer in + match Point.Map.find_opt point state.unreachable_points with + | None -> true + | Some next_attempt -> + Int64.compare heartbeat_ticks next_attempt >= 0 in Peer.Set.filter (fun peer -> (not (GS.Introspection.Connections.mem peer current_connections)) - && not (Peer.equal peer self)) + && (not (Peer.equal peer self)) + && can_be_contacted peer) peers in emit_p2p_output @@ -660,6 +666,16 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : state (IHave {topic; message_ids}) (Seq.return peer)) ; + let point_can_be_contacted point = + match Point.Map.find_opt point state.unreachable_points with + | None -> true + | Some next_attempt -> + Int64.compare gstate_view.heartbeat_ticks next_attempt >= 0 + in + let peer_can_be_contacted peer = + let point = maybe_reachable_point peer in + point_can_be_contacted point + in (* Once every 15 hearbreat ticks, try to reconnect to trusted peers if they are disconnected. Also try to reconnect to bootstrap points. *) if Int64.(equal (rem gstate_view.heartbeat_ticks 15L) 0L) then ( @@ -673,11 +689,14 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : else Seq.cons trusted_peer seq) state.trusted_peers Seq.empty + |> Seq.filter peer_can_be_contacted |> emit_p2p_output state ~mk_output:(fun trusted_peer -> Connect {peer = trusted_peer; origin = Trusted}) ; let p2p_output_stream = state.p2p_output_stream in let bootstrap_points = - state.bootstrap_points () |> Point.Set.of_list + state.bootstrap_points () + |> List.filter point_can_be_contacted + |> Point.Set.of_list in Point.Set.iter (fun point -> Stream.push (Connect_point {point}) p2p_output_stream) -- GitLab From 9ea591f9e52955edb63e8add0279dcd2e141c1f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Mon, 16 Dec 2024 16:16:50 +0100 Subject: [PATCH 8/8] Gossipsub/Worker: Clean up the map every 6h --- src/lib_gossipsub/gossipsub_worker.ml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib_gossipsub/gossipsub_worker.ml b/src/lib_gossipsub/gossipsub_worker.ml index 92da341f63d3..5ffa48f61d92 100644 --- a/src/lib_gossipsub/gossipsub_worker.ml +++ b/src/lib_gossipsub/gossipsub_worker.ml @@ -701,6 +701,12 @@ module Make (C : Gossipsub_intf.WORKER_CONFIGURATION) : Point.Set.iter (fun point -> Stream.push (Connect_point {point}) p2p_output_stream) bootstrap_points) ; + let state = + (* We reset the map every 6h. This prevents this map to contain outdated points. *) + if Int64.(equal (rem gstate_view.heartbeat_ticks 21600L) 0L) then + {state with unreachable_points = Point.Map.empty} + else state + in state let update_gossip_state state (gossip_state, output) = -- GitLab