Chromia
Start Building

11 / 27Module 1: A Rell backend with FT accountsBeginner

Basic operations

This part of the tutorial covers basic operations on the Chromia blockchain, focusing on how you write data with transactions. When you finish, you will know how to create users, publish posts, and manage followers in your dapp.

Add data through transactions

You write data to a Chromia blockchain by sending transactions. Each transaction holds one or more database actions called "operations." The operations below create users, publish posts, and set up follow relationships.

Register a user

src/registration/module.rell
operation register_user(name) {    val account = register_account();    val user = create user ( name, account.id, account );    create follower ( user = user, follower = user );}

register_user adds a new user to the database from a single parameter, name, which holds the user's name. The public key that serves as the id goes straight into the constructor, because Rell matches arguments to attributes by their type.

  • The public key must be a byte_array that is 32 or 64 bytes long.

You can also write it this way:

rell
create user( name = name, id = pubkey );

Make a post

rell/src/news_feed/operations.rell
operation make_post(content: text) {    val account = auth.authenticate();    require(content.size() <= 255, "A post cannot be longer than 255 letters");    val user = user @ { account };    create post ( user, content );}

make_post publishes a post. You pass it user_id, the ID of the author, and content, the post's text. You don't supply a timestamp, since the entity definition gives it a default value.

Follow a user

rell/src/news_feed/operations.rell
operation follow_user(follow_pubkey: pubkey) {    val account = auth.authenticate();    val user = user @ { account };    val follow = require_user(follow_pubkey);    create follower ( user = follow, follower = user );}

follow_user lets one user follow another. It takes user_id, the ID of the user doing the following, and follow_id, the ID of the user being followed. It loads both users from the database and then creates a follower entity that records the relationship.

Unfollow a user

Unfollowing means deleting the matching follower entity.

rell/src/news_feed/operations.rell
operation unfollow_user(unfollow_id: byte_array) {    val account = auth.authenticate();    val user = user @ { account };    val follow = require_user(unfollow_id);    delete follower @? { .user == follow, .follower == user };}

Test the operations

Next, you add unit tests to confirm the dapp code behaves as expected.

Test module

  1. Open the test folder in your project directory.
  2. Find the file news_feed_test.rell there. Your test cases go in this file.
src/test/news_feed_test.rell
@test module;import ^.test_operations. { create_user };import ^^.news_feed.*;import lib.ft4.test.core. { ft_auth_operation_for };
  • The @test annotation on the module declaration marks it as test code.
  • import^^.news_feed.*; pulls in everything from the news_feed module so your tests can reach the dapp code.
  • For tests that use FT4 authentication, call auth.ft_auth or auth.evm_auth
    in the same transaction, immediately before the operation. To build it, use the ft_auth_operation_for function from the test module of the ft library.

Test dapp

The following test cases check that your dapp works correctly by covering user creation, follow relationships, and posts.

src/test/news_feed_test.rell
val alice = rell.test.pubkeys.alice;val bob = rell.test.pubkeys.bob;function test_create_entities() {    rell.test.tx()        .op(create_user("Alice", alice))        .op(create_user("Bob", bob))        .run();    assert_equals(user @ { } ( @sum 1 ), 2);    rell.test.tx()        .op(ft_auth_operation_for(alice))        .op(follow_user(bob))        .op(ft_auth_operation_for(alice))        .op(make_post("My post"))        .sign(alice_kp)        .run();    assert_true(is_following(alice, bob));    assert_equals(follower @ { } ( @sum 1 ), 1);    assert_equals(post @ { } ( @sum 1 ), 1);    rell.test.tx()        .op(ft_auth_operation_for(alice))        .op(unfollow_user(bob))        .sign(alice_kp)        .run();    assert_equals(follower @ { } ( @sum 1 ), 0);}

What this test does:

  • It takes two public keys from the Rell test framework, alice and bob, and uses them as user IDs.
  • test_create_entities is a test case, which its test_ prefix signals. It sends a sequence of transactions that create users and handle their interactions.
  • After every transaction, assert_equals verifies that each table contains the expected number of entities. The count comes from @sum, which aggregates over the table; the summed value is set to 1 since only the number of entities matters.

The updated test_input_verification shows how well this approach blocks impersonation. When Charlie attempts to post, the attempt fails because he cannot produce the auth operation. Here is the revised test:

src/test/news_feed_test.rell
function test_input_verification() {    rell.test.tx()        .op(create_user("Alice", alice))        .op(create_user("Bob", bob)).run();    // Bob cannot impersonate alice    rell.test.tx()        .op(ft_auth_operation_for(alice))        .op(follow_user(bob))        .sign(bob_kp)        .run_must_fail();    rell.test.tx()        .op(ft_auth_operation_for(alice))        .op(make_post("My malicous post"))        .sign(bob_kp)        .run_must_fail();    // Alice cannot follow non-existing charlie    val f1 = rell.test.tx()        .op(ft_auth_operation_for(alice))        .op(follow_user(charlie))        .sign(alice_kp)        .run_must_fail();    assert_true(f1.message.contains("does not exist"));    // Charlie cannot create post since he does not exist    val f2 = rell.test.tx()        .op(make_post("My secret post"))        .sign(charlie_kp)        .run_must_fail();    assert_true(f2.message.contains("Expected at least two operations"));}

Run the tests

You can now run the tests to check your dapp.

  1. In a terminal, go to the root directory of your project.

  2. Run this command:

shell
chr test

The command executes the tests found in the test: modules listed in chromia.yml. When every test passes, the output confirms that your dapp behaves as intended.

Your dapp now has unit tests that help keep it reliable.

Written for this site from Chromia’s public course. Code samples are the course’s own.