feat(primitives): Introduce MintedBlock concept (#116)
This commit is contained in:
parent
4df94f94c6
commit
fe80ff7800
48 changed files with 214 additions and 188 deletions
|
|
@ -1,10 +1,4 @@
|
||||||
use std::fmt::Debug;
|
use pallas::ledger::primitives::{alonzo, byron, probing, Era};
|
||||||
|
|
||||||
use pallas::ledger::primitives::{alonzo, byron, probing, Era, Fragment};
|
|
||||||
|
|
||||||
fn pretty_print(block: impl Debug) {
|
|
||||||
println!("{:?}", block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let blocks = vec![
|
let blocks = vec![
|
||||||
|
|
@ -16,16 +10,21 @@ fn main() {
|
||||||
];
|
];
|
||||||
|
|
||||||
for block_str in blocks.iter() {
|
for block_str in blocks.iter() {
|
||||||
let bytes = hex::decode(block_str).expect("valid hex");
|
let bytes = hex::decode(block_str).expect("invalid hex");
|
||||||
|
|
||||||
match probing::probe_block_cbor_era(&bytes) {
|
match probing::probe_block_cbor_era(&bytes) {
|
||||||
probing::Outcome::Matched(era) => match era {
|
probing::Outcome::Matched(era) => match era {
|
||||||
Era::Byron => pretty_print(byron::Block::decode_fragment(&bytes)),
|
Era::Byron => {
|
||||||
|
let (_, block): (u16, byron::MainBlock) =
|
||||||
|
pallas::codec::minicbor::decode(&bytes).expect("invalid cbor");
|
||||||
|
println!("{:?}", block)
|
||||||
|
}
|
||||||
// we use alonzo for everything post-shelly since it's backward compatible
|
// we use alonzo for everything post-shelly since it's backward compatible
|
||||||
Era::Shelley => pretty_print(alonzo::BlockWrapper::decode_fragment(&bytes)),
|
Era::Shelley | Era::Allegra | Era::Mary | Era::Alonzo => {
|
||||||
Era::Allegra => pretty_print(alonzo::BlockWrapper::decode_fragment(&bytes)),
|
let (_, block): (u16, alonzo::Block) =
|
||||||
Era::Mary => pretty_print(alonzo::BlockWrapper::decode_fragment(&bytes)),
|
pallas::codec::minicbor::decode(&bytes).expect("invalid cbor");
|
||||||
Era::Alonzo => pretty_print(alonzo::BlockWrapper::decode_fragment(&bytes)),
|
println!("{:?}", block)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => println!("couldn't infer block era"),
|
_ => println!("couldn't infer block era"),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,11 @@ impl TransactionOutput {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::alonzo::{BlockWrapper, TransactionBodyComponent};
|
use pallas_codec::minicbor;
|
||||||
use crate::Fragment;
|
|
||||||
|
use crate::alonzo::{Block, TransactionBodyComponent};
|
||||||
|
|
||||||
|
type BlockWrapper = (u16, Block);
|
||||||
|
|
||||||
const KNOWN_ADDRESSES: &[&str] =&[
|
const KNOWN_ADDRESSES: &[&str] =&[
|
||||||
"addr_test1vzzql63nddp8qdgka578hx6pats290js9kmn4uay5we9fwsgza0z3",
|
"addr_test1vzzql63nddp8qdgka578hx6pats290js9kmn4uay5we9fwsgza0z3",
|
||||||
|
|
@ -36,10 +39,10 @@ mod tests {
|
||||||
fn known_address_matches() {
|
fn known_address_matches() {
|
||||||
// TODO: expand this test to include more test blocks
|
// TODO: expand this test to include more test blocks
|
||||||
let block_idx = 1;
|
let block_idx = 1;
|
||||||
let block_str = include_str!("../test_data/alonzo2.block");
|
let block_str = include_str!("../../../test_data/alonzo2.block");
|
||||||
|
|
||||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||||
let BlockWrapper(_, block) = BlockWrapper::decode_fragment(&block_bytes[..])
|
let (_, block): BlockWrapper = minicbor::decode(&block_bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||||
|
|
||||||
// don't want to pass if we don't have tx in the block
|
// don't want to pass if we don't have tx in the block
|
||||||
|
|
|
||||||
|
|
@ -50,21 +50,24 @@ impl ToHash<32> for KeepRaw<'_, TransactionBody> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use pallas_codec::minicbor;
|
||||||
use pallas_codec::minicbor::data::Int;
|
use pallas_codec::minicbor::data::Int;
|
||||||
use pallas_codec::utils::MaybeIndefArray;
|
use pallas_codec::utils::MaybeIndefArray;
|
||||||
use pallas_crypto::hash::Hash;
|
use pallas_crypto::hash::Hash;
|
||||||
|
|
||||||
use crate::alonzo::{BigInt, BlockWrapper, Constr, NativeScript, PlutusData};
|
use crate::alonzo::{BigInt, Constr, MintedBlock, NativeScript, PlutusData};
|
||||||
use crate::{Fragment, ToHash};
|
use crate::ToHash;
|
||||||
|
|
||||||
|
type BlockWrapper<'b> = (u16, MintedBlock<'b>);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_hash_works() {
|
fn transaction_hash_works() {
|
||||||
// TODO: expand this test to include more test blocks
|
// TODO: expand this test to include more test blocks
|
||||||
let block_idx = 1;
|
let block_idx = 1;
|
||||||
let block_str = include_str!("../test_data/alonzo1.block");
|
let block_str = include_str!("../../../test_data/alonzo1.block");
|
||||||
|
|
||||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||||
let block_model = BlockWrapper::decode_fragment(&block_bytes[..])
|
let (_, block_model): BlockWrapper = minicbor::decode(&block_bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||||
|
|
||||||
let valid_hashes = vec![
|
let valid_hashes = vec![
|
||||||
|
|
@ -75,7 +78,7 @@ mod tests {
|
||||||
"8838f5ab27894a6543255aeaec086f7b3405a6db6e7457a541409cdbbf0cd474",
|
"8838f5ab27894a6543255aeaec086f7b3405a6db6e7457a541409cdbbf0cd474",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (tx_idx, tx) in block_model.1.transaction_bodies.iter().enumerate() {
|
for (tx_idx, tx) in block_model.transaction_bodies.iter().enumerate() {
|
||||||
let computed_hash = tx.to_hash();
|
let computed_hash = tx.to_hash();
|
||||||
let known_hash = valid_hashes[tx_idx];
|
let known_hash = valid_hashes[tx_idx];
|
||||||
assert_eq!(hex::encode(computed_hash), known_hash)
|
assert_eq!(hex::encode(computed_hash), known_hash)
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,17 @@ impl ToCanonicalJson for super::NativeScript {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{alonzo::BlockWrapper, Fragment, ToCanonicalJson};
|
use pallas_codec::minicbor;
|
||||||
|
|
||||||
|
use crate::{alonzo::Block, ToCanonicalJson};
|
||||||
|
|
||||||
|
type BlockWrapper = (u16, Block);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datums_serialize_as_expected() {
|
fn test_datums_serialize_as_expected() {
|
||||||
let test_blocks = vec![(
|
let test_blocks = vec![(
|
||||||
include_str!("../test_data/alonzo9.block"),
|
include_str!("../../../test_data/alonzo9.block"),
|
||||||
include_str!("../test_data/alonzo9.datums"),
|
include_str!("../../../test_data/alonzo9.datums"),
|
||||||
)];
|
)];
|
||||||
|
|
||||||
for (idx, (block_str, jsonl_str)) in test_blocks.iter().enumerate() {
|
for (idx, (block_str, jsonl_str)) in test_blocks.iter().enumerate() {
|
||||||
|
|
@ -94,7 +98,7 @@ mod tests {
|
||||||
|
|
||||||
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
||||||
|
|
||||||
let BlockWrapper(_, block) = BlockWrapper::decode_fragment(&bytes[..])
|
let (_, block): BlockWrapper = minicbor::decode(&bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", idx));
|
.expect(&format!("error decoding cbor for file {}", idx));
|
||||||
|
|
||||||
let mut datums = jsonl_str.lines();
|
let mut datums = jsonl_str.lines();
|
||||||
|
|
@ -115,8 +119,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_native_scripts_serialize_as_expected() {
|
fn test_native_scripts_serialize_as_expected() {
|
||||||
let test_blocks = vec![(
|
let test_blocks = vec![(
|
||||||
include_str!("../test_data/alonzo9.block"),
|
include_str!("../../../test_data/alonzo9.block"),
|
||||||
include_str!("../test_data/alonzo9.native"),
|
include_str!("../../../test_data/alonzo9.native"),
|
||||||
)];
|
)];
|
||||||
|
|
||||||
for (idx, (block_str, jsonl_str)) in test_blocks.iter().enumerate() {
|
for (idx, (block_str, jsonl_str)) in test_blocks.iter().enumerate() {
|
||||||
|
|
@ -124,7 +128,7 @@ mod tests {
|
||||||
|
|
||||||
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
||||||
|
|
||||||
let BlockWrapper(_, block) = BlockWrapper::decode_fragment(&bytes[..])
|
let (_, block): BlockWrapper = minicbor::decode(&bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", idx));
|
.expect(&format!("error decoding cbor for file {}", idx));
|
||||||
|
|
||||||
let mut scripts = jsonl_str.lines();
|
let mut scripts = jsonl_str.lines();
|
||||||
|
|
|
||||||
|
|
@ -1418,12 +1418,12 @@ impl<C> minicbor::Encode<C> for AuxiliaryData {
|
||||||
pub type TransactionIndex = u32;
|
pub type TransactionIndex = u32;
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq)]
|
#[derive(Encode, Decode, Debug, PartialEq)]
|
||||||
pub struct Block<'b> {
|
pub struct Block {
|
||||||
#[n(0)]
|
#[n(0)]
|
||||||
pub header: Header,
|
pub header: Header,
|
||||||
|
|
||||||
#[b(1)]
|
#[b(1)]
|
||||||
pub transaction_bodies: MaybeIndefArray<KeepRaw<'b, TransactionBody>>,
|
pub transaction_bodies: MaybeIndefArray<TransactionBody>,
|
||||||
|
|
||||||
#[n(2)]
|
#[n(2)]
|
||||||
pub transaction_witness_sets: MaybeIndefArray<TransactionWitnessSet>,
|
pub transaction_witness_sets: MaybeIndefArray<TransactionWitnessSet>,
|
||||||
|
|
@ -1435,82 +1435,107 @@ pub struct Block<'b> {
|
||||||
pub invalid_transactions: Option<MaybeIndefArray<TransactionIndex>>,
|
pub invalid_transactions: Option<MaybeIndefArray<TransactionIndex>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug)]
|
/// A memory representation of an already minted block
|
||||||
pub struct BlockWrapper<'b>(#[n(0)] pub u16, #[b(1)] pub Block<'b>);
|
///
|
||||||
|
/// This structure is analogous to [Block], but it allows to retrieve the
|
||||||
|
/// original CBOR bytes for each structure that might require hashing. In this
|
||||||
|
/// way, we make sure that the resulting hash matches what exists on-chain.
|
||||||
|
#[derive(Encode, Decode, Debug, PartialEq)]
|
||||||
|
pub struct MintedBlock<'b> {
|
||||||
|
#[n(0)]
|
||||||
|
pub header: KeepRaw<'b, Header>,
|
||||||
|
|
||||||
|
#[b(1)]
|
||||||
|
pub transaction_bodies: MaybeIndefArray<KeepRaw<'b, TransactionBody>>,
|
||||||
|
|
||||||
|
#[n(2)]
|
||||||
|
pub transaction_witness_sets: MaybeIndefArray<TransactionWitnessSet>,
|
||||||
|
|
||||||
|
#[n(3)]
|
||||||
|
pub auxiliary_data_set: KeyValuePairs<TransactionIndex, KeepRaw<'b, AuxiliaryData>>,
|
||||||
|
|
||||||
|
#[n(4)]
|
||||||
|
pub invalid_transactions: Option<MaybeIndefArray<TransactionIndex>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug)]
|
#[derive(Encode, Decode, Debug)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
#[n(0)]
|
#[n(0)]
|
||||||
transaction_body: TransactionBody,
|
transaction_body: TransactionBody,
|
||||||
|
|
||||||
#[n(1)]
|
#[n(1)]
|
||||||
transaction_witness_set: TransactionWitnessSet,
|
transaction_witness_set: TransactionWitnessSet,
|
||||||
|
|
||||||
#[n(2)]
|
#[n(2)]
|
||||||
success: bool,
|
success: bool,
|
||||||
|
|
||||||
#[n(3)]
|
#[n(3)]
|
||||||
auxiliary_data: Option<AuxiliaryData>,
|
auxiliary_data: Option<AuxiliaryData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::BlockWrapper;
|
use pallas_codec::minicbor::{self, to_vec};
|
||||||
use crate::Fragment;
|
|
||||||
use pallas_codec::minicbor::to_vec;
|
use super::MintedBlock;
|
||||||
|
|
||||||
|
type BlockWrapper<'b> = (u16, MintedBlock<'b>);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_isomorphic_decoding_encoding() {
|
fn block_isomorphic_decoding_encoding() {
|
||||||
let test_blocks = vec![
|
let test_blocks = vec![
|
||||||
include_str!("../test_data/alonzo1.block"),
|
include_str!("../../../test_data/alonzo1.block"),
|
||||||
include_str!("../test_data/alonzo2.block"),
|
include_str!("../../../test_data/alonzo2.block"),
|
||||||
include_str!("../test_data/alonzo3.block"),
|
include_str!("../../../test_data/alonzo3.block"),
|
||||||
include_str!("../test_data/alonzo4.block"),
|
include_str!("../../../test_data/alonzo4.block"),
|
||||||
include_str!("../test_data/alonzo5.block"),
|
include_str!("../../../test_data/alonzo5.block"),
|
||||||
include_str!("../test_data/alonzo6.block"),
|
include_str!("../../../test_data/alonzo6.block"),
|
||||||
include_str!("../test_data/alonzo7.block"),
|
include_str!("../../../test_data/alonzo7.block"),
|
||||||
include_str!("../test_data/alonzo8.block"),
|
include_str!("../../../test_data/alonzo8.block"),
|
||||||
include_str!("../test_data/alonzo9.block"),
|
include_str!("../../../test_data/alonzo9.block"),
|
||||||
// old block without invalid_transactions fields
|
// old block without invalid_transactions fields
|
||||||
include_str!("../test_data/alonzo10.block"),
|
include_str!("../../../test_data/alonzo10.block"),
|
||||||
// peculiar block with protocol update params
|
// peculiar block with protocol update params
|
||||||
include_str!("../test_data/alonzo11.block"),
|
include_str!("../../../test_data/alonzo11.block"),
|
||||||
// peculiar block with decoding issue
|
// peculiar block with decoding issue
|
||||||
// https://github.com/txpipe/oura/issues/37
|
// https://github.com/txpipe/oura/issues/37
|
||||||
include_str!("../test_data/alonzo12.block"),
|
include_str!("../../../test_data/alonzo12.block"),
|
||||||
// peculiar block with protocol update params, including nonce
|
// peculiar block with protocol update params, including nonce
|
||||||
include_str!("../test_data/alonzo13.block"),
|
include_str!("../../../test_data/alonzo13.block"),
|
||||||
// peculiar block with overflow crash
|
// peculiar block with overflow crash
|
||||||
// https://github.com/txpipe/oura/issues/113
|
// https://github.com/txpipe/oura/issues/113
|
||||||
include_str!("../test_data/alonzo14.block"),
|
include_str!("../../../test_data/alonzo14.block"),
|
||||||
// peculiar block with many move-instantaneous-rewards certs
|
// peculiar block with many move-instantaneous-rewards certs
|
||||||
include_str!("../test_data/alonzo15.block"),
|
include_str!("../../../test_data/alonzo15.block"),
|
||||||
// peculiar block with protocol update values
|
// peculiar block with protocol update values
|
||||||
include_str!("../test_data/alonzo16.block"),
|
include_str!("../../../test_data/alonzo16.block"),
|
||||||
// peculiar block with missing nonce hash
|
// peculiar block with missing nonce hash
|
||||||
include_str!("../test_data/alonzo17.block"),
|
include_str!("../../../test_data/alonzo17.block"),
|
||||||
// peculiar block with strange AuxiliaryData variant
|
// peculiar block with strange AuxiliaryData variant
|
||||||
include_str!("../test_data/alonzo18.block"),
|
include_str!("../../../test_data/alonzo18.block"),
|
||||||
// peculiar block with strange AuxiliaryData variant
|
// peculiar block with strange AuxiliaryData variant
|
||||||
include_str!("../test_data/alonzo18.block"),
|
include_str!("../../../test_data/alonzo18.block"),
|
||||||
// peculiar block with nevative i64 overflow
|
// peculiar block with nevative i64 overflow
|
||||||
include_str!("../test_data/alonzo19.block"),
|
include_str!("../../../test_data/alonzo19.block"),
|
||||||
// peculiar block with very BigInt in plutus code
|
// peculiar block with very BigInt in plutus code
|
||||||
include_str!("../test_data/alonzo20.block"),
|
include_str!("../../../test_data/alonzo20.block"),
|
||||||
// peculiar block with bad tx hash
|
// peculiar block with bad tx hash
|
||||||
include_str!("../test_data/alonzo21.block"),
|
include_str!("../../../test_data/alonzo21.block"),
|
||||||
// peculiar block with bad tx hash
|
// peculiar block with bad tx hash
|
||||||
include_str!("../test_data/alonzo22.block"),
|
include_str!("../../../test_data/alonzo22.block"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (idx, block_str) in test_blocks.iter().enumerate() {
|
for (idx, block_str) in test_blocks.iter().enumerate() {
|
||||||
println!("decoding test block {}", idx + 1);
|
println!("decoding test block {}", idx + 1);
|
||||||
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
||||||
|
|
||||||
let block = BlockWrapper::decode_fragment(&bytes[..])
|
let block: BlockWrapper = minicbor::decode(&bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", idx));
|
.expect(&format!("error decoding cbor for file {}", idx));
|
||||||
|
|
||||||
let bytes2 =
|
let bytes2 =
|
||||||
to_vec(block).expect(&format!("error encoding block cbor for file {}", idx));
|
to_vec(block).expect(&format!("error encoding block cbor for file {}", idx));
|
||||||
|
|
||||||
assert_eq!(bytes, bytes2);
|
assert!(bytes.eq(&bytes2), "re-encoded bytes didn't match original");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ impl Address {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::byron::Block;
|
use crate::byron::MintedMainBlock;
|
||||||
use crate::Fragment;
|
|
||||||
use std::ops::Deref;
|
type BlockWrapper<'b> = (u16, MintedMainBlock<'b>);
|
||||||
|
|
||||||
const KNOWN_ADDRESSES: &[&str] = &[
|
const KNOWN_ADDRESSES: &[&str] = &[
|
||||||
"DdzFFzCqrht8QHTQXbWy2qoyPaqTN8BjyfKygGmpy9dtot1tvkBfCaVTnR22XCaaDVn3M1U6aiMShoCLzw6VWSwzQKhhJrM3YjYp3wyy",
|
"DdzFFzCqrht8QHTQXbWy2qoyPaqTN8BjyfKygGmpy9dtot1tvkBfCaVTnR22XCaaDVn3M1U6aiMShoCLzw6VWSwzQKhhJrM3YjYp3wyy",
|
||||||
|
|
@ -32,22 +32,17 @@ mod tests {
|
||||||
fn known_address_matches() {
|
fn known_address_matches() {
|
||||||
// TODO: expand this test to include more test blocks
|
// TODO: expand this test to include more test blocks
|
||||||
let block_idx = 1;
|
let block_idx = 1;
|
||||||
let block_str = include_str!("test_data/test2.block");
|
let block_str = include_str!("../../../test_data/byron2.block");
|
||||||
|
|
||||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||||
let block = Block::decode_fragment(&block_bytes[..])
|
let (_, block): BlockWrapper = pallas_codec::minicbor::decode(&block_bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||||
|
|
||||||
let block = match block {
|
|
||||||
Block::MainBlock(x) => x,
|
|
||||||
Block::EbBlock(_) => panic!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// don't want to pass if we don't have tx in the block
|
// don't want to pass if we don't have tx in the block
|
||||||
assert!(block.body.tx_payload.len() > 0);
|
assert!(block.body.tx_payload.len() > 0);
|
||||||
|
|
||||||
for tx in block.body.tx_payload.iter() {
|
for tx in block.body.tx_payload.iter() {
|
||||||
for output in tx.deref().transaction.outputs.iter() {
|
for output in tx.transaction.outputs.iter() {
|
||||||
let addr_str = output.address.to_addr_string().unwrap();
|
let addr_str = output.address.to_addr_string().unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,56 @@
|
||||||
use super::{Block, BlockHead, EbbHead, Tx};
|
use crate::ToHash;
|
||||||
|
|
||||||
|
use super::{BlockHead, EbbHead, Tx};
|
||||||
|
use pallas_codec::utils::KeepRaw;
|
||||||
use pallas_crypto::hash::{Hash, Hasher};
|
use pallas_crypto::hash::{Hash, Hasher};
|
||||||
|
|
||||||
impl EbbHead {
|
impl ToHash<32> for EbbHead {
|
||||||
pub fn to_hash(&self) -> Hash<32> {
|
fn to_hash(&self) -> Hash<32> {
|
||||||
// hash expects to have a prefix for the type of block
|
// hash expects to have a prefix for the type of block
|
||||||
Hasher::<256>::hash_cbor(&(0, self))
|
Hasher::<256>::hash_cbor(&(0, self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockHead {
|
impl ToHash<32> for KeepRaw<'_, EbbHead> {
|
||||||
pub fn to_hash(&self) -> Hash<32> {
|
fn to_hash(&self) -> Hash<32> {
|
||||||
|
// hash expects to have a prefix for the type of block
|
||||||
|
Hasher::<256>::hash_cbor(&(0, self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToHash<32> for BlockHead {
|
||||||
|
fn to_hash(&self) -> Hash<32> {
|
||||||
// hash expects to have a prefix for the type of block
|
// hash expects to have a prefix for the type of block
|
||||||
Hasher::<256>::hash_cbor(&(1, self))
|
Hasher::<256>::hash_cbor(&(1, self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Block {
|
impl ToHash<32> for KeepRaw<'_, BlockHead> {
|
||||||
pub fn to_hash(&self) -> Hash<32> {
|
fn to_hash(&self) -> Hash<32> {
|
||||||
match self {
|
// hash expects to have a prefix for the type of block
|
||||||
Block::EbBlock(x) => x.header.to_hash(),
|
Hasher::<256>::hash_cbor(&(1, self))
|
||||||
Block::MainBlock(x) => x.header.to_hash(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tx {
|
impl ToHash<32> for Tx {
|
||||||
pub fn to_hash(&self) -> Hash<32> {
|
fn to_hash(&self) -> Hash<32> {
|
||||||
Hasher::<256>::hash_cbor(self)
|
Hasher::<256>::hash_cbor(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToHash<32> for KeepRaw<'_, Tx> {
|
||||||
|
fn to_hash(&self) -> Hash<32> {
|
||||||
|
Hasher::<256>::hash(self.raw_cbor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::byron::Block;
|
use pallas_codec::minicbor;
|
||||||
use crate::Fragment;
|
|
||||||
|
use crate::{byron::MintedMainBlock, ToHash};
|
||||||
|
|
||||||
|
type BlockWrapper<'b> = (u16, MintedMainBlock<'b>);
|
||||||
|
|
||||||
const KNOWN_HASH: &'static str =
|
const KNOWN_HASH: &'static str =
|
||||||
"5c196e7394ace0449ba5a51c919369699b13896e97432894b4f0354dce8670b6";
|
"5c196e7394ace0449ba5a51c919369699b13896e97432894b4f0354dce8670b6";
|
||||||
|
|
@ -42,13 +59,13 @@ mod tests {
|
||||||
fn transaction_hash_works() {
|
fn transaction_hash_works() {
|
||||||
// TODO: expand this test to include more test blocks
|
// TODO: expand this test to include more test blocks
|
||||||
let block_idx = 1;
|
let block_idx = 1;
|
||||||
let block_str = include_str!("test_data/test1.block");
|
let block_str = include_str!("../../../test_data/byron1.block");
|
||||||
|
|
||||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||||
let block_model = Block::decode_fragment(&block_bytes[..])
|
let (_, block_model): BlockWrapper = minicbor::decode(&block_bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||||
|
|
||||||
let computed_hash = block_model.to_hash();
|
let computed_hash = block_model.header.to_hash();
|
||||||
|
|
||||||
assert_eq!(hex::encode(computed_hash), KNOWN_HASH)
|
assert_eq!(hex::encode(computed_hash), KNOWN_HASH)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,24 +46,22 @@ impl TxPayload {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::byron::Block;
|
use pallas_codec::minicbor;
|
||||||
use crate::Fragment;
|
|
||||||
|
use crate::{byron::MainBlock, ToHash};
|
||||||
|
|
||||||
|
type BlockWrapper = (u16, MainBlock);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn known_fee_matches() {
|
fn known_fee_matches() {
|
||||||
// TODO: expand this test to include more test blocks
|
// TODO: expand this test to include more test blocks
|
||||||
let block_idx = 1;
|
let block_idx = 1;
|
||||||
let block_str = include_str!("test_data/test4.block");
|
let block_str = include_str!("../../../test_data/byron4.block");
|
||||||
|
|
||||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||||
let block = Block::decode_fragment(&block_bytes[..])
|
let (_, block): BlockWrapper = minicbor::decode(&block_bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||||
|
|
||||||
let block = match block {
|
|
||||||
Block::MainBlock(x) => x,
|
|
||||||
Block::EbBlock(_) => panic!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// don't want to pass if we don't have tx in the block
|
// don't want to pass if we don't have tx in the block
|
||||||
assert!(block.body.tx_payload.len() > 0);
|
assert!(block.body.tx_payload.len() > 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ use pallas_codec::minicbor::{bytes::ByteVec, Decode, Encode};
|
||||||
use pallas_crypto::hash::Hash;
|
use pallas_crypto::hash::Hash;
|
||||||
|
|
||||||
use pallas_codec::utils::{
|
use pallas_codec::utils::{
|
||||||
CborWrap, EmptyMap, KeyValuePairs, MaybeIndefArray, OrderPreservingProperties, TagWrap,
|
CborWrap, EmptyMap, KeepRaw, KeyValuePairs, MaybeIndefArray, OrderPreservingProperties,
|
||||||
ZeroOrOneArray,
|
TagWrap, ZeroOrOneArray,
|
||||||
};
|
};
|
||||||
|
|
||||||
// required for derive attrs to work
|
// required for derive attrs to work
|
||||||
|
|
@ -907,6 +907,18 @@ pub struct MainBlock {
|
||||||
pub extra: MaybeIndefArray<Attributes>,
|
pub extra: MaybeIndefArray<Attributes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, Debug)]
|
||||||
|
pub struct MintedMainBlock<'b> {
|
||||||
|
#[b(0)]
|
||||||
|
pub header: KeepRaw<'b, BlockHead>,
|
||||||
|
|
||||||
|
#[n(1)]
|
||||||
|
pub body: BlockBody,
|
||||||
|
|
||||||
|
#[n(2)]
|
||||||
|
pub extra: MaybeIndefArray<Attributes>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug)]
|
#[derive(Encode, Decode, Debug)]
|
||||||
pub struct EbBlock {
|
pub struct EbBlock {
|
||||||
#[n(0)]
|
#[n(0)]
|
||||||
|
|
@ -919,79 +931,51 @@ pub struct EbBlock {
|
||||||
pub extra: MaybeIndefArray<Attributes>,
|
pub extra: MaybeIndefArray<Attributes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Block {
|
|
||||||
MainBlock(MainBlock),
|
|
||||||
EbBlock(EbBlock),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b, C> minicbor::Decode<'b, C> for Block {
|
|
||||||
fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result<Self, minicbor::decode::Error> {
|
|
||||||
d.array()?;
|
|
||||||
|
|
||||||
let variant = d.u32()?;
|
|
||||||
|
|
||||||
match variant {
|
|
||||||
0 => Ok(Block::EbBlock(d.decode_with(ctx)?)),
|
|
||||||
1 => Ok(Block::MainBlock(d.decode_with(ctx)?)),
|
|
||||||
_ => Err(minicbor::decode::Error::message(
|
|
||||||
"unknown variant for block",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl minicbor::Encode<()> for Block {
|
|
||||||
fn encode<W: minicbor::encode::Write>(
|
|
||||||
&self,
|
|
||||||
e: &mut minicbor::Encoder<W>,
|
|
||||||
_ctx: &mut (),
|
|
||||||
) -> Result<(), minicbor::encode::Error<W::Error>> {
|
|
||||||
match self {
|
|
||||||
Block::EbBlock(x) => {
|
|
||||||
e.array(2)?;
|
|
||||||
e.encode(0)?;
|
|
||||||
e.encode(x)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Block::MainBlock(x) => {
|
|
||||||
e.array(2)?;
|
|
||||||
e.encode(1)?;
|
|
||||||
e.encode(x)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::byron::{Block, BlockHead};
|
use super::{BlockHead, EbBlock, MintedMainBlock};
|
||||||
use crate::Fragment;
|
use pallas_codec::minicbor::{self, to_vec};
|
||||||
|
|
||||||
use pallas_codec::minicbor::to_vec;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_isomorphic_decoding_encoding() {
|
fn boundary_block_isomorphic_decoding_encoding() {
|
||||||
|
type BlockWrapper = (u16, EbBlock);
|
||||||
|
|
||||||
|
let test_blocks = vec![include_str!("../../../test_data/genesis.block")];
|
||||||
|
|
||||||
|
for (idx, block_str) in test_blocks.iter().enumerate() {
|
||||||
|
println!("decoding test block {}", idx + 1);
|
||||||
|
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
||||||
|
|
||||||
|
let block: BlockWrapper = minicbor::decode(&bytes[..])
|
||||||
|
.expect(&format!("error decoding cbor for file {}", idx));
|
||||||
|
|
||||||
|
let bytes2 =
|
||||||
|
to_vec(block).expect(&format!("error encoding block cbor for file {}", idx));
|
||||||
|
|
||||||
|
assert_eq!(hex::encode(bytes), hex::encode(bytes2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main_block_isomorphic_decoding_encoding() {
|
||||||
|
type BlockWrapper<'b> = (u16, MintedMainBlock<'b>);
|
||||||
|
|
||||||
let test_blocks = vec![
|
let test_blocks = vec![
|
||||||
include_str!("test_data/genesis.block"),
|
//include_str!("../../../test_data/genesis.block"),
|
||||||
include_str!("test_data/test1.block"),
|
include_str!("../../../test_data/byron1.block"),
|
||||||
include_str!("test_data/test2.block"),
|
include_str!("../../../test_data/byron2.block"),
|
||||||
include_str!("test_data/test3.block"),
|
include_str!("../../../test_data/byron3.block"),
|
||||||
include_str!("test_data/test4.block"),
|
include_str!("../../../test_data/byron4.block"),
|
||||||
include_str!("test_data/test5.block"),
|
include_str!("../../../test_data/byron5.block"),
|
||||||
include_str!("test_data/test6.block"),
|
include_str!("../../../test_data/byron6.block"),
|
||||||
include_str!("test_data/test7.block"),
|
include_str!("../../../test_data/byron7.block"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (idx, block_str) in test_blocks.iter().enumerate() {
|
for (idx, block_str) in test_blocks.iter().enumerate() {
|
||||||
println!("decoding test block {}", idx + 1);
|
println!("decoding test block {}", idx + 1);
|
||||||
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
||||||
|
|
||||||
let block = Block::decode_fragment(&bytes[..])
|
let block: BlockWrapper = minicbor::decode(&bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", idx));
|
.expect(&format!("error decoding cbor for file {}", idx));
|
||||||
|
|
||||||
let bytes2 =
|
let bytes2 =
|
||||||
|
|
@ -1003,13 +987,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn header_isomorphic_decoding_encoding() {
|
fn header_isomorphic_decoding_encoding() {
|
||||||
let subjects = vec![include_str!("test_data/test1.header")];
|
let subjects = vec![include_str!("../../../test_data/byron1.header")];
|
||||||
|
|
||||||
for (idx, str) in subjects.iter().enumerate() {
|
for (idx, str) in subjects.iter().enumerate() {
|
||||||
println!("decoding test header {}", idx + 1);
|
println!("decoding test header {}", idx + 1);
|
||||||
let bytes = hex::decode(str).expect(&format!("bad header file {}", idx));
|
let bytes = hex::decode(str).expect(&format!("bad header file {}", idx));
|
||||||
|
|
||||||
let block = BlockHead::decode_fragment(&bytes[..])
|
let block: BlockHead = minicbor::decode(&bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", idx));
|
.expect(&format!("error decoding cbor for file {}", idx));
|
||||||
|
|
||||||
let bytes2 =
|
let bytes2 =
|
||||||
|
|
|
||||||
|
|
@ -22,24 +22,22 @@ impl EbbHead {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::byron::Block;
|
use pallas_codec::minicbor;
|
||||||
use crate::Fragment;
|
|
||||||
|
use crate::byron::MainBlock;
|
||||||
|
|
||||||
|
type BlockWrapper = (u16, MainBlock);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn knwon_slot_matches() {
|
fn knwon_slot_matches() {
|
||||||
// TODO: expand this test to include more test blocks
|
// TODO: expand this test to include more test blocks
|
||||||
let block_idx = 1;
|
let block_idx = 1;
|
||||||
let block_str = include_str!("test_data/test1.block");
|
let block_str = include_str!("../../../test_data/byron1.block");
|
||||||
|
|
||||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||||
let block = Block::decode_fragment(&block_bytes[..])
|
let (_, block): BlockWrapper = minicbor::decode(&block_bytes[..])
|
||||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||||
|
|
||||||
let block = match block {
|
|
||||||
Block::MainBlock(x) => x,
|
|
||||||
Block::EbBlock(_) => panic!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let computed_slot = block.header.consensus_data.0.to_abs_slot();
|
let computed_slot = block.header.consensus_data.0.to_abs_slot();
|
||||||
|
|
||||||
assert_eq!(computed_slot, 4492794);
|
assert_eq!(computed_slot, 4492794);
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
|
||||||
pub trait ToCanonicalJson {
|
|
||||||
fn to_json(&self) -> serde_json::Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Era {
|
pub enum Era {
|
||||||
Byron,
|
Byron,
|
||||||
|
|
@ -37,6 +32,11 @@ pub enum Era {
|
||||||
Alonzo, // smart-contracts
|
Alonzo, // smart-contracts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
pub trait ToCanonicalJson {
|
||||||
|
fn to_json(&self) -> serde_json::Value;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ToHash<const BYTES: usize> {
|
pub trait ToHash<const BYTES: usize> {
|
||||||
fn to_hash(&self) -> pallas_crypto::hash::Hash<BYTES>;
|
fn to_hash(&self) -> pallas_crypto::hash::Hash<BYTES>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::Era;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Outcome {
|
pub enum Outcome {
|
||||||
Matched(Era),
|
Matched(Era),
|
||||||
GenesisBlock,
|
EpochBoundary,
|
||||||
Inconclusive,
|
Inconclusive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub fn probe_block_cbor_era(cbor: &[u8]) -> Outcome {
|
||||||
|
|
||||||
match tokenizer.next() {
|
match tokenizer.next() {
|
||||||
Some(Ok(Token::U8(variant))) => match variant {
|
Some(Ok(Token::U8(variant))) => match variant {
|
||||||
0 => Outcome::GenesisBlock,
|
0 => Outcome::EpochBoundary,
|
||||||
1 => Outcome::Matched(Era::Byron),
|
1 => Outcome::Matched(Era::Byron),
|
||||||
2 => Outcome::Matched(Era::Shelley),
|
2 => Outcome::Matched(Era::Shelley),
|
||||||
3 => Outcome::Matched(Era::Allegra),
|
3 => Outcome::Matched(Era::Allegra),
|
||||||
|
|
@ -41,17 +41,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn genesis_block_detected() {
|
fn genesis_block_detected() {
|
||||||
let block_str = include_str!("byron/test_data/genesis.block");
|
let block_str = include_str!("../../test_data/genesis.block");
|
||||||
let bytes = hex::decode(block_str).unwrap();
|
let bytes = hex::decode(block_str).unwrap();
|
||||||
|
|
||||||
let inference = probe_block_cbor_era(bytes.as_slice());
|
let inference = probe_block_cbor_era(bytes.as_slice());
|
||||||
|
|
||||||
assert!(matches!(inference, Outcome::GenesisBlock));
|
assert!(matches!(inference, Outcome::EpochBoundary));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn byron_block_detected() {
|
fn byron_block_detected() {
|
||||||
let block_str = include_str!("byron/test_data/test1.block");
|
let block_str = include_str!("../../test_data/byron1.block");
|
||||||
let bytes = hex::decode(block_str).unwrap();
|
let bytes = hex::decode(block_str).unwrap();
|
||||||
|
|
||||||
let inference = probe_block_cbor_era(bytes.as_slice());
|
let inference = probe_block_cbor_era(bytes.as_slice());
|
||||||
|
|
@ -61,7 +61,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shelley_block_detected() {
|
fn shelley_block_detected() {
|
||||||
let block_str = include_str!("test_data/shelley1.block");
|
let block_str = include_str!("../../test_data/shelley1.block");
|
||||||
let bytes = hex::decode(block_str).unwrap();
|
let bytes = hex::decode(block_str).unwrap();
|
||||||
|
|
||||||
let inference = probe_block_cbor_era(bytes.as_slice());
|
let inference = probe_block_cbor_era(bytes.as_slice());
|
||||||
|
|
@ -71,7 +71,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allegra_block_detected() {
|
fn allegra_block_detected() {
|
||||||
let block_str = include_str!("test_data/allegra1.block");
|
let block_str = include_str!("../../test_data/allegra1.block");
|
||||||
let bytes = hex::decode(block_str).unwrap();
|
let bytes = hex::decode(block_str).unwrap();
|
||||||
|
|
||||||
let inference = probe_block_cbor_era(bytes.as_slice());
|
let inference = probe_block_cbor_era(bytes.as_slice());
|
||||||
|
|
@ -81,7 +81,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mary_block_detected() {
|
fn mary_block_detected() {
|
||||||
let block_str = include_str!("test_data/mary1.block");
|
let block_str = include_str!("../../test_data/mary1.block");
|
||||||
let bytes = hex::decode(block_str).unwrap();
|
let bytes = hex::decode(block_str).unwrap();
|
||||||
|
|
||||||
let inference = probe_block_cbor_era(bytes.as_slice());
|
let inference = probe_block_cbor_era(bytes.as_slice());
|
||||||
|
|
@ -91,7 +91,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alonzo_block_detected() {
|
fn alonzo_block_detected() {
|
||||||
let block_str = include_str!("test_data/alonzo1.block");
|
let block_str = include_str!("../../test_data/alonzo1.block");
|
||||||
let bytes = hex::decode(block_str).unwrap();
|
let bytes = hex::decode(block_str).unwrap();
|
||||||
|
|
||||||
let inference = probe_block_cbor_era(bytes.as_slice());
|
let inference = probe_block_cbor_era(bytes.as_slice());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue