mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-09 10:43:19 -05:00
[validation] Remove fMissingInputs from AcceptToMemoryPool()
Handle this failure in the same way as all other failures: call Invalid() with the reasons for the failure.
This commit is contained in:
parent
c428622a5b
commit
3004d5a12d
11 changed files with 27 additions and 46 deletions
|
@ -39,7 +39,7 @@ static void AssembleBlock(benchmark::State& state)
|
||||||
|
|
||||||
for (const auto& txr : txs) {
|
for (const auto& txr : txs) {
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
bool ret{::AcceptToMemoryPool(::mempool, state, txr, nullptr /* pfMissingInputs */, nullptr /* plTxnReplaced */, false /* bypass_limits */, /* nAbsurdFee */ 0)};
|
bool ret{::AcceptToMemoryPool(::mempool, state, txr, nullptr /* plTxnReplaced */, false /* bypass_limits */, /* nAbsurdFee */ 0)};
|
||||||
assert(ret);
|
assert(ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,7 @@ enum class TxValidationResult {
|
||||||
*/
|
*/
|
||||||
TX_RECENT_CONSENSUS_CHANGE,
|
TX_RECENT_CONSENSUS_CHANGE,
|
||||||
TX_NOT_STANDARD, //!< didn't meet our local policy rules
|
TX_NOT_STANDARD, //!< didn't meet our local policy rules
|
||||||
/**
|
TX_MISSING_INPUTS, //!< transaction was missing some of its inputs
|
||||||
* transaction was missing some of its inputs
|
|
||||||
* TODO: ATMP uses fMissingInputs and a valid ValidationState to indicate missing inputs.
|
|
||||||
* Change ATMP to use TX_MISSING_INPUTS.
|
|
||||||
*/
|
|
||||||
TX_MISSING_INPUTS,
|
|
||||||
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
|
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
|
||||||
/**
|
/**
|
||||||
* Transaction might be missing a witness, have a witness prior to SegWit
|
* Transaction might be missing a witness, have a witness prior to SegWit
|
||||||
|
|
|
@ -1836,14 +1836,13 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se
|
||||||
const CTransactionRef porphanTx = orphan_it->second.tx;
|
const CTransactionRef porphanTx = orphan_it->second.tx;
|
||||||
const CTransaction& orphanTx = *porphanTx;
|
const CTransaction& orphanTx = *porphanTx;
|
||||||
NodeId fromPeer = orphan_it->second.fromPeer;
|
NodeId fromPeer = orphan_it->second.fromPeer;
|
||||||
bool fMissingInputs2 = false;
|
|
||||||
// Use a new TxValidationState because orphans come from different peers (and we call
|
// Use a new TxValidationState because orphans come from different peers (and we call
|
||||||
// MaybePunishNodeForTx based on the source peer from the orphan map, not based on the peer
|
// MaybePunishNodeForTx based on the source peer from the orphan map, not based on the peer
|
||||||
// that relayed the previous transaction).
|
// that relayed the previous transaction).
|
||||||
TxValidationState orphan_state;
|
TxValidationState orphan_state;
|
||||||
|
|
||||||
if (setMisbehaving.count(fromPeer)) continue;
|
if (setMisbehaving.count(fromPeer)) continue;
|
||||||
if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
|
if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
|
||||||
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
|
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
|
||||||
RelayTransaction(orphanHash, *connman);
|
RelayTransaction(orphanHash, *connman);
|
||||||
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
|
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
|
||||||
|
@ -1856,7 +1855,7 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se
|
||||||
}
|
}
|
||||||
EraseOrphanTx(orphanHash);
|
EraseOrphanTx(orphanHash);
|
||||||
done = true;
|
done = true;
|
||||||
} else if (!fMissingInputs2) {
|
} else if (orphan_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) {
|
||||||
if (orphan_state.IsInvalid()) {
|
if (orphan_state.IsInvalid()) {
|
||||||
// Punish peer that gave us an invalid orphan tx
|
// Punish peer that gave us an invalid orphan tx
|
||||||
if (MaybePunishNodeForTx(fromPeer, orphan_state)) {
|
if (MaybePunishNodeForTx(fromPeer, orphan_state)) {
|
||||||
|
@ -2492,7 +2491,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||||
|
|
||||||
LOCK2(cs_main, g_cs_orphans);
|
LOCK2(cs_main, g_cs_orphans);
|
||||||
|
|
||||||
bool fMissingInputs = false;
|
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
|
|
||||||
CNodeState* nodestate = State(pfrom->GetId());
|
CNodeState* nodestate = State(pfrom->GetId());
|
||||||
|
@ -2503,7 +2501,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||||
std::list<CTransactionRef> lRemovedTxn;
|
std::list<CTransactionRef> lRemovedTxn;
|
||||||
|
|
||||||
if (!AlreadyHave(inv) &&
|
if (!AlreadyHave(inv) &&
|
||||||
AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
|
AcceptToMemoryPool(mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
|
||||||
mempool.check(&::ChainstateActive().CoinsTip());
|
mempool.check(&::ChainstateActive().CoinsTip());
|
||||||
RelayTransaction(tx.GetHash(), *connman);
|
RelayTransaction(tx.GetHash(), *connman);
|
||||||
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
||||||
|
@ -2525,7 +2523,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||||
// Recursively process any orphan transactions that depended on this one
|
// Recursively process any orphan transactions that depended on this one
|
||||||
ProcessOrphanTx(connman, pfrom->orphan_work_set, lRemovedTxn);
|
ProcessOrphanTx(connman, pfrom->orphan_work_set, lRemovedTxn);
|
||||||
}
|
}
|
||||||
else if (fMissingInputs)
|
else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS)
|
||||||
{
|
{
|
||||||
bool fRejectedParents = false; // It may be the case that the orphans parents have all been rejected
|
bool fRejectedParents = false; // It may be the case that the orphans parents have all been rejected
|
||||||
for (const CTxIn& txin : tx.vin) {
|
for (const CTxIn& txin : tx.vin) {
|
||||||
|
|
|
@ -37,17 +37,15 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err
|
||||||
if (!mempool.exists(hashTx)) {
|
if (!mempool.exists(hashTx)) {
|
||||||
// Transaction is not already in the mempool. Submit it.
|
// Transaction is not already in the mempool. Submit it.
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
bool fMissingInputs;
|
if (!AcceptToMemoryPool(mempool, state, std::move(tx),
|
||||||
if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
|
|
||||||
nullptr /* plTxnReplaced */, false /* bypass_limits */, max_tx_fee)) {
|
nullptr /* plTxnReplaced */, false /* bypass_limits */, max_tx_fee)) {
|
||||||
|
err_string = FormatStateMessage(state);
|
||||||
if (state.IsInvalid()) {
|
if (state.IsInvalid()) {
|
||||||
err_string = FormatStateMessage(state);
|
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
|
||||||
return TransactionError::MEMPOOL_REJECTED;
|
|
||||||
} else {
|
|
||||||
if (fMissingInputs) {
|
|
||||||
return TransactionError::MISSING_INPUTS;
|
return TransactionError::MISSING_INPUTS;
|
||||||
}
|
}
|
||||||
err_string = FormatStateMessage(state);
|
return TransactionError::MEMPOOL_REJECTED;
|
||||||
|
} else {
|
||||||
return TransactionError::MEMPOOL_ERROR;
|
return TransactionError::MEMPOOL_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -894,19 +894,20 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
|
||||||
result_0.pushKV("txid", tx_hash.GetHex());
|
result_0.pushKV("txid", tx_hash.GetHex());
|
||||||
|
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
bool missing_inputs;
|
|
||||||
bool test_accept_res;
|
bool test_accept_res;
|
||||||
{
|
{
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx), &missing_inputs,
|
test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx),
|
||||||
nullptr /* plTxnReplaced */, false /* bypass_limits */, max_raw_tx_fee, /* test_accept */ true);
|
nullptr /* plTxnReplaced */, false /* bypass_limits */, max_raw_tx_fee, /* test_accept */ true);
|
||||||
}
|
}
|
||||||
result_0.pushKV("allowed", test_accept_res);
|
result_0.pushKV("allowed", test_accept_res);
|
||||||
if (!test_accept_res) {
|
if (!test_accept_res) {
|
||||||
if (state.IsInvalid()) {
|
if (state.IsInvalid()) {
|
||||||
result_0.pushKV("reject-reason", strprintf("%s", state.GetRejectReason()));
|
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
|
||||||
} else if (missing_inputs) {
|
result_0.pushKV("reject-reason", "missing-inputs");
|
||||||
result_0.pushKV("reject-reason", "missing-inputs");
|
} else {
|
||||||
|
result_0.pushKV("reject-reason", strprintf("%s", state.GetRejectReason()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result_0.pushKV("reject-reason", state.GetRejectReason());
|
result_0.pushKV("reject-reason", state.GetRejectReason());
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
|
||||||
BOOST_CHECK_EQUAL(
|
BOOST_CHECK_EQUAL(
|
||||||
false,
|
false,
|
||||||
AcceptToMemoryPool(mempool, state, MakeTransactionRef(coinbaseTx),
|
AcceptToMemoryPool(mempool, state, MakeTransactionRef(coinbaseTx),
|
||||||
nullptr /* pfMissingInputs */,
|
|
||||||
nullptr /* plTxnReplaced */,
|
nullptr /* plTxnReplaced */,
|
||||||
true /* bypass_limits */,
|
true /* bypass_limits */,
|
||||||
0 /* nAbsurdFee */));
|
0 /* nAbsurdFee */));
|
||||||
|
|
|
@ -23,7 +23,7 @@ ToMemPool(const CMutableTransaction& tx)
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
|
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), nullptr /* pfMissingInputs */,
|
return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx),
|
||||||
nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */);
|
nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -285,7 +285,6 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
|
||||||
::mempool,
|
::mempool,
|
||||||
state,
|
state,
|
||||||
tx,
|
tx,
|
||||||
/* pfMissingInputs */ &ignored,
|
|
||||||
&plTxnReplaced,
|
&plTxnReplaced,
|
||||||
/* bypass_limits */ false,
|
/* bypass_limits */ false,
|
||||||
/* nAbsurdFee */ 0));
|
/* nAbsurdFee */ 0));
|
||||||
|
|
|
@ -365,7 +365,7 @@ static void UpdateMempoolForReorg(DisconnectedBlockTransactions& disconnectpool,
|
||||||
// ignore validation errors in resurrected transactions
|
// ignore validation errors in resurrected transactions
|
||||||
TxValidationState stateDummy;
|
TxValidationState stateDummy;
|
||||||
if (!fAddToMempool || (*it)->IsCoinBase() ||
|
if (!fAddToMempool || (*it)->IsCoinBase() ||
|
||||||
!AcceptToMemoryPool(mempool, stateDummy, *it, nullptr /* pfMissingInputs */,
|
!AcceptToMemoryPool(mempool, stateDummy, *it,
|
||||||
nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */)) {
|
nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */)) {
|
||||||
// If the transaction doesn't make it in to the mempool, remove any
|
// If the transaction doesn't make it in to the mempool, remove any
|
||||||
// transactions that depend on it (which would now be orphans).
|
// transactions that depend on it (which would now be orphans).
|
||||||
|
@ -442,7 +442,6 @@ public:
|
||||||
struct ATMPArgs {
|
struct ATMPArgs {
|
||||||
const CChainParams& m_chainparams;
|
const CChainParams& m_chainparams;
|
||||||
TxValidationState &m_state;
|
TxValidationState &m_state;
|
||||||
bool* m_missing_inputs;
|
|
||||||
const int64_t m_accept_time;
|
const int64_t m_accept_time;
|
||||||
std::list<CTransactionRef>* m_replaced_transactions;
|
std::list<CTransactionRef>* m_replaced_transactions;
|
||||||
const bool m_bypass_limits;
|
const bool m_bypass_limits;
|
||||||
|
@ -538,7 +537,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
|
|
||||||
// Copy/alias what we need out of args
|
// Copy/alias what we need out of args
|
||||||
TxValidationState &state = args.m_state;
|
TxValidationState &state = args.m_state;
|
||||||
bool* pfMissingInputs = args.m_missing_inputs;
|
|
||||||
const int64_t nAcceptTime = args.m_accept_time;
|
const int64_t nAcceptTime = args.m_accept_time;
|
||||||
const bool bypass_limits = args.m_bypass_limits;
|
const bool bypass_limits = args.m_bypass_limits;
|
||||||
const CAmount& nAbsurdFee = args.m_absurd_fee;
|
const CAmount& nAbsurdFee = args.m_absurd_fee;
|
||||||
|
@ -554,10 +552,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
CAmount& nConflictingFees = ws.m_conflicting_fees;
|
CAmount& nConflictingFees = ws.m_conflicting_fees;
|
||||||
size_t& nConflictingSize = ws.m_conflicting_size;
|
size_t& nConflictingSize = ws.m_conflicting_size;
|
||||||
|
|
||||||
if (pfMissingInputs) {
|
|
||||||
*pfMissingInputs = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CheckTransaction(tx, state))
|
if (!CheckTransaction(tx, state))
|
||||||
return false; // state filled in by CheckTransaction
|
return false; // state filled in by CheckTransaction
|
||||||
|
|
||||||
|
@ -647,10 +641,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise assume this might be an orphan tx for which we just haven't seen parents yet
|
// Otherwise assume this might be an orphan tx for which we just haven't seen parents yet
|
||||||
if (pfMissingInputs) {
|
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent");
|
||||||
*pfMissingInputs = true;
|
|
||||||
}
|
|
||||||
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1047,11 +1038,11 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs
|
||||||
|
|
||||||
/** (try to) add transaction to memory pool with a specified acceptance time **/
|
/** (try to) add transaction to memory pool with a specified acceptance time **/
|
||||||
static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
||||||
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
||||||
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||||
{
|
{
|
||||||
std::vector<COutPoint> coins_to_uncache;
|
std::vector<COutPoint> coins_to_uncache;
|
||||||
MemPoolAccept::ATMPArgs args { chainparams, state, pfMissingInputs, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept };
|
MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept };
|
||||||
bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
|
bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
// Remove coins that were not present in the coins cache before calling ATMPW;
|
// Remove coins that were not present in the coins cache before calling ATMPW;
|
||||||
|
@ -1069,11 +1060,11 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
||||||
bool* pfMissingInputs, std::list<CTransactionRef>* plTxnReplaced,
|
std::list<CTransactionRef>* plTxnReplaced,
|
||||||
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept)
|
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept)
|
||||||
{
|
{
|
||||||
const CChainParams& chainparams = Params();
|
const CChainParams& chainparams = Params();
|
||||||
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, pfMissingInputs, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee, test_accept);
|
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee, test_accept);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4969,7 +4960,7 @@ bool LoadMempool(CTxMemPool& pool)
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
if (nTime + nExpiryTimeout > nNow) {
|
if (nTime + nExpiryTimeout > nNow) {
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, nullptr /* pfMissingInputs */, nTime,
|
AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, nTime,
|
||||||
nullptr /* plTxnReplaced */, false /* bypass_limits */, 0 /* nAbsurdFee */,
|
nullptr /* plTxnReplaced */, false /* bypass_limits */, 0 /* nAbsurdFee */,
|
||||||
false /* test_accept */);
|
false /* test_accept */);
|
||||||
if (state.IsValid()) {
|
if (state.IsValid()) {
|
||||||
|
|
|
@ -273,7 +273,7 @@ void PruneBlockFilesManual(int nManualPruneHeight);
|
||||||
/** (try to) add transaction to memory pool
|
/** (try to) add transaction to memory pool
|
||||||
* plTxnReplaced will be appended to with all transactions replaced from mempool **/
|
* plTxnReplaced will be appended to with all transactions replaced from mempool **/
|
||||||
bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
||||||
bool* pfMissingInputs, std::list<CTransactionRef>* plTxnReplaced,
|
std::list<CTransactionRef>* plTxnReplaced,
|
||||||
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||||
|
|
||||||
/** Get the BIP9 state for a given deployment at the current tip. */
|
/** Get the BIP9 state for a given deployment at the current tip. */
|
||||||
|
|
|
@ -209,7 +209,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx)
|
rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx)
|
||||||
|
|
||||||
# This will raise an exception since there are missing inputs
|
# This will raise an exception since there are missing inputs
|
||||||
assert_raises_rpc_error(-25, "Missing inputs", self.nodes[2].sendrawtransaction, rawtx['hex'])
|
assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex'])
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
# getrawtransaction with block hash #
|
# getrawtransaction with block hash #
|
||||||
|
|
Loading…
Add table
Reference in a new issue