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
OP_RETURN = 0x6a- Opcode that marks a script as provably unspendable
IsUnspendable Check#
Location: src/script/script.h:573-576
bool IsUnspendable() const {
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
}
- Returns
trueif script starts withOP_RETURNor exceeds maximum size
Transaction Type Classification#
TxoutType::NULL_DATA#
Location: src/script/solver.h:30
- Designated transaction output type for unspendable OP_RETURN scripts that carry data
- Comment:
//!< unspendable OP_RETURN script that carries data
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 must start with
OP_RETURN - Everything after the first byte must be push-only operations (data pushes)
- No other opcodes allowed after
OP_RETURN
Script Execution Context#
Interpreter Behavior#
Location: src/script/interpreter.cpp:666-669
case OP_RETURN:
{
return set_error(serror, SCRIPT_ERR_OP_RETURN);
}
Behavior:
- When executed,
OP_RETURNimmediately fails withSCRIPT_ERR_OP_RETURN - This makes the output provably unspendable at the script level
- Execution never proceeds past this opcode
Serialization/Deserialization#
Standard Transaction Serialization#
OP_RETURN scripts are serialized as part of transaction outputs (CTxOut) using standard Bitcoin serialization format:
- Script length - Encoded as CompactSize/VarInt
- 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):
0x00- P2PKH (Pay-to-Public-Key-Hash)0x01- P2SH (Pay-to-Script-Hash)0x02/0x03- Compressed P2PK0x04/0x05- Uncompressed P2PK
OP_RETURN Handling:
- OP_RETURN scripts do NOT receive special compression codes
- Stored with full script data + length prefix
- Falls through compression attempts and stored uncompressed
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);
}
- If script exceeds
MAX_SCRIPT_SIZEduring decompression, it's replaced with a simpleOP_RETURNto mark as invalid - Invalid data is skipped
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:
- OP_RETURN outputs are never added to the UTXO set
- Filtered out immediately when adding coins
- This is the primary pruning mechanism for OP_RETURN data
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
}
}
- OP_RETURN outputs are excluded from duplicate transaction ID checks
- Only spendable outputs matter for BIP30 validation
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;
- Default maximum: ~100KB (entire transaction weight / 4)
- Configurable via
-datacarrierand-datacarriersizeoptions
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:
- Multiple OP_RETURN outputs are allowed in a single transaction
- Aggregate size of all OP_RETURN outputs is checked
- Individual and total size must not exceed policy limits
Dust Calculation#
Location: src/policy/policy.cpp:42-43
if (txout.scriptPubKey.IsUnspendable())
return 0; // No dust threshold for unspendable outputs
- OP_RETURN outputs have no dust threshold
- Can contain any amount (including zero)
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:
maxburnamountparameter insendrawtransactionandsubmitpackageRPCs- Default:
DEFAULT_MAX_BURN_AMOUNT - Protects against accidental destruction of funds via OP_RETURN outputs
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:
- Amounts sent to OP_RETURN outputs are tracked separately
- Part of UTXO set statistics
- Used for chain analysis and reporting
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
- OP_RETURN outputs excluded from UTXO statistics
Key Processing Contexts#
1. Transaction Creation#
- Data encoded as push operations following OP_RETURN opcode
- Created using RPC commands or wallet APIs
- Standard script serialization applies
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);
}
- Size checked against
-datacarriersizepolicy limits - Burn amount checked against
maxburnamountparameter
3. Block Validation#
- Scripts validated for proper formatting
- Outputs never enter UTXO set (filtered during
AddCoin) - Amounts tracked in unspendables statistics
4. Blockchain Storage#
- Stored in blocks using normal transaction serialization
- Full script data preserved in block files
- No special handling during block storage
5. UTXO Database#
- Completely excluded from chainstate database
- Never serialized to UTXO set
- Pruned immediately when processing transactions
6. Chain Analysis#
- Amounts tracked as provably destroyed coins
- Accessible in block data for analysis
- Not recoverable or spendable
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.

Comments
No comments yet. Start the conversation.