Skip to content

Commit 0e11ff0

Browse files
committed
NEW: blacklisted tx inputs are rejected
1 parent 95b8f66 commit 0e11ff0

10 files changed

+144
-9
lines changed

qa/pull-tester/rpc-tests.py

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
'pos-load.py',
180180
'pos-reindex.py',
181181
'spork-checkpoints.py',
182+
'spork-blacklist.py',
182183
]
183184
if ENABLE_ZMQ:
184185
testScripts.append('zmq_test.py')

qa/rpc-tests/spork-blacklist.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2019 The Energi Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
from test_framework.test_framework import BitcoinTestFramework
7+
from test_framework.util import *
8+
import logging
9+
10+
class SporkBlacklistTest(BitcoinTestFramework):
11+
def __init__(self):
12+
super().__init__()
13+
self.setup_clean_chain = True
14+
15+
def advance_time(self):
16+
# Spork override requires new time
17+
set_mocktime(get_mocktime() + 30)
18+
set_node_times(self.nodes, get_mocktime())
19+
20+
def run_test(self):
21+
self.nodes[0].generate(101)
22+
sync_chain(self.nodes, timeout=10)
23+
24+
addr0 = self.nodes[0].getaccountaddress("")
25+
addr1 = self.nodes[1].getaccountaddress("")
26+
27+
logging.info("Sending OK")
28+
self.nodes[0].sendtoaddress(addr1, 2)
29+
self.nodes[0].generate(1)
30+
31+
logging.info("Issuing blacklist")
32+
sync_chain(self.nodes, timeout=10)
33+
self.nodes[1].sendtoaddress(addr0, Decimal("0.5"))
34+
self.nodes[1].spork('blacklist', get_mocktime(), addr1)
35+
36+
logging.info("Failed send")
37+
assert_raises_jsonrpc(None, "Error: The transaction was rejected! Reason given: blacklisted-input",
38+
self.nodes[1].sendtoaddress, addr0, Decimal("0.7"))
39+
self.nodes[0].sendtoaddress(addr1, 2)
40+
sync_mempools(self.nodes, timeout=10)
41+
self.nodes[0].generate(1)
42+
43+
sync_chain(self.nodes, timeout=10)
44+
assert_equal(self.nodes[1].getbalance(), 4)
45+
46+
logging.info("Disabling blacklist")
47+
self.advance_time()
48+
self.nodes[1].spork('blacklist', get_mocktime()+1, addr1)
49+
self.nodes[1].sendtoaddress(addr0, 1)
50+
sync_mempools(self.nodes, timeout=10)
51+
self.nodes[0].generate(1)
52+
53+
sync_chain(self.nodes, timeout=10)
54+
assert_greater_than(3, self.nodes[1].getbalance())
55+
56+
57+
if __name__ == '__main__':
58+
SporkBlacklistTest().main()

src/protocol.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const char *MNGOVERNANCEOBJECT="govobj";
7171
const char *MNGOVERNANCEOBJECTVOTE="govobjvote";
7272
const char *MNVERIFY="mnv";
7373
const char *CHECKPOINT="chkp";
74-
const char *BLACKLIST="black";
74+
const char *BLACKLIST="bll";
7575
};
7676

7777
static const char* ppszTypeName[] =

src/rpc/misc.cpp

+17-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
#include <univalue.h>
3434

35+
extern void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
36+
3537
/**
3638
* @note Do not add or change anything in the information returned by this
3739
* method. `getinfo` exists for backwards-compatibility only. It combines
@@ -268,7 +270,7 @@ UniValue spork(const JSONRPCRequest& request)
268270
auto& bl = item.second;
269271

270272
UniValue ret_item(UniValue::VOBJ);
271-
ret_item.push_back(Pair("script", HexStr(bl.scriptPubKey)));
273+
ScriptPubKeyToJSON(bl.scriptPubKey, ret_item, true);
272274
ret_item.push_back(Pair("since", int64_t(bl.nTimeSince)));
273275
ret_item.push_back(Pair("expires_in", int64_t(bl.nTimeSigned + CSporkBlacklist::MAX_AGE) - curr_time));
274276
ret.push_back(ret_item);
@@ -355,7 +357,18 @@ UniValue spork(const JSONRPCRequest& request)
355357
}
356358

357359
auto since = request.params[1].get_int64();
358-
auto script = CScript(ParseHex(request.params[2].get_str()));
360+
CScript script;
361+
362+
auto raw_dst = request.params[2].get_str();
363+
auto addr = CBitcoinAddress(raw_dst);
364+
365+
if (addr.IsValid()) {
366+
script = GetScriptForDestination(addr.Get());
367+
} else {
368+
auto raw_script = ParseHex(raw_dst);
369+
// NOTE: another explicit c-tor will act as stream write!
370+
script = CScript(raw_script.begin(), raw_script.end());
371+
}
359372

360373
if(sporkManager.UpdateBlacklist(script, since, *g_connman)){
361374
return "success";
@@ -364,8 +377,8 @@ UniValue spork(const JSONRPCRequest& request)
364377
"spork blacklist \"script\" since\n"
365378
"\nUpdate the value of the specific spork blacklist. Requires \"-sporkkey\" to be set to sign the message.\n"
366379
"\nArguments:\n"
367-
"1. since (number, required) The time in seconds.\n"
368-
"2. \"script\" (hexstring, required) The UTXO script in hex.\n"
380+
"1. since (number, required) The time in seconds.\n"
381+
"2. \"script_or_address\" (hexstring or address, required) The UTXO script in hex or address.\n"
369382
"\nResult:\n"
370383
" result (string) \"success\" if spork value was updated or this help otherwise\n"
371384
"\nExamples:\n"

src/spork.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ void CSporkManager::ExecuteBlacklist(const CScript &scriptPubKey, int64_t nTimeS
334334

335335
auto& chainparams = Params();
336336
Params(chainparams.NetworkIDString()).SetBlacklist(scriptPubKey, nTimeSince);
337+
ProcessScriptBlacklist(scriptPubKey, nTimeSince);
337338
}
338339

339340
bool CSporkManager::UpdateBlacklist(const CScript &scriptPubKey, int64_t nTimeSince, CConnman& connman)

src/txmempool.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,28 @@ int CTxMemPool::Expire(int64_t time) {
11781178
return stage.size();
11791179
}
11801180

1181+
int CTxMemPool::CleanupBlacklisted(const CScript& scriptPubKey, const CCoinsViewCache &view) {
1182+
LOCK(cs);
1183+
1184+
setEntries stage;
1185+
1186+
for (auto it = mapTx.begin(); it != mapTx.end(); ++it) {
1187+
for (auto &vin : it->GetTx().vin) {
1188+
auto &coin = view.AccessCoin(vin.prevout);
1189+
auto &prevout = coin.out;
1190+
1191+
if (prevout.scriptPubKey == scriptPubKey) {
1192+
LogPrintf("Found blacklisted tx in mempool: %s",
1193+
it->GetTx().GetHash().ToString().c_str());
1194+
CalculateDescendants(it, stage);
1195+
}
1196+
}
1197+
}
1198+
1199+
RemoveStaged(stage, false, MemPoolRemovalReason::CONFLICT);
1200+
return stage.size();
1201+
}
1202+
11811203
bool CTxMemPool::addUnchecked(const uint256&hash, const CTxMemPoolEntry &entry, bool validFeeEstimate)
11821204
{
11831205
LOCK(cs);

src/txmempool.h

+3
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,9 @@ class CTxMemPool
653653
/** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */
654654
int Expire(int64_t time);
655655

656+
/** Remove transactions which got blacklisted */
657+
int CleanupBlacklisted(const CScript& scriptPubKey, const CCoinsViewCache &view);
658+
656659
/** Returns false if the transaction is in the mempool and not within the chain limit specified. */
657660
bool TransactionWithinChainLimit(const uint256& txid, size_t chainLimit) const;
658661

src/validation.cpp

+31-2
Original file line numberDiff line numberDiff line change
@@ -1544,7 +1544,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
15441544
}
15451545
}// namespace Consensus
15461546

1547-
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks)
1547+
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks, int64_t block_time)
15481548
{
15491549
if (!tx.IsCoinBase())
15501550
{
@@ -1564,6 +1564,8 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
15641564
// Of course, if an assumed valid block is invalid due to false scriptSigs
15651565
// this optimization would allow an invalid chain to be accepted.
15661566
if (fScriptChecks) {
1567+
auto blacklisted = Params().Checkpoints().mapBlacklist;
1568+
15671569
for (unsigned int i = 0; i < tx.vin.size(); i++) {
15681570
const COutPoint &prevout = tx.vin[i].prevout;
15691571
const Coin& coin = inputs.AccessCoin(prevout);
@@ -1577,6 +1579,22 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
15771579
const CScript& scriptPubKey = coin.out.scriptPubKey;
15781580
const CAmount amount = coin.out.nValue;
15791581

1582+
if (fCheckpointsEnabled) {
1583+
auto blit = blacklisted.find(scriptPubKey);
1584+
auto tx_time = block_time ? block_time : GetAdjustedTime();
1585+
1586+
if ((blit != blacklisted.end()) &&
1587+
(tx_time >= blit->second)
1588+
) {
1589+
// NOTE: that's a bit of a hack, but it perhaps the least problematic
1590+
// way to cleanup blacklisted scripts from mempool which
1591+
// did not pass timestamp check at blacklisting time.
1592+
mempool.CleanupBlacklisted(scriptPubKey, inputs);
1593+
1594+
return state.Invalid(false, REJECT_CONFLICT, "blacklisted-input");
1595+
}
1596+
}
1597+
15801598
// Verify signature
15811599
CScriptCheck check(scriptPubKey, amount, tx, i, flags, cacheStore);
15821600
if (pvChecks) {
@@ -2217,7 +2235,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
22172235

22182236
std::vector<CScriptCheck> vChecks;
22192237
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
2220-
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, nScriptCheckThreads ? &vChecks : NULL))
2238+
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, nScriptCheckThreads ? &vChecks : NULL, block.GetBlockTime()))
22212239
return error("ConnectBlock(): CheckInputs on %s failed with %s",
22222240
tx.GetHash().ToString(), FormatStateMessage(state));
22232241
control.Add(vChecks);
@@ -5050,6 +5068,17 @@ bool IsThottledStakeInput(const COutPoint &out) {
50505068
((iter->second + STAKE_INPUT_THROTTLE_PERIOD) > GetAdjustedTime());
50515069
}
50525070

5071+
/** Process script blacklist upon activation */
5072+
void ProcessScriptBlacklist(const CScript& scriptPubKey, int64_t nTimeSince)
5073+
{
5074+
if (!fCheckpointsEnabled || (nTimeSince > GetAdjustedTime())) {
5075+
return;
5076+
}
5077+
5078+
LOCK(cs_main);
5079+
mempool.CleanupBlacklisted(scriptPubKey, pcoinsTip);
5080+
}
5081+
50535082
//! Guess how far we are in the verification process at the given block index
50545083
double GuessVerificationProgress(const ChainTxData& data, CBlockIndex *pindex) {
50555084
if (pindex == NULL)

src/validation.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,8 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& ma
410410
* instead of being performed inline.
411411
*/
412412
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &view, bool fScriptChecks,
413-
unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks = NULL);
413+
unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks = NULL,
414+
int64_t block_time = 0);
414415

415416
/** Apply the effects of this transaction on the UTXO set represented by view */
416417
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);
@@ -621,4 +622,6 @@ bool CheckProof(CValidationState& state, const CBlockHeader &block, const Consen
621622
bool PassStakeInputThrottle(CValidationState& state, const COutPoint &out);
622623
bool IsThottledStakeInput(const COutPoint &out);
623624

625+
void ProcessScriptBlacklist(const CScript& scriptPubKey, int64_t nTimeSince);
626+
624627
#endif // BITCOIN_VALIDATION_H

src/wallet/wallet.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -4104,7 +4104,12 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
41044104
// Broadcast
41054105
if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) {
41064106
LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason());
4107-
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
4107+
4108+
if (state.IsInvalid()) {
4109+
wtxNew.setAbandoned();
4110+
wtxNew.MarkDirty();
4111+
return false;
4112+
}
41084113
} else {
41094114
wtxNew.RelayWalletTransaction(connman, strCommand);
41104115
}

0 commit comments

Comments
 (0)