Skip to content

network/p2p_node.cpp

libp2p swarm node implementation More...

Namespaces

Name
sgns
sgns::neoswarm
sgns::neoswarm::network

Classes

Name
struct sgns::neoswarm::network::P2PNode::Impl
struct sgns::neoswarm::network::P2PNode::Impl::GossipSubs

Detailed Description

libp2p swarm node implementation

Date: 2026-05-06

Source code

#include "p2p_node.hpp"
#include "common/logging.hpp"

#include <atomic>
#include <nlohmann/json.hpp>

#include <libp2p/host/basic_host/basic_host.hpp>
#include <libp2p/injector/host_injector.hpp>
#include <libp2p/multi/multiaddress.hpp>
#include <libp2p/protocol/gossip/gossip.hpp>

namespace sgns::neoswarm::network
{
    namespace
    {
        constexpr char kTaskTopic[] = "genius/tasks/1.0.0";
        constexpr char kCRDTTopic[] = "genius/crdt/1.0.0";

        auto NetworkLogger()
        {
            return neoswarm::CreateLogger( "P2PNode" );
        }
    } // namespace

    struct P2PNode::Impl
    {
        std::string listen_addr_;
        std::string peer_m_id;
        std::vector<std::string> peers_;
        std::atomic<bool> m_running{ false };

        std::shared_ptr<libp2p::Host> host_;
        std::shared_ptr<libp2p::protocol::gossip::Gossip> gossip_;
        std::shared_ptr<libp2p::peer::IdentityManager> id_mgr_;

        // Subscription ownership — heap-allocated to avoid needing the
        // Subscription constructor/destructor symbols at link time.
        struct GossipSubs
        {
            libp2p::protocol::Subscription task_sub;
            libp2p::protocol::Subscription crdt_sub;
        };
        std::unique_ptr<GossipSubs> subs_;
    };

    P2PNode::P2PNode( std::shared_ptr<security::NodeIdentity> identity )
        : m_impl( std::make_unique<Impl>() )
        , m_identity( std::move( identity ) )
        , m_cfg( {} )
    {
    }

    P2PNode::P2PNode( std::shared_ptr<security::NodeIdentity> identity, Config cfg )
        : m_impl( std::make_unique<Impl>() )
        , m_identity( std::move( identity ) )
        , m_cfg( std::move( cfg ) )
    {
    }

    P2PNode::~P2PNode()
    {
        Stop();
    }

    // -----------------------------------------------------------------------
    // Start
    // -----------------------------------------------------------------------
    outcome::result<void> P2PNode::Start()
    {
        NetworkLogger()->info( "P2PNode starting (libp2p)..." );

        try
        {
            // 1. Create host with full libp2p stack via Boost.DI injector.
            //    makeNetworkInjector internally generates keys and creates all providers.
            auto injector = libp2p::injector::makeHostInjector();
            m_impl->host_ = injector.template create<std::shared_ptr<libp2p::Host>>();
            m_impl->id_mgr_ = injector.template create<std::shared_ptr<libp2p::peer::IdentityManager>>();

            // 2. Create GossipSub protocol using DI-provided components
            auto scheduler = injector.template create<std::shared_ptr<libp2p::basic::Scheduler>>();
            auto crypto_provider = injector.template create<std::shared_ptr<libp2p::crypto::CryptoProvider>>();
            auto key_marshaller =
                injector.template create<std::shared_ptr<libp2p::crypto::marshaller::KeyMarshaller>>();
            m_impl->gossip_ = libp2p::protocol::gossip::create( scheduler, m_impl->host_, m_impl->id_mgr_, crypto_provider,
                                                               key_marshaller, libp2p::protocol::gossip::Config{} );

            // 3. Subscribe to task and CRDT topics
            m_impl->subs_ = std::make_unique<Impl::GossipSubs>();
            m_impl->subs_->task_sub = m_impl->gossip_->subscribe(
                { kTaskTopic },
                [this]( libp2p::protocol::gossip::Gossip::SubscriptionData sub_data )
                {
                    if ( sub_data && m_taskHandler )
                    {
                        const auto& msg = sub_data.value();
                        auto json =
                            nlohmann::json::parse( std::string( msg.data.begin(), msg.data.end() ), nullptr, false );
                        if ( !json.is_discarded() )
                        {
                            Task t;
                            t.m_id = json.value( "id", "" );
                            t.m_prompt = json.value( "prompt", "" );
                            t.m_mode = static_cast<ExecutionMode>( json.value( "mode", 0 ) );
                            t.m_maxTokens = json.value( "max_tokens", 512U );
                            t.m_temperature = json.value( "temperature", 0.7f );
                            m_taskHandler( t, m_impl->peer_m_id );
                        }
                    }
                } );

            m_impl->subs_->crdt_sub =
                m_impl->gossip_->subscribe( { kCRDTTopic },
                                           [this]( libp2p::protocol::gossip::Gossip::SubscriptionData sub_data )
                                           {
                                               if ( sub_data && m_crdtHandler )
                                               {
                                                   const auto& msg = sub_data.value();
                                                   m_crdtHandler( std::string( msg.data.begin(), msg.data.end() ) );
                                               }
                                           } );

            // 4. Listen on configured address
            auto listen_ma = libp2p::multi::Multiaddress::create( m_cfg.listen_addr_.empty() ? "/ip4/0.0.0.0/tcp/0"
                                                                                            : m_cfg.listen_addr_ );
            if ( listen_ma )
            {
                (void)m_impl->host_->listen( listen_ma.value() );
            }

            // 5. Start the host and gossip
            m_impl->host_->start();
            m_impl->gossip_->start();

            m_impl->peer_m_id = m_impl->host_->getId().toBase58();
            m_impl->listen_addr_ = m_cfg.listen_addr_;
            m_impl->m_running.store( true );
            m_running = true;

            NetworkLogger()->info( "P2PNode started (libp2p): peerId={}", m_impl->peer_m_id );
        }
        catch ( const std::exception& e )
        {
            NetworkLogger()->error( "P2PNode start failed: {}", e.what() );
            return outcome::failure( Error::NetworkError );
        }

        return outcome::success();

    }

    // -----------------------------------------------------------------------
    // Stop
    // -----------------------------------------------------------------------
    void P2PNode::Stop()
    {
        if ( !m_running )
        {
            return;
        }
        if ( m_impl->gossip_ )
            m_impl->gossip_->stop();
        if ( m_impl->host_ )
            m_impl->host_->stop();
        m_impl->host_.reset();
        m_impl->gossip_.reset();
        m_impl->id_mgr_.reset();
        m_impl->m_running.store( false );
        m_running = false;
        NetworkLogger()->info( "P2PNode stopped" );
    }

    std::string P2PNode::ListenAddress() const
    {
        return m_impl->listen_addr_;
    }

    std::string P2PNode::PeerId() const
    {
        return m_impl->peer_m_id;
    }

    std::vector<std::string> P2PNode::ConnectedPeers() const
    {
        return m_impl->peers_;
    }

    // -----------------------------------------------------------------------
    // BroadcastTask
    // -----------------------------------------------------------------------
    outcome::result<void> P2PNode::BroadcastTask( const Task& task )
    {
        if ( !m_running )
        {
            return outcome::failure( Error::NetworkError );
        }

        nlohmann::json j;
        j["id"] = task.m_id;
        j["prompt"] = task.m_prompt;
        j["mode"] = static_cast<int>( task.m_mode );
        j["max_tokens"] = task.m_maxTokens;
        j["temperature"] = task.m_temperature;
        std::string payload = j.dump();

        NetworkLogger()->debug( "Broadcasting task {} to {} peers", task.m_id, m_impl->peers_.size() );

        // Publish via GossipSub to all peers
        if ( m_impl->gossip_ )
        {
            std::vector<uint8_t> data( payload.begin(), payload.end() );
            m_impl->gossip_->publish( kTaskTopic, std::move( data ) );
        }

        return outcome::success();
    }

    // -----------------------------------------------------------------------
    // BroadcastCRDT
    // -----------------------------------------------------------------------
    outcome::result<void> P2PNode::BroadcastCRDT( const std::string& crdt_data )
    {
        if ( !m_running )
        {
            return outcome::failure( Error::NetworkError );
        }
        NetworkLogger()->debug( "Broadcasting CRDT update ({} bytes)", crdt_data.size() );
        if ( m_impl->gossip_ )
        {
            std::vector<uint8_t> data( crdt_data.begin(), crdt_data.end() );
            m_impl->gossip_->publish( kCRDTTopic, std::move( data ) );
        }

        return outcome::success();
    }

} // namespace sgns::neoswarm::network

Updated on 2026-07-25 at 22:56:57 +0000