Write transaction processors for Wavelet in Rust.
In Wavelet when a valid transaction is accepted, it is applied to the ledger state. A transaction processor defines what effects a transaction should have on the ledger state.
For example, as defined in builtin/money
, a transaction tagged transfer
, if valid, should deduct amount
PERLs from the sender account's balance and add the deducted balance to the recipient
account.
A transaction processor is invoked every time a transaction gets applied. The entry function of a transaction processor should have the signature (context: TransactionContext) -> ProcessResult<()>
. The parameter context
contains the tag and payload of the transaction, where tag is for its type, and payload contains processor-defined information. Inside the entry function, you should match context.tag
against your own defined tag, and only execute your logic if the tag is what you want. Otherwise, do nothing and return Err(ProcessError::Ignore)
.
After you've written your entry function, use the macro processor_entry!(your_entry);
to register it. A transaction processor can only have one entry function.
Let's take the builtin money
processor (which handles the transfer
transaction) as an example.
First, we define all the required data structures for serializing/deserializing:
```rust
pub struct Transfer { recipient: String, amount: u64, }
pub struct ContractReason { pub kind: String, pub details: TransferReason, }
pub struct TransferReason { pub amount: u64, pub sender: String, } ```
Followed by the entry function:
``rust
// We name the entry as
handletransaction; Can change to whatever your want.
fn handle_transaction(context: TransactionContext) -> ProcessResult<()> {
// Match on the transaction tag.
match context.tag.as_str() {
"transfer" => {
// Read and decode the payload as type
Transfer`.
let payload: Transfer = context.readpayload()?;
// Load transaction sender.
// Currently, most information about the transaction is kept as immutable global state.
let sender = Account::sender();
// Load transaction recipient with `payload.recipient` as its ID.
let recipient = Account::load(&payload.recipient);
// Invoke the builtin `transfer` operation.
transaction_processor::transfer::transfer(&sender, &recipient, payload.amount)?;
// Build activation reason for the smart contract system.
let reason = transaction_processor::serde_json::to_vec(&ContractReason {
kind: "transfer".into(),
details: TransferReason {
amount: payload.amount,
sender: Account::sender_id(),
},
}).unwrap();
// If the recipient is a smart contract, activate it.
transaction_processor::contract::activate(&payload.recipient, &reason)?;
Ok(())
}
_ => Err(ProcessError::Ignore), // Ignore any transactions we don't understand.
}
} ```
Register the entry:
rust
processor_entry!(handle_transaction);
Make sure you have the latest stable Rust toolchain with the wasm32-unknown-unknown
target installed. If you don't
have the target installed yet, install it with:
rustup target add wasm32-unknown-unknown
Then, run in your project directory:
cargo build --release --target wasm32-unknown-unknown
We at Perlin love reaching out to the open-source community and are open to accepting issues and pull-requests.
For all code contributions, please ensure they adhere as close as possible to the following guidelines:
module_name: Change typed down as a sentence.
This allows our maintainers and everyone else to know what specific code
changes you wish to address.If you...
... we're hiring.
To grab our attention, just make a PR and start contributing.