Initial Vexta Core v0.1.0 release

This commit is contained in:
Capri 2026-07-15 14:07:28 +00:00
commit 25c454329d
2481 changed files with 689289 additions and 0 deletions

21
src/node/README.md Normal file
View file

@ -0,0 +1,21 @@
# src/node/
The [`src/node/`](./) directory contains code that needs to access node state
(state in `CChain`, `CBlockIndex`, `CCoinsView`, `CTxMemPool`, and similar
classes).
Code in [`src/node/`](./) is meant to be segregated from code in
[`src/wallet/`](../wallet/) and [`src/qt/`](../qt/), to ensure wallet and GUI
code changes don't interfere with node operation, to allow wallet and GUI code
to run in separate processes, and to perhaps eventually allow wallet and GUI
code to be maintained in separate source repositories.
As a rule of thumb, code in one of the [`src/node/`](./),
[`src/wallet/`](../wallet/), or [`src/qt/`](../qt/) directories should avoid
calling code in the other directories directly, and only invoke it indirectly
through the more limited [`src/interfaces/`](../interfaces/) classes.
This directory is at the moment
sparsely populated. Eventually more substantial files like
[`src/validation.cpp`](../validation.cpp) and
[`src/txmempool.cpp`](../txmempool.cpp) might be moved there.

560
src/node/blockstorage.cpp Normal file
View file

@ -0,0 +1,560 @@
// Copyright (c) 2011-2021 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/blockstorage.h>
#include <chain.h>
#include <chainparams.h>
#include <clientversion.h>
#include <consensus/validation.h>
#include <flatfile.h>
#include <fs.h>
#include <hash.h>
#include <pow.h>
#include <shutdown.h>
#include <signet.h>
#include <streams.h>
#include <undo.h>
#include <util/system.h>
#include <validation.h>
std::atomic_bool fImporting(false);
std::atomic_bool fReindex(false);
bool fHavePruned = false;
bool fPruneMode = false;
uint64_t nPruneTarget = 0;
// TODO make namespace {
RecursiveMutex cs_LastBlockFile;
std::vector<CBlockFileInfo> vinfoBlockFile;
int nLastBlockFile = 0;
/** Global flag to indicate we should check to see if there are
* block/undo files that should be deleted. Set on startup
* or if we allocate more file space when we're in prune mode
*/
bool fCheckForPruning = false;
/** Dirty block index entries. */
std::set<CBlockIndex*> setDirtyBlockIndex;
/** Dirty block file entries. */
std::set<int> setDirtyFileInfo;
// } // namespace
static FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false);
static FlatFileSeq BlockFileSeq();
static FlatFileSeq UndoFileSeq();
bool IsBlockPruned(const CBlockIndex* pblockindex)
{
return (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0);
}
// If we're using -prune with -reindex, then delete block files that will be ignored by the
// reindex. Since reindexing works by starting at block file 0 and looping until a blockfile
// is missing, do the same here to delete any later block files after a gap. Also delete all
// rev files since they'll be rewritten by the reindex anyway. This ensures that vinfoBlockFile
// is in sync with what's actually on disk by the time we start downloading, so that pruning
// works correctly.
void CleanupBlockRevFiles()
{
std::map<std::string, fs::path> mapBlockFiles;
// Glob all blk?????.dat and rev?????.dat files from the blocks directory.
// Remove the rev files immediately and insert the blk file paths into an
// ordered map keyed by block file index.
LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n");
fs::path blocksdir = gArgs.GetBlocksDirPath();
for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) {
if (fs::is_regular_file(*it) &&
it->path().filename().string().length() == 12 &&
it->path().filename().string().substr(8,4) == ".dat")
{
if (it->path().filename().string().substr(0, 3) == "blk") {
mapBlockFiles[it->path().filename().string().substr(3, 5)] = it->path();
} else if (it->path().filename().string().substr(0, 3) == "rev") {
remove(it->path());
}
}
}
// Remove all block files that aren't part of a contiguous set starting at
// zero by walking the ordered map (keys are block file indices) by
// keeping a separate counter. Once we hit a gap (or if 0 doesn't exist)
// start removing block files.
int nContigCounter = 0;
for (const std::pair<const std::string, fs::path>& item : mapBlockFiles) {
if (atoi(item.first) == nContigCounter) {
nContigCounter++;
continue;
}
remove(item.second);
}
}
std::string CBlockFileInfo::ToString() const
{
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast));
}
CBlockFileInfo* GetBlockFileInfo(size_t n)
{
LOCK(cs_LastBlockFile);
return &vinfoBlockFile.at(n);
}
static bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock, const CMessageHeader::MessageStartChars& messageStart)
{
// Open history file to append
CAutoFile fileout(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
if (fileout.IsNull()) {
return error("%s: OpenUndoFile failed", __func__);
}
// Write index header
unsigned int nSize = GetSerializeSize(blockundo, fileout.GetVersion());
fileout << messageStart << nSize;
// Write undo data
long fileOutPos = ftell(fileout.Get());
if (fileOutPos < 0) {
return error("%s: ftell failed", __func__);
}
pos.nPos = (unsigned int)fileOutPos;
fileout << blockundo;
// calculate & write checksum
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
hasher << hashBlock;
hasher << blockundo;
fileout << hasher.GetHash();
return true;
}
bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex)
{
FlatFilePos pos = pindex->GetUndoPos();
if (pos.IsNull()) {
return error("%s: no undo data available", __func__);
}
// Open history file to read
CAutoFile filein(OpenUndoFile(pos, true), SER_DISK, CLIENT_VERSION);
if (filein.IsNull()) {
return error("%s: OpenUndoFile failed", __func__);
}
// Read block
uint256 hashChecksum;
CHashVerifier<CAutoFile> verifier(&filein); // We need a CHashVerifier as reserializing may lose data
try {
verifier << pindex->pprev->GetBlockHash();
verifier >> blockundo;
filein >> hashChecksum;
} catch (const std::exception& e) {
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
}
// Verify checksum
if (hashChecksum != verifier.GetHash()) {
return error("%s: Checksum mismatch", __func__);
}
return true;
}
static void FlushUndoFile(int block_file, bool finalize = false)
{
FlatFilePos undo_pos_old(block_file, vinfoBlockFile[block_file].nUndoSize);
if (!UndoFileSeq().Flush(undo_pos_old, finalize)) {
AbortNode("Flushing undo file to disk failed. This is likely the result of an I/O error.");
}
}
void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false)
{
LOCK(cs_LastBlockFile);
FlatFilePos block_pos_old(nLastBlockFile, vinfoBlockFile[nLastBlockFile].nSize);
if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
AbortNode("Flushing block file to disk failed. This is likely the result of an I/O error.");
}
// we do not always flush the undo file, as the chain tip may be lagging behind the incoming blocks,
// e.g. during IBD or a sync after a node going offline
if (!fFinalize || finalize_undo) FlushUndoFile(nLastBlockFile, finalize_undo);
}
uint64_t CalculateCurrentUsage()
{
LOCK(cs_LastBlockFile);
uint64_t retval = 0;
for (const CBlockFileInfo& file : vinfoBlockFile) {
retval += file.nSize + file.nUndoSize;
}
return retval;
}
void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune)
{
for (std::set<int>::iterator it = setFilesToPrune.begin(); it != setFilesToPrune.end(); ++it) {
FlatFilePos pos(*it, 0);
fs::remove(BlockFileSeq().FileName(pos));
fs::remove(UndoFileSeq().FileName(pos));
LogPrintf("Prune: %s deleted blk/rev (%05u)\n", __func__, *it);
}
}
static FlatFileSeq BlockFileSeq()
{
return FlatFileSeq(gArgs.GetBlocksDirPath(), "blk", gArgs.GetBoolArg("-fastprune", false) ? 0x4000 /* 16kb */ : BLOCKFILE_CHUNK_SIZE);
}
static FlatFileSeq UndoFileSeq()
{
return FlatFileSeq(gArgs.GetBlocksDirPath(), "rev", UNDOFILE_CHUNK_SIZE);
}
FILE* OpenBlockFile(const FlatFilePos& pos, bool fReadOnly)
{
return BlockFileSeq().Open(pos, fReadOnly);
}
/** Open an undo file (rev?????.dat) */
static FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly)
{
return UndoFileSeq().Open(pos, fReadOnly);
}
fs::path GetBlockPosFilename(const FlatFilePos& pos)
{
return BlockFileSeq().FileName(pos);
}
bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown = false)
{
LOCK(cs_LastBlockFile);
unsigned int nFile = fKnown ? pos.nFile : nLastBlockFile;
if (vinfoBlockFile.size() <= nFile) {
vinfoBlockFile.resize(nFile + 1);
}
bool finalize_undo = false;
if (!fKnown) {
while (vinfoBlockFile[nFile].nSize + nAddSize >= (gArgs.GetBoolArg("-fastprune", false) ? 0x10000 /* 64kb */ : MAX_BLOCKFILE_SIZE)) {
// when the undo file is keeping up with the block file, we want to flush it explicitly
// when it is lagging behind (more blocks arrive than are being connected), we let the
// undo block write case handle it
finalize_undo = (vinfoBlockFile[nFile].nHeightLast == (unsigned int)active_chain.Tip()->nHeight);
nFile++;
if (vinfoBlockFile.size() <= nFile) {
vinfoBlockFile.resize(nFile + 1);
}
}
pos.nFile = nFile;
pos.nPos = vinfoBlockFile[nFile].nSize;
}
if ((int)nFile != nLastBlockFile) {
if (!fKnown) {
LogPrint(BCLog::VALIDATION, "Leaving block file %i: %s\n", nLastBlockFile, vinfoBlockFile[nLastBlockFile].ToString());
}
FlushBlockFile(!fKnown, finalize_undo);
nLastBlockFile = nFile;
}
vinfoBlockFile[nFile].AddBlock(nHeight, nTime);
if (fKnown) {
vinfoBlockFile[nFile].nSize = std::max(pos.nPos + nAddSize, vinfoBlockFile[nFile].nSize);
} else {
vinfoBlockFile[nFile].nSize += nAddSize;
}
if (!fKnown) {
bool out_of_space;
size_t bytes_allocated = BlockFileSeq().Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
return AbortNode("Disk space is too low!", _("Disk space is too low!"));
}
if (bytes_allocated != 0 && fPruneMode) {
fCheckForPruning = true;
}
}
setDirtyFileInfo.insert(nFile);
return true;
}
static bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize)
{
pos.nFile = nFile;
LOCK(cs_LastBlockFile);
pos.nPos = vinfoBlockFile[nFile].nUndoSize;
vinfoBlockFile[nFile].nUndoSize += nAddSize;
setDirtyFileInfo.insert(nFile);
bool out_of_space;
size_t bytes_allocated = UndoFileSeq().Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
return AbortNode(state, "Disk space is too low!", _("Disk space is too low!"));
}
if (bytes_allocated != 0 && fPruneMode) {
fCheckForPruning = true;
}
return true;
}
static bool WriteBlockToDisk(const CBlock& block, FlatFilePos& pos, const CMessageHeader::MessageStartChars& messageStart)
{
// Open history file to append
CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION);
if (fileout.IsNull()) {
return error("WriteBlockToDisk: OpenBlockFile failed");
}
// Write index header
unsigned int nSize = GetSerializeSize(block, fileout.GetVersion());
fileout << messageStart << nSize;
// Write block
long fileOutPos = ftell(fileout.Get());
if (fileOutPos < 0) {
return error("WriteBlockToDisk: ftell failed");
}
pos.nPos = (unsigned int)fileOutPos;
fileout << block;
return true;
}
bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams)
{
// Write undo information to disk
if (pindex->GetUndoPos().IsNull()) {
FlatFilePos _pos;
if (!FindUndoPos(state, pindex->nFile, _pos, ::GetSerializeSize(blockundo, CLIENT_VERSION) + 40)) {
return error("ConnectBlock(): FindUndoPos failed");
}
if (!UndoWriteToDisk(blockundo, _pos, pindex->pprev->GetBlockHash(), chainparams.MessageStart())) {
return AbortNode(state, "Failed to write undo data");
}
// rev files are written in block height order, whereas blk files are written as blocks come in (often out of order)
// we want to flush the rev (undo) file once we've written the last block, which is indicated by the last height
// in the block file info as below; note that this does not catch the case where the undo writes are keeping up
// with the block writes (usually when a synced up node is getting newly mined blocks) -- this case is caught in
// the FindBlockPos function
if (_pos.nFile < nLastBlockFile && static_cast<uint32_t>(pindex->nHeight) == vinfoBlockFile[_pos.nFile].nHeightLast) {
FlushUndoFile(_pos.nFile, true);
}
// update nUndoPos in block index
pindex->nUndoPos = _pos.nPos;
pindex->nStatus |= BLOCK_HAVE_UNDO;
setDirtyBlockIndex.insert(pindex);
}
return true;
}
bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams)
{
block.SetNull();
// Open history file to read
CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION);
if (filein.IsNull()) {
return error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString());
}
// Read block
try {
filein >> block;
} catch (const std::exception& e) {
return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString());
}
// Check the header
if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) {
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
}
// Signet only: check block solution
if (consensusParams.signet_blocks && !CheckSignetBlockSolution(block, consensusParams)) {
return error("ReadBlockFromDisk: Errors in block solution at %s", pos.ToString());
}
return true;
}
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
{
const FlatFilePos block_pos{WITH_LOCK(cs_main, return pindex->GetBlockPos())};
if (!ReadBlockFromDisk(block, block_pos, consensusParams)) {
return false;
}
if (block.GetHash() != pindex->GetBlockHash()) {
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
pindex->ToString(), block_pos.ToString());
}
return true;
}
bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start)
{
FlatFilePos hpos = pos;
hpos.nPos -= 8; // Seek back 8 bytes for meta header
CAutoFile filein(OpenBlockFile(hpos, true), SER_DISK, CLIENT_VERSION);
if (filein.IsNull()) {
return error("%s: OpenBlockFile failed for %s", __func__, pos.ToString());
}
try {
CMessageHeader::MessageStartChars blk_start;
unsigned int blk_size;
filein >> blk_start >> blk_size;
if (memcmp(blk_start, message_start, CMessageHeader::MESSAGE_START_SIZE)) {
return error("%s: Block magic mismatch for %s: %s versus expected %s", __func__, pos.ToString(),
HexStr(blk_start),
HexStr(message_start));
}
if (blk_size > MAX_SIZE) {
return error("%s: Block data is larger than maximum deserialization size for %s: %s versus %s", __func__, pos.ToString(),
blk_size, MAX_SIZE);
}
block.resize(blk_size); // Zeroing of memory is intentional here
filein.read((char*)block.data(), blk_size);
} catch (const std::exception& e) {
return error("%s: Read from block file failed: %s for %s", __func__, e.what(), pos.ToString());
}
return true;
}
bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const CBlockIndex* pindex, const CMessageHeader::MessageStartChars& message_start)
{
FlatFilePos block_pos;
{
LOCK(cs_main);
block_pos = pindex->GetBlockPos();
}
return ReadRawBlockFromDisk(block, block_pos, message_start);
}
/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp)
{
unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION);
FlatFilePos blockPos;
if (dbp != nullptr) {
blockPos = *dbp;
}
if (!FindBlockPos(blockPos, nBlockSize + 8, nHeight, active_chain, block.GetBlockTime(), dbp != nullptr)) {
error("%s: FindBlockPos failed", __func__);
return FlatFilePos();
}
if (dbp == nullptr) {
if (!WriteBlockToDisk(block, blockPos, chainparams.MessageStart())) {
AbortNode("Failed to write block");
return FlatFilePos();
}
}
return blockPos;
}
struct CImportingNow {
CImportingNow()
{
assert(fImporting == false);
fImporting = true;
}
~CImportingNow()
{
assert(fImporting == true);
fImporting = false;
}
};
void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args)
{
ScheduleBatchPriority();
{
CImportingNow imp;
// -reindex
if (fReindex) {
int nFile = 0;
while (true) {
FlatFilePos pos(nFile, 0);
if (!fs::exists(GetBlockPosFilename(pos))) {
break; // No block files left to reindex
}
FILE* file = OpenBlockFile(pos, true);
if (!file) {
break; // This error is logged in OpenBlockFile
}
LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
return;
}
nFile++;
}
pblocktree->WriteReindexing(false);
fReindex = false;
LogPrintf("Reindexing finished\n");
// To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked):
chainman.ActiveChainstate().LoadGenesisBlock();
}
// -loadblock=
for (const fs::path& path : vImportFiles) {
FILE* file = fsbridge::fopen(path, "rb");
if (file) {
LogPrintf("Importing blocks file %s...\n", path.string());
chainman.ActiveChainstate().LoadExternalBlockFile(file);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
return;
}
} else {
LogPrintf("Warning: Could not open blocks file %s\n", path.string());
}
}
// scan for better chains in the block chain database, that are not yet connected in the active best chain
// We can't hold cs_main during ActivateBestChain even though we're accessing
// the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
// the relevant pointers before the ABC call.
for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
StartShutdown();
return;
}
}
if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
StartShutdown();
return;
}
} // End scope of CImportingNow
chainman.ActiveChainstate().LoadMempool(args);
}

82
src/node/blockstorage.h Normal file
View file

@ -0,0 +1,82 @@
// Copyright (c) 2011-2021 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_BLOCKSTORAGE_H
#define DIGIBYTE_NODE_BLOCKSTORAGE_H
#include <fs.h>
#include <protocol.h> // For CMessageHeader::MessageStartChars
#include <atomic>
#include <cstdint>
#include <vector>
class ArgsManager;
class BlockValidationState;
class CBlock;
class CBlockFileInfo;
class CBlockIndex;
class CBlockUndo;
class CChain;
class CChainParams;
class ChainstateManager;
struct FlatFilePos;
namespace Consensus {
struct Params;
}
static constexpr bool DEFAULT_STOPAFTERBLOCKIMPORT{false};
/** The pre-allocation chunk size for blk?????.dat files (since 0.8) */
static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB
/** The pre-allocation chunk size for rev?????.dat files (since 0.8) */
static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
/** The maximum size of a blk?????.dat file (since 0.8) */
static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
extern std::atomic_bool fImporting;
extern std::atomic_bool fReindex;
/** Pruning-related variables and constants */
/** True if any block files have ever been pruned. */
extern bool fHavePruned;
/** True if we're running in -prune mode. */
extern bool fPruneMode;
/** Number of MiB of block files that we're trying to stay below. */
extern uint64_t nPruneTarget;
//! Check whether the block associated with this index entry is pruned or not.
bool IsBlockPruned(const CBlockIndex* pblockindex);
void CleanupBlockRevFiles();
/** Open a block file (blk?????.dat) */
FILE* OpenBlockFile(const FlatFilePos& pos, bool fReadOnly = false);
/** Translation to a filesystem path */
fs::path GetBlockPosFilename(const FlatFilePos& pos);
/** Get block file info entry for one block file */
CBlockFileInfo* GetBlockFileInfo(size_t n);
/** Calculate the amount of disk space the block & undo files currently use */
uint64_t CalculateCurrentUsage();
/**
* Actually unlink the specified files
*/
void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune);
/** Functions for disk access for blocks */
bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams);
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams);
bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start);
bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const CBlockIndex* pindex, const CMessageHeader::MessageStartChars& message_start);
bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex);
bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams);
FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp);
void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args);
#endif // DIGIBYTE_NODE_BLOCKSTORAGE_H

24
src/node/coin.cpp Normal file
View file

@ -0,0 +1,24 @@
// Copyright (c) 2019 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/coin.h>
#include <node/context.h>
#include <txmempool.h>
#include <validation.h>
void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins)
{
assert(node.mempool);
assert(node.chainman);
LOCK2(cs_main, node.mempool->cs);
CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip();
CCoinsViewMemPool mempool_view(&chain_view, *node.mempool);
for (auto& coin : coins) {
if (!mempool_view.GetCoin(coin.first, coin.second)) {
// Either the coin is not in the CCoinsViewCache or is spent. Clear it.
coin.second.Clear();
}
}
}

24
src/node/coin.h Normal file
View file

@ -0,0 +1,24 @@
// Copyright (c) 2019 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_COIN_H
#define DIGIBYTE_NODE_COIN_H
#include <map>
class COutPoint;
class Coin;
struct NodeContext;
/**
* Look up unspent output information. Returns coins in the mempool and in the
* current chain UTXO set. Iterates through all the keys in the map and
* populates the values.
*
* @param[in] node The node context to use for lookup
* @param[in,out] coins map to fill
*/
void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins);
#endif // DIGIBYTE_NODE_COIN_H

182
src/node/coinstats.cpp Normal file
View file

@ -0,0 +1,182 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/coinstats.h>
#include <coins.h>
#include <crypto/muhash.h>
#include <hash.h>
#include <index/coinstatsindex.h>
#include <serialize.h>
#include <uint256.h>
#include <util/system.h>
#include <validation.h>
#include <map>
// Database-independent metric indicating the UTXO set size
uint64_t GetBogoSize(const CScript& script_pub_key)
{
return 32 /* txid */ +
4 /* vout index */ +
4 /* height + coinbase */ +
8 /* amount */ +
2 /* scriptPubKey len */ +
script_pub_key.size() /* scriptPubKey */;
}
CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) {
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
ss << outpoint;
ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
ss << coin.out;
return ss;
}
//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
//! validation commitments are reliant on the hash constructed by this
//! function.
//!
//! If the construction of this hash is changed, it will invalidate
//! existing UTXO snapshots. This will not result in any kind of consensus
//! failure, but it will force clients that were expecting to make use of
//! assumeutxo to do traditional IBD instead.
//!
//! It is also possible, though very unlikely, that a change in this
//! construction could cause a previously invalid (and potentially malicious)
//! UTXO snapshot to be considered valid.
static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
{
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
if (it == outputs.begin()) {
ss << hash;
ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
}
ss << VARINT(it->first + 1);
ss << it->second.out.scriptPubKey;
ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
if (it == std::prev(outputs.end())) {
ss << VARINT(0u);
}
}
}
static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {}
static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
{
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
COutPoint outpoint = COutPoint(hash, it->first);
Coin coin = it->second;
muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
}
}
static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
{
assert(!outputs.empty());
stats.nTransactions++;
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
stats.nTransactionOutputs++;
stats.nTotalAmount += it->second.out.nValue;
stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
}
}
//! Calculate statistics about the unspent transaction output set
template <typename T>
static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
{
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
assert(pcursor);
if (!pindex) {
{
LOCK(cs_main);
pindex = blockman.LookupBlockIndex(view->GetBestBlock());
}
}
stats.nHeight = Assert(pindex)->nHeight;
stats.hashBlock = pindex->GetBlockHash();
// Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index && stats.index_requested) {
stats.index_used = true;
return g_coin_stats_index->LookUpStats(pindex, stats);
}
PrepareHash(hash_obj, stats);
uint256 prevkey;
std::map<uint32_t, Coin> outputs;
while (pcursor->Valid()) {
interruption_point();
COutPoint key;
Coin coin;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
if (!outputs.empty() && key.hash != prevkey) {
ApplyStats(stats, prevkey, outputs);
ApplyHash(hash_obj, prevkey, outputs);
outputs.clear();
}
prevkey = key.hash;
outputs[key.n] = std::move(coin);
stats.coins_count++;
} else {
return error("%s: unable to read value", __func__);
}
pcursor->Next();
}
if (!outputs.empty()) {
ApplyStats(stats, prevkey, outputs);
ApplyHash(hash_obj, prevkey, outputs);
}
FinalizeHash(hash_obj, stats);
stats.nDiskSize = view->EstimateSize();
return true;
}
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
{
switch (stats.m_hash_type) {
case(CoinStatsHashType::HASH_SERIALIZED): {
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex);
}
case(CoinStatsHashType::MUHASH): {
MuHash3072 muhash;
return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex);
}
case(CoinStatsHashType::NONE): {
return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex);
}
} // no default case, so the compiler can warn about missing cases
assert(false);
}
// The legacy hash serializes the hashBlock
static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats)
{
ss << stats.hashBlock;
}
// MuHash does not need the prepare step
static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {}
static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
{
stats.hashSerialized = ss.GetHash();
}
static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
{
uint256 out;
muhash.Finalize(out);
stats.hashSerialized = out;
}
static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}

68
src/node/coinstats.h Normal file
View file

@ -0,0 +1,68 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_COINSTATS_H
#define DIGIBYTE_NODE_COINSTATS_H
#include <amount.h>
#include <chain.h>
#include <coins.h>
#include <streams.h>
#include <uint256.h>
#include <cstdint>
#include <functional>
class BlockManager;
class CCoinsView;
enum class CoinStatsHashType {
HASH_SERIALIZED,
MUHASH,
NONE,
};
struct CCoinsStats
{
CoinStatsHashType m_hash_type;
int nHeight{0};
uint256 hashBlock{};
uint64_t nTransactions{0};
uint64_t nTransactionOutputs{0};
uint64_t nBogoSize{0};
uint256 hashSerialized{};
uint64_t nDiskSize{0};
CAmount nTotalAmount{0};
//! The number of coins contained.
uint64_t coins_count{0};
//! Signals if the coinstatsindex should be used (when available).
bool index_requested{true};
//! Signals if the coinstatsindex was used to retrieve the statistics.
bool index_used{false};
// Following values are only available from coinstats index
CAmount total_subsidy{0};
CAmount block_unspendable_amount{0};
CAmount block_prevout_spent_amount{0};
CAmount block_new_outputs_ex_coinbase_amount{0};
CAmount block_coinbase_amount{0};
CAmount unspendables_genesis_block{0};
CAmount unspendables_bip30{0};
CAmount unspendables_scripts{0};
CAmount unspendables_unclaimed_rewards{0};
CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
};
//! Calculate statistics about the unspent transaction output set
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {}, const CBlockIndex* pindex = nullptr);
uint64_t GetBogoSize(const CScript& script_pub_key);
CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin);
#endif // DIGIBYTE_NODE_COINSTATS_H

18
src/node/context.cpp Normal file
View file

@ -0,0 +1,18 @@
// Copyright (c) 2019-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/context.h>
#include <addrman.h>
#include <banman.h>
#include <interfaces/chain.h>
#include <net.h>
#include <net_processing.h>
#include <policy/fees.h>
#include <scheduler.h>
#include <txmempool.h>
#include <validation.h>
NodeContext::NodeContext() {}
NodeContext::~NodeContext() {}

67
src/node/context.h Normal file
View file

@ -0,0 +1,67 @@
// Copyright (c) 2019-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_CONTEXT_H
#define DIGIBYTE_NODE_CONTEXT_H
#include <cassert>
#include <functional>
#include <memory>
#include <vector>
class ArgsManager;
class BanMan;
class CAddrMan;
class CBlockPolicyEstimator;
class CConnman;
class CScheduler;
class CTxMemPool;
class ChainstateManager;
class PeerManager;
namespace interfaces {
class Chain;
class ChainClient;
class Init;
class WalletClient;
} // namespace interfaces
//! NodeContext struct containing references to chain state and connection
//! state.
//!
//! This is used by init, rpc, and test code to pass object references around
//! without needing to declare the same variables and parameters repeatedly, or
//! to use globals. More variables could be added to this struct (particularly
//! references to validation objects) to eliminate use of globals
//! and make code more modular and testable. The struct isn't intended to have
//! any member functions. It should just be a collection of references that can
//! be used without pulling in unwanted dependencies or functionality.
struct NodeContext {
//! Init interface for initializing current process and connecting to other processes.
interfaces::Init* init{nullptr};
std::unique_ptr<CAddrMan> addrman;
std::unique_ptr<CConnman> connman;
std::unique_ptr<CTxMemPool> mempool;
std::unique_ptr<CTxMemPool> stempool;
std::unique_ptr<CBlockPolicyEstimator> fee_estimator;
std::unique_ptr<PeerManager> peerman;
std::unique_ptr<ChainstateManager> chainman;
std::unique_ptr<BanMan> banman;
ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
std::unique_ptr<interfaces::Chain> chain;
//! List of all chain clients (wallet processes or other client) connected to node.
std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients;
//! Reference to chain client that should used to load or create wallets
//! opened by the gui.
interfaces::WalletClient* wallet_client{nullptr};
std::unique_ptr<CScheduler> scheduler;
std::function<void()> rpc_interruption_point = [] {};
//! Declare default constructor and destructor that are not inline, so code
//! instantiating the NodeContext struct doesn't need to #include class
//! definitions for all the unique_ptr members.
NodeContext();
~NodeContext();
};
#endif // DIGIBYTE_NODE_CONTEXT_H

706
src/node/interfaces.cpp Normal file
View file

@ -0,0 +1,706 @@
// Copyright (c) 2018-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <addrdb.h>
#include <banman.h>
#include <chain.h>
#include <chainparams.h>
#include <deploymentstatus.h>
#include <external_signer.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <interfaces/wallet.h>
#include <mapport.h>
#include <net.h>
#include <net_processing.h>
#include <netaddress.h>
#include <netbase.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/transaction.h>
#include <node/ui_interface.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <policy/rbf.h>
#include <policy/settings.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <rpc/protocol.h>
#include <rpc/server.h>
#include <shutdown.h>
#include <support/allocators/secure.h>
#include <sync.h>
#include <timedata.h>
#include <txmempool.h>
#include <uint256.h>
#include <univalue.h>
#include <util/check.h>
#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <warnings.h>
#if defined(HAVE_CONFIG_H)
#include <config/vexta-config.h>
#endif
#include <any>
#include <memory>
#include <optional>
#include <utility>
#include <boost/signals2/signal.hpp>
using interfaces::BlockTip;
using interfaces::Chain;
using interfaces::FoundBlock;
using interfaces::Handler;
using interfaces::MakeHandler;
using interfaces::Node;
using interfaces::WalletClient;
namespace node {
namespace {
class NodeImpl : public Node
{
private:
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
public:
explicit NodeImpl(NodeContext* context) { setContext(context); }
void initLogging() override { InitLogging(*Assert(m_context->args)); }
void initParameterInteraction() override { InitParameterInteraction(*Assert(m_context->args)); }
bilingual_str getWarnings() override { return GetWarnings(true); }
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
bool baseInitialize() override
{
return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(gArgs) && AppInitSanityChecks() &&
AppInitLockDataDirectory() && AppInitInterfaces(*m_context);
}
bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override
{
return AppInitMain(*m_context, tip_info);
}
void appShutdown() override
{
Interrupt(*m_context);
Shutdown(*m_context);
}
void startShutdown() override
{
StartShutdown();
// Stop RPC for clean shutdown if any of waitfor* commands is executed.
if (gArgs.GetBoolArg("-server", false)) {
InterruptRPC();
StopRPC();
}
}
bool shutdownRequested() override { return ShutdownRequested(); }
void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); }
bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); }
size_t getNodeCount(ConnectionDirection flags) override
{
return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0;
}
bool getNodesStats(NodesStats& stats) override
{
stats.clear();
if (m_context->connman) {
std::vector<CNodeStats> stats_temp;
m_context->connman->GetNodeStats(stats_temp);
stats.reserve(stats_temp.size());
for (auto& node_stats_temp : stats_temp) {
stats.emplace_back(std::move(node_stats_temp), false, CNodeStateStats());
}
// Try to retrieve the CNodeStateStats for each node.
if (m_context->peerman) {
TRY_LOCK(::cs_main, lockMain);
if (lockMain) {
for (auto& node_stats : stats) {
std::get<1>(node_stats) =
m_context->peerman->GetNodeStateStats(std::get<0>(node_stats).nodeid, std::get<2>(node_stats));
}
}
}
return true;
}
return false;
}
bool getBanned(banmap_t& banmap) override
{
if (m_context->banman) {
m_context->banman->GetBanned(banmap);
return true;
}
return false;
}
bool ban(const CNetAddr& net_addr, int64_t ban_time_offset) override
{
if (m_context->banman) {
m_context->banman->Ban(net_addr, ban_time_offset);
return true;
}
return false;
}
bool unban(const CSubNet& ip) override
{
if (m_context->banman) {
m_context->banman->Unban(ip);
return true;
}
return false;
}
bool disconnectByAddress(const CNetAddr& net_addr) override
{
if (m_context->connman) {
return m_context->connman->DisconnectNode(net_addr);
}
return false;
}
bool disconnectById(NodeId id) override
{
if (m_context->connman) {
return m_context->connman->DisconnectNode(id);
}
return false;
}
std::vector<ExternalSigner> externalSigners() override
{
#ifdef ENABLE_EXTERNAL_SIGNER
std::vector<ExternalSigner> signers = {};
const std::string command = gArgs.GetArg("-signer", "");
if (command == "") return signers;
ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
return signers;
#else
// This result is indistinguishable from a successful call that returns
// no signers. For the current GUI this doesn't matter, because the wallet
// creation dialog disables the external signer checkbox in both
// cases. The return type could be changed to std::optional<std::vector>
// (or something that also includes error messages) if this distinction
// becomes important.
return {};
#endif // ENABLE_EXTERNAL_SIGNER
}
int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; }
int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; }
size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; }
size_t getMempoolDynamicUsage() override { return m_context->mempool ? m_context->mempool->DynamicMemoryUsage() : 0; }
bool getHeaderTip(int& height, int64_t& block_time) override
{
LOCK(::cs_main);
if (::pindexBestHeader) {
height = ::pindexBestHeader->nHeight;
block_time = ::pindexBestHeader->GetBlockTime();
return true;
}
return false;
}
int getNumBlocks() override
{
LOCK(::cs_main);
return chainman().ActiveChain().Height();
}
uint256 getBestBlockHash() override
{
const CBlockIndex* tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip());
return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash();
}
int64_t getLastBlockTime() override
{
LOCK(::cs_main);
if (chainman().ActiveChain().Tip()) {
return chainman().ActiveChain().Tip()->GetBlockTime();
}
return Params().GenesisBlock().GetBlockTime(); // Genesis block's time of current network
}
double getVerificationProgress() override
{
const CBlockIndex* tip;
{
LOCK(::cs_main);
tip = chainman().ActiveChain().Tip();
}
return GuessVerificationProgress(Params().TxData(), tip);
}
bool isInitialBlockDownload() override {
return chainman().ActiveChainstate().IsInitialBlockDownload();
}
bool getReindex() override { return ::fReindex; }
bool getImporting() override { return ::fImporting; }
void setNetworkActive(bool active) override
{
if (m_context->connman) {
m_context->connman->SetNetworkActive(active);
}
}
bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); }
CFeeRate getDustRelayFee() override { return ::dustRelayFee; }
UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override
{
JSONRPCRequest req;
req.context = m_context;
req.params = params;
req.strMethod = command;
req.URI = uri;
return ::tableRPC.execute(req);
}
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) override { RPCSetTimerInterfaceIfUnset(iface); }
void rpcUnsetTimerInterface(RPCTimerInterface* iface) override { RPCUnsetTimerInterface(iface); }
bool getUnspentOutput(const COutPoint& output, Coin& coin) override
{
LOCK(::cs_main);
return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin);
}
WalletClient& walletClient() override
{
return *Assert(m_context->wallet_client);
}
std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override
{
return MakeHandler(::uiInterface.InitMessage_connect(fn));
}
std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) override
{
return MakeHandler(::uiInterface.ThreadSafeMessageBox_connect(fn));
}
std::unique_ptr<Handler> handleQuestion(QuestionFn fn) override
{
return MakeHandler(::uiInterface.ThreadSafeQuestion_connect(fn));
}
std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override
{
return MakeHandler(::uiInterface.ShowProgress_connect(fn));
}
std::unique_ptr<Handler> handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) override
{
return MakeHandler(::uiInterface.NotifyNumConnectionsChanged_connect(fn));
}
std::unique_ptr<Handler> handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn fn) override
{
return MakeHandler(::uiInterface.NotifyNetworkActiveChanged_connect(fn));
}
std::unique_ptr<Handler> handleNotifyAlertChanged(NotifyAlertChangedFn fn) override
{
return MakeHandler(::uiInterface.NotifyAlertChanged_connect(fn));
}
std::unique_ptr<Handler> handleBannedListChanged(BannedListChangedFn fn) override
{
return MakeHandler(::uiInterface.BannedListChanged_connect(fn));
}
std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) override
{
return MakeHandler(::uiInterface.NotifyBlockTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()},
GuessVerificationProgress(Params().TxData(), block));
}));
}
std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override
{
return MakeHandler(
::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()},
/* verification progress is unused when a header was received */ 0);
}));
}
NodeContext* context() override { return m_context; }
void setContext(NodeContext* context) override
{
m_context = context;
}
NodeContext* m_context{nullptr};
};
bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active)
{
if (!index) return false;
if (block.m_hash) *block.m_hash = index->GetBlockHash();
if (block.m_height) *block.m_height = index->nHeight;
if (block.m_time) *block.m_time = index->GetBlockTime();
if (block.m_max_time) *block.m_max_time = index->GetBlockTimeMax();
if (block.m_mtp_time) *block.m_mtp_time = index->GetMedianTimePast();
if (block.m_in_active_chain) *block.m_in_active_chain = active[index->nHeight] == index;
if (block.m_next_block) FillBlock(active[index->nHeight] == index ? active[index->nHeight + 1] : nullptr, *block.m_next_block, lock, active);
if (block.m_data) {
REVERSE_LOCK(lock);
if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) block.m_data->SetNull();
}
return true;
}
class NotificationsProxy : public CValidationInterface
{
public:
explicit NotificationsProxy(std::shared_ptr<Chain::Notifications> notifications)
: m_notifications(std::move(notifications)) {}
virtual ~NotificationsProxy() = default;
void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override
{
m_notifications->transactionAddedToMempool(tx, mempool_sequence);
}
void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override
{
m_notifications->transactionRemovedFromMempool(tx, reason, mempool_sequence);
}
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override
{
m_notifications->blockConnected(*block, index->nHeight);
}
void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override
{
m_notifications->blockDisconnected(*block, index->nHeight);
}
void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
{
m_notifications->updatedBlockTip();
}
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->chainStateFlushed(locator); }
std::shared_ptr<Chain::Notifications> m_notifications;
};
class NotificationsHandlerImpl : public Handler
{
public:
explicit NotificationsHandlerImpl(std::shared_ptr<Chain::Notifications> notifications)
: m_proxy(std::make_shared<NotificationsProxy>(std::move(notifications)))
{
RegisterSharedValidationInterface(m_proxy);
}
~NotificationsHandlerImpl() override { disconnect(); }
void disconnect() override
{
if (m_proxy) {
UnregisterSharedValidationInterface(m_proxy);
m_proxy.reset();
}
}
std::shared_ptr<NotificationsProxy> m_proxy;
};
class RpcHandlerImpl : public Handler
{
public:
explicit RpcHandlerImpl(const CRPCCommand& command) : m_command(command), m_wrapped_command(&command)
{
m_command.actor = [this](const JSONRPCRequest& request, UniValue& result, bool last_handler) {
if (!m_wrapped_command) return false;
try {
return m_wrapped_command->actor(request, result, last_handler);
} catch (const UniValue& e) {
// If this is not the last handler and a wallet not found
// exception was thrown, return false so the next handler can
// try to handle the request. Otherwise, reraise the exception.
if (!last_handler) {
const UniValue& code = e["code"];
if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) {
return false;
}
}
throw;
}
};
::tableRPC.appendCommand(m_command.name, &m_command);
}
void disconnect() final
{
if (m_wrapped_command) {
m_wrapped_command = nullptr;
::tableRPC.removeCommand(m_command.name, &m_command);
}
}
~RpcHandlerImpl() override { disconnect(); }
CRPCCommand m_command;
const CRPCCommand* m_wrapped_command;
};
class ChainImpl : public Chain
{
private:
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
public:
explicit ChainImpl(NodeContext& node) : m_node(node) {}
std::optional<int> getHeight() override
{
LOCK(::cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
int height = active.Height();
if (height >= 0) {
return height;
}
return std::nullopt;
}
uint256 getBlockHash(int height) override
{
LOCK(::cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
CBlockIndex* block = active[height];
assert(block);
return block->GetBlockHash();
}
bool haveBlockOnDisk(int height) override
{
LOCK(cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
CBlockIndex* block = active[height];
return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0;
}
CBlockLocator getTipLocator() override
{
LOCK(cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
return active.GetLocator();
}
bool checkFinalTx(const CTransaction& tx) override
{
LOCK(cs_main);
return CheckFinalTx(chainman().ActiveChain().Tip(), tx);
}
std::optional<int> findLocatorFork(const CBlockLocator& locator) override
{
LOCK(cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
if (CBlockIndex* fork = m_node.chainman->m_blockman.FindForkInGlobalIndex(active, locator)) {
return fork->nHeight;
}
return std::nullopt;
}
bool findBlock(const uint256& hash, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active);
}
bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active);
}
bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out) override
{
WAIT_LOCK(cs_main, lock);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) {
if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) {
return FillBlock(ancestor, ancestor_out, lock, active);
}
}
return FillBlock(nullptr, ancestor_out, lock, active);
}
bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, const FoundBlock& ancestor_out) override
{
WAIT_LOCK(cs_main, lock);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash);
const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash);
if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr;
return FillBlock(ancestor, ancestor_out, lock, active);
}
bool findCommonAncestor(const uint256& block_hash1, const uint256& block_hash2, const FoundBlock& ancestor_out, const FoundBlock& block1_out, const FoundBlock& block2_out) override
{
WAIT_LOCK(cs_main, lock);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1);
const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2);
const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr;
// Using & instead of && below to avoid short circuiting and leaving
// output uninitialized.
return FillBlock(ancestor, ancestor_out, lock, active) & FillBlock(block1, block1_out, lock, active) & FillBlock(block2, block2_out, lock, active);
}
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
double guessVerificationProgress(const uint256& block_hash) override
{
LOCK(cs_main);
return GuessVerificationProgress(Params().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash));
}
bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override
{
// hasBlocks returns true if all ancestors of block_hash in specified
// range have block data (are not pruned), false if any ancestors in
// specified range are missing data.
//
// For simplicity and robustness, min_height and max_height are only
// used to limit the range, and passing min_height that's too low or
// max_height that's too high will not crash or change the result.
LOCK(::cs_main);
if (CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) {
if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height);
for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) {
// Check pprev to not segfault if min_height is too low
if (block->nHeight <= min_height || !block->pprev) return true;
}
}
return false;
}
RBFTransactionState isRBFOptIn(const CTransaction& tx) override
{
if (!m_node.mempool) return IsRBFOptInEmptyMempool(tx);
LOCK(m_node.mempool->cs);
return IsRBFOptIn(tx, *m_node.mempool);
}
bool isInMempool(const uint256& txid) override
{
if (!m_node.mempool) return false;
LOCK(m_node.mempool->cs);
return m_node.mempool->exists(txid);
}
bool hasDescendantsInMempool(const uint256& txid) override
{
if (!m_node.mempool) return false;
LOCK(m_node.mempool->cs);
auto it = m_node.mempool->GetIter(txid);
return it && (*it)->GetCountWithDescendants() > 1;
}
bool broadcastTransaction(const CTransactionRef& tx,
const CAmount& max_tx_fee,
bool relay,
std::string& err_string) override
{
const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false);
// Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures.
// Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures
// that Chain clients do not need to know about.
return TransactionError::OK == err;
}
void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override
{
ancestors = descendants = 0;
if (!m_node.mempool) return;
m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants);
}
void getPackageLimits(unsigned int& limit_ancestor_count, unsigned int& limit_descendant_count) override
{
limit_ancestor_count = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
limit_descendant_count = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
}
bool checkChainLimits(const CTransactionRef& tx) override
{
if (!m_node.mempool) return true;
LockPoints lp;
CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp);
CTxMemPool::setEntries ancestors;
auto limit_ancestor_count = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
auto limit_ancestor_size = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000;
auto limit_descendant_count = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
auto limit_descendant_size = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000;
std::string unused_error_string;
LOCK(m_node.mempool->cs);
return m_node.mempool->CalculateMemPoolAncestors(
entry, ancestors, limit_ancestor_count, limit_ancestor_size,
limit_descendant_count, limit_descendant_size, unused_error_string);
}
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
{
if (!m_node.fee_estimator) return {};
return m_node.fee_estimator->estimateSmartFee(num_blocks, calc, conservative);
}
unsigned int estimateMaxBlocks() override
{
if (!m_node.fee_estimator) return 0;
return m_node.fee_estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
}
CFeeRate mempoolMinFee() override
{
if (!m_node.mempool) return {};
return m_node.mempool->GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
}
CFeeRate relayMinFee() override { return ::minRelayTxFee; }
CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; }
CFeeRate relayDustFee() override { return ::dustRelayFee; }
bool havePruned() override
{
LOCK(cs_main);
return ::fHavePruned;
}
bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); }
bool isInitialBlockDownload() override {
return chainman().ActiveChainstate().IsInitialBlockDownload();
}
bool shutdownRequested() override { return ShutdownRequested(); }
int64_t getAdjustedTime() override { return GetAdjustedTime(); }
void initMessage(const std::string& message) override { ::uiInterface.InitMessage(message); }
void initWarning(const bilingual_str& message) override { InitWarning(message); }
void initError(const bilingual_str& message) override { InitError(message); }
void showProgress(const std::string& title, int progress, bool resume_possible) override
{
::uiInterface.ShowProgress(title, progress, resume_possible);
}
std::unique_ptr<Handler> handleNotifications(std::shared_ptr<Notifications> notifications) override
{
return std::make_unique<NotificationsHandlerImpl>(std::move(notifications));
}
void waitForNotificationsIfTipChanged(const uint256& old_tip) override
{
if (!old_tip.IsNull()) {
LOCK(::cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
if (old_tip == active.Tip()->GetBlockHash()) return;
}
SyncWithValidationInterfaceQueue();
}
std::unique_ptr<Handler> handleRpc(const CRPCCommand& command) override
{
return std::make_unique<RpcHandlerImpl>(command);
}
bool rpcEnableDeprecated(const std::string& method) override { return IsDeprecatedRPCEnabled(method); }
void rpcRunLater(const std::string& name, std::function<void()> fn, int64_t seconds) override
{
RPCRunLater(name, std::move(fn), seconds);
}
int rpcSerializationFlags() override { return RPCSerializationFlags(); }
util::SettingsValue getRwSetting(const std::string& name) override
{
util::SettingsValue result;
gArgs.LockSettings([&](const util::Settings& settings) {
if (const util::SettingsValue* value = util::FindKey(settings.rw_settings, name)) {
result = *value;
}
});
return result;
}
bool updateRwSetting(const std::string& name, const util::SettingsValue& value) override
{
gArgs.LockSettings([&](util::Settings& settings) {
if (value.isNull()) {
settings.rw_settings.erase(name);
} else {
settings.rw_settings[name] = value;
}
});
return gArgs.WriteSettingsFile();
}
void requestMempoolTransactions(Notifications& notifications) override
{
if (!m_node.mempool) return;
LOCK2(::cs_main, m_node.mempool->cs);
for (const CTxMemPoolEntry& entry : m_node.mempool->mapTx) {
notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */);
}
}
bool isTaprootActive() const override
{
LOCK(::cs_main);
const CBlockIndex* tip = Assert(m_node.chainman)->ActiveChain().Tip();
return DeploymentActiveAfter(tip, Params().GetConsensus(), Consensus::DEPLOYMENT_TAPROOT);
}
NodeContext& m_node;
};
} // namespace
} // namespace node
namespace interfaces {
std::unique_ptr<Node> MakeNode(NodeContext* context) { return std::make_unique<node::NodeImpl>(context); }
std::unique_ptr<Chain> MakeChain(NodeContext& context) { return std::make_unique<node::ChainImpl>(context); }
} // namespace interfaces

149
src/node/psbt.cpp Normal file
View file

@ -0,0 +1,149 @@
// Copyright (c) 2009-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <amount.h>
#include <coins.h>
#include <consensus/tx_verify.h>
#include <node/psbt.h>
#include <policy/policy.h>
#include <policy/settings.h>
#include <tinyformat.h>
#include <numeric>
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
{
// Go through each input and build status
PSBTAnalysis result;
bool calc_fee = true;
CAmount in_amt = 0;
result.inputs.resize(psbtx.tx->vin.size());
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& input = psbtx.inputs[i];
PSBTInputAnalysis& input_analysis = result.inputs[i];
// We set next role here and ratchet backwards as required
input_analysis.next = PSBTRole::EXTRACTOR;
// Check for a UTXO
CTxOut utxo;
if (psbtx.GetInputUTXO(utxo, i)) {
if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) {
result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i));
return result;
}
in_amt += utxo.nValue;
input_analysis.has_utxo = true;
} else {
if (input.non_witness_utxo && psbtx.tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) {
result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i));
return result;
}
input_analysis.has_utxo = false;
input_analysis.is_final = false;
input_analysis.next = PSBTRole::UPDATER;
calc_fee = false;
}
if (!utxo.IsNull() && utxo.scriptPubKey.IsUnspendable()) {
result.SetInvalid(strprintf("PSBT is not valid. Input %u spends unspendable output", i));
return result;
}
// Check if it is final
if (!utxo.IsNull() && !PSBTInputSigned(input)) {
input_analysis.is_final = false;
// Figure out what is missing
SignatureData outdata;
bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata);
// Things are missing
if (!complete) {
input_analysis.missing_pubkeys = outdata.missing_pubkeys;
input_analysis.missing_redeem_script = outdata.missing_redeem_script;
input_analysis.missing_witness_script = outdata.missing_witness_script;
input_analysis.missing_sigs = outdata.missing_sigs;
// If we are only missing signatures and nothing else, then next is signer
if (outdata.missing_pubkeys.empty() && outdata.missing_redeem_script.IsNull() && outdata.missing_witness_script.IsNull() && !outdata.missing_sigs.empty()) {
input_analysis.next = PSBTRole::SIGNER;
} else {
input_analysis.next = PSBTRole::UPDATER;
}
} else {
input_analysis.next = PSBTRole::FINALIZER;
}
} else if (!utxo.IsNull()){
input_analysis.is_final = true;
}
}
// Calculate next role for PSBT by grabbing "minimum" PSBTInput next role
result.next = PSBTRole::EXTRACTOR;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInputAnalysis& input_analysis = result.inputs[i];
result.next = std::min(result.next, input_analysis.next);
}
assert(result.next > PSBTRole::CREATOR);
if (calc_fee) {
// Get the output amount
CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0),
[](CAmount a, const CTxOut& b) {
if (!MoneyRange(a) || !MoneyRange(b.nValue) || !MoneyRange(a + b.nValue)) {
return CAmount(-1);
}
return a += b.nValue;
}
);
if (!MoneyRange(out_amt)) {
result.SetInvalid(strprintf("PSBT is not valid. Output amount invalid"));
return result;
}
// Get the fee
CAmount fee = in_amt - out_amt;
result.fee = fee;
// Estimate the size
CMutableTransaction mtx(*psbtx.tx);
CCoinsView view_dummy;
CCoinsViewCache view(&view_dummy);
bool success = true;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& input = psbtx.inputs[i];
Coin newcoin;
if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) {
success = false;
break;
} else {
mtx.vin[i].scriptSig = input.final_script_sig;
mtx.vin[i].scriptWitness = input.final_script_witness;
newcoin.nHeight = 1;
view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true);
}
}
if (success) {
CTransaction ctx = CTransaction(mtx);
size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
result.estimated_vsize = size;
// Estimate fee rate
CFeeRate feerate(fee, size);
result.estimated_feerate = feerate;
}
}
return result;
}

56
src/node/psbt.h Normal file
View file

@ -0,0 +1,56 @@
// Copyright (c) 2009-2019 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_PSBT_H
#define DIGIBYTE_NODE_PSBT_H
#include <psbt.h>
#include <optional>
/**
* Holds an analysis of one input from a PSBT
*/
struct PSBTInputAnalysis {
bool has_utxo; //!< Whether we have UTXO information for this input
bool is_final; //!< Whether the input has all required information including signatures
PSBTRole next; //!< Which of the BIP 174 roles needs to handle this input next
std::vector<CKeyID> missing_pubkeys; //!< Pubkeys whose BIP32 derivation path is missing
std::vector<CKeyID> missing_sigs; //!< Pubkeys whose signatures are missing
uint160 missing_redeem_script; //!< Hash160 of redeem script, if missing
uint256 missing_witness_script; //!< SHA256 of witness script, if missing
};
/**
* Holds the results of AnalyzePSBT (miscellaneous information about a PSBT)
*/
struct PSBTAnalysis {
std::optional<size_t> estimated_vsize; //!< Estimated weight of the transaction
std::optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction
std::optional<CAmount> fee; //!< Amount of fee being paid by the transaction
std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction
PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next
std::string error; //!< Error message
void SetInvalid(std::string err_msg)
{
estimated_vsize = std::nullopt;
estimated_feerate = std::nullopt;
fee = std::nullopt;
inputs.clear();
next = PSBTRole::CREATOR;
error = err_msg;
}
};
/**
* Provides helpful miscellaneous information about where a PSBT is in the signing workflow.
*
* @param[in] psbtx the PSBT to analyze
* @return A PSBTAnalysis with information about the provided PSBT.
*/
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx);
#endif // DIGIBYTE_NODE_PSBT_H

139
src/node/transaction.cpp Normal file
View file

@ -0,0 +1,139 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/validation.h>
#include <net.h>
#include <net_processing.h>
#include <node/context.h>
#include <validation.h>
#include <validationinterface.h>
#include <node/transaction.h>
#include <future>
static TransactionError HandleATMPError(const TxValidationState& state, std::string& err_string_out)
{
err_string_out = state.ToString();
if (state.IsInvalid()) {
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
return TransactionError::MISSING_INPUTS;
}
return TransactionError::MEMPOOL_REJECTED;
} else {
return TransactionError::MEMPOOL_ERROR;
}
}
TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback)
{
// BroadcastTransaction can be called by either sendrawtransaction RPC or the wallet.
// chainman, mempool and peerman are initialized before the RPC server and wallet are started
// and reset after the RPC sever and wallet are stopped.
assert(node.chainman);
assert(node.mempool);
assert(node.peerman);
std::promise<void> promise;
uint256 txid = tx->GetHash();
uint256 wtxid = tx->GetWitnessHash();
bool callback_set = false;
{
LOCK(cs_main);
// If the transaction is already confirmed in the chain, don't do anything
// and return early.
CCoinsViewCache &view = node.chainman->ActiveChainstate().CoinsTip();
for (size_t o = 0; o < tx->vout.size(); o++) {
const Coin& existingCoin = view.AccessCoin(COutPoint(txid, o));
// IsSpent doesn't mean the coin is spent, it means the output doesn't exist.
// So if the output does exist, then this transaction exists in the chain.
if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN;
}
if (auto mempool_tx = node.mempool->get(txid); mempool_tx) {
// There's already a transaction in the mempool with this txid. Don't
// try to submit this transaction to the mempool (since it'll be
// rejected as a TX_CONFLICT), but do attempt to reannounce the mempool
// transaction if relay=true.
//
// The mempool transaction may have the same or different witness (and
// wtxid) as this transaction. Use the mempool's wtxid for reannouncement.
wtxid = mempool_tx->GetWitnessHash();
} else {
// Transaction is not already in the mempool.
if (max_tx_fee > 0) {
// First, call ATMP with test_accept and check the fee. If ATMP
// fails here, return error immediately.
const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false, true);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
} else if (result.m_base_fees.value() > max_tx_fee) {
return TransactionError::MAX_FEE_EXCEEDED;
} else {
// This doesnt really matter, as its just a test; but may as well be consistent
AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.stempool, tx, false, true);
}
}
// Try to submit the transaction to the stempool only (if dandelion is enabled);
if (gArgs.GetBoolArg("-dandelion", DEFAULT_DANDELION)) {
const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.stempool, tx, false, false);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
}
} else {
const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false, false);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
}
}
// Transaction was accepted to the mempool.
if (relay) {
// the mempool tracks locally submitted transactions to make a
// best-effort of initial broadcast
node.mempool->AddUnbroadcastTx(txid);
}
if (wait_callback) {
// For transactions broadcast from outside the wallet, make sure
// that the wallet has been notified of the transaction before
// continuing.
//
// This prevents a race where a user might call sendrawtransaction
// with a transaction to/from their wallet, immediately call some
// wallet RPC, and get a stale result because callbacks have not
// yet been processed.
CallFunctionInValidationInterfaceQueue([&promise] {
promise.set_value();
});
callback_set = true;
}
}
} // cs_main
if (callback_set) {
// Wait until Validation Interface clients have been notified of the
// transaction entering the mempool.
promise.get_future().wait();
}
if (relay) {
if (gArgs.GetBoolArg("-dandelion", DEFAULT_DANDELION)) {
auto current_time = GetTime<std::chrono::milliseconds>();
std::chrono::microseconds nEmbargo = DANDELION_EMBARGO_MINIMUM + PoissonNextSend(current_time, DANDELION_EMBARGO_AVG_ADD);
node.connman->insertDandelionEmbargo(txid, nEmbargo);
auto embargo_timeout = std::chrono::duration_cast<std::chrono::seconds>(nEmbargo - current_time).count();
LogPrint(BCLog::DANDELION, "dandeliontx %s embargoed for %d seconds\n", txid.ToString(), embargo_timeout);
CInv embargoTx(MSG_DANDELION_TX, txid);
node.connman->localDandelionDestinationPushInventory(embargoTx);
return TransactionError::OK;
}
node.peerman->RelayTransaction(txid, wtxid);
}
return TransactionError::OK;
}

41
src/node/transaction.h Normal file
View file

@ -0,0 +1,41 @@
// Copyright (c) 2017-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_TRANSACTION_H
#define DIGIBYTE_NODE_TRANSACTION_H
#include <attributes.h>
#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <util/error.h>
struct NodeContext;
/** Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
* Also used by the GUI when broadcasting a completed PSBT.
* By default, a transaction with a fee rate higher than this will be rejected
* by these RPCs and the GUI. This can be overridden with the maxfeerate argument.
*/
static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN * 100};
/**
* Submit a transaction to the mempool and (optionally) relay it to all P2P peers.
*
* Mempool submission can be synchronous (will await mempool entry notification
* over the CValidationInterface) or asynchronous (will submit and not wait for
* notification), depending on the value of wait_callback. wait_callback MUST
* NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid
* deadlock.
*
* @param[in] node reference to node context
* @param[in] tx the transaction to broadcast
* @param[out] err_string reference to std::string to fill with error string if available
* @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee)
* @param[in] relay flag if both mempool insertion and p2p relay are requested
* @param[in] wait_callback wait until callbacks have been processed to avoid stale result due to a sequentially RPC.
* return error
*/
[[nodiscard]] TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback);
#endif // DIGIBYTE_NODE_TRANSACTION_H

65
src/node/ui_interface.cpp Normal file
View file

@ -0,0 +1,65 @@
// Copyright (c) 2010-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/ui_interface.h>
#include <util/translation.h>
#include <boost/signals2/optional_last_value.hpp>
#include <boost/signals2/signal.hpp>
CClientUIInterface uiInterface;
struct UISignals {
boost::signals2::signal<CClientUIInterface::ThreadSafeMessageBoxSig, boost::signals2::optional_last_value<bool>> ThreadSafeMessageBox;
boost::signals2::signal<CClientUIInterface::ThreadSafeQuestionSig, boost::signals2::optional_last_value<bool>> ThreadSafeQuestion;
boost::signals2::signal<CClientUIInterface::InitMessageSig> InitMessage;
boost::signals2::signal<CClientUIInterface::NotifyNumConnectionsChangedSig> NotifyNumConnectionsChanged;
boost::signals2::signal<CClientUIInterface::NotifyNetworkActiveChangedSig> NotifyNetworkActiveChanged;
boost::signals2::signal<CClientUIInterface::NotifyAlertChangedSig> NotifyAlertChanged;
boost::signals2::signal<CClientUIInterface::ShowProgressSig> ShowProgress;
boost::signals2::signal<CClientUIInterface::NotifyBlockTipSig> NotifyBlockTip;
boost::signals2::signal<CClientUIInterface::NotifyHeaderTipSig> NotifyHeaderTip;
boost::signals2::signal<CClientUIInterface::BannedListChangedSig> BannedListChanged;
};
static UISignals g_ui_signals;
#define ADD_SIGNALS_IMPL_WRAPPER(signal_name) \
boost::signals2::connection CClientUIInterface::signal_name##_connect(std::function<signal_name##Sig> fn) \
{ \
return g_ui_signals.signal_name.connect(fn); \
}
ADD_SIGNALS_IMPL_WRAPPER(ThreadSafeMessageBox);
ADD_SIGNALS_IMPL_WRAPPER(ThreadSafeQuestion);
ADD_SIGNALS_IMPL_WRAPPER(InitMessage);
ADD_SIGNALS_IMPL_WRAPPER(NotifyNumConnectionsChanged);
ADD_SIGNALS_IMPL_WRAPPER(NotifyNetworkActiveChanged);
ADD_SIGNALS_IMPL_WRAPPER(NotifyAlertChanged);
ADD_SIGNALS_IMPL_WRAPPER(ShowProgress);
ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip);
ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip);
ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged);
bool CClientUIInterface::ThreadSafeMessageBox(const bilingual_str& message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeMessageBox(message, caption, style).value_or(false);}
bool CClientUIInterface::ThreadSafeQuestion(const bilingual_str& message, const std::string& non_interactive_message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeQuestion(message, non_interactive_message, caption, style).value_or(false);}
void CClientUIInterface::InitMessage(const std::string& message) { return g_ui_signals.InitMessage(message); }
void CClientUIInterface::NotifyNumConnectionsChanged(int newNumConnections) { return g_ui_signals.NotifyNumConnectionsChanged(newNumConnections); }
void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return g_ui_signals.NotifyNetworkActiveChanged(networkActive); }
void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); }
void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); }
void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); }
void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyHeaderTip(s, i); }
void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); }
bool InitError(const bilingual_str& str)
{
uiInterface.ThreadSafeMessageBox(str, "", CClientUIInterface::MSG_ERROR);
return false;
}
void InitWarning(const bilingual_str& str)
{
uiInterface.ThreadSafeMessageBox(str, "", CClientUIInterface::MSG_WARNING);
}

122
src/node/ui_interface.h Normal file
View file

@ -0,0 +1,122 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Copyright (c) 2014-2019 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_UI_INTERFACE_H
#define DIGIBYTE_NODE_UI_INTERFACE_H
#include <functional>
#include <memory>
#include <string>
class CBlockIndex;
enum class SynchronizationState;
struct bilingual_str;
namespace boost {
namespace signals2 {
class connection;
}
} // namespace boost
/** Signals for UI communication. */
class CClientUIInterface
{
public:
/** Flags for CClientUIInterface::ThreadSafeMessageBox */
enum MessageBoxFlags
{
ICON_INFORMATION = 0,
ICON_WARNING = (1U << 0),
ICON_ERROR = (1U << 1),
/**
* Mask of all available icons in CClientUIInterface::MessageBoxFlags
* This needs to be updated, when icons are changed there!
*/
ICON_MASK = (ICON_INFORMATION | ICON_WARNING | ICON_ERROR),
/** These values are taken from qmessagebox.h "enum StandardButton" to be directly usable */
BTN_OK = 0x00000400U, // QMessageBox::Ok
BTN_YES = 0x00004000U, // QMessageBox::Yes
BTN_NO = 0x00010000U, // QMessageBox::No
BTN_ABORT = 0x00040000U, // QMessageBox::Abort
BTN_RETRY = 0x00080000U, // QMessageBox::Retry
BTN_IGNORE = 0x00100000U, // QMessageBox::Ignore
BTN_CLOSE = 0x00200000U, // QMessageBox::Close
BTN_CANCEL = 0x00400000U, // QMessageBox::Cancel
BTN_DISCARD = 0x00800000U, // QMessageBox::Discard
BTN_HELP = 0x01000000U, // QMessageBox::Help
BTN_APPLY = 0x02000000U, // QMessageBox::Apply
BTN_RESET = 0x04000000U, // QMessageBox::Reset
/**
* Mask of all available buttons in CClientUIInterface::MessageBoxFlags
* This needs to be updated, when buttons are changed there!
*/
BTN_MASK = (BTN_OK | BTN_YES | BTN_NO | BTN_ABORT | BTN_RETRY | BTN_IGNORE |
BTN_CLOSE | BTN_CANCEL | BTN_DISCARD | BTN_HELP | BTN_APPLY | BTN_RESET),
/** Force blocking, modal message box dialog (not just OS notification) */
MODAL = 0x10000000U,
/** Do not print contents of message to debug log */
SECURE = 0x40000000U,
/** Predefined combinations for certain default usage cases */
MSG_INFORMATION = ICON_INFORMATION,
MSG_WARNING = (ICON_WARNING | BTN_OK | MODAL),
MSG_ERROR = (ICON_ERROR | BTN_OK | MODAL)
};
#define ADD_SIGNALS_DECL_WRAPPER(signal_name, rtype, ...) \
rtype signal_name(__VA_ARGS__); \
using signal_name##Sig = rtype(__VA_ARGS__); \
boost::signals2::connection signal_name##_connect(std::function<signal_name##Sig> fn);
/** Show message box. */
ADD_SIGNALS_DECL_WRAPPER(ThreadSafeMessageBox, bool, const bilingual_str& message, const std::string& caption, unsigned int style);
/** If possible, ask the user a question. If not, falls back to ThreadSafeMessageBox(noninteractive_message, caption, style) and returns false. */
ADD_SIGNALS_DECL_WRAPPER(ThreadSafeQuestion, bool, const bilingual_str& message, const std::string& noninteractive_message, const std::string& caption, unsigned int style);
/** Progress message during initialization. */
ADD_SIGNALS_DECL_WRAPPER(InitMessage, void, const std::string& message);
/** Number of network connections changed. */
ADD_SIGNALS_DECL_WRAPPER(NotifyNumConnectionsChanged, void, int newNumConnections);
/** Network activity state changed. */
ADD_SIGNALS_DECL_WRAPPER(NotifyNetworkActiveChanged, void, bool networkActive);
/**
* Status bar alerts changed.
*/
ADD_SIGNALS_DECL_WRAPPER(NotifyAlertChanged, void, );
/**
* Show progress e.g. for verifychain.
* resume_possible indicates shutting down now will result in the current progress action resuming upon restart.
*/
ADD_SIGNALS_DECL_WRAPPER(ShowProgress, void, const std::string& title, int nProgress, bool resume_possible);
/** New block has been accepted */
ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*);
/** Best header has changed */
ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, const CBlockIndex*);
/** Banlist did change. */
ADD_SIGNALS_DECL_WRAPPER(BannedListChanged, void, void);
};
/** Show warning message **/
void InitWarning(const bilingual_str& str);
/** Show error message **/
bool InitError(const bilingual_str& str);
constexpr auto AbortError = InitError;
extern CClientUIInterface uiInterface;
#endif // DIGIBYTE_NODE_UI_INTERFACE_H

36
src/node/utxo_snapshot.h Normal file
View file

@ -0,0 +1,36 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 The DigiByte Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DIGIBYTE_NODE_UTXO_SNAPSHOT_H
#define DIGIBYTE_NODE_UTXO_SNAPSHOT_H
#include <uint256.h>
#include <serialize.h>
//! Metadata describing a serialized version of a UTXO set from which an
//! assumeutxo CChainState can be constructed.
class SnapshotMetadata
{
public:
//! The hash of the block that reflects the tip of the chain for the
//! UTXO set contained in this snapshot.
uint256 m_base_blockhash;
//! The number of coins in the UTXO set contained in this snapshot. Used
//! during snapshot load to estimate progress of UTXO set reconstruction.
uint64_t m_coins_count = 0;
SnapshotMetadata() { }
SnapshotMetadata(
const uint256& base_blockhash,
uint64_t coins_count,
unsigned int nchaintx) :
m_base_blockhash(base_blockhash),
m_coins_count(coins_count) { }
SERIALIZE_METHODS(SnapshotMetadata, obj) { READWRITE(obj.m_base_blockhash, obj.m_coins_count); }
};
#endif // DIGIBYTE_NODE_UTXO_SNAPSHOT_H