Header version cleanup (#2809)

* introduce HeaderVersion (was u16) for type safety
cleanup pow ser/deser (version unused)

* fixup tests for HeaderVersion

* validate header version during header deserialization
This commit is contained in:
Antioch Peverell 2019-05-08 21:10:42 +01:00 committed by GitHub
parent 8d5f73e8f1
commit fabff51dd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 122 additions and 40 deletions

View file

@ -548,7 +548,7 @@ impl BlockHeaderPrintable {
pub fn from_header(header: &core::BlockHeader) -> BlockHeaderPrintable { pub fn from_header(header: &core::BlockHeader) -> BlockHeaderPrintable {
BlockHeaderPrintable { BlockHeaderPrintable {
hash: util::to_hex(header.hash().to_vec()), hash: util::to_hex(header.hash().to_vec()),
version: header.version, version: header.version.into(),
height: header.height, height: header.height,
previous: util::to_hex(header.prev_hash.to_vec()), previous: util::to_hex(header.prev_hash.to_vec()),
prev_root: util::to_hex(header.prev_root.to_vec()), prev_root: util::to_hex(header.prev_root.to_vec()),

View file

@ -102,8 +102,8 @@ pub enum ErrorKind {
#[fail(display = "Output is spent")] #[fail(display = "Output is spent")]
OutputSpent, OutputSpent,
/// Invalid block version, either a mistake or outdated software /// Invalid block version, either a mistake or outdated software
#[fail(display = "Invalid Block Version: {}", _0)] #[fail(display = "Invalid Block Version: {:?}", _0)]
InvalidBlockVersion(u16), InvalidBlockVersion(block::HeaderVersion),
/// We've been provided a bad txhashset /// We've been provided a bad txhashset
#[fail(display = "Invalid TxHashSet: {}", _0)] #[fail(display = "Invalid TxHashSet: {}", _0)]
InvalidTxHashSet(String), InvalidTxHashSet(String),

View file

@ -341,7 +341,7 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Result<(
// check version, enforces scheduled hard fork // check version, enforces scheduled hard fork
if !consensus::valid_header_version(header.height, header.version) { if !consensus::valid_header_version(header.height, header.version) {
error!( error!(
"Invalid block header version received ({}), maybe update Grin?", "Invalid block header version received ({:?}), maybe update Grin?",
header.version header.version
); );
return Err(ErrorKind::InvalidBlockVersion(header.version).into()); return Err(ErrorKind::InvalidBlockVersion(header.version).into());

View file

@ -20,6 +20,7 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use crate::core::block::HeaderVersion;
use crate::global; use crate::global;
use crate::pow::Difficulty; use crate::pow::Difficulty;
@ -128,10 +129,10 @@ pub const HARD_FORK_INTERVAL: u64 = YEAR_HEIGHT / 2;
/// Check whether the block version is valid at a given height, implements /// Check whether the block version is valid at a given height, implements
/// 6 months interval scheduled hard forks for the first 2 years. /// 6 months interval scheduled hard forks for the first 2 years.
pub fn valid_header_version(height: u64, version: u16) -> bool { pub fn valid_header_version(height: u64, version: HeaderVersion) -> bool {
// uncomment below as we go from hard fork to hard fork // uncomment below as we go from hard fork to hard fork
if height < HARD_FORK_INTERVAL { if height < HARD_FORK_INTERVAL {
version == 1 version == HeaderVersion::default()
/* } else if height < 2 * HARD_FORK_INTERVAL { /* } else if height < 2 * HARD_FORK_INTERVAL {
version == 2 version == 2
} else if height < 3 * HARD_FORK_INTERVAL { } else if height < 3 * HARD_FORK_INTERVAL {

View file

@ -22,7 +22,7 @@ use std::fmt;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::sync::Arc; use std::sync::Arc;
use crate::consensus::{reward, REWARD}; use crate::consensus::{self, reward, REWARD};
use crate::core::committed::{self, Committed}; use crate::core::committed::{self, Committed};
use crate::core::compact_block::{CompactBlock, CompactBlockBody}; use crate::core::compact_block::{CompactBlock, CompactBlockBody};
use crate::core::hash::{DefaultHashable, Hash, Hashed, ZERO_HASH}; use crate::core::hash::{DefaultHashable, Hash, Hashed, ZERO_HASH};
@ -168,11 +168,47 @@ impl Hashed for HeaderEntry {
} }
} }
/// Some type safety around header versioning.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct HeaderVersion(pub u16);
impl Default for HeaderVersion {
fn default() -> HeaderVersion {
HeaderVersion(1)
}
}
impl HeaderVersion {
/// Constructor taking the provided version.
pub fn new(version: u16) -> HeaderVersion {
HeaderVersion(version)
}
}
impl From<HeaderVersion> for u16 {
fn from(v: HeaderVersion) -> u16 {
v.0
}
}
impl Writeable for HeaderVersion {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u16(self.0)
}
}
impl Readable for HeaderVersion {
fn read(reader: &mut dyn Reader) -> Result<HeaderVersion, ser::Error> {
let version = reader.read_u16()?;
Ok(HeaderVersion(version))
}
}
/// Block header, fairly standard compared to other blockchains. /// Block header, fairly standard compared to other blockchains.
#[derive(Clone, Debug, PartialEq, Serialize)] #[derive(Clone, Debug, PartialEq, Serialize)]
pub struct BlockHeader { pub struct BlockHeader {
/// Version of the block /// Version of the block
pub version: u16, pub version: HeaderVersion,
/// Height of this block since the genesis block (height 0) /// Height of this block since the genesis block (height 0)
pub height: u64, pub height: u64,
/// Hash of the block previous to this in the chain. /// Hash of the block previous to this in the chain.
@ -203,7 +239,7 @@ impl DefaultHashable for BlockHeader {}
impl Default for BlockHeader { impl Default for BlockHeader {
fn default() -> BlockHeader { fn default() -> BlockHeader {
BlockHeader { BlockHeader {
version: 1, version: HeaderVersion::default(),
height: 0, height: 0,
timestamp: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc), timestamp: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc),
prev_hash: ZERO_HASH, prev_hash: ZERO_HASH,
@ -239,7 +275,7 @@ impl Writeable for BlockHeader {
if writer.serialization_mode() != ser::SerializationMode::Hash { if writer.serialization_mode() != ser::SerializationMode::Hash {
self.write_pre_pow(writer)?; self.write_pre_pow(writer)?;
} }
self.pow.write(self.version, writer)?; self.pow.write(writer)?;
Ok(()) Ok(())
} }
} }
@ -247,7 +283,8 @@ impl Writeable for BlockHeader {
/// Deserialization of a block header /// Deserialization of a block header
impl Readable for BlockHeader { impl Readable for BlockHeader {
fn read(reader: &mut dyn Reader) -> Result<BlockHeader, ser::Error> { fn read(reader: &mut dyn Reader) -> Result<BlockHeader, ser::Error> {
let (version, height, timestamp) = ser_multiread!(reader, read_u16, read_u64, read_i64); let version = HeaderVersion::read(reader)?;
let (height, timestamp) = ser_multiread!(reader, read_u64, read_i64);
let prev_hash = Hash::read(reader)?; let prev_hash = Hash::read(reader)?;
let prev_root = Hash::read(reader)?; let prev_root = Hash::read(reader)?;
let output_root = Hash::read(reader)?; let output_root = Hash::read(reader)?;
@ -255,7 +292,7 @@ impl Readable for BlockHeader {
let kernel_root = Hash::read(reader)?; let kernel_root = Hash::read(reader)?;
let total_kernel_offset = BlindingFactor::read(reader)?; let total_kernel_offset = BlindingFactor::read(reader)?;
let (output_mmr_size, kernel_mmr_size) = ser_multiread!(reader, read_u64, read_u64); let (output_mmr_size, kernel_mmr_size) = ser_multiread!(reader, read_u64, read_u64);
let pow = ProofOfWork::read(version, reader)?; let pow = ProofOfWork::read(reader)?;
if timestamp > MAX_DATE.and_hms(0, 0, 0).timestamp() if timestamp > MAX_DATE.and_hms(0, 0, 0).timestamp()
|| timestamp < MIN_DATE.and_hms(0, 0, 0).timestamp() || timestamp < MIN_DATE.and_hms(0, 0, 0).timestamp()
@ -263,6 +300,14 @@ impl Readable for BlockHeader {
return Err(ser::Error::CorruptedData); return Err(ser::Error::CorruptedData);
} }
// Check the block version before proceeding any further.
// We want to do this here because blocks can be pretty large
// and we want to halt processing as early as possible.
// If we receive an invalid block version then the peer is not on our hard-fork.
if !consensus::valid_header_version(height, version) {
return Err(ser::Error::InvalidBlockVersion);
}
Ok(BlockHeader { Ok(BlockHeader {
version, version,
height, height,
@ -283,9 +328,9 @@ impl Readable for BlockHeader {
impl BlockHeader { impl BlockHeader {
/// Write the pre-hash portion of the header /// Write the pre-hash portion of the header
pub fn write_pre_pow<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { pub fn write_pre_pow<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.version.write(writer)?;
ser_multiwrite!( ser_multiwrite!(
writer, writer,
[write_u16, self.version],
[write_u64, self.height], [write_u64, self.height],
[write_i64, self.timestamp.timestamp()], [write_i64, self.timestamp.timestamp()],
[write_fixed_bytes, &self.prev_hash], [write_fixed_bytes, &self.prev_hash],
@ -309,7 +354,7 @@ impl BlockHeader {
{ {
let mut writer = ser::BinWriter::new(&mut header_buf); let mut writer = ser::BinWriter::new(&mut header_buf);
self.write_pre_pow(&mut writer).unwrap(); self.write_pre_pow(&mut writer).unwrap();
self.pow.write_pre_pow(self.version, &mut writer).unwrap(); self.pow.write_pre_pow(&mut writer).unwrap();
writer.write_u64(self.pow.nonce).unwrap(); writer.write_u64(self.pow.nonce).unwrap();
} }
header_buf header_buf

View file

@ -239,9 +239,19 @@ impl Default for ProofOfWork {
} }
} }
impl ProofOfWork { impl Writeable for ProofOfWork {
/// Read implementation, can't define as trait impl as we need a version fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
pub fn read(_ver: u16, reader: &mut dyn Reader) -> Result<ProofOfWork, ser::Error> { if writer.serialization_mode() != ser::SerializationMode::Hash {
self.write_pre_pow(writer)?;
writer.write_u64(self.nonce)?;
}
self.proof.write(writer)?;
Ok(())
}
}
impl Readable for ProofOfWork {
fn read(reader: &mut dyn Reader) -> Result<ProofOfWork, ser::Error> {
let total_difficulty = Difficulty::read(reader)?; let total_difficulty = Difficulty::read(reader)?;
let secondary_scaling = reader.read_u32()?; let secondary_scaling = reader.read_u32()?;
let nonce = reader.read_u64()?; let nonce = reader.read_u64()?;
@ -253,20 +263,11 @@ impl ProofOfWork {
proof, proof,
}) })
} }
}
/// Write implementation, can't define as trait impl as we need a version impl ProofOfWork {
pub fn write<W: Writer>(&self, ver: u16, writer: &mut W) -> Result<(), ser::Error> {
if writer.serialization_mode() != ser::SerializationMode::Hash {
self.write_pre_pow(ver, writer)?;
writer.write_u64(self.nonce)?;
}
self.proof.write(writer)?;
Ok(())
}
/// Write the pre-hash portion of the header /// Write the pre-hash portion of the header
pub fn write_pre_pow<W: Writer>(&self, _ver: u16, writer: &mut W) -> Result<(), ser::Error> { pub fn write_pre_pow<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
ser_multiwrite!( ser_multiwrite!(
writer, writer,
[write_u64, self.total_difficulty.to_num()], [write_u64, self.total_difficulty.to_num()],

View file

@ -65,6 +65,8 @@ pub enum Error {
SortError, SortError,
/// Inputs/outputs/kernels must be unique. /// Inputs/outputs/kernels must be unique.
DuplicateError, DuplicateError,
/// Block header version (hard-fork schedule).
InvalidBlockVersion,
} }
impl From<io::Error> for Error { impl From<io::Error> for Error {
@ -87,6 +89,7 @@ impl fmt::Display for Error {
Error::DuplicateError => f.write_str("duplicate"), Error::DuplicateError => f.write_str("duplicate"),
Error::TooLargeReadErr => f.write_str("too large read"), Error::TooLargeReadErr => f.write_str("too large read"),
Error::HexError(ref e) => write!(f, "hex error {:?}", e), Error::HexError(ref e) => write!(f, "hex error {:?}", e),
Error::InvalidBlockVersion => f.write_str("invalid block version"),
} }
} }
} }
@ -109,6 +112,7 @@ impl error::Error for Error {
Error::DuplicateError => "duplicate error", Error::DuplicateError => "duplicate error",
Error::TooLargeReadErr => "too large read", Error::TooLargeReadErr => "too large read",
Error::HexError(_) => "hex error", Error::HexError(_) => "hex error",
Error::InvalidBlockVersion => "invalid block version",
} }
} }
} }

View file

@ -21,7 +21,9 @@ use crate::core::core::id::ShortIdentifiable;
use crate::core::core::transaction::{self, Transaction}; use crate::core::core::transaction::{self, Transaction};
use crate::core::core::verifier_cache::{LruVerifierCache, VerifierCache}; use crate::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
use crate::core::core::Committed; use crate::core::core::Committed;
use crate::core::core::{Block, BlockHeader, CompactBlock, KernelFeatures, OutputFeatures}; use crate::core::core::{
Block, BlockHeader, CompactBlock, HeaderVersion, KernelFeatures, OutputFeatures,
};
use crate::core::libtx::build::{self, input, output, with_fee}; use crate::core::libtx::build::{self, input, output, with_fee};
use crate::core::{global, ser}; use crate::core::{global, ser};
use crate::keychain::{BlindingFactor, ExtKeychain, Keychain}; use crate::keychain::{BlindingFactor, ExtKeychain, Keychain};
@ -198,6 +200,23 @@ fn remove_coinbase_kernel_flag() {
); );
} }
#[test]
fn serialize_deserialize_header_version() {
let mut vec1 = Vec::new();
ser::serialize(&mut vec1, &1_u16).expect("serialization failed");
let mut vec2 = Vec::new();
ser::serialize(&mut vec2, &HeaderVersion::default()).expect("serialization failed");
// Check that a header_version serializes to a
// single u16 value with no extraneous bytes wrapping it.
assert_eq!(vec1, vec2);
// Check we can successfully deserialize a header_version.
let version: HeaderVersion = ser::deserialize(&mut &vec2[..]).unwrap();
assert_eq!(version.0, 1)
}
#[test] #[test]
fn serialize_deserialize_block_header() { fn serialize_deserialize_block_header() {
let keychain = ExtKeychain::from_random_seed(false).unwrap(); let keychain = ExtKeychain::from_random_seed(false).unwrap();

View file

@ -15,6 +15,7 @@
use grin_core as core; use grin_core as core;
use self::core::consensus::*; use self::core::consensus::*;
use self::core::core::block::HeaderVersion;
use self::core::global; use self::core::global;
use self::core::pow::Difficulty; use self::core::pow::Difficulty;
use chrono::prelude::Utc; use chrono::prelude::Utc;
@ -617,15 +618,27 @@ fn test_secondary_pow_scale() {
#[test] #[test]
fn hard_forks() { fn hard_forks() {
assert!(valid_header_version(0, 1)); assert!(valid_header_version(0, HeaderVersion::new(1)));
assert!(valid_header_version(10, 1)); assert!(valid_header_version(10, HeaderVersion::new(1)));
assert!(!valid_header_version(10, 2)); assert!(!valid_header_version(10, HeaderVersion::new(2)));
assert!(valid_header_version(YEAR_HEIGHT / 2 - 1, 1)); assert!(valid_header_version(
YEAR_HEIGHT / 2 - 1,
HeaderVersion::new(1)
));
// v2 not active yet // v2 not active yet
assert!(!valid_header_version(YEAR_HEIGHT / 2, 2)); assert!(!valid_header_version(
assert!(!valid_header_version(YEAR_HEIGHT / 2, 1)); YEAR_HEIGHT / 2,
assert!(!valid_header_version(YEAR_HEIGHT, 1)); HeaderVersion::new(2)
assert!(!valid_header_version(YEAR_HEIGHT / 2 + 1, 2)); ));
assert!(!valid_header_version(
YEAR_HEIGHT / 2,
HeaderVersion::new(1)
));
assert!(!valid_header_version(YEAR_HEIGHT, HeaderVersion::new(1)));
assert!(!valid_header_version(
YEAR_HEIGHT / 2 + 1,
HeaderVersion::new(2)
));
} }
// #[test] // #[test]

View file

@ -20,7 +20,6 @@ use self::core::ser::{FixedLength, PMMRable, Readable, Reader, Writeable, Writer
use croaring; use croaring;
use croaring::Bitmap; use croaring::Bitmap;
use grin_core as core; use grin_core as core;
use std::path::Path;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct TestElem(pub [u32; 4]); pub struct TestElem(pub [u32; 4]);

View file

@ -333,7 +333,7 @@ impl Handler {
{ {
let mut writer = ser::BinWriter::new(&mut header_buf); let mut writer = ser::BinWriter::new(&mut header_buf);
bh.write_pre_pow(&mut writer).unwrap(); bh.write_pre_pow(&mut writer).unwrap();
bh.pow.write_pre_pow(bh.version, &mut writer).unwrap(); bh.pow.write_pre_pow(&mut writer).unwrap();
} }
let pre_pow = util::to_hex(header_buf); let pre_pow = util::to_hex(header_buf);
let job_template = JobTemplate { let job_template = JobTemplate {