The term name of UDF has been changed to Function, so the text has been revised. (2020/1/10)
Blockchain-inspired distributed ledger platform software developed by Scalar.
(reference) Scalar DLT | Scalar, Inc. Scalar DL Docs
Similar to blockchain in that it is tamper resistant and decentralized, Scalar DL has the following additional features:
At the same time, some restrictions are set to prevent unintended contract behavior by designers and developers.
The execution result of the smart contract executed by Scalar DL is managed by Scalar DB, which is the distributed database management software developed by the company.
Try running Scalar DB on WSL Ubuntu (Environment construction)
This time, this Scalar DL was updated, and it was said that a unique mechanism called Function (described later) that other blockchains and similar services do not have was added, so I actually tried running it with docker.
** As of December 2019, Scalar DL is provided only with a commercial license, so you need to contact Scalar separately for actual use. ** **
Ubuntu16.04 LTS (built on Windows 10 with Hyper-V) Oracle Java 8
A docker container is provided by Scalar as an execution environment for Scalar DL. https://scalardl.readthedocs.io/en/latest/installation-with-docker/
Docker Engine and Docker Compose are required to start the docker container, so install it referring to the following site. https://docs.docker.com/install/ https://docs.docker.com/compose/install/
You also need a java execution environment.
$ apt update
$ sudo apt install openjdk-8-jdk
$ git clone https://github.com/scalar-labs/scalar-samples.git
$ cd scalar-samples
$ docker login
$ sudo docker-compose build
cent os 7
.$ docker-compose up
If you add the -d
option at the end, you can start it in the background, but since you can not perform further operations until Cassandra's start is completed, it is better to display the log and check the start. Is done.
If an error such as "There is no executable file" appears during execution, check the execution permission of the file and grant the execution permission as appropriate.
$ chmod +x /path/to/file
You need to load the Cassandra server with the initial schema needed to use Scalar DL. This command only needs to be executed once at the first startup.
$ docker-compose exec cassandra cqlsh -f /create_schema.cql
Ctrl + C
or $ docker-compose down
Edit scalar-samples / conf / client.properties and configure the properties file. The following are the minimum settings.
client.properties
# A host name of Scalar DL network server.
scalar.ledger.client.server_host=localhost
# An ID of a certificate holder. It must be configured for each private key and unique in the system.
scalar.ledger.client.cert_holder_id=foo
# A certificate file path to use.
scalar.ledger.client.cert_path=/path/to/foo.pem
# A private key file path to use.
scalar.ledger.client.private_key_path=/path/to/foo-key.pem
Edit the following items according to your environment
The Scalar DL contract is a Java class that extends the Contract class and overrides the invoke method. This time, as a sample, when asset_id and state are input, a sample contract that registers state as a value in the asset specified by asset_id is created.
$ vi src/main/java/com/org1/contract/StateUpdater.java
StateUpdater.java
package com.org1.contract;
import com.scalar.ledger.asset.Asset;
import com.scalar.ledger.contract.Contract;
import com.scalar.ledger.exception.ContractContextException;
import com.scalar.ledger.ledger.Ledger;
import java.util.Optional;
import javax.json.Json;
import javax.json.JsonObject;
public class StateUpdater extends Contract {
@Override
public JsonObject invoke(Ledger ledger, JsonObject argument, Optional<JsonObject> properties) {
if (!argument.containsKey("asset_id") || !argument.containsKey("state")) {
// ContractContextException is the only throwable exception in a contract and
// it should be thrown when a contract faces some non-recoverable error
throw new ContractContextException("please set asset_id and state in the argument");
}
String assetId = argument.getString("asset_id");
int state = argument.getInt("state");
Optional<Asset> asset = ledger.get(assetId);
if (!asset.isPresent() || asset.get().data().getInt("state") != state) {
ledger.put(assetId, Json.createObjectBuilder().add("state", state).build());
}
return null;
}
}
The procedure for compiling and executing will be described later.
It is a program written in Java language. Get, Put, and Delete can be executed for Scalar DB within the same transaction as contract execution.
In other words, what can be done is that Function can "realize both Immutable Data processing that requires tamper resistance and Mutable Data processing that requires change in the same transaction." become.
At the time of design / implementation, it should be noted that the argument of the contract can be referred to in the Function and used for processing, but the argument of the Function cannot be referred to on the contract side.
Get ʻasset_id and
state from the argument of the contract, get ʻuser_id
from the argument of Function, and create a Function that registers ʻasset_id with ʻuser_id
and state
as keys.
$ vi src/main/java/com/scalar/ist/function/SchemaUpdater.java
SchemaUpdater.java
package com.scalar.ist.function;
import com.scalar.database.api.Get;
import com.scalar.database.api.Put;
import com.scalar.database.api.Result;
import com.scalar.database.io.IntValue;
import com.scalar.database.io.Key;
import com.scalar.database.io.TextValue;
import com.scalar.ledger.database.MutableDatabase;
import com.scalar.ledger.udf.Function;
import java.util.Optional;
import javax.json.JsonObject;
public class SchemaUpdater extends Function {
@Override
public void invoke(
MutableDatabase database,
JsonObject contractArgument,
Optional<JsonObject> functionArgument) {
String userId = functionArgument.get().getString("user_id");
int state = contractArgument.getInt("state");
String assetId = contractArgument.getString("asset_id");
Get get =
new Get(
new Key(new TextValue("user_id", userId)),
new Key(new IntValue("state", state)))
.forNamespace("test")
.forTable("test_schema");
database.get(get);
Put put =
new Put(
new Key(new TextValue("user_id", userId)),
new Key(new IntValue("state", state)))
.withValue(new TextValue("value",assetId))
.forNamespace("test")
.forTable("test_schema");
database.put(put);
}
}
Function creates a schema for registering values in the Scalar DB. However, the schema created here must be a schema that corresponds to the transaction. Reference: Scalar DB Docs --Internal metadata in Scalar DB
Schema creation is done by entering commands from the Cassandra shell
$ docker-compose exec cassandra cqlsh
cqlsh> create table test.test_schema
(
user_id text,
state int,
value text,
before_value text,
before_tx_committed_at bigint,
before_tx_id text,
before_tx_prepared_at bigint,
before_tx_state int,
before_tx_version int,
tx_committed_at bigint,
tx_id text,
tx_prepared_at bigint,
tx_state int,
tx_version int,
primary key (user_id, state)
);
Check the created table
cqlsh> use test;
cqlsh:test> describe tables;
test_schema
In order to execute a contract or function, it is necessary to specify a unique ID for each contract or function, sign it with a private key, and register it in Scalar DL. With this mechanism, it becomes clear who executed it, and it is possible to prevent the execution of unauthorized users.
$ ./gradlew assemble
The contract class file is created in build / classes / java / main / com / org1 / contract / StateUpdater.class
.
A simple tool for registration is available, so use it. When registering, you will need the path of the property file, the ID of the globally unique contract, the binary name of the contract, and the path of the class file.
$ client/bin/register-contract -properties conf/client.properties -contract-id StateUpdater -contract-binary-name com.org1.contract.StateUpdater -contract-class-file build/classes/java/main/com/org1/contract/StateUpdater.class
If successful, status: 200
will be displayed.
Register using the same tool as the contract. The same goes for the property file path, globally unique ID, binary name, and class file path.
client/bin/register-function -properties conf/client.properties -function-id SchemaUpdater -function-binary-name com.scalar.ist.function.SchemaUpdater -function-class-file build/classes/java/main/com/scalar/ist/function/SchemaUpdater.class
If successful, status: 200
will be displayed.
Execute the contract and Function registered in the above procedure. It is also executed by the tool, and the argument of the contract is specified by the -contract-argument
option, and it is in the format of"_functions_": ["Function ID1", "Function ID2", ...](array)
. Specify the Function to be executed in the same transaction. Arguments used in Function are specified by the -function-argument
option.
client/bin/execute-contract -properties conf/client.properties -contract-id StateUpdater -contract-argument '{"asset_id": "my_asset", "state": 1, "_functions_": ["SchemaUpdater"]}' -function-argument '{"user_id": "john"}'
If the execution is successful, status: 200
is displayed.
Note that if the contract does not update the asset, the Function will not update the schema either.
After executing the contract, check if it was executed without any problem. For the execution result of the contract, implement the contract that acquires the latest value of the asset, register and execute it, and check it.
StateReader.java
package com.org1.contract;
import com.scalar.ledger.asset.Asset;
import com.scalar.ledger.asset.InternalAsset;
import com.scalar.ledger.contract.Contract;
import com.scalar.ledger.ledger.Ledger;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import java.util.Optional;
public class StateReader extends Contract {
@Override
public JsonObject invoke(Ledger ledger, JsonObject argument, Optional<JsonObject> properties) {
String assetId = argument.getString("asset_id");
Optional<Asset> asset = ledger.get(assetId);
InternalAsset internal = (InternalAsset) asset.get();
JsonObjectBuilder builder = Json.createObjectBuilder()
.add("state", internal.data());
return builder.build();
}
}
Contract registration
$ client/bin/register-contract -properties conf/client.properties -contract-id StateReader -contract-binary-name com.org1.contract.StateReader -contract-class-file build/classes/java/main/com/org1/contract/StateReader.class
If successful, status: 200
will be displayed.
Run
client/bin/execute-contract -properties conf/client.properties -contract-id StateReader -contract-argument '{"asset_id": "my_asset"}'
If done correctly, you should see " state ": {" state ": 1}
.
To check the execution of Function, check Cassandra's schema directly.
$ docker-compose exec cassandra cqlsh
cqlsh> select * from test.test_schema;
If executed correctly, you should get the following result (values such as tx_id
are probably different)
Recommended Posts