diff --git a/src/encoding/mod.rs b/src/encoding/mod.rs index 03e2192..f7bab0d 100644 --- a/src/encoding/mod.rs +++ b/src/encoding/mod.rs @@ -3,4 +3,4 @@ mod serializer; // place the deserializer and serializer modules in the encoding module. pub use deserializer::{from_reader, Deserializer, Error as DeserializeError}; -pub use serializer::{to_bytes, to_writer, Error as SerializeError}; +pub use serializer::{serialize_array, to_bytes, to_writer, Error as SerializeError}; diff --git a/src/encoding/serializer.rs b/src/encoding/serializer.rs index 8d4e1ac..6c1d263 100644 --- a/src/encoding/serializer.rs +++ b/src/encoding/serializer.rs @@ -1,5 +1,5 @@ use serde::{ - ser::{self, SerializeSeq}, + ser::{self, SerializeTuple}, Serialize, }; use std::fmt::Display; @@ -21,6 +21,22 @@ pub enum Error { IO(#[from] io::Error), } +// Helper function to serialize array of fixed size. Serde only implements +// Serialize for arrays up to 32 elements in size. The reason being that Rust +// didn't have const generics back then and now they can't easily upgrade +// without breaking code. +pub fn serialize_array(t: &[T; N], serializer: S) -> Result +where + S: ser::Serializer, + T: Serialize, +{ + let mut ser_tuple = serializer.serialize_tuple(N)?; + for elem in t { + ser_tuple.serialize_element(elem)?; + } + ser_tuple.end() +} + // Implement ser::Error for Error impl ser::Error for Error { fn custom(msg: T) -> Self @@ -147,9 +163,9 @@ impl ser::Serializer for &mut Serializer<'_, W> { } // 'some' is serialized by writing a '1' byte followed by the value - fn serialize_some(self, value: &T) -> Result + fn serialize_some(self, value: &T) -> Result where - T: Serialize, + T: Serialize + ?Sized, { self.writer.write_all(&[1])?; value.serialize(self) @@ -177,19 +193,19 @@ impl ser::Serializer for &mut Serializer<'_, W> { // As is done here, serializers are encouraged to treat newtype structs as // insignificant wrappers around the data they contain - fn serialize_newtype_struct( + fn serialize_newtype_struct( self, _name: &'static str, value: &T, ) -> Result where - T: Serialize, + T: Serialize + ?Sized, { value.serialize(self) } // newtype_variants are not supported - fn serialize_newtype_variant( + fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, @@ -197,7 +213,7 @@ impl ser::Serializer for &mut Serializer<'_, W> { _value: &T, ) -> Result where - T: Serialize, + T: Serialize + ?Sized, { Err(Error::UnsupportedType("newtype_variant")) } diff --git a/src/lib.rs b/src/lib.rs index 6d2fbce..1d48365 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,4 @@ use core::fmt; -use std::io::{Error, Write}; - -pub trait SiaEncodable { - fn encode(&self, w: &mut W) -> Result<(), Error>; -} pub mod address; pub mod consensus; diff --git a/src/signing.rs b/src/signing.rs index e462ad7..b3af6b4 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -4,7 +4,7 @@ use std::time::SystemTime; use crate::consensus::ChainIndex; use crate::encoding::{to_writer, SerializeError}; use crate::transactions::{CoveredFields, Transaction}; -use crate::{Algorithm, Hash256, HexParseError, SiaEncodable}; +use crate::{Algorithm, Hash256, HexParseError}; use blake2b_simd::Params; use ed25519_dalek::{Signature as ED25519Signature, Signer, SigningKey, Verifier, VerifyingKey}; use serde::ser::SerializeStruct; @@ -268,7 +268,7 @@ impl SigningState { state.update(&(txn.siacoin_inputs.len() as u64).to_le_bytes()); for input in txn.siacoin_inputs.iter() { state.update(self.replay_prefix()); - input.encode(&mut state).unwrap(); + to_writer(&mut state, input)?; } state.update(&(txn.siacoin_outputs.len() as u64).to_le_bytes()); @@ -278,23 +278,23 @@ impl SigningState { state.update(&(txn.file_contracts.len() as u64).to_le_bytes()); for file_contract in txn.file_contracts.iter() { - file_contract.encode(&mut state).unwrap(); + to_writer(&mut state, file_contract)?; } state.update(&(txn.file_contract_revisions.len() as u64).to_le_bytes()); for file_contract_revision in txn.file_contract_revisions.iter() { - file_contract_revision.encode(&mut state).unwrap(); + to_writer(&mut state, file_contract_revision)?; } state.update(&(txn.storage_proofs.len() as u64).to_le_bytes()); for storage_proof in txn.storage_proofs.iter() { - storage_proof.encode(&mut state).unwrap(); + to_writer(&mut state, storage_proof).unwrap(); } state.update(&(txn.siafund_inputs.len() as u64).to_le_bytes()); for input in txn.siafund_inputs.iter() { state.update(self.replay_prefix()); - input.encode(&mut state).unwrap(); + to_writer(&mut state, input).unwrap(); } state.update(&(txn.siafund_outputs.len() as u64).to_le_bytes()); @@ -333,7 +333,7 @@ impl SigningState { for i in covered_fields.siacoin_inputs.into_iter() { state.update(self.replay_prefix()); - txn.siacoin_inputs[i].encode(&mut state).unwrap(); + to_writer(&mut state, &txn.siacoin_inputs[i])?; } for i in covered_fields.siacoin_outputs.into_iter() { @@ -341,25 +341,25 @@ impl SigningState { } for i in covered_fields.file_contracts.into_iter() { - txn.file_contracts[i].encode(&mut state).unwrap(); + to_writer(&mut state, &txn.file_contracts[i])?; } for i in covered_fields.file_contract_revisions.into_iter() { - txn.file_contract_revisions[i].encode(&mut state).unwrap(); + to_writer(&mut state, &txn.file_contract_revisions[i])?; } for i in covered_fields.storage_proofs.into_iter() { - txn.storage_proofs[i].encode(&mut state).unwrap(); + to_writer(&mut state, &txn.storage_proofs[i])?; } for i in covered_fields.siafund_inputs.into_iter() { - txn.siafund_inputs[i].encode(&mut state).unwrap(); + to_writer(&mut state, &txn.siafund_inputs[i])?; state.update(self.replay_prefix()); } for i in covered_fields.siafund_outputs.into_iter() { - to_writer(&mut state, &txn.siafund_outputs[i])?; state.update(self.replay_prefix()); + to_writer(&mut state, &txn.siafund_outputs[i])?; } for i in covered_fields.miner_fees.into_iter() { diff --git a/src/spendpolicy.rs b/src/spendpolicy.rs index be473a9..fc64811 100644 --- a/src/spendpolicy.rs +++ b/src/spendpolicy.rs @@ -233,9 +233,9 @@ impl SpendPolicy { } } - /// Encode the policy to a writer. This is used by the SiaEncodable trait to - /// handle recursive threshold policies. The version byte is only written - /// for the top-level policy. + /// Encode the policy to a writer. This is used to handle recursive + /// threshold policies. The version byte is only written for the top-level + /// policy. fn serialize_policy(&self, s: &mut S) -> Result<(), S::Error> { s.serialize_element(&self.type_prefix())?; // type prefix match self { diff --git a/src/transactions.rs b/src/transactions.rs index 4e84505..ff1f5a2 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -1,11 +1,10 @@ use core::fmt; -use std::io::{Error, Write}; -use crate::encoding::to_writer; +use crate::encoding::{serialize_array, to_writer}; use crate::Currency; use crate::Signature; use crate::{Address, UnlockConditions}; -use crate::{Hash256, HexParseError, SiaEncodable}; +use crate::{Hash256, HexParseError}; use blake2b_simd::{Params, State}; use serde::Serialize; @@ -16,7 +15,7 @@ const SIAFUND_OUTPUT_ID_PREFIX: [u8; 16] = [ b's', b'i', b'a', b'f', b'u', b'n', b'd', b' ', b'o', b'u', b't', b'p', b'u', b't', 0, 0, ]; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct SiacoinOutputID([u8; 32]); impl SiacoinOutputID { @@ -44,39 +43,25 @@ impl SiacoinOutputID { } } -impl SiaEncodable for SiacoinOutputID { - fn encode(&self, w: &mut W) -> Result<(), Error> { - w.write_all(&self.0) - } -} - impl fmt::Display for SiacoinOutputID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "scoid:{}", hex::encode(self.0)) } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct SiacoinInput { pub parent_id: SiacoinOutputID, pub unlock_conditions: UnlockConditions, } -impl SiaEncodable for SiacoinInput { - fn encode(&self, w: &mut W) -> Result<(), Error> { - self.parent_id.encode(w)?; - to_writer(w, &self.unlock_conditions).unwrap(); - Ok(()) - } -} - #[derive(Debug, Clone, Serialize)] pub struct SiacoinOutput { pub value: Currency, pub address: Address, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct SiafundOutputID([u8; 32]); impl SiafundOutputID { @@ -106,28 +91,13 @@ impl fmt::Display for SiafundOutputID { } } -impl SiaEncodable for SiafundOutputID { - fn encode(&self, w: &mut W) -> Result<(), Error> { - w.write_all(&self.0) - } -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct SiafundInput { pub parent_id: SiafundOutputID, pub unlock_conditions: UnlockConditions, pub claim_address: Address, } -impl SiaEncodable for SiafundInput { - fn encode(&self, w: &mut W) -> Result<(), Error> { - self.parent_id.encode(w)?; - to_writer(w, &self.unlock_conditions).unwrap(); - to_writer(w, &self.claim_address).unwrap(); // TODO: handle error - Ok(()) - } -} - #[derive(Debug, Clone, Serialize)] pub struct SiafundOutput { pub value: Currency, @@ -135,7 +105,7 @@ pub struct SiafundOutput { pub claim_start: Currency, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct FileContractID([u8; 32]); impl FileContractID { @@ -171,7 +141,7 @@ impl fmt::Display for FileContractID { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct FileContract { pub file_size: u64, pub file_merkle_root: Hash256, @@ -184,27 +154,7 @@ pub struct FileContract { pub revision_number: u64, } -impl SiaEncodable for FileContract { - fn encode(&self, w: &mut W) -> Result<(), Error> { - w.write_all(&self.file_size.to_le_bytes())?; - w.write_all(&self.file_merkle_root.0)?; - w.write_all(&self.window_start.to_le_bytes())?; - w.write_all(&self.window_end.to_le_bytes())?; - to_writer(w, &self.payout).unwrap(); - w.write_all(&(self.valid_proof_outputs.len() as u64).to_le_bytes())?; - for output in &self.valid_proof_outputs { - to_writer(w, output).unwrap(); - } - w.write_all(&(self.missed_proof_outputs.len() as u64).to_le_bytes())?; - for output in &self.missed_proof_outputs { - to_writer(w, output).unwrap(); - } - to_writer(w, &self.unlock_hash).unwrap(); // TODO: handle error - w.write_all(&self.revision_number.to_le_bytes()) - } -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct FileContractRevision { pub parent_id: FileContractID, pub unlock_conditions: UnlockConditions, @@ -218,47 +168,14 @@ pub struct FileContractRevision { pub unlock_hash: Address, } -impl SiaEncodable for FileContractRevision { - fn encode(&self, w: &mut W) -> Result<(), Error> { - w.write_all(self.parent_id.as_ref())?; - to_writer(w, &self.unlock_conditions).unwrap(); - w.write_all(&self.revision_number.to_le_bytes())?; - w.write_all(&self.file_size.to_le_bytes())?; - w.write_all(&self.file_merkle_root.0)?; - w.write_all(&self.window_start.to_le_bytes())?; - w.write_all(&self.window_end.to_le_bytes())?; - w.write_all(&(self.valid_proof_outputs.len() as u64).to_le_bytes())?; - for output in &self.valid_proof_outputs { - to_writer(w, output).unwrap(); - } - w.write_all(&(self.missed_proof_outputs.len() as u64).to_le_bytes())?; - for output in &self.missed_proof_outputs { - to_writer(w, output).unwrap(); - } - to_writer(w, &self.unlock_hash).unwrap(); // TODO: handle error - Ok(()) - } -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct StorageProof { pub parent_id: FileContractID, + #[serde(serialize_with = "serialize_array")] pub leaf: [u8; 64], pub proof: Vec, } -impl SiaEncodable for StorageProof { - fn encode(&self, w: &mut W) -> Result<(), Error> { - w.write_all(self.parent_id.as_ref())?; - w.write_all(&self.leaf)?; - w.write_all(&(self.proof.len() as u64).to_le_bytes())?; - for proof in &self.proof { - w.write_all(proof.as_ref())?; - } - Ok(()) - } -} - #[derive(Debug, Default, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct CoveredFields { @@ -321,18 +238,18 @@ impl fmt::Display for TransactionID { } } -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Serialize)] pub struct Transaction { - pub miner_fees: Vec, pub siacoin_inputs: Vec, pub siacoin_outputs: Vec, - pub siafund_inputs: Vec, - pub siafund_outputs: Vec, pub file_contracts: Vec, pub file_contract_revisions: Vec, pub storage_proofs: Vec, - pub signatures: Vec, + pub siafund_inputs: Vec, + pub siafund_outputs: Vec, + pub miner_fees: Vec, pub arbitrary_data: Vec>, + pub signatures: Vec, } impl Transaction { @@ -341,7 +258,7 @@ impl Transaction { buf.extend_from_slice(&(self.siacoin_inputs.len() as u64).to_le_bytes()); for input in &self.siacoin_inputs { - input.encode(&mut buf).unwrap(); + to_writer(&mut buf, input).unwrap(); } buf.extend_from_slice(&(self.siacoin_outputs.len() as u64).to_le_bytes()); @@ -351,22 +268,22 @@ impl Transaction { buf.extend_from_slice(&(self.file_contracts.len() as u64).to_le_bytes()); for file_contract in &self.file_contracts { - file_contract.encode(&mut buf).unwrap(); + to_writer(&mut buf, file_contract).unwrap(); } buf.extend_from_slice(&(self.file_contract_revisions.len() as u64).to_le_bytes()); for file_contract_revision in &self.file_contract_revisions { - file_contract_revision.encode(&mut buf).unwrap(); + to_writer(&mut buf, file_contract_revision).unwrap(); } buf.extend_from_slice(&(self.storage_proofs.len() as u64).to_le_bytes()); for storage_proof in &self.storage_proofs { - storage_proof.encode(&mut buf).unwrap(); + to_writer(&mut buf, storage_proof).unwrap(); } buf.extend_from_slice(&(self.siafund_inputs.len() as u64).to_le_bytes()); for input in &self.siafund_inputs { - input.encode(&mut buf).unwrap(); + to_writer(&mut buf, input).unwrap(); } buf.extend_from_slice(&(self.siafund_outputs.len() as u64).to_le_bytes()); @@ -390,7 +307,7 @@ impl Transaction { pub fn hash_no_sigs(&self, state: &mut State) { state.update(&(self.siacoin_inputs.len() as u64).to_le_bytes()); for input in self.siacoin_inputs.iter() { - input.encode(state).unwrap(); + to_writer(state, input).unwrap(); } state.update(&(self.siacoin_outputs.len() as u64).to_le_bytes()); @@ -400,22 +317,22 @@ impl Transaction { state.update(&(self.file_contracts.len() as u64).to_le_bytes()); for file_contract in self.file_contracts.iter() { - file_contract.encode(state).unwrap(); + to_writer(state, file_contract).unwrap(); } state.update(&(self.file_contract_revisions.len() as u64).to_le_bytes()); for file_contract_revision in self.file_contract_revisions.iter() { - file_contract_revision.encode(state).unwrap(); + to_writer(state, file_contract_revision).unwrap(); } state.update(&(self.storage_proofs.len() as u64).to_le_bytes()); for storage_proof in self.storage_proofs.iter() { - storage_proof.encode(state).unwrap(); + to_writer(state, storage_proof).unwrap(); } state.update(&(self.siafund_inputs.len() as u64).to_le_bytes()); for input in self.siafund_inputs.iter() { - input.encode(state).unwrap(); + to_writer(state, input).unwrap(); } state.update(&(self.siafund_outputs.len() as u64).to_le_bytes()); @@ -477,50 +394,6 @@ impl Transaction { } } -impl SiaEncodable for Transaction { - fn encode(&self, w: &mut W) -> Result<(), Error> { - w.write_all(&(self.siacoin_inputs.len() as u64).to_le_bytes())?; - for input in &self.siacoin_inputs { - input.encode(w)?; - } - w.write_all(&(self.siacoin_outputs.len() as u64).to_le_bytes())?; - for output in &self.siacoin_outputs { - to_writer(w, output).unwrap(); - } - w.write_all(&(self.file_contracts.len() as u64).to_le_bytes())?; - for file_contract in &self.file_contracts { - file_contract.encode(w)?; - } - w.write_all(&(self.file_contract_revisions.len() as u64).to_le_bytes())?; - for file_contract_revision in &self.file_contract_revisions { - file_contract_revision.encode(w)?; - } - w.write_all(&(self.storage_proofs.len() as u64).to_le_bytes())?; - for storage_proof in &self.storage_proofs { - storage_proof.encode(w)?; - } - w.write_all(&(self.siafund_inputs.len() as u64).to_le_bytes())?; - for input in &self.siafund_inputs { - input.encode(w)?; - } - w.write_all(&(self.siafund_outputs.len() as u64).to_le_bytes())?; - for output in &self.siafund_outputs { - to_writer(w, output).unwrap(); - } - w.write_all(&(self.miner_fees.len() as u64).to_le_bytes())?; - for fee in &self.miner_fees { - to_writer(w, fee).unwrap(); - } - w.write_all(&(self.arbitrary_data.len() as u64).to_le_bytes())?; - for data in &self.arbitrary_data { - w.write_all(&(data.len() as u64).to_le_bytes())?; - w.write_all(data)?; - } - to_writer(w, &self.signatures).unwrap(); - Ok(()) - } -} - #[cfg(test)] mod tests { use crate::encoding::to_bytes;