I’ve developed a much cleaner/safer/better way of doing BIP-300/301, and updated the BIP accordingly.
While doing that, I have updated the BIP text to (hopefully) clarify a number of points/questions that were raised over the last 12 months.
I’ve developed a much cleaner/safer/better way of doing BIP-300/301, and updated the BIP accordingly.
While doing that, I have updated the BIP text to (hopefully) clarify a number of points/questions that were raised over the last 12 months.
14@@ -15,31 +15,28 @@
15
16 ==Abstract==
17
18-In Bip300, txns are not signed via cryptographic key. Instead, they are "signed" by hashpower, over time. Like a big multisig, 13150-of-26300, where each block is a new "signature".
19+BIP-300 enables a new type of L2, where "withdrawals" (the L2-to-L1 txns) are governed by proof-of-work -- (instead of a federation or fixed set of pubkeys).
20
21-Bip300 emphasizes slow, transparent, auditable transactions which are easy for honest users to get right and very hard for dishonest users to abuse. The chief design goal for Bip300 is ''partitioning'' -- users may safely ignore Bip300 txns if they want to (or Bip300 entirely).
22+BIP-300 emphasizes slow, transparent, and auditable withdrawals -- easy for honest users to get right and hard for dishonest miners to abuse. The main design goal for BIP-300 is ''partitioning'' -- users can ignore BIP-300 txns if they wish, it makes no difference to L1 if the user validates all, some, or none of them. The second design goal is ''security'' -- users of the L2 should feel confident that, [https://www.drivechain.info/blog/fees/ if the L2 network is paying a lot of fees], then miners will want to keep it around, and the withdrawals will therefore be processed accurately.
0BIP-300 emphasizes slow, transparent, and auditable withdrawals that are easy for honest users to get right and hard for dishonest miners to abuse. The main design goal for BIP-300 is ''partitioning'' -- users can ignore BIP-300 txns if they wish; it makes no difference to L1 if the user validates all, some, or none of them. The second design goal is ''security'' -- users of the L2 should feel confident that, [https://www.drivechain.info/blog/fees/ if the L2 network is paying a lot of fees], then miners will want to keep it around, and the withdrawals will therefore be processed accurately.
14@@ -15,31 +15,28 @@
15
16 ==Abstract==
17
18-In Bip300, txns are not signed via cryptographic key. Instead, they are "signed" by hashpower, over time. Like a big multisig, 13150-of-26300, where each block is a new "signature".
19+BIP-300 enables a new type of L2, where "withdrawals" (the L2-to-L1 txns) are governed by proof-of-work -- (instead of a federation or fixed set of pubkeys).
Suggest either the long dash or the parentheses, but not both. Or a comma-space.
0BIP-300 enables a new type of L2, where "withdrawals" (the L2-to-L1 txns) are governed by proof-of-work -- instead of a federation or fixed set of pubkeys.
25+Once BIP-300 has established a "bridge" between L1 and these L2s, users can swap coins in and out instantly, only using BIP-300 for final settlement. This setup allows Bitcoin to process all the transactions in the world, of any shape or size, regardless of blocksize, node software, tech stack, or decentralization level -- all without altering L1 at all.
26
27
28 ==Motivation==
29
30+BIP-300 allows us to achieve [https://www.truthcoin.info/blog/zside-meltcast/ strong privacy], [https://www.truthcoin.info/blog/thunder/ planetary scale], and [https://www.truthcoin.info/blog/all-world-txns/ hundreds of billions of dollars in annual mining revenues], all a [https://www.drivechain.info/blog/fees/ security model] that is [https://x.com/Truthcoin/status/1701959339508965405 much stronger than] that of the [https://www.truthcoin.info/blog/ln-blackpill/ doomed Lightning Network].
missing “with”?
Is it really necessary to describe the LN as “doomed” here.
0BIP-300 allows us to achieve [https://www.truthcoin.info/blog/zside-meltcast/ strong privacy], [https://www.truthcoin.info/blog/thunder/ planetary scale], and [https://www.truthcoin.info/blog/all-world-txns/ hundreds of billions of dollars in annual mining revenues], all with a [https://www.drivechain.info/blog/fees/ security model] that is [https://x.com/Truthcoin/status/1701959339508965405 much stronger than] that of the [https://www.truthcoin.info/blog/ln-blackpill/ Lightning Network].
90 |- style="vertical-align:middle;"
91 | 6
92 | Hash2 - git commit hash
93 | uint160
94-| Intended as the git commit hash of the canonical sidechain node software. (This is not enforced anywhere by Bip300, and is for human purposes only.)
95+| Intended as the git commit hash of the canonical sidechain node software. (This is not enforced anywhere by BIP-300, and is for human purposes only.)
208@@ -219,40 +209,26 @@ Otherwise:
209
210 A sidechain fails to activate if:
211
212-* If the slot is unused: during the next 2016 blocks, it accumulates 201 fails. (Ie, 90% threshold).
213-* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (Ie, 50% threshold).
214+* If the slot is unused: during the next 2016 blocks, it accumulates 1008 fails. (Ie, 50% hashrate threshold).
215+* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (Ie, 50% hashrate threshold).
s/ie,/i.e.,/
213-* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (Ie, 50% threshold).
214+* If the slot is unused: during the next 2016 blocks, it accumulates 1008 fails. (Ie, 50% hashrate threshold).
215+* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (Ie, 50% hashrate threshold).
216
217-( Thus we can overwrite a used sidechain slot. Bip300 sidechains are already vulnerable to one catastrophe per 13150 blocks (the invalid withdrawal) so this slot-overwrite option does not change the security assumptions. )
218+( Thus we can overwrite a used sidechain slot. BIP-300 sidechains are already vulnerable to one catastrophe per 13150 blocks (the invalid withdrawal) so this slot-overwrite option does not change the security assumptions. )
0(Thus we can overwrite a used sidechain slot. BIP-300 sidechains are already vulnerable to one catastrophe per 13150 blocks (the invalid withdrawal), so this slot-overwrite option does not change the security assumptions.)
235 Sidechain withdrawals take the form of "Bundles" -- named because they "bundle up" many individual withdrawal-requests into a single rare layer1 transaction.
236
237-Sidechain full nodes aggregate the withdrawal-requests into a big set. The sidechain calculates what M6 would have to look like, to pay all of these withdrawal-requests out. Finally, the sidechain calculates what the hash of this M6 would be. This 32-byte hash identifies the Bundle.
238-
239-This 32-byte hash is what miners will be slowly ACKing over 3-6 months, not the M6 itself (nor any sidechain data, of course).
240+On the L2 side, individual withdrawal requests are periodically combined into a single CoinJoin-like withdrawal bundle. This bundle is hashed [https://github.com/LayerTwo-Labs/bip300301_messages/blob/master/src/lib.rs#L374C1-L419C2 in a particular way] (on both L2 and L1) -- this "blinded hash" commits to its own L1 fee, but (notably) it does not commit to its first tx-input (in that way, it is like AnyPrevOut, BIP-118).
0On the L2 side, individual withdrawal requests are periodically combined into a single CoinJoin-like withdrawal bundle. This bundle is hashed [https://github.com/LayerTwo-Labs/bip300301_messages/blob/master/src/lib.rs#L374C1-L419C2 in a particular way] (on both L2 and L1) -- this "blinded hash" commits to its own L1 fee, but (notably) it does not commit to its first tx-input (in that way, it is like SIGHASH_ANYPREVOUT, BIP-118).
Suggest linking to BIP118 (and perhaps drop the naming of it, so that if its name changes, then no need to update here).
241
242-A bundle either pays all its withdrawals out (via M6), or else it fails (and pays nothing out).
243+This hash is what L1 miners will slowly ACK over 3-6 months, not the M6 itself (nor any sidechain data, of course).
244
245-===== Bundle Hash = Blinded TxID of M6 =====
246+A bundle will either: pay all its withdrawals out (via M6), or else it fails (and pays nothing out for anyone).
0A bundle will either pay all its withdrawals out (via M6), or fail (and pay nothing out for anyone).
115@@ -118,11 +116,11 @@ D1 is a list of active sidechains. D1 is updated via M1 and M2.
116
117 ==== D2 (The Withdrawal List) ====
118
119-D2 lists withdrawal-attempts. If these attempts succeed, they will pay coins "from" a Bip300-locked UTXO, to new UTXOs controlled by the withdrawing-user. Each attempt pays out many users, so we call these withdrawal-attempts "Bundles".
120+Withdrawals are transactions that remove coins "from" L2 (ie, from the BIP-300 locked UTXO), and place them back on L1. Each BIP-300 withdrawal can pay out up to 6,000 withdrawals, and only one withdrawal can succeed at a time (per L2). Therefore, since all L2 users share the same large withdrawal-event, on L1 we call these withdrawals "bundles".
As you are defining the term “Bundle” here, perhaps mention it in the section title.
It looks like you are writing bundles (and other terms) as proper nouns (“Bundle”) in much of the rest of the document – though not consistently. Consider perhaps aligning them.
355+
356+==== Saving Space ====
357+
358+The version number allows us to shrink the upvote vector in many cases.
359+Version 0x00 omits the upvote vector entirely (ie, 6 bytes for the whole M4) and sets this block's M4 equal to the previous block's M4.
360+Version 0x01 uses one byte per sidechain, and can be used while all ACKed withdrawals have an index under 256 (ie, 99.99%+ of the time).
maybe clearer not to write out some numbers but not others
“under” is similar to “number”
0Version 0x01 uses 1 byte per sidechain, and can be used while all ACKed withdrawals have an index less than 256 (ie, 99.99%+ of the time).
356+==== Saving Space ====
357+
358+The version number allows us to shrink the upvote vector in many cases.
359+Version 0x00 omits the upvote vector entirely (ie, 6 bytes for the whole M4) and sets this block's M4 equal to the previous block's M4.
360+Version 0x01 uses one byte per sidechain, and can be used while all ACKed withdrawals have an index under 256 (ie, 99.99%+ of the time).
361+Version 0x02 uses a full two bytes per sidechain (each encoded in little endian), but it always works no matter how many withdrawal proposals exist.
0Version 0x02 uses a full 2 bytes per sidechain (each encoded in little endian), but it always works no matter how many withdrawal proposals exist.
31@@ -32,9 +32,9 @@ However, traditional MM has two drawbacks:
32
33 ==Notation and Example==
34
35-Note: We use notation side:\* and main:\* in front of otherwise-ambiguous words (such as "block", "node", or "chain"), to sort the mainchain version from its sidechain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
36+We use notation side:\* and main:\* in front of otherwise-ambiguous words (such as "block", "node", or "chain"), to sort the mainchain version from its sidechain/alt-chain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
0We use notation side:\* and main:\* in front of otherwise ambiguous words (such as "block", "node", or "chain"), to distinguish the mainchain version from its sidechain/alt-chain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
31@@ -32,9 +32,9 @@ However, traditional MM has two drawbacks:
32
33 ==Notation and Example==
34
35-Note: We use notation side:\* and main:\* in front of otherwise-ambiguous words (such as "block", "node", or "chain"), to sort the mainchain version from its sidechain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
36+We use notation side:\* and main:\* in front of otherwise-ambiguous words (such as "block", "node", or "chain"), to sort the mainchain version from its sidechain/alt-chain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
37
38-Example: imagine that a sidechain block contains 20,000 txns, each paying a $0.10 fee; therefore, the block is worth $2000 of fee-revenue. As usual: the sidechain's coinbase txn will pay this $2000 to someone (in this case, "Simon"). Under Bip301, Simon does no hashing, but instead makes one layer1 txn paying $1999 to the layer1 miners ("Mary").
39+Furthermore, here is an example of BIP-301 in use. Imagine that a side:block contains 20,000 txns, each paying a $0.10 fee; therefore, the side:block is worth $2000 of fee-revenue. In BIP-301 sidechain's coinbase txn will pay this $2000 to "Simon". Simon does no hashing, but instead makes one layer1 txn paying $1999 to the layer1 miners ("Mary"). Thus, Mary ends up with all of the fee-revenue, even though she didn't do anything on the sidechain.
0Furthermore, here is an example of BIP-301 in use. Imagine that a side:block contains 20,000 txns, each paying a $0.10 fee; therefore, the side:block is worth $2000 of fee revenue. In BIP-301, the sidechain's coinbase transaction will pay this $2000 to "Simon". Simon does no hashing, but instead makes one L1 transaction paying $1999 to the L1 miners ("Mary"). Thus, Mary ends up with all of the fee revenue, even though she didn't do anything on the sidechain.
79 ==Specification==
80
81+Each candidate for next side:block is defined by its unique side:blockhash "h*". (Even if the entire rest of the L2 block is identical, different Simons will have different side:coinbases and therefore different h*.)
82
83-Bip301 consists of two messages: "BMM Accept" and "BMM Request". These govern something called "h*".
84+Bip301 consists of two messages: "BMM Accept" and "BMM Request".
0BIP-301 consists of two messages: "BMM Accept" and "BMM Request".
161 This txn is invalid if it fails any of the following checks:
162
163 # Each "BMM Request", must match one corresponding "BMM Accept" (previous section).
164-# Only one BMM Request is allowed in each main:block, per sidechain. In other words, if 700 users broadcast BMM Requests for sidechain #4, then the main:miner singles out one BMM Request to include.
165-# The 4-bytes of prevMainHeaderBytes must match the last four bytes of the previous main:blockheader. Thus, Simon's txns are only valid for the current block, in the block history that he knows about (and therefore, the current sidechain history that he knows about).
166+# Only one BMM Request is allowed in each main:block, per nSidechain. In other words, if 700 users broadcast BMM Requests for sidechain #4, then the main:miner must singles out one BMM_Request_4 to include in this L1 block.
0# Only one BMM Request is allowed in each main:block, per nSidechain. In other words, if 700 users broadcast BMM Requests for sidechain [#4](/bitcoin-bips/4/), then the main:miner must single out one BMM_Request_4 to include in this L1 block.
162
163 # Each "BMM Request", must match one corresponding "BMM Accept" (previous section).
164-# Only one BMM Request is allowed in each main:block, per sidechain. In other words, if 700 users broadcast BMM Requests for sidechain #4, then the main:miner singles out one BMM Request to include.
165-# The 4-bytes of prevMainHeaderBytes must match the last four bytes of the previous main:blockheader. Thus, Simon's txns are only valid for the current block, in the block history that he knows about (and therefore, the current sidechain history that he knows about).
166+# Only one BMM Request is allowed in each main:block, per nSidechain. In other words, if 700 users broadcast BMM Requests for sidechain #4, then the main:miner must singles out one BMM_Request_4 to include in this L1 block.
167+# The 32-bytes of prevMainBlock must match the previous main:blockhash. Thus, Simon's txns are only valid for the current block, in the block history that he knows about.
0# The 32 bytes of prevMainBlock must match the previous main:blockhash. Thus, Simon's transactions are only valid for the current block, in the block history that he knows about.
166+# Only one BMM Request is allowed in each main:block, per nSidechain. In other words, if 700 users broadcast BMM Requests for sidechain #4, then the main:miner must singles out one BMM_Request_4 to include in this L1 block.
167+# The 32-bytes of prevMainBlock must match the previous main:blockhash. Thus, Simon's txns are only valid for the current block, in the block history that he knows about.
168
169
170-Most BMM Request txns will never make it into a block. Simon will make many BMM Requests, but only one will be accepted. Since only one BMM Request can become a bona fide transaction, Simon may feel comfortable making multiple offers all day long. This means Mary has many offers to choose from, and can choose the one which pays her the most.
171+Most BMM Request txns will never make it into a block. Simon will make many BMM Requests, but only one will be accepted. Since only one BMM Request can enter the L1 block, Simon may feel comfortable making multiple offers all day long. This means Mary has many offers to choose from, and can choose the one which pays her the most.
0Most BMM Request txns will never make it into a block. Simon will make many BMM Requests, but only one will be accepted. Since only one BMM Request can enter the L1 block, Simon may feel comfortable making multiple offers all day long. This means Mary has many offers to choose from and can choose the one that pays her the most.
The changes appear globally good. Didn’t do a technical review but rather a first editor pass.
In general, it may be nice to write “transaction” in lieu of “txn”, and possibly define the L1 and L2 abbreviations near the top of each BIP to clarify if they refer to their usual meaning or a different one in this context.
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
123
124-# The Bundles have a canonical order (first come first serve).
125+# The database has a canonical order (first come first serve).
126 # From one block to the next, every "Blocks Remaining" field decreases by 1.
127-# When "Blocks Remaining" reaches zero the Bundle is removed.
128+# When "Blocks Remaining" reaches zero the bundle is removed.
like the other steps in this list
0# When "Blocks Remaining" reaches zero, the bundle is removed.
31@@ -32,9 +32,9 @@ However, traditional MM has two drawbacks:
32
33 ==Notation and Example==
34
35-Note: We use notation side:\* and main:\* in front of otherwise-ambiguous words (such as "block", "node", or "chain"), to sort the mainchain version from its sidechain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
36+We use notation side:\* and main:\* in front of otherwise ambiguous words (such as "block", "node", or "chain"), to distinguish the mainchain version from its sidechain/alt-chain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
37
38-Example: imagine that a sidechain block contains 20,000 txns, each paying a $0.10 fee; therefore, the block is worth $2000 of fee-revenue. As usual: the sidechain's coinbase txn will pay this $2000 to someone (in this case, "Simon"). Under Bip301, Simon does no hashing, but instead makes one layer1 txn paying $1999 to the layer1 miners ("Mary").
39+Furthermore, here is an example of BIP-301 in use. Imagine that a side:block contains 20,000 txns, each paying a $0.10 fee; therefore, the side:block is worth $2000 of fee revenue. In BIP-301, the sidechain's coinbase txn will pay this $2000 to "Simon". Simon does no hashing, but instead makes one L1 txn paying $1999 to the L1 miners ("Mary"). Thus, Mary ends up with all of the fee-revenue, even though she didn't do anything on the sidechain.
0Furthermore, here is an example of BIP-301 in use. Imagine that a side:block contains 20,000 txns, each paying a $0.10 fee; therefore, the side:block is worth $2000 of fee revenue. In BIP-301, the sidechain's coinbase txn will pay this $2000 to "Simon". Simon does no hashing, but instead makes one L1 txn paying $1999 to the L1 miners ("Mary"). Thus, Mary ends up with all of the fee revenue, even though she didn't do anything on the sidechain.
235
236-Sidechain full nodes aggregate the withdrawal-requests into a big set. The sidechain calculates what M6 would have to look like, to pay all of these withdrawal-requests out. Finally, the sidechain calculates what the hash of this M6 would be. This 32-byte hash identifies the Bundle.
237+=== Withdrawing in Bundles ===
238
239-This 32-byte hash is what miners will be slowly ACKing over 3-6 months, not the M6 itself (nor any sidechain data, of course).
240+Sidechain withdrawals take the form of "bundles" -- named because they "bundle up" many individual withdrawal-requests into a single rare layer1 transaction.
maybe for consistency
0Sidechain withdrawals take the form of "bundles" -- named because they "bundle up" many individual withdrawal-requests into a single rare L1 transaction.
238
239-This 32-byte hash is what miners will be slowly ACKing over 3-6 months, not the M6 itself (nor any sidechain data, of course).
240+Sidechain withdrawals take the form of "bundles" -- named because they "bundle up" many individual withdrawal-requests into a single rare layer1 transaction.
241
242-A bundle either pays all its withdrawals out (via M6), or else it fails (and pays nothing out).
243+On the L2 side, individual withdrawal requests are periodically combined into a single CoinJoin-like withdrawal bundle. This bundle is hashed [https://github.com/LayerTwo-Labs/bip300301_messages/blob/master/src/lib.rs#L374C1-L419C2 in a particular way] (on both L2 and L1) -- this "blinded hash" commits to its own L1 fee, but (notably) it does not commit to its first tx-input (in that way, it is like [https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki SIGHASH_ANYPREVOUT]).
(maybe use a name that won’t potentially change, feel free to ignore)
0On the L2 side, individual withdrawal requests are periodically combined into a single CoinJoin-like withdrawal bundle. This bundle is hashed [https://github.com/LayerTwo-Labs/bip300301_messages/blob/master/src/lib.rs#L374C1-L419C2 in a particular way] (on both L2 and L1) -- this "blinded hash" commits to its own L1 fee, but (notably) it does not commit to its first tx-input (in that way, it is like [https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki BIP-118]).
14@@ -15,9 +15,9 @@
15
16 ==Abstract==
17
18-Blind Merged Mining (BMM) allows miners to mine a Sidechain/Altcoin, without running its node software (ie, without "looking" at it, hence "blind").
19+Blind Merged Mining (BMM) allows SHA-256d miners to collect transaction fee revenue from other blockchains, without running any new software (ie, without "looking" at those alt-chains, hence "blind").
0Blind Merged Mining (BMM) allows SHA-256d miners to collect transaction fee revenue from other blockchains, without running any new software (i.e., without "looking" at those alt-chains, hence "blind").
14@@ -15,9 +15,9 @@
15
16 ==Abstract==
17
18-Blind Merged Mining (BMM) allows miners to mine a Sidechain/Altcoin, without running its node software (ie, without "looking" at it, hence "blind").
19+Blind Merged Mining (BMM) allows SHA-256d miners to collect transaction fee revenue from other blockchains, without running any new software (ie, without "looking" at those alt-chains, hence "blind").
20
21-Instead, a separate sidechain user runs their node and constructs the block, paying himself the transaction fees. He then uses an equivalent amount of money to "buy" the right to find this block, from the conventional layer1 Sha256d miners.
22+Instead, this block-assembly work is done by alt-chain users. They choose the alt-chain block, and what txns go in it, the fees etc. Simultaneously, these users "bid" on L1, to win the right to be the sole creator of the alt-chain block. BIP-301 ensures that L1 miners only accept one bid (per 10 minutes, per L2 category), instead of taking all of them (which is what they would ordinarily do).
0Instead, this block-assembly work is done by alt-chain users. They choose the alt-chain block, and what txns go in it, the fees etc. Simultaneously, these users "bid" on L1 to win the right to be the sole creator of the alt-chain block. BIP-301 ensures that L1 miners only accept one bid (per 10 minutes, per L2 category), instead of taking all of them (which is what they would ordinarily do).
208@@ -219,46 +209,32 @@ Otherwise:
209
210 A sidechain fails to activate if:
211
212-* If the slot is unused: during the next 2016 blocks, it accumulates 201 fails. (Ie, 90% threshold).
213-* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (Ie, 50% threshold).
214+* If the slot is unused: during the next 2016 blocks, it accumulates 1008 fails. (i.e., 50% hashrate threshold).
215+* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (i.e., 50% hashrate threshold).
0* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails (i.e., 50% hashrate threshold).
208@@ -219,46 +209,32 @@ Otherwise:
209
210 A sidechain fails to activate if:
211
212-* If the slot is unused: during the next 2016 blocks, it accumulates 201 fails. (Ie, 90% threshold).
213-* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (Ie, 50% threshold).
214+* If the slot is unused: during the next 2016 blocks, it accumulates 1008 fails. (i.e., 50% hashrate threshold).
0* If the slot is unused: during the next 2016 blocks, it accumulates 1008 fails (i.e., 50% hashrate threshold).
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Co-authored-by: Jon Atack <jon@atack.com>
Ok those were also good changes
let me know if you’d like to have this merged as-is
I think merge it, whenever is convenient for you