Skip to content

Commit 5ecb1ed

Browse files
committed
Update sig batch to eliminate write contention.
1 parent d6b7d63 commit 5ecb1ed

7 files changed

Lines changed: 103 additions & 127 deletions

File tree

include/bitcoin/node/chasers/chaser_validate.hpp

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class BCN_API chaser_validate
8787
virtual bool mark_invalids(header_links& prevalids,
8888
const header_links& invalids, bool startup) NOEXCEPT;
8989

90-
/// Turnstile (drain/capture exclusion without blocking writers).
90+
/// Turnstile (drain/commit exclusion without blocking writers).
9191
virtual bool enter_capture() NOEXCEPT;
9292
virtual void exit_capture() NOEXCEPT;
9393

@@ -97,7 +97,6 @@ class BCN_API chaser_validate
9797

9898
private:
9999
using atomic_counter = std::atomic<size_t>;
100-
using atomic_counter_ptr = std::shared_ptr<atomic_counter>;
101100
struct counters
102101
{
103102
atomic_counter ecdsa_{};
@@ -110,31 +109,17 @@ class BCN_API chaser_validate
110109
atomic_counter missed_threshold_{};
111110
};
112111

113-
using schnorr_link = database::schnorr_link;
114-
using schnorr_link_ptr = std::shared_ptr<schnorr_link>;
115-
using cursor = system::chain::threshold::cursor;
116112
using missed = signatures::miss;
117113

118114
// Capture handlers.
119115
void do_log(const system::chain::script& missed) NOEXCEPT;
120116
void do_fire(missed miss, size_t count) NOEXCEPT;
121-
bool do_ecdsa(const system::hash_digest& digest,
122-
const system::ec_compressed& point, const system::ec_signature& sign,
123-
const header_link& link, const atomic_counter_ptr& sequence) NOEXCEPT;
124-
bool do_schnorr(const system::hash_digest& digest,
125-
const system::ec_xonly& point, const system::ec_signature& sign,
126-
const header_link& link) NOEXCEPT;
127-
bool do_multisig(const system::hash_digest& digest,
128-
const std::span<const system::ec_compressed>& points,
129-
const std::span<const system::ec_signature>& signs,
130-
const header_link& link, const atomic_counter_ptr& sequence) NOEXCEPT;
131-
bool do_threshold(const system::hash_digest& digest,
132-
const system::ec_xonly& point, const system::ec_signature& sign,
133-
const schnorr_link_ptr& fk_ptr, const header_link& link) NOEXCEPT;
134-
cursor open_threshold(size_t rows, const header_link& link) NOEXCEPT;
135117

136118
// Capture helpers.
137119
signatures get_capture(const header_link& link) NOEXCEPT;
120+
code commit_capture(bool& batched, const header_link& link) NOEXCEPT;
121+
void clear_capture() NOEXCEPT;
122+
void do_purge_capture() NOEXCEPT;
138123
std::string log_ratio(const std::string& name, size_t numerator,
139124
size_t denominator) const NOEXCEPT;
140125
void log_captures() const NOEXCEPT;

include/bitcoin/node/error.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,7 @@ enum error_t : uint8_t
114114
batch2,
115115
batch3,
116116
batch4,
117-
batch5,
118-
batch6,
119-
batch7,
120-
batch8
117+
batch5
121118
};
122119

123120
// No current need for error_code equivalence mapping.

src/chasers/chaser_validate.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,12 @@ void chaser_validate::complete_block(const code& ec, const header_link& link,
305305
{
306306
process_batch(true);
307307
}
308+
309+
// Batching has subsided, release slab capacity (no-op once released).
310+
if (current)
311+
{
312+
POST(do_purge_capture);
313+
}
308314
}
309315

310316
void chaser_validate::notify_block(const code& ec, size_t height,

src/chasers/chaser_validate_batch.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT
5959
return;
6060

6161
// Claim the drain (at most one, losers rely on the winner).
62-
// Arrivals now pause at enter_capture (briefly, until verifying_).
6362
if (draining_.exchange(true))
6463
return;
6564

66-
// Wait for in-flight captures to complete or abandon on close.
67-
// Bounded: arrivals pause rather than enter, so writers_ only drains.
65+
// Wait for in-flight commits to complete or abandon on close.
66+
// Bounded: the writer epoch spans only the per-block slab commit, so
67+
// writers_ only drains (arriving commits divert to in-place verify).
6868
while (is_nonzero(writers_.load()))
6969
{
7070
if (closed())
@@ -76,22 +76,17 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT
7676
std::this_thread::yield();
7777
}
7878

79-
// Divert paused/new arrivals to inline for the verify duration.
80-
////verifying_.store(true);
81-
8279
// Batch tables are now quiescent (no writers admitted, none in flight).
8380
// ========================================================================
8481

8582
// Retest under the claim, another drain may have just emptied the tables.
8683
if (!is_mature(residual))
8784
{
88-
////verifying_.store(false);
8985
draining_.store(false);
9086
return;
9187
}
9288

9389
const auto ec = do_process_batch(false);
94-
////verifying_.store(false);
9590
draining_.store(false);
9691
if (ec == network::error::operation_canceled)
9792
return;

src/chasers/chaser_validate_capture.cpp

Lines changed: 75 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ using namespace system::chain;
3030
using namespace database;
3131
using namespace std::placeholders;
3232

33-
// Shared pointers required for lifetime in handler parameters.
34-
BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
35-
BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED)
3633
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
3734

3835
// Capture handlers.
@@ -63,68 +60,6 @@ void chaser_validate::do_fire(missed miss, size_t count) NOEXCEPT
6360
}
6461
}
6562

66-
bool chaser_validate::do_ecdsa(const hash_digest& digest,
67-
const ec_compressed& point, const ec_signature& sign,
68-
const header_link& link, const atomic_counter_ptr& sequence) NOEXCEPT
69-
{
70-
++counters_.ecdsa_;
71-
const auto id = (*sequence)++;
72-
if (is_limited<uint16_t>(id)) return false;
73-
const auto group = narrow_cast<uint16_t>(id);
74-
const auto set = archive().set_signature(digest, point, sign, group, link);
75-
if (!set) fault(error::batch5);
76-
return set;
77-
}
78-
79-
bool chaser_validate::do_schnorr(const hash_digest& digest,
80-
const ec_xonly& point, const ec_signature& sign,
81-
const header_link& link) NOEXCEPT
82-
{
83-
++counters_.schnorr_;
84-
const auto set = archive().set_signature(digest, point, sign, link);
85-
if (!set) fault(error::batch6);
86-
return set;
87-
}
88-
89-
bool chaser_validate::do_multisig(const hash_digest& digest,
90-
const std::span<const system::ec_compressed>& points,
91-
const std::span<const system::ec_signature>& signs,
92-
const header_link& link, const atomic_counter_ptr& sequence) NOEXCEPT
93-
{
94-
counters_.multisig_ += points.size();
95-
const auto id = (*sequence)++;
96-
if (is_limited<uint16_t>(id)) return false;
97-
const auto group = narrow_cast<uint16_t>(id);
98-
const auto set = archive().set_signatures(digest, points, signs, group, link);
99-
if (!set) fault(error::batch7);
100-
return set;
101-
}
102-
103-
bool chaser_validate::do_threshold(const hash_digest& digest,
104-
const ec_xonly& point, const ec_signature& sign,
105-
const schnorr_link_ptr& fk_ptr, const header_link& link) NOEXCEPT
106-
{
107-
auto set = archive().set_signature((*fk_ptr)++, digest, point, sign, link);
108-
if (!set) fault(error::batch8);
109-
return set;
110-
}
111-
112-
chaser_validate::cursor chaser_validate::open_threshold(size_t rows,
113-
const header_link& link) NOEXCEPT
114-
{
115-
auto first = archive().allocate_signatures(rows);
116-
const auto fk = emplace_shared<schnorr_link>(first);
117-
if (fk->is_terminal())
118-
return {};
119-
120-
counters_.threshold_ += rows;
121-
return
122-
{
123-
.put = BIND_THIS(do_threshold, _1, _2, _3, fk, link),
124-
.rows = rows
125-
};
126-
}
127-
12863
// Capture helpers.
12964
// ----------------------------------------------------------------------------
13065
// private
@@ -134,19 +69,87 @@ signatures chaser_validate::get_capture(const header_link& link) NOEXCEPT
13469
if (!batch_enabled_ || link.is_terminal() || is_current_header(link))
13570
return { false };
13671

137-
const auto sequence = to_shared<atomic_counter>();
72+
// The capture populates this thread's accumulators (commit_capture consumes).
13873
return signatures
13974
{
14075
.enabled = true,
14176
.log = BIND_THIS(do_log, _1),
142-
.fire = BIND_THIS(do_fire, _1, _2),
143-
.ecdsa = BIND_THIS(do_ecdsa, _1, _2, _3, link, sequence),
144-
.schnorr = BIND_THIS(do_schnorr, _1, _2, _3, link),
145-
.multisig = BIND_THIS(do_multisig, _1, _2, _3, link, sequence),
146-
.threshold = BIND_THIS(open_threshold, _1, link)
77+
.fire = BIND_THIS(do_fire, _1, _2)
14778
};
14879
}
14980

81+
// Commit this thread's captured signatures as the block's batch rows. All
82+
// batch table state is written inside the commit epoch (turnstile). When
83+
// diverted by a drain, or upon store decline, the rows are verified in
84+
// place, equivalent to the inline evaluation their capture fabricated
85+
// (batched is cleared so the block completes by the non-batched path).
86+
code chaser_validate::commit_capture(bool& batched,
87+
const header_link& link) NOEXCEPT
88+
{
89+
code ec{};
90+
auto committed = false;
91+
auto& ecdsa = signatures::ecdsa_rows();
92+
auto& schnorr = signatures::schnorr_rows();
93+
94+
if (enter_capture())
95+
{
96+
auto& query = archive();
97+
committed =
98+
query.set_signatures(ecdsa, link) &&
99+
query.set_signatures(schnorr, link) &&
100+
query.set_prevalid(link);
101+
exit_capture();
102+
103+
// Store decline (e.g. disk full), recoverable once faulted.
104+
if (!committed)
105+
fault(error::batch5);
106+
}
107+
108+
const auto thresholds = schnorr.thresholds();
109+
const auto singles = schnorr.rows().size() - thresholds;
110+
111+
if (committed)
112+
{
113+
counters_.ecdsa_ += ecdsa.singles();
114+
counters_.multisig_ += ecdsa.multisig_keys();
115+
counters_.schnorr_ += singles;
116+
counters_.threshold_ += thresholds;
117+
}
118+
else
119+
{
120+
counters_.missed_ecdsa_ += ecdsa.singles();
121+
counters_.missed_multisig_ += ecdsa.multisig_keys();
122+
counters_.missed_schnorr_ += singles;
123+
counters_.missed_threshold_ += thresholds;
124+
125+
// Block validity remains fully determined.
126+
batched = false;
127+
if (!ecdsa.verify() || !schnorr.verify())
128+
ec = system::error::invalid_signature;
129+
}
130+
131+
ecdsa.clear();
132+
schnorr.clear();
133+
return ec;
134+
}
135+
136+
// Discard this thread's captured signatures (script failure preempted commit).
137+
void chaser_validate::clear_capture() NOEXCEPT
138+
{
139+
signatures::ecdsa_rows().clear();
140+
signatures::schnorr_rows().clear();
141+
}
142+
143+
// Release all threads' accumulator capacity (batching subsided). Safe on the
144+
// strand with an empty backlog: accumulators are populated only within
145+
// validate_block, and the strand is the sole poster of validations.
146+
void chaser_validate::do_purge_capture() NOEXCEPT
147+
{
148+
BC_ASSERT(stranded());
149+
if (is_zero(validate_backlog_.load()))
150+
signatures::purge();
151+
}
152+
150153
std::string chaser_validate::log_ratio(const std::string& name,
151154
size_t numerator, size_t denominator) const NOEXCEPT
152155
{
@@ -167,11 +170,9 @@ void chaser_validate::log_captures() const NOEXCEPT
167170
LOGN(log_ratio("Capture schnorr..", counters_.schnorr_,
168171
counters_.schnorr_ + counters_.missed_schnorr_));
169172
LOGN(log_ratio("Capture threshold", counters_.threshold_,
170-
counters_.threshold_ + zero));
173+
counters_.threshold_ + counters_.missed_threshold_));
171174
}
172175

173-
BC_POP_WARNING()
174-
BC_POP_WARNING()
175176
BC_POP_WARNING()
176177

177178
} // namespace node

src/chasers/chaser_validate_parallel.cpp

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -114,30 +114,25 @@ code chaser_validate::validate(bool& batched, bool& capturing, bool bypass,
114114
if ((ec = block.accept(ctx, subsidy_interval_, initial_subsidy_)))
115115
return ec;
116116

117-
// This critical section is mutually-exclusive with batch verification
118-
// (turnstile). All batch table state written inside the capture epoch.
119-
// ====================================================================
120-
121-
// Enter the capture turnstile. If drain is in progress, full validate.
122-
const auto entered = enter_capture();
123-
124-
// Initialize signature capture.
125-
const auto capture = get_capture(entered ? link : header_link{});
126-
capturing = entered ? capture.enabled : true;
117+
// Initialize signature capture (appends to this thread's accumulators).
118+
const auto capture = get_capture(link);
119+
capturing = capture.enabled;
127120

128121
ec = block.connect(ctx, capture);
129122

130123
// At least one signature batch was attempted (batch completion).
131124
batched = capture.batched;
132125

133-
// Mark block as valid contingent on batch verification.
134-
if (!ec && batched && !query.set_prevalid(link))
135-
ec = error::validate6;
136-
137-
if (entered)
138-
exit_capture();
139-
140-
// ====================================================================
126+
// Commit (or discard) the captured signatures, marking the block prevalid
127+
// contingent on batch verification. The commit epoch is mutually
128+
// exclusive with batch verification (turnstile).
129+
if (capturing)
130+
{
131+
if (!ec && batched)
132+
ec = commit_capture(batched, link);
133+
else
134+
clear_capture();
135+
}
141136

142137
if (ec)
143138
return ec;

src/error.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
104104
{ batch2, "batch2" },
105105
{ batch3, "batch3" },
106106
{ batch4, "batch4" },
107-
{ batch5, "batch5" },
108-
{ batch6, "batch6" },
109-
{ batch7, "batch7" },
110-
{ batch8, "batch8" }
107+
{ batch5, "batch5" }
111108
};
112109

113110
DEFINE_ERROR_T_CATEGORY(error, "node", "node code")

0 commit comments

Comments
 (0)