diff --git a/doc/tor.md b/doc/tor.md index c66725767534..2148bc5e3857 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -8,6 +8,16 @@ may not. In particular, the Tor Browser Bundle defaults to listening on port 915 See [Tor Project FAQ:TBBSocksPort](https://www.torproject.org/docs/faq.html.en#TBBSocksPort) for how to properly configure Tor. +## How to see information about your Tor configuration via Dash Core + +There are several ways to see your local onion address in Dash Core: +- in the debug log (grep for "tor:" or "AddLocal") +- in the output of RPC `getnetworkinfo` in the "localaddresses" section +- in the output of the CLI `-netinfo` peer connections dashboard + +You may set the `-debug=tor` config logging option to have additional +information in the debug log about your Tor configuration. + ## 1. Run Dash Core behind a Tor proxy @@ -160,14 +170,19 @@ The directory can be different of course, but virtual port numbers should be equ your dashd's P2P listen port (9999 by default), and target addresses and ports should be equal to binding address and port for inbound Tor connections (127.0.0.1:9996 by default). - -externalip=X You can tell Dash Core about its publicly reachable address using - this option, and this can be a .onion address. Given the above - configuration, you can find your .onion address in + -externalip=X You can tell Dash Core about its publicly reachable addresses using + this option, and this can be an onion address. Given the above + configuration, you can find your onion address in /var/lib/tor/dashcore-service/hostname. For connections coming from unroutable addresses (such as 127.0.0.1, where the - Tor proxy typically runs), .onion addresses are given + Tor proxy typically runs), onion addresses are given preference for your node to advertise itself with. + You can set multiple local addresses with -externalip. The + one that will be rumoured to a particular peer is the most + compatible one and also using heuristics, e.g. the address + with the most incoming connections, etc. + -listen You'll need to enable listening for incoming connections, as this is off by default behind a proxy. @@ -180,7 +195,7 @@ should be equal to binding address and port for inbound Tor connections (127.0.0 In a typical situation, where you're only reachable via Tor, this should suffice: - ./dashd -proxy=127.0.0.1:9050 -externalip=ssapp53tmftyjmjb.onion -listen + ./dashd -proxy=127.0.0.1:9050 -externalip=7zvj7a2imdgkdbg4f2dryd5rgtrn7upivr5eeij4cicjh65pooxeshid.onion -listen (obviously, replace the .onion address with your own). It should be noted that you still listen on all devices and another node could establish a clearnet connection, when knowing @@ -198,7 +213,7 @@ and open port 9999 on your firewall (or use port mapping, i.e., `-upnp` or `-nat If you only want to use Tor to reach .onion addresses, but not use it as a proxy for normal IPv4/IPv6 communication, use: - ./dashd -onion=127.0.0.1:9050 -externalip=ssapp53tmftyjmjb.onion -discover + ./dashd -onion=127.0.0.1:9050 -externalip=7zvj7a2imdgkdbg4f2dryd5rgtrn7upivr5eeij4cicjh65pooxeshid.onion -discover ## 3.1. List of known Dash Core Tor relays diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index 808486412bc6..79c653d6fce3 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -15,24 +15,49 @@ #include -static void BlockToJsonVerbose(benchmark::Bench& bench) { +namespace { + +struct TestBlockAndIndex { TestingSetup test_setup{}; + CBlock block{}; + uint256 blockHash{}; + CBlockIndex blockindex{}; + + TestBlockAndIndex() + { + CDataStream stream(benchmark::data::block813851, SER_NETWORK, PROTOCOL_VERSION); + char a = '\0'; + stream.write(&a, 1); // Prevent compaction - CDataStream stream(benchmark::data::block813851, SER_NETWORK, PROTOCOL_VERSION); - char a = '\0'; - stream.write(&a, 1); // Prevent compaction + stream >> block; - CBlock block; - stream >> block; + blockHash = block.GetHash(); + blockindex.phashBlock = &blockHash; + blockindex.nBits = 403014710; + } +}; - CBlockIndex blockindex; - const uint256 blockHash = block.GetHash(); - blockindex.phashBlock = &blockHash; - blockindex.nBits = 403014710; +} // namespace +static void BlockToJsonVerbose(benchmark::Bench& bench) +{ + TestBlockAndIndex data; bench.run([&] { - (void)blockToJSON(block, &blockindex, &blockindex, *llmq::chainLocksHandler, *llmq::quorumInstantSendManager, /*verbose*/ true); + auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, *llmq::chainLocksHandler, *llmq::quorumInstantSendManager, /*verbose*/ true); + ankerl::nanobench::doNotOptimizeAway(univalue); }); } BENCHMARK(BlockToJsonVerbose); + +static void BlockToJsonVerboseWrite(benchmark::Bench& bench) +{ + TestBlockAndIndex data; + auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, *llmq::chainLocksHandler, *llmq::quorumInstantSendManager, /*verbose*/ true); + bench.run([&] { + auto str = univalue.write(); + ankerl::nanobench::doNotOptimizeAway(str); + }); +} + +BENCHMARK(BlockToJsonVerboseWrite); diff --git a/src/core_io.h b/src/core_io.h index a2cfa1a69d25..696f69e933e2 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -24,7 +24,7 @@ struct CSpentIndexTxInfo; // core_read.cpp CScript ParseScript(const std::string& s); std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false); -[[nodiscard]] bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx); +[[nodiscard]] bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx); [[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk); bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header); /** diff --git a/src/core_read.cpp b/src/core_read.cpp index f0232b790257..6482860aa7a0 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -100,25 +100,31 @@ CScript ParseScript(const std::string& s) return result; } -bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx) +static bool DecodeTx(CMutableTransaction& tx, const std::vector& tx_data) { - if (!IsHex(strHexTx)) - return false; - - std::vector txData(ParseHex(strHexTx)); - CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + CDataStream ssData(tx_data, SER_NETWORK, PROTOCOL_VERSION); try { ssData >> tx; - if (!ssData.empty()) + if (!ssData.empty()) { return false; - } - catch (const std::exception&) { + } + } catch (const std::exception&) { return false; } return true; } +bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx) +{ + if (!IsHex(hex_tx)) { + return false; + } + + std::vector txData(ParseHex(hex_tx)); + return DecodeTx(tx, txData); +} + bool DecodeHexBlockHeader(CBlockHeader& header, const std::string& hex_header) { if (!IsHex(hex_header)) return false; diff --git a/src/net.cpp b/src/net.cpp index d1b27e273361..78630bd731f3 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -385,6 +385,11 @@ CNode* CConnman::FindNode(const CService& addr, bool fExcludeDisconnecting) return nullptr; } +bool CConnman::AlreadyConnectedToAddress(const CAddress& addr) +{ + return FindNode(addr.ToStringIPPort()); +} + bool CConnman::CheckIncomingNonce(uint64_t nonce) { LOCK(cs_vNodes); @@ -2397,11 +2402,30 @@ void CConnman::ThreadOpenConnections(const std::vector connect) if (nTries > 100) break; - CAddrInfo addr = addrman.SelectTriedCollision(); + CAddrInfo addr; - // SelectTriedCollision returns an invalid address if it is empty. - if (!fFeeler || !addr.IsValid()) { - addr = addrman.Select(fFeeler); + if (fFeeler) { + // First, try to get a tried table collision address. This returns + // an empty (invalid) address if there are no collisions to try. + addr = addrman.SelectTriedCollision(); + + if (!addr.IsValid()) { + // No tried table collisions. Select a new table address + // for our feeler. + addr = addrman.Select(true); + } else if (AlreadyConnectedToAddress(addr)) { + // If test-before-evict logic would have us connect to a + // peer that we're already connected to, just mark that + // address as Good(). We won't be able to initiate the + // connection anyway, so this avoids inadvertently evicting + // a currently-connected peer. + addrman.Good(addr); + // Select a new table address for our feeler instead. + addr = addrman.Select(true); + } + } else { + // Not a feeler + addr = addrman.Select(); } auto dmn = mnList.GetMNByService(addr); @@ -2768,7 +2792,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai if (!pszDest) { // banned, discouraged or exact match? - if ((m_banman && (m_banman->IsDiscouraged(addrConnect) || m_banman->IsBanned(addrConnect))) || FindNode(addrConnect.ToStringIPPort())) + if ((m_banman && (m_banman->IsDiscouraged(addrConnect) || m_banman->IsBanned(addrConnect))) || AlreadyConnectedToAddress(addrConnect)) return; // local and not a connection to itself? bool fAllowLocal = Params().AllowMultiplePorts() && addrConnect.GetPort() != GetListenPort(); diff --git a/src/net.h b/src/net.h index e1a2fcea8021..b013ba183f71 100644 --- a/src/net.h +++ b/src/net.h @@ -625,6 +625,12 @@ friend class CNode; CNode* FindNode(const std::string& addrName, bool fExcludeDisconnecting = true); CNode* FindNode(const CService& addr, bool fExcludeDisconnecting = true); + /** + * Determine whether we're already connected to a given address, in order to + * avoid initiating duplicate connections. + */ + bool AlreadyConnectedToAddress(const CAddress& addr); + bool AttemptToEvictConnection(); CNode* ConnectNode(CAddress addrConnect, const char *pszDest = nullptr, bool fCountFailure = false, ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY); void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 98cb54d55aba..f1af6ca544fb 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3017,14 +3017,8 @@ void PeerManagerImpl::ProcessMessage( // empty and no one will know who we are, so these mechanisms are // important to help us connect to the network. // - // We also update the addrman to record connection success for - // these peers (which include OUTBOUND_FULL_RELAY and FEELER - // connections) so that addrman will have an up-to-date notion of - // which peers are online and available. - // - // We skip these operations for BLOCK_RELAY peers to avoid - // potentially leaking information about our BLOCK_RELAY - // connections via the addrman or address relay. + // We skip this for BLOCK_RELAY peers to avoid potentially leaking + // information about our BLOCK_RELAY connections via address relay. if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices()); @@ -3043,11 +3037,24 @@ void PeerManagerImpl::ProcessMessage( // Get recent addresses m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR)); pfrom.fGetAddr = true; + } - // Moves address from New to Tried table in Addrman, resolves - // tried-table collisions, etc. + if (!pfrom.IsInboundConn()) { + // For non-inbound connections, we update the addrman to record + // connection success so that addrman will have an up-to-date + // notion of which peers are online and available. + // + // While we strive to not leak information about block-relay-only + // connections via the addrman, not moving an address to the tried + // table is also potentially detrimental because new-table entries + // are subject to eviction in the event of addrman collisions. We + // mitigate the information-leak by never calling + // CAddrMan::Connected() on block-relay-only peers; see + // FinalizeNode(). + // + // This moves an address from New to Tried table in Addrman, + // resolves tried-table collisions, etc. m_addrman.Good(pfrom.addr); - } std::string remoteAddr; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 86865c274f41..1e1261da156b 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -214,20 +214,9 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, llmq::CChainLocksHandler& clhandler, llmq::CInstantSendManager& isman, bool txDetails) { - // Serialize passed information without accessing chain state of the active chain! - AssertLockNotHeld(cs_main); // For performance reasons + UniValue result = blockheaderToJSON(tip, blockindex, clhandler, isman); - UniValue result(UniValue::VOBJ); - result.pushKV("hash", blockindex->GetBlockHash().GetHex()); - const CBlockIndex* pnext; - int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); - result.pushKV("confirmations", confirmations); result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION)); - result.pushKV("height", blockindex->nHeight); - result.pushKV("version", block.nVersion); - result.pushKV("versionHex", strprintf("%08x", block.nVersion)); - result.pushKV("merkleroot", block.hashMerkleRoot.GetHex()); - bool chainLock = clhandler.HasChainLock(blockindex->nHeight, blockindex->GetBlockHash()); UniValue txs(UniValue::VARR); for(const auto& tx : block.vtx) { @@ -236,7 +225,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn UniValue objTx(UniValue::VOBJ); TxToUniv(*tx, uint256(), objTx, true); bool fLocked = isman.IsLocked(tx->GetHash()); - objTx.pushKV("instantlock", fLocked || chainLock); + objTx.pushKV("instantlock", fLocked || result["chainlock"].get_bool()); objTx.pushKV("instantlock_internal", fLocked); txs.push_back(objTx); } @@ -249,20 +238,6 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn result.pushKV("cbTx", opt_cbTx->ToJson()); } } - result.pushKV("time", block.GetBlockTime()); - result.pushKV("mediantime", (int64_t)blockindex->GetMedianTimePast()); - result.pushKV("nonce", (uint64_t)block.nNonce); - result.pushKV("bits", strprintf("%08x", block.nBits)); - result.pushKV("difficulty", GetDifficulty(blockindex)); - result.pushKV("chainwork", blockindex->nChainWork.GetHex()); - result.pushKV("nTx", (uint64_t)blockindex->nTx); - - if (blockindex->pprev) - result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); - if (pnext) - result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); - - result.pushKV("chainlock", chainLock); return result; } @@ -1168,11 +1143,20 @@ static UniValue getblock(const JSONRPCRequest& request) { {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"}, - {RPCResult::Type::NUM, "size", "The block size"}, {RPCResult::Type::NUM, "height", "The block height or index"}, {RPCResult::Type::NUM, "version", "The block version"}, {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"}, {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"}, + {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM, "nonce", "The nonce"}, + {RPCResult::Type::STR_HEX, "bits", "The bits"}, + {RPCResult::Type::NUM, "difficulty", "The difficulty"}, + {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the current chain"}, + {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, + {RPCResult::Type::STR_HEX, "previousblockhash", /* optional */ true, "The hash of the previous block (if available)"}, + {RPCResult::Type::STR_HEX, "nextblockhash", /* optional */ true, "The hash of the next block (if available)"}, + {RPCResult::Type::NUM, "size", "The block size"}, {RPCResult::Type::ARR, "tx", "The transaction ids", {{RPCResult::Type::STR_HEX, "", "The transaction id"}}}, {RPCResult::Type::OBJ, "cbTx", "The coinbase special transaction", @@ -1182,15 +1166,6 @@ static UniValue getblock(const JSONRPCRequest& request) {RPCResult::Type::STR_HEX, "merkleRootMNList", "The merkle root of the masternode list"}, {RPCResult::Type::STR_HEX, "merkleRootQuorums", "The merkle root of the quorum list"}, }}, - {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, - {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, - {RPCResult::Type::NUM, "nonce", "The nonce"}, - {RPCResult::Type::STR_HEX, "bits", "The bits"}, - {RPCResult::Type::NUM, "difficulty", "The difficulty"}, - {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)"}, - {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, - {RPCResult::Type::STR_HEX, "previousblockhash", "The hash of the previous block"}, - {RPCResult::Type::STR_HEX, "nextblockhash", "The hash of the next block"}, }}, RPCResult{"for verbosity = 2", RPCResult::Type::OBJ, "", "", diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 459d01ccf365..94e0bb3e8e86 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -188,21 +188,60 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK); std::string pszDest; - std::unique_ptr pnode1 = std::make_unique(id++, NODE_NETWORK, hSocket, addr, 0, 0, CAddress(), pszDest, ConnectionType::OUTBOUND_FULL_RELAY); + std::unique_ptr pnode1 = std::make_unique( + id++, NODE_NETWORK, hSocket, addr, + /* nKeyedNetGroupIn = */ 0, + /* nLocalHostNonceIn = */ 0, + CAddress(), pszDest, ConnectionType::OUTBOUND_FULL_RELAY); BOOST_CHECK(pnode1->IsFullOutboundConn() == true); BOOST_CHECK(pnode1->IsManualConn() == false); BOOST_CHECK(pnode1->IsBlockOnlyConn() == false); BOOST_CHECK(pnode1->IsFeelerConn() == false); BOOST_CHECK(pnode1->IsAddrFetchConn() == false); BOOST_CHECK(pnode1->IsInboundConn() == false); - - std::unique_ptr pnode2 = std::make_unique(id++, NODE_NETWORK, hSocket, addr, 1, 1, CAddress(), pszDest, ConnectionType::INBOUND); + BOOST_CHECK_EQUAL(pnode1->ConnectedThroughNetwork(), Network::NET_IPV4); + + std::unique_ptr pnode2 = std::make_unique( + id++, NODE_NETWORK, hSocket, addr, + /* nKeyedNetGroupIn = */ 1, + /* nLocalHostNonceIn = */ 1, + CAddress(), pszDest, ConnectionType::INBOUND, + /* inbound_onion = */ false); BOOST_CHECK(pnode2->IsFullOutboundConn() == false); BOOST_CHECK(pnode2->IsManualConn() == false); BOOST_CHECK(pnode2->IsBlockOnlyConn() == false); BOOST_CHECK(pnode2->IsFeelerConn() == false); BOOST_CHECK(pnode2->IsAddrFetchConn() == false); BOOST_CHECK(pnode2->IsInboundConn() == true); + BOOST_CHECK_EQUAL(pnode2->ConnectedThroughNetwork(), Network::NET_IPV4); + + std::unique_ptr pnode3 = std::make_unique( + id++, NODE_NETWORK, hSocket, addr, + /* nKeyedNetGroupIn = */ 0, + /* nLocalHostNonceIn = */ 0, + CAddress(), pszDest, ConnectionType::OUTBOUND_FULL_RELAY, + /* inbound_onion = */ true); + BOOST_CHECK(pnode3->IsFullOutboundConn() == true); + BOOST_CHECK(pnode3->IsManualConn() == false); + BOOST_CHECK(pnode3->IsBlockOnlyConn() == false); + BOOST_CHECK(pnode3->IsFeelerConn() == false); + BOOST_CHECK(pnode3->IsAddrFetchConn() == false); + BOOST_CHECK(pnode3->IsInboundConn() == false); + BOOST_CHECK_EQUAL(pnode3->ConnectedThroughNetwork(), Network::NET_IPV4); + + std::unique_ptr pnode4 = std::make_unique( + id++, NODE_NETWORK, hSocket, addr, + /* nKeyedNetGroupIn = */ 1, + /* nLocalHostNonceIn = */ 1, + CAddress(), pszDest, ConnectionType::INBOUND, + /* inbound_onion = */ true); + BOOST_CHECK(pnode4->IsFullOutboundConn() == false); + BOOST_CHECK(pnode4->IsManualConn() == false); + BOOST_CHECK(pnode4->IsBlockOnlyConn() == false); + BOOST_CHECK(pnode4->IsFeelerConn() == false); + BOOST_CHECK(pnode4->IsAddrFetchConn() == false); + BOOST_CHECK(pnode4->IsInboundConn() == true); + BOOST_CHECK_EQUAL(pnode4->ConnectedThroughNetwork(), Network::NET_ONION); } BOOST_AUTO_TEST_CASE(PoissonNextSend) diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 03b7fc3fa0e3..fd3baca1b2fe 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -51,6 +51,7 @@ namespace { //! Construct wallet tx struct. WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) { + LOCK(wallet.cs_wallet); WalletTx result; bool fInputDenomFound{false}, fOutputDenomFound{false}; result.tx = wtx.tx; @@ -172,8 +173,16 @@ class WalletImpl : public Wallet { return m_wallet->SignMessage(message, pkhash, str_sig); } - bool isSpendable(const CScript& script) override { return m_wallet->IsMine(script) & ISMINE_SPENDABLE; } - bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } + bool isSpendable(const CScript& script) override + { + LOCK(m_wallet->cs_wallet); + return m_wallet->IsMine(script) & ISMINE_SPENDABLE; + } + bool isSpendable(const CTxDestination& dest) override + { + LOCK(m_wallet->cs_wallet); + return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; + } bool haveWatchOnly() override { auto spk_man = m_wallet->GetLegacyScriptPubKeyMan(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9cbe5bbcecd9..572da036d556 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -361,7 +361,7 @@ std::string COutput::ToString() const const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { - LOCK(cs_wallet); + AssertLockHeld(cs_wallet); std::map::const_iterator it = mapWallet.find(hash); if (it == mapWallet.end()) return nullptr; @@ -1326,15 +1326,13 @@ void CWallet::BlockUntilSyncedToCurrentChain() const { isminetype CWallet::IsMine(const CTxIn &txin) const { + AssertLockHeld(cs_wallet); + std::map::const_iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) { - LOCK(cs_wallet); - std::map::const_iterator mi = mapWallet.find(txin.prevout.hash); - if (mi != mapWallet.end()) - { - const CWalletTx& prev = (*mi).second; - if (txin.prevout.n < prev.tx->vout.size()) - return IsMine(prev.tx->vout[txin.prevout.n]); - } + const CWalletTx& prev = (*mi).second; + if (txin.prevout.n < prev.tx->vout.size()) + return IsMine(prev.tx->vout[txin.prevout.n]); } return ISMINE_NO; } @@ -1487,16 +1485,19 @@ bool CWallet::IsFullyMixed(const COutPoint& outpoint) const isminetype CWallet::IsMine(const CTxOut& txout) const { + AssertLockHeld(cs_wallet); return IsMine(txout.scriptPubKey); } isminetype CWallet::IsMine(const CTxDestination& dest) const { + AssertLockHeld(cs_wallet); return IsMine(GetScriptForDestination(dest)); } isminetype CWallet::IsMine(const CScript& script) const { + AssertLockHeld(cs_wallet); isminetype result = ISMINE_NO; for (const auto& spk_man_pair : m_spk_managers) { result = std::max(result, spk_man_pair.second->IsMine(script)); @@ -1508,6 +1509,7 @@ CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) cons { if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); + LOCK(cs_wallet); return ((IsMine(txout) & filter) ? txout.nValue : 0); } @@ -1525,13 +1527,12 @@ bool CWallet::IsChange(const CScript& script) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). + AssertLockHeld(cs_wallet); if (IsMine(script)) { CTxDestination address; if (!ExtractDestination(script, address)) return true; - - LOCK(cs_wallet); if (!FindAddressBookEntry(address)) { return true; } @@ -1541,6 +1542,7 @@ bool CWallet::IsChange(const CScript& script) const CAmount CWallet::GetChange(const CTxOut& txout) const { + AssertLockHeld(cs_wallet); if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); return (IsChange(txout) ? txout.nValue : 0); @@ -1548,6 +1550,7 @@ CAmount CWallet::GetChange(const CTxOut& txout) const bool CWallet::IsMine(const CTransaction& tx) const { + AssertLockHeld(cs_wallet); for (const CTxOut& txout : tx.vout) if (IsMine(txout)) return true; @@ -1606,6 +1609,7 @@ CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) c CAmount CWallet::GetChange(const CTransaction& tx) const { + LOCK(cs_wallet); CAmount nChange = 0; for (const CTxOut& txout : tx.vout) { @@ -1846,6 +1850,7 @@ void CWalletTx::GetAmounts(std::list& listReceived, nFee = nDebit - nValueOut; } + LOCK(pwallet->cs_wallet); // Sent/received. for (unsigned int i = 0; i < tx->vout.size(); ++i) { @@ -2340,6 +2345,7 @@ bool CWalletTx::IsTrusted(std::set& trusted_parents) const if (!InMempool()) return false; // Trusted if all inputs are from us and are in the mempool: + LOCK(pwallet->cs_wallet); for (const CTxIn& txin : tx->vin) { // Transactions not sent by us: not trusted @@ -3957,6 +3963,7 @@ DBErrors CWallet::ZapSelectTx(std::vector& vHashIn, std::vector::iterator mi = m_address_book.find(address); @@ -3964,8 +3971,9 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add m_address_book[address].SetLabel(strName); if (!strPurpose.empty()) /* update purpose only if requested */ m_address_book[address].purpose = strPurpose; + is_mine = IsMine(address) != ISMINE_NO; } - NotifyAddressBookChanged(this, address, strName, IsMine(address) != ISMINE_NO, + NotifyAddressBookChanged(this, address, strName, is_mine, strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) ); if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose)) return false; @@ -3980,18 +3988,17 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s bool CWallet::DelAddressBook(const CTxDestination& address) { - // If we want to delete receiving addresses, we need to take care that DestData "used" (and possibly newer DestData) gets preserved (and the "deleted" address transformed into a change entry instead of actually being deleted) - // NOTE: This isn't a problem for sending addresses because they never have any DestData yet! - // When adding new DestData, it should be considered here whether to retain or delete it (or move it?). - if (IsMine(address)) { - WalletLogPrintf("%s called with IsMine address, NOT SUPPORTED. Please report this bug! %s\n", __func__, PACKAGE_BUGREPORT); - return false; - } - + bool is_mine; WalletBatch batch(GetDatabase()); { LOCK(cs_wallet); - + // If we want to delete receiving addresses, we need to take care that DestData "used" (and possibly newer DestData) gets preserved (and the "deleted" address transformed into a change entry instead of actually being deleted) + // NOTE: This isn't a problem for sending addresses because they never have any DestData yet! + // When adding new DestData, it should be considered here whether to retain or delete it (or move it?). + if (IsMine(address)) { + WalletLogPrintf("%s called with IsMine address, NOT SUPPORTED. Please report this bug! %s\n", __func__, PACKAGE_BUGREPORT); + return false; + } // Delete destdata tuples associated with address std::string strAddress = EncodeDestination(address); for (const std::pair &item : m_address_book[address].destdata) @@ -3999,9 +4006,10 @@ bool CWallet::DelAddressBook(const CTxDestination& address) batch.EraseDestData(strAddress, item.first); } m_address_book.erase(address); + is_mine = IsMine(address) != ISMINE_NO; } - NotifyAddressBookChanged(this, address, "", IsMine(address) != ISMINE_NO, "", CT_DELETED); + NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED); batch.ErasePurpose(EncodeDestination(address)); return batch.EraseName(EncodeDestination(address)); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6ab17e321900..10da35ef744c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -885,7 +885,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati /** Interface for accessing CoinJoin state. */ interfaces::CoinJoin::Loader& coinjoin_loader() { assert(m_coinjoin_loader); return *m_coinjoin_loader; } - const CWalletTx* GetWalletTx(const uint256& hash) const; + const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } @@ -1146,20 +1146,20 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati bool GetNewDestination(const std::string label, CTxDestination& dest, std::string& error); bool GetNewChangeDestination(CTxDestination& dest, std::string& error); - isminetype IsMine(const CTxDestination& dest) const; - isminetype IsMine(const CScript& script) const; - isminetype IsMine(const CTxIn& txin) const; + isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + isminetype IsMine(const CTxIn& txin) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Returns amount of debit if the input matches the * filter, otherwise returns 0 */ CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; - isminetype IsMine(const CTxOut& txout) const; + isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const; - bool IsChange(const CTxOut& txout) const; - bool IsChange(const CScript& script) const; - CAmount GetChange(const CTxOut& txout) const; - bool IsMine(const CTransaction& tx) const; + bool IsChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsChange(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + CAmount GetChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index 91e05885a102..390a59a08229 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test BIP68 implementation.""" -from test_framework.blocktools import create_block, create_coinbase +from test_framework.blocktools import create_block, NORMAL_GBT_REQUEST_PARAMS from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -270,6 +270,8 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): # Advance the time on the node so that we can test timelocks self.nodes[0].setmocktime(cur_time+600) + # Save block template now to use for the reorg later + tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) self.nodes[0].generate(1) assert tx2.hash not in self.nodes[0].getrawmempool() @@ -313,16 +315,15 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): # diagram above). # This would cause tx2 to be added back to the mempool, which in turn causes # tx3 to be removed. - tip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount()-1), 16) - height = self.nodes[0].getblockcount() for i in range(2): - block = create_block(tip, create_coinbase(height), cur_time) - block.nVersion = 3 + block = create_block(tmpl=tmpl, ntime=cur_time) block.rehash() block.solve() tip = block.sha256 - height += 1 assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(ToHex(block))) + tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) + tmpl['previousblockhash'] = '%x' % tip + tmpl['transactions'] = [] cur_time += 1 mempool = self.nodes[0].getrawmempool() @@ -370,9 +371,7 @@ def test_bip68_not_consensus(self): assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx3)) # make a block that violates bip68; ensure that the tip updates - tip = int(self.nodes[0].getbestblockhash(), 16) - block = create_block(tip, create_coinbase(self.nodes[0].getblockcount()+1), self.mocktime + 600) - block.nVersion = 3 + block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) block.vtx.extend([tx1, tx2, tx3]) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index fb849addf9ba..f5e9bd5c0411 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1273,7 +1273,7 @@ def run_test(self): blocks = [] spend = out[32] for i in range(89, LARGE_REORG_SIZE + 89): - b = self.next_block(i, spend, version=4) + b = self.next_block(i, spend) tx = CTransaction() script_length = MAX_BLOCK_SIZE - len(b.serialize()) - 69 script_output = CScript([b'\x00' * script_length]) @@ -1292,18 +1292,18 @@ def run_test(self): self.move_tip(88) blocks2 = [] for i in range(89, LARGE_REORG_SIZE + 89): - blocks2.append(self.next_block("alt" + str(i), version=4)) + blocks2.append(self.next_block("alt" + str(i))) self.send_blocks(blocks2, False, force_send=True) # extend alt chain to trigger re-org - block = self.next_block("alt" + str(chain1_tip + 1), version=4) + block = self.next_block("alt" + str(chain1_tip + 1)) self.send_blocks([block], True, timeout=2440) # ... and re-org back to the first chain self.move_tip(chain1_tip) - block = self.next_block(chain1_tip + 1, version=4) + block = self.next_block(chain1_tip + 1) self.send_blocks([block], False, force_send=True) - block = self.next_block(chain1_tip + 2, version=4) + block = self.next_block(chain1_tip + 2) self.send_blocks([block], True, timeout=2440) self.log.info("Reject a block with an invalid block header version") @@ -1311,7 +1311,7 @@ def run_test(self): self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)', reconnect=True) self.move_tip(chain1_tip + 2) - b_cb34 = self.next_block('b_cb34', version=4) + b_cb34 = self.next_block('b_cb34') b_cb34.vtx[0].vin[0].scriptSig = b_cb34.vtx[0].vin[0].scriptSig[:-1] b_cb34.vtx[0].rehash() b_cb34.hashMerkleRoot = b_cb34.calc_merkle_root() @@ -1345,7 +1345,7 @@ def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE]) tx.rehash() return tx - def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), *, version=1): + def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), *, version=4): if self.tip is None: base_block_hash = self.genesis_hash block_time = self.mocktime + 1 diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 7eb2691d40e4..a0c4c9cf33eb 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -151,7 +151,6 @@ def set_test_params(self): # and '-dip3params=2000:2000' to create pre-dip3 blocks only self.extra_args = [[ '-whitelist=noban@127.0.0.1', - '-blockversion=4', '-maxtipage=600100', '-dip3params=2000:2000', '-par=1', # Use only one script thread to get the exact reject reason for testing ]] diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index c18e0e5070f8..6e6cdab9eb19 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -44,7 +44,6 @@ def setup_network(self): # -alertnotify and -blocknotify on node0, walletnotify on node1 self.extra_args[0].append("-alertnotify=echo > {}".format(os.path.join(self.alertnotify_dir, '%s'))) self.extra_args[0].append("-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s'))) - self.extra_args[1].append("-blockversion=211") self.extra_args[1].append("-rescan") self.extra_args[1].append("-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s')))) diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index b60545c565e1..acf3a1a87d63 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -13,7 +13,7 @@ [Policy/Consensus] Check that the new NULLDUMMY rules are enforced on the 432nd block. """ -from test_framework.blocktools import create_coinbase, create_block, create_transaction +from test_framework.blocktools import NORMAL_GBT_REQUEST_PARAMS, create_block, create_transaction from test_framework.messages import CTransaction from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework @@ -37,9 +37,10 @@ def trueDummy(tx): class NULLDUMMYTest(BitcoinTestFramework): def set_test_params(self): - self.num_nodes = 1 + # Need two nodes only so GBT doesn't complain that it's not connected + self.num_nodes = 2 self.setup_clean_chain = True - self.extra_args = [['-whitelist=127.0.0.1']] + self.extra_args = [['-whitelist=127.0.0.1']] * 2 def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -54,7 +55,6 @@ def run_test(self): coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0]) self.nodes[0].generate(427) # Block 429 self.lastblockhash = self.nodes[0].getbestblockhash() - self.tip = int("0x" + self.lastblockhash, 0) self.lastblockheight = 429 self.lastblocktime = self.mocktime + 429 @@ -88,8 +88,10 @@ def run_test(self): def block_submit(self, node, txs, accept = False): dip4_activated = self.lastblockheight + 1 >= 432 - block = create_block(self.tip, create_coinbase(self.lastblockheight + 1, dip4_activated=dip4_activated), self.lastblocktime + 1) - block.nVersion = 4 + tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) + assert_equal(tmpl['previousblockhash'], self.lastblockhash) + assert_equal(tmpl['height'], self.lastblockheight + 1) + block = create_block(tmpl=tmpl, ntime=self.lastblocktime + 1, dip4_activated=dip4_activated) for tx in txs: tx.rehash() block.vtx.append(tx) @@ -99,7 +101,6 @@ def block_submit(self, node, txs, accept = False): assert_equal(None if accept else 'block-validation-failed', node.submitblock(block.serialize().hex())) if (accept): assert_equal(node.getbestblockhash(), block.hash) - self.tip = block.sha256 self.lastblockhash = block.hash self.lastblocktime += 1 self.lastblockheight += 1 diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index d662e563c3d0..0e4f9bd2e616 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -13,6 +13,8 @@ from test_framework.blocktools import ( create_coinbase, + NORMAL_GBT_REQUEST_PARAMS, + TIME_GENESIS_BLOCK, ) from test_framework.messages import ( CBlock, @@ -26,6 +28,8 @@ assert_raises_rpc_error, ) +VERSIONBITS_TOP_BITS = 0x20000000 +VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28 def assert_template(node, block, expect, rehash=True): if rehash: @@ -45,14 +49,23 @@ def set_test_params(self): def mine_chain(self): self.log.info('Create some old blocks') - for _ in range(200): + for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 156, 156): self.bump_mocktime(156) self.nodes[0].generate(1) mining_info = self.nodes[0].getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['currentblocktx'], 0) assert_equal(mining_info['currentblocksize'], 1000) + + self.log.info('test blockversion') + self.restart_node(0, extra_args=['-mocktime={}'.format(t), '-blockversion=1337']) + self.connect_nodes(0, 1) + assert_equal(1337, self.nodes[0].getblocktemplate()['version']) + self.restart_node(0, extra_args=['-mocktime={}'.format(t)]) + self.connect_nodes(0, 1) + assert_equal(VERSIONBITS_TOP_BITS + (1 << VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT), self.nodes[0].getblocktemplate()['version']) self.restart_node(0) + # TODO: replace with connect_nodes_bi self.connect_nodes(0, 1) self.connect_nodes(1, 0) @@ -78,7 +91,7 @@ def assert_submitblock(block, result_str_1, result_str_2=None): # Mine a block to leave initial block download node.generatetoaddress(1, node.get_deterministic_priv_key().address) - tmpl = node.getblocktemplate() + tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index c88d210e2e94..4008e9c9154f 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -7,7 +7,7 @@ import random -from test_framework.blocktools import COINBASE_MATURITY, create_block, create_coinbase +from test_framework.blocktools import COINBASE_MATURITY, create_block, NORMAL_GBT_REQUEST_PARAMS from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, HeaderAndShortIDs, msg_block, msg_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, MSG_BLOCK, MSG_CMPCT_BLOCK, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ToHex, NODE_HEADERS_COMPRESSED from test_framework.p2p import p2p_lock, P2PInterface from test_framework.script import CScript, OP_TRUE, OP_DROP @@ -103,10 +103,7 @@ def skip_test_if_missing_module(self): self.skip_if_no_wallet() def build_block_on_tip(self, node): - height = node.getblockcount() - tip = node.getbestblockhash() - mtp = node.getblockheader(tip)['mediantime'] - block = create_block(int(tip, 16), create_coinbase(height + 1), mtp + 1) + block = create_block(tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) block.solve() return block @@ -718,6 +715,9 @@ def announce_cmpct_block(node, peer): assert_equal(int(node.getbestblockhash(), 16), block.sha256) def run_test(self): + # Get the nodes out of IBD + self.nodes[0].generate(1) + # Setup the p2p connections self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1)) self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK | NODE_HEADERS_COMPRESSED) diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 1df47ec541f1..7e68fe9a635c 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -4,7 +4,11 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Utilities for manipulating blocks and transactions.""" +from binascii import a2b_hex from decimal import Decimal +import io +import struct +import time import unittest from .messages import ( @@ -30,18 +34,30 @@ # Coinbase transaction outputs can only be spent after this number of new blocks (network rule) COINBASE_MATURITY = 100 -def create_block(hashprev, coinbase, ntime=None, *, version=1): +NORMAL_GBT_REQUEST_PARAMS = {"rules": []} # type: ignore[var-annotated] + +def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl=None, txlist=None, dip4_activated=False, v20_activated=False): """Create a block (with regtest difficulty).""" block = CBlock() - block.nVersion = version - if ntime is None: - import time - block.nTime = int(time.time() + 600) + if tmpl is None: + tmpl = {} + block.nVersion = version or tmpl.get('version') or 1 + block.nTime = ntime or tmpl.get('curtime') or int(time.time() + 600) + block.hashPrevBlock = hashprev or int(tmpl['previousblockhash'], 0x10) + if tmpl and not tmpl.get('bits') is None: + block.nBits = struct.unpack('>I', a2b_hex(tmpl['bits']))[0] else: - block.nTime = ntime - block.hashPrevBlock = hashprev - block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams + block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams + if coinbase is None: + coinbase = create_coinbase(height=tmpl['height'], dip4_activated=dip4_activated, v20_activated=v20_activated) block.vtx.append(coinbase) + if txlist: + for tx in txlist: + if not hasattr(tx, 'calc_sha256'): + txo = CTransaction() + txo.deserialize(io.BytesIO(tx)) + tx = txo + block.vtx.append(tx) block.hashMerkleRoot = block.calc_merkle_root() block.calc_sha256() return block