Add API version headers and map_fork_name! (#2745)

## Proposed Changes

* Add the `Eth-Consensus-Version` header to the HTTP API for the block and state endpoints. This is part of the v2.1.0 API that was recently released: https://github.com/ethereum/beacon-APIs/pull/170
* Add tests for the above. I refactored the `eth2` crate's helper functions to make this more straight-forward, and introduced some new mixin traits that I think greatly improve readability and flexibility.
* Add a new `map_with_fork!` macro which is useful for decoding a superstruct type without naming all its variants. It is now used for SSZ-decoding `BeaconBlock` and `BeaconState`, and for JSON-decoding `SignedBeaconBlock` in the API.

## Additional Info

The `map_with_fork!` changes will conflict with the Merge changes, but when resolving the conflict the changes from this branch should be preferred (it is no longer necessary to enumerate every fork). The merge fork _will_  need to be added to `map_fork_name_with`.
This commit is contained in:
Michael Sproul
2021-10-28 01:18:04 +00:00
parent 8edd9d45ab
commit 2dc6163043
10 changed files with 295 additions and 146 deletions

View File

@@ -81,16 +81,13 @@ impl<T: EthSpec> BeaconBlock<T> {
})?;
let slot = Slot::from_ssz_bytes(slot_bytes)?;
let epoch = slot.epoch(T::slots_per_epoch());
let fork_at_slot = spec.fork_name_at_slot::<T>(slot);
if spec
.altair_fork_epoch
.map_or(true, |altair_epoch| epoch < altair_epoch)
{
BeaconBlockBase::from_ssz_bytes(bytes).map(Self::Base)
} else {
BeaconBlockAltair::from_ssz_bytes(bytes).map(Self::Altair)
}
Ok(map_fork_name!(
fork_at_slot,
Self,
<_>::from_ssz_bytes(bytes)?
))
}
/// Try decoding each beacon block variant in sequence.

View File

@@ -411,16 +411,13 @@ impl<T: EthSpec> BeaconState<T> {
})?;
let slot = Slot::from_ssz_bytes(slot_bytes)?;
let epoch = slot.epoch(T::slots_per_epoch());
let fork_at_slot = spec.fork_name_at_slot::<T>(slot);
if spec
.altair_fork_epoch
.map_or(true, |altair_epoch| epoch < altair_epoch)
{
BeaconStateBase::from_ssz_bytes(bytes).map(Self::Base)
} else {
BeaconStateAltair::from_ssz_bytes(bytes).map(Self::Altair)
}
Ok(map_fork_name!(
fork_at_slot,
Self,
<_>::from_ssz_bytes(bytes)?
))
}
/// Returns the `tree_hash_root` of the state.

View File

@@ -54,6 +54,43 @@ impl ForkName {
}
}
/// Map a fork name into a fork-versioned superstruct type like `BeaconBlock`.
///
/// The `$body` expression is where the magic happens. The macro allows us to achieve polymorphism
/// in the return type, which is not usually possible in Rust without trait objects.
///
/// E.g. you could call `map_fork_name!(fork, BeaconBlock, serde_json::from_str(s))` to decode
/// different `BeaconBlock` variants depending on the value of `fork`. Note how the type of the body
/// will change between `BeaconBlockBase` and `BeaconBlockAltair` depending on which branch is
/// taken, the important thing is that they are re-unified by injecting them back into the
/// `BeaconBlock` parent enum.
///
/// If you would also like to extract additional data alongside the superstruct type, use
/// the more flexible `map_fork_name_with` macro.
#[macro_export]
macro_rules! map_fork_name {
($fork_name:expr, $t:tt, $body:expr) => {
map_fork_name_with!($fork_name, $t, { ($body, ()) }).0
};
}
/// Map a fork name into a tuple of `(t, extra)` where `t` is a superstruct type.
#[macro_export]
macro_rules! map_fork_name_with {
($fork_name:expr, $t:tt, $body:block) => {
match $fork_name {
ForkName::Base => {
let (value, extra_data) = $body;
($t::Base(value), extra_data)
}
ForkName::Altair => {
let (value, extra_data) = $body;
($t::Altair(value), extra_data)
}
}
};
}
impl FromStr for ForkName {
type Err = ();