Skip to main content

transact/families/smallbank/workload/
mod.rs

1/*
2 * Copyright 2018 Intel Corporation
3 * Copyright 2021 Cargill Incorporated
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ------------------------------------------------------------------------------
17 */
18
19pub 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    /// Verify that the SmallbankTransactionWorkload properly adds dependencies
227    ///
228    /// 1. Create a SmallbankGeneratingIter that will create 100 num_accounts
229    /// 2. Create the transaction workload
230    /// 3. Call next_transaction 100 times to verify that no transaction include any dependencies
231    ///    These transactions should be creating new accounts. Collect the txn signatures.
232    /// 4. Call next_transaction 1000 more times verifies that all these transactions should have
233    ///    dependencies and all of those dependencies should have been captured in the first
234    ///    iteration.
235    #[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        // store generate transaction signature that contain create account txn
247        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 there was a dep, increment count
258            if header.dependencies().len() > 0 {
259                acc = acc + 1;
260            }
261        }
262
263        // no transaction should have had any dep
264        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            // verify txn has dependencies
272            assert!(header.dependencies().len() > 0);
273            // if a txn has a dep not from the first 100 txn, increment count
274            if header
275                .dependencies()
276                .iter()
277                .any(|dep| !create_account_txn_ids.contains(&dep))
278            {
279                acc = acc + 1;
280            }
281        }
282
283        // no transaction should have a dep that was not generate in the first 100 txn
284        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}