diff --git a/Cargo.lock b/Cargo.lock index fc3d034550e4f..11ee0fab46792 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,7 +507,6 @@ dependencies = [ name = "beefy-primitives" version = "4.0.0-dev" dependencies = [ - "hex", "hex-literal", "parity-scale-codec", "scale-info", diff --git a/client/beefy/src/gossip.rs b/client/beefy/src/gossip.rs index dc59f664caa91..8a43b5a039478 100644 --- a/client/beefy/src/gossip.rs +++ b/client/beefy/src/gossip.rs @@ -30,7 +30,7 @@ use wasm_timer::Instant; use beefy_primitives::{ crypto::{Public, Signature}, - VoteMessage, + MmrRootHash, VoteMessage, }; use crate::keystore::BeefyKeystore; @@ -142,7 +142,9 @@ where sender: &PeerId, mut data: &[u8], ) -> ValidationResult { - if let Ok(msg) = VoteMessage::, Public, Signature>::decode(&mut data) { + if let Ok(msg) = + VoteMessage::, Public, Signature>::decode(&mut data) + { let msg_hash = twox_64(data); let round = msg.commitment.block_number; @@ -176,7 +178,9 @@ where fn message_expired<'a>(&'a self) -> Box bool + 'a> { let known_votes = self.known_votes.read(); Box::new(move |_topic, mut data| { - let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { + let msg = match VoteMessage::, Public, Signature>::decode( + &mut data, + ) { Ok(vote) => vote, Err(_) => return true, }; @@ -210,7 +214,9 @@ where return do_rebroadcast } - let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { + let msg = match VoteMessage::, Public, Signature>::decode( + &mut data, + ) { Ok(vote) => vote, Err(_) => return true, }; @@ -231,11 +237,9 @@ mod tests { use sc_network_test::Block; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; + use beefy_primitives::{crypto::Signature, Commitment, MmrRootHash, VoteMessage, KEY_TYPE}; + use crate::keystore::{tests::Keyring, BeefyKeystore}; - use beefy_primitives::{ - crypto::Signature, known_payload_ids, Commitment, MmrRootHash, Payload, VoteMessage, - KEY_TYPE, - }; use super::*; @@ -341,7 +345,10 @@ mod tests { } } - fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { + fn sign_commitment( + who: &Keyring, + commitment: &Commitment, + ) -> Signature { let store: SyncCryptoStorePtr = std::sync::Arc::new(LocalKeystore::in_memory()); SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&who.to_seed())).unwrap(); let beefy_keystore: BeefyKeystore = Some(store).into(); @@ -355,8 +362,11 @@ mod tests { let sender = sc_network::PeerId::random(); let mut context = TestContext; - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, MmrRootHash::default().encode()); - let commitment = Commitment { payload, block_number: 3_u64, validator_set_id: 0 }; + let commitment = Commitment { + payload: MmrRootHash::default(), + block_number: 3_u64, + validator_set_id: 0, + }; let signature = sign_commitment(&Keyring::Alice, &commitment); diff --git a/client/beefy/src/notification.rs b/client/beefy/src/notification.rs index f394ae6c840a2..6099c9681447b 100644 --- a/client/beefy/src/notification.rs +++ b/client/beefy/src/notification.rs @@ -24,7 +24,8 @@ use sp_runtime::traits::{Block, NumberFor}; use parking_lot::Mutex; /// Stream of signed commitments returned when subscribing. -pub type SignedCommitment = beefy_primitives::SignedCommitment>; +pub type SignedCommitment = + beefy_primitives::SignedCommitment, beefy_primitives::MmrRootHash>; /// Stream of signed commitments returned when subscribing. type SignedCommitmentStream = TracingUnboundedReceiver>; diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs index db41f0f465db6..e9f5ad2062433 100644 --- a/client/beefy/src/round.rs +++ b/client/beefy/src/round.rs @@ -53,14 +53,14 @@ fn threshold(authorities: usize) -> usize { authorities - faulty } -pub(crate) struct Rounds { - rounds: BTreeMap<(Payload, Number), RoundTracker>, +pub(crate) struct Rounds { + rounds: BTreeMap<(Hash, Number), RoundTracker>, validator_set: ValidatorSet, } -impl Rounds +impl Rounds where - P: Ord + Hash, + H: Ord + Hash, N: Ord + AtLeast32BitUnsigned + MaybeDisplay, { pub(crate) fn new(validator_set: ValidatorSet) -> Self { @@ -70,8 +70,8 @@ where impl Rounds where - H: Ord + Hash + Clone, - N: Ord + AtLeast32BitUnsigned + MaybeDisplay + Clone, + H: Ord + Hash, + N: Ord + AtLeast32BitUnsigned + MaybeDisplay, { pub(crate) fn validator_set_id(&self) -> ValidatorSetId { self.validator_set.id @@ -81,9 +81,9 @@ where self.validator_set.validators.clone() } - pub(crate) fn add_vote(&mut self, round: &(H, N), vote: (Public, Signature)) -> bool { + pub(crate) fn add_vote(&mut self, round: (H, N), vote: (Public, Signature)) -> bool { if self.validator_set.validators.iter().any(|id| vote.0 == *id) { - self.rounds.entry(round.clone()).or_default().add_vote(vote) + self.rounds.entry(round).or_default().add_vote(vote) } else { false } @@ -179,7 +179,7 @@ mod tests { let mut rounds = Rounds::>::new(validators); assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), + (H256::from_low_u64_le(1), 1), (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")) )); @@ -187,21 +187,21 @@ mod tests { // invalid vote assert!(!rounds.add_vote( - &(H256::from_low_u64_le(1), 1), + (H256::from_low_u64_le(1), 1), (Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")) )); assert!(!rounds.is_done(&(H256::from_low_u64_le(1), 1))); assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), + (H256::from_low_u64_le(1), 1), (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")) )); assert!(!rounds.is_done(&(H256::from_low_u64_le(1), 1))); assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), + (H256::from_low_u64_le(1), 1), (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")) )); @@ -225,31 +225,31 @@ mod tests { // round 1 rounds.add_vote( - &(H256::from_low_u64_le(1), 1), + (H256::from_low_u64_le(1), 1), (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), ); rounds.add_vote( - &(H256::from_low_u64_le(1), 1), + (H256::from_low_u64_le(1), 1), (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), ); // round 2 rounds.add_vote( - &(H256::from_low_u64_le(2), 2), + (H256::from_low_u64_le(2), 2), (Keyring::Alice.public(), Keyring::Alice.sign(b"I am again committed")), ); rounds.add_vote( - &(H256::from_low_u64_le(2), 2), + (H256::from_low_u64_le(2), 2), (Keyring::Bob.public(), Keyring::Bob.sign(b"I am again committed")), ); // round 3 rounds.add_vote( - &(H256::from_low_u64_le(3), 3), + (H256::from_low_u64_le(3), 3), (Keyring::Alice.public(), Keyring::Alice.sign(b"I am still committed")), ); rounds.add_vote( - &(H256::from_low_u64_le(3), 3), + (H256::from_low_u64_le(3), 3), (Keyring::Bob.public(), Keyring::Bob.sign(b"I am still committed")), ); diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index fa48e64c12b4e..3f52686930332 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -36,8 +36,8 @@ use sp_runtime::{ use beefy_primitives::{ crypto::{AuthorityId, Public, Signature}, - known_payload_ids, BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, - ValidatorSet, VersionedCommitment, VoteMessage, BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID, + BeefyApi, Commitment, ConsensusLog, MmrRootHash, SignedCommitment, ValidatorSet, + VersionedCommitment, VoteMessage, BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID, }; use crate::{ @@ -79,7 +79,7 @@ where /// Min delta in block numbers between two blocks, BEEFY should vote on min_block_delta: u32, metrics: Option, - rounds: round::Rounds>, + rounds: round::Rounds>, finality_notifications: FinalityNotifications, /// Best block we received a GRANDPA notification for best_grandpa_block: NumberFor, @@ -262,9 +262,8 @@ where return }; - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, mmr_root.encode()); let commitment = Commitment { - payload, + payload: mmr_root, block_number: notification.header.number(), validator_set_id: self.rounds.validator_set_id(), }; @@ -302,10 +301,10 @@ where } } - fn handle_vote(&mut self, round: (Payload, NumberFor), vote: (Public, Signature)) { + fn handle_vote(&mut self, round: (MmrRootHash, NumberFor), vote: (Public, Signature)) { self.gossip_validator.note_round(round.1); - let vote_added = self.rounds.add_vote(&round, vote); + let vote_added = self.rounds.add_vote(round, vote); if vote_added && self.rounds.is_done(&round) { if let Some(signatures) = self.rounds.drop(&round) { @@ -353,7 +352,7 @@ where |notification| async move { debug!(target: "beefy", "🥩 Got vote message: {:?}", notification); - VoteMessage::, Public, Signature>::decode( + VoteMessage::, Public, Signature>::decode( &mut ¬ification.message[..], ) .ok() diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index e38af745cd714..23e98012027c7 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -18,8 +18,8 @@ sp-runtime = { version = "4.0.0-dev", path = "../runtime", default-features = fa sp-std = { version = "4.0.0-dev", path = "../std", default-features = false } [dev-dependencies] -hex = "0.4.3" hex-literal = "0.3" + sp-keystore = { version = "0.10.0-dev", path = "../keystore" } [features] diff --git a/primitives/beefy/src/commitment.rs b/primitives/beefy/src/commitment.rs index 667c03cc2284c..d9e4de6e19bb7 100644 --- a/primitives/beefy/src/commitment.rs +++ b/primitives/beefy/src/commitment.rs @@ -15,65 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use codec::{Decode, Encode}; use sp_std::{cmp, prelude::*}; use crate::{crypto::Signature, ValidatorSetId}; -/// Id of different payloads in the [`Commitment`] data -pub type BeefyPayloadId = [u8; 2]; - -/// Registry of all known [`BeefyPayloadId`]. -pub mod known_payload_ids { - use crate::BeefyPayloadId; - - /// A [`Payload`] identifier for Merkle Mountain Range root hash. - /// - /// Encoded value should contain a [`beefy_primitives::MmrRootHash`] type (i.e. 32-bytes hash). - pub const MMR_ROOT_ID: BeefyPayloadId = *b"mh"; -} - -/// A BEEFY payload type allowing for future extensibility of adding additional kinds of payloads. -/// -/// The idea is to store a vector of SCALE-encoded values with an extra identifier. -/// Identifiers MUST be sorted by the [`BeefyPayloadId`] to allow efficient lookup of expected -/// value. Duplicated identifiers are disallowed. It's okay for different implementations to only -/// support a subset of possible values. -#[derive(Decode, Encode, Debug, PartialEq, Eq, Clone, Ord, PartialOrd, Hash)] -pub struct Payload(Vec<(BeefyPayloadId, Vec)>); - -impl Payload { - /// Construct a new payload given an initial vallue - pub fn new(id: BeefyPayloadId, value: Vec) -> Self { - Self(vec![(id, value)]) - } - - /// Returns a raw payload under given `id`. - /// - /// If the [`BeefyPayloadId`] is not found in the payload `None` is returned. - pub fn get_raw(&self, id: &BeefyPayloadId) -> Option<&Vec> { - let index = self.0.binary_search_by(|probe| probe.0.cmp(id)).ok()?; - Some(&self.0[index].1) - } - - /// Returns a decoded payload value under given `id`. - /// - /// In case the value is not there or it cannot be decoded does not match `None` is returned. - pub fn get_decoded(&self, id: &BeefyPayloadId) -> Option { - self.get_raw(id).and_then(|raw| T::decode(&mut &raw[..]).ok()) - } - - /// Push a `Vec` with a given id into the payload vec. - /// This method will internally sort the payload vec after every push. - /// - /// Returns self to allow for daisy chaining. - pub fn push_raw(mut self, id: BeefyPayloadId, value: Vec) -> Self { - self.0.push((id, value)); - self.0.sort_by_key(|(id, _)| *id); - self - } -} - /// A commitment signed by GRANDPA validators as part of BEEFY protocol. /// /// The commitment contains a [payload](Commitment::payload) extracted from the finalized block at @@ -81,17 +26,16 @@ impl Payload { /// GRANDPA validators collect signatures on commitments and a stream of such signed commitments /// (see [SignedCommitment]) forms the BEEFY protocol. #[derive(Clone, Debug, PartialEq, Eq, codec::Encode, codec::Decode)] -pub struct Commitment { - /// A collection of payloads to be signed, see [`Payload`] for details. +pub struct Commitment { + /// The payload being signed. /// - /// One of the payloads should be some form of cumulative representation of the chain (think - /// MMR root hash). Additionally one of the payloads should also contain some details that - /// allow the light client to verify next validator set. The protocol does not enforce any - /// particular format of this data, nor how often it should be present in commitments, however - /// the light client has to be provided with full validator set whenever it performs the - /// transition (i.e. importing first block with - /// [validator_set_id](Commitment::validator_set_id) incremented). - pub payload: Payload, + /// This should be some form of cumulative representation of the chain (think MMR root hash). + /// The payload should also contain some details that allow the light client to verify next + /// validator set. The protocol does not enforce any particular format of this data, + /// nor how often it should be present in commitments, however the light client has to be + /// provided with full validator set whenever it performs the transition (i.e. importing first + /// block with [validator_set_id](Commitment::validator_set_id) incremented). + pub payload: TPayload, /// Finalized block number this commitment is for. /// @@ -113,18 +57,20 @@ pub struct Commitment { pub validator_set_id: ValidatorSetId, } -impl cmp::PartialOrd for Commitment +impl cmp::PartialOrd for Commitment where TBlockNumber: cmp::Ord, + TPayload: cmp::Eq, { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl cmp::Ord for Commitment +impl cmp::Ord for Commitment where TBlockNumber: cmp::Ord, + TPayload: cmp::Eq, { fn cmp(&self, other: &Self) -> cmp::Ordering { self.validator_set_id @@ -135,9 +81,9 @@ where /// A commitment with matching GRANDPA validators' signatures. #[derive(Clone, Debug, PartialEq, Eq, codec::Encode, codec::Decode)] -pub struct SignedCommitment { +pub struct SignedCommitment { /// The commitment signatures are collected for. - pub commitment: Commitment, + pub commitment: Commitment, /// GRANDPA validators' signatures for the commitment. /// /// The length of this `Vec` must match number of validators in the current set (see @@ -145,7 +91,7 @@ pub struct SignedCommitment { pub signatures: Vec>, } -impl SignedCommitment { +impl SignedCommitment { /// Return the number of collected signatures. pub fn no_of_signatures(&self) -> usize { self.signatures.iter().filter(|x| x.is_some()).count() @@ -156,10 +102,10 @@ impl SignedCommitment { /// to the block justifications for the block for which the signed commitment /// has been generated. #[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)] -pub enum VersionedCommitment { +pub enum VersionedCommitment { #[codec(index = 1)] /// Current active version - V1(SignedCommitment), + V1(SignedCommitment), } #[cfg(test)] @@ -173,9 +119,9 @@ mod tests { use crate::{crypto, KEY_TYPE}; - type TestCommitment = Commitment; - type TestSignedCommitment = SignedCommitment; - type TestVersionedCommitment = VersionedCommitment; + type TestCommitment = Commitment; + type TestSignedCommitment = SignedCommitment; + type TestVersionedCommitment = VersionedCommitment; // The mock signatures are equivalent to the ones produced by the BEEFY keystore fn mock_signatures() -> (crypto::Signature, crypto::Signature) { @@ -202,9 +148,8 @@ mod tests { #[test] fn commitment_encode_decode() { // given - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode()); let commitment: TestCommitment = - Commitment { payload, block_number: 5, validator_set_id: 0 }; + Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 }; // when let encoded = codec::Encode::encode(&commitment); @@ -215,7 +160,7 @@ mod tests { assert_eq!( encoded, hex_literal::hex!( - "046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000" + "3048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000" ) ); } @@ -223,9 +168,8 @@ mod tests { #[test] fn signed_commitment_encode_decode() { // given - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode()); let commitment: TestCommitment = - Commitment { payload, block_number: 5, validator_set_id: 0 }; + Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 }; let sigs = mock_signatures(); @@ -243,11 +187,10 @@ mod tests { assert_eq!( encoded, hex_literal::hex!( - "046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000 - 10000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321 - f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a86cdd9aaa - cdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a - 0046395a71681be3d0c2a00" + "3048656c6c6f20576f726c64210500000000000000000000000000000000000000000000001000 + 0001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d + 10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a + 2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00" ) ); } @@ -255,9 +198,8 @@ mod tests { #[test] fn signed_commitment_count_signatures() { // given - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode()); let commitment: TestCommitment = - Commitment { payload, block_number: 5, validator_set_id: 0 }; + Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 }; let sigs = mock_signatures(); @@ -280,8 +222,7 @@ mod tests { block_number: u128, validator_set_id: crate::ValidatorSetId, ) -> TestCommitment { - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode()); - Commitment { payload, block_number, validator_set_id } + Commitment { payload: "Hello World!".into(), block_number, validator_set_id } } // given @@ -300,9 +241,8 @@ mod tests { #[test] fn versioned_commitment_encode_decode() { - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode()); let commitment: TestCommitment = - Commitment { payload, block_number: 5, validator_set_id: 0 }; + Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 }; let sigs = mock_signatures(); diff --git a/primitives/beefy/src/lib.rs b/primitives/beefy/src/lib.rs index cb3cf601a76bc..790b915ab98db 100644 --- a/primitives/beefy/src/lib.rs +++ b/primitives/beefy/src/lib.rs @@ -35,9 +35,7 @@ mod commitment; pub mod mmr; pub mod witness; -pub use commitment::{ - known_payload_ids, BeefyPayloadId, Commitment, Payload, SignedCommitment, VersionedCommitment, -}; +pub use commitment::{Commitment, SignedCommitment, VersionedCommitment}; use codec::{Codec, Decode, Encode}; use scale_info::TypeInfo; @@ -120,9 +118,9 @@ pub enum ConsensusLog { /// A vote message is a direct vote created by a BEEFY node on every voting round /// and is gossiped to its peers. #[derive(Debug, Decode, Encode, TypeInfo)] -pub struct VoteMessage { +pub struct VoteMessage { /// Commit to information extracted from a finalized block - pub commitment: Commitment, + pub commitment: Commitment, /// Node authority id pub id: Id, /// Node signature diff --git a/primitives/beefy/src/witness.rs b/primitives/beefy/src/witness.rs index 3ead08bdd7cb3..c28a464e72df5 100644 --- a/primitives/beefy/src/witness.rs +++ b/primitives/beefy/src/witness.rs @@ -40,9 +40,9 @@ use crate::{ /// Ethereum Mainnet), in a commit-reveal like scheme, where first we submit only the signed /// commitment witness and later on, the client picks only some signatures to verify at random. #[derive(Debug, PartialEq, Eq, codec::Encode, codec::Decode)] -pub struct SignedCommitmentWitness { +pub struct SignedCommitmentWitness { /// The full content of the commitment. - pub commitment: Commitment, + pub commitment: Commitment, /// The bit vector of validators who signed the commitment. pub signed_by: Vec, // TODO [ToDr] Consider replacing with bitvec crate @@ -51,7 +51,9 @@ pub struct SignedCommitmentWitness { pub signatures_merkle_root: TMerkleRoot, } -impl SignedCommitmentWitness { +impl + SignedCommitmentWitness +{ /// Convert [SignedCommitment] into [SignedCommitmentWitness]. /// /// This takes a [SignedCommitment], which contains full signatures @@ -61,7 +63,7 @@ impl SignedCommitmentWitness( - signed: SignedCommitment, + signed: SignedCommitment, merkelize: TMerkelize, ) -> (Self, Vec>) where @@ -84,11 +86,12 @@ mod tests { use super::*; use codec::Decode; - use crate::{crypto, known_payload_ids, Payload, KEY_TYPE}; + use crate::{crypto, KEY_TYPE}; - type TestCommitment = Commitment; - type TestSignedCommitment = SignedCommitment; - type TestSignedCommitmentWitness = SignedCommitmentWitness>>; + type TestCommitment = Commitment; + type TestSignedCommitment = SignedCommitment; + type TestSignedCommitmentWitness = + SignedCommitmentWitness>>; // The mock signatures are equivalent to the ones produced by the BEEFY keystore fn mock_signatures() -> (crypto::Signature, crypto::Signature) { @@ -113,10 +116,8 @@ mod tests { } fn signed_commitment() -> TestSignedCommitment { - let payload = - Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".as_bytes().to_vec()); let commitment: TestCommitment = - Commitment { payload, block_number: 5, validator_set_id: 0 }; + Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 }; let sigs = mock_signatures(); @@ -151,11 +152,10 @@ mod tests { assert_eq!( encoded, hex_literal::hex!( - "046d683048656c6c6f20576f726c642105000000000000000000000000000000000000000000000010 - 0000010110000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c - 746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a86 - cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bc - a2324b6a0046395a71681be3d0c2a00" + "3048656c6c6f20576f726c64210500000000000000000000000000000000000000000000001000 + 00010110000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e9 + 9a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd + 48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00" ) ); }