我使用Docker来尝试使用Scalar DL
由于UDF的术语名称已更改为“Function”,因此本文进行了修正。(2020/1/10)
「Scalar DL」是一种什么?
Scalar科技开发的分布式账本平台软件,灵感来自于区块链技术。
标量 DLT | 标量公司
标量 DL 文件
Scalar DL与区块链相似之处在于具备篡改抵抗性和分散性,但Scalar DL还具有以下独特特点。
-
- ノード数増加に伴う性能劣化が少ない、高いスケーラビリティ
-
- 高い可用性を備えたACID準拠のスマートコントラクト実行
-
- 正確なファイナリティ
- 線形化可能な一貫性
同時候设定了一些限制条件,以防止设计师和开发人员意外的合约行为。
-
- 登録したスマートコントラクトは削除できない
-
- 外部システムからスマートコントラクトのAssetを直接操作できない
- 自作したスマートコントラクトを継承できない
使用Scalar DL执行的智能合约执行结果由同公司开发的分布式数据库管理软件Scalar DB进行管理。
尝试在WSL Ubuntu上运行Scalar DB(环境设置教程)
這次Scalar DL得到了更新,並且新增了一個與其他區塊鏈和相似服務不同的独特功能(將在後文中描述)。我們實際上在docker上運行了它。
请注意
截止到2019年12月,Scalar DL仅提供商业许可证,因此如果要实际使用,需要另外向Scalar公司咨询。
联系表格
开发环境
Ubuntu16.04 LTS 在 Windows10 的 Hyper-V 上进行安装(Oracle Java 8)。
预先准备
Scalar社提供了Scalar DL的执行环境,作为基于docker容器的形式提供。
https://scalardl.readthedocs.io/en/latest/installation-with-docker/
为了启动Docker容器,需要安装Docker Engine和Docker Compose。您可以参考以下网站进行安装:
https://docs.docker.com/install/
https://docs.docker.com/compose/install/
此外,还需要安装Java的运行环境。
$ apt update
$ sudo apt install openjdk-8-jdk
Scalar DL的执行准备
克隆scalar-samples存储库
$ git clone https://github.com/scalar-labs/scalar-samples.git
$ cd scalar-samples
登录
登录到Docker
建立服务
请使用以下命令来构建:
$ sudo docker-compose build
※可能在某些情况下不需要。在 AWS 和 Azure 的 Cent OS 7 中不需要此命令。
启动容器
使用 Docker-compose 启动
在尾部添加-d选项可以使Cassandra在后台启动,但是由于无法进行后续操作直到Cassandra完全启动,因此建议显示日志并确认启动情况。
如果在运行时出现类似”找不到可执行文件”的错误,请检查文件的执行权限,并适当赋予执行权限。
$ chmod +x /path/to/file
最初加载模式/方案之载入
为了使用Scalar DL,需要将初始模式加载到Cassandra服务器上。只需在第一次启动时执行一次该命令。
$ docker-compose exec cassandra cqlsh -f /create_schema.cql
停止容器
按下Ctrl + C键,或使用$ docker-compose down命令。
编辑属性文件
编辑scalar-samples/conf/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
根据自身环境,对以下项目进行编辑。
-
- scalar.ledger.client.server_host : Scalar DLサーバのホスト名 localhostでよい
-
- scalar.ledger.client.cert_holder_id : 証明書の持ち主のID。適当な自身のユーザ名を指定しておく
-
- scalar.ledger.client.cert_path : 証明書ファイルへのパス
- scalar.ledger.client.private_key_path : 秘密鍵ファイルへのパス
合同的签订
标量DL的合约是一个扩展了Contract类并重写了invoke方法的Java类。本次我们将创建一个示例合约,该合约的功能是当输入asset_id和state时,将state值注册到由asset_id指定的资产上。
进入 src/main/java/com/org1/contract/StateUpdater.java 输入 vi 命令。
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;
}
}
关于编译和执行的步骤将在后文中提到。
使用Function和Function创建模式
“Function”是什么东西?
只需一个选项:
这是用Java语言编写的程序。
在合同执行中,可以在同一事务中对Scalar DB执行获取、放置和删除操作。
简而言之,函数可以实现在同一个事务中处理既需要保持不可变性的数据(Immutable Data)又需要进行更改的数据(Mutable Data)。
在设计和实施时,可以在函数内引用和使用合约的参数,但在合约中无法引用和使用函数的参数,需要注意这一点。
功能的示例
从合同的参数中获取asset_id和state,并从Function的参数中获取user_id,然后创建一个函数,将asset_id与user_id和state作为键进行注册。
$ 打开 src/main/java/com/scalar/ist/function/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 的架构
为了将值注册到 Scalar DB 中,需要创建一个用于此目的的函数模式。
但是,在创建模式时,必须是支持事务的形式。
参考:Scalar DB文档-Scalar DB中的内部元数据
在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)
);
确认已创建的表格
cqlsh> use test;
cqlsh:test> describe tables;
test_schema
合同和函数的注册与执行
为了执行合同和功能,需要预先指定每个合同和功能的唯一ID,并使用私钥进行签名,然后将其注册到Scalar DL中。这种机制可以明确指示执行者的身份,防止没有权限的用户执行操作,并带来其他一些好处。
编译
请运行以下命令进行构建: $ ./gradlew assemble
合同的类文件将被创建在build/classes/java/main/com/org1/contract/StateUpdater.class。
合同的注册
只需要一个选项
由于提供了用于注册的简单工具,因此请使用。在进行注册时,需要提供属性文件路径、全局唯一合约ID、合约二进制文件名和类文件路径。
$ 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
当成功时,会显示状态码为200。
注册Function
使用与合同相同的工具进行注册。同样需要属性文件的路径、全局唯一标识、二进制文件名和类文件的路径。
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
当成功时,会显示状态码:200。
执行
使用上述的步骤注册合约和执行函数。执行也是使用工具进行的,使用“-contract-argument”选项来指定合约的参数,并使用“”_functions_”: [“Function的ID1″,”Function的ID2”,…](数组)”的格式来指定在同一笔交易中要执行的函数。函数使用的参数可以使用“-function-argument”选项来指定。
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"}'
成功时,将显示状态码为200。
请注意,如果合同中未进行资产更新,则也不会进行通过函数进行的模式更新。
确认
执行合同后,进行确认是否顺利执行。我们将实施一项合同以获取资产的最新值并进行注册和执行以进行确认。
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();
}
}
合同注册
$ 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
一旦成功,会显示状态码为200。
执行
client/bin/execute-contract -properties conf/client.properties -contract-id StateReader -contract-argument '{"asset_id": "my_asset"}'
如果正确执行的话,应该显示为”state”:{“state”:1}。
关于功能的执行确认,直接检查Cassandra的模式。
$ docker-compose exec cassandra cqlsh
cqlsh> select * from test.test_schema;