Contract Branch Executions

The DSL makes it easy to run the transactions along the various execution paths of a contract.


The challenge when developing contracts is to execute the various branches the contracts can execute on. The branches that are executed depend on the state of the chain and the data provided by the parties executing the contract transactions.

With the DSL a developer can declaratively specify state transitions and then run a sequence of transitions to exercise the various branches of a contract.

state_transition and run_transitions are new DSL commands introduced to enable contract composition and execution along branches.

The execution paths are best described with an simple contract that has two branches - a simple timelock contract.

Example: A Timelock Contract

Let’s say there is a contract where Alice can spend the transaction at any time and Bob can only spend the transaction after 10 blocks. Using the miniscript policy 'or(and(older(10),pk(@alice)),pk(@bob))'. Such a contract has the following state transitions.

Diagram
Figure 1. State transitions for a contract using 'or(and(older(10),pk(@alice)),pk(@bob))' miniscript policy.

The state transition of the contract above shows how even in a simple contract, we need to be careful when exploring all execution paths. We need to verify the following behaviours:

  1. Bob can immediately spend from confirmation.

  2. Bob can spend from confirmation after delay has expired.

  3. Alice can not spend before delay has expired.

  4. Alice can spend after delay has expired.

The pain point the Bitcoin DSL solves is the ability to run through all the states and then run assertions at each state about the state of the system.

Executing the State Transition Diagram

The scripts below show how we can transition the system from one state to the other and then run assertions for the transition.

Setup

We first setup the keys, generate a confirmation transaction and get 100 confirmations for it.

state_transition :setup do
  # Generate new keys
  @alice = key :new
  @bob = key :new

  # Mine to an address with miniscript policy with CSV
  extend_chain num_blocks: 1, policy: 'or(and(older(10),pk(@alice)),pk(@bob))' (1)

  # Make coinbase at block 1 spendable
  extend_chain num_blocks: 100 (2)
  @alice_coinbase_tx = get_coinbase_at 1
end
1 Use miniscript policy as coinbase
2 Extend chain such that the coinbase is spendable

Bob Spends Immediately

state_transition :bob_spends_immediately do
  # Load the coinbase with miniscript policy
  @csv_tx = transaction inputs: [
                          {
                            tx: @alice_coinbase_tx,
                            vout: 0,
                            script_sig: 'sig:@bob' (1)
                          }
                        ],
                        outputs: [
                          {
                            descriptor: 'wpkh(@alice)',
                            amount: 49.998.sats
                          }
                        ]

  assert_mempool_accept @csv_tx

  broadcast @csv_tx (2)
  extend_chain (2)
end
1 Bob signs the transaction to spend it immediately
2 The spending transaction is broadcast and confirmed

Next we run the state transitions that lead to Bob spending the coinbase immediately - without waiting for 10 blocks.

run_transitions :setup, :bob_spends_immediately

Alice Can Not Spend Immediately

We run assertions to check that Alice can’t spend the transaction immediately.

state_transition :alice_cant_spend do
  # Load the coinbase with miniscript policy
  @csv_tx = transaction inputs: [
                          {
                            tx: @alice_coinbase_tx,
                            vout: 0,
                            script_sig: 'sig:@alice ""' (1)
                          }
                        ],
                        outputs: [
                          {
                            descriptor: 'wpkh(@alice)',
                            amount: 49.998.sats
                          }
                        ]

  assert_not_mempool_accept @csv_tx (2)
end
1 Alice signs the transaction to spend it immediately
2 Alice’s attempt is spend is not accepted by the mempool

Next we run the transitions we want to execute the branch where we check Alice can’t spend the transaction immediately.

run_transitions :reset, :setup, :alice_cant_spend

Alice Can Spend After a Delay

state_transition :alice_spends_after_delay do
  # Load the coinbase with miniscript policy
  @csv_tx = transaction inputs: [
                          {
                            tx: @alice_coinbase_tx,
                            vout: 0,
                            script_sig: 'sig:@alice ""', (1)
                            csv: 10 (2)
                          }
                        ],
                        outputs: [
                          {
                            descriptor: 'wpkh(@alice)',
                            amount: 49.998.sats
                          }
                        ]

  extend_chain num_blocks: 10 (3)

  broadcast @csv_tx (4)
  extend_chain
end
1 Alice signs the transaction
2 Set CSV for the transaction
3 Extend chain by mining 10 blocks
4 Alice’s spending transaction is broadcast and confirmed

Finally we run the transitions we want to execute the branch where we check Alice spends the transaction after a delay.

run_transitions :reset, :setup, :alice_spends_after_delay

Bob Spends After Delay

For completeness sake, we also cover the final case where Bob can spend the CSV UTXO after the timeout period.

state_transition :bob_spends_after_delay do
  # Load the coinbase with miniscript policy
  @csv_tx = transaction inputs: [
                          {
                            tx: @alice_coinbase_tx,
                            vout: 0,
                            script_sig: 'sig:@bob' (1)
                          }
                        ],
                        outputs: [
                          {
                            descriptor: 'wpkh(@alice)',
                            amount: 49.998.sats
                          }
                        ]

  extend_chain num_blocks: 10 (2)

  broadcast @csv_tx (3)
  extend_chain
end
run_transitions :reset, :setup, :bob_spends_after_delay (4)
1 Bob signs the transaction to spend it immediately
2 Extend chain by mining 10 blocks
3 The spending transaction is broadcast and confirmed
4 Finally, we run the required transitions