{"id":24058228,"url":"https://github.com/ctsrc/toy_transaction_engine","last_synced_at":"2026-06-13T17:32:16.118Z","repository":{"id":84662198,"uuid":"481331929","full_name":"ctsrc/toy_transaction_engine","owner":"ctsrc","description":"Toy transaction engine","archived":false,"fork":false,"pushed_at":"2023-12-15T09:17:29.000Z","size":3140,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-26T13:12:48.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://ctsrc.github.io/toy_transaction_engine/toy_transaction_engine/index.html","language":"Rust","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ctsrc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-13T18:32:57.000Z","updated_at":"2024-02-05T23:08:40.000Z","dependencies_parsed_at":"2025-02-26T13:12:21.904Z","dependency_job_id":"424a72e9-fda1-4531-b640-7322af1eec50","html_url":"https://github.com/ctsrc/toy_transaction_engine","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ctsrc/toy_transaction_engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctsrc%2Ftoy_transaction_engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctsrc%2Ftoy_transaction_engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctsrc%2Ftoy_transaction_engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctsrc%2Ftoy_transaction_engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ctsrc","download_url":"https://codeload.github.com/ctsrc/toy_transaction_engine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctsrc%2Ftoy_transaction_engine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34294411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-09T05:56:28.235Z","updated_at":"2026-06-13T17:32:16.098Z","avatar_url":"https://github.com/ctsrc.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Toy Transaction Engine\n\nThis is an implementation of a toy transaction engine according to a specification\nthat was handed to me by someone for implementation.\n\nThe spec itself is not public, but I was told to make the repository public.\n\n## Code Organization Overview\n\n* Command-line utility resides in [`src/main.rs`](src/main.rs).\n* CSV input parsing happens in [`transaction_engine_util/src/csv_input.rs`](transaction_engine_util/src/csv_input.rs).\n* Transaction processing happens in [`transaction_engine/src/lib.rs`](transaction_engine/src/lib.rs).\n* For CSV output, there is a single struct in [`transaction_engine_util/src/csv_output.rs`](transaction_engine_util/src/csv_output.rs)\n  which is used in the command-line utilitity when it serializes CSV output with the [csv](https://crates.io/crates/csv) crate.\n\n## Documentation\n\nThe code in this repository is annotated with doc strings. To generate documentation\nand view it in your browser, run the following command:\n\n```zsh\ncargo doc --open\n```\n\nA current copy of the generated docs is deployed with GitHub Actions after each\npush to the main branch of the toy transaction engine repository, and is available\nat https://ctsrc.github.io/toy_transaction_engine/toy_transaction_engine/index.html\n\nBesides the doc string annotations in the source, there are some relevant details\nin this readme as well. In particular, the following sections of this readme\nprovides important information and context about the assumptions and the\nimplementation of the code:\n\n* [Assumptions](#assumptions)\n* [Implementation](#implementation)\n  - [Handling of cases](#handling-of-cases)\n    * [Withdrawals](#withdrawals)\n    * [Deposits](#deposits)\n    * [Disputes](#disputes)\n    * [Resolves](#resolves)\n    * [Chargebacks](#chargebacks)\n  - [Correctness](#correctness)\n    * [State of transactions](#state-of-transactions)\n    * [Multithreading](#multithreading)\n\n## Running Tests\n\nA number of tests are included with this program and its parts.\n\nTo run the tests for all parts of the code in this repository, run:\n\n```zsh\ncargo test --workspace\n```\n\n## Command-line Usage Example\n\nThe toy transaction engine takes a single argument, which is the path to a CSV file\ncontaining transactions. The program writes its output in CSV format to `stdout`.\n\nIn order to build and run the program you need to have the Rust toolchain\ninstalled. Install the Rust toolchain from https://rustup.rs/ and ensure\nthat `cargo` is in your `$PATH`.\n\nAn example of using the program is given below, using a sample CSV input\nfile that is included in the repository root. This sample data is from\nthe spec, and I was told that it was ok to include sample data from\nthe spec as long as I documented the fact that I have done so.\n\n```zsh\ncargo run -- transactions.csv \u003e accounts.csv\n```\n\nThe resulting output redirected to `accounts.csv` from the command above\nlooks like the following:\n\n```csv\nclient,available,held,total,locked\n2,2.0000,0.0000,2.0000,false\n1,1.5000,0.0000,1.5000,false\n```\n\nNote that as per the spec, the rows of data in the output is\nnot guaranteed to be in any particular order.\n\n## Assumptions\n\nIn addition to the assumptions listed in the spec, I am making some further assumptions:\n\n1. We are not required to keep a record of the individual transactions themselves.\n2. We are not required to keep a record of the individual results of the individual transactions.\n3. Deposits and withdrawals are transactions between the system and an external\n   party such as for example a bank.\n4. Users can dispute deposits, but they cannot dispute withdrawals.\n5. A frozen account cannot withdraw money.\n6. A frozen account is still able to deposit money.\n7. A frozen account is also still able to dispute, resolve and chargeback.\n\nAssumption 4 requires some explanation: According to the spec, the dispute process\ngoes down one of two possible paths; transaction -\u003e dispute -\u003e resolve, or\ntransaction -\u003e dispute -\u003e chargeback. The chargeback, which is the final state\nof a dispute, says that funds that were held have now been withdrawn.\nMeanwhile, if the transaction under dispute was itself a withdrawal, and with\nassumption 3 in mind, we are not able to \"unwithdraw\" money that has been withdrawn.\n\n## Implementation\n\nThis section of the readme gives a high level overview of the implementation.\nFor more details about the implementation please see also the comments\nthroughout the source code itself.\n\nNote also that where applicable, comments in the source are doc comments, so that\nyou can also run `cargo doc --open --bin toy_transaction_engine_command`\nin the repo root and browse the generated documentation to read such comments.\n\n### Handling of cases\n\n#### Withdrawals\n\nAs a consequence of our assumption that withdrawals cannot be disputed, we can\n\"forget\" the withdrawal transaction as soon as we have processed it.\n\nIf a withdrawal attempts to withdraw more than the available amount, then\nwe return an error indicating that this is not allowed.\n\nIf the account is currently frozen then we return an error indicating this.\n\n#### Deposits\n\nWe need to remember deposits for a while -- potentially \"forever\" -- as they could\nlater get disputed.\n\n#### Disputes\n\nWe need to remember disputes until we see either a resolve or a chargeback for\nthe disputed transaction.\n\nIf a transaction is already under dispute then we will return an error indicating as much.\n\nIf a transaction cannot be found then we return an error indicating this.\n\nIf the client id of the user submitting the dispute does not match the client id\nof the user that created the transaction then we consider the dispute to be not valid.\nThis situation is handled for us when we look for the transaction as we include\nthe user id in the key that we look up transaction by.\n\nA dispute for a past deposit can result in negative available balance on the account,\nif there have been withdrawals or disputes since the time at which the deposit was made.\nThis is fine and expected.\n\n#### Resolves\n\nWhen a dispute is resolved, we forget the dispute, but we keep remembering the deposit\nin case the same transaction is disputed again by the user.\n\nIf a current dispute cannot be found then we return an error indicating this.\n\n(As with disputes, user id must match, and is handled because we include the user id\nin the key that we look up dispute by.)\n\n#### Chargebacks\n\nWhen a transaction gets chargeback, the user shall not be able to dispute the same transaction\nagain as the amount has been sent back to their third party bank account. Therefore, we forget\nabout the dispute (and therefore also the transaction itself) after chargeback has been processed.\n\nIf a current dispute cannot be found when processing a chargeback\nthen we return an error indicating this.\n\n(As with disputes and resolves, user id must match, and is handled because we include the user id\nin the key that we look up dispute by.)\n\nWhen the chargeback is processed, the total funds can become negative. This is expected.\nI guess that is part of the reason why the spec says to freeze the account of the user\nafter processing a chargeback.\n\n### Correctness\n\nAssuming that the logic of the handling of the cases as listed above is correct,\nthe implementation itself should also be correct. Transactions are processed\nserially in the order that they are read, with the code currently being\nsingle threaded, and we use `std::collections::HashMap` for the deposits\nand disputes that we want to remember, as well as for the accounts.\n\n#### State of transactions\n\nState of past transactions is handled by maintaining two collections of transactions;\ndeposit transactions and dispute transactions. When a valid deposit transaction is\nreceived, we remember the transaction for as long as we do not yet see a valid\ndispute for the transaction.\n\nWhen a transaction is disputed, we remove the transaction from our collection\nof deposit transactions and put an entry for the transaction in our collection\nof disputed transactions.\n\nWhen a dispute is resolved, we remove the transaction from our collection of\ndisputed transactions and put an entry for it back in the collection of\ndeposit transactions, in case the transaction is disputed again in the future.\n\nWhen a dispute is charged back, we remove the transaction from our collection of\ndisputed transactions, and we then forget about the transaction as it cannot\nbe disputed again in the future after it has been charged back.\n\n#### Multithreading\n\nAfter finishing the single threaded version of the program I will have\na look at splitting up the work across the logical CPU cores of the host\nsystem. In the multi-threaded version of the program, we will read the CSV\ninput on a single thread and we will use the client id when deciding which\nthread to send the transaction data for processing to. As such there will\nnot be any synchronization needed between the transaction processing threads.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctsrc%2Ftoy_transaction_engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fctsrc%2Ftoy_transaction_engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctsrc%2Ftoy_transaction_engine/lists"}