diff --git a/fastpay/src/client.rs b/fastpay/src/client.rs index 8a61423e1455c..47b08a0de4b5b 100644 --- a/fastpay/src/client.rs +++ b/fastpay/src/client.rs @@ -80,8 +80,7 @@ fn make_client_state( account.key.copy(), committee, authority_clients, - account.sent_certificates.clone(), - account.received_certificates.clone(), + account.certificates.clone(), account.object_ids.clone(), ) } diff --git a/fastpay/src/config.rs b/fastpay/src/config.rs index cfbb59b389b75..2d9a644684e9d 100644 --- a/fastpay/src/config.rs +++ b/fastpay/src/config.rs @@ -102,8 +102,7 @@ pub struct UserAccount { pub key: KeyPair, pub object_ids: BTreeMap, pub gas_object_ids: BTreeSet, // Every id in gas_object_ids should also be in object_ids. - pub sent_certificates: Vec, - pub received_certificates: Vec, + pub certificates: BTreeMap, } impl UserAccount { @@ -119,8 +118,7 @@ impl UserAccount { key, object_ids, gas_object_ids, - sent_certificates: Vec::new(), - received_certificates: Vec::new(), + certificates: BTreeMap::new(), } } } @@ -225,8 +223,7 @@ impl AccountsConfig { .get_mut(&state.address()) .expect("Updated account should already exist"); account.object_ids = state.object_ids().clone(); - account.sent_certificates = state.sent_certificates().clone(); - account.received_certificates = state.received_certificates().cloned().collect(); + account.certificates = state.all_certificates().clone(); } pub fn update_for_received_transfer(&mut self, certificate: CertifiedOrder) { @@ -234,14 +231,10 @@ impl AccountsConfig { OrderKind::Transfer(transfer) => { if let Address::FastPay(recipient) = &transfer.recipient { if let Some(config) = self.accounts.get_mut(recipient) { - if let Err(position) = config - .received_certificates - .binary_search_by_key(&certificate.order.digest(), |cert| { - cert.order.digest() - }) - { - config.received_certificates.insert(position, certificate) - } + config + .certificates + .entry(certificate.order.digest()) + .or_insert(certificate); } } } diff --git a/fastpay_core/src/authority.rs b/fastpay_core/src/authority.rs index 2b038f23d0525..b4e4ec606a0fc 100644 --- a/fastpay_core/src/authority.rs +++ b/fastpay_core/src/authority.rs @@ -292,31 +292,29 @@ impl AuthorityState { &self, request: ObjectInfoRequest, ) -> Result { - if let Some(seq) = request.request_sequence_number { + let requested_certificate = if let Some(seq) = request.request_sequence_number { // TODO(https://github.com/MystenLabs/fastnft/issues/123): Here we need to develop a strategy // to provide back to the client the object digest for specific objects requested. Probably, // we have to return the full ObjectRef and why not the actual full object here. - let obj = self - .object_state(&request.object_id) - .await - .map_err(|_| FastPayError::ObjectNotFound)?; // Get the Transaction Digest that created the object - let transaction_digest = self - .parent(&(request.object_id, seq.increment()?, obj.digest())) - .await + let parent_iterator = self + .get_parent_iterator(request.object_id, Some(seq.increment()?)) + .await?; + let (_, transaction_digest) = parent_iterator + .first() .ok_or(FastPayError::CertificateNotfound)?; // Get the cert from the transaction digest - let requested_certificate = Some( - self.read_certificate(&transaction_digest) + Some( + self.read_certificate(transaction_digest) .await? .ok_or(FastPayError::CertificateNotfound)?, - ); - self.make_object_info(request.object_id, requested_certificate) - .await + ) } else { - self.make_object_info(request.object_id, None).await - } + None + }; + self.make_object_info(request.object_id, requested_certificate) + .await } } diff --git a/fastpay_core/src/client.rs b/fastpay_core/src/client.rs index 0d6821de3a5cb..5ddec7de1f0da 100644 --- a/fastpay_core/src/client.rs +++ b/fastpay_core/src/client.rs @@ -3,6 +3,7 @@ use crate::downloader::*; use anyhow::{bail, ensure}; +use fastx_types::messages::Address::FastPay; use fastx_types::{ base_types::*, committee::Committee, error::FastPayError, fp_ensure, messages::*, }; @@ -10,7 +11,8 @@ use futures::{future, StreamExt, TryFutureExt}; use move_core_types::identifier::Identifier; use move_core_types::language_storage::TypeTag; use rand::seq::SliceRandom; -use std::collections::{btree_map, BTreeMap, BTreeSet, HashMap}; +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::time::Duration; use tokio::time::timeout; @@ -59,14 +61,12 @@ pub struct ClientState { pending_transfer: Option, // The remaining fields are used to minimize networking, and may not always be persisted locally. - /// Transfer certificates that we have created ("sent"). - /// Normally, `sent_certificates` should contain one certificate for each index in `0..next_sequence_number`. - sent_certificates: Vec, - /// Known received certificates, indexed by sender and sequence number. - /// TODO: API to search and download yet unknown `received_certificates`. - received_certificates: BTreeMap, + /// Known certificates, indexed by TX digest. + certificates: BTreeMap, /// The known objects with it's sequence number owned by the client. object_ids: BTreeMap, + + object_certs: BTreeMap>, } // Operations are considered successful when they successfully reach a quorum of authorities. @@ -127,8 +127,7 @@ impl ClientState { secret: KeyPair, committee: Committee, authority_clients: HashMap, - sent_certificates: Vec, - received_certificates: Vec, + certificates: BTreeMap, object_ids: BTreeMap, ) -> Self { Self { @@ -137,12 +136,9 @@ impl ClientState { committee, authority_clients, pending_transfer: None, - sent_certificates, - received_certificates: received_certificates - .into_iter() - .map(|cert| (cert.order.digest(), cert)) - .collect(), + certificates, object_ids, + object_certs: BTreeMap::new(), } } @@ -150,8 +146,15 @@ impl ClientState { self.address } - pub fn next_sequence_number(&self, object_id: ObjectID) -> SequenceNumber { - self.object_ids[&object_id] + pub fn next_sequence_number( + &self, + object_id: &ObjectID, + ) -> Result { + if self.object_ids.contains_key(object_id) { + Ok(self.object_ids[object_id]) + } else { + Err(FastPayError::ObjectNotFound) + } } pub fn object_ids(&self) -> &BTreeMap { @@ -162,12 +165,19 @@ impl ClientState { &self.pending_transfer } - pub fn sent_certificates(&self) -> &Vec { - &self.sent_certificates + pub fn certificates(&self, object_id: &ObjectID) -> impl Iterator { + self.object_certs + .get(object_id) + .into_iter() + .flat_map(|cert_digests| { + cert_digests + .iter() + .filter_map(|digest| self.certificates.get(digest)) + }) } - pub fn received_certificates(&self) -> impl Iterator { - self.received_certificates.values() + pub fn all_certificates(&self) -> &BTreeMap { + &self.certificates } } @@ -444,7 +454,7 @@ where if let CommunicateAction::SendOrder(order) = action { let result: Result = client.handle_order(order).await; - match result { + return match result { Ok(OrderInfoResponse { signed_order: Some(inner_signed_order), .. @@ -454,10 +464,10 @@ where FastPayError::ErrorWhileProcessingTransferOrder ); inner_signed_order.check(committee)?; - return Ok(Some(inner_signed_order)); + Ok(Some(inner_signed_order)) } - Err(err) => return Err(err), - _ => return Err(FastPayError::ErrorWhileProcessingTransferOrder), + Err(err) => Err(err), + _ => Err(FastPayError::ErrorWhileProcessingTransferOrder), }; } Ok(None) @@ -466,7 +476,7 @@ where .await?; // Terminate downloader task and retrieve the content of the cache. handle.stop().await?; - let mut certificates: Vec<_> = task.await.unwrap().filter_map(Result::ok).collect(); + let mut certificates: Vec<_> = task.await?.filter_map(Result::ok).collect(); if let CommunicateAction::SendOrder(order) = action { let certificate = CertifiedOrder { order, @@ -490,15 +500,16 @@ where /// Make sure we have all our certificates with sequence number /// in the range 0..self.next_sequence_number - pub async fn download_certificates(&self) -> Result, FastPayError> { - let mut sent_certificates = Vec::new(); + pub async fn download_certificates( + &mut self, + ) -> Result>, FastPayError> { + let mut sent_certificates: BTreeMap> = BTreeMap::new(); for (object_id, next_sequence_number) in self.object_ids.clone() { let known_sequence_numbers: BTreeSet<_> = self - .sent_certificates - .iter() - .filter(|cert| cert.order.object_id() == &object_id) - .map(|cert| cert.order.sequence_number()) + .certificates(&object_id) + .flat_map(|cert| cert.order.input_objects()) + .filter_map(|(id, seq, _)| if id == object_id { Some(seq) } else { None }) .collect(); let mut requester = CertificateRequester::new( @@ -508,30 +519,41 @@ where object_id, ); + let entry = sent_certificates.entry(object_id).or_default(); // TODO: it's inefficient to loop through sequence numbers to retrieve missing cert, rethink this logic when we change certificate storage in client. let mut number = SequenceNumber::from(0); while number < next_sequence_number { if !known_sequence_numbers.contains(&number) { let certificate = requester.query(number).await?; - sent_certificates.push(certificate); + entry.push(certificate); } number = number.increment().unwrap_or_else(|_| SequenceNumber::max()); } } - - sent_certificates.sort_by_key(|cert| cert.order.sequence_number()); Ok(sent_certificates) } /// Transfers an object to a recipient address. async fn transfer( &mut self, - (object_id, sequence_number, _object_digest): ObjectRef, - gas_payment: ObjectRef, + object_id: ObjectID, + gas_payment: ObjectID, recipient: Address, ) -> Result { + // TODO(https://github.com/MystenLabs/fastnft/issues/123): Include actual object digest here + let object_ref = ( + object_id, + self.next_sequence_number(&object_id)?, + ObjectDigest::new([0; 32]), + ); + let gas_payment = ( + gas_payment, + self.next_sequence_number(&gas_payment)?, + ObjectDigest::new([0; 32]), + ); + let transfer = Transfer { - object_ref: (object_id, sequence_number, _object_digest), + object_ref, sender: self.address, recipient, gas_payment, @@ -540,6 +562,14 @@ where let certificate = self .execute_transfer(order, /* with_confirmation */ true) .await?; + + if let FastPay(address) = recipient { + if address != self.address { + self.object_certs.remove(&object_id); + self.object_ids.remove(&object_id); + } + } + Ok(certificate) } @@ -548,54 +578,43 @@ where /// We assume certificates to be valid and sent by us, and their sequence numbers to be unique. fn update_certificates( &mut self, - certificates: Vec, + object_id: &ObjectID, + certificates: &[CertifiedOrder], ) -> Result<(), FastPayError> { - for new_cert in &certificates { - let object_id = *new_cert.order.object_id(); - let mut new_next_sequence_number = self.next_sequence_number(object_id); - - if new_cert.order.sequence_number() >= new_next_sequence_number { - new_next_sequence_number = new_cert - .order - .sequence_number() - .increment() - .unwrap_or_else(|_| SequenceNumber::max()); - } - // store cert in sent_certificate if the cert is created by the client, else store it in received certificates. - // TODO: Refactor how we stor certs in client to avoid this logic https://github.com/MystenLabs/fastnft/issues/82 - if *new_cert.order.sender() == self.address { - self.sent_certificates.push(new_cert.clone()); - } else { - self.received_certificates - .insert(new_cert.order.digest(), new_cert.clone()); + for new_cert in certificates { + let (_, seq, _) = new_cert + .order + .input_objects() + .iter() + .find(|(id, _, _)| object_id == id) + .copied() + .ok_or::(FastPayError::ObjectNotFound)?; + + let mut new_next_sequence_number = self.next_sequence_number(object_id)?; + + if seq >= new_next_sequence_number { + new_next_sequence_number = + seq.increment().unwrap_or_else(|_| SequenceNumber::max()); } - // Atomic update - self.object_ids.insert(object_id, new_next_sequence_number); - // Sanity check - // Some certificates of the object will be from received_certs if the object is originated from other sender. - // TODO: Maybe we should store certificates in one place sorted by object_ref instead of sent/received? - let mut sent_certificates: Vec = self - .sent_certificates - .iter() - .filter(|cert| *cert.order.object_id() == object_id) - .cloned() - .collect(); + self.certificates + .insert(new_cert.order.digest(), new_cert.clone()); - let mut received_certs: Vec = self - .received_certificates - .values() - .filter(|cert| *cert.order.object_id() == object_id) - .cloned() - .collect(); + // Atomic update + self.object_ids.insert(*object_id, new_next_sequence_number); - sent_certificates.append(&mut received_certs); + let certs = self.object_certs.entry(*object_id).or_default(); - assert_eq!( - sent_certificates.len(), - usize::from(self.next_sequence_number(object_id)) - ); + if !certs.contains(&new_cert.order.digest()) { + certs.push(new_cert.order.digest()); + } } + // Sanity check + let certificates_count = self.certificates(object_id).count(); + assert_eq!( + certificates_count, + usize::from(self.next_sequence_number(object_id)?) + ); Ok(()) } @@ -610,7 +629,7 @@ where "Client state has a different pending transfer", ); ensure!( - order.sequence_number() == self.next_sequence_number(*order.object_id()), + order.sequence_number() == self.next_sequence_number(order.object_id())?, "Unexpected sequence number" ); self.pending_transfer = Some(order.clone()); @@ -618,7 +637,7 @@ where .communicate_transfers( self.address, *order.object_id(), - self.sent_certificates.clone(), + self.certificates(order.object_id()).cloned().collect(), CommunicateAction::SendOrder(order.clone()), ) .await?; @@ -627,20 +646,25 @@ where // and `next_sequence_number`. (Note that if we were using persistent // storage, we should ensure update atomicity in the eventuality of a crash.) self.pending_transfer = None; - self.update_certificates(new_sent_certificates)?; + + // Only valid for object transfer, where input_objects = output_objects + for (object_id, _, _) in order.input_objects() { + self.update_certificates(&object_id, &new_sent_certificates)?; + } // Confirm last transfer certificate if needed. if with_confirmation { self.communicate_transfers( self.address, *order.object_id(), - self.sent_certificates.clone(), + self.certificates(order.object_id()).cloned().collect(), CommunicateAction::SynchronizeNextSequenceNumber( - self.next_sequence_number(*order.object_id()), + self.next_sequence_number(order.object_id())?, ), ) .await?; } - Ok(self.sent_certificates.last().unwrap().clone()) + // the object_certs has been updated by update_certificates above, .last().unwrap() should be safe here. + Ok(self.certificates(order.object_id()).last().unwrap().clone()) } async fn download_own_object_ids( @@ -853,21 +877,7 @@ where gas_payment: ObjectID, recipient: FastPayAddress, ) -> AsyncResult<'_, CertifiedOrder, anyhow::Error> { - Box::pin(self.transfer( - ( - object_id, - self.next_sequence_number(object_id), - // TODO(https://github.com/MystenLabs/fastnft/issues/123): Include actual object digest here - ObjectDigest::new([0; 32]), - ), - ( - gas_payment, - self.next_sequence_number(gas_payment), - // TODO(https://github.com/MystenLabs/fastnft/issues/123): Include actual object digest here - ObjectDigest::new([0; 32]), - ), - Address::FastPay(recipient), - )) + Box::pin(self.transfer(object_id, gas_payment, Address::FastPay(recipient))) } fn receive_object( @@ -892,13 +902,15 @@ where ) .await?; // Everything worked: update the local balance. - if let btree_map::Entry::Vacant(entry) = - self.received_certificates.entry(certificate.order.digest()) + if let Entry::Vacant(entry) = + self.certificates.entry(certificate.order.digest()) { - self.object_ids.insert( - transfer.object_ref.0, - transfer.object_ref.1.increment().unwrap(), - ); + self.object_ids + .insert(transfer.object_ref.0, transfer.object_ref.1.increment()?); + self.object_certs + .entry(transfer.object_ref.0) + .or_default() + .push(certificate.order.digest()); entry.insert(certificate); } Ok(()) @@ -920,7 +932,7 @@ where let transfer = Transfer { object_ref: ( object_id, - self.next_sequence_number(object_id), + self.next_sequence_number(&object_id)?, // TODO(https://github.com/MystenLabs/fastnft/issues/123): Include actual object digest here ObjectDigest::new([0; 32]), ), @@ -928,7 +940,7 @@ where recipient: Address::FastPay(recipient), gas_payment: ( gas_payment, - self.next_sequence_number(gas_payment), + self.next_sequence_number(&gas_payment)?, // TODO(https://github.com/MystenLabs/fastnft/issues/123): Include actual object digest here ObjectDigest::new([0; 32]), ), @@ -957,11 +969,12 @@ where for (object_id, sequence_number, _) in object_ids { self.object_ids.insert(object_id, sequence_number); } - // Recover missing certificates. let new_certificates = self.download_certificates().await?; - self.update_certificates(new_certificates)?; + for (id, certs) in new_certificates { + self.update_certificates(&id, &certs)?; + } Ok(authority_name) }) } diff --git a/fastpay_core/src/unit_tests/client_tests.rs b/fastpay_core/src/unit_tests/client_tests.rs index 173827384ae0a..13f6f42387d93 100644 --- a/fastpay_core/src/unit_tests/client_tests.rs +++ b/fastpay_core/src/unit_tests/client_tests.rs @@ -13,6 +13,7 @@ use std::{ }; use tokio::runtime::Runtime; +use fastx_types::error::FastPayError::ObjectNotFound; use std::env; use std::fs; @@ -154,8 +155,7 @@ fn make_client( secret, committee, authority_clients, - Vec::new(), - Vec::new(), + BTreeMap::new(), BTreeMap::new(), ) } @@ -278,8 +278,8 @@ fn test_initiating_valid_transfer() { .block_on(sender.transfer_object(object_id_1, gas_object, recipient)) .unwrap(); assert_eq!( - sender.next_sequence_number(object_id_1), - SequenceNumber::from(1) + sender.next_sequence_number(&object_id_1), + Err(ObjectNotFound) ); assert_eq!(sender.pending_transfer, None); assert_eq!( @@ -319,10 +319,7 @@ fn test_initiating_valid_transfer_despite_bad_authority() { let certificate = rt .block_on(sender.transfer_object(object_id, gas_object, recipient)) .unwrap(); - assert_eq!( - sender.next_sequence_number(object_id), - SequenceNumber::from(1) - ); + assert_eq!(sender.next_sequence_number(&object_id), Err(ObjectNotFound)); assert_eq!(sender.pending_transfer, None); assert_eq!( rt.block_on(sender.get_strong_majority_owner(object_id)), @@ -354,8 +351,8 @@ fn test_initiating_transfer_low_funds() { .is_err()); // Trying to overspend does not block an account. assert_eq!( - sender.next_sequence_number(object_id_2), - SequenceNumber::from(0) + sender.next_sequence_number(&object_id_2), + Ok(SequenceNumber::from(0)) ); // assert_eq!(sender.pending_transfer, None); assert_eq!( @@ -416,10 +413,6 @@ fn test_bidirectional_transfer() { .block_on(client1.transfer_object(object_id, gas_object1, client2.address)) .unwrap(); - assert_eq!( - client1.next_sequence_number(object_id), - SequenceNumber::from(1) - ); assert_eq!(client1.pending_transfer, None); // Confirm client1 lose ownership of the object. @@ -432,11 +425,7 @@ fn test_bidirectional_transfer() { rt.block_on(client2.get_strong_majority_owner(object_id)), Some((client2.address, SequenceNumber::from(1))) ); - // Confirm sequence number is consistent between authorities and client. - assert_eq!( - rt.block_on(client1.get_strong_majority_sequence_number(object_id)), - client1.next_sequence_number(object_id) - ); + // Confirm certificate is consistent between authorities and client. assert_eq!( rt.block_on(client1.request_certificate( @@ -461,10 +450,6 @@ fn test_bidirectional_transfer() { rt.block_on(client2.transfer_object(object_id, gas_object2, client1.address)) .unwrap(); - assert_eq!( - client2.next_sequence_number(object_id), - SequenceNumber::from(2) - ); assert_eq!(client2.pending_transfer, None); // Confirm client2 lose ownership of the object. @@ -519,8 +504,8 @@ fn test_receiving_unconfirmed_transfer() { )) .unwrap(); assert_eq!( - client1.next_sequence_number(object_id), - SequenceNumber::from(1) + client1.next_sequence_number(&object_id), + Ok(SequenceNumber::from(1)) ); assert_eq!(client1.pending_transfer, None); // ..but not confirmed remotely, hence an unchanged balance and sequence number. @@ -552,14 +537,14 @@ fn test_client_state_sync() { let mut sender = rt.block_on(init_local_client_state(authority_objects)); let old_object_ids = sender.object_ids.clone(); - let old_sent_certificate = sender.sent_certificates.clone(); + let old_certificate = sender.certificates.clone(); // Remove all client-side data sender.object_ids.clear(); - sender.sent_certificates.clear(); + sender.certificates.clear(); assert!(rt.block_on(sender.get_owned_objects()).unwrap().is_empty()); assert!(sender.object_ids.is_empty()); - assert!(sender.sent_certificates.is_empty()); + assert!(sender.certificates.is_empty()); // Sync client state rt.block_on(sender.sync_client_state_with_random_authority()) @@ -568,25 +553,21 @@ fn test_client_state_sync() { // Confirm data are the same after sync assert!(!rt.block_on(sender.get_owned_objects()).unwrap().is_empty()); assert_eq!(old_object_ids, sender.object_ids); - assert_eq!(old_sent_certificate, sender.sent_certificates); + assert_eq!(old_certificate, sender.certificates); } #[test] fn test_client_state_sync_with_transferred_object() { let rt = Runtime::new().unwrap(); - let (authority_clients, committee) = init_local_authorities(4); + let (authority_clients, committee) = init_local_authorities(1); let mut client1 = make_client(authority_clients.clone(), committee.clone()); let mut client2 = make_client(authority_clients.clone(), committee); let object_id = ObjectID::random(); let gas_object_id = ObjectID::random(); - let authority_objects = vec![ - vec![object_id, gas_object_id], - vec![object_id, gas_object_id], - vec![object_id, gas_object_id], - vec![object_id, gas_object_id], - ]; + let authority_objects = vec![vec![object_id, gas_object_id]]; + rt.block_on(fund_account( authority_clients.values().collect(), &mut client1, @@ -606,18 +587,105 @@ fn test_client_state_sync_with_transferred_object() { // Client 2's local object_id and cert should be empty before sync assert!(rt.block_on(client2.get_owned_objects()).unwrap().is_empty()); assert!(client2.object_ids.is_empty()); - assert!(client2.received_certificates.is_empty()); - assert!(client2.sent_certificates.is_empty()); + assert!(client2.certificates.is_empty()); // Sync client state - while client2.object_ids.is_empty() { - rt.block_on(client2.sync_client_state_with_random_authority()) - .unwrap(); - } + rt.block_on(client2.sync_client_state_with_random_authority()) + .unwrap(); // Confirm client 2 received the new object id and cert assert_eq!(1, rt.block_on(client2.get_owned_objects()).unwrap().len()); assert_eq!(1, client2.object_ids.len()); - assert_eq!(1, client2.received_certificates.len()); - assert_eq!(0, client2.sent_certificates.len()); + assert_eq!(1, client2.certificates.len()); +} + +#[test] +fn test_client_certificate_state() { + let rt = Runtime::new().unwrap(); + let number_of_authorities = 1; + let (authority_clients, committee) = init_local_authorities(number_of_authorities); + let mut client1 = make_client(authority_clients.clone(), committee.clone()); + let mut client2 = make_client(authority_clients.clone(), committee); + + let object_id_1 = ObjectID::random(); + let object_id_2 = ObjectID::random(); + let gas_object_id_1 = ObjectID::random(); + let gas_object_id_2 = ObjectID::random(); + + let client1_objects = vec![object_id_1, object_id_2, gas_object_id_1]; + let client2_objects = vec![gas_object_id_2]; + + let client1_objects: Vec> = (0..number_of_authorities) + .map(|_| client1_objects.clone()) + .collect(); + + let client2_objects: Vec> = (0..number_of_authorities) + .map(|_| client2_objects.clone()) + .collect(); + + rt.block_on(fund_account( + authority_clients.values().collect(), + &mut client1, + client1_objects, + )); + + rt.block_on(fund_account( + authority_clients.values().collect(), + &mut client2, + client2_objects, + )); + + // Transfer object to client2. + rt.block_on(client1.transfer_object(object_id_1, gas_object_id_1, client2.address)) + .unwrap(); + rt.block_on(client1.transfer_object(object_id_2, gas_object_id_1, client2.address)) + .unwrap(); + // Should have 2 certs after 2 transfer + assert_eq!(2, client1.certificates.len()); + // Only gas_object left in account, so object_certs link should only have 1 entry + assert_eq!(1, client1.object_certs.len()); + // it should have 2 certificates associated with the gas object + assert!(client1.object_certs.contains_key(&gas_object_id_1)); + assert_eq!(2, client1.object_certs.get(&gas_object_id_1).unwrap().len()); + // Sequence number should be 2 for gas object after 2 mutation. + assert_eq!( + Ok(SequenceNumber::from(2)), + client1.next_sequence_number(&gas_object_id_1) + ); + + rt.block_on(client2.sync_client_state_with_random_authority()) + .unwrap(); + + // Client 2 should retrieve 2 certificates for the 2 transactions after sync + assert_eq!(2, client2.certificates.len()); + assert!(client2.object_certs.contains_key(&object_id_1)); + assert!(client2.object_certs.contains_key(&object_id_2)); + assert_eq!(1, client2.object_certs.get(&object_id_1).unwrap().len()); + assert_eq!(1, client2.object_certs.get(&object_id_2).unwrap().len()); + // Sequence number for object 1 and 2 should be 1 after 1 mutation. + assert_eq!( + Ok(SequenceNumber::from(1)), + client2.next_sequence_number(&object_id_1) + ); + assert_eq!( + Ok(SequenceNumber::from(1)), + client2.next_sequence_number(&object_id_2) + ); + // Transfer object 2 back to client 1. + rt.block_on(client2.transfer_object(object_id_2, gas_object_id_2, client1.address)) + .unwrap(); + + assert_eq!(3, client2.certificates.len()); + assert!(client2.object_certs.contains_key(&object_id_1)); + assert!(!client2.object_certs.contains_key(&object_id_2)); + assert!(client2.object_certs.contains_key(&gas_object_id_2)); + assert_eq!(1, client2.object_certs.get(&object_id_1).unwrap().len()); + assert_eq!(1, client2.object_certs.get(&gas_object_id_2).unwrap().len()); + + rt.block_on(client1.sync_client_state_with_random_authority()) + .unwrap(); + + assert_eq!(3, client1.certificates.len()); + assert!(client1.object_certs.contains_key(&object_id_2)); + assert_eq!(2, client1.object_certs.get(&object_id_2).unwrap().len()); }