Sample Actor Code Walkthrough
Assuming you've successfully built and run the unit test, let's get into the code structure details of this sample actor.
Folder structure overview
Folders and their usage:
Cargo.lock
is a temp file generated during building. It doesn't require further inspection unless you want to check the dependency versions.Cargo.toml
is the root cargo config of this project. It contains only two other workspaces:codec
andimpl
.build.sh
is the script that builds the wasm actor.codec
is one of the two main workspaces. It's related to the definitions of the data structures that will be used by other modules. Consider it an "interface" definition.impl
is another of the two main workspaces. It's related to the implementation of the code logic.rust-toolchain.toml
defines the build environment, versions. etctarget
is generated during build process. It stores the compiled wasm actor and temp files.
Most of the time, you only need to work on the codec and impl folders without touching any of the other files/folders.
The Cargo.toml
file:
The rust-toolchain.toml
file:
The build.sh
file:
codec folder
Use ls
to list the files in the codec
folder:
The contents of the Cargo.toml
file inside the codec folder:
In the package section, you can modify your project name, version etc.
There are two dependencies:
tea-sdk is the main sdk entry to all other TEA sdk modules.
serde is crates.io/crates/serde.
In our tutorial, while adding more logic to the project, we'll have more and more dependencies added into this Cargo file.
Use ls
to list the files in the src folder:
Let's take a look into error.rs
using cat error.rs
:
Rust is a strong typed language which requires all error types to be defined in detail. In the code above we've defined two error types that are used in our sample-actor.
HttpActionNotSupported: In this version of the code, only http GET is handled while other types may throw this error.
GreetingNameEmpty: When receiving a Greeting request from the client, the logic requires a name field inside the request. This name will be used in the response string such as "Hello your-name". But if the name field is empty, the handler will throw this error.
When new errors need to be created, simply add new Error names under the define_scrope!
macro. We'll see more examples like this in the later steps of this tutorial.
Let's take a look into lib.rs
using cat lib.rs
:
In this file we defined three structure types:
GreetingRequest: This request needs a parameter String. It could be a developer's name like Alice.
AddRequest: This is another example handler that adds two i32 numbers from the input.
AddResponse: Send back the result of adding two input numbers.
There is no GreetingResponse because we just print the "Hello Alice" to the console without any return to the client for demo purposes. In order to make the compiler happy, you would need to have the #[response(())]
line. This tells the compiler that this request doesn't attach a response type written by the developer, but it does return a () as response. If you don't have such a line, the compile will give you an error because it considers that you missed a response.
Request and Response are the most important concepts in the actor design, it deserve a standalone chapter to explain. Please go to the "understand request and response" chapter in this tutorial step to learn more.
There's another HTTP GET handler to deal with client request that returns "Hello World". We'll get into that when we walk through the impl
workspace.
impl folder
Let's cd impl
and ls
:
Let's look at the Cargo.toml
file with cat Cargo.toml
:
Note the line sample-actor-codec = { path = "../codec" }
.
We'll need to use the data types defined in the codec folder.
key.pem is a private key file that the developer of this actor knows. It's used for verification purposes by the TEA-runtime to check if the final built wasm binary is correctly signed by the original developer when upgrading. You can generate key.pem using the openssl tool: openssl genrsa -out key.pem
. As a developer, please make sure you keep the key.pem file securely stored. Whoever has such a pem file can impersonate you to sign a malicious wasm file under your name.
Let's look at the manifest.yaml
file with cat manifest.yaml
:
The manifest is an important definition file for this actor. It's part of the binary wasm that's signed during the build.
The actor_id should be a unique id for every actor. An example of a typical actor name is "com.tea_core_team.sample_for_tutorial". We'll release a naming convention later.
The owner_id is the developer's ETH address (H160). Make sure you input it correctly as it's used for payment.
The token_id is the H160 ETH address that the TApp owns. Before deployment, the developer needs to create such a TApp in the TEA developer portal first to obtain a token_id for this TApp. In our local dev-runner, it's ok to set it to 0x0, since we won't be using the Developer Portal in the local dev-runner. All actors that work for this TApp will need to use this token_id for billing purposes. It's important to match the owner of the TApp, the owner_id, to your ETH address as the developer. The Developer Portal will reject your request if there's a mismatch during deployment or upgrade.
The access
is a public disclaimer that lists of all the other modules that this actor will communicate with. Please make sure you only claim the modules that this actor absolutely needs to communicate with, otherwise it may cause security concerns from users. For example, if an actor claims to communicate a billing related module that it's not permitted to access, the end user or reviewers would mark this as a security concern for this actor in the community. On the other hand, if this actor attempts to communicate with another module that's not listed in the access
list, it will be rejected at runtime by the TEA security logic.
If you cd
into the src
folder, you can run ls
to see the three files:
error.rs
defines the Error typesslib.rs
is the main entrance point for the code logic.tests.rs
contains all unit tests.
Using cat error.rs
to view the error.rs
file:
Remember that we've defined HttpActionNotSupported
and HttpActionNotSupported
IDs in the codec project. Here we'll put them into SampleActor
to connect those IDs to the actually structures defined right below.
Let's use the HttpActionNotSupported
as an example:
This error has a parameter string. When it throws this error, the name of the unsupported method can be assigned as the parameter, so that the user can get a more meaningful error detail. The error string will look like "Http method POST is not supported" in case of "post".
The lib.rs
file has all the main logic that handles requests:
You can leave those macros untouched by your project. Those macros are designed to simplify the code.
First we define the actor structure:
Then we list all Types that should be handled using:
After this we implement those trait Handles by writing impl HandlerActor for Actor
and fill in the code logic for each impl.
In our sample actor example, we only handle three developer-defined instances of business logic and one system request which is called Activate
.
Activate is called when the actor is loaded into the runtime for the first time and starts running. The default behavior is in the following code:
These code register this actor to the http adapter because this actor will need to receive http requests from the outside world. Once registered, the http adapter (another service running outside of the enclave) will know where to dispatch the http request.
In our case, we add a say-hello
string vec as the action name. This action name will be used when handling http requests later. Beause our sample actor is so simple it doesn't have any complex logic. We simply put a "say-hello" whenever we receive an http request regardless what the request content is. We'll see more complicated examples in the future steps of our tutorial.
Here we handle the HttpRequest:
Because we registered the http request to the "say-hello" action name, it should always get a 'Hello world!' string.
To demonstrate a more complex case, let's imagine the request contains a parameter (the developers name). For that we have the GreetingsRequest:
In this example handler, the name
is the parameter of the request. The handler checks if it's empty then returns a GreetingNameEmpty error. If it's not empty, then print the Hello, THE_INPUT_NAME !
to the console. That's why you can see it when you run the unit test.
We can also have have multiple parameters in one request. We have the AddRequest handler to demonstrate this:
This handler simply adds up two input numbers and returns the sum.
Note, the GreetingRequest and AddRequest don't have http registered, so you cannot call them directly from the browser by sending an http request. They're only available for unit testing in our current step. We'll add more http request handlers in the future steps of this tutorial.
You can find how those requests are handled and what the expected results are from the test.rs
source code.
Writing unit tests is very important for TEA development.
Here's an example unit test file, test.rs
:
Last updated