Before following this tutorial, make sure you've installed
- Docker (https://www.docker.com/)
- Metamask (https://metamask.io)
You will need a private-public key pair to register your service in SNET. Generate them in Metamask before you start this tutorial.
Run this tutorial from a bash terminal.
We'll use Java gRPC, for more details see https://grpc.io/docs/
In this tutorial we'll create a Java service and publish it in SingularityNET.
Setup a ubuntu:18.04
docker container using provided Dockerfile
.
$ docker build --build-arg language=java -t snet_java_service https://github.com/singnet/wiki.git#master:/tutorials/Docker
$ docker run -p 7000:7000 -ti snet_java_service bash
From this point we follow the tutorial in the Docker container's prompt.
# cd wiki/tutorials/howToWriteJavaService
Create the skeleton structure for your service's project
# ./create_project.sh PROJECT_NAME ORGANIZATION_NAME SERVICE_NAME SERVICE_PORT
PROJECT_NAME
is a short tag for your project. It will be used to name
project's directory and as a namespace tag in the .proto file.
ORGANIZATION_NAME
is the name of an organization that you are a member or owner.
SERVICE_NAME
is the name of your service.
SERVICE_PORT
is the port number (in localhost) the service will listen to.
create_project.sh
will create a directory named PROJECT_NAME
with a basic
empty implementation of the service.
In this tutorial we'll implement a service with two methods:
- int div(int a, int b)
- string check(int a)
So we'll use this command line to create project's skeleton
# ./create_project.sh tutorial snet math-operations 7070
# cd tutorial
Now we'll customize the skeleton code to actually implement our basic service.
We need to edit ../howToWriteJavaService/tutorial/src/main/java/service_spec/tutorial.proto
and define
- the data structures used to carry input and output of the methods, and
- the RPC API of the service.
Take a look at https://developers.google.com/protocol-buffers/docs/overview to
understand everything you can do in the .proto
file.
Edit the proto file:
# nano src/main/java/service_spec/tutorial.proto
In this tutorial our proto file should be like this:
syntax = "proto3";
option java_generic_services = true;
option java_multiple_files = true;
message IntPair {
int32 a = 1;
int32 b = 2;
}
message SingleInt {
int32 v = 1;
}
message SingleString {
string s = 1;
}
service ServiceDefinition {
rpc div(IntPair) returns (SingleInt) {}
rpc check(SingleInt) returns (SingleString) {}
}
Each message
statement define a data structure used either as input or output
in the API. The service
statement defines the RPC API itself.
In order to actually implement our API we need to edit the JavaServer.java file
.
Look for SERVICE_API
and replace doSomething()
by our actual API methods:
@Override
public void div(IntPair request, StreamObserver<SingleInt> responseObserver) {
int result = request.getA() / request.getB();
SingleInt reply = SingleInt.newBuilder().setV(result).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
Now we'll write a client to test our server locally (without using the
blockchain). Edit JavaClient.java
.
Look for TEST_CODE
and replace doSomething()
implementation by our
testing code:
public void div(int a, int b) {
logger.info("Trying to divide "+a+" by "+ b);
IntPair request = IntPair.newBuilder().setA(a).setB(b).build();
SingleInt response;
try {
response = blockingStub.div(request);
logger.log(Level.INFO, "Result: " + response.getV());
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
}
To compile the protobuf and generate server and client jar:
Note 1: protobuf compile is embedded in the commands below. For more details, please edit build.sh.
Note 2: On you project name, used in the previous command ./create_project.sh
To generate a server application:
# sh build.sh tutorial server
To generate a client application:
# sh build.sh tutorial client
To test our server locally (without using the blockchain)
# java -jar ./bin/JavaServer.jar &
In a new terminal instance
# java -jar ./bin/JavaClient.jar 12 4
You should have something like the following output:
# java -jar ./bin/JavaServer.jar &
[1] 1627
Nov 18, 2018 5:27:16 AM JavaServer start
INFO: Server listening on 7070
# java -jar ./bin/JavaClient.jar 12 4
Client connected on port: 7070
Nov 18, 2018 5:30:13 AM JavaClient div
INFO: Trying to div 12 by 4
Nov 18, 2018 5:30:13 AM JavaClient div
At this point you have successfully built a gRPC Java service. The executables can be used from anywhere inside the container (they don't need anything from the installation directory) or outside the container.
The next steps in this tutorial will publish the service in SingularityNET.
Now you must follow the howToPublishService tutorial to publish this service or use our script (next step).
You'll also need a SNET CLI
identity (check step 3 from howToPublishService).
First, make sure you killed the server
process started in Step 7.
Then publish and start your service:
# ./publishAndStartService.sh PAYMENT_ADDRESS
Replace PAYMENT_ADDRESS
by your public key (wallet).
Example:
# ./publishAndStartService.sh 0xA6E06cF37110930D2906e6Ae70bA6224eDED917B
This will start the SNET Daemon
and your service. If everything goes well you will
see the blockchain transaction logs and then the following messages
(respectively from: your service and SNET Daemon
):
[blockchain log]
INFO: Server listening on 7070
[daemon initial log]
INFO[0002] Blockchain is enabled: instantiate payment validation interceptor
INFO[0002] PaymentChannelStorageClient="&{ConnectionTimeout:5s RequestTimeout:3s Endpoints:[http://127.0.0.1:2379]}"
INFO[0002] Default payment handler registered defaultPaymentType=escrow
DEBU[0002] starting daemon
You can double check if it has been properly published using
# snet organization list-services snet
Optionally you can un-publish the service
# snet service delete snet math-operations
Actually, since this is just a tutorial, you are expected to un-publish your service as soon as you finish the tests.
Other snet
commands and options (as well as their documentation) can be found
here.
You can test your service making requests in command line:
The testServiceRequest.sh
script is set to use channel id 0
, if your
SNET CLI
identity already had opened previous channels, you'll have to
set channel id manually at.
# ./testServiceRequest.sh 12 4
[blockchain log]
response:
v: 3
That's it. Remember to delete your service as explained in Step 9.
# snet service delete snet math-operations