Scenario 4: A CTV vault on two chains with different rules¶
Goal: Run the same covenant on a chain with CTV and on a chain without CTV. Find that the chain without CTV does not enforce the covenant, because there the opcode is still NOP4.
#lang bitcoin/model
;; Scenario 4: a CTV vault on two chains with different rules.
(require rackunit)
(define-consensus ctv-rules #:extends bitcoin
#:opcodes (upgrade nop4 #:to ctv))
(chain mainnet #:rules bitcoin)
(chain signet #:rules ctv-rules)
(keys alice cold mallory)
(check-equal? (format "~s" (diff-consensus bitcoin ctv-rules)) "((opcode #xb3 nop4 -> ctv))")
(define tmpl
(template #:outputs (list (output 'to-cold (wpkh cold) (btc 49.98)))))
(contract vault () (ctv tmpl))
(define results
(for/list ([ch (list mainnet signet)])
(define cb (first (mine 1 #:on ch #:to alice)))
(void (mine 100 #:on ch))
(define-tx lock
#:inputs ([cb #:sign alice])
#:outputs ([vaulted (vault) (btc 49.99)]))
(confirm lock)
(list (try (spend vaulted ; thief ignores the template
#:outputs (list (output 'stolen (wpkh mallory) (btc 49.98)))))
(try (spend vaulted ; the honest spend follows it
#:outputs (list (output 'to-cold (wpkh cold) (btc 49.98))))))))
(define thief (map first results))
(check-pred accepted? (first thief)) ; covenant unenforced
(check-equal? (rejected-rule (second thief)) 'ctv-template-mismatch)
(check-equal? (rejected-chain (second thief)) 'signet)
(check-true (andmap accepted? (map second results)))
(check-equal? (audit (vault) #:on mainnet)
'((warning #:rule-unenforced ctv #:chain mainnet #:runs-as nop4)))
(check-equal? (audit (vault) #:on signet) '())
What occurs¶
- The rule set.
define-consensusmakesctv-rules: bitcoin with NOP4 changed to CTV.diff-consensusshows this one change. See Consensus as a value. - The vault.
templatedescribes the only transaction that can spend the coin: 49.98 BTC tocold.(contract vault () (ctv tmpl))locks a coin to the template. -
Two chains. The same code runs on both chains in a loop.
- The spend of the thief does not obey the template.
mainnetaccepts it, because there thectvof the script runs as NOP4.explainshows(op ctv #:as nop4 …). -
signetrejects it:(rejected #:chain signet #:rule ctv-template-mismatch #:input 0 #:fields ((outputs all)) #:expected (…) #:got (…) …) -
Both chains accept the honest spend.
- The spend of the thief does not obey the template.
-
The audit.
auditgives a warning before you trust the vault on mainnet:((warning #:rule-unenforced ctv #:chain mainnet #:runs-as nop4)). On signet, it returns().
The same method applies to each soft-fork proposal. CAT, CSFS, TXHASH or a new sighash mode is a proposal opcode or a selector, with a define-consensus.
Against real nodes¶
tests/replay-4.rkt replays the log two times:
| Targets | Result |
|---|---|
| mainnet on Core, signet on Bitcoin Inquisition | 14 confirmed. The mempool of Core refuses the NOP4 spend of the thief, because it is not standard. A block can include it, as the model says (#:mempool-only). |
| Both chains on Core | 12 confirmed and 2 unverified. The two signet spends that run CTV return (model-only-rule ctv). |
Forms in this scenario: define-consensus, #:extends, upgrade, diff-consensus, template, (ctv t), audit, more than one chain in a session.