Trong bài viết này tôi sẽ giải thích cách sử dụng Z.com Cloud BlockChain hay là Conoha BlockChain. Mặc dù có hướng dẫn công khai được cũng cấp bởi Z.com nhưng chỉ là những ví dụ làm sao để tạo Contract . Còn để làm 1 ứng dụng thực tế thì phải làm thế nào thì có lẽ thật khó để tưởng tượng ra được. Chính vì vậy trong bài viết này sẽ giải thích code làm thế nào để tạo ra 1 ứng dựng thực tiễn.

Trong bài viết này mình không giải thích tổng quan  về Smart Contract, và cách deploy Smart Contract lên Zcom BlockChain, vì nó đã được mô tả trong hướng dẫn của Z.com ở link bên duới, các bạn có thể tham khảo . Bài  viết dành cho những ai đang sử dụng dịch vụ của Z.com BlockChain, có thể sẽ khó hiểu cho những ai không sử dụng dịch vụ này !!!

https://guide.blockchain.z.com/en/

Tóm lược về ứng dụng :

Ví dụ về các ứng dụng được phát triển dựa trên nền tảng BlockChain thì đồng tiền ảo là một đại diện tiêu biểu cho các ứng dụng đó. Trong bài biết này tối trình bày cách tạo ra đồng tiền ảo . Các chức năng của ứng dụng gồm có kiểm tra số tiền sở hữu và chức năng chuyển tiền . Ta dùng ngôn ngữ Solidity để tạo SmartContract và HTML + JavaScript để tạo ứng dụng web .

Smart Contract :

Smart Contract mà sử dụng Z.com Cloud Blockchain thì sẽ được cấu thành từ 4 Contract :

- Contract interface sẽ là MainContract .

- Business logic sẽ là LogicContract .

- Việc ghi log sẽ là EventContract .

- Ghi và lưu dữ liệu sẽ là TableContract .

Lần này ứng dụng tạo đồng tiền ảo sẽ có tên là MyCoin .

Mỗi quan hệ của các Contract sẽ giống như sơ đồ bên dưới.

mycoin_class

Main Contract

Main Contract sẽ là  MyCoinContract_v1.sol ở  phía  dưới .

pragma solidity ^0.4.2;

import '../../gmo/contracts/VersionContract.sol';
import './MyCoinLogic_v1.sol';

contract MyCoinContract_v1 is VersionContract {
uint constant TIMESTAMP_RANGE = 60;
MyCoinLogic_v1 public logic_v1;

function MyCoinContract_v1(ContractNameService _cns, MyCoinLogic_v1 _logic) VersionContract(_cns, 'MyCoin') {
    logic_v1 = _logic;
}

function setMyCoinLogic_v1(MyCoinLogic_v1 _logic) onlyByProvider {
    logic_v1 = _logic;
}

function checkTimestamp(uint _timestamp) private constant returns(bool) {
    return (now - TIMESTAMP_RANGE < _timestamp && _timestamp < now + TIMESTAMP_RANGE);
}

function exist(address _account) constant returns (bool) {
    return logic_v1.exist(_account);
}

function send(bytes _sign, uint _timestamp, address _to, uint _value) {
    if (!checkTimestamp(_timestamp)) throw;
    bytes32 hash = calcEnvHash('send');
    hash = sha3(hash, _timestamp);
    hash = sha3(hash, _to);
    hash = sha3(hash, _value);
    address sender = recoverAddress(hash, _sign);
    logic_v1.send(sender, _to, _value);
}

function getAmount(address _account) constant returns (uint) {
    return logic_v1.getAmount(_account);
}

}

Kế thừa VersionContract : 

Main Contract thì cần phải kế thừa VersionContract .  Ngoài  ra ta cũng cần phải truyền CNS và tên Contract như là tham số cho Contructor của VersionContract.

Thiết lập Logic Contract Interface :

MainContract là list các Contract Interface , vì có nhiệm vụ gửi request đến  Business Logic nên cần phải có Logic Contract Interface . Vì thế ta thiết lập Logic Contract trong Contructor . Ngoài ra, để thay đổi  Logic Contract do fix bug  chẳng hạn , ta sử dụng setter của Logic Contract .  Lúc này, bằng cách sử dụng onlyProvider  thì chỉ có tài khoản mà đã deloy Contract này  thì mới có thể thay đổi được .

Lấy địa chỉ của end user :

Khi sử dụng Z.com Cloud BlockChain cho chức năng thanh toán  thì ta  không  thể sử dụng msg.sender để lấy địa chỉ của end user . Chính vì  vậy để lấy được địa chỉ của end user thì  cần  phải đưa vào  trong  tham số của Transaction cái sign mà end user đã ký  kết . Tham số đầu tiên  của Transaction chính  là sign của end user. Sử dụng sign này  ta có  thể xây  dựng lại được địa chỉ của end user . Để có  thế xây dựng lại thìta cần phải có  giá  trị băm (hash) của tham số , và  ta dùng  calcEncHash để làm điều đó . 

Kiểm tra Timestamp : 

Nếu trong sign của end user không có timestamp , thì có thể xảy ra việc nhiều lần gửi cùng 1 request với những tham số giống nhau . Để phòng ngừa điều đó , ta đưa timestamp vào trong tham số của request và so sánh thời gian chạy Transaction để loại trừ được vấn để trên .

Logic Contract 

Logic Contract sẽ là  MyCoinLogic_v1.sol  ở  phía  dưới .

pragma solidity ^0.4.2;

import '../../gmo/contracts/VersionLogic.sol';
import '../../gmo/contracts/AddressGroup_v1.sol';
import './MyCoinAccountTable_v1.sol';
import './MyCoinEvent_v1.sol';

contract MyCoinLogic_v1 is VersionLogic {
uint constant TOTAL_AMOUNT = 1000000000;

bool public initDone = true;
MyCoinAccountTable_v1 public myCoinTable_v1;
MyCoinEvent_v1 public event_v1;

modifier onlyFirst() {
    if (!initDone) throw;
    _;
}

modifier sendable(address _sender, uint _value) {
    uint amount = getAmount(_sender);
    if (amount < _value) throw;
    _;
}

function MyCoinLogic_v1(ContractNameService _cns, MyCoinAccountTable_v1 _myCoinTable, MyCoinEvent_v1 _event) VersionLogic(_cns, 'MyCoin') {
    myCoinTable_v1 = _myCoinTable;
    event_v1 = _event;
}

function init(address _initAccount) onlyByProvider onlyFirst {
    myCoinTable_v1.create(_initAccount);
    myCoinTable_v1.setAmount(_initAccount, TOTAL_AMOUNT);
    initDone = false;
}

function setMyCoinTable_v1(MyCoinAccountTable_v1 _myCoinTable) onlyByProvider {
    myCoinTable_v1 = _myCoinTable;
}

function setMyCoinEvent_v1(MyCoinEvent_v1 _event) onlyByProvider {
    event_v1 = _event;
}

function exist(address _account) constant returns (bool) {
    return myCoinTable_v1.exist(bytes32(_account));
}

function send(address _from, address _to, uint _value) onlyByVersionContractOrLogic sendable(_from, _value) {
    if (!exist(_to)) myCoinTable_v1.create(_to);
    myCoinTable_v1.setAmount(_from, getAmount(_from) - _value);
    myCoinTable_v1.setAmount(_to, getAmount(_to) + _value);
    event_v1.send(_from, _to, _value);
}

function getAmount(address _account) constant returns (uint) {
    return myCoinTable_v1.getAmount(_account);
}

}

Kế thừa VersionLogic : 

Logic Contract thì cần phải kế thừa VersionLogic .  Ngoài  ra ta cũng cần phải truyền CNS và tên Contract như là tham số cho Contructor của VersionLogic.

Phát hành tiền tệ :

Trong ứng dụng lần này , chức năng phát hành tiền tệ không được thực hiên , nên đầu tiên ta cần phải phát hành  toàn bộ tiền tệ .  Sử dụng init  ta phát hành tiền tệ cho tài khoản đang có  với  chỉ tham số  TOTAL_AMOUNT  . Lúc này  , với  onlyProvider  thì chỉ có tài khoản mà đã deloy Contract này và với  onlyFirst  thì chỉ có lần chạy đầu tiên mới có thể thực hiện được.

Thiết lập Contract Interface cần thiết  :

MyCoinLogic_v1 dùng để quản lý EventContract và dữ liệu sở hữu tiền ảo . MyCoinAccountTable_v1 dùng để truy cập vào TableContract .Do đó ta cần phải thiết lập những Contract Instance này trong Contructor và setter .

Hạn chế quyền truy cập của Transaction :

Về LogicContract , việc chấp nhận các transaction  từ các Contract  khác ngoài MainContract  nên ta  có quan ngại về vấn đề an ninh. Do đó, bằng cách gắn onlyByVersionContractOrLogic  vào các hàm Transaction , ta có thể hạn chế quyền truy cập ngòai trừ MainContract và một phiên bản riêng của LogicContract.

Xử lý gửi tiền : 

Khi gửi tiền , thì bên gửi tiền phải có sở hữu số tiền nhiều hơn số tiền muốn gửi . Dùng  sendable (thật ra thì Z.com muốn dùng từ  payable  để diễn tả việc kiểm tra này , nhưng đây là từ khóa của ETH nên không thể dùng lại ) để kiểm tra , nếu số tiền sở hữu ít hơn số   tiền muốn gửi thì lỗi sẽ xảy ra .

Table Contract 

Table contract dùng để quản lý dữ liệu về tiền sở hữu của end user sẽ được trình bày trong  MyCoinAccountTable_v1.sol  bên dưới .

pragma solidity ^0.4.2;

import '../../gmo/contracts/VersionField.sol';

contract MyCoinAccountTable_v1 is VersionField {
struct Account {
bool isCreated;
uint amount;
}

mapping(bytes32 => Account) public accounts;

function MyCoinAccountTable_v1(ContractNameService _cns) VersionField(_cns, 'MyCoin') {}

/** OVERRIDE */
function setDefault(bytes32 _id) private {
    accounts[_id] = Account({ isCreated: true, amount: 0 });
}

/** OVERRIDE */
function existIdAtCurrentVersion(bytes32 _id) constant returns (bool) {
    return accounts[_id].isCreated;
}

function create(address _account) onlyByNextVersionOrVersionLogic {
    bytes32 id = bytes32(_account);
    if (exist(id)) throw;
    accounts[id] = Account({ isCreated: true, amount: 0 });
}

function setAmount(address _account, uint _amount) onlyByNextVersionOrVersionLogic {
    bytes32 id = bytes32(_account);
    prepare(id);
    accounts[id].amount = _amount;
}

function getAmount(address _account) constant returns (uint) {
    bytes32 id = bytes32(_account);
    if (shouldReturnDefault(id)) return 0;
    return accounts[id].amount;
}

}

Kế thừa VersionField

Table Contract cần phải kế thừa VersionFiled .  Và Contructor của VersionField cần phải được truyền tham số CNS và Contract Name .

Cấu trúc Data 

accounts  là một mảng kết hợp, có key là địa chỉ của người dùng,  value  có chứa Account để quản lý dữ liệu tiền. Tuy nhiên , hạn chế của VersionField là key chỉ bytes32 , vì vậy ta phải cast address thành bytes32 để sử dụng . 

Tạo hàm abstract 

Trong VersionField có 2 hàm abstract là setDefault và existIdAtCurrentVersion , vì vậy ta cần phải tạo 2 hàm đó . setDefault dùng để thiết lập   giá  trị mặc   định của data của version này .  existIdAtCurrentVersion thì nếu trong version này có tồn tại data thì  trả về true.

Các column setter 

Table Contract thì không bao gồm logic ,  chỉ dùng cho setter và getter của các cột data. Trong setter bằng việc gắn   onlyByNextVersionOrVersionLogic thì có thể hạn chế việc truy cập ngoài những version tiếp theo của Table Contract và Logic Contract .  Ngoài ra , trường hợp nhiều version đang triển khai , ta có thê  gọi prepare để tổng hợp thống nhất  dữ liệu .

Các column getter 

Trường hợp nhiều version đang triển khai ,  để tổng hợp thống nhất dữ liệu  ta gọi  shouldReturnDefault  , nếu mà là true thì trả về giá  trị mặc định . Nếu không phải true thì trả về giá  trị của version này .

Event Contract

Event Contract là  MyCoinEvent_v1.sol như bên dưới .

pragma solidity ^0.4.2;

import '../../gmo/contracts/VersionEvent.sol';

contract MyCoinEvent_v1 is VersionEvent {
function MyCoinEvent_v1(ContractNameService _cns) VersionEvent(_cns, 'MyCoin') {}

event SendEvent(address indexed _from, address indexed _to, uint _value);

function send(address _from, address _to, uint _value) onlyByVersionLogic {
    SendEvent(_from, _to, _value);
}

}

Kế thùa VersionEvent 

Event Contract thì cần phải kế thừa VersionEvent .  Ngoài ra , ta cần phải truyền CNS và Contract Name vào  contructor của VersionEvent  như là tham số . 

 Event chuyển tiền 

Trong ứng dụng lần này cái sử lý cần ghi lại log thì chỉ có sử lý chuyển tiền .  Để ghi lại sự kiện chuyển tiền ta định nghĩa  SendEvent .  Tham số ghi lại  là nới chuyển tiền , nới nhận tiền ,  và số tiền gửi .  Lúc này  gắn thêm  indexed vào ta có thể search  trong event log .

Hàm event chuyển tiền 

Để có thể sinh ra event chuyển tiền từ trong LogicContract , thì ta cần hàm để gọi event chuyển tiền . Thêm  onlyByVersionLogic để hạn  chế quyền truy cập ngoài  trừ từ LogicContract .

Web Application 

Mình  sẽ giải thích làm sao để sử dụng  MyCoin đã tạo ở trên trong ứng dụng Web . Để đến đước bước này bạn phải deploy  contract của bạn và đăng ký CNS và ABI trong màn  hình  quản lý như trong hướng dẫn của Z.com

Tạo key 

Z.com Cloud BlockChain  việc chứng minh là có đúng là chính là người dùng đang thao tác hay không thì ta cần phải đăng ký  khóa bí mật . Việc tạo khóa bị mật đó được trình bày phía dưới . 

<!DOCTYPE html>
<html>

<head>
<title></title>
<script src="http://beta.blockchain.z.com/static/client/lib/eth-client.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript">
var account;

function create() {
    ethClient.Account.create('https://beta.blockchain.z.com', $('#password').val(), function(err, instance) {
        if (err) {
            console.log(err);
        } else {
            account = instance;
            localStorage.setItem('account', account.serialize());
            $('#address').html(account.serialize());
        }
    });
};

function getAddress() {
    var serializedAccount = localStorage.getItem('account');
    account = ethClient.Account.deserialize(serializedAccount);
    $('#address').html(account.getAddress());
};

&lt;/script&gt;

</head>

<body>
<table>
<tr>
<td>password:</td>
<td>
<input id="password" value="password" />
</td>
<td>
<button onclick="create()">create</button>
<button onclick="getAddress()">getAddress</button>
</td>
<td><span id="address" /></td>
</tr>
</table>
</body>

</html>

Library 

Tham khảo link bên dưới

Account · BlockChain Service API

Tạo account 

Dùng  ethClient.Account.create  để tạo account người dùng.  Tham số đầu tiền là URL , tham số thứ 2 là password của account  . Giá  trị trả về sẽ là 1 account instance . Lưu nó  trong localStorage .

Lấy address 

Sử dụng getAddress ta có thể lấy address  của account . 

Ứng dụng chuyển tiền 

<!DOCTYPE html>
<html>

<head>
<title></title>
<script src="http://beta.blockchain.z.com/static/client/lib/eth-client.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript">
const CNS_ADDRESS = '0xf4eaf03dff63deafdfb68f202734ce15ec53eead';
const MY_COIN_ABI = [{"constant":true,"inputs":[],"name":"provider","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"isVersionContract","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_sign","type":"bytes"},{"name":"_timestamp","type":"uint256"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"send","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"exist","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"isVersionLogic","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"contractName","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_logic","type":"address"}],"name":"setMyCoinLogic_v1","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"logic_v1","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"isVersionContractOrLogic","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"cns","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"getAmount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getContractName","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getCns","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"_cns","type":"address"},{"name":"_logic","type":"address"}],"payable":false,"type":"constructor"}];
const serializedAccount = localStorage.getItem('account');
const account = ethClient.Account.deserialize(serializedAccount);
const contract = new ethClient.AltExecCnsContract(account, CNS_ADDRESS);

function send() {
    contract.sendTransaction('password', 'MyCoin', 'send', [Math.floor(new Date().getTime() / 1000), $("#to-address").val(), $("#value").val()], MY_COIN_ABI, function(err, res) {
        if (err) {
            alert('error');
            console.error(err);
        } else {
            console.log(res);
        };
    });
}

function getAmount() {
    contract.call('password', 'MyCoin', 'getAmount', [account.getAddress()], MY_COIN_ABI, function(err, amount) {
        if (err) {
            alert('err');
            console.error(err);
        } else {
            $('#amount').text(amount);
        }
    });
}

$(document).ready(function() {
    $('#my-address').text(account.getAddress());
    getAmount();
});
&lt;/script&gt;

</head>

<body>
<h3>アカウント情報</h3>
<div>
<div>アドレス</div>
<div id="my-address"></div>
</div>
<div>
<div>所持金</div>
<div id="amount"></div>
</div>
<h3>送金</h3>
<div>
<div>送金先</div>
<input id="to-address" />
</div>
<div>
<div>金額</div>
<input id="value" />
</div>
<div>
<button onclick="send()">決定</button>
</div>
</body>
</html>

Thiết lập truy cập đến Contract 

Bạn cần phải đăng ký CNS và ABI của Contract để có thế truy cập đến Contract thông qua API . Ngoài ra  bằng việc sử dụng   ethClient.AltExecCnsContract , bạn có thể thực hiện sendTransaction và call đối với Contract .

Hiện thị số tiền sở hữu 

Thực  hiện call đôi với getAmount của MyCoin Contract với tham số là địa chỉ của chính mình .

Sử lý gửi tiền 

Thực hiện sendTransaction đối với send của MyCoin Contract . Với tham số là timestamp , địa chỉ gửi tiền , số tiền gửi . 

Demo 

Bấy giờ ta sẽ tiến hành demo ứng dụng . Nội dung sẽ là hiện thị số tiền sở hữu và sử lý gửi tiền .

Hiện thị số tiền sở hữu 

Đâu tiên địa chỉ của Account All currencies là  0xfc865f061f183129985e9224a52293a95bb1f916. Địa chỉ của Account bình  thường là  0xadd1c664db02f7fcb898ed6cbb1fc63635c11a1d .

Số tiền của  Account All currencies là  1000000000 , số tiền của tài khoản bình thưởng là  0 .

coin-admin-infocoin-user-info

Sử lý gửi tiền 

Sẽ  tiến hành gửi tiền từ  Account All currenciesn tới Account bình thường . Nếu thành công , số tiền của tài khoản bình thường sẽ tăng lên . 

coin-admin-sendcoin-user-receive

 

Khi sử lý gửi tiền nếu mà số tiền gửi nhiều hơn số tiền sở hữu sẽ sinh ra lỗi .

coin-user-send-error

coin-user-error21

Sủ lý gửi tiền nếu thành công , sẽ sinh ra event .Từ Truffle  Console ta có lấy được log chuyển tiền từ Account  All currenciesn bằng tay như là ở phía dưới . 

var event = MyCoinEvent_v1.deployed().SendEvent({from:'0xfc865f061f183129985e9224a52293a95bb1f916'}, {fromBlock:0,toBlock:'latest'});
event.get(function(err, res) {
  console.log(res);
});

Log lấy được sẽ như bên dưới .  Tham số của event được định nghĩa bằng  MyCoinEvent_v1.sol sẽ được ghi trong   args .

[
  {
    address: '0xcba5b1c1a823505e6003d59019572dd50d80aca8',
    blockHash: '0xc42d0a275227258d7eee1d3ce84564e7bc35f746c610988989d90eafc0a59905',
    blockNumber: 1051987,
    logIndex: 0,
    transactionHash: '0xc4c2e25a8f2b9e3d1e727ecdd79f9af3bee8ff4a1f126e9bf9bcf7cfefb19107',
    transactionIndex: 0,
    transactionLogIndex: '0x0',
    type: 'mined',
    event: 'SendEvent',
    args:
    {
      _from: '0xfc865f061f183129985e9224a52293a95bb1f916',
      _to: '0xadd1c664db02f7fcb898ed6cbb1fc63635c11a1d',
      _value: [Object]
    }
  }
]

Kết luận 

Ta đã thực hiện xong ứng dụng tiền ảo đơn giản bẳng cách sử dụng Z.com clound BlockChain .  Dựa vào ví dụ này có thể giúp  ta hiểu được cái code Solidity của Smart Contract của dịch vụ này.  Giúp ta cảm thấy thật đơn giản khi phát triển dịch vụ của BlockChain .

Tham khảo :

https://recruit.gmo.jp/engineer/jisedai/blog/blockchain_coin_v1/