Skip to content

storage/gcs_global_db.cpp

GCS GlobalDB component implementation — init-style lifecycle (D-13), PubSub acquired from GeniusSDK via GeniusSDKGetPubSub() (D-15/D-16a), GlobalDB::Error mapped to NEO-SWARM codes at the boundary (D-14). More...

Namespaces

Name
sgns
sgns::neoswarm
sgns::neoswarm::storage

Detailed Description

GCS GlobalDB component implementation — init-style lifecycle (D-13), PubSub acquired from GeniusSDK via GeniusSDKGetPubSub() (D-15/D-16a), GlobalDB::Error mapped to NEO-SWARM codes at the boundary (D-14).

Date: 2026-08-10

Source code

#include "storage/gcs_global_db.hpp"

#include <chrono>
#include <utility>

#include <libp2p/basic/scheduler.hpp>
#include <libp2p/basic/scheduler/asio_scheduler_backend.hpp>
#include <libp2p/basic/scheduler/scheduler_impl.hpp>

#include "GeniusSDK.h"

#include "crdt/crdt_options.hpp"
#include "crdt/globaldb/globaldb.hpp"

#include "ipfs_lite/ipfs/graphsync/impl/network/network.hpp"
#include "ipfs_lite/ipfs/graphsync/impl/local_requests.hpp"

#include "ipfs_pubsub/gossip_pubsub.hpp"

namespace sgns::neoswarm::storage
{
    namespace
    {
        constexpr uint32_t kSchedulerTickMs = 100;

        Error MapGlobalDbError( crdt::GlobalDB::Error globalDbErr ) noexcept
        {
            switch ( globalDbErr )
            {
                case crdt::GlobalDB::Error::ROCKSDB_IO:
                case crdt::GlobalDB::Error::IPFS_DB_NOT_CREATED:
                case crdt::GlobalDB::Error::DAG_SYNCHER_NOT_LISTENING:
                case crdt::GlobalDB::Error::CRDT_DATASTORE_NOT_CREATED:
                case crdt::GlobalDB::Error::PUBSUB_BROADCASTER_NOT_CREATED:
                case crdt::GlobalDB::Error::INVALID_PARAMETERS:
                case crdt::GlobalDB::Error::GLOBALDB_NOT_STARTED:
                    return Error::GcsDbError;
            }
            return Error::GcsDbError;
        }

        crdt::GlobalDB::Error ExtractGlobalDbError( const std::error_code &ec ) noexcept
        {
            return static_cast<crdt::GlobalDB::Error>( ec.value() );
        }
    } // namespace

    GcsGlobalDb::GcsGlobalDb( Config cfg ) noexcept
        : m_cfg( std::move( cfg ) ), m_logger( neoswarm::CreateLogger( "GcsGlobalDb" ) )
    {
        // Constructor stores config only — no fallible work (D-13).
    }

    GcsGlobalDb::~GcsGlobalDb()
    {
        Shutdown();
    }

    outcome::result<void> GcsGlobalDb::Initialize()
    {
        // Step 2: PubSub acquisition (D-15, D-16, D-16a) — pull from the in-process GeniusSDK.
        // GeniusSDKGetPubSub() returns an opaque, non-owning handle to the SDK's internal
        // GossipPubSub (ownership stays with GeniusSDK's node instance — D-20 ordering
        // guarantees the SDK, and therefore this handle, outlives this component).
        void *pubsubHandle = GeniusSDKGetPubSub();
        if ( !pubsubHandle )
        {
            m_logger->error( "GcsGlobalDb::Initialize — GeniusSDKGetPubSub() returned nullptr; "
                             "GeniusSDK init chain has not run yet, or pubsub is not started "
                             "(D-20 ordering: SDK before GlobalDB)" );
            return outcome::failure( Error::SdkNotInitialized );
        }

        // Non-owning alias: no-op deleter, since GeniusSDK's node instance retains true
        // ownership of the underlying GossipPubSub for the lifetime of the process.
        auto pubsub = std::shared_ptr<sgns::ipfs_pubsub::GossipPubSub>(
            static_cast<sgns::ipfs_pubsub::GossipPubSub *>( pubsubHandle ),
            []( sgns::ipfs_pubsub::GossipPubSub * ) {} );

        return Initialize( std::move( pubsub ) );
    }

    outcome::result<void> GcsGlobalDb::Initialize( std::shared_ptr<sgns::ipfs_pubsub::GossipPubSub> pubsub )
    {
        // Step 1: Guard — double Initialize() is a programmer error.
        if ( m_running.load() )
        {
            m_logger->error( "GcsGlobalDb::Initialize called twice — already running" );
            return outcome::failure( Error::GcsDbError );
        }

        if ( !pubsub )
        {
            m_logger->error( "GcsGlobalDb::Initialize — null pubsub injected" );
            return outcome::failure( Error::GcsDbError );
        }

        // Step 3: Local construction (D-17, D-04) — mirror globaldb_integration.cpp:100-107.
        m_io = std::make_shared<boost::asio::io_context>();
        m_scheduler = std::make_shared<libp2p::basic::SchedulerImpl>(
            std::make_shared<libp2p::basic::AsioSchedulerBackend>( m_io ),
            libp2p::basic::Scheduler::Config{ std::chrono::milliseconds{ kSchedulerTickMs } } );
        m_graphsyncNetwork = std::make_shared<sgns::ipfs_lite::ipfs::graphsync::Network>( pubsub->GetHost(),
                                                                                          m_scheduler );
        m_generator = std::make_shared<sgns::ipfs_lite::ipfs::graphsync::RequestIdGenerator>();

        // Step 4: GlobalDB::New (D-03, D-04) — nullptr datastore, default BackupOptions{} (GCS
        // backups are disabled; the blockchain GlobalDB's backup policy is independent per D-01).
        auto dbResult = crdt::GlobalDB::New( m_io,
                                             m_cfg.m_dbPath,
                                             std::move( pubsub ),
                                             crdt::CrdtOptions::DefaultOptions(),
                                             m_graphsyncNetwork,
                                             m_scheduler,
                                             m_generator,
                                             nullptr,
                                             crdt::GlobalDB::BackupOptions{} );
        if ( dbResult.has_error() )
        {
            const auto &dbError = dbResult.error();
            m_logger->error( "GcsGlobalDb::Initialize — GlobalDB::New failed: {} (value {})",
                             dbError.message(),
                             dbError.value() );
            m_db.reset();
            m_generator.reset();
            m_graphsyncNetwork.reset();
            m_scheduler.reset();
            m_io.reset();
            return outcome::failure( MapGlobalDbError( ExtractGlobalDbError( dbError ) ) );
        }
        m_db = std::move( dbResult.value() );

        // Step 5: Start the GlobalDB.
        m_db->Start();

        // Step 6: Topic wiring (D-07) — listen first, then broadcast.
        m_db->AddListenTopic( Config::kReputationTopic );
        auto broadcastResult = m_db->AddBroadcastTopic( Config::kReputationTopic );
        if ( broadcastResult.has_error() )
        {
            m_logger->error( "GcsGlobalDb::Initialize — AddBroadcastTopic('{}') failed",
                             Config::kReputationTopic );
            m_db->ShutdownNow();
            m_db.reset();
            m_generator.reset();
            m_graphsyncNetwork.reset();
            m_scheduler.reset();
            m_io.reset();
            return outcome::failure( Error::GcsDbError );
        }

        // Step 7: Spawn the io thread (mirrors globaldb_integration.cpp:122).
        auto io = m_io;
        m_ioThread = std::thread( [io]() { io->run(); } );
        m_running.store( true );
        m_logger->info( "GcsGlobalDb initialized at '{}' — topic '{}' wired", m_cfg.m_dbPath, Config::kReputationTopic );
        return outcome::success();
    }

    void GcsGlobalDb::Shutdown() noexcept
    {
        if ( !m_running.load() )
        {
            return;
        }

        if ( m_db )
        {
            m_db->ShutdownNow(); // idempotent per GlobalDB contract
        }
        if ( m_io )
        {
            m_io->stop();
        }
        if ( m_ioThread.joinable() )
        {
            m_ioThread.join();
        }
        m_db.reset();
        m_generator.reset();
        m_graphsyncNetwork.reset();
        m_scheduler.reset();
        m_io.reset();
        m_running.store( false );
        m_logger->info( "GcsGlobalDb shut down" );
    }

    bool GcsGlobalDb::IsRunning() const noexcept
    {
        return m_running.load();
    }

} // namespace sgns::neoswarm::storage

Updated on 2026-10-09 at 03:59:42 +0000