Skip to content

Commit

Permalink
Added -ordislow
Browse files Browse the repository at this point in the history
We if -ordislow=1 (default), we maintain a rolling bloom filter of
1 million txns (~12.6MB memory) to keep track of ordinal-containing
mempool txns seen. If a block arrives that contains such a txn, even
before we fully evaluate it, we suppress announcements of the block
to peers.

Also added logic to refuse to upload the block to any peers after the
fact as well.

Required a new concept in the script engine: a script "warning", which
gets bubbled up to caller and is used to decide if a txn we have seen
has ordinals or not.
  • Loading branch information
cculianu committed Nov 20, 2023
1 parent b37cd09 commit ef80cb4
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 50 deletions.
4 changes: 4 additions & 0 deletions src/chain.h
Expand Up @@ -206,6 +206,10 @@ class CBlockIndex
//! (memory only) Maximum nTime in the chain up to and including this block.
unsigned int nTimeMax{0};

//! (memory only) Block validation script warnings. This is updated by ConnectBlock() & AcceptBlock() only.
//! May be 0 for old blocks. Is valid only for a block that was recently Accepted and/or Connected.
uint32_t nScriptWarningFlags{0u};

explicit CBlockIndex(const CBlockHeader& block)
: nVersion{block.nVersion},
hashMerkleRoot{block.hashMerkleRoot},
Expand Down
30 changes: 26 additions & 4 deletions src/checkqueue.h
Expand Up @@ -10,12 +10,16 @@
#include <util/threadnames.h>

#include <algorithm>
#include <atomic>
#include <iterator>
#include <type_traits>
#include <vector>

template <typename T>
class CCheckQueueControl;

class CScriptCheck;

/**
* Queue for verifications that have to be performed.
* The verifications are represented by a type T, which must provide an
Expand Down Expand Up @@ -65,6 +69,9 @@ class CCheckQueue
std::vector<std::thread> m_worker_threads;
bool m_request_stop GUARDED_BY(m_mutex){false};

/// Script execution warning bits encountered in this run. Master thread clears this each time before returning from Wait()
std::atomic_uint32_t m_warnings = 0u;

/** Internal function that does bulk of the verification work. */
bool Loop(bool fMaster) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
{
Expand Down Expand Up @@ -119,8 +126,16 @@ class CCheckQueue
}
// execute work
for (T& check : vChecks)
if (fOk)
if (fOk) {
fOk = check();
if constexpr (std::is_base_of_v<CScriptCheck, T>) {
// Sort of a hack for OrdiSlow; bubble up any script warnings.
if (const auto warn = check.GetScriptWarning()) {
// Set the warning bit
m_warnings.fetch_or(static_cast<uint32_t>(warn));
}
}
} else break;
vChecks.clear();
} while (true);
}
Expand Down Expand Up @@ -154,9 +169,13 @@ class CCheckQueue
}

//! Wait until execution finishes, and return whether all evaluations were successful.
bool Wait() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
bool Wait(uint32_t* pwarnings = nullptr) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
{
return Loop(true /* master thread */);
const bool ret = Loop(true /* master thread */);
// Atomically retrive warnings and clear all warnings seen for this run
const uint32_t wval = m_warnings.exchange(0u);
if (pwarnings) *pwarnings = wval;
return ret;
}

//! Add a batch of checks to the queue
Expand Down Expand Up @@ -208,6 +227,7 @@ class CCheckQueueControl
{
private:
CCheckQueue<T> * const pqueue;
uint32_t warnings = 0u;
bool fDone;

public:
Expand All @@ -226,7 +246,7 @@ class CCheckQueueControl
{
if (pqueue == nullptr)
return true;
bool fRet = pqueue->Wait();
bool fRet = pqueue->Wait(&warnings);
fDone = true;
return fRet;
}
Expand All @@ -238,6 +258,8 @@ class CCheckQueueControl
}
}

uint32_t GetWarnings() const { return warnings; }

~CCheckQueueControl()
{
if (!fDone)
Expand Down
4 changes: 4 additions & 0 deletions src/init.cpp
Expand Up @@ -599,6 +599,7 @@ void SetupServerArgs(ArgsManager& argsman)
MAX_OP_RETURN_RELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", DEFAULT_MEMPOOL_FULL_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-ordislow", strprintf("Do not relay newly-arrived blocks that contain ordinal transactions (default: %u)", DEFAULT_ORDISLOW), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-ordisrespector", strprintf("Do not allow ordinal inscription transactions into the mempool and do not relay them (default: %u)", !DEFAULT_ALLOW_RELAY_INSCRIPTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY,
OptionsCategory::NODE_RELAY);
Expand Down Expand Up @@ -1022,6 +1023,9 @@ bool AppInitParameterInteraction(const ArgsManager& args)
}
}

// -ordislow
g_ordiSlow = args.GetBoolArg("-ordislow", DEFAULT_ORDISLOW);

return true;
}

Expand Down
3 changes: 3 additions & 0 deletions src/logging.cpp
Expand Up @@ -183,6 +183,7 @@ const CLogCategoryDesc LogCategories[] =
{BCLog::TXRECONCILIATION, "txreconciliation"},
{BCLog::SCAN, "scan"},
{BCLog::TXPACKAGES, "txpackages"},
{BCLog::ORDISLOW, "ordislow"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},
};
Expand Down Expand Up @@ -289,6 +290,8 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
return "scan";
case BCLog::LogFlags::TXPACKAGES:
return "txpackages";
case BCLog::LogFlags::ORDISLOW:
return "ordislow";
case BCLog::LogFlags::ALL:
return "all";
}
Expand Down
1 change: 1 addition & 0 deletions src/logging.h
Expand Up @@ -69,6 +69,7 @@ namespace BCLog {
TXRECONCILIATION = (1 << 27),
SCAN = (1 << 28),
TXPACKAGES = (1 << 29),
ORDISLOW = (1 << 30),
ALL = ~(uint32_t)0,
};
enum class Level {
Expand Down
10 changes: 10 additions & 0 deletions src/net_processing.cpp
Expand Up @@ -1994,6 +1994,10 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
if (!DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) return;

uint256 hashBlock(pblock->GetHash());
if (g_ordiSlow && (pindex->nScriptWarningFlags & SCRIPT_WARN_ORDINAL_INSCRIPTION)) {
LogPrint(BCLog::ORDISLOW, "%s: Refusing to relay new block %s because it contains known-ordinal txns\n", __func__, hashBlock.ToString());
return;
}
const std::shared_future<CSerializedNetMsg> lazy_ser{
std::async(std::launch::deferred, [&] { return msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })};

Expand Down Expand Up @@ -2260,6 +2264,12 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv&
LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom.GetId());
return;
}
// Do not send new blocks that contain ordinals if they are at or beyond the tip and g_ordiSlow == true
if (g_ordiSlow && (pindex->nScriptWarningFlags & SCRIPT_WARN_ORDINAL_INSCRIPTION)
&& (pindex == m_chainman.ActiveTip() || pindex->pprev == m_chainman.ActiveTip())) {
LogPrint(BCLog::ORDISLOW, "%s: Ignoring request from peer=%i for a newly-arrived block that contains ordinals\n", __func__, pfrom.GetId());
return;
}
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
// disconnect node in case we have reached the outbound limit for serving historical blocks
if (m_connman.OutboundTargetReached(true) &&
Expand Down
39 changes: 22 additions & 17 deletions src/script/interpreter.cpp
Expand Up @@ -30,6 +30,11 @@ inline bool set_error(ScriptError* ret, const ScriptError serror)
return false;
}

inline void set_warning(ScriptWarning* ret, const ScriptWarning warning)
{
if (ret) *ret = warning;
}

} // namespace

bool CastToBool(const valtype& vch)
Expand Down Expand Up @@ -414,7 +419,7 @@ static bool DetectOrdinalPattern(const CScript& script, CScript::const_iterator
return script.GetOp(pc, next_opcode, vch) && next_opcode == opcode_to_check;
}

bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror, ScriptWarning* swarning)
{
static const CScriptNum bnZero(0);
static const CScriptNum bnOne(1);
Expand Down Expand Up @@ -488,11 +493,11 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&

// Match ordinal inscriptions, and either reject them or flag caller that we saw an ordinal
if (fExec && fMayContainOrdinals && DetectOrdinalPattern(script, pc, opcode)) {
// unconditionally set warning flag first
set_warning(swarning, SCRIPT_WARN_ORDINAL_INSCRIPTION);
if (flags & SCRIPT_VERIFY_DISCOURAGE_INSCRIPTIONS) {
// relay rule: -ordisrespector=1
return set_error(serror, SCRIPT_ERR_DISALLOWED_INSCRIPTION);
} else {
// May be block txn; TODO: flag caller in some way
}
}

Expand Down Expand Up @@ -1255,10 +1260,10 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
return set_success(serror);
}

bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, ScriptWarning* swarning)
{
ScriptExecutionData execdata;
return EvalScript(stack, script, flags, checker, sigversion, execdata, serror);
return EvalScript(stack, script, flags, checker, sigversion, execdata, serror, swarning);
}

namespace {
Expand Down Expand Up @@ -1807,7 +1812,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;

static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror)
static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror, ScriptWarning* swarning)
{
std::vector<valtype> stack{stack_span.begin(), stack_span.end()};

Expand Down Expand Up @@ -1839,7 +1844,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
}

// Run the script interpreter.
if (!EvalScript(stack, exec_script, flags, checker, sigversion, execdata, serror)) return false;
if (!EvalScript(stack, exec_script, flags, checker, sigversion, execdata, serror, swarning)) return false;

// Scripts inside witness implicitly require cleanstack behaviour
if (stack.size() != 1) return set_error(serror, SCRIPT_ERR_CLEANSTACK);
Expand Down Expand Up @@ -1892,7 +1897,7 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
return q.CheckTapTweak(p, merkle_root, control[0] & 1);
}

static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh)
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, ScriptWarning* swarning, bool is_p2sh)
{
CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH; leaf script in P2TR)
Span stack{witness.stack};
Expand All @@ -1911,14 +1916,14 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
if (memcmp(hash_exec_script.begin(), program.data(), 32)) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
}
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror);
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror, swarning);
} else if (program.size() == WITNESS_V0_KEYHASH_SIZE) {
// BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
if (stack.size() != 2) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); // 2 items in witness
}
exec_script << OP_DUP << OP_HASH160 << program << OP_EQUALVERIFY << OP_CHECKSIG;
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror);
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror, swarning);
} else {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
}
Expand Down Expand Up @@ -1958,7 +1963,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
exec_script = CScript(script.begin(), script.end());
execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack, PROTOCOL_VERSION) + VALIDATION_WEIGHT_OFFSET;
execdata.m_validation_weight_left_init = true;
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror);
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror, swarning);
}
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
Expand All @@ -1975,7 +1980,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
// There is intentionally no return statement here, to be able to use "control reaches end of non-void function" warnings to detect gaps in the logic above.
}

bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, ScriptWarning* swarning)
{
static const CScriptWitness emptyWitness;
if (witness == nullptr) {
Expand All @@ -1992,12 +1997,12 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
// scriptSig and scriptPubKey must be evaluated sequentially on the same stack
// rather than being simply concatenated (see CVE-2010-5141)
std::vector<std::vector<unsigned char> > stack, stackCopy;
if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror))
if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror, swarning))
// serror is set
return false;
if (flags & SCRIPT_VERIFY_P2SH)
stackCopy = stack;
if (!EvalScript(stack, scriptPubKey, flags, checker, SigVersion::BASE, serror))
if (!EvalScript(stack, scriptPubKey, flags, checker, SigVersion::BASE, serror, swarning))
// serror is set
return false;
if (stack.empty())
Expand All @@ -2015,7 +2020,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
}
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/false)) {
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, swarning, /*is_p2sh=*/false)) {
return false;
}
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
Expand Down Expand Up @@ -2043,7 +2048,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end());
popstack(stack);

if (!EvalScript(stack, pubKey2, flags, checker, SigVersion::BASE, serror))
if (!EvalScript(stack, pubKey2, flags, checker, SigVersion::BASE, serror, swarning))
// serror is set
return false;
if (stack.empty())
Expand All @@ -2060,7 +2065,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
// reintroduce malleability.
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
}
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/true)) {
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, swarning, /*is_p2sh=*/true)) {
return false;
}
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
Expand Down
6 changes: 3 additions & 3 deletions src/script/interpreter.h
Expand Up @@ -344,9 +344,9 @@ uint256 ComputeTapbranchHash(Span<const unsigned char> a, Span<const unsigned ch
* Requires control block to have valid length (33 + k*32, with k in {0,1,..,128}). */
uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash);

bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr);
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr);
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr);
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr, ScriptWarning* swarning = nullptr);
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr, ScriptWarning* swarning = nullptr);
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr, ScriptWarning* swarning = nullptr);

size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags);

Expand Down
35 changes: 35 additions & 0 deletions src/script/script_error.cpp
Expand Up @@ -5,6 +5,9 @@

#include <script/script_error.h>

#include <crypto/common.h>

#include <cassert>
#include <string>

std::string ScriptErrorString(const ScriptError serror)
Expand Down Expand Up @@ -123,3 +126,35 @@ std::string ScriptErrorString(const ScriptError serror)
}
return "unknown error";
}

std::string ScriptWarningString(const ScriptWarning warning)
{
switch (warning) {
case SCRIPT_WARN_NONE:
return "No warning";
case SCRIPT_WARN_ORDINAL_INSCRIPTION:
return "Ordinal inscription detected";
default: break;
}
return "unknown warning";
}

std::string ScriptWarningStrings(uint32_t warning_flags)
{
std::string ret;
bool seen_unknown = false;
while (warning_flags) {
const auto bit = CountBits(warning_flags);
assert(bit > 0 && bit <= 32);
const ScriptWarning w = static_cast<ScriptWarning>(1u << (bit-1u));
warning_flags &= ~w;
if (w >= SCRIPT_WARN_UNKNOWN) {
// ensure "unknown warning" appears at most once in the list
if (seen_unknown) continue;
seen_unknown = true;
}
if (!ret.empty()) ret += ",";
ret += ScriptWarningString(w);
}
return ret;
}

0 comments on commit ef80cb4

Please sign in to comment.