View in context in the libbitcoin Slack #general mirror.
15:28:32 5,366,900 sigs 3s 1.79M sigs/s 15:30:45 1,071,533 sigs 0s ~1.07M sigs/s 15:30:55 2,315,127 sigs 0s ~2.32M sigs/s 15:31:06 4,558,730 sigs 1s 4.56M sigs/s 15:32:16 29,354,060 sigs 8s 3.67M sigs/s 15:33:31 36,497,618 sigs 10s 3.65M sigs/s 15:37:20 1,468,875 sigs 0s ~1.47M sigs/s 15:38:18 5,883,209 sigs 1s 5.88M sigs/s
[network] threads = 24 [node] threads = 24 maximum_concurrency = 300000 batch_signatures = 10000000 warn_dirty_background_ratio = 90 warn_dirty_ratio = 90
[outbound] connections = 1500 connect_batch_size = 1 host_pool_capacity = 200000 seeding_timeout_seconds = 420
node.delay_inbound = false native.bind = 127.0.0.1:8080 database.ecdsa_size = 10000000000 database.schnorr_size = 10000000000(edited)
[database] header_rate = 15 txs_rate = 15 tx_rate = 15 point_rate = 15 ins_rate = 15 outs_rate = 15 input_rate = 15 output_rate = 15 candidate_rate = 15 confirmed_rate = 15 duplicate_rate = 15 prevout_rate = 15 strong_tx_rate = 15 validated_bk_rate = 15 validated_tx_rate = 15 filter_tx_rate = 15 filter_bk_rate = 15 address_rate = 15 ecdsa_rate = 15 schnorr_rate = 15
vm.dirty_ratio = 95 vm.dirty_background_ratio = 95 vm.dirty_bytes = 0 vm.dirty_background_bytes = 0 vm.dirty_expire_centisecs = 12000 vm.dirty_writeback_centisecs = 12000 SwapTotal = 0 SwapFree = 0
[bitcoin] milestone = 0000000000000000000000000000000000000000000000000000000000000000:0 [database] address_rate = 15 candidate_rate = 15 confirmed_rate = 15 duplicate_rate = 15 ecdsa_rate = 15 ecdsa_size = 10000000000 filter_bk_rate = 15 filter_tx_rate = 15 header_rate = 15 input_rate = 15 ins_rate = 15 output_rate = 15 outs_rate = 15 path = /var/lib/libbitcoin-server/blockchain point_rate = 15 prevout_buckets = 1000000 prevout_rate = 15 prevout_size = 4000000000 schnorr_rate = 15 schnorr_size = 10000000000 silent_buckets = 1 silent_rate = 50 silent_size = 4000000000 silent_start_height = 709632 strong_tx_rate = 15 turbo = true tx_rate = 15 txs_rate = 15 validated_bk_rate = 15 validated_tx_rate = 15 [electrum] bind = 0.0.0.0:50001 maximum_history = 1000000 maximum_subscriptions = 1000000 self_bind = 136.243.9.246:50001 server_name = libbitcoin-gpu-test silent_payment_threads = 20 [inbound] bind = 0.0.0.0:8333 [log] path = /var/log/libbitcoin-server [native] bind = 136.243.9.246:8180 connections = 16 [network] threads = 20 [node] batch_signatures = 5000000 delay_inbound = false maximum_concurrency = 500000 threads = 14 warn_dirty_background_ratio = 90 warn_dirty_ratio = 90 [outbound] connect_batch_size = 5 connections = 100 host_pool_capacity = 10000 seeding_timeout_seconds = 420 [peer] enable_relay = false
because the GPU tables drained, but confirmation is still stuck at 2950001
despite us having 295002, the node kept downloading
batched_ . it seems that the chaser can acquire a lock after the rows are written but before the blocks push_batch() handler has ran. so it verifies and purges the rows but only links in batched_ are marked as validbatched_ is protected by the strand. Any pending update to batched is added after the critical section completes, because it is also stranded. // set_block_valid(batched_ excluding ecdsa/schnorr failures)
// ------------------------------------------------------------------------
if (!process_valids(residual))
{
fault(error::batch4);
return;
}for (const auto& link: batched_)
{
if (link == header_link::terminal)
continue;
size_t height{};
if (!query.get_height(height, link) ||
!query.set_block_valid(link))
return false;
notify_block(system::error::success, height, link, false);
}
batched_.clear();a has a bad sig. when validating `a` the bad signature row is written into the batch table under the batch mutex. But a’s block id is not added to batched_ at that moment, rather it is posted later to the strand via push_batch().
before push_batch() runs, block b finishes and triggers batch verification. verifier sees a’s bad signature row, correctly marks a unconfirmable, and purges the table. but because a’s id is not yet in batched_, the invalid link cleanup cannot mark a’s entry as terminal.
now a’s delayed push_batch() finally runs and appends a to batched_. On the next batch, process_valids() treats every non-terminal link in batched_ as valid, so a can be marked valid even though it was already marked unconfirmable for an invalid signature.
relevant code paths (not using line links because i also have some local changes):
// chaser_validate_parallel.cpp
{
std::shared_lock lock{ mutex_, std::defer_lock };
if (capturing) lock.lock();
if ((ec = block.connect(ctx, capture)))
return ec;
batched = capture.batched;
faulted = capture.faulted;
}// chaser_validate.cpp
if (batched)
{
++batch_backlog_;
POST(push_batch, link, height);
return;
}// chaser_validate_batch.cpp batched_.push_back(link); process_batch(is_maximum());
// chaser_validate_batch.cpp
for (const auto& link: invalids)
{
if (!query.get_height(height, link) ||
!query.set_block_unconfirmable(link))
return false;
notify_block(system::error::invalid_signature, height, link, false);
}
std::ranges::replace_if(batched_, [&](const auto& link) NOEXCEPT
{
return contains(invalids, link);
}, header_link::terminal);// chaser_validate_batch.cpp
for (const auto& link: batched_)
{
if (link == header_link::terminal)
continue;
if (!query.get_height(height, link) ||
!query.set_block_valid(link))
return false;
notify_block(system::error::success, height, link, false);
}BOOST_AUTO_TEST_CASE(chaser_validate__process_batch__late_invalid_batched_link__does_not_become_valid)
{
configuration config{ chain::selection::mainnet };
config.database.path = TEST_DIRECTORY;
config.node.batch_signatures = 1;
const auto events = [](auto, auto) NOEXCEPT {};
store store_{ config.database };
query query_{ store_ };
BOOST_REQUIRE(test::clear(TEST_DIRECTORY));
BOOST_REQUIRE_EQUAL(store_.create(events), database::error::success);
BOOST_REQUIRE(query_.initialize(config.bitcoin.genesis_block));
const chain::block invalid_block{ block1_data, true };
const chain::block trigger_block{ block2_data, true };
const chain::block valid_block{ block3_data, true };
BOOST_REQUIRE(query_.set(invalid_block, database::context{ 0, 1, 0 }, false, false));
BOOST_REQUIRE(query_.set(trigger_block, database::context{ 0, 2, 0 }, false, false));
BOOST_REQUIRE(query_.set(valid_block, database::context{ 0, 3, 0 }, false, false));
const auto invalid_link = query_.to_header(invalid_block.hash());
const auto trigger_link = query_.to_header(trigger_block.hash());
const auto valid_link = query_.to_header(valid_block.hash());
BOOST_REQUIRE(query_.push_candidate(invalid_link));
BOOST_REQUIRE(query_.push_candidate(trigger_link));
BOOST_REQUIRE(query_.push_candidate(valid_link));
// invalid_link has a bad sig row, trigger_link has a good sig row.
BOOST_REQUIRE(query_.set_signature(sighash_bad, ecdsa_compressed,
ecdsa_signature, 0, invalid_link));
BOOST_REQUIRE(query_.set_signature(ecdsa_sighash, ecdsa_compressed,
ecdsa_signature, 0, trigger_link));
BOOST_REQUIRE_EQUAL(query_.ecdsa_records(), 2u);
code invalid_state{};
code trigger_state{};
code valid_state{};
{
const network::logger log{};
full_node node{ query_, config, log };
batch_test_chaser chaser{ node };
// trigger_link push_batch() processes both table rows before
// invalid_link delayed push_batch() has appended invalid_link to batched_
chaser.complete_batched(trigger_link, 2u);
chaser.drain();
BOOST_REQUIRE_EQUAL(query_.ecdsa_records(), 0u);
BOOST_REQUIRE_EQUAL(query_.get_block_state(invalid_link),
database::error::block_unconfirmable);
BOOST_REQUIRE_EQUAL(query_.get_block_state(trigger_link),
database::error::block_valid);
// invalid_link arrives late into batched_ after its bad row was purged
chaser.complete_batched(invalid_link, 1u);
chaser.drain();
// later good batch causes process_valids() to iterate the stale
// invalid_link and call set_block_valid() on it.
BOOST_REQUIRE(query_.set_signature(ecdsa_sighash, ecdsa_compressed,
ecdsa_signature, 0, valid_link));
chaser.complete_batched(valid_link, 3u);
chaser.drain();
invalid_state = query_.get_block_state(invalid_link);
trigger_state = query_.get_block_state(trigger_link);
valid_state = query_.get_block_state(valid_link);
}
BOOST_REQUIRE(!store_.close(events));
// this fails on upstream: invalid_state becomes block_valid.
BOOST_CHECK_EQUAL(invalid_state, database::error::block_unconfirmable);
BOOST_CHECK_EQUAL(trigger_state, database::error::block_valid);
BOOST_CHECK_EQUAL(valid_state, database::error::block_valid);
} (edited)diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp
index a269929f..9b8cfda7 100644
--- a/src/chasers/chaser_validate_batch.cpp
+++ b/src/chasers/chaser_validate_batch.cpp
@@ -210,8 +210,13 @@ bool chaser_validate::process_valids(bool residual) NOEXCEPT
continue;
size_t height{};
- if (!query.get_height(height, link) ||
- !query.set_block_valid(link))
+ if (!query.get_height(height, link))
+ return false;
+
+ if (query.get_block_state(link) == database::error::block_unconfirmable)
+ continue;
+
+ if (!query.set_block_valid(link))
return false;
notify_block(system::error::success, height, link, false);i think i got a decent way to control batch size without locking everything up
//...We also don't want to read block state before // every write of valid state.
which obviously comes with some performance hits
but by controlling the batch size, i am consistently seeing 5M/s which is what i wanted
also i set batch_size, i want it to be exactly that haha
secp256k1.bool chaser_validate::process_invalids(const header_links& invalids) NOEXCEPT
{
BC_ASSERT(stranded());
auto& query = archive();
for (const auto& link: invalids)
{
size_t height{};
if (!query.get_height(height, link) ||
!query.set_block_unconfirmable(link))
return false;
invalids_.push_back(link);
notify_block(system::error::invalid_signature, height, link, false);
}
// Set all invalid links in batched_ to terminal (to be skipped).
if (!invalids_.empty())
{
std::ranges::replace_if(batched_, [&](const auto& link) NOEXCEPT
{
return contains(invalids_, link);
}, header_link::terminal);
}
return true;
} (edited)