[][src]Module exonum_explorer_service::api::websocket

WebSocket API of the explorer service.

Overview

All communication via WebSockets uses JSON encoding.

The API follows the publisher-subscriber pattern. Clients can subscribe to events. There are two types of events encapsulated in Notification:

  • block creation
  • commitment of a transaction

Subscription types are encapsulated in SubscriptionType. A single client may have multiple subscriptions.

Besides pub-sub, clients may send signed transactions wrapped in TransactionHex. A client should set subscriptions and send transactions using IncomingMessage type. The server responds to each IncomingMessage with a Response, which wraps the response type (() for subscriptions, TransactionResponse for transactions).

There are three WS endpoints, which differ by the initial subscription for the client:

  • api/explorer/v1/ws does not set any subscriptions
  • api/explorer/v1/blocks/subscribe sets subscription to blocks
  • api/explorer/v1/transactions/subscribe sets subscription to transactions. The parameters of the subscription are encoded in the query as TransactionFilter

Examples

Connecting to generic endpoint and setting a subscription:

use websocket::OwnedMessage;

fn stringify(data: &impl serde::Serialize) -> OwnedMessage {
    OwnedMessage::Text(serde_json::to_string(data).unwrap())
}

fn parse<T: serde::de::DeserializeOwned>(data: OwnedMessage) -> T {
    match data {
        OwnedMessage::Text(ref s) => serde_json::from_str(s).unwrap(),
        _ => panic!("Unexpected message"),
    }
}

let mut testkit = TestKitBuilder::validator()
    .with(Spec::new(ExplorerFactory).with_default_instance())
    .build();
let api = testkit.api();
let url = api.public_url("api/explorer/v1/ws");
let mut client = websocket::ClientBuilder::new(&url)?.connect_insecure()?;

// Send a subscription message.
let subscription = SubscriptionType::Blocks;
let message = IncomingMessage::SetSubscriptions(vec![subscription]);
client.send_message(&stringify(&message))?;
// The server should respond with an empty response.
let response: Response<()> = parse(client.recv_message()?);
assert_matches!(response, Response::Success { .. });

// Create a block and check that it is received by the client.
let block = testkit.create_block();
let response = parse::<Notification>(client.recv_message()?);
assert_matches!(
    response,
    Notification::Block(ref header) if *header == block.header
);

Sending a transaction and receiving it as a notification:

// `stringify` and `parse` functions are defined as in the previous example.

#[exonum_interface]
trait ServiceInterface<Ctx> {
    type Output;
    #[interface_method(id = 0)]
    fn do_nothing(&self, ctx: Ctx, _seed: u32) -> Self::Output;
}

#[derive(Debug, ServiceDispatcher, ServiceFactory)]
#[service_dispatcher(implements("ServiceInterface"))]
struct MyService;
// Some implementations skipped for `MyService`...

let mut testkit = TestKitBuilder::validator()
   .with(Spec::new(ExplorerFactory).with_default_instance())
   .with(Spec::new(MyService).with_default_instance())
   .build();
let api = testkit.api();

// Signal that we want to receive notifications about `MyService` transactions.
let url = format!(
    "api/explorer/v1/transactions/subscribe?instance_id={}",
    MyService::INSTANCE_ID
);
let mut client = websocket::ClientBuilder::new(&api.public_url(&url))?
    .connect_insecure()?;

// Create a transaction and send it via WS.
let tx = gen_keypair().do_nothing(MyService::INSTANCE_ID, 0);
let tx_hex = TransactionHex::new(&tx);
let message = IncomingMessage::Transaction(tx_hex);
client.send_message(&stringify(&message))?;

// Receive a notification that the transaction was successfully accepted
// into the memory pool.
let res: Response<TransactionResponse> = parse(client.recv_message()?);
let response = res.into_result().unwrap();
let tx_hash = response.tx_hash;

// Create a block.
let block = testkit.create_block();
assert_eq!(block.len(), 1); // The block contains the sent transaction.

// Receive a notification about the committed transaction.
let notification = parse::<Notification>(client.recv_message()?);
assert_matches!(
    notification,
    Notification::Transaction(ref summary) if summary.tx_hash == tx_hash
);

Structs

CommittedTransactionSummary

Summary about a particular transaction in the blockchain. Does not include transaction content.

TransactionFilter

Filter for transactions by service instance and (optionally) method identifier within the service.

Enums

IncomingMessage

Messages proactively sent by WebSocket clients to the server.

Notification

Notification message passed to WebSocket clients.

Response

Response to a WebSocket client. Roughly equivalent to Result<T, String>.

SubscriptionType

Subscription type for new blocks or committed transactions.