Merge blockview-28.1+knots

This commit is contained in:
Luke Dashjr 2025-03-05 03:27:08 +00:00
commit d69357dcf5
19 changed files with 685 additions and 24 deletions

View File

@ -46,6 +46,7 @@ QT_MOC_CPP = \
qt/moc_bitcoinamountfield.cpp \
qt/moc_bitcoingui.cpp \
qt/moc_bitcoinunits.cpp \
qt/moc_blockview.cpp \
qt/moc_clientmodel.cpp \
qt/moc_coincontroldialog.cpp \
qt/moc_coincontroltreewidget.cpp \
@ -119,6 +120,7 @@ BITCOIN_QT_H = \
qt/bitcoinamountfield.h \
qt/bitcoingui.h \
qt/bitcoinunits.h \
qt/blockview.h \
qt/clientmodel.h \
qt/coincontroldialog.h \
qt/coincontroltreewidget.h \
@ -234,6 +236,7 @@ BITCOIN_QT_BASE_CPP = \
qt/bitcoinamountfield.cpp \
qt/bitcoingui.cpp \
qt/bitcoinunits.cpp \
qt/blockview.cpp \
qt/clientmodel.cpp \
qt/csvmodelwriter.cpp \
qt/guiutil.cpp \

View File

@ -46,8 +46,8 @@ public:
* @param[in] options options for creating the block
* @returns a block template
*/
virtual std::unique_ptr<node::CBlockTemplate> createNewBlock(const CScript& script_pub_key, const node::BlockCreateOptions& options={}) = 0;
virtual std::unique_ptr<node::CBlockTemplate> createNewBlock2(const CScript& script_pub_key, const node::BlockCreateOptions& assemble_options) = 0;
virtual std::shared_ptr<node::CBlockTemplate> createNewBlock(const CScript& script_pub_key, const node::BlockCreateOptions& options={}) = 0;
virtual std::shared_ptr<node::CBlockTemplate> createNewBlock2(const CScript& script_pub_key, const node::BlockCreateOptions& assemble_options) = 0;
/**
* Processes new block. A valid new block is automatically relayed to peers.

View File

@ -935,16 +935,16 @@ public:
return TestBlockValidity(state, chainman().GetParams(), chainman().ActiveChainstate(), block, tip, /*fCheckPOW=*/false, check_merkle_root);
}
std::unique_ptr<CBlockTemplate> createNewBlock(const CScript& script_pub_key, const BlockCreateOptions& options) override
std::shared_ptr<CBlockTemplate> createNewBlock(const CScript& script_pub_key, const BlockCreateOptions& options) override
{
BlockAssembler::Options assemble_options{options};
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
return createNewBlock2(script_pub_key, assemble_options);
}
std::unique_ptr<CBlockTemplate> createNewBlock2(const CScript& script_pub_key, const BlockCreateOptions& assemble_options) override
std::shared_ptr<CBlockTemplate> createNewBlock2(const CScript& script_pub_key, const BlockCreateOptions& assemble_options) override
{
return BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key);
return BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options, m_node}.CreateNewBlock(script_pub_key);
}
NodeContext* context() override { return &m_node; }

View File

@ -16,6 +16,7 @@
#include <consensus/validation.h>
#include <deploymentstatus.h>
#include <logging.h>
#include <node/context.h>
#include <policy/feerate.h>
#include <policy/policy.h>
#include <pow.h>
@ -23,6 +24,7 @@
#include <util/moneystr.h>
#include <util/time.h>
#include <validation.h>
#include <validationinterface.h>
#include <algorithm>
#include <utility>
@ -82,10 +84,11 @@ BlockCreateOptions BlockCreateOptions::Clamped() const
return options;
}
BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options)
BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options, const NodeContext& node)
: chainparams{chainstate.m_chainman.GetParams()},
m_mempool{options.use_mempool ? mempool : nullptr},
m_chainstate{chainstate},
m_node{node},
m_options{options.Clamped()}
{
// Whether we need to account for byte usage (in addition to weight usage)
@ -133,7 +136,7 @@ void BlockAssembler::resetBlock()
blockFinished = false;
}
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
std::shared_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
{
const auto time_start{SteadyClock::now()};
@ -221,6 +224,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
Ticks<MillisecondsDouble>(time_2 - time_1),
Ticks<MillisecondsDouble>(time_2 - time_start));
if (m_node.validation_signals) m_node.validation_signals->NewBlockTemplate(pblocktemplate);
return std::move(pblocktemplate);
}

View File

@ -29,6 +29,7 @@ class Chainstate;
class ChainstateManager;
namespace Consensus { struct Params; };
namespace node { struct NodeContext; };
namespace node {
@ -140,7 +141,7 @@ class BlockAssembler
{
private:
// The constructed block template
std::unique_ptr<CBlockTemplate> pblocktemplate;
std::shared_ptr<CBlockTemplate> pblocktemplate;
bool fNeedSizeAccounting;
@ -159,6 +160,7 @@ private:
const CChainParams& chainparams;
const CTxMemPool* const m_mempool;
Chainstate& m_chainstate;
const NodeContext& m_node;
// Variables used for addPriorityTxs
int lastFewTxs;
@ -167,10 +169,10 @@ private:
public:
using Options = BlockCreateOptions;
explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options);
explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options, const NodeContext& node);
/** Construct a new block template with coinbase to scriptPubKeyIn */
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn);
std::shared_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn);
inline static std::optional<int64_t> m_last_block_num_txs{};
inline static std::optional<int64_t> m_last_block_weight{};

View File

@ -7,6 +7,7 @@
#include <qt/bitcoingui.h>
#include <qt/bitcoinunits.h>
#include <qt/blockview.h>
#include <qt/clientmodel.h>
#include <qt/createwalletdialog.h>
#include <qt/guiconstants.h>
@ -603,6 +604,14 @@ void BitcoinGUI::createMenuBar()
window_menu->addAction(m_show_netwatch_action);
window_menu->addAction(showMempoolStatsAction);
auto show_blockview_action = new QAction(tr("Block &Visualizer"), this);
window_menu->addAction(show_blockview_action);
connect(show_blockview_action, &QAction::triggered, [this] {
auto blockview = new GuiBlockView(platformStyle, m_network_style);
blockview->setClientModel(clientModel);
GUIUtil::bringToFront(blockview);
});
window_menu->addSeparator();
for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) {
QAction* tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type));

513
src/qt/blockview.cpp Normal file
View File

@ -0,0 +1,513 @@
// Copyright (c) 2024 Luke Dashjr
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <qt/blockview.h>
#include <interfaces/node.h>
#include <logging.h>
#include <node/context.h>
#include <node/miner.h>
#include <primitives/block.h>
#include <util/strencodings.h>
#include <validation.h>
#include <validationinterface.h>
#include <qt/bitcoinunits.h>
#include <qt/clientmodel.h>
#include <qt/guiutil.h>
#include <qt/networkstyle.h>
#include <qt/optionsmodel.h>
#include <cmath>
#include <numbers>
#include <QComboBox>
#include <QLabel>
#include <QGraphicsEllipseItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QHBoxLayout>
#include <QVBoxLayout>
static constexpr qreal TX_PADDING_NEXT{4};
static constexpr qreal TX_PADDING_NEARBY{2};
static constexpr qreal EXPECTED_WHITESPACE_PERCENT{1.5};
static constexpr auto RADIAN_DIVISOR{8};
void ScalingGraphicsView::resizeEvent(QResizeEvent * const event)
{
fitInView(scene()->sceneRect(), Qt::KeepAspectRatio);
QGraphicsView::resizeEvent(event);
}
class BlockViewValidationInterface final : public CValidationInterface
{
private:
GuiBlockView& m_bv;
public:
explicit BlockViewValidationInterface(GuiBlockView& bv) : m_bv(bv) {}
void BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& block_cached, const CBlockIndex* pblockindex) override {
m_bv.updateBestBlock(pblockindex->nHeight);
if (!m_bv.m_follow_tip) return;
std::shared_ptr<const CBlock> block = block_cached;
auto chainman = m_bv.getChainstateManager();
Assert(chainman);
if (!block) {
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
if (!chainman->m_blockman.ReadBlockFromDisk(*pblock, *pblockindex)) {
// Indicate error somehow?
return;
}
block = pblock;
}
const auto block_subsidy = GetBlockSubsidy(pblockindex->nHeight, chainman->GetParams().GetConsensus());
m_bv.setBlock(block, block_subsidy);
}
void NewBlockTemplate(const std::shared_ptr<node::CBlockTemplate>& blocktemplate) override {
{
LOCK(m_bv.m_mutex);
if (m_bv.m_block) {
// Update cached template, but don't render it
m_bv.m_block_template = blocktemplate;
return;
}
}
m_bv.setBlock(blocktemplate);
}
};
void GuiBlockView::updateBestBlock(const int height)
{
m_block_chooser->setItemText(1, tr("Newest block (%1)").arg(height));
}
GuiBlockView::GuiBlockView(const PlatformStyle *platformStyle, const NetworkStyle *networkStyle, QWidget *parent) :
QDialog(parent, GUIUtil::dialog_flags | Qt::WindowMaximizeButtonHint)
{
setWindowTitle(tr(PACKAGE_NAME) + " - " + tr("Block View") + " " + networkStyle->getTitleAddText());
setWindowIcon(networkStyle->getTrayAndWindowIcon());
resize(640, 640);
QVBoxLayout * const layout = new QVBoxLayout(this);
setLayout(layout);
auto hlayout = new QHBoxLayout;
layout->addLayout(hlayout);
hlayout->addWidget(new QLabel(tr("Displayed block: ")));
m_block_chooser = new QComboBox(this);
hlayout->addWidget(m_block_chooser, 1);
connect(m_block_chooser, QOverload<int>::of(&QComboBox::currentIndexChanged), [=, this](const int index){
m_follow_tip = false;
auto ud = m_block_chooser->itemData(index).toInt();
if (ud == -3) {
m_block_chooser->setEditable(false);
auto block_template = WITH_LOCK(m_mutex, return m_block_template);
if (block_template) {
setBlock(block_template);
} else {
clear();
}
return;
}
auto chainman = getChainstateManager();
if (!chainman) {
clear();
return;
}
auto& blockman = chainman->m_blockman;
CBlockIndex *pblockindex;
if (ud == -2) {
m_follow_tip = true;
pblockindex = WITH_LOCK(::cs_main, return chainman->ActiveChain().Tip());
if (!pblockindex) {
clear();
return;
}
} else if (ud == -1) {
m_block_chooser->setEditable(true);
m_block_chooser->clearEditText();
return;
} else {
auto qtxt = m_block_chooser->itemText(index);
auto txt = qtxt.toStdString();
auto blockhash{uint256::FromHex(txt)};
if (blockhash) {
LOCK(cs_main);
pblockindex = blockman.LookupBlockIndex(*blockhash);
} else if (auto height = ToIntegral<int>(txt)) {
LOCK(cs_main);
pblockindex = chainman->ActiveChain()[*height];
} else {
pblockindex = nullptr;
}
if (!pblockindex) {
clear();
QMessageBox::critical(this, tr("Invalid block"), tr("\"%1\" is not a valid block height or hash!").arg(qtxt));
m_block_chooser->removeItem(index);
return;
}
}
std::shared_ptr<CBlock> block = std::make_shared<CBlock>();
if ((!blockman.ReadBlockFromDisk(*block, *pblockindex)) || block->vtx.empty()) {
clear();
const bool is_pruned = WITH_LOCK(::cs_main, return blockman.IsBlockPruned(*pblockindex));
if (is_pruned) {
QMessageBox::critical(this, tr("Pruned block"), tr("Block %1 (%2) is pruned.").arg(pblockindex->nHeight).arg(QString::fromStdString(pblockindex->GetBlockHash().ToString())));
} else {
QMessageBox::critical(this, tr("Error reading block"), tr("Block %1 (%2) could not be loaded.").arg(pblockindex->nHeight).arg(QString::fromStdString(pblockindex->GetBlockHash().ToString())));
}
m_block_chooser->removeItem(index);
return;
}
m_block_chooser->setEditable(false);
const auto block_subsidy = GetBlockSubsidy(pblockindex->nHeight, chainman->GetParams().GetConsensus());
setBlock(block, block_subsidy);
});
// Items initialized later, after ClientModel is available
m_scene = new QGraphicsScene(this);
m_scene->setSceneRect(0, 0, 1, 1);
auto view = new ScalingGraphicsView(m_scene, this);
layout->addWidget(view);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setStyleSheet("QGraphicsView { background: transparent; }");
view->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
connect(m_scene, &QGraphicsScene::sceneRectChanged, [view](const QRectF& rect){
view->fitInView(rect, Qt::KeepAspectRatio);
});
hlayout = new QHBoxLayout;
layout->addLayout(hlayout);
hlayout->addWidget(new QLabel(tr("Transactions"), this));
m_lbl_tx_count = new QLabel(this);
m_lbl_tx_count->setAlignment(Qt::AlignRight);
hlayout->addWidget(m_lbl_tx_count);
hlayout = new QHBoxLayout;
layout->addLayout(hlayout);
hlayout->addWidget(new QLabel(tr("Txn Fees"), this));
m_lbl_tx_fees = new QLabel(this);
m_lbl_tx_fees->setAlignment(Qt::AlignRight);
hlayout->addWidget(m_lbl_tx_fees);
connect(&m_timer, &QTimer::timeout, this, &GuiBlockView::updateScene);
m_validation_interface = new BlockViewValidationInterface(*this);
}
GuiBlockView::~GuiBlockView()
{
if (m_validation_interface) {
setClientModel(nullptr);
delete m_validation_interface;
m_validation_interface = nullptr;
}
}
void GuiBlockView::setClientModel(ClientModel *model)
{
if (m_client_model) {
auto& validation_signals = m_client_model->node().context()->validation_signals;
if (validation_signals) {
validation_signals->UnregisterValidationInterface(m_validation_interface);
}
disconnect(m_client_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &GuiBlockView::updateDisplayUnit);
}
m_client_model = model;
if (model) {
connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &GuiBlockView::updateDisplayUnit);
updateDisplayUnit();
if (m_block_chooser->count() == 0) {
m_block_chooser->addItem(tr("This node's preferred block template"), -3);
m_block_chooser->addItem("", -2);
m_block_chooser->addItem(tr("Specific block"), -1);
m_block_chooser->setCurrentIndex(1);
}
auto chainman = getChainstateManager();
if (chainman) {
const auto pblockindex = WITH_LOCK(::cs_main, return chainman->ActiveChain().Tip());
updateBestBlock(pblockindex->nHeight);
}
auto& validation_signals = model->node().context()->validation_signals;
if (validation_signals) {
validation_signals->RegisterValidationInterface(m_validation_interface);
}
}
}
ChainstateManager* GuiBlockView::getChainstateManager() const
{
if (!m_client_model) return nullptr;
auto node_ctx = m_client_model->node().context();
if (!node_ctx) return nullptr;
auto& chainman = node_ctx->chainman;
if (!chainman) return nullptr;
return &(*chainman);
}
void GuiBlockView::clear()
{
LOCK(m_mutex);
m_block_fees = -1;
m_lbl_tx_count->setText("");
m_block.reset();
m_block_template.reset();
for (auto& [wtxid, elem] : m_elements) {
const auto gi = elem.gi;
m_scene->removeItem(gi);
delete gi;
}
m_elements.clear();
}
bool GuiBlockView::any_overlap(const Bubble& proposed, const std::vector<Bubble>& others)
{
for (const auto& other : others) {
const auto x_dist = std::abs(other.pos.x() - proposed.pos.x());
const auto y_dist = std::abs(other.pos.y() - proposed.pos.y());
const auto dist = std::sqrt((x_dist * x_dist) + (y_dist * y_dist));
if (dist < proposed.radius + other.radius + TX_PADDING_NEARBY) {
return true;
}
}
return false;
}
void GuiBlockView::setBlock(std::shared_ptr<const CBlock> block, const CAmount block_subsidy)
{
LOCK(m_mutex);
m_block_fees = [&] {
CAmount total{0};
Assert(!block->vtx.empty());
for (const auto& outp : block->vtx[0]->vout) {
total += outp.nValue;
}
return total - block_subsidy;
}();
m_block = block;
m_block_template.reset();
m_block_changed = true;
updateElements(/*instant=*/ true);
}
void GuiBlockView::setBlock(std::shared_ptr<const node::CBlockTemplate> blocktemplate)
{
LOCK(m_mutex);
const bool instant = (bool)m_block; // force instant if changing from real block to template
m_block_fees = -blocktemplate->vTxFees.front();
m_block.reset();
m_block_template = blocktemplate;
m_block_changed = true;
updateElements(/*instant=*/ instant);
}
void GuiBlockView::updateBlockFees(CAmount block_fees)
{
if (block_fees < 0) {
m_lbl_tx_fees->setText("");
return;
}
const auto unit = m_client_model ? m_client_model->getOptionsModel()->getDisplayUnit() : BitcoinUnit::BTC;
m_lbl_tx_fees->setText(BitcoinUnits::formatWithUnit(unit, block_fees));
}
void GuiBlockView::updateDisplayUnit()
{
const auto block_fees = WITH_LOCK(m_mutex, return m_block_fees);
updateBlockFees(block_fees);
}
constexpr qreal offscreen{99};
void GuiBlockView::updateElements(bool instant)
{
if (!m_block_changed) return;
m_timer.stop();
m_block_changed = false;
auto pblocktemplate = m_block_template;
auto pblock = m_block;
auto& block = pblock ? *pblock : pblocktemplate->block;
instant |= m_elements.empty();
for (auto& el : m_elements) {
el.second.target_loc.setY(offscreen);
}
m_bubblegraph = std::make_unique<BubbleGraph>();
auto& bubbles = m_bubblegraph->bubbles;
size_t total_txs_size{0};
qreal limit_halfwidth{std::sqrt(::GetSerializeSize(TX_WITH_WITNESS(block))) * EXPECTED_WHITESPACE_PERCENT / 2};
for (size_t i = 1; i < block.vtx.size(); ++i) {
auto& tx = *block.vtx[i];
auto& el = m_elements[tx.GetWitnessHash()];
QPointF preferred_loc;
double diameter;
const auto tx_size = tx.GetTotalSize();
total_txs_size += tx_size;
const bool fresh_bubble = !el.gi;
if (fresh_bubble) {
diameter = 2 * std::sqrt(tx_size / std::numbers::pi);
} else {
// preferred_loc = el.gi->pos();
diameter = el.gi->boundingRect().height();
}
Bubble proposed{ .pos = {}, .radius = diameter / 2, .el = &el, };
qreal x_extremity{proposed.radius};
if (bubbles.empty()) {
proposed.pos.setY(-proposed.radius);
}
for (auto bubble_it = bubbles.rbegin(); bubble_it != bubbles.rend(); ++bubble_it) {
const auto& centre = bubble_it->pos;
QPointF preferred_loc_rel(preferred_loc.x() - centre.x(), preferred_loc.y() - centre.y());
double preferred_angle;
if (preferred_loc_rel.isNull()) {
preferred_angle = std::numbers::pi / 2;
} else {
preferred_angle = std::atan2(preferred_loc.y() - centre.y(), preferred_loc.x() - centre.x());
}
const auto distance = bubble_it->radius + proposed.radius + TX_PADDING_NEXT;
double angle = preferred_angle;
bool found{false};
while (true) {
proposed.pos = QPointF(centre.x() + (distance * std::cos(angle)), centre.y() + (distance * std::sin(angle)));
x_extremity = std::abs(proposed.pos.x()) + proposed.radius;
if (proposed.pos.y() < -proposed.radius && x_extremity <= limit_halfwidth && !any_overlap(proposed, bubbles)) {
found = true;
break;
}
if (angle < preferred_angle) {
angle = preferred_angle + (preferred_angle - angle);
} else {
angle = preferred_angle - (angle - preferred_angle) - (std::numbers::pi / RADIAN_DIVISOR);
}
if (angle > preferred_angle + std::numbers::pi) {
break;
}
}
if (found) break;
}
m_bubblegraph->min_x = std::min(m_bubblegraph->min_x, proposed.pos.x() - proposed.radius);
m_bubblegraph->max_x = std::max(m_bubblegraph->max_x, proposed.pos.x() + proposed.radius);
m_bubblegraph->min_y = std::min(m_bubblegraph->min_y, proposed.pos.y() - proposed.radius);
bubbles.push_back(proposed);
el.target_loc = proposed.pos;
}
m_lbl_tx_count->setText(tr("%1 (%2)").arg(block.vtx.size() - 1).arg(tr("%1 kB").arg(total_txs_size / 1000.0, 0, 'f', 1)));
updateBlockFees(m_block_fees);
m_bubblegraph->instant = instant;
QMetaObject::invokeMethod(this, "updateSceneInit", Qt::QueuedConnection);
}
void GuiBlockView::updateSceneInit()
{
LOCK(m_mutex);
if (!m_bubblegraph) return;
for (auto& bubble : m_bubblegraph->bubbles) {
auto& el = *bubble.el;
if (!el.gi) {
const auto diameter = bubble.radius * 2;
auto gi = m_scene->addEllipse(0, 0, diameter, diameter, QPen(palette().window(), TX_PADDING_NEARBY));
el.gi = gi;
gi->setBrush(QColor(Qt::blue));
gi->setPos(bubble.pos.x() - bubble.radius, m_bubblegraph->instant ? (bubble.pos.y() - bubble.radius) : offscreen);
}
}
for (auto it = m_elements.begin(); it != m_elements.end(); ) {
const auto& target_loc = it->second.target_loc;
const auto gi = it->second.gi;
bool delete_el{false};
if (target_loc.y() == offscreen || !gi /* never got a chance to exist */) {
delete_el = true;
// TODO: if confirmed, slide it off the bottom
// TODO: if conflicted, pop the bubble?
// TODO: if delayed, move off the top
} else {
if (gi->y() == offscreen) {
gi->setY(m_bubblegraph->min_y - gi->boundingRect().height());
}
}
if (delete_el) {
if (gi) {
m_scene->removeItem(gi);
delete gi;
}
it = m_elements.erase(it);
} else {
++it;
}
}
m_scene->setSceneRect(m_bubblegraph->min_x, m_bubblegraph->min_y, m_bubblegraph->max_x - m_bubblegraph->min_x, -m_bubblegraph->min_y);
if (!m_bubblegraph->instant) {
m_frame_div = 4;
updateScene();
m_timer.start(100);
}
m_bubblegraph.reset();
}
void GuiBlockView::updateScene()
{
LOCK(m_mutex);
bool all_completed{true};
for (auto it = m_elements.begin(); it != m_elements.end(); ) {
const auto& target_loc = it->second.target_loc;
QGraphicsItem* gi = it->second.gi;
const auto radius = gi->boundingRect().width() / 2;
const QPointF current_loc(gi->pos().x() + radius, gi->pos().y() + radius);
bool delete_el{false};
if (target_loc != current_loc) {
// Get 25% closer each tick
QPointF new_loc(current_loc.x() + ((target_loc.x() - current_loc.x()) / m_frame_div),
current_loc.y() + ((target_loc.y() - current_loc.y()) / m_frame_div));
if (std::abs(new_loc.x() - target_loc.x()) < TX_PADDING_NEXT) {
new_loc.setX(target_loc.x());
}
if (std::abs(new_loc.y() - target_loc.y()) < TX_PADDING_NEXT) {
new_loc.setY(target_loc.y());
}
gi->setPos(new_loc.x() - radius, new_loc.y() - radius);
if (new_loc == target_loc) {
if (target_loc.y() + radius < m_scene->sceneRect().y() || target_loc.y() - radius > 0) {
delete_el = true;
}
} else {
all_completed = false;
}
}
if (delete_el) {
m_scene->removeItem(gi);
delete gi;
it = m_elements.erase(it);
} else {
++it;
}
}
--m_frame_div;
if (all_completed) {
m_timer.stop();
}
}

120
src/qt/blockview.h Normal file
View File

@ -0,0 +1,120 @@
// Copyright (c) 2024 Luke Dashjr
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_QT_BLOCKVIEW_H
#define BITCOIN_QT_BLOCKVIEW_H
#include <consensus/amount.h>
#include <sync.h>
#include <threadsafety.h>
#include <util/transaction_identifier.h>
#include <atomic>
#include <map>
#include <memory>
#include <vector>
#include <QDialog>
#include <QGraphicsView>
#include <QPointF>
#include <QTimer>
QT_BEGIN_NAMESPACE
class QComboBox;
class QGraphicsItem;
class QGraphicsScene;
class QLabel;
class QWidget;
QT_END_NAMESPACE
class CBlock;
namespace node { struct CBlockTemplate; }
class ChainstateManager;
class ClientModel;
class CValidationInterface;
class NetworkStyle;
class PlatformStyle;
class ScalingGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
using QGraphicsView::QGraphicsView;
void resizeEvent(QResizeEvent *event) override;
};
class BlockViewValidationInterface;
class GuiBlockView : public QDialog
{
Q_OBJECT
public:
RecursiveMutex m_mutex;
private:
struct SceneElement {
QGraphicsItem* gi;
QPointF target_loc;
};
struct Bubble {
QPointF pos;
double radius;
SceneElement *el;
};
struct BubbleGraph {
std::vector<Bubble> bubbles;
qreal min_x{0};
qreal max_x{0};
qreal min_y{0};
bool instant;
};
std::map<Wtxid, SceneElement> m_elements GUARDED_BY(m_mutex);
std::unique_ptr<BubbleGraph> m_bubblegraph GUARDED_BY(m_mutex);
QGraphicsScene *m_scene;
QTimer m_timer;
unsigned int m_frame_div;
QComboBox *m_block_chooser;
QLabel *m_lbl_tx_count;
QLabel *m_lbl_tx_fees;
BlockViewValidationInterface *m_validation_interface;
static bool any_overlap(const Bubble& proposed, const std::vector<Bubble>& others);
protected:
void updateElements(bool instant) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
void updateBlockFees(CAmount block_fees);
protected Q_SLOTS:
void updateDisplayUnit();
void updateSceneInit();
void updateScene();
public:
ClientModel *m_client_model{nullptr};
bool m_block_changed GUARDED_BY(m_mutex);
CAmount m_block_fees GUARDED_BY(m_mutex) {-1};
std::shared_ptr<const node::CBlockTemplate> m_block_template GUARDED_BY(m_mutex);
std::shared_ptr<const CBlock> m_block GUARDED_BY(m_mutex);
std::atomic<bool> m_follow_tip;
GuiBlockView(const PlatformStyle *, const NetworkStyle *, QWidget * parent = nullptr);
~GuiBlockView();
void setClientModel(ClientModel *model);
ChainstateManager* getChainstateManager() const;
void updateBestBlock(int height);
void clear();
void setBlock(std::shared_ptr<const CBlock> block, CAmount block_subsidy);
void setBlock(std::shared_ptr<const node::CBlockTemplate> blocktemplate);
};
#endif // BITCOIN_QT_BLOCKVIEW_H

View File

@ -162,7 +162,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const
{
UniValue blockHashes(UniValue::VARR);
while (nGenerate > 0 && !chainman.m_interrupt) {
std::unique_ptr<CBlockTemplate> pblocktemplate(miner.createNewBlock(coinbase_script));
auto pblocktemplate = miner.createNewBlock(coinbase_script);
if (!pblocktemplate.get())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
@ -373,7 +373,7 @@ static RPCHelpMan generateblock()
{
LOCK(chainman.GetMutex());
{
std::unique_ptr<CBlockTemplate> blocktemplate{miner.createNewBlock(coinbase_script, {.use_mempool = false})};
auto blocktemplate = miner.createNewBlock(coinbase_script, {.use_mempool = false});
if (!blocktemplate) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
}
@ -865,7 +865,7 @@ static RPCHelpMan getblocktemplate()
// Update block
static CBlockIndex* pindexPrev;
static int64_t time_start;
static std::unique_ptr<CBlockTemplate> pblocktemplate;
static std::shared_ptr<CBlockTemplate> pblocktemplate;
if (!pindexPrev || pindexPrev->GetBlockHash() != tip ||
bypass_cache ||
(miner.getTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5))

View File

@ -68,7 +68,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev,
const CScript& scriptPubKey)
{
BlockAssembler::Options options;
std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock(scriptPubKey);
std::shared_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options, m_node}.CreateNewBlock(scriptPubKey);
CBlock& block = pblocktemplate->block;
block.hashPrevBlock = prev->GetBlockHash();
block.nTime = prev->nTime + 1;

View File

@ -176,7 +176,7 @@ FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
miner_options.nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;
miner_options.test_block_validity = false;
node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options};
node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options, g_setup->m_node};
node::MiniMiner mini_miner{pool, outpoints};
assert(mini_miner.IsReadyToCalculate());

View File

@ -97,7 +97,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Cha
BlockAssembler::Options options;
options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT);
options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
auto assembler = BlockAssembler{chainstate, &tx_pool, options};
auto assembler = BlockAssembler{chainstate, &tx_pool, options, g_setup->m_node};
auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE);
Assert(block_template->block.vtx.size() >= 1);
}

View File

@ -68,7 +68,7 @@ BlockAssembler MinerTestingSetup::AssemblerForTest(CTxMemPool& tx_mempool)
options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
options.nBlockMaxSize = MAX_BLOCK_SERIALIZED_SIZE;
options.blockMinFeeRate = blockMinFeeRate;
return BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options};
return BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options, m_node};
}
constexpr static struct {
@ -137,7 +137,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
Txid hashHighFeeTx = tx.GetHash();
tx_mempool.addUnchecked(entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U);
BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx);
BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashHighFeeTx);
@ -609,7 +609,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
{
// Note that by default, these tests run with size accounting enabled.
CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
std::unique_ptr<CBlockTemplate> pblocktemplate;
std::shared_ptr<CBlockTemplate> pblocktemplate;
CTxMemPool& tx_mempool{*m_node.mempool};
// Simple block creation, nothing special yet:

View File

@ -21,7 +21,7 @@ static void mineBlock(const node::NodeContext& node, std::chrono::seconds block_
auto curr_time = GetTime<std::chrono::seconds>();
SetMockTime(block_time); // update time so the block is created with it
node::BlockAssembler::Options options;
CBlock block = node::BlockAssembler{node.chainman->ActiveChainstate(), nullptr, options}.CreateNewBlock(CScript() << OP_TRUE)->block;
CBlock block = node::BlockAssembler{node.chainman->ActiveChainstate(), nullptr, options, node}.CreateNewBlock(CScript() << OP_TRUE)->block;
while (!CheckProofOfWork(block.GetHash(), block.nBits, node.chainman->GetConsensus())) ++block.nNonce;
block.fChecked = true; // little speedup
SetMockTime(curr_time); // process block at current time

View File

@ -112,7 +112,7 @@ std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coi
const BlockAssembler::Options& assembler_options)
{
auto block = std::make_shared<CBlock>(
BlockAssembler{Assert(node.chainman)->ActiveChainstate(), Assert(node.mempool.get()), assembler_options}
BlockAssembler{Assert(node.chainman)->ActiveChainstate(), Assert(node.mempool.get()), assembler_options, node}
.CreateNewBlock(coinbase_scriptPubKey)
->block);

View File

@ -390,7 +390,7 @@ CBlock TestChain100Setup::CreateBlock(
Chainstate& chainstate)
{
BlockAssembler::Options options;
CBlock block = BlockAssembler{chainstate, nullptr, options}.CreateNewBlock(scriptPubKey)->block;
CBlock block = BlockAssembler{chainstate, nullptr, options, m_node}.CreateNewBlock(scriptPubKey)->block;
Assert(block.vtx.size() == 1);
for (const CMutableTransaction& tx : txns) {

View File

@ -66,7 +66,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash)
static uint64_t time = Params().GenesisBlock().nTime;
BlockAssembler::Options options;
auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock(CScript{} << i++ << OP_TRUE);
auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options, m_node}.CreateNewBlock(CScript{} << i++ << OP_TRUE);
auto pblock = std::make_shared<CBlock>(ptemplate->block);
pblock->hashPrevBlock = prev_hash;
pblock->nTime = ++time;
@ -331,7 +331,7 @@ BOOST_AUTO_TEST_CASE(witness_commitment_index)
CScript pubKey;
pubKey << 1 << OP_TRUE;
BlockAssembler::Options options;
auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock(pubKey);
auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options, m_node}.CreateNewBlock(pubKey);
CBlock pblock = ptemplate->block;
CTxOut witness;

View File

@ -260,3 +260,8 @@ void ValidationSignals::NewPoWValidBlock(const CBlockIndex *pindex, const std::s
LOG_EVENT("%s: block hash=%s", __func__, block->GetHash().ToString());
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.NewPoWValidBlock(pindex, block); });
}
void ValidationSignals::NewBlockTemplate(const std::shared_ptr<node::CBlockTemplate>& blocktemplate) {
LOG_EVENT("%s", __func__);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.NewBlockTemplate(blocktemplate); });
}

View File

@ -25,6 +25,7 @@ class BlockValidationState;
class CBlock;
class CBlockIndex;
struct CBlockLocator;
namespace node { struct CBlockTemplate; }
enum class MemPoolRemovalReason;
struct RemovedMempoolTransactionInfo;
struct NewMempoolTransactionInfo;
@ -157,6 +158,8 @@ protected:
* has been received and connected to the headers tree, though not validated yet.
*/
virtual void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& block) {};
virtual void NewBlockTemplate(const std::shared_ptr<node::CBlockTemplate>& blocktemplate) {}
/**
* Notifies the validation interface that it is being unregistered
*/
@ -234,6 +237,7 @@ public:
void ChainStateFlushed(ChainstateRole, const CBlockLocator &);
void BlockChecked(const CBlock&, const BlockValidationState&);
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);
void NewBlockTemplate(const std::shared_ptr<node::CBlockTemplate>& blocktemplate);
};
#endif // BITCOIN_VALIDATIONINTERFACE_H