Fix TransactionStatusMeta breakage in blockstore (#12587)

* Add helper to facilitate deserializing legacy structs

* Use default_on_eof to fix blockstore vis-a-vis TransactionStatusMeta

* Add should-panic test and comments
This commit is contained in:
Tyera Eulberg 2020-09-30 11:49:35 -06:00 committed by GitHub
parent fce3c70b72
commit 865d01c38d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 134 additions and 0 deletions

View File

@ -0,0 +1,131 @@
use serde::{Deserialize, Deserializer};
/// This helper function enables successful deserialization of versioned structs; new structs may
/// include additional fields if they impl Default and are added to the end of the struct. Right
/// now, this function is targeted at `bincode` deserialization; the error match may need to be
/// updated if another package needs to be used in the future.
pub fn default_on_eof<'de, T, D>(d: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
let result = T::deserialize(d);
match result {
Err(err) if err.to_string() == "io error: unexpected end of file" => Ok(T::default()),
result => result,
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use bincode::deserialize;
#[test]
fn test_default_on_eof() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Foo {
bar: u16,
#[serde(deserialize_with = "default_on_eof")]
baz: Option<u16>,
#[serde(deserialize_with = "default_on_eof")]
quz: String,
}
let data = vec![1, 0];
assert_eq!(
Foo {
bar: 1,
baz: None,
quz: "".to_string(),
},
deserialize(&data).unwrap()
);
let data = vec![1, 0, 0];
assert_eq!(
Foo {
bar: 1,
baz: None,
quz: "".to_string(),
},
deserialize(&data).unwrap()
);
let data = vec![1, 0, 1];
assert_eq!(
Foo {
bar: 1,
baz: None,
quz: "".to_string(),
},
deserialize(&data).unwrap()
);
let data = vec![1, 0, 1, 0];
assert_eq!(
Foo {
bar: 1,
baz: None,
quz: "".to_string(),
},
deserialize(&data).unwrap()
);
let data = vec![1, 0, 1, 0, 0, 1];
assert_eq!(
Foo {
bar: 1,
baz: Some(0),
quz: "".to_string(),
},
deserialize(&data).unwrap()
);
let data = vec![1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 116];
assert_eq!(
Foo {
bar: 1,
baz: Some(0),
quz: "t".to_string(),
},
deserialize(&data).unwrap()
);
}
#[test]
#[should_panic]
fn test_default_on_eof_additional_untagged_fields() {
// If later fields are not tagged `deserialize_with = "default_on_eof"`, deserialization
// will panic on any missing fields/data
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Foo {
bar: u16,
#[serde(deserialize_with = "default_on_eof")]
baz: Option<u16>,
quz: String,
}
// Fully populated struct will deserialize
let data = vec![1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 116];
assert_eq!(
Foo {
bar: 1,
baz: Some(0),
quz: "t".to_string(),
},
deserialize(&data).unwrap()
);
// Will panic because `quz` is missing, even though `baz` is tagged
let data = vec![1, 0, 1, 0];
assert_eq!(
Foo {
bar: 1,
baz: None,
quz: "".to_string(),
},
deserialize(&data).unwrap()
);
}
}

View File

@ -17,6 +17,7 @@ pub mod builtins;
pub mod clock;
pub mod commitment_config;
pub mod decode_error;
pub mod deserialize_utils;
pub mod entrypoint_native;
pub mod epoch_info;
pub mod epoch_schedule;

View File

@ -14,6 +14,7 @@ use crate::{
use solana_sdk::{
clock::{Slot, UnixTimestamp},
commitment_config::CommitmentConfig,
deserialize_utils::default_on_eof,
instruction::CompiledInstruction,
message::{Message, MessageHeader},
pubkey::Pubkey,
@ -141,6 +142,7 @@ pub struct TransactionStatusMeta {
pub fee: u64,
pub pre_balances: Vec<u64>,
pub post_balances: Vec<u64>,
#[serde(deserialize_with = "default_on_eof")]
pub inner_instructions: Option<Vec<InnerInstructions>>,
}