Chain interaction framework

MSP Serverless platform on-chain interaction framework #

Framework scheme #

Architecture #

The MSP Serverless programming framework supports user-defined providers. The wasm compiled by the application, that is, the actor, can interact with the provider in a manner similar to local function calls through abi. In this example, we implement the provider of weelink, so that the wasm program can easily interact with the blockchain.

Untitled

abi interface #

Defines the abi interface for weelink interaction, which can execute/query any on-chain contract. The specific definition of abi is as follows:

invoke(method, args string)

Send the transaction interface, the name of the chaincode function called and the string after the corresponding parameter array json marshal

query(method, …args string)

Send the transaction interface, the name of the chaincode function to be queried, and the string after the corresponding parameter array json marshal

###interface

Use smithy to define the above abi, and define the operations of Invoke and Query. Then use the wash tool to generate the rust SDK library, so that providers and actors can use this library to greatly simplify the code.

/// The Weelink service has a single method, calculate, which
/// calculates the factorial of its whole number parameter.
@wasmbus(
    contractId: "wasmcloud:oneitfarm:weelink",
    actorReceive: true,
    providerReceive: true )
serviceWeelink {
  version: "0.1",
  operations: [ Invoke, Query ]
}

/// Invoke - Execute transaction
operation Invoke {
    input: InvokeRequest,
    output: InvokeResponse,
}

/// Query - Get info from contract.
operation Query {
    input: QueryRequest,
    output: QueryResponse,
}

provider #

For the provider, implement the methods of invoke and query to exchange data on the chain. Its main logic is as follows:

/// Handle Weelink methods
#[async_trait]
impl Weelink for FakePayProvider {
    async fn invoke(&self, ctx: &Context, arg: &InvokeRequest) -> RpcResult<InvokeResponse>{
        // ...
        let res = dyncallgo::call_invoke(method_ref, addr_ref, amount);
        match res {
            Ok(message) => Ok(InvokeResponse{success: true, message: Some(message.to_string()) }),
            Err(e) => Ok(InvokeResponse{success: false, message: Some(format!("{:?}", e)) }),
        }
    }
    /// Query - Get info from contract.
    async fn query(&self, ctx: &Context, arg: &QueryRequest) -> RpcResult<QueryResponse>{
        // ...
        let res = dyncallgo::call_query(method_ref, addr_ref);
        match res {
            Ok(message) => Ok(QueryResponse{success: true, message: Some(message.to_string()) }),
            Err(e) => Ok(QueryResponse{success: false, message: Some(format!("{:?}", e)) }),
        }
    }
}

actor #

The actor can implement any business logic. Here we implement a test actor. When the actor’s invoke interface is called, the actor will call the provider for token transfer. When the actor’s query interface is called, the actor queries the token balance of the address specified by the parameter.

Its core logic is as follows, to determine whether the interface is invoke, and then execute the corresponding logic:

/// Implementation of HttpServer trait methods
#[async_trait]
impl HttpServer for WeelinkActor {

    /// Returns a greeting, "Hello World", in the response body.
    /// If the request contains a query parameter 'name=NAME', the
    /// response is changed to "Hello NAME"
    async fn handle_request(
        &self,
        _ctx: &Context,
        req: &HttpRequest,
    ) -> std::result::Result<HttpResponse, RpcError> {
        info!("request arrived");
        let provider = WeelinkSender::new();
        if req.path.contains("invoke") {
            let invoke_request = &InvokeRequest{address, amount:count, method:"".to_string()};
            let res = provider.invoke(_ctx, invoke_request).await?;
            if !res.success {
                /* handle not authorized */
                return Result::Err(RpcError::from(format!("invoke failed {}", res.message.unwrap()).to_string()));
            }
            message = res.message.unwrap().clone(); // todo change unwrap to match
        } else {
            let res = provider.query(_ctx, query_request).await?;
            if !res.success {
                /* handle not authorized */
                return Result::Err(RpcError::from(format!("query failed {}", res.message.unwrap()).to_string()));
            }
            message = res.message.unwrap().clone(); // todo change unwrap to match
        }
        Ok(HttpResponse {
            body: message.as_bytes().to_vec(),
            ..Default::default()
        })
    }
}

Model test #

steps #

add provider

Untitled

add actor

Untitled

Definitionlink

Untitled

Untitled

Effect #

To call the wasm code, first call the account view function, then call the account transaction function, and call the account view function again to display the account balance.

Untitled

wasmcloud elastic platform interface

Untitled

Meaning #

This model test shows that the wasm code can use the function of the provider to interact with the chain, and obtain the ability to interact with the chain by calling the local ABI function. At the same time, as a program, wasm can implement any business in it. Logic, as a serverless bridge to open up on-chain and off-chain interactions.

Basic knowledge #

Contract #

Weelink supports Ethereum smart contracts, so this test uses standard ERC20 contracts for model testing.

The interaction process with the chain is implemented by the go language and programmed into a dynamic link library for rust to use. The go source code is currently in /Users/zc/Desktop/companyWorkspace/blockchain/chain_adapter/codetest/web3/eth/wasmclouddemo.

Remark #

Due to go provider will support in the near future, and the process and library development workload of the rust chain operation is heavy, the current company’s internal technology stack It is said that maintaining the mind is also a little higher. Therefore, rust is used to call the go dynamic link library to implement the first version of the demo, and then switch to the go provider. Implement the actor in the tinygo way to lower the threshold for programming.

refer to #

Use rust to call the go dynamic library

libloading rust library

Introduction notes on using libloading in Rust

cgo result has go pointer

[Conversion between Rust and C language strings](https://blo