-
Notifications
You must be signed in to change notification settings - Fork 106
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
Represent coinbase inputs explicitly and download blocks. #240
Conversation
224b608
to
21b20f3
Compare
86ba2bf
to
d1006a6
Compare
Closes #236. |
Coinbase inputs are handled differently from other inputs and have different consensus rules, so they should be represented differently in the source code. This lets us discard extraneous details (for instance, it's not necessary to maintain the all-zero hash) and specialize logic.
BIP34, which is included in Zcash, encodes the block height into each block by adding it into the unused BitcoinScript field of the block's coinbase transaction. However, this is done just by requiring that the script pushes the block height onto the stack when it executes, and there are multiple different ways to push data onto the stack in BitcoinScript. Also, the genesis block does not include the block height, by accident. Because we want to *parse* transactions into an algebraic data type that encodes their structural properties, rather than allow possibly-invalid data to float through the internals of our node, we want to extract the block height upfront and store it separately from the rest of the coinbase data, which is inert. So the serialization code now contains just enough logic to parse BitcoinScript-encoded block heights, and special-case the encoding of the genesis block. Elsewhere in the source code, the `LockTime` struct requires that we must use block heights less than 500,000,000 (above which the number is interpreted as a unix timestamp, not a height). To unify invariants, we ensure that the parsing logic works with block heights up to 500,000,000, even though these are unlikely to ever be used for Zcash.
These are instead set by the negotiated version.
Bitcoin does this either with `getblocks` (returns up to 500 following block hashes) or `getheaders` (returns up to 2000 following block headers, not just hashes). However, Bitcoin headers are much smaller than Zcash headers, which contain a giant Equihash solution block, and many Zcash blocks don't have many transactions in them, so the block header is often similarly sized to the block itself. Because we're aiming to have a highly parallel network layer, it seems better to use `getblocks` to implement `FindBlocks` (which is necessarily sequential) and parallelize the processing of the block downloads.
Because we represent each transaction version as a different variant of the Transaction enum, we end up in a situation where fields that are common to different transaction versions are awkward to access, requiring a match statement with identical match arms. To fix this, this commit adds the following convenience methods: * `Transaction::inputs() -> impl Iterator<Item=&TransparentInput>`; * `Transaction::outputs() -> impl Iterator<Item=&TransparentOutput>`; * `Transaction::lock_time() -> LockTime`; * `Transaction::expiry_height() -> Option<ExpiryHeight>`; The last returns an `Option` because the field is only present in V3 and V4 transactions. There are some remaining fields that do not get common accessors, because it probably doesn't make sense to access independently of knowing the transaction version: `joinsplit_data`, `shielded_data`, `value_balance`.
(Don't check any information about them, just blindly download).
The previous outbound peer connection logic got requests to connect to new peers and processed them one at a time, making single connection attempts and retrying if the connection attempt failed. This was quite slow, because many connections fail, and we have to wait for timeouts. Instead, this logic connects to new peers concurrently (up to 50 at a time).
Rebased onto |
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.
🚢
@@ -15,6 +15,13 @@ use super::*; | |||
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270; | |||
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085; | |||
|
|||
const GENESIS_COINBASE_DATA: [u8; 77] = [ |
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.
😭
BlockHeight(data[1] as u32 + ((data[2] as u32) << 8) + ((data[3] as u32) << 16)), | ||
data.split_off(4), | ||
)), | ||
// The genesis block does not encode the block height by mistake; special case it. |
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.
👍
} | ||
|
||
fn coinbase_height_len(height: BlockHeight) -> usize { | ||
// We can't write this as a match statement on stable until exclusive range |
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.
👍
self.previous_output.zcash_serialize(&mut writer)?; | ||
self.signature_script.zcash_serialize(&mut writer)?; | ||
writer.write_u32::<LittleEndian>(self.sequence)?; | ||
match self { |
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.
👍
signature_script: Script::zcash_deserialize(&mut reader)?, | ||
sequence: reader.read_u32::<LittleEndian>()?, | ||
}) | ||
// This inlines the OutPoint deserialization to peek at the hash 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.
👍
@@ -81,6 +82,15 @@ impl Handler { | |||
Finished(Err(Arc::new(PeerError::WrongBlock).into())) | |||
} | |||
} | |||
(FindBlocks, Message::Inv(inv_hashes)) => Finished(Ok(Response::BlockHeaderHashes( |
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.
👍
@@ -317,6 +327,15 @@ where | |||
tx, | |||
) | |||
}), | |||
(AwaitingRequest, FindBlocks { known_blocks, stop }) => self |
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.
👍
// <FuturesUnordered as Stream> returns None when empty. | ||
// Keeping an unresolved future in the pool means the stream | ||
// never terminates. | ||
handshakes.push(future::pending().boxed()); |
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.
👍
} | ||
} | ||
// did a drill sergeant write this? no there's just no Either3 |
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.
🤣
@@ -6,6 +6,9 @@ | |||
|
|||
//#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] | |||
#![forbid(unsafe_code)] | |||
// Tracing causes false positives on this lint: | |||
// https://github.com/tokio-rs/tracing/issues/553 | |||
#![allow(clippy::cognitive_complexity)] |
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.
👍
Represent and parse coinbase inputs explicitly, then follow on with some code that tests this on the live network:
FindBlocks
request /BlockHeaderHashes
response;Closes #241 by incorporation.