RustyChain is a standalone blockchain ledger application that supports various transactions for managing fungible tokens. This README.md file provides documentation for the different transaction types and their associated parameters.
The main features supported in the current version of the application are as follows-
-
Transaction to Initialize Fungible Tokens: Users can create a transaction for initializing a new Fungible token using an API. They need to provide basic information such as token's name, symbol, decimals and initial supply in order to register the transaction.
-
Transaction to Mint Fungible Tokens: Any token initialized for the first time results in crediting the initial requested supply to the owner's wallet. Owner can now decide to either transfer these tokens to another user's address or simply mint new tokens to him/her. If new tokens are minted, then it will result in the increment of the total_supply of the token and new tokens will get credited to the recipient's address.
-
Transaction to Burn Fungible Tokens: The opposite of Mint is a Burn transaction. Burn results in reducing the balance of a user's wallet and the overall total supply of the token. Only an owner can burn tokens available in his wallet.
-
Transaction to Transfer Fungible Tokens: Users can transfer tokens from one wallet to another. If the target wallet doesn't yet exist, then it created first and then credited with the token balance. User can only transfer the amount of tokens which is available in their wallet, if a balance in excess is sent in the transaction then eventually at the time of transaction execution it will result in a failure.
-
Mine Blocks: Last, the application supports mining of blocks with a PoA arrangement. A selected miner gets rights to mine the blocks on ledger, currently a block gets created with at max 2 transactions and at the least 0 transactions too. This limit is configurable and can easily be updated.
Mining of a block results in execution of the transactions in raw state. After the execution is done, the transaction moves into either Success or Fail states.
- Service Maintenance Scheduling: Quite overlooked yet an essential feature of a web application is administered ability to put service on maintenance. This feature allows admin to temporarily put the entire REST APIs on maintenance mode, during this period updates to the backend service can be performed without having to worry about serving client requests. Only an admin can put the service on hold and resume it via an API call.
The main aim behind creating this RUST application was to setup a boilerplate application showing how to use RUST for REST API use cases. We have used the following tools/crates while creating this codebase-
- Actix - Server side framework
- Diesel - ORM
- r2d2 - Connection pool manager
- PostgreSQL - SQL database
- utoipa - Autogenerated OpenAPI documentation for Rust REST APIs
- Docker - Containerizing framework
- Swagger - API documentation
- Redoc - API documentation
- Rapidoc - API documentation
Onion architecture is a software design pattern that organizes the codebase into concentric layers, with the core layer containing the most fundamental and independent code, and the outer layers containing more specific and dependent code. This separation of concerns makes the codebase more maintainable and scalable, as each layer can be changed without affecting the other layers.
Actix web is a popular web framework for Rust that is well-suited for implementing the application layer of the onion architecture. It is built on top of the Actix actor framework, which provides a clear separation of concerns and modularity. Actix web is also high-performance and scalable, making it a good choice for building web applications of all sizes.
In simpler terms, onion architecture is a way of organizing your code so that it is easier to maintain and update. Actix web is a web framework that can be used to implement onion architecture in Rust.
Here is an analogy:
Imagine a cake with multiple layers. Each layer has a different flavor and texture, but they all work together to create a delicious cake. The onion architecture is similar to a cake in that each layer is independent of the other layers. This means that you can change the flavor or texture of one layer without affecting the other layers.
Actix web is like the frosting on the cake. It provides a layer of functionality that makes it easy to build web applications. However, Actix web is not necessary for onion architecture. You can use any web framework you want to implement onion architecture.
The onion architecture is a layered architecture that is based on the onion model. Where each layer in the onion model is used to define the different layers of an application.
For this rust implementation 4 layers are used.
- api (app) module: The outermost layer that contains the controllers and the endpoints definition, serialization and deserialization of the data, validation and error handling.
- infrastructure: Layer that typically include database connections, external APIs calls, logging and configuration management.
- services: Layer that contains the application's services, which encapsulate the core business logic and provide a higher-level abstraction for the application to interact with the domain entities.
- domain: The innermost layer that contains the core business logic and entities of the application.
Folder structure:
.
├── migrations
├── scripts
│ └── run_postgres.sh # Run postgres in docker locally
├── src
│ ├── api
│ │ ├── controllers
│ │ │ └── ... # controllers for the api
│ │ ├── dto # Data transfer objects
│ │ │ └── ... # Individual DTOs
│ │ └── errors.py
│ ├── infrastructure
│ │ ├── services
│ │ │ └── ... # Services that use third party libraries or services (e.g. email service)
│ │ ├── databases
│ │ │ └── ... # Database adapaters and initialization
│ │ ├── repositories
│ │ │ └── ... # Repositories for interacting with the databases
│ │ └── models
│ │ └── ... # Database models
│ ├── domain
│ │ ├── mod.rs
│ │ ├── constants.rs
│ │ ├── errors.rs
│ │ ├── models
│ │ │ └── ... # Business logic models traits or structs
│ │ ├── services
│ │ │ └── ... # Service traits
│ │ └── repositories
│ │ └── ... # Repository traits
│ ├── services
│ │ └── ... # Concrete service implementation for interacting with the domain (business logic)
│ ├── container.rs
│ ├── create_app.rs # app factory
│ ├── lib.rs
│ └── main.rs
- migrations: Alembic's migration scripts are stored here.
- scripts: contains the application's configuration settings.
The current schema looks as follows:
Initialize a new fungible token.
Parameters:
from_address
: Token owner's wallet address.to_address
: System contract address.transaction_type
: INIT_FT (TransactionType::InitFt).value
: Initial supply of tokens.symbol
: Token symbol.name
: Token name.decimals
: Token's allowed decimals.
Example Usage:
POST API Endpoint : http://localhost:8080/api/transactions
JSON Payload:
{
"from_address": "0x00000000000000000000000000000000000ARPIT",
"to_address": "0x00000000000000000000000000SYSTEMCONTRACT",
"transaction_type": "INIT_FT",
"value": 10000000000,
"data": {
"symbol": "APPL",
"name": "Apple",
"decimals": 0
}
}
Mint fresh tokens of the initialized fungible token.
Parameters:
from_address
: Token owner's wallet address.to_address
: Address to which tokens need to be minted.transaction_type
: MINT_FT (TransactionType::MintFt).value
: Number of tokens to be minted.token_address
: Address of the token to be minted, owned by thefrom_address
.
Example Usage:
POST API Endpoint : http://localhost:8080/api/transactions
JSON Payload:
{
"from_address": "0x00000000000000000000000000000000000ARPIT",
"to_address": "0x742d35Cc6634C0532925a3b844Bc454e4430JOHN",
"transaction_type": "MINT_FT",
"value": 200,
"data": {
"token_address": "0x8de21e962545c8622a9139387160405a8cee49f5"
}
}
Burn existing tokens in the fungible token contract.
Parameters:
from_address
: Wallet address of the user who has tokens to burn.to_address
: NULL address (can be left empty or set to NULL).transaction_type
: BURN_FT (TransactionType::BurnFt).value
: Amount of tokens to be burnt (should be less than or equal to thefrom_address
's balance).token_address
: Address of the token to be burned.
Example Usage:
POST API Endpoint : http://localhost:8080/api/transactions
JSON Payload:
{
"from_address": "0xUserAddress",
"to_address": "",
"transaction_type": "BURN_FT",
"value": 500,
"data": {
"token_address": "0x8de21e962545c8622a9139387160405a8cee49f5"
}
}
Transfer owned tokens from one wallet address to another.
Parameters:
from_address
: Sender's wallet address (user who has tokens).to_address
: Receiver's wallet address.transaction_type
: TRANSFER_FT (TransactionType::TransferFt).value
: Amount of tokens to be transferred (should be less than or equal to sender's balance).token_address
: Address of the token to be transferred.
Example Usage:
POST API Endpoint : http://localhost:8080/api/transactions
JSON Payload:
{
"from_address": "0xSenderAddress",
"to_address": "0xReceiverAddress",
"transaction_type": "TRANSFER_FT",
"value": 200,
"data": {
"token_address": "0x8de21e962545c8622a9139387160405a8cee49f5"
}
}
Retrieve all transactions in paginated manner
Example Usage:
GET API Endpoint : http://localhost:8080/api/transactions
Retrieve a transaction by its hash
Parameters:
txn_hash
: Transaction Hash.
Example Usage:
GET API Endpoint : http://localhost:8080/api/transactions/{txn_hash}
Creates a new block
Parameters:
miner_address
: Miner's wallet address.
Example Usage:
POST API Endpoint : http://localhost:8080/api/blocks
JSON Payload:
{
"miner_address": "string"
}
Retrieve all blocks in paginated manner
Example Usage:
GET API Endpoint : http://localhost:8080/api/blocks
Retrieve a block in by its number
Parameters:
id
: Block Number.
Example Usage:
GET API Endpoint : http://localhost:8080/api/blocks/{id}
Retrieve all the fungible tokens in paginated manner
Example Usage:
GET API Endpoint : http://localhost:8080/api/fts
Retrieve a fungible token by its hex address
Parameters:
token_address
: Address of the Fungible Token.
Example Usage:
GET API Endpoint : http://localhost:8080/api/fts/{token_address}
Retrieve all the wallets in paginated manner
Example Usage:
GET API Endpoint : http://localhost:8080/api/wallets
Retrieve a wallet by its address and the associated token
Parameters:
wallet_address
: Address of the User's Wallet.token_address
: Address of the Fungible Token.
Example Usage:
GET API Endpoint : http://localhost:8080/api/wallets/{wallet_address}/{token_address}
Retrieve the maintenance status of the backend
Example Usage:
GET API Endpoint : http://localhost:8080/admin/maintenance/status
Update the maintenance status of the backend
Parameters:
maintenance
: Set to true/false.
Example Usage:
POST API Endpoint : http://localhost:8080/admin/maintenance/status
JSON Payload:
{
"maintenance": false
}
- Take a git pull on your local machine
- Ensure a postgreSQL instance is running locally/remotely
- Rename .env.sample to .env
- Set DATABASE_URL env variable in the .env file
- Set MAX_DB_SESSIONS_PER_WORKER to a realistic number (1/2/3 should be fine for local usage)
- Run 'diesel setup' command to setup the database
- Run 'diesel migration run' command to run all the migrations
- Run 'cargo watch -x run' to run with hot reloading enabled or simply 'cargo run'
- To try the integration tests, run 'sh scripts/test.sh'
/usr/local/opt/postgresql@14/bin/postgres -D /usr/local/var/postgresql@14
Steps to perform migrations after checkout-
diesel setup
diesel migration generate <migration_name>
diesel migration run
Command to create a new migration script-
diesel migration generate <migration_name>
If you want to reset the database and rerun the migrations, use the following command-
diesel database reset
Use the watch command if hot reloading is desired
cargo watch -x run
cargo run
To launch the test cases with a clean database use the following command-
sh scripts/test.sh
http://localhost:8080/swagger-ui/
This project is licensed under the MIT License - see the LICENSE file for details.