#transaction #processor #wavelet #write #transfer #sender #account

transaction-processor

Write transaction processors for Wavelet in Rust

2 releases

Uses old Rust 2015

0.0.1 Nov 8, 2018
0.0.0 Aug 27, 2018

#12 in #wavelet

35 downloads per month

MIT license

18KB
340 lines

transaction-processor-rs

Documentation

Write transaction processors for Wavelet in Rust.

What's a transaction processor?

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.

Write your own transaction processor

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:

#[derive(Deserialize)]
pub struct Transfer {
    recipient: String,
    amount: u64,
}

#[derive(Serialize)]
pub struct ContractReason {
    pub kind: String,
    pub details: TransferReason,
}

#[derive(Serialize)]
pub struct TransferReason {
    pub amount: u64,
    pub sender: String,
}

Followed by the entry function:

// We name the entry as `handle_transaction`; 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.read_payload()?;

            // 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:

processor_entry!(handle_transaction);

Build

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

Contributions

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:

  1. Fix all warnings from the Rust compiler, unless in some very special cases.
  2. Commit messages are in the format 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.
  3. Consider backwards compatibility. New methods are perfectly fine, though changing the existing public API for example should only be done should there be a good reason.

If you...

  1. love the work we are doing,
  2. want to work full-time with us,
  3. or are interested in getting paid for working on open-source projects

... we're hiring.

To grab our attention, just make a PR and start contributing.

Dependencies

~0.7–1.5MB
~34K SLoC