-
Notifications
You must be signed in to change notification settings - Fork 122
libain gRPC intgration
libain-rs
takes advantage of Rust async/await to offer JSON RPC and gRPC servers. It also generates the necessary C++ glue code for seamlessly transitioning RPC functions to FFI functions which are then used by the library to serve RPC calls.
Advantages:
- Having a typed language-agnostic spec (protobuf) which can then be used by other languages to generate their own clients and communicate through gRPC
- Getting rid of
libevent
dependency in C++ - Pave way for a new testing framework in Rust
Most of the work is automated in the Rust side (through codegen generated from build script), so you'll only have to modify the protobuf files and add/change C++ functions. This should automatically reflect in the served RPCs.
-
init_runtime
: To instantiate logger and runtime (event loop, scheduler and executor). The ownership is then given to C++ through an opaque type, which is then required for the rest of the functions. -
start_servers
: Spawns RPC servers in a separate thread bound to the provided addresses. -
stop_servers
: Consumes the runtime once and for all and shuts down the servers.
Let's take getbestblockhash
function as an example.
Firstly, we should write a fair enough protobuf definition for this function:
syntax = "proto3";
package rpc;
import "google/protobuf/empty.proto";
import "types/block.proto";
service Blockchain {
// Returns the hash of the best (tip) block in the most-work fully-validated chain.
rpc GetBestBlockHash(google.protobuf.Empty) returns (types.BlockResult);
}
This will reside in protobuf/rpc/blockchain.proto
inside libain-rs
The corresponding type will be in protobuf/types/block.proto
syntax = "proto3";
package types;
message BlockResult {
string hash = 1; // Hex-encoded data for block hash
}
In codegen.rs
, we already have:
pub mod types {
tonic::include_proto!("types");
}
pub mod rpc {
tonic::include_proto!("rpc");
}
This will include the code generated from both rpc
and types
protobuf modules.
As we've added a new service, we have to mount it on both JSON RPC and gRPC servers. This can be done in lib.rs
. All we have to do is import BlockchainService
from codegen::rpc
and invoke BlockchainService::service()
and BlockchainService::module()
functions for adding them to the corresponding servers.
During compilation, Rust will emit the C++ function signature inside libain.cpp
and libain.hpp
files in target/
directory. The generated Rust function will look like:
fn GetBestBlockHash(result: &mut BlockResult) -> Result<()>;
You can debug the generated code by navigating to target/debug/build/ain-grpc-{checksum}/out/*.rs
and run cargo fmt -- target/debug/build/ain-grpc-*/out/*.rs
to format the code for readability.
The corresponding C++ function will look like:
void GetBestBlockHash(BlockResult &result);
Note that the return value is passed as a mutable reference. This way, Rust will have exclusive ownership to the struct and we don't have to move/free anything in C++ side.
These changes should then be pushed to a branch in libain-rs
repo.
libain-rs
branch should now be reflected in depends/packages/libain.mk
inside ain
repo.
Based on the emitted function signature, the actual implementation in defid
will now look like:
void GetBestBlockHash(BlockResult &result)
{
LOCK(cs_main);
result.hex_data = ::ChainActive().Tip()->GetBlockHash().GetHex();
}
Now, if we run ./make.sh build
, we'll have the newly added function as part of defid
.