title: networkregistry/NetworkRegistry.cpp summary: Implementation of the SecureCRDT-backed per-privateNetworkId membership registry (D-03, D-06): TPR-majority bootstrap, cached self-governance, secret-free records.
networkregistry/NetworkRegistry.cpp¶
Implementation of the SecureCRDT-backed per-privateNetworkId membership registry (D-03, D-06): TPR-majority bootstrap, cached self-governance, secret-free records. More...
Namespaces¶
| Name |
|---|
| sgns |
| sgns::networkregistry |
Detailed Description¶
Implementation of the SecureCRDT-backed per-privateNetworkId membership registry (D-03, D-06): TPR-majority bootstrap, cached self-governance, secret-free records.
Date: 2026-09-01 Henrique A. Klein ([email protected])
Source code¶
#include "networkregistry/NetworkRegistry.hpp"
#include <algorithm>
#include <system_error>
#include <unordered_set>
#include "base/hexutil.hpp"
#include "crdt/globaldb/globaldb.hpp"
#include "securecrdt/QuorumThresholdValidation.hpp"
namespace sgns::networkregistry
{
namespace
{
bool HasPeerIdMultihashPrefix( const std::string &entry )
{
return entry.rfind( "Qm", 0 ) == 0 || entry.rfind( "12D3KooW", 0 ) == 0;
}
bool IsLowerHex( const std::string &value )
{
if ( value.empty() || value.size() % 2 != 0 )
{
return false;
}
return std::all_of( value.begin(), value.end(), []( unsigned char c ) {
return ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'f' );
} );
}
std::vector<std::string> SplitLines( const std::string &raw )
{
std::vector<std::string> lines;
size_t start = 0;
while ( start <= raw.size() )
{
const auto pos = raw.find( '\n', start );
if ( pos == std::string::npos )
{
if ( start < raw.size() )
{
lines.push_back( raw.substr( start ) );
}
break;
}
lines.push_back( raw.substr( start, pos - start ) );
start = pos + 1;
}
return lines;
}
std::string EscapeRegex( const std::string &value )
{
static const std::string metacharacters = R"(\.^$|()[]{}*+?)";
std::string result;
result.reserve( value.size() * 2 );
for ( const char byte : value )
{
if ( metacharacters.find( byte ) != std::string::npos )
{
result.push_back( '\\' );
}
result.push_back( byte );
}
return result;
}
bool ParseCountLine( const std::string &line, const std::string &tag, size_t &count )
{
if ( line.rfind( tag + " ", 0 ) != 0 )
{
return false;
}
const std::string number = line.substr( tag.size() + 1 );
if ( number.empty() || number.size() > 9 )
{
return false;
}
if ( !std::all_of( number.begin(), number.end(), []( unsigned char c ) { return c >= '0' && c <= '9'; } ) )
{
return false;
}
count = static_cast<size_t>( std::stoull( number ) );
return true;
}
sgns::base::Logger &FactoryLogger()
{
static sgns::base::Logger logger = sgns::base::createLogger( "networkregistry" );
return logger;
}
} // namespace
//
// NetworkMembershipPayload
//
// Wire layout (line-based, '\n'-delimited; PeerId base58 and hex address
// characters never contain '\n'):
// SGNS-NETREG-1
// PEERS <n>
// SIGNERS <m>
// VERSION <v>
// FINGERPRINT <hex-or-empty>
// <peer 1..n>
// <signer 1..m>
//
NetworkMembershipPayload::NetworkMembershipPayload( std::vector<std::string> peers,
std::vector<std::string> signers,
uint32_t pnet_key_version,
std::string pnet_key_fingerprint ) :
network_peers( std::move( peers ) ),
network_signers( std::move( signers ) ),
pnet_key_version( pnet_key_version ),
pnet_key_fingerprint( std::move( pnet_key_fingerprint ) )
{
}
std::vector<uint8_t> NetworkMembershipPayload::SerializeToBytes() const
{
std::string out;
out.append( MAGIC.data(), MAGIC.size() );
out += '\n';
out += "PEERS " + std::to_string( network_peers.size() ) + '\n';
out += "SIGNERS " + std::to_string( network_signers.size() ) + '\n';
out += "VERSION " + std::to_string( pnet_key_version ) + '\n';
out += "FINGERPRINT " + pnet_key_fingerprint + '\n';
for ( const auto &peer : network_peers )
{
out += peer;
out += '\n';
}
for ( const auto &signer : network_signers )
{
out += signer;
out += '\n';
}
return std::vector<uint8_t>( out.begin(), out.end() );
}
std::optional<NetworkMembershipPayload> NetworkMembershipPayload::FromBytes( const std::vector<uint8_t> &bytes )
{
if ( bytes.empty() )
{
return std::nullopt;
}
const std::string raw( bytes.begin(), bytes.end() );
if ( raw.rfind( std::string( MAGIC ) + "\n", 0 ) != 0 )
{
return std::nullopt;
}
const auto lines = SplitLines( raw.substr( MAGIC.size() + 1 ) );
// 4 header lines + n peers + m signers
if ( lines.size() < 4 )
{
return std::nullopt;
}
size_t peer_count = 0;
size_t signer_count = 0;
if ( !ParseCountLine( lines[0], "PEERS", peer_count ) || !ParseCountLine( lines[1], "SIGNERS", signer_count ) )
{
return std::nullopt;
}
const std::string version_line = lines[2];
if ( version_line.rfind( "VERSION ", 0 ) != 0 )
{
return std::nullopt;
}
const std::string version_number = version_line.substr( 8 );
if ( version_number.empty() || version_number.size() > 9 ||
!std::all_of( version_number.begin(),
version_number.end(),
[]( unsigned char c ) { return c >= '0' && c <= '9'; } ) )
{
return std::nullopt;
}
const std::string fingerprint_line = lines[3];
if ( fingerprint_line.rfind( "FINGERPRINT ", 0 ) != 0 )
{
return std::nullopt;
}
const std::string fingerprint = fingerprint_line.substr( 12 );
if ( lines.size() != 4 + peer_count + signer_count )
{
return std::nullopt;
}
NetworkMembershipPayload payload;
payload.network_peers.assign( lines.begin() + 4,
lines.begin() + 4 + static_cast<std::ptrdiff_t>( peer_count ) );
payload.network_signers.assign( lines.begin() + 4 + static_cast<std::ptrdiff_t>( peer_count ), lines.end() );
payload.pnet_key_version = static_cast<uint32_t>( std::stoull( version_number ) );
payload.pnet_key_fingerprint = fingerprint;
return payload;
}
bool NetworkMembershipPayload::DeserializeFromBytes( const std::vector<uint8_t> &bytes )
{
auto payload = FromBytes( bytes );
if ( !payload )
{
return false;
}
*this = std::move( *payload );
return true;
}
bool NetworkMembershipPayload::Verify( const std::vector<uint8_t> &payload ) const
{
if ( payload.empty() )
{
return false;
}
// Structural-only check: re-parse independently and validate shape --
// never diff against cached/mutable state (TrustedPeerListPayload
// convention).
auto decoded = FromBytes( payload );
if ( !decoded )
{
return false;
}
if ( decoded->network_peers.empty() )
{
return false;
}
std::unordered_set<std::string> unique_entries;
for ( const auto &peer : decoded->network_peers )
{
if ( peer.empty() || !HasPeerIdMultihashPrefix( peer ) || !unique_entries.insert( peer ).second )
{
return false; // empty, non-PeerId, or duplicate entry
}
}
for ( const auto &signer : decoded->network_signers )
{
if ( !sgns::base::IsHexAddress( signer ) || !unique_entries.insert( signer ).second )
{
return false; // malformed or duplicate signer address
}
}
if ( decoded->pnet_key_version < 1 )
{
return false;
}
if ( !decoded->pnet_key_fingerprint.empty() && !IsLowerHex( decoded->pnet_key_fingerprint ) )
{
return false;
}
return true;
}
void NetworkMembershipPayload::Apply()
{
// Cache overwrite happens in NetworkRegistry::TryConfirm (the owning
// registry), mirroring TrustedPeerListPayload's no-op Apply.
}
//
// NetworkRegistry
//
NetworkRegistry::NetworkRegistry( std::shared_ptr<sgns::securecrdt::SecureCrdt> secure_crdt,
std::shared_ptr<sgns::trustedpeer::TrustedPeerRegistry> global_trusted_peers,
std::string private_network_id,
std::vector<std::string> initial_network_peers,
uint64_t network_quorum_threshold,
std::vector<std::string> initial_network_signers,
std::string pnet_key_fingerprint,
sgns::crdt::HierarchicalKey base_key,
std::shared_ptr<sgns::crdt::GlobalDB> global_db ) :
secure_crdt_( std::move( secure_crdt ) ),
global_trusted_peers_( std::move( global_trusted_peers ) ),
base_key_( std::move( base_key ) ),
network_quorum_threshold_( network_quorum_threshold ),
cached_network_peers_( std::move( initial_network_peers ) ),
cached_network_signers_( std::move( initial_network_signers ) ),
pnet_key_fingerprint_( std::move( pnet_key_fingerprint ) ),
private_network_id_( std::move( private_network_id ) ),
global_db_( std::move( global_db ) )
{
// Snapshot the TPR bootstrap authority NOW (cached-only resolution
// never consults the live TPR again -- Pitfall 9).
tpr_bootstrap_peers_ = global_trusted_peers_->GetCurrentPeers();
tpr_majority_threshold_ = sgns::securecrdt::StrictMajorityQuorumFloor( tpr_bootstrap_peers_.size() );
}
NetworkRegistry::~NetworkRegistry()
{
Unregister();
}
sgns::crdt::HierarchicalKey NetworkRegistry::DefaultBaseKey( const std::string &private_network_id )
{
return sgns::crdt::HierarchicalKey( "network-registry" ).ChildString( private_network_id );
}
outcome::result<std::shared_ptr<NetworkRegistry>> NetworkRegistry::New(
std::shared_ptr<sgns::securecrdt::SecureCrdt> secure_crdt,
std::shared_ptr<sgns::trustedpeer::TrustedPeerRegistry> global_trusted_peers,
std::string private_network_id,
std::vector<std::string> initial_network_peers,
uint64_t network_quorum_threshold,
std::vector<std::string> initial_network_signers,
std::string pnet_key_fingerprint,
std::shared_ptr<sgns::crdt::GlobalDB> global_db )
{
if ( !secure_crdt || !global_trusted_peers || private_network_id.empty() )
{
return outcome::failure( std::errc::invalid_argument );
}
const auto tpr_peers = global_trusted_peers->GetCurrentPeers();
// Floor check #1: the bootstrap threshold is the strict majority of
// the TPR's CURRENT peer set (D-06) -- the bootstrap record confirms
// only with a majority of the global trusted peers.
const auto bootstrap_threshold = sgns::securecrdt::StrictMajorityQuorumFloor( tpr_peers.size() );
auto floor_result = sgns::securecrdt::ValidateQuorumThreshold( bootstrap_threshold, tpr_peers.size() );
if ( floor_result.has_error() )
{
return floor_result.error();
}
// Floor check #2: the self-governance quorum must clear the strict
// majority floor over the network membership.
floor_result = sgns::securecrdt::ValidateQuorumThreshold( network_quorum_threshold, initial_network_peers.size() );
if ( floor_result.has_error() )
{
return floor_result.error();
}
// Floor check #2b: when member signers are provisioned, the
// self-governance quorum must also be satisfiable by them.
if ( !initial_network_signers.empty() )
{
floor_result = sgns::securecrdt::ValidateQuorumThreshold( network_quorum_threshold,
initial_network_signers.size() );
if ( floor_result.has_error() )
{
return floor_result.error();
}
}
// WR-03: non-destructive duplicate check. Resolving an existing
// policy entry for this network id means a live registry already
// owns the branch. Fail here -- BEFORE any make_shared/Register call
// -- so nothing is registered, no instance is created, and the live
// entry (and the registry still using it) remains fully functional.
// Without this, SecureCrdtRegistry::Register would REPLACE the live
// entry, the destroyed duplicate's ~NetworkRegistry -> Unregister()
// would then remove the newly inserted entry, and the first registry
// would be left with no policy entry (every subsequent write failing
// UNREGISTERED_KEY).
const auto base_key = DefaultBaseKey( private_network_id );
if ( secure_crdt->Registry().Resolve( base_key.GetKey() ).has_value() )
{
// Names the public network id / derived base key only -- never
// any key material (D-03 logging posture).
FactoryLogger()->error( "{}: a registry for private network {} is already registered (base key {}); "
"duplicate construction refused, live registry untouched",
__func__,
private_network_id,
base_key.GetKey() );
return outcome::failure( std::errc::address_in_use );
}
auto instance = std::make_shared<NetworkRegistry>( std::move( secure_crdt ),
std::move( global_trusted_peers ),
private_network_id,
std::move( initial_network_peers ),
network_quorum_threshold,
std::move( initial_network_signers ),
std::move( pnet_key_fingerprint ),
base_key,
std::move( global_db ) );
if ( !instance->RegisterSignerSetSource() )
{
return outcome::failure( std::errc::address_in_use );
}
if ( instance->global_db_ )
{
// G-WR-02: fail-closed construction. A failed change-callback
// registration would otherwise leave New succeeding with NO live
// refresh on this node -- the 15-12 live-widening core feature
// silently dead. Fail construction instead.
if ( !instance->RegisterCrdtChangeCallback() )
{
instance->logger_->error( "{}: registering the CRDT change callback for pattern "
"\"{}\" failed (pattern already live on the GlobalDB?); "
"failing construction -- live membership refresh must not "
"silently degrade",
__func__,
"/?" + EscapeRegex( instance->base_key_.GetKey() ) + "(/sig/.*)?" );
// Explicit teardown (NOT destructor-driven): the just-registered
// policy entry's peer_registry strong capture pins the instance
// in the SecureCrdtRegistry, so ~NetworkRegistry would never
// run while the caller still holds the failure result.
// Unregister() removes the entry (owner_token matches THIS
// instance), breaking the pin; no refresh thread and no
// callback pattern exist at this point (cleared on the
// failure path), so the rest of the teardown is a no-op.
instance->Unregister();
return outcome::failure( std::errc::address_in_use );
}
}
// WR-04: both production wiring paths run SecureCrdt::RegisterFilters()
// BEFORE this factory (GeniusNode.cpp:1037 vs :1059; the
// TrustStartupController path has the same ordering), so the
// network-registry/<id> pattern would otherwise receive NO ingest
// element filter and remote-originated unsigned/under-signed
// membership payloads and sig children would be accepted into the
// datastore without canonical-signer verification. RegisterFilters()
// is safely re-runnable: it re-snapshots AllEntries() (now including
// this registry's pattern) and re-installs one element filter per
// pattern (re-registration replaces-and-succeeds).
if ( !instance->secure_crdt_->RegisterFilters() )
{
instance->logger_->error( "{}: re-registering SecureCrdt ingest filters failed for base key {}",
__func__,
instance->base_key_.GetKey() );
// Same pinning hazard as the callback-failure branch above: the
// registered policy entry strongly captures the instance, so the
// explicit Unregister() here is what actually removes it (and any
// partially installed per-pattern filter) -- without it a "failed"
// New would leave a live zombie policy entry that blocks every
// retry with address_in_use.
instance->Unregister();
return outcome::failure( std::errc::io_error );
}
instance->logger_->info( "{}: SecureCrdt ingest filters re-registered -- network-registry branch {} "
"is now ingest-filtered",
__func__,
instance->base_key_.GetKey() );
return instance;
}
bool NetworkRegistry::RegisterSignerSetSource()
{
sgns::securecrdt::SecureCrdtRegistryEntry entry;
entry.signer_set_source = [weak_self = weak_from_this()](
const std::string & ) -> outcome::result<sgns::securecrdt::SignerSetSnapshot>
{
auto self = weak_self.lock();
if ( !self )
{
return sgns::securecrdt::SignerSetSnapshot{};
}
return self->ResolveSignerSet();
};
entry.make_instance = []() -> std::shared_ptr<sgns::securecrdt::ISignedCRDTData>
{ return std::make_shared<NetworkMembershipPayload>(); };
entry.owner_token = ®istry_token_;
// D-04: record the explicit per-key authority so Resolve() callers
// can see which PeerRegistry owns this network's branch.
entry.peer_registry = shared_from_this();
// G-WR-04: atomic-detecting insert. The Resolve() pre-check in New()
// is only the descriptive fast path; THIS call is the safety
// mechanism -- RegisterIfAbsent can never replace a live entry, so
// two racing New() constructions can no longer clobber each other's
// policy entry (the loser fails with address_in_use and its teardown
// removes nothing it does not own).
return secure_crdt_->Registry().RegisterIfAbsent( EscapeRegex( base_key_.GetKey() ), std::move( entry ) );
}
bool NetworkRegistry::RegisterCrdtChangeCallback()
{
// BurnConfig pattern (re-derived for a trust-transitioning registry):
// refresh the cache when a base_key or sig/<addr> child element
// arrives, so later quorum-signed membership changes land without an
// explicit TryConfirm call. Unlike BurnConfig, this registry's
// signer-set authority TRANSITIONS at confirmation, so the quorum
// read prunes stale signature children (GlobalDB::Remove) -- running
// that re-entrantly from inside a datastore Put callback corrupts
// the datastore. The callback therefore only flags + notifies; the
// refresh thread performs the actual TryConfirm.
change_callback_pattern_ = "/?" + EscapeRegex( base_key_.GetKey() ) + "(/sig/.*)?";
auto weak_self = weak_from_this();
const bool registered = global_db_->RegisterNewElementCallback(
change_callback_pattern_,
[weak_self]( sgns::crdt::CRDTCallbackManager::NewDataPair, const std::string & )
{
if ( auto self = weak_self.lock() )
{
self->refresh_pending_.store( true, std::memory_order_release );
std::lock_guard<std::mutex> lock( self->refresh_mutex_ );
self->refresh_cv_.notify_one();
}
} );
if ( !registered )
{
// G-WR-02: clear the pattern so the teardown path (and the
// caller's failure handling) knows no callback of ours is live --
// the pre-existing registration under this pattern belongs to
// someone else and must not be removed by our Unregister().
change_callback_pattern_.clear();
logger_->error( "{}: change-callback pattern \"{}\" could not be registered "
"(already live on the GlobalDB)",
__func__,
"/?" + EscapeRegex( base_key_.GetKey() ) + "(/sig/.*)?" );
return false;
}
refresh_stopping_.store( false, std::memory_order_release );
refresh_thread_ = std::thread( [weak_self] {
if ( auto self = weak_self.lock() )
{
self->RefreshLoop();
}
} );
return true;
}
void NetworkRegistry::RefreshLoop()
{
while ( !refresh_stopping_.load( std::memory_order_acquire ) )
{
std::unique_lock<std::mutex> lock( refresh_mutex_ );
refresh_cv_.wait( lock,
[&]
{
return refresh_pending_.load( std::memory_order_acquire )
|| refresh_stopping_.load( std::memory_order_acquire );
} );
if ( refresh_stopping_.load( std::memory_order_acquire ) )
{
return;
}
// WR-02: drain-once semantics -- the notification that woke this
// iteration is consumed here, WHILE still holding refresh_mutex_
// (before the unlock below, which stays before TryConfirm). The
// mutex-guarded clear is ordered against the datastore callback's
// mutex-guarded notify, so a notification arriving DURING
// TryConfirm re-sets the flag and the next loop iteration wakes
// and processes it -- no lost refresh. After the last
// notification the wait predicate is false again and the thread
// returns to waiting (no permanent busy-spin of TryConfirm +
// GlobalDB scans). Deliberately NO retry semantics: re-setting
// the flag after a success(false) TryConfirm would resurrect the
// spin.
refresh_pending_.store( false, std::memory_order_release );
lock.unlock();
refresh_attempts_.fetch_add( 1, std::memory_order_relaxed );
auto confirmed = TryConfirm();
if ( confirmed.has_error() )
{
logger_->warn( "{}: TryConfirm failed: {}", __func__, confirmed.error().message() );
}
}
}
uint64_t NetworkRegistry::RefreshAttemptsForTesting() const
{
return refresh_attempts_.load( std::memory_order_relaxed );
}
outcome::result<sgns::securecrdt::SignerSetSnapshot> NetworkRegistry::CurrentSignerSet() const
{
return ResolveSignerSet();
}
outcome::result<sgns::securecrdt::SignerSetSnapshot> NetworkRegistry::ResolveSignerSet() const
{
// Cached state ONLY -- NEVER ReadIfQuorum from here (Pitfall 9: this
// runs inside SecureCrdt's verification flow).
std::shared_lock<std::shared_mutex> lock( cache_mutex_ );
if ( !bootstrap_confirmed_ )
{
return sgns::securecrdt::SignerSetSnapshot{ tpr_bootstrap_peers_, tpr_majority_threshold_ };
}
return sgns::securecrdt::SignerSetSnapshot{ cached_network_signers_, network_quorum_threshold_ };
}
outcome::result<void> NetworkRegistry::SeedBootstrap( const std::vector<std::string> &initial_network_peers )
{
logger_->info( "{}: seeding bootstrap membership record ({} peers) for private network",
__func__,
initial_network_peers.size() );
std::vector<std::string> signers;
{
std::shared_lock<std::shared_mutex> lock( cache_mutex_ );
signers = cached_network_signers_;
}
const NetworkMembershipPayload payload( initial_network_peers,
std::move( signers ),
1,
pnet_key_fingerprint_ );
// No self-signature: TPR member nodes add signatures through the
// standard propose/sign flow until the TPR-majority quorum is met.
return secure_crdt_->ProposeValue( base_key_, payload.SerializeToBytes() );
}
outcome::result<void> NetworkRegistry::ProposeMembershipChange( const std::vector<std::string> &new_peers,
const std::vector<std::string> &new_signers )
{
logger_->info( "{}: proposing membership change ({} peers)", __func__, new_peers.size() );
std::vector<std::string> signers = new_signers;
if ( signers.empty() )
{
// Empty replacement would permanently fail-close self-governance:
// keep the currently-cached signer list instead.
std::shared_lock<std::shared_mutex> lock( cache_mutex_ );
signers = cached_network_signers_;
}
std::string fingerprint;
{
std::shared_lock<std::shared_mutex> lock( cache_mutex_ );
fingerprint = pnet_key_fingerprint_;
}
const NetworkMembershipPayload payload( new_peers, std::move( signers ), 1, std::move( fingerprint ) );
return secure_crdt_->ProposeValue( base_key_, payload.SerializeToBytes() );
}
outcome::result<void> NetworkRegistry::SignMembershipChange( const std::string &signer_address,
const std::vector<uint8_t> &signature )
{
logger_->info( "{}: signing membership change (signer={})", __func__, signer_address );
return secure_crdt_->AddSignature( base_key_,
signer_address,
std::vector<uint8_t>( signature.begin(), signature.end() ) );
}
outcome::result<bool> NetworkRegistry::TryConfirm()
{
auto read_result = secure_crdt_->ReadIfQuorum( base_key_ );
if ( read_result.has_error() )
{
return read_result.error();
}
if ( !read_result.value().has_value() )
{
return outcome::success( false );
}
const auto bytes = read_result.value()->toVector();
auto payload = NetworkMembershipPayload::FromBytes( bytes );
if ( !payload )
{
logger_->error( "{}: confirmed value failed to deserialize", __func__ );
return outcome::failure( std::errc::bad_message );
}
if ( !payload->Verify( bytes ) )
{
logger_->error( "{}: confirmed value failed structural verification", __func__ );
return outcome::failure( std::errc::bad_message );
}
payload->Apply();
{
std::unique_lock<std::shared_mutex> lock( cache_mutex_ );
cached_network_peers_ = payload->GetNetworkPeers();
cached_network_signers_ = payload->GetNetworkSigners();
bootstrap_confirmed_ = true;
}
logger_->info( "{}: confirmed membership record ({} peers)", __func__, payload->GetNetworkPeers().size() );
return outcome::success( true );
}
std::vector<std::string> NetworkRegistry::GetCurrentPeers() const
{
std::shared_lock<std::shared_mutex> lock( cache_mutex_ );
return cached_network_peers_;
}
bool NetworkRegistry::IsBootstrapConfirmed() const
{
std::shared_lock<std::shared_mutex> lock( cache_mutex_ );
return bootstrap_confirmed_;
}
void NetworkRegistry::Unregister()
{
// Step 1: remove the policy entry (compare-and-remove with the owner
// token). `removed_own_entry` scopes the Step-4 filter teardown to the
// case where THIS instance owned the pattern -- a duplicate-New loser
// (or a teardown racing a newer owner) must never strip the LIVE
// registry's ingest filter.
const bool removed_own_entry = secure_crdt_
&& secure_crdt_->Registry().UnregisterIf( EscapeRegex( base_key_.GetKey() ), ®istry_token_ );
// Step 2: stop + join the refresh thread BEFORE tearing down the
// callback or the db references it uses.
refresh_stopping_.store( true, std::memory_order_release );
{
std::lock_guard<std::mutex> lock( refresh_mutex_ );
refresh_cv_.notify_all();
}
if ( refresh_thread_.joinable() )
{
refresh_thread_.join();
}
// Step 3: remove the change callback (only when OUR registration is
// the live one -- the pattern is empty on every path that did not
// register, so a pre-existing foreign callback under the same pattern
// is never touched).
if ( global_db_ && !change_callback_pattern_.empty() )
{
global_db_->UnregisterNewElementCallback( change_callback_pattern_ );
change_callback_pattern_.clear();
}
// Step 4 (G-WR-01): remove the ingest element filter LAST. The
// filter's lambda captures the registry entry BY VALUE (strong
// peer_registry pin, 15-11) -- removing it may drop the final
// NetworkRegistry reference and re-enter ~NetworkRegistry ->
// Unregister(); every earlier step is already torn down by then, and
// the re-entered call's Step 1/Step 4 are no-ops (entry gone ->
// removed_own_entry false; pattern already removed -> erase-remove on
// a missing pattern).
if ( secure_crdt_ && removed_own_entry )
{
secure_crdt_->UnregisterFiltersFor( EscapeRegex( base_key_.GetKey() ) );
}
}
} // namespace sgns::networkregistry
Updated on 2026-10-06 at 13:34:21 +0000