@@ -309,12 +309,17 @@ std::optional<pool::PeerConnectionType> NodeImpl::handle_version(std::unique_ptr
309309 peer->write (std::move (getaddrs_msg));
310310 }
311311
312- // Reject peers running too-old protocol
313- if (msg->m_version < m_tracker.m_params ->minimum_protocol_version )
312+ // Reject peers running too-old protocol. The floor is the RUNTIME
313+ // ratcheted value (cold 1400, lifted to 3500 once >=95% of window work
314+ // desires the best share's version -- apply_min_protocol_ratchet), NOT the
315+ // immutable config floor. Read lock-free; compute thread publishes via store.
316+ const uint32_t min_proto =
317+ m_runtime_min_protocol_version.load (std::memory_order_relaxed);
318+ if (msg->m_version < min_proto)
314319 {
315320 LOG_WARNING << " Peer " << msg->m_addr_from .m_endpoint .to_string ()
316321 << " protocol " << msg->m_version
317- << " < minimum " << m_tracker. m_params -> minimum_protocol_version
322+ << " < minimum " << min_proto
318323 << " , disconnecting" ;
319324 throw std::runtime_error (" peer protocol too old" );
320325 }
@@ -806,6 +811,58 @@ void NodeImpl::notify_local_share(const uint256& share_hash)
806811 run_think ();
807812}
808813
814+ void NodeImpl::apply_min_protocol_ratchet ()
815+ {
816+ // Runtime wiring of the accept-floor ratchet #602 landed as a pure function.
817+ // Mirrors oracle main.py:213-216 (run on each best-share advance) + data.py:857
818+ // update_min_protocol_version: over the window [CHAIN_LENGTH*9/10, CHAIN_LENGTH]
819+ // behind the best share's PARENT, lift the inbound P2P accept-floor 1400 -> 3500
820+ // once the best share's VERSION holds >=95% of the work-weighted desired-version
821+ // tally. Runtime sibling of the 60% version-switch gate (share_check.hpp step 2),
822+ // reading the SAME window. MUST be called under exclusive m_tracker_mutex.
823+ const uint32_t target = PoolConfig::SHARE_MINIMUM_PROTOCOL_VERSION ; // 3500
824+ const uint32_t current =
825+ m_runtime_min_protocol_version.load (std::memory_order_relaxed);
826+ if (current >= target) // already ratcheted -> latched, no-op
827+ return ;
828+ if (m_best_share_hash.IsNull () || !m_tracker.chain .contains (m_best_share_hash))
829+ return ;
830+
831+ // best share's VERSION (share TYPE version) + its parent hash
832+ // (oracle: previous_share = shares[best_share.previous_share_hash]).
833+ int64_t best_version = 0 ;
834+ uint256 prev_hash;
835+ m_tracker.chain .get_share (m_best_share_hash).invoke ([&](auto * obj) {
836+ best_version = std::remove_pointer_t <decltype (obj)>::version;
837+ prev_hash = obj->m_prev_hash ;
838+ });
839+ if (prev_hash.IsNull () || !m_tracker.chain .contains (prev_hash))
840+ return ;
841+
842+ const int32_t CL = static_cast <int32_t >(m_tracker.m_params ->chain_length );
843+ const int32_t parent_height = m_tracker.chain .get_height (prev_hash);
844+
845+ // Sample the SAME window the 60% switch gate reads (share_check.hpp:1610):
846+ // anchor = nth_parent(parent, CHAIN_LENGTH*9/10), size = CHAIN_LENGTH/10.
847+ const uint32_t window_start = (static_cast <uint32_t >(CL ) * 9 ) / 10 ;
848+ const uint32_t window_size = static_cast <uint32_t >(CL ) / 10 ;
849+ auto anchor = m_tracker.chain .get_nth_parent_key (prev_hash, window_start);
850+ auto weights = m_tracker.get_desired_version_weights (
851+ anchor, static_cast <int32_t >(window_size));
852+
853+ // apply_min_protocol_ratchet_decision applies the main.py:212 full-window
854+ // guard (parent_height >= CHAIN_LENGTH) the pure ratchet omits, then delegates.
855+ const uint32_t lifted = dgb::apply_min_protocol_ratchet_decision (
856+ parent_height, CL , weights, best_version, current, target);
857+ if (lifted != current) {
858+ m_runtime_min_protocol_version.store (lifted, std::memory_order_relaxed);
859+ LOG_INFO << " [min-proto-ratchet] MINIMUM_PROTOCOL_VERSION " << current
860+ << " -> " << lifted << " (>=95% window work desires v"
861+ << best_version << " , best="
862+ << m_best_share_hash.GetHex ().substr (0 , 16 ) << " )" ;
863+ }
864+ }
865+
809866uint256 NodeImpl::best_share_hash ()
810867{
811868 // p2pool's think() returns the best VERIFIED head — not the raw chain tip.
@@ -1465,6 +1522,7 @@ void NodeImpl::run_think()
14651522 if (!result.best .IsNull ()) {
14661523 best_changed = (m_best_share_hash != result.best );
14671524 m_best_share_hash = result.best ;
1525+ apply_min_protocol_ratchet (); // oracle main.py:216 (per best-share advance)
14681526 }
14691527
14701528 // Phase 1c: drive the whale-departure detector with the fresh best
@@ -1843,6 +1901,7 @@ void NodeImpl::clean_tracker()
18431901
18441902 if (!result.best .IsNull ()) {
18451903 m_best_share_hash = result.best ;
1904+ apply_min_protocol_ratchet (); // oracle main.py:216 (per best-share advance)
18461905 }
18471906
18481907 flush_verified_to_leveldb ();
@@ -2014,6 +2073,7 @@ void NodeImpl::clean_tracker()
20142073 if (!result.best .IsNull ()) {
20152074 clean_best_changed = (m_best_share_hash != result.best );
20162075 m_best_share_hash = result.best ;
2076+ apply_min_protocol_ratchet (); // oracle main.py:216 (per best-share advance)
20172077 }
20182078 publish_snapshot ();
20192079 flush_verified_to_leveldb ();
0 commit comments