diff --git a/fastpay/Cargo.toml b/fastpay/Cargo.toml index 3362c56..4fd1f25 100644 --- a/fastpay/Cargo.toml +++ b/fastpay/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] bytes = "0.5.6" clap = "2.33.3" +either = "1.6.1" env_logger = "0.7.1" failure = "0.1.8" futures = "0.3.5" diff --git a/fastpay/src/network.rs b/fastpay/src/network.rs index e3f48db..b73eeee 100644 --- a/fastpay/src/network.rs +++ b/fastpay/src/network.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::transport::*; +use either::Either; use fastpay_core::{authority::*, base_types::*, client::*, error::*, messages::*, serialize::*}; use bytes::Bytes; @@ -164,6 +165,23 @@ impl MessageHandler for RunningServerState { Err(error) => Err(error), } } + SerializedMessage::ConsensusOrder(message) => { + match self.server.state.handle_consensus_order(*message) { + Ok(Either::Left(vote)) => { + // Response + Ok(Some(serialize_vote(&vote))) + } + Ok(Either::Right(continuations)) => { + // Cross-shard requests + for continuation in continuations { + self.handle_continuation(continuation).await; + } + // No response. (TODO: this is a bit rough) + Ok(None) + } + Err(error) => Err(error), + } + } SerializedMessage::InfoQuery(message) => self .server .state diff --git a/fastpay_core/Cargo.toml b/fastpay_core/Cargo.toml index 5001e30..b9e8ca5 100644 --- a/fastpay_core/Cargo.toml +++ b/fastpay_core/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] bcs = "0.1.3" bincode = "1.3.1" +either = "1.6.1" failure = "0.1.8" futures = "0.3.5" generic-array = { version = "0.14.4", features = ["serde"] } diff --git a/fastpay_core/src/account.rs b/fastpay_core/src/account.rs index 760d5a3..ed22ad0 100644 --- a/fastpay_core/src/account.rs +++ b/fastpay_core/src/account.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{base_types::*, error::FastPayError, messages::*}; +use std::collections::BTreeMap; /// State of a FastPay account. #[derive(Debug, Default)] @@ -108,10 +109,34 @@ impl AccountState { ); Value::Confirm(request) } - Operation::CloseAccount | Operation::ChangeOwner { .. } => { + Operation::StartConsensusInstance { + new_id, accounts, .. + } => { + // Verify the new UID. + let expected_id = request.account_id.make_child(request.sequence_number); + fp_ensure!( + new_id == &expected_id, + FastPayError::InvalidNewAccountId(new_id.clone()) + ); + // Make sure accounts are unique. + let numbers = accounts + .clone() + .into_iter() + .collect::>(); + fp_ensure!( + numbers.len() == accounts.len(), + FastPayError::InvalidRequestOrder + ); + Value::Confirm(request) + } + Operation::Skip | Operation::CloseAccount | Operation::ChangeOwner { .. } => { // Nothing to check. Value::Confirm(request) } + Operation::LockInto { .. } => { + // Nothing to check. + Value::Lock(request) + } }; Ok(value) } @@ -127,7 +152,9 @@ impl AccountState { operation ); match operation { - Operation::OpenAccount { .. } => (), + Operation::OpenAccount { .. } + | Operation::StartConsensusInstance { .. } + | Operation::Skip => (), Operation::ChangeOwner { new_owner } => { self.owner = Some(*new_owner); } @@ -137,9 +164,9 @@ impl AccountState { Operation::Transfer { amount, .. } => { self.balance.try_sub_assign((*amount).into())?; } - Operation::Spend { .. } => { + Operation::Spend { .. } | Operation::LockInto { .. } => { // impossible under BFT assumptions. - unreachable!("Spend operation are never confirmed"); + unreachable!("Spend and lock operation are never confirmed"); } }; self.confirmed_log.push(certificate); diff --git a/fastpay_core/src/authority.rs b/fastpay_core/src/authority.rs index 5d7daf0..135d531 100644 --- a/fastpay_core/src/authority.rs +++ b/fastpay_core/src/authority.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - account::AccountState, base_types::*, committee::Committee, error::FastPayError, messages::*, + account::AccountState, base_types::*, committee::Committee, consensus::ConsensusState, + error::FastPayError, messages::*, }; +use either::Either; use std::collections::{BTreeMap, BTreeSet, HashSet}; #[cfg(test)] @@ -20,6 +22,8 @@ pub struct AuthorityState { pub key_pair: KeyPair, /// States of FastPay accounts. pub accounts: BTreeMap, + /// States of consensus instances. + pub instances: BTreeMap, /// The latest transaction index of the blockchain that the authority has seen. pub last_transaction_index: SequenceNumber, /// The sharding ID of this authority shard. 0 if one shard. @@ -60,6 +64,12 @@ pub trait Authority { order: CoinCreationOrder, ) -> Result<(Vec, Vec), FastPayError>; + /// Process a message meant for a consensus instance. + fn handle_consensus_order( + &mut self, + order: ConsensusOrder, + ) -> Result>, FastPayError>; + /// Force synchronization to finalize requests from Primary to FastPay. fn handle_primary_synchronization_order( &mut self, @@ -148,14 +158,25 @@ impl AuthorityState { operation: Operation, certificate: Certificate, ) -> Result<(), FastPayError> { - let recipient = operation - .recipient() - .ok_or(FastPayError::InvalidCrossShardRequest)?; - // Verify sharding. - fp_ensure!(self.in_shard(recipient), FastPayError::WrongShard); - // Execute the recipient's side of the operation. - let account = self.accounts.entry(recipient.clone()).or_default(); - account.apply_operation_as_recipient(&operation, certificate)?; + if let Some(recipient) = operation.recipient() { + fp_ensure!(self.in_shard(recipient), FastPayError::WrongShard); + // Execute the recipient's side of the operation. + let account = self.accounts.entry(recipient.clone()).or_default(); + account.apply_operation_as_recipient(&operation, certificate)?; + } + match &operation { + Operation::StartConsensusInstance { + new_id, + accounts, + functionality, + } => { + fp_ensure!(self.in_shard(new_id), FastPayError::WrongShard); + assert!(!self.instances.contains_key(new_id)); // guaranteed under BFT assumptions. + let instance = ConsensusState::new(*functionality, accounts.clone()); + self.instances.insert(new_id.clone(), instance); + } + _ => (), + } // This concludes the confirmation of `certificate`. Ok(()) } @@ -345,6 +366,167 @@ impl Authority for AuthorityState { Ok((votes, continuations)) } + /// Process a message meant for a consensus instance. + fn handle_consensus_order( + &mut self, + order: ConsensusOrder, + ) -> Result>, FastPayError> { + match order { + ConsensusOrder::Propose { + proposal, + owner, + signature, + locks, + } => { + let instance = self + .instances + .get_mut(&proposal.instance_id) + .ok_or_else(|| { + FastPayError::UnknownConsensusInstance(proposal.instance_id.clone()) + })?; + // Process lock certificates. + for lock in locks { + lock.check(&self.committee)?; + match lock.value { + Value::Lock(Request { + account_id, + operation: Operation::LockInto { instance_id, owner }, + sequence_number, + }) if instance_id == proposal.instance_id + && Some(&sequence_number) + == instance.sequence_numbers.get(&account_id) => + { + // Update locking status for `account_id`. + instance.locked_accounts.insert(account_id, owner); + instance.participants.insert(owner); + } + _ => fp_bail!(FastPayError::InvalidConsensusOrder), + } + } + // Verify the signature and that the author of the signature is authorized. + fp_ensure!( + instance.participants.contains(&owner), + FastPayError::InvalidConsensusOrder + ); + signature.check(&proposal, owner)?; + // Check validity of the proposal and obtain the corresponding requests. + let requests = instance.make_requests(proposal.decision)?; + // TODO: verify that `proposal.round` is "available". + // Verify safety. + if let Some(proposed) = &instance.proposed { + fp_ensure!( + (proposed.round == proposal.round + && proposed.decision == proposal.decision) + || proposed.round < proposal.round, + FastPayError::UnsafeConsensusProposal + ); + } + if let Some(locked) = &instance.locked { + fp_ensure!( + locked.round < proposal.round && locked.decision == proposal.decision, + FastPayError::UnsafeConsensusProposal + ); + } + // Update proposed decision. + instance.proposed = Some(proposal.clone()); + // Vote in favor of pre-commit (aka lock). + let value = Value::PreCommit { proposal, requests }; + let vote = Vote::new(value, &self.key_pair); + Ok(Either::Left(vote)) + } + ConsensusOrder::HandlePreCommit { certificate } => { + certificate.check(&self.committee)?; + let (proposal, requests) = match certificate.value { + Value::PreCommit { proposal, requests } => (proposal, requests), + _ => fp_bail!(FastPayError::InvalidConsensusOrder), + }; + let instance = self + .instances + .get_mut(&proposal.instance_id) + .ok_or_else(|| { + FastPayError::UnknownConsensusInstance(proposal.instance_id.clone()) + })?; + // Verify safety. + if let Some(proposed) = &instance.proposed { + fp_ensure!( + proposed.round <= proposal.round, + FastPayError::UnsafeConsensusPreCommit + ); + } + if let Some(locked) = &instance.locked { + fp_ensure!( + locked.round <= proposal.round, + FastPayError::UnsafeConsensusPreCommit + ); + } + // Update locked decision. + instance.locked = Some(proposal.clone()); + // Vote in favor of commit. + let value = Value::Commit { proposal, requests }; + let vote = Vote::new(value, &self.key_pair); + Ok(Either::Left(vote)) + } + ConsensusOrder::HandleCommit { certificate, locks } => { + certificate.check(&self.committee)?; + let (proposal, requests) = match &certificate.value { + Value::Commit { proposal, requests } => (proposal, requests), + _ => fp_bail!(FastPayError::InvalidConsensusOrder), + }; + // Success. + // Only execute the requests in the commit once. + let mut requests = { + if self.instances.contains_key(&proposal.instance_id) { + requests.clone() + } else { + Vec::new() + } + }; + // Process lock certificates to add skip requests if needed. + if let ConsensusDecision::Abort = &proposal.decision { + for lock in locks { + lock.check(&self.committee)?; + match lock.value { + Value::Lock(Request { + account_id, + operation: + Operation::LockInto { + instance_id, + owner: _, + }, + sequence_number, + }) if instance_id == proposal.instance_id => { + requests.push(Request { + account_id: account_id.clone(), + operation: Operation::Skip, + sequence_number, + }); + } + _ => fp_bail!(FastPayError::InvalidConsensusOrder), + } + } + } + // Remove the consensus instance if needed. + self.instances.remove(&proposal.instance_id); + // Prepate cross-requests. + let continuations = requests + .iter() + .map(|request| { + let shard_id = self.which_shard(&request.account_id); + CrossShardContinuation::Request { + shard_id, + request: Box::new(CrossShardRequest::ProcessConfirmedRequest { + request: request.clone(), + certificate: certificate.clone(), + }), + } + }) + .collect(); + Ok(Either::Right(continuations)) + } + } + } + + /// Finalize a request from Primary. fn handle_primary_synchronization_order( &mut self, order: PrimarySynchronizationOrder, @@ -387,6 +569,13 @@ impl Authority for AuthorityState { self.accounts.remove(&account_id); Ok(()) } + CrossShardRequest::ProcessConfirmedRequest { + request, + certificate, + } => { + self.process_confirmed_request(request, certificate)?; // TODO: process continuations + Ok(()) + } } } @@ -418,6 +607,7 @@ impl AuthorityState { name, key_pair, accounts: BTreeMap::new(), + instances: BTreeMap::new(), last_transaction_index: SequenceNumber::new(), shard_id: 0, number_of_shards: 1, @@ -435,6 +625,7 @@ impl AuthorityState { name: key_pair.public(), key_pair, accounts: BTreeMap::new(), + instances: BTreeMap::new(), last_transaction_index: SequenceNumber::new(), shard_id, number_of_shards, diff --git a/fastpay_core/src/client.rs b/fastpay_core/src/client.rs index 2714826..e97086a 100644 --- a/fastpay_core/src/client.rs +++ b/fastpay_core/src/client.rs @@ -718,7 +718,10 @@ where | Operation::SpendAndTransfer { .. } => { self.key_pair = None; } - Operation::OpenAccount { .. } => (), + Operation::OpenAccount { .. } + | Operation::Skip + | Operation::LockInto { .. } + | Operation::StartConsensusInstance { .. } => (), } // Record certificate. self.sent_certificates.push(certificate); diff --git a/fastpay_core/src/consensus.rs b/fastpay_core/src/consensus.rs new file mode 100644 index 0000000..bc0b1b8 --- /dev/null +++ b/fastpay_core/src/consensus.rs @@ -0,0 +1,74 @@ +// Copyright (c) Facebook Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{base_types::*, error::FastPayError, messages::*}; +use std::collections::{BTreeMap, BTreeSet}; + +/// State of a one-shot consensus instance. +#[derive(Debug)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct ConsensusState { + /// Functionality realized by this instance. + pub functionality: Functionality, + /// Accounts expected to be locked and managed by the protocol. + pub accounts: Vec, + /// Expected sequence number for each locked account. + pub sequence_numbers: BTreeMap, + /// Accounts locked so far and for which controlling key. + pub locked_accounts: BTreeMap, + /// Authorized participants. + pub participants: BTreeSet, + /// Pending proposal. + pub proposed: Option, + /// Pending locked (i.e. pre-committed) proposal. + pub locked: Option, +} + +impl ConsensusState { + pub fn new(functionality: Functionality, expected: Vec<(AccountId, SequenceNumber)>) -> Self { + let accounts: Vec<_> = expected.iter().map(|(id, _)| id.clone()).collect(); + let sequence_numbers: BTreeMap<_, _> = expected.into_iter().collect(); + assert_eq!(accounts.len(), sequence_numbers.len()); + Self { + functionality, + accounts, + sequence_numbers, + locked_accounts: BTreeMap::new(), + participants: BTreeSet::new(), + proposed: None, + locked: None, + } + } + + pub(crate) fn make_requests( + &self, + decision: ConsensusDecision, + ) -> Result, FastPayError> { + match (self.functionality, decision) { + (_, ConsensusDecision::Abort) => Ok(Vec::new()), + (Functionality::AtomicSwap, ConsensusDecision::Confirm) => { + let num_accounts = self.accounts.len(); + for id in &self.accounts { + fp_ensure!( + self.locked_accounts.contains_key(id), + FastPayError::MissingConsensusLock { + account_id: id.clone() + } + ); + } + let mut requests = Vec::new(); + for (i, id) in self.accounts.iter().enumerate() { + let sequence_number = *self.sequence_numbers.get(id).unwrap(); + let next_id = &self.accounts[(i + 1) % num_accounts]; + let new_owner = *self.locked_accounts.get(next_id).unwrap(); + requests.push(Request { + account_id: id.clone(), + operation: Operation::ChangeOwner { new_owner }, + sequence_number, + }); + } + Ok(requests) + } + } + } +} diff --git a/fastpay_core/src/error.rs b/fastpay_core/src/error.rs index 907f5ca..f2b1f62 100644 --- a/fastpay_core/src/error.rs +++ b/fastpay_core/src/error.rs @@ -120,4 +120,21 @@ pub enum FastPayError { UnexpectedMessage, #[fail(display = "Network error while querying service: {:?}.", error)] ClientIoError { error: String }, + + // Consensus + #[fail(display = "Unknown consensus instance {:?}", 0)] + UnknownConsensusInstance(AccountId), + #[fail(display = "Invalid consensus order.")] + InvalidConsensusOrder, + #[fail(display = "Unsafe consensus proposal.")] + UnsafeConsensusProposal, + #[fail( + display = "The following account has not been locked yet: {}", + account_id + )] + MissingConsensusLock { account_id: AccountId }, + #[fail(display = "Invalid consensus proposal.")] + InvalidConsensusProposal, + #[fail(display = "Unsafe consensus pre-commit.")] + UnsafeConsensusPreCommit, } diff --git a/fastpay_core/src/generate_format.rs b/fastpay_core/src/generate_format.rs index 4693af1..efab6a4 100644 --- a/fastpay_core/src/generate_format.rs +++ b/fastpay_core/src/generate_format.rs @@ -17,6 +17,8 @@ fn get_registry() -> Result { tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; + tracer.trace_type::(&samples)?; + tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; diff --git a/fastpay_core/src/lib.rs b/fastpay_core/src/lib.rs index 1bbf02d..7015a85 100644 --- a/fastpay_core/src/lib.rs +++ b/fastpay_core/src/lib.rs @@ -11,6 +11,7 @@ pub mod authority; pub mod base_types; pub mod client; pub mod committee; +pub mod consensus; pub mod downloader; pub mod fastpay_smart_contract; pub mod messages; diff --git a/fastpay_core/src/messages.rs b/fastpay_core/src/messages.rs index fcd4188..f922ba2 100644 --- a/fastpay_core/src/messages.rs +++ b/fastpay_core/src/messages.rs @@ -39,6 +39,8 @@ pub enum Operation { new_id: AccountId, new_owner: AccountOwner, }, + /// Do nothing. (This can be used for testing or to unlock accounts after a consensus decisions.) + Skip, /// Close the account. CloseAccount, /// Change the authentication key of the account. @@ -56,6 +58,23 @@ pub enum Operation { amount: Amount, user_data: UserData, }, + /// Start a consensus protocol `new_id` to manage the given `accounts`. + StartConsensusInstance { + new_id: AccountId, + functionality: Functionality, + accounts: Vec<(AccountId, SequenceNumber)>, + }, + /// Lock the account into the given consensus instance, managed by the given key. + LockInto { + instance_id: AccountId, + owner: AccountOwner, + }, +} + +/// A one-shot funtionality implemented in a consensus instance. +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] +pub enum Functionality { + AtomicSwap, } /// A request containing an account operation. @@ -92,13 +111,44 @@ pub struct Coin { pub seed: u128, } +/// Functionality-dependent consensus decision. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)] +pub enum ConsensusDecision { + Abort, + Confirm, +} + +/// The proposal of a particular decision during consensus. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +pub struct ConsensusProposal { + pub instance_id: AccountId, + pub round: SequenceNumber, + pub decision: ConsensusDecision, +} + /// A statement to be certified by the authorities. // TODO: decide if we split Vote & Certificate in one type per kind of value. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub enum Value { + // -- Account management -- + /// The request was validated but confirmation will require additional steps. Lock(Request), + /// The request is ready to be confirmed (i.e. executed). Confirm(Request), + // -- Assets -- + /// This coin is ready to be spent. Coin(Coin), + // -- Consensus -- + /// The proposal was validated but confirmation will require additional steps. + PreCommit { + proposal: ConsensusProposal, + requests: Vec, + }, + /// The proposal is ready to be committed, thus confirming a number of requests. + Commit { + proposal: ConsensusProposal, + requests: Vec, + }, } /// The balance of an account plus linked coins to be used in a coin creation contract. @@ -131,6 +181,25 @@ pub struct CoinCreationOrder { pub locks: Vec, } +/// Same as RequestOrder but meant for a consensus instance. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub enum ConsensusOrder { + Propose { + proposal: ConsensusProposal, + owner: AccountOwner, + signature: Signature, + locks: Vec, + }, + HandlePreCommit { + certificate: Certificate, + }, + HandleCommit { + certificate: Certificate, + locks: Vec, + }, +} + /// A vote on a statement from an authority. #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(test, derive(Eq, PartialEq))] @@ -182,8 +251,16 @@ pub struct AccountInfoResponse { #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(test, derive(Eq, PartialEq))] pub enum CrossShardRequest { - UpdateRecipient { certificate: Certificate }, - DestroyAccount { account_id: AccountId }, + UpdateRecipient { + certificate: Certificate, + }, + DestroyAccount { + account_id: AccountId, + }, + ProcessConfirmedRequest { + request: Request, + certificate: Certificate, + }, } impl Operation { @@ -199,12 +276,7 @@ impl Operation { .. } | OpenAccount { new_id: id, .. } => Some(id), - - Operation::Spend { .. } - | Operation::SpendAndTransfer { .. } - | Operation::CloseAccount - | Transfer { .. } - | ChangeOwner { .. } => None, + _ => None, } } @@ -424,3 +496,4 @@ impl ConfirmationOrder { impl BcsSignable for RequestValue {} impl BcsSignable for Value {} impl BcsSignable for CoinCreationContract {} +impl BcsSignable for ConsensusProposal {} diff --git a/fastpay_core/src/serialize.rs b/fastpay_core/src/serialize.rs index 5eb4994..a929b3d 100644 --- a/fastpay_core/src/serialize.rs +++ b/fastpay_core/src/serialize.rs @@ -17,6 +17,7 @@ pub enum SerializedMessage { RequestOrder(Box), ConfirmationOrder(Box), CoinCreationOrder(Box), + ConsensusOrder(Box), InfoQuery(Box), // Outbound Vote(Box), @@ -36,6 +37,7 @@ enum ShallowSerializedMessage<'a> { RequestOrder(&'a RequestOrder), ConfirmationOrder(&'a ConfirmationOrder), CoinCreationOrder(&'a CoinCreationOrder), + ConsensusOrder(&'a ConsensusOrder), InfoQuery(&'a AccountInfoQuery), // Outbound Vote(&'a Vote), @@ -120,6 +122,10 @@ pub fn serialize_votes(value: &[Vote]) -> Vec { serialize(&ShallowSerializedMessage::Votes(value)) } +pub fn serialize_consensus_order(value: &ConsensusOrder) -> Vec { + serialize(&ShallowSerializedMessage::ConsensusOrder(value)) +} + pub fn serialize_vote(value: &Vote) -> Vec { serialize(&ShallowSerializedMessage::Vote(value)) } diff --git a/fastpay_core/tests/staged/fastpay.yaml b/fastpay_core/tests/staged/fastpay.yaml index 3670845..1f84c94 100644 --- a/fastpay_core/tests/staged/fastpay.yaml +++ b/fastpay_core/tests/staged/fastpay.yaml @@ -91,6 +91,47 @@ ConfirmationOrder: STRUCT: - certificate: TYPENAME: Certificate +ConsensusDecision: + ENUM: + 0: + Abort: UNIT + 1: + Confirm: UNIT +ConsensusOrder: + ENUM: + 0: + Propose: + STRUCT: + - proposal: + TYPENAME: ConsensusProposal + - owner: + TYPENAME: PublicKeyBytes + - signature: + TYPENAME: Signature + - locks: + SEQ: + TYPENAME: Certificate + 1: + HandlePreCommit: + STRUCT: + - certificate: + TYPENAME: Certificate + 2: + HandleCommit: + STRUCT: + - certificate: + TYPENAME: Certificate + - locks: + SEQ: + TYPENAME: Certificate +ConsensusProposal: + STRUCT: + - instance_id: + TYPENAME: AccountId + - round: + TYPENAME: SequenceNumber + - decision: + TYPENAME: ConsensusDecision CrossShardRequest: ENUM: 0: @@ -103,6 +144,13 @@ CrossShardRequest: STRUCT: - account_id: TYPENAME: AccountId + 2: + ProcessConfirmedRequest: + STRUCT: + - request: + TYPENAME: Request + - certificate: + TYPENAME: Certificate FastPayError: ENUM: 0: @@ -189,6 +237,27 @@ FastPayError: ClientIoError: STRUCT: - error: STR + 33: + UnknownConsensusInstance: + NEWTYPE: + TYPENAME: AccountId + 34: + InvalidConsensusOrder: UNIT + 35: + UnsafeConsensusProposal: UNIT + 36: + MissingConsensusLock: + STRUCT: + - account_id: + TYPENAME: AccountId + 37: + InvalidConsensusProposal: UNIT + 38: + UnsafeConsensusPreCommit: UNIT +Functionality: + ENUM: + 0: + AtomicSwap: UNIT HashValue: NEWTYPESTRUCT: TUPLEARRAY: @@ -213,20 +282,22 @@ Operation: - new_owner: TYPENAME: PublicKeyBytes 2: - CloseAccount: UNIT + Skip: UNIT 3: + CloseAccount: UNIT + 4: ChangeOwner: STRUCT: - new_owner: TYPENAME: PublicKeyBytes - 4: + 5: Spend: STRUCT: - account_balance: TYPENAME: Amount - contract_hash: TYPENAME: HashValue - 5: + 6: SpendAndTransfer: STRUCT: - recipient: @@ -235,6 +306,25 @@ Operation: TYPENAME: Amount - user_data: TYPENAME: UserData + 7: + StartConsensusInstance: + STRUCT: + - new_id: + TYPENAME: AccountId + - functionality: + TYPENAME: Functionality + - accounts: + SEQ: + TUPLE: + - TYPENAME: AccountId + - TYPENAME: SequenceNumber + 8: + LockInto: + STRUCT: + - instance_id: + TYPENAME: AccountId + - owner: + TYPENAME: PublicKeyBytes PublicKeyBytes: NEWTYPESTRUCT: TUPLEARRAY: @@ -283,27 +373,31 @@ SerializedMessage: NEWTYPE: TYPENAME: CoinCreationOrder 3: + ConsensusOrder: + NEWTYPE: + TYPENAME: ConsensusOrder + 4: InfoQuery: NEWTYPE: TYPENAME: AccountInfoQuery - 4: + 5: Vote: NEWTYPE: TYPENAME: Vote - 5: + 6: Votes: NEWTYPE: SEQ: TYPENAME: Vote - 6: + 7: InfoResponse: NEWTYPE: TYPENAME: AccountInfoResponse - 7: + 8: Error: NEWTYPE: TYPENAME: FastPayError - 8: + 9: CrossShardRequest: NEWTYPE: TYPENAME: CrossShardRequest @@ -332,6 +426,22 @@ Value: Coin: NEWTYPE: TYPENAME: Coin + 3: + PreCommit: + STRUCT: + - proposal: + TYPENAME: ConsensusProposal + - requests: + SEQ: + TYPENAME: Request + 4: + Commit: + STRUCT: + - proposal: + TYPENAME: ConsensusProposal + - requests: + SEQ: + TYPENAME: Request Vote: STRUCT: - value: