Skip to content

processing/processing_subtask_queue_channel_pubsub.cpp

Namespaces

Name
sgns
sgns::processing

Source code

#include "processing_subtask_queue_channel_pubsub.hpp"
#include <base/util.hpp>
#include "base/gossip_auth.hpp"
#include "base/sgns_version.hpp"

namespace sgns::processing
{
    ProcessingSubTaskQueueChannelPubSub::ProcessingSubTaskQueueChannelPubSub(
        std::shared_ptr<GossipPubSub> gossipPubSub,
        const std::string            &processingQueueChannelId ) :
        m_gossipPubSub( std::move( gossipPubSub ) )
    {
        auto processing_queue_topic = processingQueueChannelId + sgns::version::GetNetAndVersionAppendix();
        m_processingQueueChannel    = std::make_shared<GossipPubSubTopic>( m_gossipPubSub, processing_queue_topic );
    }

    ProcessingSubTaskQueueChannelPubSub::~ProcessingSubTaskQueueChannelPubSub()
    {
        m_logger->debug( "[RELEASED] this: {}", reinterpret_cast<size_t>( this ) );
    }

    outcome::result<
        std::variant<std::chrono::milliseconds, std::shared_future<std::shared_ptr<GossipPubSubTopic::Subscription>>>>
    ProcessingSubTaskQueueChannelPubSub::Listen( std::chrono::milliseconds msSubscriptionWaitingDuration )
    {
        // Subscribe to the processing queue channel
        auto subscription_future = m_processingQueueChannel->Subscribe(
            [weakSelf = weak_from_this()]( boost::optional<const GossipPubSub::Message &> message )
            {
                if ( auto self = weakSelf.lock() )
                {
                    self->OnProcessingChannelMessage( message );
                }
            },
            msSubscriptionWaitingDuration.count() == 0 // If waiting duration is 0, subscribe now
        );

        if ( msSubscriptionWaitingDuration.count() > 0 )
        {
            // If a waiting duration is provided, wait for the subscription to complete
            std::chrono::milliseconds resultTime;
            bool                      success = waitForCondition(
                [&subscription_future]()
                { return subscription_future.wait_for( std::chrono::seconds( 0 ) ) == std::future_status::ready; },
                msSubscriptionWaitingDuration,
                &resultTime );

            if ( success )
            {
                m_logger->debug( "Subscription established after {} ms", resultTime.count() );
                // Fixed: Use consistent type (GossipPubSubTopic::Subscription)
                return std::variant<std::chrono::milliseconds,
                                    std::shared_future<std::shared_ptr<GossipPubSubTopic::Subscription>>>( resultTime );
            }
            m_logger->error( "Subscription not established within the specified time ({} ms)",
                             msSubscriptionWaitingDuration.count() );
            return outcome::failure( boost::system::errc::timed_out );
        }

        // If no waiting requested, return the future
        // Fixed: Use std::move for efficiency (though not strictly required for shared_future)
        return std::variant<std::chrono::milliseconds,
                            std::shared_future<std::shared_ptr<GossipPubSubTopic::Subscription>>>(
            std::move( subscription_future ) );
    }

    void ProcessingSubTaskQueueChannelPubSub::RequestQueueOwnership( const std::string &nodeId )
    {
        // Send a request to grab a subtask queue
        SGProcessing::ProcessingChannelMessage message;
        message.mutable_subtask_queue_request()->set_node_id( nodeId );

        // Private-network publish sealing (CR-G01): seal under a set filter
        // with the gossip host keypair; fail closed when a filter is set but
        // no key is wired. No filter -> raw publish, byte-identical.
        const std::string raw_payload = message.SerializeAsString();
        sgns::networkregistry::MembershipFilter membershipFilter;
        std::shared_ptr<const libp2p::crypto::KeyPair> signingKey;
        {
            std::lock_guard<std::mutex> guard( m_mutexMembershipFilter );
            membershipFilter = m_membershipFilter;
            signingKey       = m_gossipSigningKey;
        }
        if ( !membershipFilter )
        {
            m_processingQueueChannel->Publish( raw_payload );
        }
        else if ( !signingKey )
        {
            m_logger->error( "Queue channel publish FAILED CLOSED: membership filter set but no "
                             "gossip signing key wired" );
        }
        else
        {
            auto from_bytes = sgns::base::DeriveGossipFromBytes( *signingKey );
            if ( from_bytes.has_error() )
            {
                m_logger->error( "Queue channel publish FAILED CLOSED: cannot derive from-bytes "
                                 "from the gossip signing key" );
            }
            else
            {
                auto sealed = sgns::base::SealGossipPayload( *signingKey, from_bytes.value(), sgns::base::detail::StringSpan( raw_payload ) );
                if ( sealed.has_error() )
                {
                    m_logger->error( "Queue channel publish FAILED CLOSED: sealing failed ({})",
                                     static_cast<int>( sealed.error() ) );
                }
                else
                {
                    m_processingQueueChannel->Publish( sealed.value() );
                }
            }
        }
    }

    void ProcessingSubTaskQueueChannelPubSub::PublishQueue( std::shared_ptr<SGProcessing::SubTaskQueue> queue )
    {
        SGProcessing::ProcessingChannelMessage message;
        message.set_allocated_subtask_queue( queue.get() );

        // Private-network publish sealing (CR-G01): seal under a set filter;
        // fail closed when a filter is set but no key is wired. No filter ->
        // raw publish, byte-identical.
        const std::string raw_payload = message.SerializeAsString();
        message.release_subtask_queue();
        sgns::networkregistry::MembershipFilter membershipFilter;
        std::shared_ptr<const libp2p::crypto::KeyPair> signingKey;
        {
            std::lock_guard<std::mutex> guard( m_mutexMembershipFilter );
            membershipFilter = m_membershipFilter;
            signingKey       = m_gossipSigningKey;
        }
        if ( !membershipFilter )
        {
            m_processingQueueChannel->Publish( raw_payload );
        }
        else if ( !signingKey )
        {
            m_logger->error( "Queue channel publish FAILED CLOSED: membership filter set but no "
                             "gossip signing key wired" );
        }
        else
        {
            auto from_bytes = sgns::base::DeriveGossipFromBytes( *signingKey );
            if ( from_bytes.has_error() )
            {
                m_logger->error( "Queue channel publish FAILED CLOSED: cannot derive from-bytes "
                                 "from the gossip signing key" );
            }
            else
            {
                auto sealed = sgns::base::SealGossipPayload( *signingKey, from_bytes.value(), sgns::base::detail::StringSpan( raw_payload ) );
                if ( sealed.has_error() )
                {
                    m_logger->error( "Queue channel publish FAILED CLOSED: sealing failed ({})",
                                     static_cast<int>( sealed.error() ) );
                }
                else
                {
                    m_processingQueueChannel->Publish( sealed.value() );
                }
            }
        }
    }

    void ProcessingSubTaskQueueChannelPubSub::SetQueueRequestSink( QueueRequestSink queueRequestSink )
    {
        m_queueRequestSink = std::move( queueRequestSink );
    }

    void ProcessingSubTaskQueueChannelPubSub::SetQueueUpdateSink( QueueUpdateSink queueUpdateSink )
    {
        m_queueUpdateSink = std::move( queueUpdateSink );
    }

    void ProcessingSubTaskQueueChannelPubSub::SetMembershipFilter( sgns::networkregistry::MembershipFilter filter )
    {
        std::lock_guard<std::mutex> guard( m_mutexMembershipFilter );
        m_membershipFilter = std::move( filter );
    }

    void ProcessingSubTaskQueueChannelPubSub::SetGossipSigningKey( std::shared_ptr<const libp2p::crypto::KeyPair> key )
    {
        std::lock_guard<std::mutex> guard( m_mutexMembershipFilter );
        m_gossipSigningKey = std::move( key );
    }

    void ProcessingSubTaskQueueChannelPubSub::OnProcessingChannelMessage(
        boost::optional<const GossipPubSub::Message &> message )
    {
        // Membership gate (15-13) + payload authentication (15-14, CR-G01):
        // under a set membership filter the message is FIRST authenticated
        // (OpenGossipPayload: envelope present, embedded key derives the
        // from-field PeerId, signature covers from+payload) and only THEN
        // authorized BEFORE any queue ownership/sync handling. Unsigned or
        // unverifiable messages are denied under a set filter even when
        // `from` names a member. Empty filter = public pass-through (raw
        // parse, byte-identical); empty/malformed `from` fails
        // OpenGossipPayload itself (fail-closed).
        gsl::span<const uint8_t> channel_parse_source;
        if ( message )
        {
            channel_parse_source = gsl::span<const uint8_t>( message->data.data(), message->data.size() );
            sgns::networkregistry::MembershipFilter membershipFilter;
            {
                std::lock_guard<std::mutex> guard( m_mutexMembershipFilter );
                membershipFilter = m_membershipFilter;
            }
            if ( membershipFilter )
            {
                auto opened = sgns::base::OpenGossipPayload(
                    gsl::span<const uint8_t>( message->from.data(), message->from.size() ),
                    channel_parse_source );
                if ( opened.has_error() )
                {
                    m_logger->debug( "Processing queue channel message failed payload authentication ({}) -- ignored",
                                     static_cast<int>( opened.error() ) );
                    return;
                }
                channel_parse_source = opened.value().payload;
                if ( !sgns::networkregistry::AuthorizeGossipSender( membershipFilter, message->from ) )
                {
                    m_logger->debug( "Processing queue channel message from unauthorized sender ignored" );
                    return;
                }
            }
        }

        if ( message )
        {
            SGProcessing::ProcessingChannelMessage channelMesssage;
            if ( channelMesssage.ParseFromArray( channel_parse_source.data(),
                                                 static_cast<int>( channel_parse_source.size() ) ) )
            {
                if ( channelMesssage.has_subtask_queue_request() )
                {
                    HandleSubTaskQueueRequest( channelMesssage );
                }
                else if ( channelMesssage.has_subtask_queue() )
                {
                    HandleSubTaskQueue( channelMesssage );
                }
            }
        }
    }

    void ProcessingSubTaskQueueChannelPubSub::HandleSubTaskQueueRequest(
        SGProcessing::ProcessingChannelMessage &channelMesssage )
    {
        if ( m_queueRequestSink )
        {
            auto message = channelMesssage.subtask_queue_request();
            if ( !m_queueRequestSink( message ) )
            {
                m_logger->debug( "Queue request is pending for node {}", message.node_id() );
            }
            else
            {
                m_logger->debug( "Queue request was immediately fulfilled for node {}", message.node_id() );
            }
        }
    }

    void ProcessingSubTaskQueueChannelPubSub::HandleSubTaskQueue(
        SGProcessing::ProcessingChannelMessage &channelMesssage )
    {
        auto message = channelMesssage.release_subtask_queue();
        if ( m_queueUpdateSink )
        {
            auto queueChanged = m_queueUpdateSink( message );
            m_logger->debug( "Queue changed = {} during release for node", queueChanged );
        }
    }

    size_t ProcessingSubTaskQueueChannelPubSub::GetActiveNodesCount() const
    {
        // include ourselves
        return m_processingQueueChannel->getPeerCount() + 1;
    }

    std::vector<libp2p::peer::PeerId> ProcessingSubTaskQueueChannelPubSub::GetActiveNodes() const
    {
        return m_processingQueueChannel->getAllPeers();
    }

}

Updated on 2026-10-06 at 13:34:21 +0000