Skip to main content

Delivery chain (receive message)

For the delivery chain, we only need to think about two things: we want to keep track of which orders we should deliver but must also take into account if, for some reason, the message is delayed such that the shipment becomes ready before it was created. This would never happen in a real-life scenario, but we'll consider it in this in this example.

We, therefore, create two entities in a new file, src/delivery_chain.rell to hold this information:

src/delivery_chain.rell
module;

enum shipping_state {
CREATED,
DISPATCHED,
DELIVERED
}

entity delivery {
key order_id: integer;
index customer_id: integer;
shipping_address: text;
mutable shipping_state;
}

// orders that can be dispatched but have not yet been created
entity pending_delivery {
key order_id: integer;
}

This model says that each order can have three states for when it is created, dispatched, and delivered. Let's add a few queries to fetch these values and an operation to mark a shipment as delivered.

src/delivery_chain.rell
operation accept_delivery(order_id: integer) {
require(delivery @ { order_id }.shipping_state == shipping_state.DISPATCHED, "Order must be dispatched before it can be completed");
update delivery @ { order_id } ( shipping_state = shipping_state.DELIVERED);
}

query get_delivery_details(order_id: integer) = delivery @* { order_id } ($.to_struct());

query list_deliveries() = delivery @* {} ($.to_struct());

Receiving a message

To be able to receive a message, we must first configure our new blockchain and add a reference to the receiver that we defined in the introduction. Let's add the following to our chromia.yml file.

chromia.yml
blockchains:
delivery-chain:
module: delivery_chain
config:
<<: *receiver
icmf:
receiver:
local:
- topic: "L_delivery"
brid: null
- topic: "L_shipment_ready"
brid: null

In this configuration, we make the dapp subscribe to the topics L_delivery and L_shipment_ready topics, as defined by our dapp code. The brid should point to the producer of the message topic, but as we have not deployed our dapp to a network yet, we leave it as null.

info

Note that null only works when testing locally. When deploying to a real network, the producer chain must be deployed first to obtain the brid to be specified in this field.

To handle the two message types, we add the following imports.

src/delivery_chain.rell
import messages. { topic.*, msg };
import lib.icmf.receiver.{ receive_icmf_message };

and define a new extension of receive_icmf_message

src/delivery_chain.rell
@extend(receive_icmf_message)
function (sender: byte_array, topic: text, body: gtv) {
when (topic) {
NEW_DELIVERY -> handle_new_delivery(msg.delivery_details.from_gtv(body));
SHIPMENT_READY -> handle_shipment_ready(msg.shipment_ready.from_gtv(body));
else -> require(false, "Message type %s not handled".format(topic));
}
}

function handle_new_delivery(msg: msg.delivery_details) {
val state = pending_delivery @? { msg.order_id } (shipping_state.DISPATCHED) ?: shipping_state.CREATED;
create delivery(
order_id = msg.order_id,
customer_id = msg.customer_id,
shipping_address = msg.shipping_address,
shipping_state = state
);
delete pending_delivery @? { msg.order_id };
}

function handle_shipment_ready(msg: msg.shipment_ready) {
if (not exists(delivery @? { .order_id == msg.order_id })) {
create pending_delivery ( msg.order_id );
} else {
update delivery @ { .order_id == msg.order_id } ( shipping_state = shipping_state.DISPATCHED );
}
}

The extension checks the topic and switches behavior depending on the topic. If the message topic is not handled, we fail the operation, halting the block building process, as this means incorrect configuration. We take the two message types by creating or updating entities respectively to mark the delivery with the correct state.

Testing

Similarly, for the order chain, we create a new test module, delivery_chain_test.rell, and configure it

chromia.yml
blockchains:
delivery-chain:
...
test:
modules:
- delivery_chain_test

We then create a test that emits events using ICMF test utilities and makes sure the correct shipment orders and states are created:

src/delivery_chain_test.rell
@test module;

import delivery_chain.*;
import lib.icmf.test.{ test_icmf_message };

function test_make_delivery() {
rell.test.tx().op(
test_icmf_message(
x"",
NEW_DELIVERY,
msg.delivery_details(
order_id = 1,
customer_id = 10,
shipping_address = "MyStreet 101"
)
.to_gtv()
)
)
.run();
assert_equals(delivery @ { .order_id == 1 }.shipping_state, shipping_state.CREATED);
rell.test.tx().op(accept_delivery(1)).run_must_fail("must be dispatched before");
rell.test.tx().op(
test_icmf_message(
x"",
SHIPMENT_READY,
msg.shipment_ready(order_id = 1)
.to_gtv()
)
)
.run();
assert_equals(delivery @ { .order_id == 1 }.shipping_state, shipping_state.DISPATCHED);
rell.test.tx().op(accept_delivery(1)).run();
}