transact/families/smallbank/workload/
mod.rs1pub mod error;
20pub mod playlist;
21
22use std::cmp::Eq;
23use std::collections::HashMap;
24use std::hash::Hash;
25
26use cylinder::Signer;
27use protobuf::Message;
28
29use crate::error::InvalidStateError;
30use crate::protocol::{
31 batch::{BatchBuilder, BatchPair},
32 sabre::ExecuteContractActionBuilder,
33 transaction::TransactionPair,
34};
35use crate::protos::smallbank::{
36 SmallbankTransactionPayload, SmallbankTransactionPayload_PayloadType,
37};
38use crate::workload::{BatchWorkload, ExpectedBatchResult, TransactionWorkload};
39
40use self::playlist::make_addresses;
41use self::playlist::SmallbankGeneratingIter;
42
43pub struct SmallbankTransactionWorkload {
44 generator: SmallbankGeneratingIter,
45 signer: Box<dyn Signer>,
46 dependencies: SignatureTracker<u32>,
47}
48
49impl SmallbankTransactionWorkload {
50 pub fn new(generator: SmallbankGeneratingIter, signer: Box<dyn Signer>) -> Self {
51 Self {
52 generator,
53 signer,
54 dependencies: SignatureTracker::new(),
55 }
56 }
57
58 fn add_signature_if_create_account(
59 &mut self,
60 payload: &SmallbankTransactionPayload,
61 signature: String,
62 ) {
63 if payload.get_payload_type() == SmallbankTransactionPayload_PayloadType::CREATE_ACCOUNT {
64 self.dependencies
65 .add_signature(payload.get_create_account().get_customer_id(), signature);
66 }
67 }
68
69 fn get_dependencies_for_customer_ids(&self, customer_ids: &[u32]) -> Vec<String> {
70 customer_ids
71 .iter()
72 .filter_map(|id| self.dependencies.get_signature(id))
73 .map(|sig| sig.to_owned())
74 .collect()
75 }
76
77 fn get_dependencies(&self, payload: &SmallbankTransactionPayload) -> Vec<String> {
78 match payload.get_payload_type() {
79 SmallbankTransactionPayload_PayloadType::DEPOSIT_CHECKING => self
80 .get_dependencies_for_customer_ids(&[payload
81 .get_deposit_checking()
82 .get_customer_id()]),
83 SmallbankTransactionPayload_PayloadType::WRITE_CHECK => self
84 .get_dependencies_for_customer_ids(&[payload.get_write_check().get_customer_id()]),
85 SmallbankTransactionPayload_PayloadType::TRANSACT_SAVINGS => self
86 .get_dependencies_for_customer_ids(&[payload
87 .get_transact_savings()
88 .get_customer_id()]),
89 SmallbankTransactionPayload_PayloadType::SEND_PAYMENT => self
90 .get_dependencies_for_customer_ids(&[
91 payload.get_send_payment().get_source_customer_id(),
92 payload.get_send_payment().get_dest_customer_id(),
93 ]),
94 SmallbankTransactionPayload_PayloadType::AMALGAMATE => self
95 .get_dependencies_for_customer_ids(&[
96 payload.get_amalgamate().get_source_customer_id(),
97 payload.get_amalgamate().get_dest_customer_id(),
98 ]),
99 _ => vec![],
100 }
101 }
102}
103
104impl TransactionWorkload for SmallbankTransactionWorkload {
105 fn next_transaction(
106 &mut self,
107 ) -> Result<(TransactionPair, Option<ExpectedBatchResult>), InvalidStateError> {
108 let payload = self
109 .generator
110 .next()
111 .ok_or_else(|| InvalidStateError::with_message("No payload available".to_string()))?;
112 let addresses = make_addresses(&payload);
113 let dependencies = self.get_dependencies(&payload);
114
115 let payload_bytes = payload.write_to_bytes().map_err(|_| {
116 InvalidStateError::with_message("Unable to convert payload to bytes".to_string())
117 })?;
118
119 let txn_pair = ExecuteContractActionBuilder::new()
120 .with_name(String::from("smallbank"))
121 .with_version(String::from("1.0"))
122 .with_inputs(addresses.clone())
123 .with_outputs(addresses)
124 .with_payload(payload_bytes)
125 .into_payload_builder()
126 .map_err(|err| {
127 InvalidStateError::with_message(format!(
128 "Unable to convert execute action into sabre payload: {}",
129 err
130 ))
131 })?
132 .into_transaction_builder()
133 .map_err(|err| {
134 InvalidStateError::with_message(format!(
135 "Unable to convert execute payload into transaction: {}",
136 err
137 ))
138 })?
139 .with_dependencies(dependencies)
140 .build_pair(&*self.signer)
141 .map_err(|err| {
142 InvalidStateError::with_message(format!(
143 "Failed to build transaction pair: {}",
144 err
145 ))
146 })?;
147
148 self.add_signature_if_create_account(
149 &payload,
150 txn_pair.transaction().header_signature().to_owned(),
151 );
152
153 Ok((txn_pair, None))
154 }
155}
156
157pub struct SmallbankBatchWorkload {
158 transaction_workload: SmallbankTransactionWorkload,
159 signer: Box<dyn Signer>,
160}
161
162impl SmallbankBatchWorkload {
163 pub fn new(
164 transaction_workload: SmallbankTransactionWorkload,
165 signer: Box<dyn Signer>,
166 ) -> Self {
167 Self {
168 transaction_workload,
169 signer,
170 }
171 }
172}
173
174impl BatchWorkload for SmallbankBatchWorkload {
175 fn next_batch(
176 &mut self,
177 ) -> Result<(BatchPair, Option<ExpectedBatchResult>), InvalidStateError> {
178 let (txn, result) = self.transaction_workload.next_transaction()?;
179 Ok((
180 BatchBuilder::new()
181 .with_transactions(vec![txn.take().0])
182 .build_pair(&*self.signer)
183 .map_err(|err| {
184 InvalidStateError::with_message(format!("Failed to build batch pair: {}", err))
185 })?,
186 result,
187 ))
188 }
189}
190
191struct SignatureTracker<T>
192where
193 T: Eq + Hash,
194{
195 signature_by_id: HashMap<T, String>,
196}
197
198impl<T> SignatureTracker<T>
199where
200 T: Eq + Hash,
201{
202 pub fn new() -> SignatureTracker<T> {
203 SignatureTracker {
204 signature_by_id: HashMap::new(),
205 }
206 }
207
208 pub fn get_signature(&self, id: &T) -> Option<&String> {
209 self.signature_by_id.get(id)
210 }
211
212 pub fn add_signature(&mut self, id: T, signature: String) {
213 self.signature_by_id.insert(id, signature);
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 use cylinder::{secp256k1::Secp256k1Context, Context, Signer};
222
223 const NUM_CREATE_ACCOUNTS: usize = 100;
224 const NUM_TO_CONSIDER: usize = 1_000;
225
226 #[test]
236 fn test_dependencies() {
237 let seed = 8411989621121823827u64;
238
239 let payload_generator = SmallbankGeneratingIter::new(NUM_CREATE_ACCOUNTS, seed);
240
241 let signer = new_signer();
242
243 let mut transaction_workload =
244 SmallbankTransactionWorkload::new(payload_generator, signer.clone());
245
246 let mut create_account_txn_ids = Vec::new();
248
249 let mut acc = 0;
250 for _ in 0..100 {
251 let (txn_pair, _) = transaction_workload
252 .next_transaction()
253 .expect("Unable to get txn pair");
254 let (txn, header) = txn_pair.take();
255 create_account_txn_ids.push(txn.header_signature().to_string());
256
257 if header.dependencies().len() > 0 {
259 acc = acc + 1;
260 }
261 }
262
263 assert_eq!(acc, 0);
265
266 for _ in 0..NUM_TO_CONSIDER {
267 let (txn_pair, _) = transaction_workload
268 .next_transaction()
269 .expect("Unable to get txn pair");
270 let header = txn_pair.header();
271 assert!(header.dependencies().len() > 0);
273 if header
275 .dependencies()
276 .iter()
277 .any(|dep| !create_account_txn_ids.contains(&dep))
278 {
279 acc = acc + 1;
280 }
281 }
282
283 assert_eq!(acc, 0);
285 }
286
287 fn new_signer() -> Box<dyn Signer> {
288 let context = Secp256k1Context::new();
289 let key = context.new_random_private_key();
290 context.new_signer(key)
291 }
292}