Nominates a sidechain for slot S, carrying the full description. The proposal starts a voting window; it does nothing on its own.
Eight messages carry every drivechain operation — proposing a sidechain, moving coins in and out, and blind merged mining. Five of them can only be minted into a block's coinbase by a miner running the enforcer. Three travel the peer-to-peer network as ordinary transactions. Nearly every confusing failure comes from mistaking one kind for the other.
A drivechain message is either a coinbase output in a mainchain block, or an output in an ordinary transaction. That single fact decides who can produce it, how it reaches the network, and what it costs to get it accepted.
An OP_RETURN output in the block's own coinbase transaction. It exists only because the miner who found that block chose to put it there.
Emitted by: the block producer. In practice, a miner whose template comes from a BIP300/301 enforcer. A miner on a stock getblocktemplate cannot produce these at all.
Outputs in normal mainchain transactions. They relay through the mempool like any other transaction and pay ordinary fees.
Emitted by: anyone. Any node can broadcast one; any miner can mine one. No enforcer required to create or relay them.
The enforcer parses the coinbase lane by iterating every output of the coinbase transaction and attempting CoinbaseMessage::parse on each scriptPubKey. Its CoinbaseMessage enum has exactly five variants: M1, M2, M3, M4, M7. Nothing else is a coinbase message.
| Msg | Name | Tag | Lives in | Purpose |
|---|---|---|---|---|
| M1 | ProposeSidechain | D5 E0 C4 AF | coinbase | Nominate a sidechain into slot S |
| M2 | AckSidechain | D6 E1 C5 DF | coinbase | One miner vote for a pending proposal |
| M3 | ProposeBundle | D4 5A A9 43 | coinbase | Nominate a withdrawal bundle |
| M4 | AckBundles | D7 7D 17 76 | coinbase | Vote on the bundle slate |
| M5 | Deposit | — | transaction | Move BTC into the sidechain treasury |
| M6 | Withdrawal | — | transaction | Pay an approved bundle out of the treasury |
| M7 | BmmAccept | D1 61 73 68 | coinbase | Accept one sidechain block per slot |
| M8 | BmmRequest | 00 BF 00 | transaction | Bid to have a sidechain block accepted |
M5 and M6 carry no tag because they are not tagged OP_RETURN messages at all — they are recognised by their shape, specifically the OP_DRIVECHAIN treasury output they create or spend.
Nominates a sidechain for slot S, carrying the full description. The proposal starts a voting window; it does nothing on its own.
One miner's vote for a pending proposal, identified by the double-SHA256 of its description. Accumulated votes are what activate a slot.
Nominates a withdrawal bundle — a batch of sidechain exits — by its txid. Like M1, it opens a vote rather than settling anything.
Votes across the whole slate of pending bundles at once. Four encodings trade precision against coinbase bytes.
An ordinary transaction that spends the sidechain's current treasury UTXO and recreates it with more value in it. The destination on the sidechain rides along in a separate OP_RETURN.
Pays an approved bundle out of the treasury. Exactly one input (the old treasury UTXO); output 0 is the new treasury UTXO, and every output after it is a payout.
The accept half of blind merged mining. It names one sidechain block hash as accepted for slot S in this block — and it is the only thing that actually advances a sidechain.
The bid. A normal transaction whose OP_RETURN commits to a candidate sidechain block hash, with the bribe paid as the transaction fee. It is bound to one specific parent block and dies with it.
%%{init: {"theme":"base","themeVariables":{"fontFamily":"IBM Plex Mono, monospace","fontSize":"13px","primaryColor":"#EDEFF3","primaryTextColor":"#15181D","primaryBorderColor":"#A9761C","lineColor":"#5E6672","actorBkg":"#FFFFFF","actorBorder":"#5E6672","actorTextColor":"#15181D","signalColor":"#15181D","signalTextColor":"#15181D","noteBkgColor":"#F6EEDC","noteBorderColor":"#A9761C","noteTextColor":"#15181D","sequenceNumberColor":"#FFFFFF"}}}%%
sequenceDiagram
autonumber
participant Op as Sidechain operator
participant Miners as Mainchain miners
participant CB as Block coinbase
participant Enf as Enforcer (every node)
Op->>Miners: ask for slot S (out of band)
Miners->>CB: M1 ProposeSidechain (S, description)
Enf->>Enf: record proposal, start age counter
loop each subsequent block
Miners->>CB: M2 AckSidechain (S, sha256d(description))
Enf->>Enf: vote_count += 1
end
Enf->>Enf: vote_count > threshold AND age within window?
Enf-->>Op: slot S activated
| Preset | Bundle max age | Bundle threshold | Used slot age / votes | Unused slot age / votes |
|---|---|---|---|---|
| MAINNET | 26,300 | 13,150 | 26,300 / 13,150 | 2,016 / 1,815 |
| FORKNET | 144 | 72 | 144 / 72 | 36 / 30 |
| SHORT | 10 | 5 | 10 / 5 | 10 / 5 |
A slot that was previously occupied is a used slot and gets the longer window; a never-used slot gets the shorter one. Reading the wrong row is an easy way to conclude an activation was impossible when it was not — a proposal 119 blocks old with 73 votes fails the FORKNET unused rule (36 / 30) but passes the used one (144 / 72).
%%{init: {"theme":"base","themeVariables":{"fontFamily":"IBM Plex Mono, monospace","fontSize":"13px","primaryColor":"#EDEFF3","primaryTextColor":"#15181D","primaryBorderColor":"#2B7A87","lineColor":"#5E6672","actorBkg":"#FFFFFF","actorBorder":"#5E6672","actorTextColor":"#15181D","signalColor":"#15181D","signalTextColor":"#15181D","noteBkgColor":"#E2F0F2","noteBorderColor":"#2B7A87","noteTextColor":"#15181D","sequenceNumberColor":"#FFFFFF"}}}%%
sequenceDiagram
autonumber
participant User as Depositor / withdrawer
participant Mem as Mainchain mempool
participant CB as Block coinbase
participant Tre as Treasury UTXO (Ctip)
rect rgb(226, 240, 242)
Note over User,Tre: Deposit — no coinbase message needed
User->>Mem: M5 tx, spends Ctip, recreates it larger
Mem->>Tre: mined by any miner
Tre->>Tre: Ctip value += deposit
end
rect rgb(246, 238, 220)
Note over User,Tre: Withdrawal — gated by two miner votes
User->>CB: bundle proposed via M3 (S, bundle_txid)
loop voting window
CB->>CB: M4 AckBundles upvotes the slate
end
CB-->>Mem: bundle approved
Mem->>Tre: M6 tx pays out, recreates Ctip smaller
end
%%{init: {"theme":"base","themeVariables":{"fontFamily":"IBM Plex Mono, monospace","fontSize":"13px","primaryColor":"#EDEFF3","primaryTextColor":"#15181D","primaryBorderColor":"#A9761C","lineColor":"#5E6672","actorBkg":"#FFFFFF","actorBorder":"#5E6672","actorTextColor":"#15181D","signalColor":"#15181D","signalTextColor":"#15181D","noteBkgColor":"#F6EEDC","noteBorderColor":"#A9761C","noteTextColor":"#15181D","sequenceNumberColor":"#FFFFFF"}}}%%
sequenceDiagram
autonumber
participant SC as Sidechain node
participant W as Enforcer wallet
participant Mem as Mainchain mempool
participant Plain as Plain miner
participant EnfM as Enforcer-driven miner
SC->>SC: build candidate block, hash it as H*
SC->>W: CreateBmmCriticalDataTransaction(S, H*, prev_tip)
W->>W: reject if prev_tip is no longer the tip
W->>Mem: broadcast M8 (fee = bribe)
Mem-->>Plain: relayed
Mem-->>EnfM: relayed
alt mined by a plain miner
Plain->>Plain: includes M8, keeps the fee
Plain--xSC: no M7 in coinbase — sidechain does NOT advance
else mined by an enforcer-driven miner
EnfM->>EnfM: includes M8 AND appends M7 to its coinbase
EnfM-->>SC: commitment found; block connects
end
Raising the bribe on an M8 does not persuade a plain miner to help — they structurally cannot emit M7. A higher fee only matters when competing against other enforcer-driven miners for the single M7 slot that sidechain gets in that block.
The enforcer keys pending requests on (prev_mainchain_block_hash, sidechain_number), so two M8s for the same parent and slot are recorded as conflicting. One can win; the rest are dead weight.
A sidechain node bidding into a network with no enforcer-driven miners produces exactly this, block after block — the request is made, relayed, and never accepted:
INFO attempt BMM: created TX: 436ed0e0…
WARN confirm BMM: received new block without our BMM commitment
side_hash=26817a96… main_height=996455 bmm_commitment=none
bmm_commitment=none is the tell: a mainchain block arrived carrying no M7 for this slot at all. Nothing about the bid was wrong; nobody was in a position to accept it.
Given a mainchain block, the enforcer answers "what happened to the drivechains?" in two passes, and they are not interchangeable:
| Pass | Where it looks | What it finds |
|---|---|---|
| 1 | Every output of the coinbase transaction, parsed as a CoinbaseMessage |
M1, M2, M3, M4, M7 — proposals, votes, and this block's BMM accepts |
| 2 | The block's ordinary transactions, matched by shape | M5 and M6 by their OP_DRIVECHAIN treasury output; M8 by its tagged OP_RETURN |
An M8 found in pass 2 with no matching M7 from pass 1 raises NotAcceptedByMiners — but that error is explicitly non-fatal. The mainchain block stays valid and the transaction's fee is collected; the M8 simply is not treated as a BMM request, and the sidechain does not advance. Two M7s for the same slot in one coinbase are a different matter: MultipleBmmBlocks is fatal and the block is rejected.