-
Notifications
You must be signed in to change notification settings - Fork 41
add BIP141 conventions for serialized coinbase #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add BIP141 conventions for serialized coinbase #153
Conversation
GitGab19
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK
|
My SegWit knowledge is a bit rusty, but IIRC the coinbase transaction doesn't need to have a witness, and typically doesn't. It doesn't matter what the outputs are, i.e. paying to a segwit address doesn't make it a segwit transaction. But if a pool wants to add a witness for some reason, then it becomes a witness transaction and they need to correctly serialize it. Could be worth elaborating a bit on this in the text. |
hmm, I'd say that's not correct. If there are SegWit transactions in the block, the Coinbase transaction does need to have a witness (or the so called "witness reserved value"). BIP141 has a section called
The commitment hash takes the Coinbase's Sure, you could argue this is TIPICALLY BIP141 also has a section called
which means this field could eventually carry something different than
correct. what forces the Coinbase to be a SegWit transaction (with marker and flag, which are also relevant on this discussion, aside from a potentially-zero'ed witness) is the fact that there are SegWit transactions in the block, which forces the Coinbase to carry a witness commitment, which in turn forces the Coinbase to have a witness (even if it's zero'ed). we can only get away with a non-SegWit Coinbase if all transactions in the block do not have witness data (or the block is empty), in which case the commitment is optional. |
fab10bb to
847eb54
Compare
847eb54 to
4ddd08e
Compare
|
I see where my confusion came from. I didn't realize that setting the coinbase witness (to some 32 byte value) was already mandatory (for SegWit blocks). I thought it was just a proposed hack to deal with BIP30 / BIP34. Found the relevant code in Bitcoin Core: In /** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */
void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const;In void ChainstateManager::UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const
{
int commitpos = GetWitnessCommitmentIndex(block);
static const std::vector<unsigned char> nonce(32, 0x00);
if (commitpos != NO_WITNESS_COMMITMENT && DeploymentActiveAfter(pindexPrev, *this, Consensus::DEPLOYMENT_SEGWIT) && !block.vtx[0]->HasWitness()) {
CMutableTransaction tx(*block.vtx[0]);
tx.vin[0].scriptWitness.stack.resize(1);
tx.vin[0].scriptWitness.stack[0] = nonce;
block.vtx[0] = MakeTransactionRef(std::move(tx));
}
}This is what sets the coinbase witness to My confusion came from an earlier discussion about using the witness commitment to fix BIP30 / BIP34: https://delvingbitcoin.org/t/great-consensus-cleanup-revival/710:
The new consensus rule would not require One practical way to achieve that is for Bitcoin Core to always set Eventually BIP54 picked a different approach to deal with BIP30 / BIP34, so I didn't think about this anymore. Interestingly there was an earlier discussion on the Sv2 spec to add this value to the |
|
|
||
| [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) introduced the notion of Segregated Witness (SegWit) into the Bitcoin protocol. | ||
|
|
||
| This deserves some consideration in the Stratum V2 protocol design, mainly because this affects how the Coinbase transaction is serialized across different messages. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So maybe add:
For a block that contains any SegWit transactions (in practice almost any non-empty block), the coinbase transaction MUST have a witness as well as an OP_RETURN output.
The
OP_RETURNoutput is provided by Template Provider in thecoinbase_tx_outputsfield.The coinbase witness is not provided by the Template Provider. Although today it's typically
0x00..00, no assumption should be made about this value. The only consensus requirement is that it is a 32 byte push, but future consensus rules may require a different value.For a block that does not contain any SegWit transactions, the coinbase transaction generated by current versions of Bitcoin Core won't have a witness. But that's not a consensus rule. Future versions and other node implements MAY set a witness anyway.
In other words, if the Template Provider provides an empty block, it is not safe to assume its coinbase transaction doesn't have a witness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OP_RETURN output is provided by Template Provider in the coinbase_tx_outputs field.
Although today it's typically 0x00..00, no assumption should be made about this value.
These two sentences are conflicting.
TP provides an OP_RETURN with a commitment hash that's calculated with a Coinbase witness as part of its pre-image. So whoever is downstream to TP is pretty much forced to abide by the Coinbase witness that TP chose, despite the fact that this is not really communicated anywhere on Sv2 TDP.
Unfortunately, there is a NewTemplate.coinbase_tx_outputs (where OP_RETURN with witness commitments is sent), but there's no NewTemplate.coinbase_witness or alike.
As expected, our current TP implementation does it with a 0x00..00 witness.
I validated this empirically, by trying to propagate a block with a witness commitment sent by our current TP and a Coinbase witness different than 0x00..00. The block was rejected by its peers.
So either:
- A: TP should stop providing the
OP_RETURNwith the witness commitment, leaving it for the receiving end to decide which Coinbase witness they want to use and calculate a witness commitment accordingly
OR - B: we establish some convention that says it's always
0x00..00until some soft-fork changes it into consensus-critical
Option A would pretty much render NewTemplate.coinbase_tx_outputs useless. Or at least, AFAIK, sending Witness commitment is the only meaningful purpose for this field.
Option B would force a spec upgrade if-and-when a soft-fork changes things with regards to Coinbase's witness reserved value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option B would force a spec upgrade if-and-when a soft-fork changes things with regards to Coinbase's witness reserved value.
This would be a disaster, because it's hard enough to get miners to upgrade their node software, let alone their entire sv2 stack.
It sounds like closing #15 was a mistake?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As expected, our current TP implementation does it with a
0x00..00witness.
I'm a bit confused about what you mean here. You're referring to the TP I implemented (sv2-tp and its bitcoind -sv2 predecessor), right? This lets Bitcoin Core generate a block template. The coinbase it uses for that is never communicated, but it indeed uses 0x00..00 and the OP_RETURN value reflects that.
I validated this empirically, by trying to propagate a block with a witness commitment sent by our current TP and a Coinbase witness different than
0x00..00. The block was rejected by its peers.
For SubmitSolution the coinbase has to be reconstructed by other SRI roles. The Template Provider just passes it along. Are you saying that if these other roles pick a different value than 0x00..00 then the result is broken? That would make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like closing #15 was a mistake?
According to my understanding, yes. Or at least as far as we expect:
- the Template Provider to be the one who calculates the Witness Commitment
- Sv2 to be reasonably future-proof with regards to soft-forks
When it comes to who's responsible for calculating the Witness Commitment, an alternative option would be to shift this responsibility to the Sv2 role who receives NewTemplate. The tradeoffs would be:
- this would delay creation of jobs, since:
- a
RequestTransactionData/.Successround-trip would be necessary - we'd have to wait for the calculation of the Witness Root
- a
- we don't know what kind of consensus-critical data would have to be committed into
witness reserved valueon this hypothetical soft fork... does the Sv2 role have all the necessary data to make that decision, or is that something that only a Bitcoin Full Node could do?
So having TP be responsible for calculating the Witness Commitment feels like a sensible design decision.
But as long as NewTemplate carries no information about Coinbase witness (which was proposed and rejected on #15), the Sv2 role that receives this message and wants to propagate a block later has no option but to ASSUME a value for Coinbase witness. And today we live with this "implied convention" of using 0x000...000, which is an intuitively sensible choice (but really could have been anything, given that this field is not consensus-critical).
It's kinda sad to realize that the protocol is not future-proof on this regard, especially given that this could have been solved with just one extra field on NewTemplate, as #15 proposed.
However, I must also point out that the purpose of this current PR is not to advocate for spec changes, but rather to simply establish conventions that avoids confusion and enables smooth interoperability with what the Bitcoin protocol consensus looks like today. So I'm pretty ok with saying "just use 0x000...000 for witness reserved value" as an official convention and leaving future/hypothetical problems for the future.
In case protocol stakeholders (@jakubtrnka @Fi3 @TheBlueMatt) feel this deserves a discussion for spec change, we can do it on a separate issue/PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few words suggested by @Sjors #153 (comment)
but not all
that's because I feel this topic about future implications of Coinbase witness is derailing into a discussion on its own, and as stated above I'd like to keep the scope of this PR constrained to conventions around serialization without diving into potential spec changes
so I crated #156 as a place where we can discuss about it more freely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think it's worth mentioning that the witness commitment is (currently) assumed to be zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean the Coinbase witness (currently aka witness reserved value) is assumed to be zero, right?
the witness commitment is 0x6a24aa21a9ed followed by the hash of the concatenation of witness root and witness reserved value, so we don't really expect it to be zero.
anyways, just added a commit for this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean the Coinbase witness (currently aka witness reserved value)
Yes
| When concatenated with `extranonce_prefix` + `extranonce`, these fields form a serialized Coinbase transaction that is then hashed to produce a `txid`. Together with `merkle_path`, this `txid` | ||
| will then form the `merkle_root` of the Block Header. | ||
|
|
||
| In case the Template's Coinbase is a SegWit transaction, BIP141 fields MUST be stripped away from `coinbase_tx_prefix` and `coinbase_tx_suffix`, otherwise clients would be calculating a `merkle_root` with the Coinbase's `wtxid`, which goes against Bitcoin Consensus. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So IIUC NewExtendedMiningJob is the only place that needs to strip witness data. Maybe that suggests a defect in the spec? I'm not familiar with how extended mining jobs work, but isn't there a way to avoid stripping witness data still give clients the data they need to get the correct merkle_root?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, first some recap on Sv2 Standard vs Extended Jobs:
Standard Jobs have a NewMiningJob.merkle_root field. That's meant to be an innovation that makes messages smaller and puts the burden of Merkle Root calculation on the Server. There's no extranonce rolling.
Extended Jobs are closer to Sv1, in the sense that there's a coinbase_tx_prefix (akin to Sv1 Coinbase1) and coinbase_tx_suffix (akin to Sv1 Coinbase2). That's meant to allow either:
- broadcasting a single job to multiple channels
- allowing extranonce rolling
AFAIK most Sv1 servers send their mining.notify with Coinbase1 + Coinbase2 that's already stripped of BIP141 fields. Maybe some Sv1 firmware is able to detect whether that's not the case and strip it afterwards, but according to @Fi3 that's not ubiquous, so not safe to assume.
Until recently, SRI was sending and expecting NewExtendedMiningJob messages that contained BIP141 fields serialized into coinbase_tx_prefix and coinbase_tx_suffix. Until stratum-mining/stratum#1399 led us to realize that Braiins was doing Extended Jobs that follow the Sv1 pattern of stripping those.
isn't there a way to avoid stripping witness data still give clients the data they need to get the correct merkle_root
We could leave it up for the client to figure out whether NewExtendedMiningJob already stripped BIP141 or not, and stripping it before hashing. But @TheBlueMatt opposed this idea here and here (with some mixup in terminology).
I agree with you that it's a bit confusing to follow one pattern for one message and a different pattern in other messages. @Fi3 also pointed this out here, which motivated the addition of the new BIP141 section into 03-Protocol-Overview.md on this PR.
Overall, when it comes to NewExtendedMiningJob I'm not really opinionated towards doing one way or the other.
But I'm strongly opinionated that it's of upmost importance to have clear (even if somewhat counter-intuitive) conventions on the spec, which is why I'm pushing for these discussions. Not having anything is a recipe for interoperability hell, as we already started to see with stratum-mining/stratum#1399
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should have a clear distinction between Stratum v2 and Stratum v1, with the Translator role taking care of serialisation changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in fairness, the receiving end of NewExtendedMiningJob (be it Sv2 firmware or tProxy) has no purpose for BIP141 fields, given that coinbase_tx_prefix + coinbase_tx_suffix are only meant to enable calculation of the Coinbase's txid then merkle_root
so one could argue that forcing NewExtendedMiningJob to carry BIP141 would be bloating the message size unnecessarily, and we'd be sacrificing bandwidth efficiency with no real purpose other than making the spec slightly less confusing for the reader
as much as I value clarity and cleanness in the spec, this feels like a reasonable tradeoff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally think that a tiny increase in message size is worth the reduction in complexity, unless it you're at the edge of some network packet size and want to prevent roundtrips.
But for the purpose of this PR it may be better to just document things as they are now.
|
I opened https://github.com/Sjors/sv2-tp/pull/10 which adds There's absolutely no rush to do this, since there are currently no soft fork proposals that change the witness reserve value. And I can trivially keep the PR rebased. However, the switch to IPC may be a good opportunity to make a breaking change. |
|
Another way you can test the impact of the assumed witness reserved value is by patching Bitcoin Core: diff --git a/src/validation.cpp b/src/validation.cpp
index 8fcc719a68..c231899fd3 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -4079,7 +4079,7 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu
void ChainstateManager::UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const
{
int commitpos = GetWitnessCommitmentIndex(block);
- static const std::vector<unsigned char> nonce(32, 0x00);
+ static const std::vector<unsigned char> nonce(32, 0x01);
if (commitpos != NO_WITNESS_COMMITMENT && DeploymentActiveAfter(pindexPrev, *this, Consensus::DEPLOYMENT_SEGWIT) && !block.vtx[0]->HasWitness()) {
CMutableTransaction tx(*block.vtx[0]);
tx.vin[0].scriptWitness.stack.resize(1);
@@ -4092,7 +4092,7 @@ std::vector<unsigned char> ChainstateManager::GenerateCoinbaseCommitment(CBlock&
{
std::vector<unsigned char> commitment;
int commitpos = GetWitnessCommitmentIndex(block);
- std::vector<unsigned char> ret(32, 0x00);
+ std::vector<unsigned char> ret(32, 0x01);
if (commitpos == NO_WITNESS_COMMITMENT) {
uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr);
CHash256().Write(witnessroot).Write(ret).Finalize(witnessroot);This code is only used for mining, both for tests (some Bitcoin Core tests will fail because they use hardcoded block and transaction hashes on regtest) and the Mining interface that's used by the Template Provider. This should impact the block templates generated by the Template Provider. Once combined with https://github.com/Sjors/sv2-tp/pull/10 such a change should not break anything. |
|
ACK 93ca151 But I didn't study the |
e8f0531 to
fbde4a2
Compare
fbde4a2 to
7251e64
Compare
as discussed in #149