Asking Claude about OP_RETURN in Bitcoin Core

average_gary

7. Oktober 2025October 7, 2025 · 7 Min. Lesezeit7 min read

can you find all references to OP_RETURN data and outline how it's is handled. When is it serialized/deserialized and in what context?

⏺ I'll search for all references to OP_RETURN in the codebase and analyze how it's handled.

⏺ Search(pattern: "OP_RETURN") ⎿  Found 73 files (ctrl+o to expand)

⏺ Search(pattern: "OP_RETURN", output_mode: "content") ⎿  Found 1241 lines (ctrl+o to expand)

⏺ Let me read the key source files to understand how OP_RETURN data is handled:

⏺ Read(src/script/script.h) ⎿  Read 642 lines (ctrl+o to expand)

⏺ Read(src/script/interpreter.cpp) ⎿  Error: File content (27382 tokens) exceeds maximum allowed tokens (25000). Please use offset and limit parameters to read specific portions of the file, or use the GrepTool to search for specific content.

⏺ Read(src/script/solver.cpp) ⎿  Read 229 lines (ctrl+o to expand)

⏺ Read(src/compressor.h) ⎿  Read 119 lines (ctrl+o to expand)

⏺ Read(src/validation.cpp) ⎿  Read 100 lines (ctrl+o to expand)

⏺ Let me search for more specific OP_RETURN handling in validation and script execution:

⏺ Search(pattern: "IsUnspendable", output_mode: "content") ⎿  Found 238 lines (ctrl+o to expand)

⏺ Read(src/script/interpreter.cpp) ⎿  Read 200 lines (ctrl+o to expand)

⏺ Search(pattern: "NULL_DATA", output_mode: "content") ⎿  Found 170 lines (ctrl+o to expand)

⏺ Let me look at how OP_RETURN is handled during script execution and look for where the data is extracted:

⏺ Search(pattern: "case OP_RETURN", output_mode: "content") ⎿  Found 43 lines (ctrl+o to expand)

⏺ Read(src/rpc/rawtransaction_util.cpp) ⎿  Read 100 lines (ctrl+o to expand)

⏺ Search(pattern: "datacarrier", output_mode: "content") ⎿  Found 352 lines (ctrl+o to expand)

⏺ Now let me check how the data is serialized/deserialized in transactions and the compressor:

⏺ Read(src/script/script.cpp) ⎿  Read 100 lines (ctrl+o to expand)

⏺ Search(pattern: "CompressScript|DecompressScript", output_mode: "content") ⎿  Found 326 lines (ctrl+o to expand)

⏺ Now let me read the actual compressor implementation:

⏺ Read(src/compressor.cpp) ⎿  Read 193 lines (ctrl+o to expand)

⏺ Based on my analysis of the Bitcoin Core codebase, here's a comprehensive outline of how OP_RETURN data is handled:

OP_RETURN Data Handling in Bitcoin Core

Definition and Recognition#

Script Definition#

Location: src/script/script.h:111

IsUnspendable Check#

Location: src/script/script.h:573-576

bool IsUnspendable() const {
    return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
}

Transaction Type Classification#

TxoutType::NULL_DATA#

Location: src/script/solver.h:30

Detection Logic#

Location: src/script/solver.cpp:185-187

if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN &&
    scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) {
    return TxoutType::NULL_DATA;
}

Requirements:


Script Execution Context#

Interpreter Behavior#

Location: src/script/interpreter.cpp:666-669

case OP_RETURN:
{
    return set_error(serror, SCRIPT_ERR_OP_RETURN);
}

Behavior:


Serialization/Deserialization#

Standard Transaction Serialization#

OP_RETURN scripts are serialized as part of transaction outputs (CTxOut) using standard Bitcoin serialization format:

  1. Script length - Encoded as CompactSize/VarInt
  2. Script bytes - Raw bytes including opcode + data pushes

No special handling during normal transaction serialization.

UTXO Set Compression#

Location: src/compressor.h:54-96, src/compressor.cpp:55-84

Compression

bool CompressScript(const CScript& script, CompressedScript& out)

Special Cases (codes 0-5):

OP_RETURN Handling:

Decompression Error Handling

Location: src/compressor.h:87-90

if (nSize > MAX_SCRIPT_SIZE) {
    // Overly long script, replace with a short invalid one
    script << OP_RETURN;
    s.ignore(nSize);
}

UTXO Management#

AddCoin Filtering#

Location: src/coins.cpp:74

void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) {
    assert(!coin.IsSpent());
    if (coin.out.scriptPubKey.IsUnspendable()) return;  // Early exit
    // ...
}

Key Behavior:

BIP30 Duplicate Transaction Checking#

Location: src/validation.cpp:2334

for (size_t o = 0; o < tx.vout.size(); o++) {
    if (!tx.vout[o].scriptPubKey.IsUnspendable()) {
        // Only check spendable outputs for BIP30
    }
}

Policy and Validation#

Size Limits#

Location: src/policy/policy.h:73-78

static const bool DEFAULT_ACCEPT_DATACARRIER = true;
static const unsigned int MAX_OP_RETURN_RELAY = MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR;

Standardness Check#

Location: src/policy/policy.cpp:144-150

if (whichType == TxoutType::NULL_DATA) {
    unsigned int size = txout.scriptPubKey.size();
    if (size > datacarrier_bytes_left) {
        reason = "datacarrier";
        return false;
    }
    datacarrier_bytes_left -= size;
}

Rules:

Dust Calculation#

Location: src/policy/policy.cpp:42-43

if (txout.scriptPubKey.IsUnspendable())
    return 0;  // No dust threshold for unspendable outputs

RPC and Burn Protection#

Maximum Burn Amount Check#

Location: src/rpc/mempool.cpp:85-87

if((out.scriptPubKey.IsUnspendable() || !out.scriptPubKey.HasValidOps()) &&
   out.nValue > max_burn_amount) {
    throw JSONRPCTransactionError(TransactionError::MAX_BURN_EXCEEDED);
}

Parameters:


Statistics and Reporting#

Coin Statistics Tracking#

Location: src/kernel/coinstats.h:65-66

// Total cumulative amount of outputs sent to unspendable scripts
// (OP_RETURN for example) up to and including this block
CAmount total_unspendables_scripts{0};

Tracking:

GetBlockStats RPC#

Location: src/rpc/blockchain.cpp:2000

if (out.scriptPubKey.IsUnspendable()) continue;
// Skip unspendable outputs since they are not included in the UTXO set

Key Processing Contexts#

1. Transaction Creation#

2. Mempool Acceptance#

Location: src/validation.cpp:795

if (m_pool.m_opts.require_standard &&
    !IsStandardTx(tx, m_pool.m_opts.max_datacarrier_bytes, ...)) {
    return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
}

3. Block Validation#

4. Blockchain Storage#

5. UTXO Database#

6. Chain Analysis#


Summary#

OP_RETURN data is handled as follows:

Stage Serialization Context
Transaction Creation Standard serialization (VarInt + bytes) Encoded in scriptPubKey
Network Propagation Standard serialization Part of transaction messages
Mempool Standard serialization Policy checks applied
Block Storage Standard serialization Stored in block files
UTXO Set Not serialized Filtered out by IsUnspendable() check
Script Execution Never executed Returns SCRIPT_ERR_OP_RETURN

Key Insight: OP_RETURN data is never deserialized for execution and is never stored in the UTXO set. It exists only in blockchain history for data storage purposes and is provably unspendable.

Written by

Comments

No comments yet. Start the conversation.

Contents

10 sections

  1. Definition and Recognition
  2. Transaction Type Classification
  3. Script Execution Context
  4. Serialization/Deserialization
  5. UTXO Management
  6. Policy and Validation
  7. RPC and Burn Protection
  8. Statistics and Reporting
  9. Key Processing Contexts
  10. Summary