Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,13 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
if (!tx.IsCoinBase())
{
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) {
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHashMalFix().ToString(), FormatStateMessage(state));
// Capture the inner reject code and reason set by CheckTxInputs before
// state.DoS() overwrites them, so the specific reason is preserved.
unsigned int innerCode = state.GetRejectCode();
std::string innerReason = state.GetRejectReason();
return state.DoS(100, error("%s: Consensus::CheckTxInputs: %s, %s", __func__,
tx.GetHashMalFix().ToString(), FormatStateMessage(state)),
innerCode, innerReason);
}
nFees += txfee;
if (!MoneyRange(nFees)) {
Expand Down
4 changes: 2 additions & 2 deletions src/coloridentifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ struct ColorIdentifier
CSHA256().Write(scriptVector.data(), scriptVector.size()).Finalize(payload);
}

ColorIdentifier(const unsigned char* pbegin, const unsigned char* pend):type(UintToToken(*pbegin)) {
ColorIdentifier(const unsigned char* pbegin, const unsigned char* pend):type(UintToToken(*pbegin)), payload{} {
CSerActionUnserialize ser_action;
CDataStream s((const char*)pbegin, (const char*)pend, SER_NETWORK, INIT_PROTO_VERSION);
SerializationOp(s, ser_action);
}

ColorIdentifier(const std::vector<unsigned char>& in) {
ColorIdentifier(const std::vector<unsigned char>& in):type(TokenTypes::NONE), payload{} {
CSerActionUnserialize ser_action;
CDataStream s(in, SER_NETWORK, INIT_PROTO_VERSION);
SerializationOp(s, ser_action);
Expand Down
62 changes: 55 additions & 7 deletions src/httprpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
static std::map<std::string, std::set<std::string>> g_rpc_whitelist;
static bool g_rpc_whitelist_default = false;

/* Per-IP failed-auth backoff. State for one peer address. */
struct AuthFailState {
int64_t next_allowed_ms{0}; // earliest time (GetTimeMillis) a new attempt is accepted
int consecutive{0}; // consecutive failure count; reset on success
};

// Max number of IP entries kept in the backoff table.
// When exceeded, expired entries are pruned; if still over, all are cleared.
static constexpr size_t AUTH_FAIL_CACHE_MAX = 1024;

static Mutex g_auth_fail_mutex;
static std::map<std::string, AuthFailState> g_failed_auths GUARDED_BY(g_auth_fail_mutex);

// Backoff for the n-th consecutive failure: 250ms, 500ms, 1s, … capped at 32s.
static int64_t AuthBackoffMs(int n)
{
return 250LL << std::min(n - 1, 7);
}

static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id)
{
// Send error reply from json-rpc error object
Expand Down Expand Up @@ -175,19 +194,48 @@ static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
}

JSONRPCRequest jreq;
jreq.peerAddr = req->GetPeer().ToString();
CService peer = req->GetPeer();
jreq.peerAddr = peer.ToString();
if (!RPCAuthorized(authHeader.second, jreq.authUser)) {
const std::string peerIP = peer.ToStringIP();
const int64_t now = GetTimeMillis();
LOCK(g_auth_fail_mutex);
// Keep the table bounded: first remove expired entries, then if still full
// evict the single entry whose backoff expires soonest (cheapest to lose).
// Never clear() the entire table — that would let an attacker with 1025 IPs
// reset all throttled peers simultaneously.
if (g_failed_auths.size() >= AUTH_FAIL_CACHE_MAX) {
for (auto it = g_failed_auths.begin(); it != g_failed_auths.end(); ) {
it = (it->second.next_allowed_ms <= now) ? g_failed_auths.erase(it) : ++it;
}
if (g_failed_auths.size() >= AUTH_FAIL_CACHE_MAX) {
auto victim = std::min_element(g_failed_auths.begin(), g_failed_auths.end(),
[](const auto& a, const auto& b) {
return a.second.next_allowed_ms < b.second.next_allowed_ms;
});
g_failed_auths.erase(victim);
}
}
AuthFailState& state = g_failed_auths[peerIP];
if (now < state.next_allowed_ms) {
// Within backoff window: reject silently (suppress log spam)
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
req->WriteReply(HTTP_UNAUTHORIZED);
return false;
}
state.consecutive++;
state.next_allowed_ms = now + AuthBackoffMs(state.consecutive);
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr);

/* Deter brute-forcing
If this results in a DoS the user really
shouldn't have their RPC port exposed. */
MilliSleep(250);

req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
req->WriteReply(HTTP_UNAUTHORIZED);
return false;
}
// Successful auth: clear any backoff for this IP
{
const std::string peerIP = peer.ToStringIP();
LOCK(g_auth_fail_mutex);
g_failed_auths.erase(peerIP);
}

try {
// Parse request
Expand Down
12 changes: 9 additions & 3 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ void Shutdown()
g_connman.reset();
g_txindex.reset();

if (g_is_mempool_loaded && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
if (g_is_mempool_loaded && gArgs.GetBoolArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
DumpMempool();
}

Expand Down Expand Up @@ -730,7 +730,7 @@ static void ThreadImport(std::vector<fs::path> vImportFiles, bool fReloadxfield)
return;
}
} // End scope of CImportingNow
if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
if (gArgs.GetBoolArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
LoadMempool();
}
g_is_mempool_loaded = !ShutdownRequested();
Expand Down Expand Up @@ -1124,7 +1124,13 @@ bool AppInitParameterInteraction()
if ((gArgs.GetChainMode() == TAPYRUS_OP_MODE::PROD) && acceptnonstdtxn)
return InitError(strprintf("acceptnonstdtxn is not supported for %s chain", TAPYRUS_MODES::GetChainName(TAPYRUS_OP_MODE::PROD)));
#endif
nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp);
{
int64_t bytesPerSigOp = gArgs.GetArg("-bytespersigop", (int64_t)nBytesPerSigOp);
if (bytesPerSigOp < 1 || bytesPerSigOp > (int64_t)std::numeric_limits<unsigned int>::max()) {
return InitError(strprintf("-bytespersigop value %d is out of range [1, %u]", bytesPerSigOp, std::numeric_limits<unsigned int>::max()));
}
nBytesPerSigOp = (unsigned int)bytesPerSigOp;
}

if (!g_wallet_init_interface.ParameterInteraction()) return false;

Expand Down
7 changes: 6 additions & 1 deletion src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,12 @@ void CConnman::Ban(const CSubNet& subNet, const BanReason &banReason, int64_t ba
bantimeoffset = gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME);
sinceUnixEpoch = false;
}
banEntry.nBanUntil = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset;
const int64_t banBase = sinceUnixEpoch ? 0 : GetTime();
// Clamp to prevent signed int64 overflow, which would silently set a past expiry.
if (bantimeoffset > std::numeric_limits<int64_t>::max() - banBase) {
bantimeoffset = std::numeric_limits<int64_t>::max() - banBase;
}
banEntry.nBanUntil = banBase + bantimeoffset;

{
LOCK(cs_setBanned);
Expand Down
21 changes: 9 additions & 12 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,11 +399,11 @@ static bool MarkBlockAsReceived(const uint256& hash, std::optional<NodeId> from
// Still erase from_peer's entry below — do not return early and leak it.
}
bool found = false;
while (rangeInFlight.first != rangeInFlight.second) {
auto itInFlight = rangeInFlight.first->second;
for (auto it = rangeInFlight.first; it != rangeInFlight.second; ) {
auto itInFlight = it->second;
auto node_id = itInFlight.first;
if (from_peer && *from_peer != node_id) {
rangeInFlight.first++;
++it;
continue;
}

Expand All @@ -421,13 +421,12 @@ static bool MarkBlockAsReceived(const uint256& hash, std::optional<NodeId> from
}
state->nStallingSince = 0;

rangeInFlight.first = mapBlocksInFlight.erase(rangeInFlight.first);
it = mapBlocksInFlight.erase(it);
found = true;
// When a specific peer is given, one entry is expected — return immediately.
// When nullopt, continue to erase all in-flight entries for this hash so
// no other peer's vBlocksInFlight entry is left dangling.
if (from_peer) return true;
if (from_peer) return true; // erase exactly one when targeted
// when from_peer is nullopt, continue the loop to drain all peers
}
assert(!found || from_peer || mapBlocksInFlight.count(hash) == 0);
return found;
}

Expand Down Expand Up @@ -644,7 +643,7 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec
} else if (blockrequested && waitingfor == -1) {
// This is the first already-in-flight block.
auto iter = mapBlocksInFlight.equal_range(pindex->GetBlockHash());
waitingfor = iter.second->second.first;
waitingfor = iter.first->second.first;
}
}
}
Expand Down Expand Up @@ -1994,9 +1993,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
std::vector<CAddress> vAddr;
vRecv >> vAddr;

// Don't want addr from older versions unless seeding
if (connman->GetAddressCount() > MAX_ADDR_TO_SEND)
return true;
if (vAddr.size() > MAX_ADDR_TO_SEND)
{
LOCK(cs_main);
Expand Down Expand Up @@ -2633,6 +2629,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
} else {
// Give up for this peer and wait for other peer(s)
MarkBlockAsReceived(pindex->GetBlockHash(), pfrom->GetId());
return true;
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/policy/packages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,13 @@ bool SubmitPackageToMempool(const Package& package,
opt.state.missingInputs = opt.missingInputs.size() > 0;
results.emplace(tx->GetHashMalFix(), opt.state);

if (virtualView && opt.state.IsValid()) {
// Only add to the virtual overlay if the tx was TRULY accepted.
// IsValid() returns true even when missingInputs is set (DoAllInputsExist
// deliberately avoids calling state.Invalid() so callers can distinguish
// orphans from consensus failures). Adding a missing-inputs tx to the
// overlay would let its outputs be found by downstream txs, causing them
// to spuriously pass TEST_ONLY validation.
if (virtualView && opt.state.IsValid() && !opt.state.missingInputs) {
virtualView->AddVirtualTx(*tx);
}
}
Expand All @@ -139,7 +145,7 @@ bool SubmitPackageToMempool(const Package& package,
// Relay only after the entire package has been successfully admitted.
// Relaying per-tx inside the loop would gossip the admitted prefix of a
// partially-failed package, enabling free relay amplification attacks.
if (success && opt.flags != MempoolAcceptanceFlags::TEST_ONLY) {
if (success && opt.flags != MempoolAcceptanceFlags::TEST_ONLY && g_connman) {
CConnman& connman = *g_connman;
for (auto& tx : package) {
RelayTransaction(*tx, &connman);
Expand Down
15 changes: 14 additions & 1 deletion src/primitives/xfield.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ class XFieldAggPubKey {
}

inline bool IsValid() const {
return CPubKey(data.begin(), data.end()).IsFullyValid();
if (data.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE) return false;
if (data[0] != 0x02 && data[0] != 0x03) return false;
CPubKey k(data.begin(), data.end());
return k.IsFullyValid() && k.IsCompressed();
}

inline std::string ToString() const;
Expand Down Expand Up @@ -262,7 +265,17 @@ struct CXField {
xfieldValue = value; break;
}
case TAPYRUS_XFIELDTYPES::NONE:
// No payload bytes for NONE. Any bytes that a peer appended
// after the type byte are NOT consumed here — they will be
// read by the next field (proof). CheckBlockHeader catches
// this by requiring proof to be exactly SCHNORR_SIGNATURE_SIZE
// bytes before attempting verification.
break;
default:
// Unknown xfield type: the consistency check below will throw
// BadXFieldException, but an explicit default makes the intent
// clear and prevents silent fallthrough for future enum values.
throw BadXFieldException(xfieldType, xfieldValue);
}
if(GetXFieldTypeFrom(xfieldValue) != xfieldType) {
throw BadXFieldException(xfieldType, xfieldValue);
Expand Down
28 changes: 24 additions & 4 deletions src/rest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,12 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)

switch (rf) {
case RetFormat::HEX: {
// Each binary byte is encoded as 2 hex characters; reject before ParseHex
const size_t maxHexBody = 2 * (sizeof(bool)
+ GetSizeOfCompactSize(MAX_GETUTXOS_OUTPOINTS)
+ MAX_GETUTXOS_OUTPOINTS * sizeof(COutPoint) + 16);
if (strRequestMutable.size() > maxHexBody)
return RESTERR(req, HTTP_BAD_REQUEST, "Request body too large");
// convert hex to bin, continue then with bin part
std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
Expand All @@ -461,6 +467,12 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
//deserialize only if user sent a request
if (strRequestMutable.size() > 0)
{
const size_t maxBinBody = sizeof(bool)
+ GetSizeOfCompactSize(MAX_GETUTXOS_OUTPOINTS)
+ MAX_GETUTXOS_OUTPOINTS * sizeof(COutPoint) + 16;
if (strRequestMutable.size() > maxBinBody)
return RESTERR(req, HTTP_BAD_REQUEST, "Request body too large");

if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
return RESTERR(req, HTTP_BAD_REQUEST, "Combination of URI scheme inputs and raw post data is not allowed");

Expand Down Expand Up @@ -496,6 +508,10 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
std::string bitmapStringRepresentation;
std::vector<bool> hits;
bitmap.resize((vOutPoints.size() + 7) / 8);
// Snapshot height and tip hash under the same cs_main lock used for coin lookup
// so the returned (height, tipHash, utxos) triple is internally consistent.
int chainHeight = 0;
uint256 chainTip;
{
auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool& mempool) {
for (const COutPoint& vOutPoint : vOutPoints) {
Expand All @@ -512,9 +528,13 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
CCoinsViewCache& viewChain = *pcoinsTip;
CCoinsViewMemPool viewMempool(&viewChain, mempool);
process_utxos(viewMempool, mempool);
chainHeight = chainActive.Height();
chainTip = chainActive.Tip()->GetBlockHash();
} else {
LOCK(cs_main); // no need to lock mempool!
process_utxos(*pcoinsTip, CTxMemPool());
chainHeight = chainActive.Height();
chainTip = chainActive.Tip()->GetBlockHash();
}

for (size_t i = 0; i < hits.size(); ++i) {
Expand All @@ -529,7 +549,7 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
// serialize data
// use exact same output as mentioned in Bip64
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
ssGetUTXOResponse << chainHeight << chainTip << bitmap << outs;
std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();

req->WriteHeader("Content-Type", "application/octet-stream");
Expand All @@ -539,7 +559,7 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)

case RetFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
ssGetUTXOResponse << chainHeight << chainTip << bitmap << outs;
std::string strHex = HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) + "\n";

req->WriteHeader("Content-Type", "text/plain");
Expand All @@ -552,8 +572,8 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)

// pack in some essentials
// use more or less the same output as mentioned in Bip64
objGetUTXOResponse.pushKV("chainHeight", chainActive.Height());
objGetUTXOResponse.pushKV("chaintipHash", chainActive.Tip()->GetBlockHash().GetHex());
objGetUTXOResponse.pushKV("chainHeight", chainHeight);
objGetUTXOResponse.pushKV("chaintipHash", chainTip.GetHex());
objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);

UniValue utxos(UniValue::VARR);
Expand Down
Loading
Loading