diff --git a/.aspell.en.pws b/.aspell.en.pws
index 97b6ebdbd..e897f76bb 100644
--- a/.aspell.en.pws
+++ b/.aspell.en.pws
@@ -367,7 +367,12 @@ fips
rfc
varint
CompactSize
+multipath
+mpp
tlvs
snprintf
GitHub
IRC
+bitmasks
+CSPRNG
+lexicographically
diff --git a/01-messaging.md b/01-messaging.md
index 90cbaa45b..ef4768220 100644
--- a/01-messaging.md
+++ b/01-messaging.md
@@ -51,12 +51,13 @@ A receiving node:
- upon receiving a message of _even_, unknown type:
- MUST fail the channels.
-The messages are grouped logically into four groups, ordered by the most significant bit that is set:
+The messages are grouped logically into five groups, ordered by the most significant bit that is set:
- Setup & Control (types `0`-`31`): messages related to connection setup, control, supported features, and error reporting (described below)
- Channel (types `32`-`127`): messages used to setup and tear down micropayment channels (described in [BOLT #2](02-peer-protocol.md))
- Commitment (types `128`-`255`): messages related to updating the current commitment transaction, which includes adding, revoking, and settling HTLCs as well as updating fees and exchanging signatures (described in [BOLT #2](02-peer-protocol.md))
- Routing (types `256`-`511`): messages containing node and channel announcements, as well as any active route exploration (described in [BOLT #7](07-routing-gossip.md))
+ - Custom (types `32768`-`65535`): experimental and application-specific messages
The size of the message is required by the transport layer to fit into a 2-byte unsigned int; therefore, the maximum possible size is 65535 bytes.
@@ -66,6 +67,13 @@ A node:
- MUST fail the channels.
- that negotiates an option in this specification:
- MUST include all the fields annotated with that option.
+ - When defining custom messages:
+ - SHOULD pick a random `type` to avoid collision with other custom types.
+ - SHOULD pick a `type` that doesn't conflict with other experiments listed in [this issue](https://github.com/lightningnetwork/lightning-rfc/issues/716).
+ - SHOULD pick an odd `type` identifiers when regular nodes should ignore the
+ additional data.
+ - SHOULD pick an even `type` identifiers when regular nodes should reject
+ the message and close the connection.
### Rationale
@@ -100,7 +108,7 @@ A `tlv_record` represents a single field, encoded in the form:
A `varint` is a variable-length, unsigned integer encoding using the
[BigSize](#appendix-a-bigsize-test-vectors) format, which resembles the bitcoin
CompactSize encoding but uses big-endian for multi-byte values rather than
-little-endian.
+little-endian.
A `tlv_stream` is a series of (possibly zero) `tlv_record`s, represented as the
concatenation of the encoded `tlv_record`s. When used to extend existing
@@ -214,7 +222,7 @@ integers can be omitted:
The following convenience types are also defined:
* `chain_hash`: a 32-byte chain identifier (see [BOLT #0](00-introduction.md#glossary-and-terminology-guide))
-* `channel_id`: a 32-byte channel_id (see [BOLT #2](02-peer-protocol.md#definition-of-channel-id)
+* `channel_id`: a 32-byte channel_id (see [BOLT #2](02-peer-protocol.md#definition-of-channel-id))
* `sha256`: a 32-byte SHA2-256 hash
* `signature`: a 64-byte bitcoin Elliptic Curve signature
* `point`: a 33-byte Elliptic Curve point (compressed encoding as per [SEC 1 standard](http://www.secg.org/sec1-v2.pdf#subsubsection.2.3.3))
@@ -226,18 +234,26 @@ The following convenience types are also defined:
Once authentication is complete, the first message reveals the features supported or required by this node, even if this is a reconnection.
-[BOLT #9](09-features.md) specifies lists of global and local features. Each feature is generally represented in `globalfeatures` or `localfeatures` by 2 bits. The least-significant bit is numbered 0, which is _even_, and the next most significant bit is numbered 1, which is _odd_.
+[BOLT #9](09-features.md) specifies lists of features. Each feature is generally represented by 2 bits. The least-significant bit is numbered 0, which is _even_, and the next most significant bit is numbered 1, which is _odd_. For historical reasons, features are divided into global and local feature bitmasks.
-Both fields `globalfeatures` and `localfeatures` MUST be padded to bytes with 0s.
+The `features` field MUST be padded to bytes with 0s.
1. type: 16 (`init`)
2. data:
* [`u16`:`gflen`]
* [`gflen*byte`:`globalfeatures`]
- * [`u16`:`lflen`]
- * [`lflen*byte`:`localfeatures`]
+ * [`u16`:`flen`]
+ * [`flen*byte`:`features`]
+ * [`init_tlvs`:`tlvs`]
-The 2-byte `gflen` and `lflen` fields indicate the number of bytes in the immediately following field.
+1. tlvs: `init_tlvs`
+2. types:
+ 1. type: 1 (`networks`)
+ 2. data:
+ * [`...*chain_hash`:`chains`]
+
+
+The optional `networks` indicates the chains the node is interested in.
#### Requirements
@@ -245,26 +261,37 @@ The sending node:
- MUST send `init` as the first Lightning message for any connection.
- MUST set feature bits as defined in [BOLT #9](09-features.md).
- MUST set any undefined feature bits to 0.
- - SHOULD use the minimum lengths required to represent the feature fields.
+ - SHOULD NOT set features greater than 13 in `globalfeatures`.
+ - SHOULD use the minimum length required to represent the `features` field.
+ - SHOULD set `networks` to all chains it will gossip or open channels for.
The receiving node:
- MUST wait to receive `init` before sending any other messages.
+ - MUST combine (logical OR) the two feature bitmaps into one logical `features` map.
- MUST respond to known feature bits as specified in [BOLT #9](09-features.md).
- upon receiving unknown _odd_ feature bits that are non-zero:
- MUST ignore the bit.
- upon receiving unknown _even_ feature bits that are non-zero:
- MUST fail the connection.
+ - upon receiving `networks` containing no common chains
+ - MAY fail the connection.
+ - if the feature vector does not set all known, transitive dependencies:
+ - MUST fail the connection.
#### Rationale
+There used to be two feature bitfields here, but for backwards compatibility they're now
+combined into one.
+
This semantic allows both future incompatible changes and future backward compatible changes. Bits should generally be assigned in pairs, in order that optional features may later become compulsory.
Nodes wait for receipt of the other's features to simplify error
diagnosis when features are incompatible.
-The feature masks are split into local features (which only affect the
-protocol between these two nodes) and global features (which can affect
-HTLCs and are thus also advertised to other nodes).
+Since all networks share the same port, but most implementations only
+support a single network, the `networks` fields avoids nodes
+erroneously believing they will receive updates about their preferred
+network, or that they can open channels.
### The `error` Message
@@ -875,7 +902,7 @@ failure:
## References
-1. http://www.unicode.org/charts/PDF/U2600.pdf
+1. http://www.unicode.org/charts/PDF/U2600.pdf
## Authors
diff --git a/02-peer-protocol.md b/02-peer-protocol.md
index 4c94c5408..66d539e5e 100644
--- a/02-peer-protocol.md
+++ b/02-peer-protocol.md
@@ -6,7 +6,7 @@ operation, and closing.
# Table of Contents
* [Channel](#channel)
- * [Definition of `channel_id`](#definition-of-channel-id)
+ * [Definition of `channel_id`](#definition-of-channel_id)
* [Channel Establishment](#channel-establishment)
* [The `open_channel` Message](#the-open_channel-message)
* [The `accept_channel` Message](#the-accept_channel-message)
@@ -187,16 +187,22 @@ The `shutdown_scriptpubkey` allows the sending node to commit to where
funds will go on mutual close, which the remote node should enforce
even if a node is compromised later.
-[ FIXME: Describe dangerous feature bit for larger channel amounts. ]
+The `option_support_large_channel` is a feature used to let everyone
+know this node will accept `funding_satoshis` greater than or equal to 2^24.
+Since it's broadcast in the `node_announcement` message other nodes can use it to identify peers
+willing to accept large channel even before exchanging the `init` message with them.
#### Requirements
The sending node:
- MUST ensure the `chain_hash` value identifies the chain it wishes to open the channel within.
- MUST ensure `temporary_channel_id` is unique from any other channel ID with the same peer.
- - MUST set `funding_satoshis` to less than 2^24 satoshi.
+ - if both nodes advertised `option_support_large_channel`:
+ - MAY set `funding_satoshis` greater than or equal to 2^24 satoshi.
+ - otherwise:
+ - MUST set `funding_satoshis` to less than 2^24 satoshi.
- MUST set `push_msat` to equal or less than 1000 * `funding_satoshis`.
- - MUST set `funding_pubkey`, `revocation_basepoint`, `htlc_basepoint`, `payment_basepoint`, and `delayed_payment_basepoint` to valid DER-encoded, compressed, secp256k1 pubkeys.
+ - MUST set `funding_pubkey`, `revocation_basepoint`, `htlc_basepoint`, `payment_basepoint`, and `delayed_payment_basepoint` to valid secp256k1 pubkeys in compressed format.
- MUST set `first_per_commitment_point` to the per-commitment point to be used for the initial commitment transaction, derived as specified in [BOLT #3](03-transactions.md#per-commitment-secret-requirements).
- MUST set `channel_reserve_satoshis` greater than or equal to `dust_limit_satoshis`.
- MUST set undefined bits in `channel_flags` to 0.
@@ -234,19 +240,18 @@ The receiving node MUST fail the channel if:
- `max_accepted_htlcs` is greater than 483.
- it considers `feerate_per_kw` too small for timely processing or unreasonably large.
- `funding_pubkey`, `revocation_basepoint`, `htlc_basepoint`, `payment_basepoint`, or `delayed_payment_basepoint`
-are not valid DER-encoded compressed secp256k1 pubkeys.
+are not valid secp256k1 pubkeys in compressed format.
- `dust_limit_satoshis` is greater than `channel_reserve_satoshis`.
- the funder's amount for the initial commitment transaction is not sufficient for full [fee payment](03-transactions.md#fee-payment).
- both `to_local` and `to_remote` amounts for the initial commitment transaction are less than or equal to `channel_reserve_satoshis` (see [BOLT 3](03-transactions.md#commitment-transaction-outputs)).
+ - `funding_satoshis` is greater than or equal to 2^24 and the receiver does not support `option_support_large_channel`.
The receiving node MUST NOT:
- consider funds received, using `push_msat`, to be received until the funding transaction has reached sufficient depth.
#### Rationale
-The requirement for `funding_satoshi` to be less than 2^24 satoshi is a temporary self-imposed limit while implementations are not yet considered stable.
-It can be lifted at any point in time, or adjusted for other currencies, since it is solely enforced by the endpoints of a channel.
-Specifically, [the routing gossip protocol](07-routing-gossip.md) does not discard channels that have a larger capacity.
+The requirement for `funding_satoshis` to be less than 2^24 satoshi was a temporary self-imposed limit while implementations were not yet considered stable, it can be lifted by advertising `option_support_large_channel`.
The *channel reserve* is specified by the peer's `channel_reserve_satoshis`: 1% of the channel total is suggested. Each side of a channel maintains this reserve so it always has something to lose if it were to try to broadcast an old, revoked commitment transaction. Initially, this reserve may not be met, as only one side has funds; but the protocol ensures that there is always progress toward meeting this reserve, and once met, it is maintained.
@@ -264,12 +269,6 @@ are above both `dust_limit_satoshis`.
Details for how to handle a channel failure can be found in [BOLT 5:Failing a Channel](05-onchain.md#failing-a-channel).
-#### Future
-
-It would be easy to have a local feature bit which indicated that a
-receiving node was prepared to fund a channel, which would reverse this
-protocol.
-
### The `accept_channel` Message
This message contains information about a node and indicates its
@@ -425,11 +424,6 @@ funds are at risk. If the fundee were to remember the channel forever, this
would create a Denial of Service risk; therefore, forgetting it is recommended
(even if the promise of `push_msat` is significant).
-#### Future
-
-An SPV proof could be added and block hashes could be routed in separate
-messages.
-
## Channel Close
Nodes can negotiate a mutual close of the connection, which unlike a
diff --git a/03-transactions.md b/03-transactions.md
index c72bd0981..d01bee93b 100644
--- a/03-transactions.md
+++ b/03-transactions.md
@@ -62,7 +62,7 @@ Most transaction outputs used here are pay-to-witness-script-hash[BIP141](h
`2 2 OP_CHECKMULTISIG`
-* Where `pubkey1` is the numerically lesser of the two DER-encoded `funding_pubkey` and where `pubkey2` is the numerically greater of the two.
+* Where `pubkey1` is the lexicographically lesser of the two `funding_pubkey` in compressed format, and where `pubkey2` is the lexicographically greater of the two.
## Commitment Transaction
@@ -575,7 +575,7 @@ A double-check, that all previous secrets derive correctly, is needed;
if this check fails, the secrets were not generated from the same seed:
insert_secret(secret, I):
- B = where_to_put_secret(secret, I)
+ B = where_to_put_secret(I)
# This tracks the index of the secret in each bucket across the traversal.
for b in 0 to B:
diff --git a/04-onion-routing.md b/04-onion-routing.md
index 17851c7a8..5bdf88d2b 100644
--- a/04-onion-routing.md
+++ b/04-onion-routing.md
@@ -48,7 +48,12 @@ A node:
* [Key Generation](#key-generation)
* [Pseudo Random Byte Stream](#pseudo-random-byte-stream)
* [Packet Structure](#packet-structure)
+ * [Legacy HopData Payload Format](#legacy-hop_data-payload-format)
+ * [TLV Payload Format](#tlv_payload-format)
+ * [Basic Multi-Part Payments](#basic-multi-part-payments)
+ * [Accepting and Forwarding a Payment](#accepting-and-forwarding-a-payment)
* [Payload for the Last Node](#payload-for-the-last-node)
+ * [Non-strict Forwarding](#non-strict-forwarding)
* [Shared Secret](#shared-secret)
* [Blinding Ephemeral Keys](#blinding-ephemeral-keys)
* [Packet Construction](#packet-construction)
@@ -101,14 +106,17 @@ A number of encryption and verification keys are derived from the shared secret:
to obfuscate the per-hop information
- _mu_: used during the HMAC generation
- _um_: used during error reporting
+ - _pad_: use to generate random filler bytes for the starting mix-header
+ packet
-The key generation function takes a key-type (_rho_=`0x72686F`, _mu_=`0x6d75`,
-or _um_=`0x756d`) and a 32-byte secret as inputs and returns a 32-byte key.
+The key generation function takes a key-type (_rho_=`0x72686F`, _mu_=`0x6d75`,
+_um_=`0x756d`, or _pad_=`0x706164`) and a 32-byte secret as inputs and returns
+a 32-byte key.
Keys are generated by computing an HMAC (with `SHA256` as hashing algorithm)
-using the appropriate key-type (i.e. _rho_, _mu_, or _um_) as HMAC-key and the
-32-byte shared secret as the message. The resulting HMAC is then returned as the
-key.
+using the appropriate key-type (i.e. _rho_, _mu_, _um_, or _pad_) as HMAC-key
+and the 32-byte shared secret as the message. The resulting HMAC is then
+returned as the key.
Notice that the key-type does not include a C-style `0x00`-termination-byte,
e.g. the length of the _rho_ key-type is 3 bytes, not 4.
@@ -132,7 +140,7 @@ The packet consists of four sections:
generation
- a 1300-byte `hop_payloads` consisting of multiple, variable length,
`hop_payload` payloads or up to 20 fixed sized legacy `hop_data` payloads.
- - a 32-byte `HMAC`, used to verify the packet's integrity
+ - a 32-byte `hmac`, used to verify the packet's integrity
The network format of the packet consists of the individual sections
serialized into one contiguous byte-stream and then transferred to the packet
@@ -157,11 +165,11 @@ It is 1300 bytes long and has the following structure:
2. data:
* [`varint`:`length`]
* [`hop_payload_length`:`hop_payload`]
- * [`32*byte`:`HMAC`]
+ * [`32*byte`:`hmac`]
* ...
* `filler`
-Where, the `length`, `hop_payload` (with contents dependent on `length`), and `HMAC` are repeated for each hop;
+Where, the `length`, `hop_payload` (with contents dependent on `length`), and `hmac` are repeated for each hop;
and where, `filler` consists of obfuscated, deterministically-generated padding, as detailed in [Filler Generation](#filler-generation).
Additionally, `hop_payloads` is incrementally obfuscated at each hop.
@@ -198,7 +206,7 @@ Field descriptions:
* `amt_to_forward`: The amount, in millisatoshis, to forward to the next
receiving peer specified within the routing information.
- This value amount MUST include the origin node's computed _fee_ for the
+ For non-final nodes, this value amount MUST include the origin node's computed _fee_ for the
receiving peer. When processing an incoming Sphinx packet and the HTLC
message that it is encapsulated within, if the following inequality doesn't hold,
then the HTLC should be rejected as it would indicate that a prior hop has
@@ -206,9 +214,11 @@ Field descriptions:
incoming_htlc_amt - fee >= amt_to_forward
- Where `fee` is either calculated according to the receiving peer's advertised fee
- schema (as described in [BOLT #7](07-routing-gossip.md#htlc-fees))
- or is 0, if the processing node is the final node.
+ Where `fee` is calculated according to the receiving peer's advertised fee
+ schema (as described in [BOLT #7](07-routing-gossip.md#htlc-fees).
+
+ For the final node, this value MUST be exactly equal to the incoming htlc
+ amount, otherwise the HTLC should be rejected.
* `outgoing_cltv_value`: The CLTV value that the _outgoing_ HTLC carrying
the packet should have.
@@ -233,7 +243,7 @@ When forwarding HTLCs, nodes MUST construct the outgoing HTLC as specified
within `hop_data` above; otherwise, deviation from the specified HTLC
parameters may lead to extraneous routing failure.
-### `tlv_payload` payload format
+### `tlv_payload` format
This is a more flexible format, which avoids the redundant `short_channel_id` field for the final node.
@@ -248,18 +258,113 @@ This is a more flexible format, which avoids the redundant `short_channel_id` fi
1. type: 6 (`short_channel_id`)
2. data:
* [`short_channel_id`:`short_channel_id`]
+ 1. type: 8 (`payment_data`)
+ 2. data:
+ * [`32*byte`:`payment_secret`]
+ * [`tu64`:`total_msat`]
### Requirements
The writer:
- - MUST include `amt_to_forward` and `outgoing_cltv_value` for every node.
- - MUST include `short_channel_id` for every non-final node.
- - MUST NOT include `short_channel_id` for the final node.
+ - Unless `node_announcement`, `init` message or the [BOLT #11](11-payment-encoding.md#tagged-fields) offers feature `var_onion_optin`:
+ - MUST use the legacy payload format instead.
+ - For every node:
+ - MUST include `amt_to_forward` and `outgoing_cltv_value`.
+ - For every non-final node:
+ - MUST include `short_channel_id`
+ - MUST NOT include `payment_data`
+ - For the final node:
+ - MUST NOT include `short_channel_id`
+ - if the recipient provided `payment_secret`:
+ - MUST include `payment_data`
+ - MUST set `payment_secret` to the one provided
+ - MUST set `total_msat` to the total amount it will send
The reader:
- MUST return an error if `amt_to_forward` or `outgoing_cltv_value` are not present.
+ - if it is the final node:
+ - MUST treat `total_msat` as if it were equal to `amt_to_forward` if it
+ is not present.
+
+The requirements for the contents of these fields are specified [above](#legacy-hop_data-payload-format)
+and [below](#basic-multi-part-payments).
-The requirements for the contents of these fields are specified [above](#legacy-hop_data-payload-format).
+### Basic Multi-Part Payments
+
+An HTLC may be part of a larger "multi-part" payment: such
+"base" atomic multipath payments will use the same `payment_hash` for
+all paths.
+
+Note that `amt_to_forward` is the amount for this HTLC only: a
+`total_msat` field containing a greater value is a promise by the
+ultimate sender that the rest of the payment will follow in succeeding
+HTLCs; we call these outstanding HTLCs which have the same preimage,
+an "HTLC set".
+
+#### Requirements
+
+The writer:
+ - if the invoice offers the `basic_mpp` feature:
+ - MAY send more than one HTLC to pay the invoice.
+ - MUST use the same `payment_hash` on all HTLCs in the set.
+ - SHOULD send all payments at approximately the same time.
+ - SHOULD try to use diverse paths to the recipient for each HTLC.
+ - SHOULD retry and/or re-divide HTLCs which fail.
+ - if the invoice specifies an `amount`:
+ - MUST set `total_msat` to at least that `amount`, and less
+ than or equal to twice `amount`.
+ - otherwise:
+ - MUST set `total_msat` to the amount it wishes to pay.
+ - MUST ensure that the total `amount_msat` of the HTLC set which arrives at the payee
+ is equal to `total_msat`.
+ - MUST NOT send another HTLC if the total `amount_msat` of the HTLC set is already greater or equal to `total_msat`.
+ - MUST include `payment_secret`.
+ - otherwise:
+ - MUST set `total_msat` equal to `amt_to_forward`.
+
+The final node:
+ - MUST fail the HTLC if dictated by Requirements under [Failure Messages](#failure-messages)
+ - Note: "amount paid" specified there is the `total_msat` field.
+ - if it does not support `basic_mpp`:
+ - MUST fail the HTLC if `total_msat` is not exactly equal to `amt_to_forward`.
+ - otherwise, if it supports `basic_mpp`:
+ - MUST add it to the HTLC set corresponding to that `payment_hash`.
+ - SHOULD fail the entire HTLC set if `total_msat` is not the same for
+ all HTLCs in the set.
+ - if the total `amount_msat` of this HTLC set equals `total_msat`:
+ - SHOULD fulfill all HTLCs in the HTLC set
+ - otherwise, if the total `amount_msat` of this HTLC set is less than
+ `total_msat`:
+ - MUST NOT fulfill any HTLCs in the HTLC set
+ - MUST fail all HTLCs in the HTLC set after some reasonable timeout.
+ - SHOULD wait for at least 60 seconds after the initial HTLC.
+ - SHOULD use `mpp_timeout` for the failure message.
+ - MUST require `payment_secret` for all HTLCs in the set.
+ - if it fulfills any HTLCs in the HTLC set:
+ - MUST fulfill the entire HTLC set.
+
+#### Rationale
+
+If `basic_mpp` is present it causes a delay to allow other partial
+payments to combine. The total amount must be sufficient for the
+desired payment, just as it must be for single payments. But this must
+be reasonably bounded to avoid a denial-of-service.
+
+Because invoices do not necessarily specify an amount, and because
+payers can add noise to the final amount, the total amount must be
+sent explicitly. The requirements allow exceeding this slightly, as
+it simplifies adding noise to the amount when splitting, as well as
+scenarios in which the senders are genuinely independent (friends
+splitting a bill, for example).
+
+The restriction on sending an HTLC once the set is over the agreed total prevents the preimage being released before all
+the partial payments have arrived: that would allow any intermediate
+node to immediately claim any outstanding partial payments.
+
+An implementation may choose not to fulfill an HTLC set which
+otherwise meets the amount criterion (eg. some other failure, or
+invoice timeout), however if it were to fulfill only some of them,
+intermediary nodes could simply claim the remaining ones.
# Accepting and Forwarding a Payment
@@ -313,6 +418,8 @@ using an alternate channel.
When building the route, the origin node MUST use a payload for
the final node with the following values:
+* `payment_secret`: set to the payment secret specified by the recipient (e.g.
+ `payment_secret` from a [BOLT #11](11-payment-encoding.md) payment invoice)
* `outgoing_cltv_value`: set to the final expiry specified by the recipient (e.g.
`min_final_cltv_expiry` from a [BOLT #11](11-payment-encoding.md) payment invoice)
* `amt_to_forward`: set to the final amount specified by the recipient (e.g. `amount`
@@ -334,7 +441,7 @@ simply discard its payload.
The origin node establishes a shared secret with each hop along the route using
Elliptic-curve Diffie-Hellman between the sender's ephemeral key at that hop and
the hop's node ID key. The resulting curve point is serialized to the
-DER-compressed representation and hashed using `SHA256`. The hash output is used
+compressed format and hashed using `SHA256`. The hash output is used
as the 32-byte shared secret.
Elliptic-curve Diffie-Hellman (ECDH) is an operation on an EC private key and
@@ -402,9 +509,15 @@ The construction returns a single 1366-byte packet along with the first receivin
The packet construction is performed in the reverse order of the route, i.e.
the last hop's operations are applied first.
-The packet is initialized with 1366 `0x00`-bytes.
+The packet is initialized with 1300 _random_ bytes derived from a CSPRNG
+(ChaCha20). The _pad_ key referenced above is used to extract additional random
+bytes from a ChaCha20 stream, using it as a CSPRNG for this purpose. Once the
+`paddingKey` has been obtained, ChaCha20 is used with an all zero nonce, to
+generate 1300 random bytes. Those random bytes are then used as the starting
+state of the mix-header to be created.
-A filler is generated (see [Filler Generation](#filler-generation)) using the shared secret.
+A filler is generated (see [Filler Generation](#filler-generation)) using the
+shared secret.
For each hop in the route, in reverse order, the sender applies the
following operations:
@@ -413,7 +526,7 @@ following operations:
- `shift_size` is defined as the length of the `hop_payload` plus the varint encoding of the length and the length of that HMAC. Thus if the payload length is `l` then the `shift_size` is `1 + l + 32` for `l < 253`, otherwise `3 + l + 32` due to the varint encoding of `l`.
- The `hop_payload` field is right-shifted by `shift_size` bytes, discarding the last `shift_size`
bytes that exceed its 1300-byte size.
- - The varint-serialized length, serialized `hop_payload` and `HMAC` are copied into the following `shift_size` bytes.
+ - The varint-serialized length, serialized `hop_payload` and `hmac` are copied into the following `shift_size` bytes.
- The _rho_-key is used to generate 1300 bytes of pseudo-random byte stream
which is then applied, with `XOR`, to the `hop_payloads` field.
- If this is the last hop, i.e. the first iteration, then the tail of the
@@ -469,6 +582,12 @@ func NewOnionPacket(paymentPath []*btcec.PublicKey, sessionKey *btcec.PrivateKey
// Allocate and initialize fields to zero-filled slices
var mixHeader [routingInfoSize]byte
var nextHmac [hmacSize]byte
+
+ // Our starting packet needs to be filled out with random bytes, we
+ // generate some determinstically using the session private key.
+ paddingKey := generateKey("pad", sessionKey.Serialize()
+ paddingBytes := generateCipherStream(paddingKey, routingInfoSize)
+ copy(mixHeader[:], paddingBytes)
// Compute the routing information for each hop along with a
// MAC of the routing information using the shared key for that hop.
@@ -546,7 +665,7 @@ generates `2*1300` pseudo-random bytes (using the _rho_-key), and applies the re
The first few bytes correspond to the varint-encoded length `l` of the `hop_payload`, followed by `l` bytes of the resulting routing information become the `hop_payload`, and the 32 byte HMAC.
The next 1300 bytes are the `hop_payloads` for the outgoing packet.
-A special `HMAC` value of 32 `0x00`-bytes indicates that the currently processing hop is the intended recipient and that the packet should not be forwarded.
+A special `hmac` value of 32 `0x00`-bytes indicates that the currently processing hop is the intended recipient and that the packet should not be forwarded.
If the HMAC does not indicate route termination, and if the next hop is a peer of the
processing node; then the new packet is assembled. Packet assembly is accomplished
@@ -827,9 +946,10 @@ handling by the processing node.
* [`u64`:`htlc_msat`]
* [`u32`:`height`]
-The `payment_hash` is unknown to the final node, the amount for that
-`payment_hash` is incorrect or the CLTV expiry of the htlc is too close to the
-current block height for safe handling.
+The `payment_hash` is unknown to the final node, the `payment_secret` doesn't
+match the `payment_hash`, the amount for that `payment_hash` is incorrect or
+the CLTV expiry of the htlc is too close to the current block height for safe
+handling.
The `htlc_msat` parameter is superfluous, but left in for backwards
compatibility. The value of `htlc_msat` always matches the amount specified in
@@ -844,12 +964,15 @@ between sending a payment with the wrong final CLTV expiry and an intermediate
hop delaying the payment so that the receiver's invoice CLTV delta requirement
is no longer met.
-Note: Originally PERM|16 (`incorrect_payment_amount`) and PERM|17
+Note: Originally PERM|16 (`incorrect_payment_amount`) and 17
(`final_expiry_too_soon`) were used to differentiate incorrect htlc parameters
from unknown payment hash. Sadly, sending this response allows for probing
attacks whereby a node which receives an HTLC for forwarding can check guesses
as to its final destination by sending payments with the same hash but much
lower values or expiry heights to potential destinations and check the response.
+Care must be taken by implementations to differentiate the previously
+non-permanent case for `final_expiry_too_soon` (17) from the other, permanent
+failures now represented by `incorrect_or_unknown_payment_details` (PERM|15).
1. type: 18 (`final_incorrect_cltv_expiry`)
2. data:
@@ -885,6 +1008,11 @@ or is incomplete. If the failure can be narrowed down to a specific tlv type in
the payload, the erring node may include that `type` and its byte `offset` in
the decrypted byte stream.
+1. type: 23 (`mpp_timeout`)
+
+The complete amount of the multi-part payment was not received within a
+reasonable time.
+
### Requirements
An _erring node_:
@@ -952,6 +1080,10 @@ An _intermediate hop_ MUST NOT, but the _final node_:
- if the payment hash has already been paid:
- MAY treat the payment hash as unknown.
- MAY succeed in accepting the HTLC.
+ - if the `payment_secret` doesn't match the expected value for that `payment_hash`,
+ or the `payment_secret` is required and is not present:
+ - MUST fail the HTLC.
+ - MUST return an `incorrect_or_unknown_payment_details` error.
- if the amount paid is less than the amount expected:
- MUST fail the HTLC.
- MUST return an `incorrect_or_unknown_payment_details` error.
@@ -969,7 +1101,7 @@ An _intermediate hop_ MUST NOT, but the _final node_:
- if the `outgoing_cltv_value` does NOT correspond with the `cltv_expiry` from
the final node's HTLC:
- MUST return `final_incorrect_cltv_expiry` error.
- - if the `amt_to_forward` is greater than the `incoming_htlc_amt` from the
+ - if the `amt_to_forward` does NOT correspond with the `incoming_htlc_amt` from the
final node's HTLC:
- MUST return a `final_incorrect_htlc_amount` error.
diff --git a/07-routing-gossip.md b/07-routing-gossip.md
index e1d43c76a..54b685d47 100644
--- a/07-routing-gossip.md
+++ b/07-routing-gossip.md
@@ -20,7 +20,7 @@ multiple `node_announcement` messages, in order to update the node information.
# Table of Contents
- * [Definition of `short_channel_id`](#definition-of-short-channel-id)
+ * [Definition of `short_channel_id`](#definition-of-short_channel_id)
* [The `announcement_signatures` Message](#the-announcement_signatures-message)
* [The `channel_announcement` Message](#the-channel_announcement-message)
* [The `node_announcement` Message](#the-node_announcement-message)
@@ -166,8 +166,8 @@ The origin node:
as specified in [BOLT #2](02-peer-protocol.md#the-funding_locked-message).
- Note: the corresponding output MUST be a P2WSH, as described in [BOLT #3](03-transactions.md#funding-transaction-output).
- MUST set `node_id_1` and `node_id_2` to the public keys of the two nodes
- operating the channel, such that `node_id_1` is the numerically-lesser of the
- two DER-encoded keys sorted in ascending numerical order.
+ operating the channel, such that `node_id_1` is the lexicographically-lesser of the
+ two compressed keys sorted in ascending lexicographic order.
- MUST set `bitcoin_key_1` and `bitcoin_key_2` to `node_id_1` and `node_id_2`'s
respective `funding_pubkey`s.
- MUST compute the double-SHA256 hash `h` of the message, beginning at offset
@@ -180,16 +180,15 @@ The origin node:
- MUST set `bitcoin_signature_1` and `bitcoin_signature_2` to valid
signatures of the hash `h` (using `bitcoin_key_1` and `bitcoin_key_2`'s
respective secrets).
- - SHOULD set `len` to the minimum length required to hold the `features` bits
+ - MUST set `features` based on what features were negotiated for this channel, according to [BOLT #9](09-features.md#assigned-features-flags)
+ - MUST set `len` to the minimum length required to hold the `features` bits
it sets.
The receiving node:
- MUST verify the integrity AND authenticity of the message by verifying the
signatures.
- if there is an unknown even bit in the `features` field:
- - MUST NOT parse the remainder of the message.
- - MUST NOT add the channel to its local network view.
- - SHOULD NOT forward the announcement.
+ - MUST NOT attempt to route messages through the channel.
- if the `short_channel_id`'s output does NOT correspond to a P2WSH (using
`bitcoin_key_1` and `bitcoin_key_2`, as specified in
[BOLT #3](03-transactions.md#funding-transaction-output)) OR the output is
@@ -243,8 +242,6 @@ New channel features are possible in the future: backwards compatible (or
optional) features will have _odd_ feature bits, while incompatible features
will have _even_ feature bits
(["It's OK to be odd!"](00-introduction.md#glossary-and-terminology-guide)).
-Incompatible features will result in the announcement not being forwarded by
-nodes that do not understand them.
## The `node_announcement` Message
@@ -311,6 +308,7 @@ The origin node:
to 0.
- SHOULD ensure `ipv4_addr` AND `ipv6_addr` are routable addresses.
- MUST NOT include more than one `address descriptor` of the same type.
+ - MUST set `features` according to [BOLT #9](09-features.md#assigned-features-flags)
- SHOULD set `flen` to the minimum length required to hold the `features`
bits it sets.
@@ -324,11 +322,10 @@ any future fields appended to the end):
- SHOULD fail the connection.
- MUST NOT process the message further.
- if `features` field contains _unknown even bits_:
- - MUST NOT parse the remainder of the message.
- - MAY discard the message altogether.
- SHOULD NOT connect to the node.
- - MAY forward `node_announcement`s that contain an _unknown_ `features` _bit_,
- regardless of if it has parsed the announcement or not.
+ - Unless paying a [BOLT #11](11-payment-encoding.md) invoice which does not
+ have the same bit(s) set, MUST NOT attempt to send payments _to_ the node.
+ - MUST NOT route a payment _through_ the node.
- SHOULD ignore the first `address descriptor` that does NOT match the types
defined above.
- if `addrlen` is insufficient to hold the address descriptors of the
@@ -352,8 +349,9 @@ any future fields appended to the end):
New node features are possible in the future: backwards compatible (or
optional) ones will have _odd_ `feature` _bits_, incompatible ones will have
-_even_ `feature` _bits_. These may be propagated by nodes even if they
-cannot process the announcements themselves.
+_even_ `feature` _bits_. These will be propagated normally; incompatible
+feature bits here refer to the nodes, not the `node_announcement` message
+itself.
New address types may be added in the future; as address descriptors have
to be ordered in ascending order, unknown ones can be safely ignored.
@@ -458,15 +456,15 @@ The origin node:
- otherwise:
- MUST set the `direction` bit of `channel_flags` to 1.
- if the `htlc_maximum_msat` field is present:
- - MUST set the `option_channel_htlc_max` bit of `message_flags` to 1.
- - MUST set `htlc_maximum_msat` to the maximum value it will send through this channel for a single HTLC.
- - MUST set this to less than or equal to the channel capacity.
- - MUST set this to less than or equal to `max_htlc_value_in_flight_msat`
- it received from the peer.
- - for channels with `chain_hash` identifying the Bitcoin blockchain:
- - MUST set this to less than 2^32.
+ - MUST set the `option_channel_htlc_max` bit of `message_flags` to 1.
+ - MUST set `htlc_maximum_msat` to the maximum value it will send through this channel for a single HTLC.
+ - MUST set this to less than or equal to the channel capacity.
+ - MUST set this to less than or equal to `max_htlc_value_in_flight_msat`
+ it received from the peer.
+ - for channels with `chain_hash` identifying the Bitcoin blockchain:
+ - MUST set this to less than 2^32.
- otherwise:
- - MUST set the `option_channel_htlc_max` bit of `message_flags` to 0.
+ - MUST set the `option_channel_htlc_max` bit of `message_flags` to 0.
- MUST set bits in `channel_flags` and `message_flags `that are not assigned a meaning to 0.
- MAY create and send a `channel_update` with the `disable` bit set to 1, to
signal a channel's temporary unavailability (e.g. due to a loss of
@@ -522,10 +520,10 @@ The receiving node:
- MUST consider `htlc_maximum_msat` not to be present.
- otherwise:
- if `htlc_maximum_msat` is not present or greater than channel capacity:
- - MAY blacklist this `node_id`
- - SHOULD ignore this channel during route considerations.
- - otherwise:
- - SHOULD consider the `htlc_maximum_msat` when routing.
+ - MAY blacklist this `node_id`
+ - SHOULD ignore this channel during route considerations.
+ - otherwise:
+ - SHOULD consider the `htlc_maximum_msat` when routing.
### Rationale
@@ -676,13 +674,13 @@ The receiver:
- MUST reply with the latest `node_announcement` for `node_id_1`
- if bit 4 of `query_flag` is set and it has received a `node_announcement` from `node_id_2`:
- MUST reply with the latest `node_announcement` for `node_id_2`
- - SHOULD NOT wait for the next outgoing gossip flush to send these.
+ - SHOULD NOT wait for the next outgoing gossip flush to send these.
- SHOULD avoid sending duplicate `node_announcements` in response to a single `query_short_channel_ids`.
- MUST follow these responses with `reply_short_channel_ids_end`.
- if does not maintain up-to-date channel information for `chain_hash`:
- - MUST set `complete` to 0.
+ - MUST set `complete` to 0.
- otherwise:
- - SHOULD set `complete` to 1.
+ - SHOULD set `complete` to 1.
#### Rationale
@@ -733,7 +731,7 @@ Though it is possible, it would not be very useful to ask for checksums without
2. types:
1. type: 1 (`timestamps_tlv`)
2. data:
- * [`u8`:`encoding_type`]
+ * [`u8`:`encoding_type`]
* [`...*byte`:`encoded_timestamps`]
1. type: 3 (`checksums_tlv`)
2. data:
@@ -779,8 +777,7 @@ The receiver of `query_channel_range`:
- if it has not sent all `reply_channel_range` to a previously received `query_channel_range` from this sender:
- MAY fail the connection.
- MUST respond with one or more `reply_channel_range` whose combined range
- cover the requested `first_blocknum` to `first_blocknum` plus
- `number_of_blocks` minus one.
+ cover the requested `first_blocknum` to `first_blocknum` plus `number_of_blocks` minus one.
- For each `reply_channel_range`:
- MUST set with `chain_hash` equal to that of `query_channel_range`,
- MUST encode a `short_channel_id` for every open channel it knows in blocks `first_blocknum` to `first_blocknum` plus `number_of_blocks` minus one.
@@ -830,17 +827,19 @@ The receiver:
- SHOULD send all gossip messages whose `timestamp` is greater or
equal to `first_timestamp`, and less than `first_timestamp` plus
`timestamp_range`.
- - MAY wait for the next outgoing gossip flush to send these.
- - SHOULD restrict future gossip messages to those whose `timestamp`
- is greater or equal to `first_timestamp`, and less than
- `first_timestamp` plus `timestamp_range`.
+ - MAY wait for the next outgoing gossip flush to send these.
+ - SHOULD send gossip messages as it generates them regardless of `timestamp`.
+ - Otherwise (relayed gossip):
+ - SHOULD restrict future gossip messages to those whose `timestamp`
+ is greater or equal to `first_timestamp`, and less than
+ `first_timestamp` plus `timestamp_range`.
- If a `channel_announcement` has no corresponding `channel_update`s:
- - MUST NOT send the `channel_announcement`.
+ - MUST NOT send the `channel_announcement`.
- Otherwise:
- - MUST consider the `timestamp` of the `channel_announcement` to be the `timestamp` of a corresponding `channel_update`.
- - MUST consider whether to send the `channel_announcement` after receiving the first corresponding `channel_update`.
+ - MUST consider the `timestamp` of the `channel_announcement` to be the `timestamp` of a corresponding `channel_update`.
+ - MUST consider whether to send the `channel_announcement` after receiving the first corresponding `channel_update`.
- If a `channel_announcement` is sent:
- - MUST send the `channel_announcement` prior to any corresponding `channel_update`s and `node_announcement`s.
+ - MUST send the `channel_announcement` prior to any corresponding `channel_update`s and `node_announcement`s.
#### Rationale
@@ -858,6 +857,12 @@ is simple to implement.
In the case where the `channel_announcement` is nonetheless missed,
`query_short_channel_ids` can be used to retrieve it.
+Nodes can use `timestamp_filter` to reduce their gossip load when they
+have many peers (eg. setting `first_timestamp` to `0xFFFFFFFF` after the
+first few peers, in the assumption that propagation is adequate).
+This assumption of adequate propagation does not apply for gossip messages
+generated directly by the node itself, so they should ignore filters.
+
## Initial Sync
If a node requires an initial sync of gossip messages, it will be flagged
@@ -875,7 +880,7 @@ interactions with them.
A node:
- if the `gossip_queries` feature is negotiated:
- - MUST NOT relay any gossip messages unless explicitly requested.
+ - MUST NOT relay any gossip messages it did not generate itself, unless explicitly requested.
- otherwise:
- if it requires a full copy of the peer's routing state:
- SHOULD set the `initial_routing_sync` flag to 1.
@@ -907,11 +912,13 @@ A receiving node:
A node:
- if the `gossip_queries` feature is negotiated:
- - MUST not send gossip until it receives `gossip_timestamp_filter`.
+ - MUST not send gossip it did not generate itself, until it receives `gossip_timestamp_filter`.
- SHOULD flush outgoing gossip messages once every 60 seconds, independently of
the arrival times of the messages.
- Note: this results in staggered announcements that are unique (not
duplicated).
+ - SHOULD NOT forward gossip messages to peers who sent `networks` in `init`
+ and did not specify the `chain_hash` of this gossip message.
- MAY re-announce its channels regularly.
- Note: this is discouraged, in order to keep the resource requirements low.
- upon connection establishment:
@@ -1072,7 +1079,7 @@ per [HTLC Fees](#htlc-fees):
fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 )
- 200 + ( 4999999 * 2000 / 1000000 ) = 10199
+ 200 + ( 4999999 * 2000 / 1000000 ) = 10199
Similarly, it would need to add B->C's `channel_update` `cltv_expiry` (20), C's
requested `min_final_cltv_expiry` (9), and the cost for the _shadow route_ (42).
@@ -1092,7 +1099,7 @@ A->D's `update_add_htlc` message would be:
* `amount_msat`: 5020398
* `cltv_expiry`: current-block-height + 40 + 9 + 42
* `onion_routing_packet`:
- * `amt_to_forward` = 4999999
+ * `amt_to_forward` = 4999999
* `outgoing_cltv_value` = current-block-height + 9 + 42
And D->C's `update_add_htlc` would again be the same as B->C's direct payment
diff --git a/08-transport.md b/08-transport.md
index 699b02b0f..391b01ba4 100644
--- a/08-transport.md
+++ b/08-transport.md
@@ -154,7 +154,7 @@ The following functions will also be referenced:
* `ECDH(k, rk)`: performs an Elliptic-Curve Diffie-Hellman operation using
`k`, which is a valid `secp256k1` private key, and `rk`, which is a valid public key
- * The returned value is the SHA256 of the DER-compressed format of the
+ * The returned value is the SHA256 of the compressed format of the
generated point.
* `HKDF(salt,ikm)`: a function defined in `RFC 5869`[3](#reference-3),
@@ -202,11 +202,11 @@ As a concluding step, both sides mix the responder's public key into the
handshake digest:
* The initiating node mixes in the responding node's static public key
- serialized in Bitcoin's DER-compressed format:
+ serialized in Bitcoin's compressed format:
* `h = SHA-256(h || rs.pub.serializeCompressed())`
* The responding node mixes in their local static public key serialized in
- Bitcoin's DER-compressed format:
+ Bitcoin's compressed format:
* `h = SHA-256(h || ls.pub.serializeCompressed())`
### Handshake Exchange
diff --git a/09-features.md b/09-features.md
index ee79290d4..d8a01ca4b 100644
--- a/09-features.md
+++ b/09-features.md
@@ -1,14 +1,10 @@
# BOLT #9: Assigned Feature Flags
-This document tracks the assignment of `localfeatures` and `globalfeatures`
-flags in the `init` message ([BOLT #1](01-messaging.md)) along with the
-`features` flag fields in the `channel_announcement` and `node_announcement`
-messages ([BOLT #7](07-routing-gossip.md)).
-The flags are tracked separately, since new flags will likely be added over time.
-
-The `features` flags in the routing messages are a subset of the
-`globalfeatures` flags, as `localfeatures`, by definition, are only of interest
-to direct peers.
+This document tracks the assignment of `features` flags in the `init`
+message ([BOLT #1](01-messaging.md)), as well as `features` fields in
+the `channel_announcement` and `node_announcement` messages ([BOLT
+#7](07-routing-gossip.md)). The flags are tracked separately, since
+new flags will likely be added over time.
Flags are numbered from the least-significant bit, at bit 0 (i.e. 0x1,
an _even_ bit). They are generally assigned in pairs so that features
@@ -16,29 +12,46 @@ can be introduced as optional (_odd_ bits) and later upgraded to be compulsory
(_even_ bits), which will be refused by outdated nodes:
see [BOLT #1: The `init` Message](01-messaging.md#the-init-message).
-## Assigned `localfeatures` flags
-
-These flags may only be used in the `init` message:
-
-| Bits | Name | Description | Link |
-|-------|----------------------------------|---------------------------------------------------------------------------|------------------------------|
-| 0/1 | `option_data_loss_protect` | Requires or supports extra `channel_reestablish` fields | [BOLT #2][bolt02-retransmit] |
-| 3 | `initial_routing_sync` | Indicates that the sending node needs a complete routing information dump | [BOLT #7][bolt07-sync] |
-| 4/5 | `option_upfront_shutdown_script` | Commits to a shutdown scriptpubkey when opening channel | [BOLT #2][bolt02-open] |
-| 6/7 | `gossip_queries` | More sophisticated gossip control | [BOLT #7][bolt07-query] |
-| 10/11 | `gossip_queries_ex` | Gossip queries can include additional information | [BOLT #7][bolt07-query] |
-| 12/13| `option_static_remotekey` | Static key for remote output | [BOLT #3](03-transactions.md) |
-
-## Assigned `globalfeatures` flags
+Some features don't make sense on a per-channels or per-node basis, so
+each feature defines how it is presented in those contexts. Some
+features may be required for opening a channel, but not a requirement
+for use of the channel, so the presentation of those features depends
+on the feature itself.
-The following `globalfeatures` bits are currently assigned by this specification:
+The Context column decodes as follows:
+* `I`: presented in the `init` message.
+* `N`: presented in the `node_announcement` messages
+* `C`: presented in the `channel_announcement` message.
+* `C-`: presented in the `channel_announcement` message, but always odd (optional).
+* `C+`: presented in the `channel_announcement` message, but always even (required).
+* `9`: presented in [BOLT 11](11-payment-encoding.md) invoices.
-| Bits | Name | Description | Link |
-|------|-------------------|--------------------------------------------------------------------|---------------------------------------|
-| 8/9 | `var_onion_optin` | This node requires/supports variable-length routing onion payloads | [Routing Onion Specification][bolt04] |
+| Bits | Name | Description | Context | Dependencies | Link |
+|-------|----------------------------------|-----------------------------------------------------------|----------|-------------------|---------------------------------------|
+| 0/1 | `option_data_loss_protect` | Requires or supports extra `channel_reestablish` fields | IN | | [BOLT #2][bolt02-retransmit] |
+| 3 | `initial_routing_sync` | Sending node needs a complete routing information dump | I | | [BOLT #7][bolt07-sync] |
+| 4/5 | `option_upfront_shutdown_script` | Commits to a shutdown scriptpubkey when opening channel | IN | | [BOLT #2][bolt02-open] |
+| 6/7 | `gossip_queries` | More sophisticated gossip control | IN | | [BOLT #7][bolt07-query] |
+| 8/9 | `var_onion_optin` | Requires/supports variable-length routing onion payloads | IN9 | | [Routing Onion Specification][bolt04] |
+| 10/11 | `gossip_queries_ex` | Gossip queries can include additional information | IN | `gossip_queries` | [BOLT #7][bolt07-query] |
+| 12/13 | `option_static_remotekey` | Static key for remote output | IN | | [BOLT #3](03-transactions.md) |
+| 14/15 | `payment_secret` | Node supports `payment_secret` field | IN9 | `var_onion_optin` | [Routing Onion Specification][bolt04] |
+| 16/17 | `basic_mpp` | Node can receive basic multi-part payments | IN9 | `payment_secret` | [BOLT #4][bolt04-mpp] |
+| 18/19 | `option_support_large_channel` | Can create large channels | INC+ | | [BOLT #2](02-peer-protocol.md#the-open_channel-message) |
## Requirements
+The origin node:
+ * If it supports a feature above, SHOULD set the corresponding odd
+ bit in all feature fields indicated by the Context column unless
+ indicated that it must set the even feature bit instead.
+ * If it requires a feature above, MUST set the corresponding even
+ feature bit in all feature fields indicated by the Context column,
+ unless indicated that it must set the odd feature bit instead.
+ * MUST NOT set feature bits it does not support.
+ * MUST NOT set feature bits in fields not specified by the table above.
+ * MUST set all transitive feature dependencies.
+
The requirements for receiving specific bits are defined in the linked sections in the table above.
The requirements for feature bits that are not defined
above can be found in [BOLT #1: The `init` Message](01-messaging.md#the-init-message).
@@ -49,6 +62,17 @@ There is no _even_ bit for `initial_routing_sync`, as there would be little
point: a local node can't determine if a remote node complies, and it must
interpret the flag, as defined in the initial spec.
+Note that for feature flags which are available in both the `node_announcement`
+and [BOLT 11](11-payment-encoding.md) invoice contexts, the features as set in
+the [BOLT 11](11-payment-encoding.md) invoice should override those set in the
+`node_announcement`. This keeps things consistent with the unknown features
+behavior as specified in [BOLT 7](07-routing-gossip.md#the-node_announcement-message).
+
+The origin must set all transitive feature dependencies in order to create a
+well-formed feature vector. By validating all known dependencies up front, this
+simplifies logic gated on a single feature bit; the feature's dependencies are
+known to be set, and do not need to be validated at every feature gate.
+

This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).
@@ -58,3 +82,4 @@ This work is licensed under a [Creative Commons Attribution 4.0 International Li
[bolt04]: 04-onion-routing.md
[bolt07-sync]: 07-routing-gossip.md#initial-sync
[bolt07-query]: 07-routing-gossip.md#query-messages
+[bolt04-mpp]: 04-onion-routing.md#basic-multi-part-payments
diff --git a/11-payment-encoding.md b/11-payment-encoding.md
index b03409311..2103e4a01 100644
--- a/11-payment-encoding.md
+++ b/11-payment-encoding.md
@@ -64,6 +64,7 @@ A writer:
- if a specific minimum `amount` is required for successful payment:
- MUST include that `amount`.
- MUST encode `amount` as a positive decimal integer with no leading 0s.
+ - If the `p` multiplier is used the last decimal of `amount` MUST be `0`.
- SHOULD use the shortest representation possible, by using the largest
multiplier or omitting the multiplier.
@@ -89,6 +90,9 @@ Donation addresses often don't have an associated amount, so `amount`
is optional in that case. Usually a minimum payment is required for
whatever is being offered in return.
+The `p` multiplier would allow to specify sub-millisatoshi amounts, which cannot be transferred on the network, since HTLCs are denominated in millisatoshis.
+Requiring a trailing `0` decimal ensures that the `amount` represents an integer number of millisatoshis.
+
# Data Part
The data part of a Lightning invoice consists of multiple sections:
@@ -129,6 +133,7 @@ Each Tagged Field is of the form:
Currently defined tagged fields are:
* `p` (1): `data_length` 52. 256-bit SHA256 payment_hash. Preimage of this provides proof of payment.
+* `s` (16): `data_length` 52. This 256-bit secret prevents forwarding nodes from probing the payment recipient.
* `d` (13): `data_length` variable. Short description of purpose of payment (UTF-8), e.g. '1 cup of coffee' or 'ナンセンス 1杯'
* `n` (19): `data_length` 53. 33-byte public key of the payee node
* `h` (23): `data_length` 52. 256-bit description of purpose of payment (SHA256). This is used to commit to an associated description that is over 639 bytes, but the transport mechanism for the description in that case is transport specific and not defined here.
@@ -148,7 +153,7 @@ Currently defined tagged fields are:
### Requirements
A writer:
- - MUST include exactly one `p` field.
+ - MUST include exactly one `p` and `s` fields.
- MUST set `payment_hash` to the SHA2 256-bit hash of the `payment_preimage`
that will be given in return for payment.
- MUST include either exactly one `d` or exactly one `h` field.
@@ -191,8 +196,8 @@ A writer:
- MUST specify the most-preferred field first, followed by less-preferred fields, in order.
A reader:
- - MUST skip over unknown fields, OR an `f` field with unknown `version`, OR `p`, `h`, or
- `n` fields that do NOT have `data_length`s of 52, 52, or 53, respectively.
+ - MUST skip over unknown fields, OR an `f` field with unknown `version`, OR `p`, `h`, `s` or
+ `n` fields that do NOT have `data_length`s of 52, 52, 52 or 53, respectively.
- if the `9` field contains unknown _odd_ bits that are non-zero:
- MUST ignore the bit.
- if the `9` field contains unknown _even_ bits that are non-zero:
@@ -202,6 +207,8 @@ A reader:
description.
- if a valid `n` field is provided:
- MUST use the `n` field to validate the signature instead of performing signature recovery.
+ - if there is a valid `s` field:
+ - MUST use that as [`payment_secret`](04-onion-routing.md#tlv_payload-payload-format)
### Rationale
@@ -272,12 +279,36 @@ Don't be like the school of [Little Bobby Tables](https://xkcd.com/327/).
## Feature Bits
Feature bits allow forward and backward compatibility, and follow the
-_it's ok to be odd_ rule.
+_it's ok to be odd_ rule. Features appropriate for use in the `9` field
+are marked in [BOLT 9](09-features.md).
The field is big-endian. The least-significant bit is numbered 0,
which is _even_, and the next most significant bit is numbered 1,
which is _odd_.
+Note that the `payment_secret` feature prevents probing attacks from nodes
+along the path, but only if made compulsory: yet doing so will break
+older clients which do not understand the feature. It is compulsory
+for `basic_mpp` however, as that is also a recent feature, and makes
+nodes more vulnerable to probing attacks as there is no lower-bound
+on the amount sent.
+
+### Requirements
+
+A writer:
+ - MUST set the `9` field to a feature vector compliant with the
+ [BOLT 9 origin node requirements](09-features.md#requirements).
+ - MUST set an `s` field if and only if the `payment_secret` feature is set.
+
+A reader:
+ - if the feature vector does not set all known, transitive feature dependencies:
+ - MUST NOT attempt the payment.
+ - if the `basic_mpp` feature is offered in the invoice:
+ - MAY pay using [Basic multi-part payments](04-onion-routing.md#basic-multi-part-payments).
+ - otherwise:
+ - MUST NOT use [Basic multi-part payments](04-onion-routing.md#basic-multi-part-payments).
+
+
# Payer / Payee Interactions
These are generally defined by the rest of the Lightning BOLT series,
@@ -317,7 +348,7 @@ https://github.com/rustyrussell/lightning-payencode
# Examples
-NB: all the following examples are signed with `priv_key`=`e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734`.
+NB: all the following examples are signed with `priv_key`=`e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734`. Also, the first 9 examples are legacy: modern invoices have an `s` field.
> ### Please make a donation of any amount using payment_hash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad
> lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w
@@ -534,8 +565,8 @@ Breakdown:
* `6c6e626332306d0b25fe64570d0e496dbd9f8b0d000dbb44824f751380da37c6dba89b14f6f92047d63f576e304021a00008101820283038404800081018202830384048000810182028303840480810243500c318a1e0a628b34025e8c9019ab6d09b64c2b3c66a693d0dc63194b02481931000` hex of data for signing (prefix + data after separator up to the start of the signature)
* `399a8b167029fda8564fd2e99912236b0b8017e7d17e416ae17307812c92cf42` hex of SHA256 of the preimage
-> ### Please send $30 for coffee beans to the same peer, which supports features 1 and 9
-> lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9qzsze992adudgku8p05pstl6zh7av6rx2f297pv89gu5q93a0hf3g7lynl3xq56t23dpvah6u7y9qey9lccrdml3gaqwc6nxsl5ktzm464sq73t7cl
+> ### Please send $30 for coffee beans to the same peer, which supports features 9, 15 and 99, using secret 0x1111111111111111111111111111111111111111111111111111111111111111
+> lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu
Breakdown:
@@ -547,14 +578,17 @@ Breakdown:
* `d`: short description
* `q5`: `data_length` (`q` = 0, `5` = 20; 0 * 32 + 20 == 20)
* `vdhkven9v5sxyetpdees`: 'coffee beans'
+* `s`: payment secret
+ * `p5`: `data_length` (`p` = 1, `5` = 20; 1 * 32 + 20 == 52)
+ * `zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs`: 0x1111111111111111111111111111111111111111111111111111111111111111
* `9`: features
- * `qz`: `data_length` (`q` = 0, `z` = 2; 0 * 32 + 2 == 2)
- * `sz`: b1000000010
-* `e992adudgku8p05pstl6zh7av6rx2f297pv89gu5q93a0hf3g7lynl3xq56t23dpvah6u7y9qey9lccrdml3gaqwc6nxsl5ktzm464sq`: signature
-* `73t7cl`: Bech32 checksum
+ * `q5`: `data_length` (`q` = 0, `5` = 20; 0 * 32 + 20 == 20)
+ * `sqqqqqqqqqqqqqqqpqsq`: b1000....00001000001000000000
+* `67gye39hfg3zd8rgc8032tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgp`: signature
+* `tq44qu`: Bech32 checksum
-> # Same, but using invalid unknown feature 100
-> lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9q4pqqqqqqqqqqqqqqqqqqszk3ed62snp73037h4py4gry05eltlp0uezm2w9ajnerhmxzhzhsu40g9mgyx5v3ad4aqwkmvyftzk4k9zenz90mhjcy9hcevc7r3lx2sphzfxz7
+> # Same, but adding invalid unknown feature 100
+> lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqsqq40wa3khl49yue3zsgm26jrepqr2eghqlx86rttutve3ugd05em86nsefzh4pfurpd9ek9w2vp95zxqnfe2u7ckudyahsa52q66tgzcp6t2dyk
Breakdown:
@@ -566,11 +600,14 @@ Breakdown:
* `d`: short description
* `q5`: `data_length` (`q` = 0, `5` = 20; 0 * 32 + 20 == 20)
* `vdhkven9v5sxyetpdees`: 'coffee beans'
+* `s`: payment secret
+ * `p5`: `data_length` (`p` = 1, `5` = 20; 1 * 32 + 20 == 52)
+ * `zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs`: 0x1111111111111111111111111111111111111111111111111111111111111111
* `9`: features
* `q4`: `data_length` (`q` = 0, `4` = 21; 0 * 32 + 21 == 21)
- * `pqqqqqqqqqqqqqqqqqqsz`: b00001...(90 zeroes)...1000000010
-* `k3ed62snp73037h4py4gry05eltlp0uezm2w9ajnerhmxzhzhsu40g9mgyx5v3ad4aqwkmvyftzk4k9zenz90mhjcy9hcevc7r3lx2sp`: signature
-* `hzfxz7`: Bech32 checksum
+ * `psqqqqqqqqqqqqqqqpqsqq`: b000011000....00001000001000000000
+* `40wa3khl49yue3zsgm26jrepqr2eghqlx86rttutve3ugd05em86nsefzh4pfurpd9ek9w2vp95zxqnfe2u7ckudyahsa52q66tgzcp`: signature
+* `6t2dyk`: Bech32 checksum
# Authors
diff --git a/bolt04/onion-test-multi-frame.json b/bolt04/onion-test-multi-frame.json
index 495de9e13..5d20be295 100644
--- a/bolt04/onion-test-multi-frame.json
+++ b/bolt04/onion-test-multi-frame.json
@@ -31,7 +31,7 @@
}
]
},
- "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71a060daf367132b378b3a3883c0e2c0e026b8900b2b5cdbc784e1a3bb913f88a9c50f7d61ab590531cf08000178a333a347f8b4072ed056f820f77774345e183a342ec4729f3d84accf515e88adddb85ecc08daba68404bae9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf050597c681185f336b1da63b0939aa2b7c50b21b5eb7b6ad66c81fab98a3cdf73f658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d7fca6a0e4ecc9f9de611a90da6e99c39551094c56e3196f282c5dffd9fc4b2fc12f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa337ef811d5345a9e16b60de1767b209645ba40bd1f9a5f75bc04feca9b27c5554be4fe83fac2cb83aa447a817bb85ae966c68b420063833fada375e2f515965e687a45699632902672c654d1d18d7bcbf55e8fa57f63f2da449f8e1e606e8722df081e5f193fc4179feb99ad22819afdeef211f7c54afdba92aeef0c00b7bc2b65a4813c01f907a8377585708f2d4c940a25328e585714c8ded0a9a4d7a6de1027c1cb7a0198cd3db68b58c0704dfd0cfbe624e9cd18cc0ae5d96697bb476708b9ee0403d211e64e0d5a7683a7a9a140c02f0ff1c6e67a302941b4052bdea8a63e70a3ad62c5b89c698f1fd3c7685cb49705096cad702d02d93bcb1c27a409f4c9bddec001205ca4a2740f19b50900be81c7e847f1a863deea8d35701f1355cad8db57b1d4eb2ab4e29587734785abfb46ddede71928213d7d089dfdeda052827f459f1688cc0935bd47e7bcec27427c8376dcce7e22699567c0d145f8a7db33f6758815f1f15f9f7a9760dec4f34ae095edda4c64e9735bdd029c4e32c2ee31ba47ec5e6bdb97813d52dbd15b4e0b7a2c7f790ae64104d99f38c127f0a093288fa34144adb16b8968d4fa7656fcec99de8503dd46d3b03620a71c7cd085364abd30dccf7fbda25a1cdc102600149c9af1c97aa0372cd2e1909f28ac5c686f432b310e79528c9b8b9e8f314c1e74621ce6308ad2278b81d460892e0d9dd38b7c76d58be6dfd10ae7583ee1e7ef5b3f6f78dc60af0950df1b00cc55b6d178ba2e476bea0eaeef49323b83f05804159e7aef4eed4cc60dd07be76f067dfd0bcfb0b806b69ba921336a20c43c832d0cab8fa3ddeb29e3bf07b0d98a112eb07802756235a49d44a8b82a950d84e95e01971f0e106ccb337f07384e21620e0ad39e16ed9edca123226cf55ac44f449eeb53e38a7f27d101806e4823e4efcc887414240ee6826c4a5cb1c6443ad36ebf905a435c1d9054e54173911b17b5b40f60b3d9fd5f12eac54ca1e20191f5f18544d5fd3d665e9bcef96fb44b76110aa64d9db4c86c9513cbdad546538e8aec521fbe83ceac5e74a15629f1ed0b870a1d0d1e5680b6d6100d1bd3f3b9043bd35b8919c4088f1949b8be89e4701eb870f8ed64fafa446c78df3ea",
+ "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a710f8eaf9ccc768f66bb5dec1f7827f33c43fe2ddd05614c8283aa78e9e7573f87c50f7d61ab590531cf08000178a333a347f8b4072e1cea42da7552402b10765adae3f581408f35ff0a71a34b78b1d8ecae77df96c6404bae9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf057f5995d9731c4bf796fb0e41c885d488dcbc68eb742e27f44310b276edc6f652658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d710ee5c193921909bdd75db331cd9d7581a39fca50814ed8d9d402b86e7f8f6ac2f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa3379834bc2380d58e9d23237821475a1874484783a15d68f47d3dc339f38d9bf925655d5c946778680fd6d1f062f84128895aff09d35d6c92cca63d3f95a9ee8f2a84f383b4d6a087533e65de12fc8dcaf85777736a2088ff4b22462265028695b37e70963c10df8ef2458756c73007dc3e544340927f9e9f5ea4816a9fd9832c311d122e9512739a6b4714bba590e31caa143ce83cb84b36c738c60c3190ff70cd9ac286a9fd2ab619399b68f1f7447be376ce884b5913c8496d01cbf7a44a60b6e6747513f69dc538f340bc1388e0fde5d0c1db50a4dcb9cc0576e0e2474e4853af9623212578d502757ffb2e0e749695ed70f61c116560d0d4154b64dcf3cbf3c91d89fb6dd004dc19588e3479fcc63c394a4f9e8a3b8b961fce8a532304f1337f1a697a1bb14b94d2953f39b73b6a3125d24f27fcd4f60437881185370bde68a5454d816e7a70d4cea582effab9a4f1b730437e35f7a5c4b769c7b72f0346887c1e63576b2f1e2b3706142586883f8cf3a23595cc8e35a52ad290afd8d2f8bcd5b4c1b891583a4159af7110ecde092079209c6ec46d2bda60b04c519bb8bc6dffb5c87f310814ef2f3003671b3c90ddf5d0173a70504c2280d31f17c061f4bb12a978122c8a2a618bb7d1edcf14f84bf0fa181798b826a254fca8b6d7c81e0beb01bd77f6461be3c8647301d02b04753b0771105986aa0cbc13f7718d64e1b3437e8eef1d319359914a7932548c91570ef3ea741083ca5be5ff43c6d9444d29df06f76ec3dc936e3d180f4b6d0fbc495487c7d44d7c8fe4a70d5ff1461d0d9593f3f898c919c363fa18341ce9dae54f898ccf3fe792136682272941563387263c51b2a2f32363b804672cc158c9230472b554090a661aa81525d11876eefdcc45442249e61e07284592f1606491de5c0324d3af4be035d7ede75b957e879e9770cdde2e1bbc1ef75d45fe555f1ff6ac296a2f648eeee59c7c08260226ea333c285bcf37a9bbfa57ba2ab8083c4be6fc2ebe279537d22da96a07392908cf22b233337a74fe5c603b51712b43c3ee55010ee3d44dd9ba82bba3145ec358f863e04bbfa53799a7a9216718fd5859da2f0deb77b8e315ad6868fdec9400f45a48e6dc8ddbaeb3",
"decode": [
"4141414141414141414141414141414141414141414141414141414141414141",
"4242424242424242424242424242424242424242424242424242424242424242",
diff --git a/bolt04/onion-test-v0.json b/bolt04/onion-test-v0.json
index e30a715bf..66755f4fe 100644
--- a/bolt04/onion-test-v0.json
+++ b/bolt04/onion-test-v0.json
@@ -11,7 +11,7 @@
"payload": "0000000000000000000000000000000000000000000000000000000000000000",
"rhokey": "ce496ec94def95aadd4bec15cdb41a740c9f2b62347c4917325fcc6fb0453986",
"mukey": "b57061dc6d0a2b9f261ac410c8b26d64ac5506cbba30267a649c28c179400eba",
- "hmac": "65f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf"
+ "hmac": "b8640887e027e946df96488b47fbc4a4fadaa8beda4abe446fafea5403fae2ef"
},
{
"realm": 0,
@@ -20,7 +20,7 @@
"payload": "0101010101010101000000000000000100000001000000000000000000000000",
"rhokey": "450ffcabc6449094918ebe13d4f03e433d20a3d28a768203337bc40b6e4b2c59",
"mukey": "05ed2b4a3fb023c2ff5dd6ed4b9b6ea7383f5cfe9d59c11d121ec2c81ca2eea9",
- "hmac": "9b122c79c8aee73ea2cdbc22eca15bbcc9409a4cdd73d2b3fcd4fe26a492d376"
+ "hmac": "a93aa4f40241cef3e764e24b28570a0db39af82ab5102c3a04e51bec8cca9394"
},
{
"realm": 0,
@@ -29,7 +29,7 @@
"payload": "0202020202020202000000000000000200000002000000000000000000000000",
"rhokey": "11bf5c4f960239cb37833936aa3d02cea82c0f39fd35f566109c41f9eac8deea",
"mukey": "caafe2820fa00eb2eeb78695ae452eba38f5a53ed6d53518c5c6edf76f3f5b78",
- "hmac": "548e58057ab0a0e6c2d8ad8e855d89f9224279a5652895ea14f60bffb81590eb"
+ "hmac": "5d1b11f1efeaa9be32eb1c74b113c0b46f056bb49e2a35a51ceaece6bd31332c"
},
{
"realm": 0,
@@ -38,7 +38,7 @@
"payload": "0303030303030303000000000000000300000003000000000000000000000000",
"rhokey": "cbe784ab745c13ff5cffc2fbe3e84424aa0fd669b8ead4ee562901a4a4e89e9e",
"mukey": "5052aa1b3d9f0655a0932e50d42f0c9ba0705142c25d225515c45f47c0036ee9",
- "hmac": "0daed5f832ef34ea8d0d2cc0699134287a2739c77152d9edc8fe5ccce7ec838f"
+ "hmac": "19ca6357b5552b28e50ae226854eec874bbbf7025cf290a34c06b4eff5d2bac0"
},
{
"realm": 0,
@@ -47,11 +47,11 @@
"payload": "0404040404040404000000000000000400000004000000000000000000000000",
"rhokey": "034e18b8cc718e8af6339106e706c52d8df89e2b1f7e9142d996acf88df8799b",
"mukey": "8e45e5c61c2b24cb6382444db6698727afb063adecd72aada233d4bf273d975a",
- "hmac": "62cc962876e734e089e79eda497077fb411fac5f36afd43329040ecd1e16c6d9"
+ "hmac": "16d4553c6084b369073d259381bb5b02c16bb2c590bbd9e69346cf7ebd563229"
}
]
},
- "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf",
+ "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71e87f9aab8f6378c6ff744c1f34b393ad28d065b535c1a8668d85d3b34a1b3befd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a1f9e7abc789266cc861cabd95818c0fc8efbdfdc14e3f7c2bc7eb8d6a79ef75ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d014698cf05d742557763d9cb743faeae65dcc79dddaecf27fe5942be5380d15e9a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040a2a2fba158a0d8085926dc2e44f0c88bf487da56e13ef2d5e676a8589881b4869ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565a9f99728426ce2380a9580e2a9442481ceae7679906c30b1a0e21a10f26150e0645ab6edfdab1ce8f8bea7b1dee511c5fd38ac0e702c1c15bb86b52bca1b71e15b96982d262a442024c33ceb7dd8f949063c2e5e613e873250e2f8708bd4e1924abd45f65c2fa5617bfb10ee9e4a42d6b5811acc8029c16274f937dac9e8817c7e579fdb767ffe277f26d413ced06b620ede8362081da21cf67c2ca9d6f15fe5bc05f82f5bb93f8916bad3d63338ca824f3bbc11b57ce94a5fa1bc239533679903d6fec92a8c792fd86e2960188c14f21e399cfd72a50c620e10aefc6249360b463df9a89bf6836f4f26359207b765578e5ed76ae9f31b1cc48324be576e3d8e44d217445dba466f9b6293fdf05448584eb64f61e02903f834518622b7d4732471c6e0e22e22d1f45e31f0509eab39cdea5980a492a1da2aaac55a98a01216cd4bfe7abaa682af0fbff2dfed030ba28f1285df750e4d3477190dd193f8643b61d8ac1c427d590badb1f61a05d480908fbdc7c6f0502dd0c4abb51d725e92f95da2a8facb79881a844e2026911adcc659d1fb20a2fce63787c8bb0d9f6789c4b231c76da81c3f0718eb7156565a081d2be6b4170c0e0bcebddd459f53db2590c974bca0d705c055dee8c629bf854a5d58edc85228499ec6dde80cce4c8910b81b1e9e8b0f43bd39c8d69c3a80672729b7dc952dd9448688b6bd06afc2d2819cda80b66c57b52ccf7ac1a86601410d18d0c732f69de792e0894a9541684ef174de766fd4ce55efea8f53812867be6a391ac865802dbc26d93959df327ec2667c7256aa5a1d3c45a69a6158f285d6c97c3b8eedb09527848500517995a9eae4cd911df531544c77f5a9a2f22313e3eb72ca7a07dba243476bc926992e0d1e58b4a2fc8c7b01e0cad726237933ea319bad7537d39f3ed635d1e6c1d29e97b3d2160a09e30ee2b65ac5bce00996a73c008bcf351cecb97b6833b6d121dcf4644260b2946ea204732ac9954b228f0beaa15071930fd9583dfc466d12b5f0eeeba6dcf23d5ce8ae62ee5796359d97a4a15955c778d868d0ef9991d9f2833b5bb66119c5f8b396fd108baed7906cbb3cc376d13551caed97fece6f42a4c908ee279f1127fda1dd3ee77d8de0a6f3c135fa3f1cffe38591b6738dc97b55f0acc52be9753ce53e64d7e497bb00ca6123758df3b68fad99e35c04389f7514a8e36039f541598a417275e77869989782325a15b5342ac5011ff07af698584b476b35d941a4981eac590a07a092bb50342da5d3341f901aa07964a8d02b623c7b106dd0ae50bfa007a22d46c8772fa55558176602946cb1d11ea5460db7586fb89c6d3bcd3ab6dd20df4a4db63d2e7d52380800ad812b8640887e027e946df96488b47fbc4a4fadaa8beda4abe446fafea5403fae2ef",
"decode": [
"4141414141414141414141414141414141414141414141414141414141414141",
"4242424242424242424242424242424242424242424242424242424242424242",
diff --git a/tests/events/README.md b/tests/events/README.md
new file mode 100644
index 000000000..467424593
--- /dev/null
+++ b/tests/events/README.md
@@ -0,0 +1,23 @@
+# Implementation Tests for Lightning Specifications
+
+This directory contains conversation-style tests for Lightning
+implementations. The format is documented in
+[tests/events/test-spec.md](test-spec.md), and the base driver for an
+implementation is in the [tools/test-events.py](../../tools/test-events.py).
+
+To run the tests, you need to write a driver for your particular
+implementation, like the one for
+[c-lightning](../../tools/test-events-clightning.py). Then extract the
+format of all messages into a file, like so:
+
+ $ python3 tools/extract-formats.py 0*.md > format.csv
+
+Finally, you can run a test like so:
+
+ $ tools/test-events-clightning.py formats.csv tests/events/*.events
+
+The `-v` option will give more verbose output, which is particularly
+useful when debugging your test script.
+
+Good luck!
+Rusty.
diff --git a/tests/events/bolt1-01-init.events b/tests/events/bolt1-01-init.events
new file mode 100644
index 000000000..f9dc5dd6d
--- /dev/null
+++ b/tests/events/bolt1-01-init.events
@@ -0,0 +1,79 @@
+# Variations on init exchange.
+# Spec: MUST respond to known feature bits as specified in [BOLT #9](09-features.md).
+
+# Trivial variable to serve as a demonstration
+EXPECT_INIT=expect-send: type=init
+
+1. connect: privkey=0000000000000000000000000000000000000000000000000000000000000003
+2. $EXPECT_INIT
+3. recv: type=init globalfeatures= features=
+
+ 1. nothing
+ 1. disconnect:
+
+4. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ # Even if we don't send anything, it should send init.
+ 1. $EXPECT_INIT
+
+ # Minimal possible init message.
+ # Spec: MUST send `init` as the first Lightning message for any connection.
+ 1. $EXPECT_INIT
+ 2. recv: type=init globalfeatures= features=
+
+ # SHOULD NOT set features greater than 13 in `globalfeatures`.
+ 1. $EXPECT_INIT globalfeatures=000000/030000
+ # init msg with unknown odd global bit (19): no error
+ 2. recv: type=init globalfeatures=020000 features=
+
+ # Sanity check that bits 34 and 35 are not used!
+ 1. $EXPECT_INIT features=0000000000/0300000000
+ # init msg with unknown odd local bit (19): no error
+ 2. recv: type=init globalfeatures= features=020000
+
+ # init msg with unknown even global bit (34): you will error
+ 1. $EXPECT_INIT
+ 2. recv: type=init globalfeatures=0100000000 features=
+ 3. expect-error:
+
+ # init msg with unknown even local bit (34): you will error
+ 1. $EXPECT_INIT
+ 2. recv: type=init globalfeatures= features=0100000000
+ 3. expect-error:
+
+ # If you don't support `option_data_loss_protect`, you will be ok if
+ # we ask for it.
+ 1. $EXPECT_INIT features=00/03 !option_data_loss_protect
+ 2. recv: type=init globalfeatures= features=02 !option_data_loss_protect
+
+ # If you don't support `option_data_loss_protect`, you will error if
+ # we require it.
+ 1. $EXPECT_INIT features=00/03 !option_data_loss_protect
+ 2. recv: type=init globalfeatures= features=01 !option_data_loss_protect
+ 3. expect-error: !option_data_loss_protect
+
+ # If you support `option_data_loss_protect`, you will advertize it odd.
+ 1. $EXPECT_INIT features=02/03 option_data_loss_protect/odd
+
+ # If you require `option_data_loss_protect`, you will advertize it even.
+ 1. $EXPECT_INIT features=01/03 option_data_loss_protect/even
+
+ # If you don't support `option_static_remotekey`, you will be ok if
+ # we ask for it.
+ 1. $EXPECT_INIT features=0000/3000 !option_static_remotekey
+ 2. recv: type=init globalfeatures= features=2000 !option_static_remotekey
+
+ # If you don't support `option_static_remotekey`, you will error if
+ # we require it.
+ 1. $EXPECT_INIT features=0000/3000 !option_static_remotekey
+ 2. recv: type=init globalfeatures= features=1000 !option_static_remotekey
+ 3. expect-error: !option_static_remotekey
+
+ # If you support `option_static_remotekey`, you will advertize it odd.
+ 1. $EXPECT_INIT features=2000/3000 option_static_remotekey/odd
+
+ # If you require `option_static_remotekey`, you will advertize it even.
+ 1. $EXPECT_INIT features=1000/3000 option_static_remotekey/even
+
+ # Note: it's undefined what you'll do if implementation requires
+ # an option and isn't offered it. The recipient of the required feature
+ # bit should notice the requirement and error.
diff --git a/tests/events/bolt1-02-unknown-messages.events b/tests/events/bolt1-02-unknown-messages.events
new file mode 100644
index 000000000..50fe30658
--- /dev/null
+++ b/tests/events/bolt1-02-unknown-messages.events
@@ -0,0 +1,22 @@
+# Init exchange, with unknown messages
+#
+# BOLT #1:
+# The receiving node:
+#...
+# - upon receiving unknown _odd_ feature bits that are non-zero:
+# - MUST ignore the bit.
+# - upon receiving unknown _even_ feature bits that are non-zero:
+# - MUST fail the connection.
+
+1. connect: privkey=0000000000000000000000000000000000000000000000000000000000000003
+2. expect-send: type=init
+3. recv: type=init globalfeatures= features=
+
+ 1. nothing
+
+ # Unknown odd is OK.
+ 1. recv: type=9999
+
+ # Unknown even causes error.
+ 1. recv: type=10000
+ 2. expect-error:
diff --git a/tests/events/bolt2-01-open_channel.events b/tests/events/bolt2-01-open_channel.events
new file mode 100644
index 000000000..4b1b04478
--- /dev/null
+++ b/tests/events/bolt2-01-open_channel.events
@@ -0,0 +1,189 @@
+# Variations on open_channel, accepter + opener perspectives
+#
+include setup.incl
+
+1. block: $BLOCK_102
+
+2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+3. expect-send: type=init
+4. recv: type=init globalfeatures= features=
+
+ # Start with the 'accepter' side of an open_channel (test runner initiates)
+ # We assume a funding_per_kw=253 Satoshi/kSipa.
+ # This gives a channel of 999878sat
+ 1. recv: type=open_channel
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ funding_satoshis=999878
+ push_msat=0
+ dust_limit_satoshis=546
+ max_htlc_value_in_flight_msat=4294967295
+ channel_reserve_satoshis=9998
+ htlc_minimum_msat=0
+ feerate_per_kw=253
+ to_self_delay=6
+ max_accepted_htlcs=483
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000020
+ funding_pubkey=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000021
+ revocation_basepoint=021697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000022
+ payment_basepoint=031be68a5a028f2601d0e80d468c344ba331d611b96c358b6032e8b4da0547fc11
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000023
+ delayed_payment_basepoint=03605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000024
+ htlc_basepoint=02e0392cfa338aaf2f0b56c563e3e5e67a5d5fefe3388f85d90c899da20f0198f9
+ # shachain seed=0000000000000000000000000000000000000000000000000000000000000000
+ # first per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ first_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8
+ channel_flags=01
+
+ # Ignore unknown odd messages
+ 1. nothing
+ 1. recv: type=9999
+
+ 2. expect-send: type=accept_channel
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000010
+ funding_pubkey=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000011
+ revocation_basepoint=03defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000012
+ payment_basepoint=025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000013
+ delayed_payment_basepoint=022b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000014
+ htlc_basepoint=024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97
+ # shachain seed=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ # first per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ first_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06
+ minimum_depth=3
+ # If these are different, the commitment tx will be different!
+ to_self_delay=6
+ channel_reserve_satoshis=9998
+
+ # Ignore unknown messages
+ 1. nothing
+ 1. recv: type=9999
+
+ 3. recv: type=funding_created
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # Funding tx is 020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+ # txid=41085b995c1f591cfc3ae79ccde012bf0b37c7bde23d80a61c9732bdd6210b2f
+ funding_txid=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ funding_output_index=0
+ # node's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000160014749af8703f0d1fd8890a553bd62e9caf15f7bad44cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:97b9f4b67c7d404c82f97d86d7e5b2689e366abf1609abd889143a5999c6df47)
+
+ 4. expect-send: type=funding_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # test's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000220020233d69d88092351875ce0b9fd5ea576b2307c539eaed7abdf97fbb26720f01ac4cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:49fcc656b58e78f639b8af4bca65fe1ee948ea36eb7629222320518e33a42f29)
+
+ 5. block: height=103 n=3 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+
+ 6. expect-send: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+
+ 7. recv: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d
+
+ # Ignore unknown odd messages
+ 1. nothing
+ 1. recv: type=9999
+
+
+ # Now we test the 'opener' side of an open_channel (node initiates)
+ # We set funding_per_kw=253 Satoshi/kSipa.
+ 1. fundchannel: amount=999877 utxo=16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/1 feerate=253perkw
+
+ # This gives a channel of 999877sat
+ 1. expect-send: type=open_channel
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ funding_satoshis=999877
+ push_msat=0
+ dust_limit_satoshis=546
+ max_htlc_value_in_flight_msat=18446744073709551615
+ htlc_minimum_msat=0
+ channel_reserve_satoshis=9998
+ feerate_per_kw=253
+ to_self_delay=6
+ max_accepted_htlcs=483
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000010
+ funding_pubkey=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000011
+ revocation_basepoint=03defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000012
+ payment_basepoint=025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000013
+ delayed_payment_basepoint=022b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000014
+ htlc_basepoint=024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97
+ # shachain seed=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ # first per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ first_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06
+ channel_flags=01
+
+ # Ignore unknown odd messages
+ 1. nothing
+ 1. recv: type=9999
+
+ 2. recv: type=accept_channel
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ dust_limit_satoshis=546
+ max_htlc_value_in_flight_msat=4294967295
+ channel_reserve_satoshis=9998
+ htlc_minimum_msat=0
+ minimum_depth=3
+ # If these are different, the commitment tx will be different!
+ to_self_delay=6
+ max_accepted_htlcs=483
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000020
+ funding_pubkey=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000021
+ revocation_basepoint=021697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000022
+ payment_basepoint=031be68a5a028f2601d0e80d468c344ba331d611b96c358b6032e8b4da0547fc11
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000023
+ delayed_payment_basepoint=03605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000024
+ htlc_basepoint=02e0392cfa338aaf2f0b56c563e3e5e67a5d5fefe3388f85d90c899da20f0198f9
+ # shachain seed=0000000000000000000000000000000000000000000000000000000000000000
+ # first per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ first_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8
+
+ # Ignore unknown messages
+ 1. nothing
+ 1. recv: type=9999
+
+ 3. expect-send: type=funding_created
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # Funding tx is
+ # txid=189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d
+ funding_txid=1d3160756ceeaf5474f389673aafe0484e58260927871ce92f388f72b0409c18
+ funding_output_index=0
+ # opener's commitment tx is 02000000011d3160756ceeaf5474f389673aafe0484e58260927871ce92f388f72b0409c1800000000006d669280010e410f000000000022002002ea9a3a14d15893571391cb43308d0cf2c873025a7ee3e1c59fc94e7d0b820750e05c20
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:b5f455ab7e6dc6cbdd0e4f5ff9623091f5e2317d90215734f0b7b468b2de4d9f)
+
+ 4. recv: type=funding_signed
+ channel_id=1d3160756ceeaf5474f389673aafe0484e58260927871ce92f388f72b0409c18
+ # accepter's commitment tx is 02000000011d3160756ceeaf5474f389673aafe0484e58260927871ce92f388f72b0409c1800000000006d669280010e410f000000000016001413619a3971c150996e7d1f391d9ade1405df87ec50e05c20
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:9afc89f545df32a456355a74f435d9f40bdb0b255f38af3d19ff344ab3ea0ea1)
+
+ 5. block: height=103 n=3
+
+ 6. recv: type=funding_locked
+ channel_id=1d3160756ceeaf5474f389673aafe0484e58260927871ce92f388f72b0409c18
+ next_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d
+
+ 7. expect-send: type=funding_locked
+ channel_id=1d3160756ceeaf5474f389673aafe0484e58260927871ce92f388f72b0409c18
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+
+ # Ignore unknown odd messages
+ 1. nothing
+ 1. recv: type=9999
+
diff --git a/tests/events/bolt2-02-add-htlc.events b/tests/events/bolt2-02-add-htlc.events
new file mode 100644
index 000000000..2efc8f79c
--- /dev/null
+++ b/tests/events/bolt2-02-add-htlc.events
@@ -0,0 +1,540 @@
+# Variations on adding an HTLC.
+
+# BOLT #7:
+# The origin node:
+# - MAY create a `channel_update` to communicate the channel
+# parameters to the channel peer, even though the channel has not
+# yet been announced (i.e. the `announce_channel` bit was not set).
+
+# In fact, it makes sense to do this after each reconnect.
+MAYBE_UPDATE=maybe-send: type=channel_update
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ # FIXME: Fill other expected fields here!
+
+include setup.incl
+
+1. block: $BLOCK_102
+
+2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+3. expect-send: type=init
+4. recv: type=init globalfeatures= features=2002
+5. recv: type=open_channel
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ funding_satoshis=999878
+ push_msat=0
+ dust_limit_satoshis=546
+ max_htlc_value_in_flight_msat=4294967295
+ channel_reserve_satoshis=9998
+ htlc_minimum_msat=0
+ feerate_per_kw=253
+ # node has to_self_delay=6; we use 5 to test differentiation
+ to_self_delay=5
+ max_accepted_htlcs=483
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000020
+ funding_pubkey=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000021
+ revocation_basepoint=021697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000022
+ payment_basepoint=031be68a5a028f2601d0e80d468c344ba331d611b96c358b6032e8b4da0547fc11
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000023
+ delayed_payment_basepoint=03605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000024
+ htlc_basepoint=02e0392cfa338aaf2f0b56c563e3e5e67a5d5fefe3388f85d90c899da20f0198f9
+ # shachain seed=0000000000000000000000000000000000000000000000000000000000000000
+ # first per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ first_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8
+ channel_flags=01
+
+6. expect-send: type=accept_channel
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000010
+ funding_pubkey=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000011
+ revocation_basepoint=03defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000012
+ payment_basepoint=025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000013
+ delayed_payment_basepoint=022b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000014
+ htlc_basepoint=024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97
+ # shachain seed=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ # per_commitment_secret #0=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ first_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06
+ # If these are different, the commitment tx will be different!
+ to_self_delay=6
+ channel_reserve_satoshis=9998
+
+7. recv: type=funding_created
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # Funding tx is 020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+ # txid=41085b995c1f591cfc3ae79ccde012bf0b37c7bde23d80a61c9732bdd6210b2f
+ funding_txid=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ funding_output_index=0
+ # !option_static_remotekey: node's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000160014749af8703f0d1fd8890a553bd62e9caf15f7bad44cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:97b9f4b67c7d404c82f97d86d7e5b2689e366abf1609abd889143a5999c6df47) !option_static_remotekey
+ # option_static_remotekey: node's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000160014e142ca9bfc2d56cd0adb82f8dc870424767389f74cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:f116f87d1a90f2d598b37e922dd568a8757a703b197f00fb131d089060f32493) option_static_remotekey
+
+8. expect-send: type=funding_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # test's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000220020233d69d88092351875ce0b9fd5ea576b2307c539eaed7abdf97fbb26720f01ac4cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:49fcc656b58e78f639b8af4bca65fe1ee948ea36eb7629222320518e33a42f29)
+
+9. block: height=103 n=3 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+
+10. expect-send: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+
+11. recv: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # per_commitment_secret #1 = dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3
+ next_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d
+
+12. $MAYBE_UPDATE
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ # Note: if we opened channel with option_static_remotekey, it stands even
+ # if we don't negotiate it again!
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+
+ 7. $MAYBE_UPDATE
+ # BOLT #2:
+ # - if `next_commitment_number` is 1 in both the
+ # `channel_reestablish` it sent and received:
+ # - MUST retransmit `funding_locked`.
+ # - otherwise:
+ # - MUST NOT retransmit `funding_locked`.
+ 8. expect-send: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+
+# That aside has nothing to do with the next aside.
+13. nothing
+
+# Add a dust HTLC
+ 1. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ amount_msat=1000
+ # preimage=0000000000000000000000000000000000000000000000000000000000000000
+ payment_hash=66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000003E8000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b3113cbed88e6cfb566f7a693bb63c9a89925c1f5df0a115b4893128866a81c1b
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ 8. expect-send: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+ 9. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ amount_msat=1000
+ # preimage=0000000000000000000000000000000000000000000000000000000000000000
+ payment_hash=66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000003E8000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b3113cbed88e6cfb566f7a693bb63c9a89925c1f5df0a115b4893128866a81c1b
+
+ 2. recv: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:15d8bb3b8ec14a670144d6d06c399e68fe8cf472d889ed65084d86da20064930) !option_static_remotekey
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:a52afdb15b8282c140516e809793bafb5d7d8f20d246ffe390e837c6a03889b8) option_static_remotekey
+ htlc_signature=
+
+ 3. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+
+ 4. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:8319b604143b4dedec0f3670abdf7d1a1c320aaefe589dba4aefbb1d083ec22d)
+ htlc_signature=
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # If tester did not receive node's revoke_and_ack:
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ # - MUST re-send the `revoke_and_ack`.
+ 8. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+ 9. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:8319b604143b4dedec0f3670abdf7d1a1c320aaefe589dba4aefbb1d083ec22d)
+ htlc_signature=
+
+ # If tester did receive node's revoke_and_ack, but not commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ 8. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:8319b604143b4dedec0f3670abdf7d1a1c320aaefe589dba4aefbb1d083ec22d)
+ htlc_signature=
+
+ # If tester did receive node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+ 5. recv: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ # per_commitment_secret #2=c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a
+ next_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # tester must have received node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+ # Add a non-dust HTLC
+ 1. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ amount_msat=1000000
+ # preimage=0000000000000000000000000000000000000000000000000000000000000000
+ payment_hash=66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000F4240000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd6b6fd853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b1450d92edf1300d7cfd2ffa175ab1c551817d67b9d095c976faaa23e598ba243
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ 8. expect-send: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+ 9. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ amount_msat=1000000
+ # preimage=0000000000000000000000000000000000000000000000000000000000000000
+ payment_hash=66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000F4240000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd6b6fd853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b1450d92edf1300d7cfd2ffa175ab1c551817d67b9d095c976faaa23e598ba243
+
+ 2. recv: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # !option_static_remotekey:
+ ## remote_commitment
+ # input amount 999878sat, funding_wscript 522103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e652103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a52ae, key 03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # unsigned remote commitment tx: 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a98002e8030000000000002200204a7ee4c37da8a8ab48e53517c27173098a61f794d2adb84faf0c86ec0c18385ffc3c0f0000000000160014ded1a6b285067b547db1d27bc87d32b84ac949cc4dff0020
+ ## Output 0: LOCAL HTLC 0
+ # unsigned htlc tx for output 0: 0200000001d3617e206fcf3d62ccaf0328e6ef88b9936afde335552e720226447eb4afffcc000000000000000000013703000000000000220020c950c75b39235322de605f8d8ff7533edf8cfa4e5edd090425c326cfbf4d216700000000
+ # wscript: 76a9143eb3762c8c276c6298a7aa4b6891873d3a4cacd88763ac6721037c6136c02af011b4f0f592d2dc7fc8b5b273b394ed8e6457130ad7c74dda0b4f7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21027f79127629a39c5b967ed0f02a27e50e388efd95fe1601a1b74e50ca0eee92fd52ae677502c800b175ac6868
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:e0872852d75887dd5ea2787aa1dee5188b7962ee61750df37475ec01dbd3dd11) !option_static_remotekey
+ htlc_signature=SIG(54b99d3db5bbf9326bcadd27ff599ec9ec286469482482c1f92f70b31e786177:be34fbaec509cd340a2ced0eaf9cb652ef72f5b4f78e4450cdc592727d7008c6) !option_static_remotekey
+ # option_static_remotekey:
+ ## remote_commitment
+ # input amount 999878sat, funding_wscript 522103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e652103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a52ae, key 03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # unsigned remote commitment tx: 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a98002e8030000000000002200204a7ee4c37da8a8ab48e53517c27173098a61f794d2adb84faf0c86ec0c18385ffc3c0f0000000000160014e142ca9bfc2d56cd0adb82f8dc870424767389f74dff0020
+ # Output 0: LOCAL HTLC 0
+ # unsigned htlc tx for output 0: 0200000001a366eac6a4f4aad69c3b292d275f1122e34aa97d091ae808422ef4b6d7599c5f000000000000000000013703000000000000220020cdd8da56037931fe7395cbe19893316feb98077ee1acb1b62f31ef850201f29000000000
+ # wscript: 76a9143eb3762c8c276c6298a7aa4b6891873d3a4cacd88763ac6721037c6136c02af011b4f0f592d2dc7fc8b5b273b394ed8e6457130ad7c74dda0b4f7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21027f79127629a39c5b967ed0f02a27e50e388efd95fe1601a1b74e50ca0eee92fd52ae677502c800b175ac6868
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:5a077920e9a7ec1a548de73b44987193d561f0d7b3648e0147b07775fabd2500) option_static_remotekey
+ htlc_signature=SIG(54b99d3db5bbf9326bcadd27ff599ec9ec286469482482c1f92f70b31e786177:cf81dc68974679ec580ace3aa26e46c06b2bfff16ebf51ccf3c34228b5635339) option_static_remotekey
+
+ 3. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+
+ 4. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # Note: because we only have to-self output, signatures don't change
+ # with option_static_remotekey
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:6dd008b3a07ec3d19a15eecb5982ef762f43ad04966a279d328062dbb2c1e64b)
+ htlc_signature=SIG(94549ae2f31fa7adf477a20c34d7c43ec5c4f2144c3ed7281127b00c85f02139:e96e9d7044cb56d55f124a7493f9c13b6389e64ce31efd22e04bb9809a4c3394)
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # If tester did not receive node's revoke_and_ack:
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ # - MUST re-send the `revoke_and_ack`.
+ 8. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+ 9. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:6dd008b3a07ec3d19a15eecb5982ef762f43ad04966a279d328062dbb2c1e64b)
+ htlc_signature=SIG(94549ae2f31fa7adf477a20c34d7c43ec5c4f2144c3ed7281127b00c85f02139:e96e9d7044cb56d55f124a7493f9c13b6389e64ce31efd22e04bb9809a4c3394)
+
+ # If tester did receive node's revoke_and_ack, but not commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ 8. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:6dd008b3a07ec3d19a15eecb5982ef762f43ad04966a279d328062dbb2c1e64b)
+ htlc_signature=SIG(94549ae2f31fa7adf477a20c34d7c43ec5c4f2144c3ed7281127b00c85f02139:e96e9d7044cb56d55f124a7493f9c13b6389e64ce31efd22e04bb9809a4c3394)
+
+ # If tester did receive node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+ 5. recv: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ # per_commitment_secret #2=c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a
+ next_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # tester must have received node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=2002
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
diff --git a/tests/events/bolt2-03-htlc-fail.events b/tests/events/bolt2-03-htlc-fail.events
new file mode 100644
index 000000000..72c7318db
--- /dev/null
+++ b/tests/events/bolt2-03-htlc-fail.events
@@ -0,0 +1,419 @@
+# Variations on HTLC failure.
+
+# BOLT #7:
+# The origin node:
+# - MAY create a `channel_update` to communicate the channel
+# parameters to the channel peer, even though the channel has not
+# yet been announced (i.e. the `announce_channel` bit was not set).
+
+# In fact, it makes sense to do this after each reconnect.
+MAYBE_UPDATE=maybe-send: type=channel_update
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ # FIXME: Fill other expected fields here!
+
+include setup.incl
+
+1. block: $BLOCK_102
+
+2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+3. expect-send: type=init
+4. recv: type=init globalfeatures= features=2002
+5. recv: type=open_channel
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ funding_satoshis=999878
+ push_msat=0
+ dust_limit_satoshis=546
+ max_htlc_value_in_flight_msat=4294967295
+ channel_reserve_satoshis=9998
+ htlc_minimum_msat=0
+ feerate_per_kw=253
+ # node has to_self_delay=6; we use 5 to test differentiation
+ to_self_delay=5
+ max_accepted_htlcs=483
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000020
+ funding_pubkey=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000021
+ revocation_basepoint=021697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000022
+ payment_basepoint=031be68a5a028f2601d0e80d468c344ba331d611b96c358b6032e8b4da0547fc11
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000023
+ delayed_payment_basepoint=03605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000024
+ htlc_basepoint=02e0392cfa338aaf2f0b56c563e3e5e67a5d5fefe3388f85d90c899da20f0198f9
+ # shachain seed=0000000000000000000000000000000000000000000000000000000000000000
+ # first per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ first_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8
+ channel_flags=01
+
+6. expect-send: type=accept_channel
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000010
+ funding_pubkey=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000011
+ revocation_basepoint=03defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000012
+ payment_basepoint=025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000013
+ delayed_payment_basepoint=022b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000014
+ htlc_basepoint=024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97
+ # shachain seed=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ # per_commitment_secret #0=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ first_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06
+ # If these are different, the commitment tx will be different!
+ to_self_delay=6
+ channel_reserve_satoshis=9998
+
+7. recv: type=funding_created
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # Funding tx is 020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+ # txid=41085b995c1f591cfc3ae79ccde012bf0b37c7bde23d80a61c9732bdd6210b2f
+ funding_txid=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ funding_output_index=0
+ # !option_static_remotekey: node's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000160014749af8703f0d1fd8890a553bd62e9caf15f7bad44cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:97b9f4b67c7d404c82f97d86d7e5b2689e366abf1609abd889143a5999c6df47) !option_static_remotekey
+ # option_static_remotekey: node's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000160014e142ca9bfc2d56cd0adb82f8dc870424767389f74cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:f116f87d1a90f2d598b37e922dd568a8757a703b197f00fb131d089060f32493) option_static_remotekey
+
+8. expect-send: type=funding_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # test's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000220020233d69d88092351875ce0b9fd5ea576b2307c539eaed7abdf97fbb26720f01ac4cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:49fcc656b58e78f639b8af4bca65fe1ee948ea36eb7629222320518e33a42f29)
+
+9. block: height=103 n=3 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+
+10. expect-send: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+
+11. recv: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # per_commitment_secret #1 = dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3
+ next_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d
+
+12. $MAYBE_UPDATE
+
+ # Add a dust HTLC
+ 1. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ amount_msat=1000
+ # preimage=0000000000000000000000000000000000000000000000000000000000000000
+ payment_hash=66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000003E8000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b3113cbed88e6cfb566f7a693bb63c9a89925c1f5df0a115b4893128866a81c1b
+
+ 2. recv: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:15d8bb3b8ec14a670144d6d06c399e68fe8cf472d889ed65084d86da20064930) !option_static_remotekey
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:a52afdb15b8282c140516e809793bafb5d7d8f20d246ffe390e837c6a03889b8) option_static_remotekey
+ htlc_signature=
+
+ 3. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+
+ 4. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:8319b604143b4dedec0f3670abdf7d1a1c320aaefe589dba4aefbb1d083ec22d)
+ htlc_signature=
+
+ 5. recv: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ # per_commitment_secret #2=c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a
+ next_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # tester must have received node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+ # Add a non-dust HTLC
+ 1. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ amount_msat=1000000
+ # preimage=0000000000000000000000000000000000000000000000000000000000000000
+ payment_hash=66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000F4240000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd6b6fd853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b1450d92edf1300d7cfd2ffa175ab1c551817d67b9d095c976faaa23e598ba243
+
+ 2. recv: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:e0872852d75887dd5ea2787aa1dee5188b7962ee61750df37475ec01dbd3dd11) !option_static_remotekey
+ htlc_signature=SIG(54b99d3db5bbf9326bcadd27ff599ec9ec286469482482c1f92f70b31e786177:be34fbaec509cd340a2ced0eaf9cb652ef72f5b4f78e4450cdc592727d7008c6) !option_static_remotekey
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:5a077920e9a7ec1a548de73b44987193d561f0d7b3648e0147b07775fabd2500) option_static_remotekey
+ htlc_signature=SIG(54b99d3db5bbf9326bcadd27ff599ec9ec286469482482c1f92f70b31e786177:cf81dc68974679ec580ace3aa26e46c06b2bfff16ebf51ccf3c34228b5635339) option_static_remotekey
+
+ 3. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+
+ 4. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:6dd008b3a07ec3d19a15eecb5982ef762f43ad04966a279d328062dbb2c1e64b)
+ htlc_signature=SIG(94549ae2f31fa7adf477a20c34d7c43ec5c4f2144c3ed7281127b00c85f02139:e96e9d7044cb56d55f124a7493f9c13b6389e64ce31efd22e04bb9809a4c3394)
+
+ 5. recv: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ # per_commitment_secret #2=c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a
+ next_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # tester must have received node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+# Either way, it should reject HTLC, as it's not a known preimage.
+13. expect-send: type=update_fail_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ # FIXME: check that reason is correct!
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # tester must have received node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ # Now it will re-transmit
+ 8. expect-send: type=update_fail_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+
+14. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:34441617fff47a6624d7e0c82d2d5fd6b01013ca1413e65f1d2d46fc5afe5131)
+ htlc_signature=
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # tester has not received last commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ # Now it will re-transmit
+ 8. expect-send: type=update_fail_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ 9. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:34441617fff47a6624d7e0c82d2d5fd6b01013ca1413e65f1d2d46fc5afe5131)
+ htlc_signature=
+
+ # tester *has* received last commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+15. recv: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3
+ # per_commitment_secret #3=ba65d7b0ef55a3ba300d4e87af29868f394f8f138d78a7011669c79b37b936f4
+ next_per_commitment_point=030f1b0e7b158de0d85a05ec050304dcf189c879a888e5ca1ce45e829d4b075d37
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # tester must have received last commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=2
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_data_loss_protect
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+16. recv: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:f3d47c8ebc473392eae272f60930ef76794476e2738e1a11f5865603f2aa0abb) !option_static_remotekey
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:32c4246f301b7f4d2f8a84628dc729923aec2a2ad2cd60be3836e544294b2b0c) option_static_remotekey
+ htlc_signature=
+
+17. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964
+ # per_commitment_secret #3=27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116
+ next_per_commitment_point=02d8489d0db616cbdfc77d5eadd56c1bc8f136b8fb6b184167ff5cc7fceed71977
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # tester *has not* received revoke_and_ack
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=2
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_data_loss_protect
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+
+ # tester *has* received revoke_and_ack
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=2
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_data_loss_protect
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=2
+ your_last_per_commitment_secret=c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964 option_data_loss_protect
+ your_last_per_commitment_secret=c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
diff --git a/tests/events/bolt2-04-htlc-fulfill.events b/tests/events/bolt2-04-htlc-fulfill.events
new file mode 100644
index 000000000..045185f30
--- /dev/null
+++ b/tests/events/bolt2-04-htlc-fulfill.events
@@ -0,0 +1,415 @@
+# Variations on HTLC success.
+
+# BOLT #7:
+# The origin node:
+# - MAY create a `channel_update` to communicate the channel
+# parameters to the channel peer, even though the channel has not
+# yet been announced (i.e. the `announce_channel` bit was not set).
+
+# In fact, it makes sense to do this after each reconnect.
+MAYBE_UPDATE=maybe-send: type=channel_update
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ # FIXME: Fill other expected fields here!
+
+include setup.incl
+
+1. block: $BLOCK_102
+
+# Make two invoices: one is dust, one is not.
+2. invoice: amount=1000 preimage=0000000000000000000000000000000000000000000000000000000000001000
+3. invoice: amount=1000000 preimage=0000000000000000000000000000000000000000000000000000000001000000
+
+4. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+5. expect-send: type=init
+6. recv: type=init globalfeatures= features=2002
+7. recv: type=open_channel
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ funding_satoshis=999878
+ push_msat=0
+ dust_limit_satoshis=546
+ max_htlc_value_in_flight_msat=4294967295
+ channel_reserve_satoshis=9998
+ htlc_minimum_msat=0
+ feerate_per_kw=253
+ to_self_delay=5
+ max_accepted_htlcs=483
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000020
+ funding_pubkey=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000021
+ revocation_basepoint=021697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000022
+ payment_basepoint=031be68a5a028f2601d0e80d468c344ba331d611b96c358b6032e8b4da0547fc11
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000023
+ delayed_payment_basepoint=03605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000024
+ htlc_basepoint=02e0392cfa338aaf2f0b56c563e3e5e67a5d5fefe3388f85d90c899da20f0198f9
+ # shachain seed=0000000000000000000000000000000000000000000000000000000000000000
+ # first per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ first_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8
+ channel_flags=01
+
+8. expect-send: type=accept_channel
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # funding_privkey=0000000000000000000000000000000000000000000000000000000000000010
+ funding_pubkey=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # revocation_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000011
+ revocation_basepoint=03defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34
+ # payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000012
+ payment_basepoint=025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc
+ # delayed_payment_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000013
+ delayed_payment_basepoint=022b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c
+ # htlc_basepoint_secret=0000000000000000000000000000000000000000000000000000000000000014
+ htlc_basepoint=024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97
+ # shachain seed=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ # per_commitment_secret #0=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ first_per_commitment_point=0288a618cb6027c3218a37cbe9e882379f17d87d03f6e99d0b60292478d2aded06
+ # If these are different, the commitment tx will be different!
+ to_self_delay=6
+ channel_reserve_satoshis=9998
+
+9. recv: type=funding_created
+ temporary_channel_id=0000000000000000000000000000000000000000000000000000000000000000
+ # Funding tx is 020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+ # txid=41085b995c1f591cfc3ae79ccde012bf0b37c7bde23d80a61c9732bdd6210b2f
+ funding_txid=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ funding_output_index=0
+ # !option_static_remotekey: node's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000160014749af8703f0d1fd8890a553bd62e9caf15f7bad44cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:97b9f4b67c7d404c82f97d86d7e5b2689e366abf1609abd889143a5999c6df47) !option_static_remotekey
+ # option_static_remotekey: node's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000160014e142ca9bfc2d56cd0adb82f8dc870424767389f74cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:f116f87d1a90f2d598b37e922dd568a8757a703b197f00fb131d089060f32493) option_static_remotekey
+
+10. expect-send: type=funding_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # test's commitment tx is 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a980010f410f0000000000220020233d69d88092351875ce0b9fd5ea576b2307c539eaed7abdf97fbb26720f01ac4cff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:49fcc656b58e78f639b8af4bca65fe1ee948ea36eb7629222320518e33a42f29)
+
+11. block: height=103 n=3 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c6410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d818711738202473044022047e9e6e798ba9adb6c84bdcd6230a96fb6de9dcca84d81103fb2bc08906cb884022027599b1e80289eaf238e9a00119a79a0ccceab7d83d54719e10bd0c3300a0d34012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+
+12. expect-send: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206
+
+13. recv: type=funding_locked
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # per_commitment_secret #1 = dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3
+ next_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d
+
+14. $MAYBE_UPDATE
+
+# First invoice-paying HTLC
+15. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ amount_msat=1000
+ # preimage=0000000000000000000000000000000000000000000000000000000000001000
+ payment_hash=0193d8ff39177fc604d8c0e60d5495222da10cd84d4ae6d12bf84ca923158b31
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000003E8000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941ba2804c6057dd454fbb44ad5e52b3383b9bcfda3647eefe650ab93b99ee60c8da
+
+16. recv: type=update_add_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=1
+ amount_msat=1000000
+ # preimage=0000000000000000000000000000000000000000000000000000000001000000
+ payment_hash=954a6575b642bdddd05409cf5973ba837f25b2e391950be91fa23334093d88f5
+ cltv_expiry=200
+ # hop_data[0] = 00000000000000000000000000000F4240000000C8000000000000000000000000
+ onion_routing_packet=0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd6b6fd853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b5be6403eef1373ff72577187afc526cac3c467f07dd00cc91886109c914781d7
+
+17. recv: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # !option_static_remotekey:
+ ## remote_commitment
+ # input amount 999878sat, funding_wscript 522103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e652103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a52ae, key 03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # unsigned remote commitment tx: 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a98002e80300000000000022002061a5a700a99098d0c53f5d58b0c4f041ab861467b4eb5841a9d80ba300daf8dafb3c0f0000000000160014ded1a6b285067b547db1d27bc87d32b84ac949cc4dff0020
+ ## Output 0: LOCAL HTLC 1
+ # unsigned htlc tx for output 0: 02000000014e7c13514515d6baba93a001522446dcb1fbcd73d759b647c9b835cb6c3800c2000000000000000000013703000000000000220020cdd8da56037931fe7395cbe19893316feb98077ee1acb1b62f31ef850201f29000000000
+ # wscript: 76a9143eb3762c8c276c6298a7aa4b6891873d3a4cacd88763ac6721037c6136c02af011b4f0f592d2dc7fc8b5b273b394ed8e6457130ad7c74dda0b4f7c8201208763a9148139a4b000a75011a6f624005c9482c04160d94588527c21027f79127629a39c5b967ed0f02a27e50e388efd95fe1601a1b74e50ca0eee92fd52ae677502c800b175ac6868
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:d9c6623c3375c009da48e0277d7bf9ef8d0a788405907c815720f7a6c1f974a0) !option_static_remotekey
+ htlc_signature=SIG(54b99d3db5bbf9326bcadd27ff599ec9ec286469482482c1f92f70b31e786177:ad84fd11115dc61e0542f869c22c24184a03ed3b6cf9ac06cb71e3d9e8c1b3db) !option_static_remotekey
+ # option_static_remotekey:
+ ## remote_commitment
+ # input amount 999878sat, funding_wscript 522103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e652103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a52ae, key 03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ # unsigned remote commitment tx: 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a98002e80300000000000022002061a5a700a99098d0c53f5d58b0c4f041ab861467b4eb5841a9d80ba300daf8dafb3c0f0000000000160014e142ca9bfc2d56cd0adb82f8dc870424767389f74dff0020
+ ## Output 0: LOCAL HTLC 1
+ # unsigned htlc tx for output 0: 020000000162b2ce7a192e54c942c3abca50c0535cb60990f08480c7fbb02cf66d641d6f23000000000000000000013703000000000000220020cdd8da56037931fe7395cbe19893316feb98077ee1acb1b62f31ef850201f29000000000
+ # wscript: 76a9143eb3762c8c276c6298a7aa4b6891873d3a4cacd88763ac6721037c6136c02af011b4f0f592d2dc7fc8b5b273b394ed8e6457130ad7c74dda0b4f7c8201208763a9148139a4b000a75011a6f624005c9482c04160d94588527c21027f79127629a39c5b967ed0f02a27e50e388efd95fe1601a1b74e50ca0eee92fd52ae677502c800b175ac6868
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:c127a333bd94cec1a7b2c443f500c0954c19f605a2f1df58b7ea1bbda4d99532) option_static_remotekey
+ htlc_signature=SIG(54b99d3db5bbf9326bcadd27ff599ec9ec286469482482c1f92f70b31e786177:40c3525d2e1690d63458ccca18193d59571a695dcdf805206a88e5cda9edff9a) option_static_remotekey
+
+18. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+
+19. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:a52bd92ccb9195c5b45bb2badf390f2c070f20b22edf24fac5cd99f1e1e6db11)
+ htlc_signature=SIG(94549ae2f31fa7adf477a20c34d7c43ec5c4f2144c3ed7281127b00c85f02139:17b05a9103b123ad6d6c990d6262649c92fa730f00345b9c677608526400b06e)
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+
+ # If tester did not receive node's revoke_and_ack:
+ 1. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=0
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_data_loss_protect
+ your_last_per_commitment_secret=0000000000000000000000000000000000000000000000000000000000000000 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 2. $MAYBE_UPDATE
+ # - MUST re-send the `revoke_and_ack` and `commitment_signed`
+ 3. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc
+ # per_commitment_secret 2=2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8
+ next_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad
+ 4. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:a52bd92ccb9195c5b45bb2badf390f2c070f20b22edf24fac5cd99f1e1e6db11)
+ htlc_signature=SIG(94549ae2f31fa7adf477a20c34d7c43ec5c4f2144c3ed7281127b00c85f02139:17b05a9103b123ad6d6c990d6262649c92fa730f00345b9c677608526400b06e)
+
+ # If tester did receive node's revoke_and_ack, but not commitment_signed
+ 1. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=1
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02037803a3228ec3a517835480ffac64c0557d9d75e0fe85861ab0be9eb224e6f8 option_data_loss_protect !option_static_remotekey
+ 2. $MAYBE_UPDATE
+ # - MUST re-send `commitment_signed`
+ 3. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:a52bd92ccb9195c5b45bb2badf390f2c070f20b22edf24fac5cd99f1e1e6db11)
+ htlc_signature=SIG(94549ae2f31fa7adf477a20c34d7c43ec5c4f2144c3ed7281127b00c85f02139:17b05a9103b123ad6d6c990d6262649c92fa730f00345b9c677608526400b06e)
+
+ # If tester did receive node's commitment_signed
+ 1. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 2. $MAYBE_UPDATE
+
+20. recv: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148
+ # per_commitment_secret #2=c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a
+ next_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad
+
+# It should accept both of them, but could be in any order.
+# (Could also do this in two separate commits, but I didn't add that).
+21. Any order:
+ 1. expect-send: type=update_fulfill_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ payment_preimage=0000000000000000000000000000000000000000000000000000000000001000
+ 1. expect-send: type=update_fulfill_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=1
+ payment_preimage=0000000000000000000000000000000000000000000000000000000001000000
+
+# Separator: next is not part of Any order.
+22. nothing
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ # tester must have received node's commitment_signed
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+ 6. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 7. $MAYBE_UPDATE
+ # Now it will re-transmit
+ 8. Any order:
+ 1. expect-send: type=update_fulfill_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ payment_preimage=0000000000000000000000000000000000000000000000000000000000001000
+ 1. expect-send: type=update_fulfill_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=1
+ payment_preimage=0000000000000000000000000000000000000000000000000000000001000000
+
+23. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ # !option_static_remotekey:
+ # unsigned local commitment tx: 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a98002e903000000000000160014f546ff0865eeecfdef611a3298eca91e656e73b5263d0f00000000002200209d2eb597904551cd8f3b6ee050cef80002b976d5ccf447cefd6a6fc93966580c4eff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:79d2311d86c38520b3db2477ffc8b971dd518177cbc4c9d8944f819034e45269) !option_static_remotekey
+
+ # option_static_remotekey:
+ # unsigned local commitment tx: 02000000012f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b08410000000000f436a98002e903000000000000160014f0f4189b8cf9f2db0ab8d3a3c009e1823a58842e263d0f00000000002200209d2eb597904551cd8f3b6ee050cef80002b976d5ccf447cefd6a6fc93966580c4eff0020
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:fe72bb88dfa91ff143147b4a1c2a5d42185628b70e77745ac0b35a7592b5e897) option_static_remotekey
+ htlc_signature=
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_data_loss_protect
+ your_last_per_commitment_secret=02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206 option_data_loss_protect !option_static_remotekey
+
+ # tester has not received last commitment_signed
+ 1. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=2
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d option_data_loss_protect !option_static_remotekey
+ 2. $MAYBE_UPDATE
+ # Now it will re-transmit
+ 3. Any order:
+ 1. expect-send: type=update_fulfill_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=0
+ payment_preimage=0000000000000000000000000000000000000000000000000000000000001000
+ 1. expect-send: type=update_fulfill_htlc
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ id=1
+ payment_preimage=0000000000000000000000000000000000000000000000000000000001000000
+ 4. expect-send: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:79d2311d86c38520b3db2477ffc8b971dd518177cbc4c9d8944f819034e45269) !option_static_remotekey
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000010:fe72bb88dfa91ff143147b4a1c2a5d42185628b70e77745ac0b35a7592b5e897) option_static_remotekey
+ htlc_signature=
+
+ # tester *has* received last commitment_signed
+ 1. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad option_data_loss_protect !option_static_remotekey
+ 2. $MAYBE_UPDATE
+
+24. recv: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3
+ # per_commitment_secret #3=ba65d7b0ef55a3ba300d4e87af29868f394f8f138d78a7011669c79b37b936f4
+ next_per_commitment_point=030f1b0e7b158de0d85a05ec050304dcf189c879a888e5ca1ce45e829d4b075d37
+
+25. recv: type=commitment_signed
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:ade3011148ec17fcddb87113a1fb6f1c053b669f81196a1b50f1b3c047d9cfe2) !option_static_remotekey
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000020:4b9baaa069e2f9d78037e4721609dc8d55249daa36e29f410d74e64cfaf7d645) option_static_remotekey
+ htlc_signature=
+
+26. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964
+ # per_commitment_secret #3=27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116
+ next_per_commitment_point=02d8489d0db616cbdfc77d5eadd56c1bc8f136b8fb6b184167ff5cc7fceed71977
+
+ # Optional reconnection testing.
+ 1. nothing
+
+ # Ignore unknown odd messages
+ 1. recv: type=9999
+
+ 1. disconnect:
+ 2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000002
+ 3. expect-send: type=init
+ 4. recv: type=init globalfeatures= features=02
+ 5. expect-send: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=2
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_data_loss_protect
+ your_last_per_commitment_secret=dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3 option_static_remotekey !option_data_loss_protect
+ my_current_per_commitment_point=03bca7c4ebe7eb7e8e40b8c2a7b4dde7f4d48404c9a62859d09fe2d00151af40ad option_data_loss_protect !option_static_remotekey
+
+ # tester *has not* received revoke_and_ack
+ 1. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=1
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_data_loss_protect
+ your_last_per_commitment_secret=7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad option_data_loss_protect !option_static_remotekey
+ 2. $MAYBE_UPDATE
+ 3. expect-send: type=revoke_and_ack
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ per_commitment_secret=c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964
+ # per_commitment_secret #3=27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116
+ next_per_commitment_point=02d8489d0db616cbdfc77d5eadd56c1bc8f136b8fb6b184167ff5cc7fceed71977
+
+ # tester *has* received revoke_and_ack
+ 1. recv: type=channel_reestablish
+ channel_id=2f0b21d6bd32971ca6803de2bdc7370bbf12e0cd9ce73afc1c591f5c995b0841
+ next_commitment_number=3
+ next_revocation_number=2
+ your_last_per_commitment_secret=c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964 option_data_loss_protect
+ your_last_per_commitment_secret=c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964 option_static_remotekey !option_data_loss_protect
+ # If we have option_static_remotekey, this field is ignored (must be valid point though!)
+ my_current_per_commitment_point=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65 option_static_remotekey
+ my_current_per_commitment_point=02e1ea4f6d28dade887280214c359fc808066a64e750e3c81747dc3074833ff0ad option_data_loss_protect !option_static_remotekey
+ 2. $MAYBE_UPDATE
diff --git a/tests/events/bolt7-01-channel_announcement-success.events b/tests/events/bolt7-01-channel_announcement-success.events
new file mode 100644
index 000000000..197e0f50c
--- /dev/null
+++ b/tests/events/bolt7-01-channel_announcement-success.events
@@ -0,0 +1,110 @@
+# Simple gossip tests.
+
+include setup.incl
+
+1. block: $BLOCK_102
+
+2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000003
+3. expect-send: type=init
+4. recv: type=init features= globalfeatures=
+
+# Funding tx spending 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/1, feerate 253 to bitcoin privkeys 0000000000000000000000000000000000000000000000000000000000000010 and 0000000000000000000000000000000000000000000000000000000000000020
+# txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d
+5. block: height=103 n=6 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c5410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d8187117382024730440220798d96d5a057b5b7797988a855217f41af05ece3ba8278366e2f69763c72e785022065d5dd7eeddc0766ddf65557c92b9c52c301f23f94d2cf681860d32153e6ae1e012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+
+6. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+
+# New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement, as there is no channel_update.
+7. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+8. expect-send: type=init
+9. recv: type=init features=08 globalfeatures=
+10. must-not-send: type=channel_announcement
+11. disconnect:
+
+12. recv: type=channel_update
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000002:5133e0542731e0b4b70b4cb99f8fb7dab2e6658b5a5add8d9dfd1a8e2c549f95)
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ timestamp=1565587763
+ message_flags=0
+ channel_flags=0
+ cltv_expiry_delta=144
+ htlc_minimum_msat=0
+ fee_base_msat=1000
+ fee_proportional_millionths=10
+
+# Now we'll relay to a new peer.
+13. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+14. expect-send: type=init
+15. recv: type=init features=08 globalfeatures=
+16. expect-send: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+17. expect-send: type=channel_update
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000002:5133e0542731e0b4b70b4cb99f8fb7dab2e6658b5a5add8d9dfd1a8e2c549f95)
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ timestamp=1565587763
+ message_flags=0
+ channel_flags=0
+ cltv_expiry_delta=144
+ htlc_minimum_msat=0
+ fee_base_msat=1000
+ fee_proportional_millionths=10
+
+# And it will relay this, too.
+18. recv: conn=0000000000000000000000000000000000000000000000000000000000000003 type=channel_update
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000003:d86dd6f31dc7956bae1e86407f38548fb2cda5f3f7441577694b1c5d455f153f)
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ timestamp=1565587763
+ message_flags=1
+ channel_flags=1
+ cltv_expiry_delta=48
+ htlc_minimum_msat=0
+ fee_base_msat=100
+ fee_proportional_millionths=11
+ htlc_maximum_msat=100000
+19. expect-send: type=channel_update
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000003:d86dd6f31dc7956bae1e86407f38548fb2cda5f3f7441577694b1c5d455f153f)
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ timestamp=1565587763
+ message_flags=1
+ channel_flags=1
+ cltv_expiry_delta=48
+ htlc_minimum_msat=0
+ fee_base_msat=100
+ fee_proportional_millionths=11
+ htlc_maximum_msat=100000
+20. disconnect:
+
+# Now, if channel closes, it won't be relayed.
+
+21. block: height=109 n=1 tx=020000000001011d3160756ceeaf5474f389673aafe0484e58260927871ce92f388f72b0409c180000000000ffffffff010e410f00000000001600141b42e1fc7b1cd93a469fa67ed5eabf36ce354dd60400483045022100d93a21312af5b9a46041d2189e5b72f593fc865d920f705d76a25a728de5790302207995cc2dd45ff20c96ccea8b117be41581da8b84466dabfeea728ed858a3a7fd0147304402206d9f5e3b2b2540002ffc37815cef3fbc4ba7646a7bca1aa7605941edd735dee802205130da104d584df6b59c18704c94cd4edd032aed7e0c3044dc8815183552f2dd0147522103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e652103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a52ae00000000
+
+22. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+23. expect-send: type=init
+24. recv: type=init features=08 globalfeatures=
+25. must-not-send: type=channel_announcement
+26. must-not-send: type=channel_update
diff --git a/tests/events/bolt7-02-channel_announcement-failures.events b/tests/events/bolt7-02-channel_announcement-failures.events
new file mode 100644
index 000000000..3f581376f
--- /dev/null
+++ b/tests/events/bolt7-02-channel_announcement-failures.events
@@ -0,0 +1,207 @@
+# Tests for malformed/bad channel_announcement
+include setup.incl
+
+1. block: $BLOCK_102
+
+2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000003
+3. expect-send: type=init
+4. recv: type=init features= globalfeatures=
+
+# Funding tx spending 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/1, feerate 253 to bitcoin privkeys 0000000000000000000000000000000000000000000000000000000000000010 and 0000000000000000000000000000000000000000000000000000000000000020
+# txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d
+5. block: height=103 n=1 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c5410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d8187117382024730440220798d96d5a057b5b7797988a855217f41af05ece3ba8278366e2f69763c72e785022065d5dd7eeddc0766ddf65557c92b9c52c301f23f94d2cf681860d32153e6ae1e012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+
+ # Invalid `channel_announcement`: short_channel_id too young.
+ # It's allowed (even encouraged!) to cache this, so we separate this
+ # from the other tests.
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+
+ # Invalid `channel_announcement`: short_channel_id *still* too young.
+ 1. block: height=104 n=4
+ 2. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+
+ # This makes announcement otherwise acceptable.
+ 1. block: height=104 n=5
+
+ # Invalid `channel_announcement`: bad node_signature_1.
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791b)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ 2. expect-error:
+
+ # Invalid `channel_announcement`: bad node_signature_2.
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791b)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ 2. expect-error:
+
+ # Invalid `channel_announcement`: bad bitcoin_signature_1.
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791b)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ 2. expect-error:
+
+ # Invalid `channel_announcement`: bad bitcoin_signature_2.
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791b)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+ 2. expect-error:
+
+ # Ignored `channel_announcement`: bad chain_hash
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf1889100
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+
+ # Invalid `channel_announcement`: short_channel_id DNE
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:31592d21e4e2bb642dd9af658ab9ba629322fc028682f1b2eeb9afd8dfbf5cb5)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:31592d21e4e2bb642dd9af658ab9ba629322fc028682f1b2eeb9afd8dfbf5cb5)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:31592d21e4e2bb642dd9af658ab9ba629322fc028682f1b2eeb9afd8dfbf5cb5)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:31592d21e4e2bb642dd9af658ab9ba629322fc028682f1b2eeb9afd8dfbf5cb5)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x5x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+
+ # Invalid `channel_announcement`: short_channel_id output DNE
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:ad0d16d5bcc1826294f8c316e801a95a4f3b1514fed0b9f38e0314ebcc0f2409)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:ad0d16d5bcc1826294f8c316e801a95a4f3b1514fed0b9f38e0314ebcc0f2409)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:ad0d16d5bcc1826294f8c316e801a95a4f3b1514fed0b9f38e0314ebcc0f2409)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:ad0d16d5bcc1826294f8c316e801a95a4f3b1514fed0b9f38e0314ebcc0f2409)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x1
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+
+ # Invalid `channel_announcement`: short_channel_id tx does not match
+ # (second bitcoin privkey is ...021 instead of ...020)
+ 1. recv: type=channel_announcement
+ node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:57b5c0c52e239a20200877037040492feb27a92d0184ac47c979e1ef5c0a0a4a)
+ node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:57b5c0c52e239a20200877037040492feb27a92d0184ac47c979e1ef5c0a0a4a)
+ bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:57b5c0c52e239a20200877037040492feb27a92d0184ac47c979e1ef5c0a0a4a)
+ bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000021:57b5c0c52e239a20200877037040492feb27a92d0184ac47c979e1ef5c0a0a4a)
+ features=
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
+ node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
+ bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
+ bitcoin_key_2=021697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5
+
+# To test if it accepted one of those channel_announcements, give it
+# the matching channel_update, which should make it broadcast.
+6. connect: privkey=0000000000000000000000000000000000000000000000000000000000000004
+7. recv: type=channel_update
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000002:5133e0542731e0b4b70b4cb99f8fb7dab2e6658b5a5add8d9dfd1a8e2c549f95)
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x0
+ timestamp=1565587763
+ message_flags=0
+ channel_flags=0
+ cltv_expiry_delta=144
+ htlc_minimum_msat=0
+ fee_base_msat=1000
+ fee_proportional_millionths=10
+8. recv: type=channel_update
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000002:2ce3390486bfbb1cd2a93022aa6a52e0453f819cc0f233bb7995f4b93bfd787e)
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x1x1
+ timestamp=1565587763
+ message_flags=0
+ channel_flags=0
+ cltv_expiry_delta=144
+ htlc_minimum_msat=0
+ fee_base_msat=1000
+ fee_proportional_millionths=10
+9. recv: type=channel_update
+ signature=SIG(0000000000000000000000000000000000000000000000000000000000000002:85f489ae81c450943aa159c0013c4536c8a104b5419be6276e61e165956081e6)
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ short_channel_id=103x5x0
+ timestamp=1565587763
+ message_flags=0
+ channel_flags=0
+ cltv_expiry_delta=144
+ htlc_minimum_msat=0
+ fee_base_msat=1000
+ fee_proportional_millionths=10
+
+# New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement.
+10. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+11. expect-send: type=init
+12. recv: type=init features=08 globalfeatures=
+13. must-not-send: type=channel_announcement
+14. must-not-send: type=channel_update
diff --git a/tests/events/bolt7-10-gossip_timestamp_filter.events b/tests/events/bolt7-10-gossip_timestamp_filter.events
new file mode 100644
index 000000000..5b3186cee
--- /dev/null
+++ b/tests/events/bolt7-10-gossip_timestamp_filter.events
@@ -0,0 +1,135 @@
+# Tests for gossip_timestamp_filter.
+
+# Variables for some standard gossiping messages.
+include setup.incl
+1. block: $BLOCK_102
+
+2. connect: privkey=0000000000000000000000000000000000000000000000000000000000000003
+3. expect-send: type=init
+4. recv: type=init features= globalfeatures=
+
+5. block: $BLOCK_103
+
+6. recv: $CHAN_ANN_103x1x0
+7. recv: $NODE_ANN_002
+
+# New peer connects, asks for gossip_timestamp_filter=all. We *won't* relay channel_announcement, as there is no channel_update.
+8. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+9. expect-send: type=init
+10. recv: type=init features= globalfeatures=
+11. recv: type=gossip_timestamp_filter
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ first_timestamp=0
+ timestamp_range=4294967295
+12. must-not-send: type=channel_announcement
+13. must-not-send: type=node_announcement
+14. disconnect:
+
+15. recv: $CHAN_UPDATE_103x1x0_002
+
+# New peer connects, asks for gossip_timestamp_filter=all. update and node announcement will be relayed.
+16. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+17. expect-send: type=init
+18. recv: type=init features= globalfeatures=
+19. recv: type=gossip_timestamp_filter
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ first_timestamp=0
+ timestamp_range=4294967295
+20. expect-send: $CHAN_ANN_103x1x0
+21. Any order:
+ 1. expect-send: $CHAN_UPDATE_103x1x0_002
+ 1. expect-send: $NODE_ANN_002
+22. disconnect:
+
+# BOLT 7:
+# The receiver:
+# - SHOULD send all gossip messages whose `timestamp` is greater or
+# equal to `first_timestamp`, and less than `first_timestamp` plus
+# `timestamp_range`.
+
+23. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+24. expect-send: type=init
+25. recv: type=init features= globalfeatures=
+26. recv: type=gossip_timestamp_filter
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ first_timestamp=1000
+ timestamp_range=1565586763
+27. must-not-send: type=channel_announcement
+28. must-not-send: type=channel_update
+29. must-not-send: type=node_announcement
+30. disconnect:
+
+31. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+32. expect-send: type=init
+33. recv: type=init features= globalfeatures=
+34. recv: type=gossip_timestamp_filter
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ first_timestamp=1565587764
+ timestamp_range=4294967295
+35. must-not-send: type=channel_announcement
+36. must-not-send: type=channel_update
+37. must-not-send: type=node_announcement
+38. disconnect:
+
+# These two succeed in getting the gossip, then stay connected for next test.
+39. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+40. expect-send: type=init
+41. recv: type=init features= globalfeatures=
+42. recv: type=gossip_timestamp_filter
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ first_timestamp=1565587763
+ timestamp_range=4294967295
+43. expect-send: $CHAN_ANN_103x1x0
+44. Any order:
+ 1. expect-send: $CHAN_UPDATE_103x1x0_002
+ 1. expect-send: $NODE_ANN_002
+
+45. connect: privkey=0000000000000000000000000000000000000000000000000000000000000006
+46. expect-send: type=init
+47. recv: type=init features= globalfeatures=
+48. recv: type=gossip_timestamp_filter
+ chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f
+ first_timestamp=1000
+ timestamp_range=1565586764
+49. expect-send: $CHAN_ANN_103x1x0
+50. Any order:
+ 1. expect-send: $CHAN_UPDATE_103x1x0_002
+ 1. expect-send: $NODE_ANN_002
+
+# BOLT 7:
+# - SHOULD restrict future gossip messages to those whose `timestamp`
+# is greater or equal to `first_timestamp`, and less than
+# `first_timestamp` plus `timestamp_range`.
+
+51. block: $BLOCK_109
+
+52. recv: conn=0000000000000000000000000000000000000000000000000000000000000003
+ $CHAN_ANN_109x1x0
+
+53. recv: conn=0000000000000000000000000000000000000000000000000000000000000003
+ $CHAN_UPDATE_109x1x0_004
+
+54. recv: conn=0000000000000000000000000000000000000000000000000000000000000003
+ $CHAN_UPDATE_109x1x0_005
+
+55. recv: conn=0000000000000000000000000000000000000000000000000000000000000003
+ $NODE_ANN_004
+
+# 005's filter covers this, 006's doesn't.
+56. expect-send: conn=0000000000000000000000000000000000000000000000000000000000000005
+ $CHAN_ANN_109x1x0
+
+57. Any order:
+ 1. expect-send: conn=0000000000000000000000000000000000000000000000000000000000000005
+ $CHAN_UPDATE_109x1x0_004
+ 1. expect-send: conn=0000000000000000000000000000000000000000000000000000000000000005
+ $CHAN_UPDATE_109x1x0_005
+ 1. expect-send: conn=0000000000000000000000000000000000000000000000000000000000000005
+ $NODE_ANN_004
+
+58. must-not-send: conn=0000000000000000000000000000000000000000000000000000000000000006
+ type=channel_announcement
+59. must-not-send: conn=0000000000000000000000000000000000000000000000000000000000000006
+ type=channel_update
+60. must-not-send: conn=0000000000000000000000000000000000000000000000000000000000000006
+ type=node_announcement
diff --git a/tests/events/bolt7-20-query_channel_range.events b/tests/events/bolt7-20-query_channel_range.events
new file mode 100644
index 000000000..2cafa2d4b
--- /dev/null
+++ b/tests/events/bolt7-20-query_channel_range.events
@@ -0,0 +1,98 @@
+# Tests for query_channel_range.
+
+# Note for gossip_channel_range: we are *allowed* to return a superset
+# of what they ask, so if someone does that this test must be modified
+# to accept it (add an option_IMPL_gossip_query_superset).
+#
+# Meanwhile, we assume an exact reply.
+
+include setup.incl
+1. block: $BLOCK_102
+
+# Our two channels.
+2. block: $BLOCK_103
+3. block: $BLOCK_109
+
+4. connect: privkey=0000000000000000000000000000000000000000000000000000000000000003
+5. expect-send: type=init
+6. recv: type=init features= globalfeatures=
+# They can send this, we'll ignore it.
+7. maybe-send: type=gossip_timestamp_filter
+
+# Channel 103x1x0 (between 002 and 003)
+8. recv: $CHAN_ANN_103x1x0
+9. recv: $CHAN_UPDATE_103x1x0_002
+
+# Channel 109x1x0 (between 004 and 005)
+10. recv: $CHAN_ANN_109x1x0
+11. recv: $CHAN_UPDATE_109x1x0_005
+12. recv: $CHAN_UPDATE_109x1x0_004
+
+# New peer connects, with gossip_query option.
+13. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+14. expect-send: type=init
+15. recv: type=init features=80 globalfeatures=
+16. maybe-send: type=gossip_timestamp_filter
+
+ # No queries? Must not get anything.
+ 1. must-not-send: type=channel_announcement
+ 2. must-not-send: type=channel_update
+ 3. must-not-send: type=node_announcement
+
+ # This should elicit an empty response (we assume no zlib for that!)
+ 1. recv: type=query_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=0 number_of_blocks=103 option_gossip_queries
+ 2. One of: option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=0 number_of_blocks=103 encoded_short_ids=00 option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=0 number_of_blocks=103 encoded_short_ids=01789c030000000001 option_gossip_queries
+
+ # This should get the first one, not the second.
+ 1. recv: type=query_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=1 option_gossip_queries
+ 2. One of: option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=1 encoded_short_ids=000000670000010000 option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=1 encoded_short_ids=01789c6360486760606460000002750069 option_gossip_queries
+
+ # This should get the second one, not the first.
+ 1. recv: type=query_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=109 number_of_blocks=4294967295 option_gossip_queries
+ 2. One of: option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=109 number_of_blocks=4294967295 encoded_short_ids=0000006d0000010000 option_gossip_queries
+ # Could truncate number_of_blocks.
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=109 number_of_blocks=4294967187 encoded_short_ids=0000006d0000010000 option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=109 number_of_blocks=4294967295 encoded_short_ids=01789c6360c8656060646000000299006f option_gossip_queries
+ # Could truncate number_of_blocks.
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=109 number_of_blocks=4294967187 encoded_short_ids=01789c6360c8656060646000000299006f option_gossip_queries
+
+ # This should get both.
+ 1. recv: type=query_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 option_gossip_queries
+ 2. One of: option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 option_gossip_queries
+
+ # This should get appended timestamp fields with option_gossip_queries_ex
+ 1. recv: type=query_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 tlvs=010101 option_gossip_queries
+ 2. One of: option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 option_gossip_queries !option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 option_gossip_queries !option_gossip_queries_ex
+ # Timestamps may or may not be zlib encoded.
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 tlvs=0111005d50f933000000005d50f9355d50f934 option_gossip_queries option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 tlvs=011601789c8b0df869cc0004b1013f4d81d804002b46058f option_gossip_queries option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 tlvs=011601789c8b0df869cc0004b1013f4d81d804002b46058f option_gossip_queries option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 tlvs=011601789c8b0df869cc0004b1013f4d81d804002b46058f option_gossip_queries option_gossip_queries_ex option_gossip_queries option_gossip_queries_ex
+
+ # This should get appended checksum fields with option_gossip_queries_ex
+ 1. recv: type=query_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 tlvs=010102 option_gossip_queries
+ 2. One of: option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 option_gossip_queries !option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 option_gossip_queries !option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 tlvs=03101112fa3000000000f32ce9689bece840 option_gossip_queries option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 tlvs=03101112fa3000000000f32ce9689bece840 option_gossip_queries option_gossip_queries_ex
+
+ # This should append timestamps and checksums with option_gossip_queries_ex
+ 1. recv: type=query_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 tlvs=010103 option_gossip_queries
+ 2. One of: option_gossip_queries
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 option_gossip_queries !option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 option_gossip_queries !option_gossip_queries_ex
+ # Timestamps may or may not be zlib encoded.
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 tlvs=0111005d50f933000000005d50f9355d50f93403101112fa3000000000f32ce9689bece840 option_gossip_queries option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=00000067000001000000006d0000010000 tlvs=011601789c8b0df869cc0004b1013f4d81d804002b46058f03101112fa3000000000f32ce9689bece840 option_gossip_queries option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 tlvs=0111005d50f933000000005d50f9355d50f93403101112fa3000000000f32ce9689bece840 option_gossip_queries option_gossip_queries_ex
+ 1. expect-send: type=reply_channel_range chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f first_blocknum=103 number_of_blocks=7 encoded_short_ids=01789c6360486760606400825c300d00084e00d7 tlvs=011601789c8b0df869cc0004b1013f4d81d804002b46058f03101112fa3000000000f32ce9689bece840 option_gossip_queries option_gossip_queries_ex
diff --git a/tests/events/bolt7-25-query_short_channel_ids.events b/tests/events/bolt7-25-query_short_channel_ids.events
new file mode 100644
index 000000000..aa8c34669
--- /dev/null
+++ b/tests/events/bolt7-25-query_short_channel_ids.events
@@ -0,0 +1,124 @@
+# Tests for query_short_channel_ids.
+
+include setup.incl
+1. block: $BLOCK_102
+
+# Our three channels, and four nodes.
+2. block: $BLOCK_103
+3. block: $BLOCK_109
+4. block: $BLOCK_115
+
+5. connect: privkey=0000000000000000000000000000000000000000000000000000000000000003
+6. expect-send: type=init
+7. recv: type=init features= globalfeatures=
+# They can send this, we'll ignore it.
+8. maybe-send: type=gossip_timestamp_filter
+
+# Channel 103x1x0 (between 002 and 003), only one update, both node_announce
+9. recv: $CHAN_ANN_103x1x0
+10. recv: $CHAN_UPDATE_103x1x0_002
+11. recv: $NODE_ANN_002
+12. recv: $NODE_ANN_003
+
+# Channel 109x1x0 (between 004 and 005): both updates, only one node_announce
+13. recv: $CHAN_ANN_109x1x0
+# Node-announce first is legal
+14. recv: $NODE_ANN_004
+15. recv: $CHAN_UPDATE_109x1x0_004
+16. recv: $CHAN_UPDATE_109x1x0_005
+
+# Channel 115x1x0 (between 003 and 004): no updates.
+17. recv: $CHAN_ANN_115x1x0
+
+# New peer connects, with gossip_query option.
+18. connect: privkey=0000000000000000000000000000000000000000000000000000000000000005
+19. expect-send: type=init
+20. recv: type=init features=80 globalfeatures=
+21. maybe-send: type=gossip_timestamp_filter
+
+# Query for non-existent channels (104x1x0 105x1x0 106x1x0 107x1x0)
+22. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=000000680000010000000069000001000000006a000001000000006b0000010000
+23. expect-send: type=reply_short_channel_ids_end
+
+# zlib version
+24. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=01789c6360c86060606400824c289d05a5b3c134001de001ab
+25. expect-send: type=reply_short_channel_ids_end
+
+# Query for one channel (103x1x0)
+26. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=000000670000010000
+27. expect-send: $CHAN_ANN_103x1x0
+28. Any order:
+ 1. expect-send: $CHAN_UPDATE_103x1x0_002
+ 1. expect-send: $NODE_ANN_002
+ 1. expect-send: $NODE_ANN_003
+29. expect-send: type=reply_short_channel_ids_end
+
+# Query for two channels (103x1x0 109x1x0)
+30. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000
+31. One of: # the channel_announcements must lead, then it gets messy.
+ 1. expect-send: $CHAN_ANN_103x1x0
+ 2. Any order: # Not really *any* order, since ann must predate update.
+ 1. expect-send: $CHAN_UPDATE_103x1x0_002
+ 1. expect-send: $NODE_ANN_002
+ 1. expect-send: $NODE_ANN_003
+ 1. expect-send: $CHAN_ANN_109x1x0
+ 1. expect-send: $CHAN_UPDATE_109x1x0_004
+ 1. expect-send: $CHAN_UPDATE_109x1x0_005
+ 1. expect-send: $NODE_ANN_004
+ 1. expect-send: $CHAN_ANN_109x1x0
+ 2. Any order:
+ 1. expect-send: $CHAN_ANN_103x1x0
+ 1. expect-send: $CHAN_UPDATE_103x1x0_002
+ 1. expect-send: $NODE_ANN_002
+ 1. expect-send: $NODE_ANN_003
+ 1. expect-send: $CHAN_UPDATE_109x1x0_004
+ 1. expect-send: $CHAN_UPDATE_109x1x0_005
+ 1. expect-send: $NODE_ANN_004
+32. expect-send: type=reply_short_channel_ids_end
+
+# Query for specific things only, if supported.
+
+# Announce messages only.
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=0103000101 option_gossip_queries_ex
+ # zlib-encoded variant
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=010b01789c6364040000050003 option_gossip_queries_ex
+33. Any order: option_gossip_queries_ex
+ 1. expect-send: $CHAN_ANN_103x1x0 option_gossip_queries_ex
+ 1. expect-send: $CHAN_ANN_109x1x0 option_gossip_queries_ex
+34. expect-send: type=reply_short_channel_ids_end option_gossip_queries_ex
+
+# Node 1 channel_update messages only.
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=0103000202 option_gossip_queries_ex
+ # zlib-encoded variant
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=010b01789c6362020000080005 option_gossip_queries_ex
+
+35. Any order: option_gossip_queries_ex
+ 1. expect-send: $CHAN_UPDATE_103x1x0_002 option_gossip_queries_ex
+ 1. expect-send: $CHAN_UPDATE_109x1x0_005 option_gossip_queries_ex
+36. expect-send: type=reply_short_channel_ids_end option_gossip_queries_ex
+
+# Node 2 channel_update messages only.
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=0103000404 option_gossip_queries_ex
+ # zlib-encoded variant
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=010b01789c63610100000e0009 option_gossip_queries_ex
+
+37. expect-send: $CHAN_UPDATE_109x1x0_004 option_gossip_queries_ex
+38. expect-send: type=reply_short_channel_ids_end option_gossip_queries_ex
+
+# Node 1 announcements only.
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=0103000808 option_gossip_queries_ex
+ # zlib-encoded variant
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=010b01789ce3e00000001a0011 option_gossip_queries_ex
+
+39. expect-send: $NODE_ANN_002 option_gossip_queries_ex
+40. expect-send: type=reply_short_channel_ids_end option_gossip_queries_ex
+
+# Node 2 announcements only.
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=0103001010 option_gossip_queries_ex
+ # zlib-encoded variant
+ 1. recv: type=query_short_channel_ids chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f encoded_short_ids=00000067000001000000006d0000010000 tlvs=010b01789c1310000000320021 option_gossip_queries_ex
+
+41. Any order: option_gossip_queries_ex
+ 1. expect-send: $NODE_ANN_003 option_gossip_queries_ex
+ 1. expect-send: $NODE_ANN_004 option_gossip_queries_ex
+42. expect-send: type=reply_short_channel_ids_end option_gossip_queries_ex
diff --git a/tests/events/setup.incl b/tests/events/setup.incl
new file mode 100644
index 000000000..e3ac04770
--- /dev/null
+++ b/tests/events/setup.incl
@@ -0,0 +1,81 @@
+# Include this to create channels and set variables for gossip messages.
+
+# BLOCK_102 allows funding of channels with several UTXOs for easy testing.
+#
+# Here are the keys to spend funds, derived from BIP32 seed
+# `0000000000000000000000000000000000000000000000000000000000000001`:
+#
+# pubkey 0/0/1: 02d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b
+# privkey 0/0/1: 76edf0c303b9e692da9cb491abedef46ca5b81d32f102eb4648461b239cb0f99
+# WIF 0/0/1: cRZtHFwyrV3CS1Muc9k4sXQRDhqA1Usgi8r7NhdEXLgM5CUEZufg
+# P2WPKH 0/0/1: bcrt1qsdzqt93xsyewdjvagndw9523m27e52er5ca7hm
+# UTXO: 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/1 (0.01BTC)
+#
+# pubkey 0/0/2: 038f1573b4238a986470d250ce87c7a91257b6ba3baf2a0b14380c4e1e532c209d
+# privkey 0/0/2: bc2f48a76a6b8815940accaf01981d3b6347a68fbe844f81c50ecbadf27cd179
+# WIF 0/0/2: cTtWRYC39drNzaANPzDrgoYsMgs5LkfE5USKH9Kr9ySpEEdjYt3E
+# P2WPKH 0/0/2: bcrt1qlkt93775wmf33uacykc49v2j4tayn0yj25msjn
+# UTXO: 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/0 (0.02BTC)
+#
+# pubkey 0/0/3: 02ffef0c295cf7ca3a4ceb8208534e61edf44c606e7990287f389f1ea055a1231c
+# privkey 0/0/3: 16c5027616e940d1e72b4c172557b3b799a93c0582f924441174ea556aadd01c
+# WIF 0/0/3: cNLxnoJSQDRzXnGPr4ihhy2oQqRBTjdUAM23fHLHbZ2pBsNbqMwb
+# P2WPKH 0/0/3: bcrt1q2ng546gs0ylfxrvwx0fauzcvhuz655en4kwe2c
+# UTXO: 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/3 (0.03BTC)
+#
+# pubkey 0/0/4: 026957e53b46df017bd6460681d068e1d23a7b027de398272d0b15f59b78d060a9
+# privkey 0/0/4: 53ac43309b75d9b86bef32c5bbc99c500910b64f9ae089667c870c2cc69e17a4
+# WIF 0/0/4: cQPMJRjxse9i1jDeCo8H3khUMHYfXYomKbwF5zUqdPrFT6AmtTbd
+# P2WPKH 0/0/4: bcrt1qrdpwrlrmrnvn535l5eldt64lxm8r2nwkv0ruxq
+# UTXO: 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/4 (0.04BTC)
+#
+# pubkey 0/0/5: 03a9f795ff2e4c27091f40e8f8277301824d1c3dfa6b0204aa92347314e41b1033
+# privkey 0/0/5: 16be98a5d4156f6f3af99205e9bc1395397bca53db967e50427583c94271d27f
+# WIF 0/0/5: cNLuxyjvR6ga2q6fdmSKxAd1CPQDShKV9yoA7zFKT7GJwZXr9MmT
+# P2WPKH 0/0/5: bcrt1q622lwmdzxxterumd746eu3d3t40pq53p62zhlz
+# UTXO: 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/2 (49.89995320BTC)
+
+BLOCK_102=height=102 tx=020000000001017b8705087f9bddd2777021d2a1dfefc2f1c5afa833b5c4ab00ccc8a556d042830000000000feffffff0580841e0000000000160014fd9658fbd476d318f3b825b152b152aafa49bc9240420f000000000016001483440596268132e6c99d44dae2d151dabd9a2b2338496d2901000000160014d295f76da2319791f36df5759e45b15d5e105221c0c62d000000000016001454d14ae910793e930d8e33d3de0b0cbf05aa533300093d00000000001600141b42e1fc7b1cd93a469fa67ed5eabf36ce354dd6024730440220782128cb0319a8430a687c51411e34cfaa6641da9a8f881d8898128cb5c46897022056e82d011a95fd6bcb6d0d4f10332b0b0d1227b2c4ced59e540eb708a4b24e4701210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179865000000
+
+# Funding tx spending 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/1, feerate 253 to bitcoin privkeys 0000000000000000000000000000000000000000000000000000000000000010 and 0000000000000000000000000000000000000000000000000000000000000020 (txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c756031
+BLOCK_103=height=103 n=6 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160100000000ffffffff01c5410f0000000000220020c46bf3d1686d6dbb2d9244f8f67b90370c5aa2747045f1aeccb77d8187117382024730440220798d96d5a057b5b7797988a855217f41af05ece3ba8278366e2f69763c72e785022065d5dd7eeddc0766ddf65557c92b9c52c301f23f94d2cf681860d32153e6ae1e012102d6a3c2d0cf7904ab6af54d7c959435a452b24a63194e1c4e7c337d3ebbb3017b00000000
+
+# This channel claimed by nodeids with privkeys ...002 and ...003.
+CHAN_ANN_103x1x0=type=channel_announcement node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000002:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a) node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a) bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000010:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a) bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000020:4ad0946fa8c3996015dec325c4b0540299287427ec8d8313842ce6f8dc06791a) features= chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=103x1x0 node_id_1=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 bitcoin_key_1=03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a bitcoin_key_2=03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65
+
+CHAN_UPDATE_103x1x0_002=type=channel_update signature=SIG(0000000000000000000000000000000000000000000000000000000000000002:5133e0542731e0b4b70b4cb99f8fb7dab2e6658b5a5add8d9dfd1a8e2c549f95) chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=103x1x0 timestamp=1565587763 message_flags=0 channel_flags=0 cltv_expiry_delta=144 htlc_minimum_msat=0 fee_base_msat=1000 fee_proportional_millionths=10
+
+NODE_ANN_002=type=node_announcement signature=SIG(0000000000000000000000000000000000000000000000000000000000000002:511128a78a9a0f1cac973ceb37533497fde5586b54fad3c887d1037195a4ddbd) features= timestamp=1565587763 node_id=02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 rgb_color=02c604 alias=3032633630343766393434316564376436643330343534303665393563303763 addresses=01080808082607
+
+CHAN_UPDATE_103x1x0_003=type=channel_update signature=SIG(0000000000000000000000000000000000000000000000000000000000000003:d86dd6f31dc7956bae1e86407f38548fb2cda5f3f7441577694b1c5d455f153f) chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=103x1x0 timestamp=1565587763 message_flags=1 channel_flags=1 cltv_expiry_delta=48 htlc_minimum_msat=0 fee_base_msat=100 fee_proportional_millionths=11 htlc_maximum_msat=100000
+
+NODE_ANN_003=type=node_announcement signature=SIG(0000000000000000000000000000000000000000000000000000000000000003:15cf94034b8916d507d90d836f8b2a18b6c513b032714056099fb875db9cec3e) features= timestamp=1565587763 node_id=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 rgb_color=02f930 alias=3032663933303861303139323538633331303439333434663835663839643532 addresses=0151b6887026070220014c4e1cc141001e6f65fffec8a825260703c43068ceb641d7b25c3a26070441cf248da2034dfa9351a9e946d71ce86f561f50b67753fd8e385d44647bf62cdb91032607
+
+# A later node-announcement, a little different.
+NODE_ANN_003_LATER=type=node_announcement signature=SIG(0000000000000000000000000000000000000000000000000000000000000003:22de31ba66317a121263102b35cf05fa5c0e5a2100071740428693fa414e9dc5) features= timestamp=1565597764 node_id=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 rgb_color=02f930 alias=3032663933303861303139323538633331303439333434663835663839643532 addresses=0441cf248da2034dfa9351a9e946d71ce86f561f50b67753fd8e385d44647bf62cdb91032607
+
+# Funding tx spending 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/0, feerate 253 to bitcoin privkeys 0000000000000000000000000000000000000000000000000000000000000030 and 0000000000000000000000000000000000000000000000000000000000000040 (txid db029ee8cc511625887c192c5bb264249fe69b9b86eb627a52f9a313ba231ade)
+BLOCK_109=height=109 n=6 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160000000000ffffffff0105841e0000000000220020fa73be60259cea454ee79a963514f0b7622db62eadc88daafe377bfa2aa30fbb0247304402205735b9750a90be1ca09cdf91d6697bde3746a390698ca754d516b56c72880bae02203c1deef3645cc20e300db1a808ffc7c2f57be200761ee3cf1a479d1e1aef70bc0121038f1573b4238a986470d250ce87c7a91257b6ba3baf2a0b14380c4e1e532c209d00000000
+
+# This channel claimed by nodeids with privkeys ...004 and ...005.
+CHAN_ANN_109x1x0=type=channel_announcement node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000005:dd64b4844ef9728c2486f9bf71273070941f68e4047e8420d764b8e543a2841b) node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000004:dd64b4844ef9728c2486f9bf71273070941f68e4047e8420d764b8e543a2841b) bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000040:dd64b4844ef9728c2486f9bf71273070941f68e4047e8420d764b8e543a2841b) bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000030:dd64b4844ef9728c2486f9bf71273070941f68e4047e8420d764b8e543a2841b) features= chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=109x1x0 node_id_1=022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4 node_id_2=02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13 bitcoin_key_1=03bf23c1542d16eab70b1051eaf832823cfc4c6f1dcdbafd81e37918e6f874ef8b bitcoin_key_2=026eca335d9645307db441656ef4e65b4bfc579b27452bebc19bd870aa1118e5c3
+
+CHAN_UPDATE_109x1x0_004=type=channel_update signature=SIG(0000000000000000000000000000000000000000000000000000000000000004:d0ba981a8ae3f36494765920e7c2b15c823342baa3a594f5013e699d58d75c7d) chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=109x1x0 timestamp=1565587764 message_flags=0 channel_flags=1 cltv_expiry_delta=144 htlc_minimum_msat=0 fee_base_msat=1000 fee_proportional_millionths=10
+
+NODE_ANN_004=type=node_announcement signature=SIG(0000000000000000000000000000000000000000000000000000000000000004:80307748653b7608ad9932d800581169b4a091de0c5e9084b4d9ff3b96d5d91a) features= timestamp=1565587764 node_id=02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13 rgb_color=02e493 alias=3032653439336462663163313064383066333538316534393034393330623134 addresses=
+
+NODE_ANN_004_LATER=type=node_announcement signature=SIG(0000000000000000000000000000000000000000000000000000000000000004:bde4752f90c84e9372c0badf9f0b29a024ed51b623b5f16742f5bb2ca9142f28) features= timestamp=1565597765 node_id=02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13 rgb_color=02e493 alias=3032653439336462663163313064383066333538316534393034393330623134 addresses=
+
+CHAN_UPDATE_109x1x0_005=type=channel_update signature=SIG(0000000000000000000000000000000000000000000000000000000000000005:3f698cd8c502919b5bde2cf02ccd7cb5b3023d73dfd2b8d87da3bcc8fa689963) chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=109x1x0 timestamp=1565587765 message_flags=1 channel_flags=0 cltv_expiry_delta=48 htlc_minimum_msat=0 fee_base_msat=100 fee_proportional_millionths=11 htlc_maximum_msat=100000
+
+NODE_ANN_005=type=node_announcement signature=SIG(0000000000000000000000000000000000000000000000000000000000000005:cd127af408247cd7e9eac84e1f723e94a43946616be367c9546db1196b108cbe) features= timestamp=1565587765 node_id=022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4 rgb_color=022f8b alias=3032326638626465346431613037323039333535623461373235306135633531 addresses=022a03b0c0000300d000000000240020012607
+
+# Funding tx spending 16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b/3, feerate 253 to bitcoin privkeys 0000000000000000000000000000000000000000000000000000000000000050 and 0000000000000000000000000000000000000000000000000000000000000060 (txid 03330f41079aba9a595310c9c4d78676e5291ee6f1931dd7686f46ed16096186)
+BLOCK_115=height=115 n=6 tx=020000000001016b85f654d8186f4d5dd32a977b2cf8c4b01ff4634152acba16b654c1c85a83160300000000ffffffff0145c62d00000000002200208225164b456194e9721a5ff5ea4df731d3d663f48f3ba96961dc9d0617ea2bf20247304402200bf2e7a300f8d268c9480732748707b36e43f6225f5330eca2cfa00b21c7159a02205390a3469a14b3a48714bb44db6d8ee838ef5e29b3063a32cba22a3a2d4f3e00012102ffef0c295cf7ca3a4ceb8208534e61edf44c606e7990287f389f1ea055a1231c00000000
+
+# This channel claimed by nodeids with privkeys ...003 and ...004.
+CHAN_ANN_115x1x0=type=channel_announcement node_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000004:4a77eec60b0275f0bdcf1ac572bfd63ff7eaa61050ff54049168e9858279eec0) node_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000003:4a77eec60b0275f0bdcf1ac572bfd63ff7eaa61050ff54049168e9858279eec0) bitcoin_signature_1=SIG(0000000000000000000000000000000000000000000000000000000000000060:4a77eec60b0275f0bdcf1ac572bfd63ff7eaa61050ff54049168e9858279eec0) bitcoin_signature_2=SIG(0000000000000000000000000000000000000000000000000000000000000050:4a77eec60b0275f0bdcf1ac572bfd63ff7eaa61050ff54049168e9858279eec0) features= chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=115x1x0 node_id_1=02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13 node_id_2=02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 bitcoin_key_1=033f0e80e574456d8f8fa64e044b2eb72ea22eb53fe1efe3a443933aca7f8cb0e3 bitcoin_key_2=03e9623bbef1bf90ec0d7c744ed34659f010e6e638637161270ecd31e14f87f62e
+
+CHAN_UPDATE_115x1x0_003=type=channel_update signature=SIG(0000000000000000000000000000000000000000000000000000000000000003:d42e6feecf1cbb4428b026c7a3c76860531518d90ab9ea2aa94eee2fe1daec0a) chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=115x1x0 timestamp=1565597764 message_flags=0 channel_flags=1 cltv_expiry_delta=144 htlc_minimum_msat=0 fee_base_msat=1000 fee_proportional_millionths=10
+
+CHAN_UPDATE_115x1x0_004=type=channel_update signature=SIG(0000000000000000000000000000000000000000000000000000000000000004:03d22228832ced80b0f4985ecfea1da3d398cabb213159a66f8eccda312ea58c) chain_hash=06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f short_channel_id=115x1x0 timestamp=1565597765 message_flags=1 channel_flags=0 cltv_expiry_delta=48 htlc_minimum_msat=0 fee_base_msat=100 fee_proportional_millionths=11 htlc_maximum_msat=100000
diff --git a/tests/events/test-spec.md b/tests/events/test-spec.md
new file mode 100644
index 000000000..525e155f0
--- /dev/null
+++ b/tests/events/test-spec.md
@@ -0,0 +1,204 @@
+# Format for Event-based Test Specifications
+
+The programmatic test cases for the spec are a tree of events and
+expected responses which test various scenarios described in the
+specification. They serve only as guidelines: in some cases a
+compliant implementation might produce a different response than that
+given here, though that suggests further examination of the test case,
+the implementation, or both.
+
+## General Line Format
+
+FILE := SEQUENCE*
+
+SEQUENCE := SEQUENCE_LINE+
+SEQUENCE_LINE := SEQUENCE_STEP OPTION_SPEC* | META_LINE
+SEQUENCE_STEP := INDENT4* NUMBER `.` SPACE+ EVENT_OR_SERIES OPTION_SPEC*
+
+EVENT_OR_SERIES := EVENT | `One of:` | `Any order:`
+EVENT := INPUT_EVENT | OUTPUT_EVENT | `nothing`
+
+META_LINE := COMMENT | SPACE* | VARSET | INCLUDE
+
+COMMENT := `#` [SPACE|STRING]*
+VARSET := IDENTIFIER`=`[SPACE|STRING]* OPTION_SPEC*
+INCLUDE := `include` SPACE STRING OPTION_SPEC*
+
+Comment and blank lines are ignored.
+
+Variable lines set variables which can be expanded in any position with
+a `$` prefix. There's currently no scope to variables.
+
+Include lines pull in other files, which is helpful for complex tests.
+
+Other lines are indented by multiples of 4 spaces; a line not indented
+by a multiple of 4 is to be joined with the previous line (this allows
+nicer formatting for long lines).
+
+Each non-comment line indicates something to do to the implementation
+(input event) or some response it should give (output event).
+
+Indentation indicates alternative sequences, eg. this reflects two tests,
+STEP1->STEP2a->STEP3 and STEP1->STEP2b->STEP3:
+
+ 1. STEP1
+ 1. STEP2a
+ 1. STEP2b
+ 2. STEP3
+
+A step must either have NUMBER 1, in which case it follows directly
+from the parent, or NUMBER one greater than the previous step at the
+same level, in which case it follows the previous.
+
+There must be exactly one top-level `1.` step.
+
+The special marker `Any order:` indicates that the following sequences
+starting with distinct output events will occur, but might happen in
+any order. This is useful for gossip messages which can be ordered in
+multiple ways:
+
+ 1. STEP1
+ 2. Any order:
+ 1. STEP2a
+ 1. STEP2b
+ 1. STEP2c
+
+This means the test will accept STEP1->STEP2a->STEP2b->STEP2c,
+STEP1->STEP2a->STEP2c->STEP2b, STEP1->STEP2b->STEP2a->STEP2c,
+STEP1->STEP2b->STEP2c->STEP2a, STEP1->STEP2c->STEP2a->STEP2b,
+or STEP1->STEP2c->STEP2b->STEP2a.
+
+The special marker `One of:` indicates sequences starting with distinct
+output events, only one of which could occur. This is useful for optional
+outputs which are more constrained, eg:
+
+ 1. STEP1
+ 2. One of:
+ 1. STEP2a
+ 2. STEP2b
+ 1. STEP2c
+ 2. STEP3
+
+This means the test will accept STEP1->STEP2a->STEP3, STEP1->STEP2b->STEP3,
+or STEP1->STEP2c->STEP3.
+
+## Option Specifiers
+
+OPTION_SPEC := SPACE+ [`!`]OPTION_NAME
+OPTION_NAME := `opt`IDENTIFIER[`/`ODD_OR_EVEN]
+ODD_OR_EVEN := `odd` | `even`
+
+Some individual lines only apply if certain options are (not) supported.
+If the implementation does not support the option (or does support the
+option and it's preceeded by `!`), the line should be ignored. This
+can be used to set/omit certain fields according to certain options,
+or even whole steps.
+
+You can also filter by whether options are optional (`odd`) or
+compulsory (`even`).
+
+## Input Events
+
+INPUT_EVENT := CONNECT | RECV | BLOCK | DISCONNECT | FUNDCHANCMD | INVOICECMD
+
+CONNECT := `connect:` SPACE+ CONNECT_OPTS
+CONNECT_OPTS := `privkey=` HEX64
+
+RECV := `recv:` [CONNSPEC] SPACE+ `type=` TYPENAME RECV_FIELDSPEC*
+TYPENAME := IDENTIFIER|NUMBER
+RECV_FIELDSPEC := SPACE+ IDENTIFIER`=`FIELDVALUE
+FIELDVALUE := HEX | NUMBER
+
+BLOCK := `block:` `height=`NUMBER SPACE+ `n`=NUMBER SPACE+ [TX*]
+TX := `tx=`HEXSTRING
+
+DISCONNECT := `disconnect:` SPACE+ CONNSPEC
+
+FUNDCHANCMD := `fundchannel:` [CONNSPEC] SPACE+ `amount=`NUMBER SPACE+ `utxo=`HEX`/`NUMBER
+
+INVOICECMD := `invoice:` SPACE+ `amount=`NUMBER SPACE+ `preimage=`HEX64
+
+CONNSPEC := SPACE+ `conn=`HEX64
+
+Input events are:
+* `connect`: a connection established with another peer. These examples
+ assume a successful cryptographic handshake. We provide the private key.
+* `recv`: an incoming message. The `type` is one of the message types
+ defined in the spec or a raw number. The other fields, if any, define
+ the individual fields: each non-optional field will be specified. Integer
+ fields can be specified as decimal integers, all other fields are hexidecimal
+ (note: this is confusing, as bitcoin usually reversed txids, and we don't!)
+ Length fields are not specified, but derived from the length of the hexidecimal
+ field. The special (hex) field `extra` indicates additional data to be appended. The optional `conn` argument allows you to specify which `connect`
+ you're referring to. The default is the last one.
+* `block`: a generated block at a given height. Any `tx` specified
+ are to be placed in the (first) block. If `n` is more than 1, it's a short
+ cut for generating additional blocks.
+* `disconnect`: a connection closed by a peer.
+* `fundchannel`: tell the implementation to initiate the opening of a channel of the given `amount` of satoshis with the specific peer identified by `conn` (default, last `connect`). The funding comes from a single `utxo`, as specified by txid and output number.
+* `invoice`: tell the implementation to accept a payment of `amount` msatoshis, with payment_preimage `preimage`.
+
+## Output Events
+
+OUTPUT_EVENT := EXPECT_SEND | MAYBE_SEND | MUST_NOT_SEND | EXPECT_TX | EXPECT_ERROR
+
+EXPECT_SEND := `expect-send:` [CONNSPEC] SPACE+ `type=` TYPENAME SPACE+ SEND_FIELDSPEC*
+SEND_FIELDSPEC := IDENTIFIER`=`SPECVALUE
+SPECVALUE := FIELDVALUE | HEX`/`HEX | `absent` | `*`LENGTH_RANGE
+LENGTH_RANGE := `*`NUMBER | `*`NUMBER`-`NUMBER
+
+MAYBE_SEND := `maybe-send:` [CONNSPEC] SPACE+ `type=` TYPENAME SEND_FIELDSPEC*
+
+MUST_NOT_SEND := `must-not-send:` [CONNSPEC] SPACE+ `type=` TYPENAME SEND_FIELDSPEC*
+
+EXPECT_TX := `expect-tx:` SPACE+ `tx=`HEX
+
+EXPECT_ERROR := `expect-error:` [CONNSPEC]
+
+Output events are:
+* `expect-send`: a message the implementation is expected to send. Any field specified must match exactly for the test to pass; the value`/`mask notation is used to compare bits against a mask; the field should be zero-padded for comparison if necessary. `*` is used to specify a length (in bytes) or a length range. The special field value `absent` means the (presumably optional) field must not be present.
+* `maybe-send`: a message the implementation may send, at any point from now on (until the next `disconnect`)
+* `must-not-send`: a message the implementation must not send, at any point from now on (until the next `disconnect`). This implies waiting at the end of the test (for a gossip flush!) to make sure it doesn't send it.
+* `expect-tx`: a transaction the implementation is expected to broadcast. The transactions here assume deterministic signatures.
+* `expect-error`: the implementation is expected to detect an error. This is generally an `expect-send` of `type=error` but it's legal for it to simply close the connection. If there's no `expect-error` event, the implementation is expected *not* to have an error.
+
+
+## Test Node Setup
+
+The peer secret of the test node is assumed
+`0000000000000000000000000000000000000000000000000000000000000001`
+which makes its public key
+`0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798`.
+
+The `minimum_depth` setting of the test node is assumed to be 3.
+
+The following secrets are used for the first channel (if successive
+channels exist in tests, they are only used for gossip test and their
+exact configuration is not tested); it's assumed that RFC6979 (using
+HMAC-SHA256) is used to generate transaction signatures.
+
+ funding_privkey: 0000000000000000000000000000000000000000000000000000000000000010
+ revocation_basepoint_secret: 0000000000000000000000000000000000000000000000000000000000000011
+ payment_basepoint_secret: 0000000000000000000000000000000000000000000000000000000000000012
+ delayed_payment_basepoint_secret: 0000000000000000000000000000000000000000000000000000000000000013
+ htlc_basepoint_secret: 0000000000000000000000000000000000000000000000000000000000000014
+ per_commitment_secret_seed: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+
+
+## Blockchain Setup
+
+The initial blockchain is a bitcoind `regtest` chain, which has the
+following initial blocks:
+
+ Block 0 (genesis): 0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000
+ Block 1: 0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f7b8705087f9bddd2777021d2a1dfefc2f1c5afa833b5c4ab00ccc8a556d04283f5a1095dffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a01000000160014751e76e8199196d454941c45d1b3a323f1433bd60000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000
+
+The coinbase pays 50 BTC to the following key/address:
+
+ privkey: 0000000000000000000000000000000000000000000000000000000000000001
+ WIF: cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA
+ P2WPKH: bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080
+
+A further 100 blocks are generated to allow the 50 BTC output to be
+spent, so block height is 101. The file `setup.incl` contains more
+helpers for standard setups.
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644
index 000000000..19c680565
--- /dev/null
+++ b/tools/Makefile
@@ -0,0 +1,6 @@
+#! /usr/bin/make
+
+check:
+ @# E501 line too long (N > 79 characters)
+ @# W503: line break before binary operator
+ flake8 --ignore=E501,W503 test-events.py
diff --git a/tools/sig-verify.py b/tools/sig-verify.py
new file mode 100644
index 000000000..904a0b5a4
--- /dev/null
+++ b/tools/sig-verify.py
@@ -0,0 +1,46 @@
+#! /usr/bin/python3
+# This script verifies hashes from keys, using
+# ECDSA over the SECP256k1 curve
+
+# Released by Lisa Neigut under CC0:
+# https://creativecommons.org/publicdomain/zero/1.0/
+
+from secp256k1 import PrivateKey
+
+def verify_sig(secret, hash_digest, sig_bytes):
+ """
+ Verify that the provided signature was in fact
+ generated by the given private key for the
+ given hash_digest
+
+ inputs:
+ - privkey: a hex-string private key scalar
+ - hash_digest: a hex-string hash message
+ - sig: a 64-byte array of concatenated r + s values of the signature to verify
+
+ returns:
+ - True if valid signature,
+ - False otherwise
+ """
+ if len(sig_bytes) != 64:
+ raise ValueError("Expected a 64-byte array for sig, got {} ({})".format(len(sig_bytes), sig_bytes.hex()))
+
+ privkey = PrivateKey(bytes(bytearray.fromhex(secret)), raw=True)
+ sig = privkey.ecdsa_deserialize_compact(sig_bytes)
+ return privkey.pubkey.ecdsa_verify(bytes(bytearray.fromhex(hash_digest)), sig, raw=True)
+
+
+def generate_sig(secret, hash_digest):
+ """
+ Given a secret (privkey) scalar and hash_digets,
+ returns a valid signature (64-bytes compact)
+
+ inputs:
+ privkey: a hex-string private key scalar
+ hash_digest: a hex-string hash message to sign
+
+ returns: 64-byte compact ecdsa sig
+ """
+ privkey = PrivateKey(bytes(bytearray.fromhex(secret)), raw=True)
+ sig = privkey.ecdsa_sign(bytes(bytearray.fromhex(hash_digest)), raw=True)
+ return privkey.ecdsa_serialize_compact(sig)
diff --git a/tools/test-events-clightning.py b/tools/test-events-clightning.py
new file mode 100755
index 000000000..0a802ee1b
--- /dev/null
+++ b/tools/test-events-clightning.py
@@ -0,0 +1,409 @@
+#! /usr/bin/python3
+# This script exercises the c-lightning implementation
+
+# Released by Rusty Russell under CC0:
+# https://creativecommons.org/publicdomain/zero/1.0/
+
+import bitcoin
+import bitcoin.rpc
+import importlib
+import lightning
+import os
+import shutil
+import struct
+import subprocess
+import tempfile
+import time
+
+from concurrent import futures
+from ephemeral_port_reserve import reserve
+
+test = importlib.import_module('test-events')
+
+TIMEOUT = int(os.getenv("TIMEOUT", "30"))
+LIGHTNING_SRC = os.getenv("LIGHTNING_SRC", '../lightning/')
+
+
+def wait_for(success, timeout=TIMEOUT):
+ start_time = time.time()
+ interval = 0.25
+ while not success() and time.time() < start_time + timeout:
+ time.sleep(interval)
+ interval *= 2
+ if interval > 5:
+ interval = 5
+ return time.time() <= start_time + timeout
+
+
+# Stolen from lightning/tests/utils.py
+class SimpleBitcoinProxy:
+ """Wrapper for BitcoinProxy to reconnect.
+
+ Long wait times between calls to the Bitcoin RPC could result in
+ `bitcoind` closing the connection, so here we just create
+ throwaway connections. This is easier than to reach into the RPC
+ library to close, reopen and reauth upon failure.
+ """
+ def __init__(self, btc_conf_file, *args, **kwargs):
+ self.__btc_conf_file__ = btc_conf_file
+
+ def __getattr__(self, name):
+ if name.startswith('__') and name.endswith('__'):
+ # Python internal stuff
+ raise AttributeError
+
+ # Create a callable to do the actual call
+ proxy = bitcoin.rpc.RawProxy(btc_conf_file=self.__btc_conf_file__)
+
+ def f(*args):
+ return proxy._call(name, *args)
+
+ # Make debuggers show rather than >
+ f.__name__ = name
+ return f
+
+
+class Bitcoind(object):
+ """Starts regtest bitcoind on an ephemeral port, and returns the RPC proxy"""
+ def __init__(self, basedir):
+ self.bitcoin_dir = os.path.join(basedir, "bitcoind")
+ if not os.path.exists(self.bitcoin_dir):
+ os.makedirs(self.bitcoin_dir)
+ self.bitcoin_conf = os.path.join(self.bitcoin_dir, 'bitcoin.conf')
+ self.cmd_line = [
+ 'bitcoind',
+ '-datadir={}'.format(self.bitcoin_dir),
+ '-server',
+ '-regtest',
+ '-logtimestamps',
+ '-nolisten']
+ self.port = reserve()
+ print("Port is {}, dir is {}".format(self.port, self.bitcoin_dir))
+ # For after 0.16.1 (eg. 3f398d7a17f136cd4a67998406ca41a124ae2966), this
+ # needs its own [regtest] section.
+ with open(self.bitcoin_conf, 'w') as f:
+ f.write("regtest=1\n")
+ f.write("rpcuser=rpcuser\n")
+ f.write("rpcpassword=rpcpass\n")
+ f.write("[regtest]\n")
+ f.write("rpcport={}\n".format(self.port))
+ self.rpc = SimpleBitcoinProxy(btc_conf_file=self.bitcoin_conf)
+
+ def start(self):
+ self.proc = subprocess.Popen(self.cmd_line, stdout=subprocess.PIPE)
+
+ # Wait for it to startup.
+ while b'Done loading' not in self.proc.stdout.readline():
+ pass
+
+ # Block #1.
+ self.rpc.submitblock('0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f7b8705087f9bddd2777021d2a1dfefc2f1c5afa833b5c4ab00ccc8a556d04283f5a1095dffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a01000000160014751e76e8199196d454941c45d1b3a323f1433bd60000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000')
+ self.rpc.generatetoaddress(100, self.rpc.getnewaddress())
+
+
+ def stop(self):
+ self.proc.kill()
+
+ def restart(self):
+ # Only restart if we have to.
+ if self.rpc.getblockcount() != 102 or self.rpc.getrawmempool() == []:
+ self.stop()
+ shutil.rmtree(os.path.join(self.bitcoin_dir, 'regtest'))
+ self.start()
+
+
+class CLightningRunner(object):
+
+ def __init__(self, args):
+ self.connections = []
+ self.cleanup_callbacks = []
+ self.fundchannel_future = None
+ self.is_fundchannel_kill = False
+
+ directory = tempfile.mkdtemp(prefix='test-events-')
+ self.bitcoind = Bitcoind(directory)
+ self.bitcoind.start()
+ self.executor = futures.ThreadPoolExecutor(max_workers=20)
+
+ self.lightning_dir = os.path.join(directory, "lightningd")
+ if not os.path.exists(self.lightning_dir):
+ os.makedirs(self.lightning_dir)
+ self.lightning_port = reserve()
+
+ self.startup_flags = []
+ for flag in args.startup_flags:
+ self.startup_flags.append("--{}".format(flag))
+
+ def start(self):
+ self.proc = subprocess.Popen(['{}/lightningd/lightningd'.format(LIGHTNING_SRC),
+ '--lightning-dir={}'.format(self.lightning_dir),
+ '--funding-confirms=3',
+ '--dev-force-tmp-channel-id=0000000000000000000000000000000000000000000000000000000000000000',
+ '--dev-force-privkey=0000000000000000000000000000000000000000000000000000000000000001',
+ '--dev-force-bip32-seed=0000000000000000000000000000000000000000000000000000000000000001',
+ '--dev-force-channel-secrets=0000000000000000000000000000000000000000000000000000000000000010/0000000000000000000000000000000000000000000000000000000000000011/0000000000000000000000000000000000000000000000000000000000000012/0000000000000000000000000000000000000000000000000000000000000013/0000000000000000000000000000000000000000000000000000000000000014/FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
+ '--dev-bitcoind-poll=1',
+ '--dev-fast-gossip',
+ '--dev-gossip-time=1565587763',
+ '--bind-addr=127.0.0.1:{}'.format(self.lightning_port),
+ '--network=regtest',
+ '--bitcoin-rpcuser=rpcuser',
+ '--bitcoin-rpcpassword=rpcpass',
+ '--bitcoin-rpcport={}'.format(self.bitcoind.port),
+ '--log-level=debug',
+ '--log-file=log']
+ + self.startup_flags)
+ self.rpc = lightning.LightningRpc(os.path.join(self.lightning_dir, "regtest", "lightning-rpc"))
+
+ def node_ready(rpc):
+ try:
+ rpc.getinfo()
+ return True
+ except Exception:
+ return False
+
+ if not wait_for(lambda: node_ready(self.rpc)):
+ raise subprocess.TimeoutExpired(self.proc,
+ "Could not contact lightningd")
+
+ # Make sure that we see any funds that come to our wallet
+ for i in range(5):
+ self.rpc.newaddr()
+
+ def kill_fundchannel(self):
+ fut = self.fundchannel_future
+ self.fundchannel_future = None
+ self.is_fundchannel_kill = True
+ if fut:
+ try:
+ fut.result(0)
+ except:
+ pass
+
+ def shutdown(self):
+ for cb in self.cleanup_callbacks:
+ cb()
+
+ def stop(self):
+ for cb in self.cleanup_callbacks:
+ cb()
+ self.rpc.stop()
+ self.bitcoind.stop()
+ for c in self.connections:
+ c.proc.kill()
+
+ def __enter__(self):
+ self.start()
+ return self
+
+ def __exit__(self, type, value, tb):
+ self.stop()
+
+ def restart(self):
+ for cb in self.cleanup_callbacks:
+ cb()
+ self.rpc.stop()
+ self.bitcoind.restart()
+ for c in self.connections:
+ c.proc.kill()
+
+ # Make a clean start
+ os.remove(os.path.join(self.lightning_dir, "regtest", "gossip_store"))
+ os.remove(os.path.join(self.lightning_dir, "regtest", "lightningd.sqlite3"))
+ os.remove(os.path.join(self.lightning_dir, "regtest", "log"))
+ self.start()
+
+ def connect(self, conn, line):
+ # FIXME: This serves to settle the gossip, eg tests/events/bolt7-20-query_channel_range.events fails without this.
+ time.sleep(1)
+ # FIXME: Open-code the lightning enc protocol in Python!
+ conn.proc = subprocess.Popen(['{}/devtools/gossipwith'.format(LIGHTNING_SRC),
+ '--privkey={}'.format(conn.connkey),
+ '--stdin',
+ '--no-init',
+ '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798@localhost:{}'.format(self.lightning_port)],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ bufsize=0)
+
+ def getblockheight(self):
+ return self.bitcoind.rpc.getblockcount()
+
+ def trim_blocks(self, newheight):
+ h = self.bitcoind.rpc.getblockhash(newheight + 1)
+ self.bitcoind.rpc.invalidateblock(h)
+
+ def add_blocks(self, txs, n, line):
+ for tx in txs:
+ self.bitcoind.rpc.sendrawtransaction(tx)
+ self.bitcoind.rpc.generatetoaddress(n, self.bitcoind.rpc.getnewaddress())
+
+ if not wait_for(lambda: self.rpc.getinfo()['blockheight'] == self.getblockheight()):
+ raise test.ValidationError(line,
+ "Node did not sync to blockheight:"
+ " {} vs {}"
+ .format(self.rpc.getinfo()['blockheight'],
+ self.getblockheight()))
+
+ def disconnect(self, conn, line):
+ # FIXME: Inject a bad enc packet, so it hangs up on us *after*
+ # processing
+ time.sleep(1)
+ conn.proc.terminate()
+ conn.proc.wait(30)
+
+ def recv(self, conn, outbuf, line):
+ try:
+ rawl = struct.pack('>H', len(outbuf))
+ conn.proc.stdin.write(rawl)
+
+ while len(outbuf) != 0:
+ written = conn.proc.stdin.write(outbuf)
+ outbuf = outbuf[written:]
+ except BrokenPipeError:
+ # This happens when they've sent an error and closed; try
+ # reading it to figure out what went wrong.
+ fut = self.executor.submit(self._readmsg, conn)
+ try:
+ msg = fut.result(1)
+ except futures.TimeoutError:
+ msg = None
+ if msg:
+ raise test.ValidationError(line, "Connection closed after sending {}".format(msg.hex()))
+ else:
+ raise test.ValidationError(line, "Connection closed")
+
+
+ def fundchannel(self, conn, amount, txid, outnum, feerate, line):
+ """
+ amount - amount to fund the channel with
+ txid - txid of the utxo to use
+ outnum - outnum of the utxo to use
+ feerate - feerate to use when building the tx
+ line - line where this event was invoked, for error logging
+ """
+ # First, check that another fundchannel isn't already running
+ if self.fundchannel_future:
+ if not self.fundchannel_future.done():
+ raise test.InternalError(line,
+ "Called fundchannel while another fundchannel is still in process")
+ self.fundchannel_future = None
+
+ def _fundchannel(self, amount, txid, outnum, feerate, line):
+ # node_id, amount, feerate=, announce=, close_to=
+ if not wait_for(lambda: len(self.rpc.listpeers()['peers']) > 0):
+ raise test.InternalError(line, "No peers found to fund channel with")
+
+ peer_id = self.rpc.listpeers()['peers'][0]['id']
+ result = self.rpc.fundchannel_start(peer_id, amount, feerate=feerate)
+
+ # Build a transaction
+ funding_addr = result['funding_address']
+ tx = self.rpc.txprepare([{funding_addr:amount}], feerate=feerate, utxos=["{}:{}".format(txid, outnum)])
+
+ # Get the vout index of the funding output
+ decode = self.bitcoind.rpc.decoderawtransaction(tx['unsigned_tx'])
+ txout = -1
+ for vout in decode['vout']:
+ if vout['scriptPubKey']['addresses'][0] == funding_addr:
+ txout = vout['n']
+ break
+
+ if txout < 0:
+ raise test.InternalError(line,
+ "Unable to find txout for {} (tx:{})".format(funding_addr, decode))
+
+ self.rpc.fundchannel_complete(peer_id, tx['txid'], txout)
+ self.rpc.txsend(tx['txid'])
+ return True
+
+ def _done(fut):
+ exception = fut.exception(0)
+ if exception and not self.is_fundchannel_kill:
+ raise(exception)
+ self.fundchannel_future = None
+ self.is_fundchannel_kill = False
+ self.cleanup_callbacks.remove(self.kill_fundchannel)
+
+ fut = self.executor.submit(_fundchannel, self, amount,
+ txid, outnum, feerate, line)
+ fut.add_done_callback(_done)
+ self.fundchannel_future = fut
+ self.cleanup_callbacks.append(self.kill_fundchannel)
+
+
+ def invoice(self, amount, preimage, line):
+ self.rpc.invoice(msatoshi=amount,
+ label=str(line),
+ description='invoice from {}'.format(line),
+ preimage=preimage)
+
+ def _readmsg(self, conn):
+ rawl = conn.proc.stdout.read(2)
+ length = struct.unpack('>H', rawl)[0]
+ msg = bytes()
+ while len(msg) < length:
+ msg += conn.proc.stdout.read(length - len(msg))
+ return msg
+
+ def expect_send(self, conn, line, timeout=TIMEOUT):
+ fut = self.executor.submit(self._readmsg, conn)
+ try:
+ return fut.result(timeout)
+ except futures.TimeoutError:
+ raise test.ValidationError(line, "Timed out")
+
+ def wait_for_finalmsg(self, conn):
+ # We told it to flush gossip every 1000msec, so give 2 seconds here.
+ while True:
+ fut = self.executor.submit(self._readmsg, conn)
+ try:
+ return fut.result(2)
+ except futures.TimeoutError:
+ return None
+
+ def expect_tx(self, tx, line):
+ def tx_in_mempool(tx):
+ for txid in self.bitcoind.rpc.getrawmempool():
+ if self.bitcoind.rpc.getrawtransaction(txid) == tx:
+ return True
+ return False
+
+ # This tx should appear in the mempool.
+ if not wait_for(lambda: tx_in_mempool(tx)):
+ raise test.ValidationError(line, "Did not broadcast the transaction")
+
+ def expect_error(self, conn, line):
+ while True:
+ msg = self.expect_send(conn, line)
+
+ # If we got an error, mark it
+ if struct.unpack('>H', msg[0:2]) == (17,):
+ return
+
+ def final_error(self):
+ # Just make sure it doesn't send an ERROR, but only give it 1 second.
+ # FIXME: We should just use poll to see if any output pending!
+ for c in self.connections:
+ try:
+ msg = self.expect_send(c, None, 1)
+ # If we got an error, mark it
+ if struct.unpack('>H', msg[0:2]) == (17,):
+ return msg.hex()
+ except test.ValidationError:
+ pass
+ return None
+
+
+if __name__ == "__main__":
+ parser = test.setup_cmdline_options()
+ args = parser.parse_args()
+ # Here are the options we support.
+ args.option += subprocess.run(['{}/lightningd/lightningd'.format(LIGHTNING_SRC),
+ '--list-features-only'],
+ stdout=subprocess.PIPE, check=True).stdout.decode('utf-8').splitlines()
+
+ # We use a context here, so we can always kill processes at exit
+ with CLightningRunner(args) as runner:
+ test.main(args, runner)
diff --git a/tools/test-events.py b/tools/test-events.py
new file mode 100755
index 000000000..c3e8dcb37
--- /dev/null
+++ b/tools/test-events.py
@@ -0,0 +1,1777 @@
+#! /usr/bin/python3
+# This script exercises a lightning implementation using a JSON test case.
+
+# Released by Rusty Russell under CC0:
+# https://creativecommons.org/publicdomain/zero/1.0/
+
+import argparse
+from copy import copy
+import importlib
+import fileinput
+import networkx as nx
+from os import path
+import re
+import string
+import struct
+import sys
+import matplotlib.pyplot as plt
+
+# Helper for validating / generating signatures
+Sigs = importlib.import_module('sig-verify')
+
+# Populated by read_csv
+messages = []
+
+# Populated by read_csv
+tlvstreams = set()
+
+# From 01-messaging.md#fundamental-types:
+name2size = {'byte': 1,
+ 'u16': 2,
+ 'u32': 4,
+ 'u64': 8,
+ 'short_channel_id': 8,
+ 'chain_hash': 32,
+ 'channel_id': 32,
+ 'sha256': 32,
+ 'preimage': 32,
+ 'secret': 32,
+ 'point': 33,
+ 'pubkey': 33,
+ 'signature': 64}
+
+name2structfmt = {'byte': 'B',
+ 'u16': '>H',
+ 'u32': '>I',
+ 'u64': '>Q',
+ 'short_channel_id': '>Q'}
+
+
+def setup_cmdline_options():
+ """Create an argparse.ArgumentParser for standard cmdline options"""
+ class OptionAction(argparse._AppendAction):
+ def __call__(self, parser, namespace, values, option_string=None):
+ if not values.endswith('/even') and not values.endswith('/odd'):
+ raise argparse.ArgumentTypeError("{} must end in /odd or /even"
+ .format(option_string))
+ super().__call__(parser, namespace, values, option_string)
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('csv_file', help='CSV file describing packet formats)')
+ parser.add_argument('input', nargs='*', default=[None],
+ help='Files to read in (or stdin)')
+ parser.add_argument('-v', '--verbose', help='Show working',
+ action="store_true")
+ parser.add_argument('--draw-events', help='Output ".png"',
+ action="store_true")
+ parser.add_argument('--exhaustive', help='Try all possible paths',
+ action="store_true")
+ parser.add_argument('--via', type=str,
+ help='Test shortest path via this specific [file:]line')
+ parser.add_argument('--flatten-failpath', help='Output a valid input file for the failing path',
+ action="store_true")
+ parser.add_argument('-o', '--option', action=OptionAction, default=[],
+ help='Indicate supported option')
+ parser.add_argument('-f', '--startup-flags', help="Pass these flags through to daemon "
+ "under test.\n'--' will be prepended.",
+ type=str, action="append", default=[])
+
+ return parser
+
+
+class Line(object):
+ """Line of input from the testfile (could be multiple if they continue)"""
+ def __init__(self, filename, linestart, lineend, indentlevel, line):
+ self.filename = filename
+ self.linestart = linestart
+ self.lineend = lineend
+ self.indentlevel = indentlevel
+ self.line = line[:]
+
+ def __copy__(self):
+ return Line(self.filename, self.linestart, self.lineend,
+ self.indentlevel, self.line)
+
+ def __iadd__(self, other):
+ """+= two Lines: assumes they are adjacent."""
+ if type(other) != Line:
+ return NotImplemented
+ # Tokens across filesystem boundaries (ie. include) don't add.
+ if other.filename == self.filename:
+ self.linestart = min(self.linestart, other.linestart)
+ self.lineend = max(self.lineend, other.lineend)
+ self.line += '\n' + other.line
+ return self
+
+ def __str__(self):
+ # Human-readable line numbers are 1-based
+ if self.linestart == self.lineend:
+ return "{}:{}".format(self.filename, self.linestart + 1)
+ else:
+ return "{}:{}-{}".format(self.filename, self.linestart + 1,
+ self.lineend + 1)
+
+ def __repr__(self):
+ return "Line " + self.__str__()
+
+ def flatten(self, index, stopline, prefix):
+ print("{}# {}".format(prefix, self))
+ parts = self.line.partition('.')
+ print("{}{}.{}".format(prefix, index, parts[2]))
+ return self == stopline, index + 1
+
+
+def pack(typename, v):
+ """Pack this value as this type"""
+ if typename in name2structfmt:
+ return struct.pack(name2structfmt[typename], v)
+
+ # FIXME: This is our non-TLV code
+ if typename in tlvstreams:
+ return v
+
+ if typename in Subtype.objs:
+ return Subtype.objs[typename].pack(v)
+
+ # Is 'SIG()' tuple?
+ if typename == 'signature' and isinstance(v, tuple):
+ return Sigs.generate_sig(v[0], v[1])
+
+ # Pack directly as bytes
+ assert len(v) == name2size[typename]
+ return bytes(v)
+
+
+def unpack_from(typename, bytestream, offset):
+ """Unpack from bytestream as this type. Returns len, value, or None, None"""
+ if typename in name2structfmt:
+ size = struct.calcsize(name2structfmt[typename])
+ if size + offset > len(bytestream):
+ return None, None
+ return (size, struct.unpack_from(name2structfmt[typename],
+ bytestream, offset)[0])
+
+ # FIXME: This is our non-TLV code
+ if typename in tlvstreams:
+ return len(bytestream) - offset, bytestream[offset:]
+
+ if typename in Subtype.objs:
+ subtype = Subtype.objs[typename]
+ return subtype.unpack(bytestream, offset)
+
+ # Unpack directly as bytes
+ size = name2size[typename]
+ if size + offset > len(bytestream):
+ return None, None
+ return size, bytestream[offset:offset + size]
+
+
+class ValidationError(Exception):
+ def __init__(self, line, message):
+ self.line = line
+ # Call the base class constructor with the parameters it needs
+ super().__init__(str(line) + ": Validation failed: " + message)
+
+
+class LineError(Exception):
+ def __init__(self, line, message):
+ # Call the base class constructor with the parameters it needs
+ super().__init__(str(line) + ": Parsing failed:" + message)
+
+
+class InternalError(Exception):
+ def __init__(self, line, message):
+ # Call the base class constructor with the parameters it needs
+ super().__init__(str(line) + ": Error encountered:" + message)
+
+
+# #### Dummy runner which you should replace with real one. ####
+class DummyRunner(object):
+ def __init__(self, args):
+ self.verbose = args.verbose
+ pass
+
+ def restart(self):
+ if self.verbose:
+ print("[RESTART]")
+ self.blockheight = 102
+
+ def shutdown(self):
+ print("[SHUTDOWN]")
+
+ def connect(self, id, line):
+ if self.verbose:
+ print("[CONNECT {}]".format(line))
+
+ def getblockheight(self):
+ return self.blockheight
+
+ def trim_blocks(self, newheight):
+ if self.verbose:
+ print("[TRIMBLOCK TO HEIGHT {}]".format(newheight))
+ self.blockheight = newheight
+
+ def add_blocks(self, txs, n, line):
+ if self.verbose:
+ print("[ADDBLOCKS {} WITH {} TXS]".format(n, len(txs)))
+ self.blockheight += n
+
+ def disconnect(self, conn, line):
+ if self.verbose:
+ print("[DISCONNECT {}]".format(line))
+
+ def wait_for_finalmsg(self, conn):
+ if self.verbose:
+ print("[WAIT-FOR-FINAL]")
+ return None
+
+ def recv(self, conn, outbuf, line):
+ if self.verbose:
+ print("[RECV {} {}]".format(line, outbuf.hex()))
+
+ def fundchannel(self, conn, amount, txid, outnum, feerate, line):
+ if self.verbose:
+ print("[FUNDCHANNEL TO {} for {} with UTXO {}/{} {} {}]"
+ .format(conn, amount, txid, outnum, feerate, line))
+
+ def invoice(self, amount, preimage, line):
+ if self.verbose:
+ print("[INVOICE for {} with PREIMAGE {} {}]"
+ .format(amount, preimage, line))
+
+ def expect_send(self, conn, line):
+ if self.verbose:
+ print("[EXPECT-SEND {}]".format(line))
+ # return bytes.fromhex(input("{}? ".format(line)))
+
+ def expect_tx(self, tx, line):
+ if self.verbose:
+ print("[EXPECT-TX {} {}]".format(tx.hex(), line))
+
+ def expect_error(self, conn, line):
+ if self.verbose:
+ print("[EXPECT-ERROR {}]", line)
+
+ def final_error(self):
+ if self.verbose:
+ print("[EXPECT NO ERROR]")
+ return None
+
+# #### End dummy runner which you should replace with real one. ####
+
+
+class Field(object):
+ def __init__(self, message, name, typename, count, options):
+ self.message = message
+ self.name = name
+ self.typename = typename
+ self.options = options
+ self.islenvar = False
+ # This contains all the integer types: otherwise it's a hexstring,
+ self.isinteger = (typename in name2structfmt and
+ not (typename == 'byte' and count))
+
+ # This is set for static-sized array.
+ self.arraylen = None
+ # This is set for variable-sized array.
+ self.arrayvar = None
+
+ if count:
+ # If array is a variable, must be prior field.
+ try:
+ self.arraylen = int(count)
+ except ValueError:
+ self.arrayvar = message.findField(count)
+ self.arrayvar.islenvar = True
+
+ def is_optional(self):
+ """TLVs are always considered optional"""
+ return self.options != [] or self.typename in tlvstreams
+
+ @staticmethod
+ def field_from_str(line, typename, isinteger, s):
+ if typename == "short_channel_id":
+ parts = s.split('x')
+ if len(parts) != 3:
+ raise LineError(line, "short_channel_id should be NxNxN")
+ try:
+ return ((int(parts[0]) << 40)
+ | (int(parts[1]) << 16) | (int(parts[2])))
+ except ValueError:
+ raise LineError(line, "short_channel_id should be xx")
+ # Int variants
+ if isinteger:
+ try:
+ v = int(s)
+ except ValueError:
+ raise LineError(line, "{} should be integer".format(typename))
+
+ if v >= (1 << (name2size[typename] * 8)):
+ raise LineError(line, "{} must be < {} bytes"
+ .format(typename, name2size[typename]))
+ return v
+
+ # Special handling for 'SIG(privkey:hash)'
+ if s.startswith('SIG('):
+ if not s.endswith(')'):
+ raise LineError(line, "SIG() improperly formatted. {}".format(s))
+
+ privkey, hash_digest = s[4:-1].split(':')
+ return (privkey, hash_digest)
+
+ # Everything else is a hex string.
+ try:
+ v = bytes.fromhex(s)
+ except ValueError:
+ raise LineError(line, "Non-hex value for {}: '{}'"
+ .format(typename, s))
+
+ # FIXME: This is our non-TLV code
+ if typename in tlvstreams:
+ return v
+
+ if len(v) != name2size[typename]:
+ raise LineError(line, "{} must be {} bytes long not {}"
+ .format(typename, name2size[typename], len(v)))
+ return v
+
+ def field_value(self, line, value):
+ """Decodes a value for this field: returns (fieldvalue, arrsize)
+ or (fieldvalue, None) if not an array"""
+ # If it's an array, expect a JSON array unless it's a byte array.
+ if self.arraylen or self.arrayvar:
+ if self.typename == 'byte':
+ try:
+ v = bytes.fromhex(value)
+ except ValueError:
+ if value.startswith('*'):
+ v = value
+ return v, v
+ raise LineError(line,
+ "Non-hex value for {} byte array: '{}'"
+ .format(self.name, value))
+ # Known length? Check
+ if self.arraylen and len(v) != self.arraylen:
+ raise LineError(line,
+ "{} byte array should be length {} not {}"
+ .format(self.name, self.arraylen, len(v)))
+ return v, len(v)
+ elif self.typename in tlvstreams:
+ v = bytes.fromhex(value)
+ return v, len(v)
+ elif self.typename in Subtype.objs: # Subtypes
+ subtype = Subtype.objs[self.typename]
+ return subtype.parse(line, value)
+ else:
+ arr = []
+ # Empty string doesn't quite do what we want with split.
+ if value == '':
+ values = []
+ else:
+ values = value.split(',')
+ for v in values:
+ arr += [self.field_from_str(line, self.typename,
+ self.isinteger, v)]
+ # Known length? Check
+ if self.arraylen and len(arr) != self.arraylen:
+ raise LineError(line,
+ "{} array should be length {} not {}"
+ .format(self.name, self.arraylen, len(v)))
+ return arr, len(arr)
+ else:
+ return (self.field_from_str(line,
+ self.typename, self.isinteger, value),
+ None)
+
+ def __repr__(self):
+ s = "{}:{}".format(self.name, self.typename)
+ if self.arraylen:
+ s += "[{}]".format(self.arraylen)
+ elif self.arrayvar:
+ s += "[{}]".format(self.arrayvar.name)
+ return s
+
+
+class Message(object):
+ # * 0x8000 (BADONION): unparsable onion encrypted by sending peer
+ # * 0x4000 (PERM): permanent failure (otherwise transient)
+ # * 0x2000 (NODE): node failure (otherwise channel)
+ # * 0x1000 (UPDATE): new channel update enclosed
+ onion_types = {'BADONION': 0x8000,
+ 'PERM': 0x4000,
+ 'NODE': 0x2000,
+ 'UPDATE': 0x1000}
+
+ def __init__(self, name, value):
+ self.name = name
+ self.value = self.parse_value(value)
+ self.fields = []
+
+ def parse_value(self, value):
+ result = 0
+ for token in value.split('|'):
+ if token in self.onion_types.keys():
+ result |= self.onion_types[token]
+ else:
+ result |= int(token)
+
+ return result
+
+ def findField(self, fieldname):
+ for f in self.fields:
+ if f.name == fieldname:
+ return f
+ return None
+
+ def addField(self, field):
+ self.fields.append(field)
+
+ def __repr__(self):
+ return "{}:{}:{}".format(self.name, self.value, self.fields)
+
+
+def find_message(messages, name):
+ for m in messages:
+ if m.name == name:
+ return m
+
+ return None
+
+
+class Subtype(Message):
+ objs = {}
+
+ def __init__(self, name):
+ Message.__init__(self, name, '0')
+
+ def parse(self, line, s):
+ """ Given an input line for a subtype, parse it into a dict
+ of fields for that subtype """
+ if len(s) == 0:
+ return b'', 0
+
+ if s[0] == '[':
+ i = 1
+ arr = []
+ while i < len(s) and s[i] != ']':
+ end, obj = self._parse_obj(line, s[i:])
+ arr.append(obj)
+ i += end
+ if i < len(s) and s[i] == ',':
+ i += 1
+ return arr, len(arr)
+ else:
+ _, obj = self.parse_obj(line, s)
+ return obj, None
+
+ def _parse_obj(self, line, s):
+ if s[0] != '{':
+ raise LineError(line, "Subtype formatted incorrectly. got {} when expecting an open bracket ({},{})"
+ .format(s[0], self.name, s))
+
+ end, fields = self.find_fields(s)
+ if end < 0:
+ raise LineError(line, "Subtype formatted incorrectly. No end bracket found ({}, {})"
+ .format(self.name, s))
+
+ val = {}
+ for f_name, f_val in fields:
+ sub_field = self.findField(f_name)
+
+ if not sub_field:
+ raise LineError(line, "{} subtype error. Unable to find field {}"
+ .format(self.name, f_name))
+
+ val[f_name], vararrlen = sub_field.field_value(line, f_val)
+ if vararrlen is not None:
+ val[sub_field.arrayvar.name] = vararrlen
+
+ return end + 1, val
+
+ def find_fields(self, s):
+ """ Handle nested arrays + objects. Returns set of fields for
+ the upper most object and the 'end' count of where this object ends.
+ {abc=[{},{}],bcd=[{},{}],...],abc=[]},{abc=[{},{}]}... """
+ arr_count = 0
+ bracket_count = 0
+ end = len(s)
+ field_set = []
+ i = 0
+ field = ''
+ tok = None
+ while i < end:
+ if s[i] == '[':
+ arr_count += 1
+ field += s[i]
+ elif s[i] == ']':
+ arr_count -= 1
+ field += s[i]
+ elif s[i] == '{':
+ bracket_count += 1
+ if i != 0:
+ field += s[i]
+ elif s[i] == '}':
+ bracket_count -= 1
+ if bracket_count == 0:
+ field_set.append((tok, field))
+ return i, field_set
+ else:
+ field += s[i]
+ elif s[i] == ',':
+ if bracket_count == 1 and arr_count == 0:
+ field_set.append((tok, field))
+ field = ''
+ tok = None
+ else:
+ field += s[i]
+ elif s[i] == '=':
+ if not tok:
+ tok = field
+ field = ''
+ else:
+ field += s[i]
+ else:
+ field += s[i]
+
+ i += 1
+ return -1, None
+
+ def pack(self, values):
+ """ We need to return bytes for each field """
+ bites = bytes([])
+ for f in self.fields:
+ if f.name in values:
+ val = values[f.name]
+ if f.arrayvar or f.arraylen:
+ for a in val:
+ bites += pack(f.typename, a)
+ else:
+ bites += pack(f.typename, val)
+ return bites
+
+ def unpack(self, bytestream, offset):
+ """ For every field, unpack it"""
+ result = {}
+ lenfields = {}
+ offset_start = offset
+ for f in self.fields:
+ offset, v = unpack_field(f, lenfields, bytestream, offset)
+ result[f.name] = v
+
+ return offset - offset_start, result
+
+ def compare(self, msgname, vals, expected):
+ if not bool(expected):
+ if bool(vals):
+ return ("Nothing expected for {}.{} but returned {}"
+ .format(msgname, self.name, vals))
+ return None
+ for name, exp_val in expected.items():
+ if name not in vals:
+ return ("Expected field {}.{}.{} "
+ "not present in values"
+ .format(msgname, self.name, name))
+
+ f = self.findField(name)
+ if not f:
+ return ("Field {} is not known for subtype {}({})"
+ .format(name, self.name, msgname))
+
+ val = vals[name]
+ err = compare_results(msgname + "." + self.name, f, val, exp_val)
+ if err is not None:
+ return err
+
+ return None
+
+
+def unpack_field(field, lenfields, bytestream, offset):
+ # If it's an array, we need the whole thing.
+ if field.arrayvar or field.arraylen:
+ if field.arrayvar:
+ num = lenfields[field.arrayvar.name]
+ else:
+ num = field.arraylen
+ # Array of bytes is special: treat raw.
+ if field.typename == 'byte':
+ v = bytestream[offset:offset + num]
+ offset += num
+ else:
+ v = []
+ for i in range(0, num):
+ size, var = unpack_from(field.typename, bytestream, offset)
+ if size is None:
+ raise ValueError('Response too short to extract {}[{}]: {}'
+ .format(field.name, i, bytestream.hex()))
+ offset += size
+ v += [var]
+ else:
+ size, v = unpack_from(field.typename, bytestream, offset)
+ if size is None:
+ # Optional fields might not exist
+ if field.is_optional():
+ v = None
+ size = 0
+ else:
+ raise ValueError('Response too short to extract {} {} ({}): {}'
+ .format(field.name, field.typename, offset, v))
+ offset += size
+
+ # If it's used as a length, save it.
+ if field.islenvar:
+ lenfields[field.name] = int(v)
+
+ return offset, v
+
+
+def read_csv(args):
+ for line in fileinput.input(args.csv_file):
+ parts = line.rstrip().split(',')
+
+ if parts[0] == 'msgtype':
+ # eg msgtype,commit_sig,132
+ messages.append(Message(parts[1], parts[2]))
+ elif parts[0] == 'msgdata':
+ m = find_message(messages, parts[1])
+ if m is None:
+ raise ValueError('Unknown message {}'.format(parts[1]))
+
+ # eg. msgdata,channel_reestablish,your_last_per_commitment_secret
+ # ,secret,,1option209
+ m.addField(Field(m, parts[2], parts[3], parts[4], parts[5:]))
+ elif parts[0] == 'subtype':
+ Subtype.objs[parts[1]] = Subtype(parts[1])
+ elif parts[0] == 'subtypedata':
+ if parts[1] not in Subtype.objs:
+ raise ValueError('Unknown subtype {}'.format(parts[1]))
+ # Insert fields into dict for subtype
+ subtype = Subtype.objs[parts[1]]
+ subtype.addField(Field(subtype, parts[2], parts[3], parts[4], parts[5:]))
+ elif parts[0] == 'tlvtype':
+ tlvstreams.add(parts[1])
+
+
+def line_keyval(line, part):
+ """Return , from key=val part of line, or raise LineError"""
+ p = part.partition('=')
+ if p[1] != '=':
+ raise LineError(line, "Malformed key {} does not contain '='"
+ .format(part))
+ return p[0], p[2]
+
+
+def parse_params(line, parts, compulsorykeys, optionalkeys=[]):
+ """Given an array of = make a dict, checking we have all compulsory
+# keys."""
+ ret = {}
+ for i in parts:
+ k, v = line_keyval(line, i)
+ if k in ret.keys():
+ raise LineError(line, "Duplicate key {}".format(k))
+ if k in compulsorykeys:
+ compulsorykeys.remove(k)
+ elif k in optionalkeys:
+ optionalkeys.remove(k)
+ else:
+ raise LineError(line, "Unknown key {}".format(k))
+ ret[k] = v
+
+ if compulsorykeys != []:
+ raise LineError(line, "No specification for key {}"
+ .format(compulsorykeys[0]))
+ return ret
+
+
+def check_hex(line, val, digits):
+ if not all(c in string.hexdigits for c in val):
+ raise LineError(line, "{} is not valid hex".format(val))
+ if len(val) != digits:
+ raise LineError(line, "{} not {} characters long".format(val, digits))
+
+
+class Connection(object):
+ """Trivial class to represent a connection: often decorated by others"""
+ def __init__(self, connkey):
+ self.connkey = connkey
+ self.maybe_sends = []
+ self.must_not_sends = []
+
+ def __str__(self):
+ return str(self.connkey)
+
+
+def optional_connection(line, params):
+ """Trivial helper to return & remove conn=key if specified, None if not"""
+ ret = params.pop('conn', None)
+ if ret is not None:
+ check_hex(line, ret, 64)
+ return ret
+
+
+def optional_connection_then_type(line, parts):
+ """Trivial helper to extract & remove conn= and type= from parts"""
+ if len(parts) < 1:
+ raise LineError(line, "Missing type=")
+
+ k, v = line_keyval(line, parts.pop(0))
+ if k == 'conn':
+ check_hex(line, v, 64)
+ conn = v
+ if len(parts) < 1:
+ raise LineError(line, "Missing type=")
+ k, v = line_keyval(line, parts.pop(0))
+ else:
+ conn = None
+
+ if k != 'type':
+ raise LineError(line, "Expected type=")
+ return conn, v
+
+
+def which_connection(line, runner, connkey):
+ """Helper to get the conn they asked for, or default if connkey=None"""
+ if connkey is None:
+ if len(runner.connections) == 0:
+ raise LineError(line, "No active 'connect'")
+ return runner.connections[-1]
+ for c in runner.connections:
+ if c.connkey == connkey:
+ return c
+ raise LineError(line, "No active 'connect' {}".format(connkey))
+
+
+def end_connection(runner, conn, line):
+ """Helper to wait to see if any must-not-send are triggered at end"""
+ if conn.must_not_sends == []:
+ return
+
+ # We assume we don't get an infinite stream of msgs!
+ while True:
+ msg = runner.wait_for_finalmsg(conn)
+ if msg is None:
+ return
+ for m in conn.must_not_sends:
+ if message_match(m.expectmsg, m.expectfields, msg) is None:
+ raise ValidationError(line, "must-not-send at {} violated by {}"
+ .format(m.line, msg.hex()))
+
+ # If it was an (unexpected) error, we've failed.
+ if not runner.expected_error and struct.unpack('>H', msg[0:2]) == (17,):
+ raise ValidationError(line,
+ "Unexpected error occurred: {}".format(msg.hex()))
+
+
+class NothingEvent(object):
+ def __init__(self, line, parts):
+ parse_params(line, parts, [])
+
+ def action(self, runner, line):
+ pass
+
+
+class ConnectEvent(object):
+ def __init__(self, line, parts):
+ self.privkey = parse_params(line, parts, ['privkey'])['privkey']
+ check_hex(line, self.privkey, 64)
+
+ def action(self, runner, line):
+ conn = Connection(self.privkey)
+ if conn.connkey in [c.connkey for c in runner.connections]:
+ raise LineError(line,
+ "Already have connection to {}".format(conn.connkey))
+ runner.connections.append(conn)
+ runner.connect(conn, line)
+
+
+class DisconnectEvent(object):
+ def __init__(self, line, parts):
+ d = parse_params(line, parts, [], ['conn'])
+ self.connkey = optional_connection(line, d)
+
+ def action(self, runner, line):
+ conn = which_connection(line, runner, self.connkey)
+
+ end_connection(runner, conn, line)
+ runner.disconnect(conn, line)
+ runner.connections.remove(conn)
+
+
+class RecvEvent(object):
+ def __init__(self, line, parts):
+ self.connkey, t = optional_connection_then_type(line, parts)
+
+ msg = find_message(messages, t)
+ if not msg:
+ # Allow raw integers.
+ msg = Message('unknown', t)
+
+ # See what fields are allowed.
+ fields = []
+ optfields = ['extra']
+ for f in msg.fields:
+ # Lengths are implied
+ if f.islenvar:
+ continue
+ # Optional fields are, um, optional.
+ if f.is_optional():
+ optfields.append(f.name)
+ else:
+ fields.append(f.name)
+
+ # This fails if non-optional fields aren't specified.
+ d = parse_params(line, parts, fields, optfields)
+
+ # Now get values for assembling the message.
+ values = {}
+ for f in msg.fields:
+ # Lengths are implied
+ if f.islenvar:
+ continue
+ if f.name not in d:
+ continue
+ value, vararrlen = f.field_value(line, d[f.name])
+ values[f.name] = value
+ if f.arrayvar:
+ values[f.arrayvar.name] = vararrlen
+
+ # BOLT #1:
+ # After decryption, all Lightning messages are of the form:
+ #
+ # 1. `type`: a 2-byte big-endian field indicating the type of message
+ # 2. `payload`: a variable-length payload that comprises the remainder
+ # of the message and that conforms to a format matching the `type`
+ self.b = struct.pack(">H", msg.value)
+ for f in msg.fields:
+ if f.name not in values:
+ continue
+
+ v = values[f.name]
+ if f.arrayvar or f.arraylen:
+ for a in v:
+ if f.typename == 'signature' and isinstance(a, tuple):
+ sig = Sigs.generate_sig(a[0], a[1])
+ a = sig
+ self.b += pack(f.typename, a)
+ else:
+ if f.typename == 'signature' and isinstance(v, tuple):
+ v = Sigs.generate_sig(v[0], v[1])
+ self.b += pack(f.typename, v)
+
+ if 'extra' in d:
+ self.b += bytes.fromhex(d['extra'])
+
+ def action(self, runner, line):
+ runner.recv(which_connection(line, runner, self.connkey),
+ self.b, line)
+
+def compare_results(msgname, f, v, exp):
+ """ f -> field; v -> value; exp -> expected value """
+
+ # If they specify field=absent, it must not be there.
+ if exp is None:
+ if v is not None:
+ return "Field {} is present".format(f.name)
+ else:
+ return None
+
+ if v is None:
+ return ("Optional field {} is not present"
+ .format(f.name))
+
+ # Do signature verification, if necessary
+ if (f.typename == 'signature' and isinstance(exp,tuple)) or (f.typename == 'signature'
+ and (f.arrayvar or f.arraylen) and isinstance(exp,list)):
+ if f.arrayvar or f.arraylen:
+ for (e, val) in list(map(lambda x,y: (x,y), exp, v)):
+ if isinstance(e, tuple):
+ if not Sigs.verify_sig(e[0], e[1], val):
+ return "Invalid signature ({}) for privkey {}, hash {}".format(
+ val.hex(), e[0], e[1])
+ elif e != val:
+ return ("Expected {}.{}(type:{}) {} but got {}"
+ .format(msgname,
+ f.name, f.typename, e.hex(), val.hex()))
+ else:
+ # v should be a valid signature, a byte-array r||s
+ if not Sigs.verify_sig(exp[0], exp[1], v):
+ return "Invalid signature ({}) for privkey {}, hash {}".format(v.hex(), exp[0], exp[1])
+ # Successfully matched all sigs!!
+ return None
+
+ if isinstance(exp, tuple):
+ # Out-of-range bitmaps are considered 0 (eg. feature tests)
+ if len(v) < len(exp[0]):
+ cmpv = b'\x00' * (len(exp[0]) - len(v)) + v
+ elif len(v) > len(exp[0]):
+ cmpv = v[-len(exp[0]):]
+ else:
+ cmpv = v
+
+ for i in range(0, len(exp[0])):
+ if cmpv[i] & exp[1][i] != exp[0][i]:
+ return ("Expected {}.{} mask 0x{}"
+ " value 0x{} but got 0x{}"
+ " (offset {} different)"
+ .format(msgname, f.name,
+ exp[1].hex(), exp[0].hex(),
+ v.hex(), len(exp[0]) - 1 - i))
+ # Use subtype comparer
+ elif f.typename in Subtype.objs:
+ if not v and not exp:
+ return None
+ return Subtype.objs[f.typename].compare(msgname, v[0], exp[0])
+
+ # Simple comparison
+ elif v != exp:
+ if f.isinteger:
+ # Instead of checking a field's value, allow a test
+ # to specify a range of bytelen that's valid.
+ # This is needed for witness data verifications.
+ # e.g. *71-73 or *73 for exactly 73 bytes
+ if isinstance(exp, str) and exp.startswith('*') and check_range(exp[1:], v):
+ return None
+ valstr = str(v)
+ expectstr = str(exp)
+ # Same as above note about a range of length
+ elif isinstance(exp,str) and exp.startswith('*'):
+ if check_range(exp[1:], len(v.hex()) // 2):
+ return None
+ expectstr = "result of bytelen {}".format(exp[1:])
+ valstr = str(len(v.hex()) // 2)
+ else:
+ valstr = v.hex()
+ expectstr = exp.hex()
+ return ("Expected {}.{} {} but got {}"
+ .format(msgname,
+ f.name, expectstr, valstr))
+ return None
+
+
+def check_range(exp, val_len):
+ len_range = exp.split('-')
+ if len(len_range) > 2:
+ raise ValueError("Expected size range invalid", exp)
+
+ if len(len_range) == 1:
+ return int(len_range[0]) == val_len
+
+ return int(len_range[0]) <= val_len and int(len_range[1]) >= val_len
+
+def message_match(expectmsg, expectfields, b):
+ """Internal helper to see if b matches expectmsg & expectfields.
+
+ Returns explanation string if it didn't match, otherwise None."""
+ msgtype = struct.unpack_from(">H", b)[0]
+ off = 2
+
+ if msgtype != expectmsg.value:
+ return "Expected msg {} but got {}: {}".format(expectmsg.name,
+ msgtype, b.hex())
+ # Keep length fields
+ lenfields = {}
+ for f in expectmsg.fields:
+ off, v = unpack_field(f, lenfields, b, off)
+
+ # They expect a value from this.
+ if f.name in expectfields:
+ exp = expectfields[f.name]
+ err = compare_results(expectmsg.name, f, v, exp)
+ if err is not None:
+ return err + ": {}".format(b.hex())
+
+ return None
+
+
+def maybesend_match(conn, msg):
+ """Internal helper to see if msg matches one of the previous maybe-sends"""
+ for m in conn.maybe_sends:
+ failreason = message_match(m.expectmsg, m.expectfields, msg)
+ if failreason is None:
+ conn.maybe_sends.remove(m)
+ return True
+
+ return False
+
+
+class ExpectSendEvent(object):
+ def __init__(self, line, parts, maybe=False, mustnot=False):
+ self.line = line
+ self.maybe = maybe
+ self.mustnot = mustnot
+
+ self.connkey, t = optional_connection_then_type(line, parts)
+
+ self.expectmsg = find_message(messages, t)
+ if not self.expectmsg:
+ raise LineError(line, "Unknown message type")
+ self.expectfields = {}
+
+ optfields = []
+ for f in self.expectmsg.fields:
+ # Lengths are implied
+ if f.islenvar:
+ continue
+ optfields.append(f.name)
+
+ # All fields are optional
+ d = parse_params(line, parts, [], optfields)
+
+ for v in d.keys():
+ # IDENTIFIER`=`FIELDVALUE | IDENTIFIER`=`HEX/HEX | `absent` |
+ # IDENTIFIER`=`SIG(HEX:HEX)
+ f = self.expectmsg.findField(v)
+
+ parts = d[v].partition('/')
+ if parts[1] == '/':
+ self.expectfields[v] = (bytes.fromhex(parts[0]),
+ bytes.fromhex(parts[2]))
+ if len(self.expectfields[v][0]) != len(self.expectfields[v][1]):
+ raise LineError(line, "Unequal value/mask lengths")
+ else:
+ if parts[0] == 'absent':
+ if not f.is_optional():
+ raise LineError(line, "{} is not optional".format(f.name))
+ self.expectfields[v] = None
+ else:
+ self.expectfields[v], _ = f.field_value(line, parts[0])
+
+ def __repr__(self):
+ if self.mustnot:
+ return "must-not-send:{}:{}".format(self.expectmsg.name, self.line)
+ if self.maybe:
+ return "maybe-send:{}:{}".format(self.expectmsg.name, self.line)
+ return "expect-send:{}:{}".format(self.expectmsg.name, self.line)
+
+ def action(self, runner, line):
+ conn = which_connection(line, runner, self.connkey)
+ # If this is 'maybe-send' then just add it to maybe list.
+ if self.maybe:
+ conn.maybe_sends.append(self)
+ return
+ # If this is 'must-not-send' then just add it to maybe list.
+ elif self.mustnot:
+ conn.must_not_sends.append(self)
+ return
+
+ msg = runner.expect_send(conn, line)
+
+ # We let the dummy runner "pass" always.
+ if type(runner) == DummyRunner:
+ return
+
+ while True:
+ failreason = message_match(self.expectmsg, self.expectfields, msg)
+ if failreason is None:
+ return
+
+ if maybesend_match(conn, msg):
+ msg = runner.expect_send(conn, line)
+ else:
+ break
+
+ if conn.maybe_sends != []:
+ raise ValidationError(line,
+ failreason
+ + " (and none of {})".format(conn.maybe_sends))
+ raise ValidationError(line, failreason)
+
+
+class BlockEvent(object):
+ def __init__(self, line, parts):
+ # Since parse_params doesn't allow dups, feed it one part at a time
+ self.blockheight = int(parse_params(line, [parts[0]], ['height'])['height'])
+ self.txs = []
+ self.n = 1
+
+ # Since parse_params doesn't allow dups, feed it one part at a time
+ for i in range(1, len(parts)):
+ # n is only valid as first arg.
+ if i == 1:
+ d = parse_params(line, [parts[i]], [], ['n', 'tx'])
+ if 'n' in d:
+ if self.n != 1:
+ raise LineError(line, "Can't specify n more than once")
+ self.n = int(d['n'])
+ continue
+ else:
+ d = parse_params(line, [parts[i]], ['tx'])
+ self.txs.append(d['tx'])
+
+ def action(self, runner, line):
+ # Oops, did they ask us to produce a block with no predecessor?
+ if runner.getblockheight() + 1 < self.blockheight:
+ raise LineError(line, "Cannot generate block #{} at height {}".
+ format(self.blockheight, runner.getblockheight()))
+
+ # Throw away blocks we're replacing.
+ if runner.getblockheight() >= self.blockheight:
+ runner.trim_blocks(self.blockheight - 1)
+
+ # Add new one
+ runner.add_blocks(self.txs, self.n, line)
+ assert runner.getblockheight() == self.blockheight - 1 + self.n
+
+
+class ExpectTxEvent(object):
+ def __init__(self, line, parts):
+ self.tx = bytes.fromhex(parse_params(line, parts, ['tx'])['tx'])
+
+ def action(self, runner, line):
+ runner.expect_tx(self.tx, line)
+
+
+class FundChannelEvent(object):
+ def __init__(self, line, parts):
+ d = parse_params(line, parts, ['amount', 'utxo', 'feerate'], ['conn'])
+ self.connkey = optional_connection(line, d)
+ self.amount = int(d['amount'])
+ parts = d['utxo'].partition('/')
+ check_hex(line, parts[0], 64)
+ self.utxo = (parts[0], int(parts[2]))
+ self.feerate = d['feerate']
+
+ def action(self, runner, line):
+ runner.fundchannel(which_connection(line, runner, self.connkey),
+ self.amount, self.utxo[0],
+ self.utxo[1], self.feerate, line)
+
+
+class InvoiceEvent(object):
+ def __init__(self, line, parts):
+ d = parse_params(line, parts, ['amount', 'preimage'])
+ self.preimage = d['preimage']
+ check_hex(line, self.preimage, 64)
+ self.amount = int(d['amount'])
+
+ def action(self, runner, line):
+ runner.invoice(self.amount, self.preimage, line)
+
+
+class ExpectErrorEvent(object):
+ def __init__(self, line, parts):
+ d = parse_params(line, parts, [], ['conn'])
+ self.connkey = optional_connection(line, d)
+
+ def action(self, runner, line):
+ runner.expected_error = True
+ runner.expect_error(which_connection(line, runner, self.connkey), line)
+
+
+class Event(object):
+ def __init__(self, args, desc, line):
+ self.args = args
+ self.line = copy(line)
+
+ parts = desc.split()
+ self.event = parts[0]
+
+ if parts[0] == 'connect:':
+ self.actor = ConnectEvent(line, parts[1:])
+ elif parts[0] == 'disconnect:':
+ self.actor = DisconnectEvent(line, parts[1:])
+ elif parts[0] == 'recv:':
+ self.actor = RecvEvent(line, parts[1:])
+ elif parts[0] == 'expect-send:':
+ self.actor = ExpectSendEvent(line, parts[1:])
+ elif parts[0] == 'maybe-send:':
+ self.actor = ExpectSendEvent(line, parts[1:], maybe=True)
+ elif parts[0] == 'must-not-send:':
+ self.actor = ExpectSendEvent(line, parts[1:], mustnot=True)
+ elif parts[0] == 'block:':
+ self.actor = BlockEvent(line, parts[1:])
+ elif parts[0] == 'expect-tx:':
+ self.actor = ExpectTxEvent(line, parts[1:])
+ elif parts[0] == 'fundchannel:':
+ self.actor = FundChannelEvent(line, parts[1:])
+ elif parts[0] == 'invoice:':
+ self.actor = InvoiceEvent(line, parts[1:])
+ elif parts[0] == 'expect-error:':
+ self.actor = ExpectErrorEvent(line, parts[1:])
+ elif parts[0] == 'nothing':
+ self.actor = NothingEvent(line, parts[1:])
+ else:
+ raise LineError(line, "Unknown event type {}".format(parts[0]))
+
+ def __repr__(self):
+ return "Event({}, {})".format(self.event, str(self.line))
+
+ def flatten(self, number, stopline, prefix=''):
+ # Nothing doesn't even need outputting
+ if type(self.actor) == NothingEvent:
+ return False, number
+ return self.line.flatten(number, stopline, prefix)
+
+ def num_steps(self):
+ return 1
+
+ def act(self, runner):
+ if self.args.verbose:
+ print("# running {}".format(self))
+ self.actor.action(runner, self.line)
+
+
+class Sequence(object):
+ """Ordered sequence of Events"""
+ def __init__(self, args):
+ self.args = args
+ self.events = []
+ self.line = None
+
+ def add_event(self, e):
+ self.events.append(e)
+ if self.line is None:
+ self.line = copy(e.line)
+ else:
+ self.line += e.line
+
+ def flatten(self, number, stopline, prefix=''):
+ stop = False
+ for e in self.events:
+ stop, number = e.flatten(number, stopline, prefix)
+ if stop:
+ break
+ return stop, number
+
+ def num_steps(self):
+ return sum([e.num_steps() for e in self.events])
+
+ def __str__(self):
+ return "{}".format(self.line)
+
+ def __repr__(self):
+ return "Sequence:{}".format(self)
+
+ def run(self, runner, start=0):
+ if self.args.verbose:
+ print("# running {}:".format(self))
+ for e in self.events[start:]:
+ e.act(runner)
+
+
+def match_which_sequence(msg, sequences):
+ """Return which sequence starts with expect-msg: msg, or None"""
+ for s in sequences:
+ failreason = message_match(s.events[0].actor.expectmsg,
+ s.events[0].actor.expectfields, msg)
+ if failreason is None:
+ return s
+
+ return None
+
+
+class OneOfEvent(object):
+ """Event representing multiple possible sequences"""
+ def __init__(self, args, line):
+ self.line = line
+ self.args = args
+ self.sequences = []
+
+ def __str__(self):
+ return str(self.line)
+
+ def flatten(self, number, stopline, prefix):
+ # FIXME: if stopline is in here, we ignore it
+ _, number = self.line.flatten(number, stopline, prefix)
+ i = 1
+ for s in self.sequences:
+ _, i = s.flatten(i, stopline, ' ')
+ return False, number
+
+ def num_steps(self):
+ # Use the mean of the separate sequences as a guesstimate.
+ return sum([s.num_steps() for s in self.sequences]) / len(self.sequences)
+
+ def add_sequence(self, seq):
+ actor = seq.events[0].actor
+ if type(actor) != ExpectSendEvent:
+ # We could relax this a bit if necessary, eg 'expect-error' or
+ # 'expect-tx' would be possible.
+ raise LineError(seq.events[0].line,
+ "First sequence event in One Of must be expect-send")
+ # They have to match on what conn the specify, too.
+ if len(self.sequences) != 0:
+ if actor.connkey != self.connkey:
+ raise LineError(seq.events[0].line,
+ "All first sequence event in One Of must same conn=")
+ else:
+ self.connkey = actor.connkey
+ self.sequences.append(seq)
+
+ def act(self, runner):
+ if self.args.verbose:
+ print("# running {}".format(self))
+
+ # For DummyRunner, we assume the first.
+ if type(runner) == DummyRunner:
+ return self.sequences[0].run(runner)
+
+ conn = which_connection(self.line, runner, self.connkey)
+ while True:
+ msg = runner.expect_send(conn, self.line)
+ s = match_which_sequence(msg, self.sequences)
+ if s is not None:
+ # We found the sequence, run the rest of it.
+ s.run(runner, start=1)
+ return
+
+ if maybesend_match(conn, msg):
+ continue
+
+ raise ValidationError(self.line,
+ "None of the sequences matched {}"
+ .format(msg.hex()))
+
+
+class AnyOrderEvent(object):
+ """Event representing sequences in any order"""
+ def __init__(self, args, line):
+ self.line = line
+ self.args = args
+ self.sequences = []
+
+ def __str__(self):
+ return str(self.line)
+
+ def flatten(self, number, stopline, prefix):
+ # FIXME: if stopline is in here, we ignore it
+ _, number = self.line.flatten(number, stopline, prefix)
+ i = 1
+ for s in self.sequences:
+ _, i = s.flatten(i, stopline, ' ')
+ return False, number
+
+ def num_steps(self):
+ return sum([s.num_steps() for s in self.sequences])
+
+ def add_sequence(self, seq):
+ actor = seq.events[0].actor
+ if type(actor) != ExpectSendEvent:
+ # We could relax this a bit if necessary, eg 'expect-error' or
+ # 'expect-tx' would be possible.
+ raise LineError(seq.events[0].line,
+ "First sequence event in Any Order must be expect-send")
+ # They have to match on what conn the specify, too.
+ if len(self.sequences) != 0:
+ if actor.connkey != self.connkey:
+ raise LineError(seq.events[0].line,
+ "All first sequence event in Any Order must same conn=")
+ else:
+ self.connkey = actor.connkey
+ self.sequences.append(seq)
+
+ def act(self, runner):
+ if self.args.verbose:
+ print("# running {}".format(self))
+
+ # For DummyRunner, we assume the first.
+ if type(runner) == DummyRunner:
+ return self.sequences[0].run(runner)
+
+ conn = which_connection(self.line, runner, self.connkey)
+ sequences = self.sequences[:]
+ while sequences != []:
+ msg = runner.expect_send(conn, self.line)
+ s = match_which_sequence(msg, sequences)
+
+ if s is not None:
+ sequences.remove(s)
+ s.run(runner, start=1)
+ elif not maybesend_match(conn, msg):
+ raise ValidationError(self.line,
+ "None of the sequences matched {}"
+ .format(msg.hex()))
+
+
+# Loads a Sequence at this indent level (and any children embedded in
+# it, if allow_children). Returns the initial Sequence, a list of
+# Sequence leaves, and the next linenum.
+def load_sequence(args, lines, linenum, indentlevel, graph):
+ count = 1
+ init_seq = Sequence(args)
+
+ seq = init_seq
+ terminals = [seq]
+
+ if graph is not None:
+ graph.add_node(seq)
+
+ # We always parse one child.
+ if lines[linenum].indentlevel != indentlevel:
+ raise LineError(lines[linenum], "Expected {} indents.", indentlevel)
+ if not lines[linenum].line.startswith('1.'):
+ raise LineError(lines[linenum], "Expected 1.")
+
+ while linenum < len(lines):
+ # Unindent? We're done.
+ if lines[linenum].indentlevel < indentlevel:
+ return init_seq, terminals, linenum
+
+ # Indent? Parse children.
+ if lines[linenum].indentlevel == indentlevel + 1:
+ if graph is None:
+ raise LineError(lines[linenum],
+ "Cannot have indentations inside 'One of'/'Any order'")
+ child, childterms, linenum = load_sequence(args,
+ lines, linenum,
+ indentlevel + 1, graph)
+
+ # Attach this child to our current seq.
+ if args.verbose:
+ print("# child {} -> {}".format(seq, child))
+
+ graph.add_edge(seq, child)
+
+ # Seq is no longer terminal
+ if seq in terminals:
+ terminals.remove(seq)
+
+ # These will bet attached onto the next sequence.
+ terminals += childterms
+ continue
+ elif lines[linenum].indentlevel != indentlevel:
+ raise LineError(lines[linenum], "Unexpected indent.")
+
+ # Same level.
+ parts = lines[linenum].line.partition('.')
+ if parts[1] != '.':
+ raise LineError(lines[linenum],
+ "Expected '{}.' or '1.'".format(count))
+
+ # Unexpected 1. means a new start.
+ if parts[0] != str(count):
+ return init_seq, terminals, linenum
+
+ if parts[2].split() == ['One', 'of:']:
+ event = OneOfEvent(args, lines[linenum])
+
+ # We expect indented sequences
+ linenum += 1
+ while linenum < len(lines) and lines[linenum].indentlevel == indentlevel + 1:
+ # We don't allow sub-nodes here, so terminals will be [child]
+ child, _, linenum = load_sequence(args, lines, linenum,
+ indentlevel + 1,
+ None)
+ event.add_sequence(child)
+
+ if event.sequences == []:
+ raise LineError(lines[linenum],
+ "Expected indented sequences after 'One of:'")
+ elif parts[2].split() == ['Any', 'order:']:
+ event = AnyOrderEvent(args, lines[linenum])
+
+ # We expect indented sequences
+ linenum += 1
+ while linenum < len(lines) and lines[linenum].indentlevel == indentlevel + 1:
+ # We don't allow sub-nodes here, so terminals will be [child]
+ child, _, linenum = load_sequence(args, lines, linenum,
+ indentlevel + 1,
+ None)
+ event.add_sequence(child)
+
+ if event.sequences == []:
+ raise LineError(lines[linenum],
+ "Expected indented sequences after 'Any order:'")
+ else:
+ event = Event(args, parts[2], lines[linenum])
+ linenum += 1
+
+ # Any children from last step, start new Sequence for them to connect.
+ if terminals != [seq]:
+ seq = Sequence(args)
+ seq.add_event(event)
+ graph.add_node(seq)
+ for c in terminals:
+ if args.verbose:
+ print("# {} -> {}".format(c, seq))
+ graph.add_edge(c, seq)
+ terminals = [seq]
+ else:
+ seq.add_event(event)
+ count += 1
+
+ # Any children will continue from our last event(s).
+ return init_seq, terminals, linenum
+
+
+def run_test(args, path, runner):
+ if args.verbose:
+ print("## RESTART")
+ runner.restart()
+ runner.connections = []
+ runner.expected_error = False
+ for seq in path:
+ seq.run(runner)
+
+ # Make sure they didn't send any must-not-sends at the end.
+ for conn in runner.connections:
+ end_connection(runner, conn, path[-1].line)
+
+ if not runner.expected_error:
+ error = runner.final_error()
+ if error is not None:
+ raise ValidationError(None,
+ "Unexpected error occurred: {}".format(error))
+
+
+def line_minus_comments(verbose, line, linenum):
+ """Strips any comment from a line, and trailing whitespace."""
+ # Get the line.
+ arr = line.rstrip().partition('#')
+ if arr[1] == '#' and verbose:
+ if arr[2] != '':
+ print("# {}: {}".format(linenum, arr[2]))
+ return arr[0].rstrip()
+
+
+def filter_out(args, line, filename, linenum):
+ """Trim options: we discard the line if it doesn't qualify."""
+ while True:
+ m = re.search("(?P!?)"
+ "(?Popt[A-Za-z_]*)"
+ r"(?P(/(odd|even))?)\s*$", line)
+ if m is None:
+ return line
+
+ if m.group('oddoreven') != '':
+ present = m.group('optname') + m.group('oddoreven') in args.option
+ else:
+ present = (m.group('optname') + '/odd' in args.option
+ or m.group('optname') + '/even' in args.option)
+
+ # If option was specified as --option, invert must be set.
+ wanted = m.group('invert') != '!'
+ if present != wanted:
+ if args.verbose:
+ print("# Removing line {}: requires {}{}{}"
+ .format(Line(filename, linenum, linenum, 0, line),
+ m.group('invert'),
+ m.group('optname'),
+ m.group('oddoreven')))
+ return ''
+ line = line[:m.start()]
+
+
+def indentation(s):
+ """Returns str with indent stripped, and effective indentation amount"""
+ level = 0
+ consumed = 0
+ for i in range(len(s)):
+ if s[i] == ' ':
+ level += 1
+ elif s[i] == '\t':
+ # I keep puttiing in tabs by mistake. Make them got to next 8.
+ level = (level + 8) // 8 * 8
+ else:
+ break
+ consumed = i + 1
+ return s[consumed:], level
+
+
+def parse_file(args, f, filename, variables):
+ """Get non-comment lines, as [(linenums,indentlevel,line)], grab vars"""
+ content = []
+ lines = f.readlines()
+ i = 0
+ while i < len(lines):
+ line_start = i
+
+ line = line_minus_comments(args.verbose, lines[i], line_start)
+ line = filter_out(args, line, filename, line_start)
+ if line == '':
+ i += 1
+ continue
+
+ # Store indentation level, remove it.
+ line, indent = indentation(line)
+ if indent % 4 != 0:
+ raise LineError(Line(filename, line_start, line_start, 0, line),
+ "Indent is not a multiple of 4!")
+ indentlevel = indent // 4
+
+ i += 1
+ line_end = line_start
+
+ # Grab any continuation lines
+ while i < len(lines):
+ lookahead = line_minus_comments(False, lines[i], i)
+ if lookahead == '':
+ i += 1
+ continue
+ lookahead, indent = indentation(lookahead)
+ # Line continuations are non-4 indent, but must be greater.
+ if indent % 4 == 0:
+ break
+ if indent < indentlevel * 4:
+ raise LineError(Line(filename, i, i, indentlevel, lines[i]),
+ "Indent is not a multiple of 4!")
+ line += ' ' + filter_out(args, lookahead, filename, i)
+ line_end = i
+ i += 1
+
+ # Expand variables.
+ while True:
+ m = re.search(r"\$(?P[A-Za-z_0-9]+)", line)
+ if m is None:
+ break
+
+ if not m.group('varname') in variables:
+ raise LineError(Line(filename, i, i, indentlevel, lines[i]),
+ "Unknown variable {}".format(m.group('varname')))
+
+ line = (line[:m.start()]
+ + variables[m.group('varname')]
+ + line[m.end():])
+
+ # Check if we're merely setting a variable; we do this now so
+ # we can expand inside parse_file itself.
+ parts = line.partition('=')
+ if parts[1] == '=' and re.fullmatch('[A-Za-z0-9_]+', parts[0]):
+ if parts[0] in variables:
+ raise LineError(Line(filename, line_start, line_end,
+ indentlevel, lines[i]),
+ "Re-setting var {}".format(parts[0]))
+ variables[parts[0]] = parts[2]
+ # Similarly, do include directives immediately.
+ elif line.startswith('include '):
+ # Filenames are assumed to be relative.
+ subfilename = path.join(path.dirname(filename), line[8:])
+ subf = open(subfilename)
+ # This can set, and use, variables.
+ sublines, variables = parse_file(args, subf, subfilename, variables)
+ # Indent entire file as per this include line.
+ for l in sublines:
+ l.indentlevel += indentlevel
+ content += sublines
+ else:
+ content.append(Line(filename, line_start, line_end, indentlevel,
+ line))
+
+ return content, variables
+
+
+def main(args, runner):
+ read_csv(args)
+ if args.verbose:
+ print("# loaded {} message types".format(len(messages)))
+
+ lines = []
+ for filename in args.input:
+ if filename is None:
+ filename = ''
+ f = sys.stdin
+ else:
+ f = open(filename)
+
+ lines, _ = parse_file(args, f, filename, {})
+ f.close()
+
+ graph = nx.DiGraph()
+ root, terminals, linenum = load_sequence(args, lines, 0, 0, graph)
+
+ # We only support one root sequence for now.
+ if linenum != len(lines):
+ raise LineError(lines[linenum],
+ "Unexpected lines after end of first sequence")
+
+ if args.draw_events:
+ labels = {}
+ for seq in graph.nodes():
+ labels[seq] = str(seq)
+
+ nx.draw_circular(graph, labels=labels, node_size=50, font_size=4)
+ plt.savefig(filename + ".png", dpi=300)
+
+ # Edge weight == ops in sequence it leads to. Since we walk
+ # most-expensive-first, this gives maximum testing coverage to
+ # first run.
+ for e in graph.edges():
+ graph.edges[e]['weight'] = e[1].num_steps()
+
+ # Get all paths
+ paths = []
+ if args.via:
+ parts = args.via.partition(':')
+ if parts[1] == ':':
+ filename = parts[0]
+ linenum = int(parts[2])
+ else:
+ filename = None
+ linenum = int(parts[0])
+ via = None
+ for seq in graph.nodes():
+ if filename and seq.line.filename != filename:
+ continue
+ if linenum < seq.line.linestart:
+ continue
+ if linenum > seq.line.lineend:
+ continue
+ via = seq
+ break
+
+ if not via:
+ raise ValueError("{} not found".format(args.destination))
+
+ path1 = nx.shortest_path(graph, root, via, 'weight')
+
+ # Now get to any terminal.
+ for t in terminals:
+ try:
+ path2 = nx.shortest_path(graph, via, t, 'weight')
+ break
+ except nx.exception.NetworkXNoPath:
+ pass
+ paths = [path1 + path2]
+ elif args.exhaustive:
+ # This does the simple ones first, which is usually what
+ # you want.
+ for t in terminals:
+ paths += nx.shortest_simple_paths(graph, root, t, 'weight')
+ else:
+ while any([graph.edges[e]['weight'] != 0 for e in graph.edges()]):
+ for t in terminals:
+ # Start with "longest" first. This is a super slow way
+ # to calc this!
+ path = list(nx.shortest_simple_paths(graph, root, t, 'weight'))[-1]
+ new_edge = False
+ for e in nx.utils.misc.pairwise(path):
+ if graph.edges[e]['weight'] > 0:
+ new_edge = True
+ graph.edges[e]['weight'] = 0
+
+ if new_edge:
+ if args.verbose:
+ print("PATH: {}".format(path))
+ paths.append(path)
+ break
+
+ # Special case of a single sequence
+ if graph.number_of_nodes() == 1:
+ paths = [[root]]
+
+ if not args.verbose:
+ print("{}:{} paths: ".format(filename, len(paths)), end='', flush=True)
+ for path in paths:
+ try:
+ run_test(args, path, runner)
+ except ValidationError as v:
+ print("ERROR during {}".format([p.line for p in path]), file=sys.stderr)
+ if args.flatten_failpath:
+ index = 1
+ print("FAILPATH to {}:".format(v.line))
+ for p in path:
+ stop, index = p.flatten(index, v.line)
+ if stop:
+ break
+ raise
+ if not args.verbose:
+ print('.', end='', flush=True)
+
+ runner.shutdown()
+ print("OK")
+
+
+if __name__ == "__main__":
+ parser = setup_cmdline_options()
+ args = parser.parse_args()
+
+ main(args, DummyRunner(args))