+8

ICON - TCP/IP của nền tảng Blockchain ?

1. Giới thiệu

ICON là một nền tảng Blockchain 2.0 (có nhiều điểm tương đồng với Ethereum). ICON chạy các hợp đồng thông mình với giao thức đồng thuận là BFT-DPoS (Delegated Proof-of-Stake), ICON cũng có phát hành tiền mã hóa giống như Ethereum với tên gọi là ICX.

Mục tiêu mà đội ngũ phát triển ICON hướng đến trong tương lai là kết nối giữa các mạng blockchain với nhau, giúp chúng dễ dàng trao đổi tài sản cũng như dữ liệu. Có một ví dụ mà đội ngũ phát triển của ICON đưa ra để so sánh việc kết nối được các mạng blockchain với nhau, cũng giống như việc ra đời của bộ giao thức TCP/IP giúp kết nối các mạng riêng với nhau thành mạng Internet toàn cầu. ICON mong muốn họ sẽ là TCP/IP của Blockchain 😀

Bên giới là video giới thiệu về ICON từ đội ngũ nhà phát triển

2. Một số khái niệm cơ bản

Tài khoản

Giống như Ethereum, ICON cũng có 2 loại tài khoản đó là:

  • Tài khoản người dùng (Externally Owned Accounts): Bắt đầu bằng hx, độ dài 20 bytes hex
  • Tài khoản hợp đồng thông minh: Bắt đầu bằng cx, độ dài 20 bytes hex. Không có private key.

Giao dịch

Các loại giao dịch trên ICON

  • Chuyển ICX
  • Lưu data vào blockchain
  • Deploy hay update SCORE
  • Thay đổi state của SCORE

Lưu ý: Smart contract trên ICON được gọi là SCORE

Các tham số khi thực hiện một giao dịch:

  • from: Địa chỉ tạo giao dịch
  • to: Địa chỉ nhận giao dịch (Khi deploy thì địa chỉ nhận là cx0000000000000000000000000000000000000000 )
  • value: Lượng ICX gửi đi
  • stepLimit: Số step tối đa mà giao dịch có thể thực hiện (gần tương tự như gasLimit trong Ethereum)
  • time stamp: Thời gian tạo giao dịch
  • nonce: Số thứ tự giao dịch của tài khoản gửi (tương tự như nonce trong Ethereum)
  • nid: Network ID (Phân biệt giữa mainnet, testnet)
  • data type: Giúp phân biệt loại giao dịch. Gồm 3 giá trị call, deploymessage. Với giao dịch chuyển ICX thông thường, data type sẽ được bỏ qua.
  • data: Nội dung giao dịch (có thể là các tham số truyền vào để gọi hàm trong contract)
  • signature: Chữ ký số của người gửi giao dịch
  • transaction hash: Giá trị băm định danh giao dịch

Phí giao dịch

Step là đơn vị đo lường để tính phí giao dịch trên mạng ICON. Số lượng Step mà một giao dịch cần phụ thuộc vào lượng tài nguyên tính toán cần bỏ ra để thực hiện giao dịch đó. Tỷ giá ban đầu, 1 ICX = 100,000,000 Step. Tỷ giá này có thể được thay đổi nếu giá ICX tăng quá cao hoặc bị giảm xuống rất thấp.

Công thức tính step

Step = max ( ∑ βiSi + C, C )

Lưu ý: Số lượng step tối đa mà một giao dịch có thể tiêu thụ là 2.5 tỷ step

Với:

  • Si là số lần thực hiện một lời gọi cụ thể nào đến đến hợp đồng thông minh.
  • βi là số step sẽ được tiêu thụ với từng lời gọi cụ thể.
  • C là số lượng step tối thiểu một giao dịch cần phải chi trả (Hằng số với giá trị 100.000)

Bên dưới là 2 bảng lần lượt mô tả các giá trị khả dĩ của Si và các giá trị step tiêu thụ với mỗi lời gọi tương ứng đến contract.

Chính sách hỗ trợ phí giao dịch cho người dùng

Bình thường, khi thực hiện giao dịch, người dùng sẽ chịu toàn bộ chi phí. Với ICON, đội ngũ phát triển đã thiết kế một tính năng cho phép chủ của contract có thể hỗ trợ một phần hoặc toàn bộ chi phí giao dịch khi người dùng gọi đến contract của họ.

Phần trăm phí hỗ trợ giao động từ 0 - 100% (nghĩa là có thể không hỗ trợ phí). Nếu bên phát triển contract muốn hỗ trợ phí cho người dùng, thì họ phải gửi trước vào contract 1 lượng ICX để đến khi người dùng gọi đến, lượng ICX này sẽ được khấu trừ vào phí giao dịch của người dùng tùy theo tỷ lệ.

Virtual Step

Virtual Step là hệ thống tính phí mới của mạng ICON dành cho chủ sở hữu contract. Nó được tạo ra hàng tháng tương ứng với số lượng ICX được gửi vào contract và khoảng thời gian ký gửi ICX vào contract đó. Ngoài việc hỗ trợ phí cho người dùng bằng ICX, chủ sở hữu SCORE có thể trả phí bằng Virtual Step. Điều này giúp chủ sở hữu contract giảm 1 phần gánh nặng chi phí cũng như khuyến khích tăng phần trăm hỗ trợ phí lên cao hơn cho người dùng.

Một số đặc điểm:

  • Virtual StepStep có tỷ lệ 1:1
  • Virtual Step được tạo ra sau mỗi 1,296,000 blocks (1 tháng). Khi đó, các Virtual Step chưa dùng sẽ hết hạn.
  • Không thể chuyển Virtual Step đi đâu cả.
  • Phí hỗ trợ người dùng sẽ tiêu Virtual Step trước, nếu hết Virtual Step mới tiêu đến ICX

Tính toán Virtual Step

  • Khi gửi ICX vào contract, cần thiết lập đặt số tiền gửi và thời hạn gửi.
  • Thời gian gửi có thể được thiết lập từ tối thiểu là 1 tháng đến tối đa là 24 tháng.
  • Số tiền gửi tối thiểu từ 5.000 ICX đến tối đa 100.000 ICX.

Tùy vào thời gian ký gửi ngắn hay dài mà số virutal step nhận được sau mỗi tháng sẽ khác nhau:

Ví dụ:

  • Gửi 10.000 ICX vào với thời gian 1 tháng. Sau 1 tháng nhận được 80,000,000,000 Virtual Step, tương đương với 8 % của số tiền gửi 10.000, là 800 ICX.
  • Gửi 10.000 ICX vào với thời gian 24 tháng. Mỗi tháng nhận được 202,400,000,000 Virtual Step, tương đương với 20.24 % của số tiền gửi 10.000 ICX.

Lưu ý: Các chủ sở hữu contract sẽ phải chịu một khoản phạt nếu họ rút ICX đã ký gửi trước khi kết thúc khoảng thời gian định trước. Tiền phạt bằng tổng số Virtual Step được sử dụng trong thời gian trước đó.

ICON nodes

ICON nodes có 3 loại

  • Peer: có thể tham gia vào giao thức đồng thuận, có thể tạo mới block trên mạng.
  • Citizen: Đồng bộ hóa dữ liệu blockchain và chuyển tiếp giao dịch đến Peer.
  • Light: Lưu các block header của mạng, thực hiện nhiệm vụ xác minh giao dịch.

3. Viết smart contract

SCORE trên ICON được viết bằng ngôn ngữ Python.

Trước tiên, các bạn cần cài công cụ tbears, tbears là một công cụ giúp init, deploy và tương tác với smart contract trên mạng ICON (tương tự như truffle bên Ethereum). Các cài đặt tbears các bạn có thể tham khảo tại đây

Hello world

Chúng ta sẽ bắt đầu vơi một smart contract đơn giản nhất

Bước 1: Khởi tạo

# tbears init [project_name] [main_class_name]
tbears init hello HelloWorld

Trong folder hello mới tạo ra, chúng ta sẽ thấy 3 file

  • __init.py__: Ban đầu rỗng, file này giúp trình thông dịch Python nhận folder này như 1 package.
  • hello.py: FIle chính, chứa logic của contract
  • package.json: File này gồm các thông tin cơ bản của contract như version, name, ...
// package.json
{
	"version": "0.0.1",
	"main_module": "hello_world",
	"main_score": "HelloWorld"
}
# hello-world.py
from iconservice import *

TAG = 'HelloWorld'

# class HelloWorld kế thừa class IconScoreBase của thư viện iconservice
class HelloWorld(IconScoreBase):
	# Hàm __init__ là hàm constructor của class, được gọi khi nào contract được deploy mới 
	def __init__(self, db: IconScoreDatabase) -> None:
	  super().__init__(db)
     
	# Hàm on_install chỉ được gọi 1 lần khi deploy contract lần đầu
	# Khi contract được update thì nó không được gọi lại nữa
	def on_install(self) -> None:
	  super().on_install()
      
	# Hàm on_update được gọi khi contract được update 
	def on_update(self) -> None:
	  super().on_update()
      
	# Hàm hello sẽ trả về thông điệp "Hello" bất cứ khi nào được gọi
	@external(readonly=True)
	def hello(self) -> str:
	  Logger.debug(f 'Hello, world!', TAG)
	  return "Hello"

Implement Token

ICON hỗ trợ implement nhiều chuẩn token khác nhau (có tham khảo từ các chuẩn ERC của Ethereum), các chuẩn token của ICON được gọi được định nghĩa tại đây

Ở ví dụ này, chúng ta sẽ thử implement chuẩn token IRC-2 là dạng Fungibles Token (IRC-2 được tham khảo từ 2 chuẩn ERC-20 và ERC-223 của Ethereum). Định nghĩa về chuẩn IRC-2 các bạn có thể tham khảo ở đây

tbears init sample-token SampleToken
``

```py
# sample_token.py
from iconservice import *

TAG = 'SampleToken'

## Định nghĩa interface theo chuẩn IRC-2
class TokenStandard:
    @abstractmethod
    def name(self) -> str:
        pass

    @abstractmethod
    def symbol(self) -> str:
        pass

    @abstractmethod
    def decimals(self) -> int:
        pass

    @abstractmethod
    def totalSupply(self) -> int:
        pass

    @abstractmethod
    def balanceOf(self, _owner: Address) -> int:
        pass

    @abstractmethod
    def transfer(self, _to: Address, _value: int, _data: bytes = None):
        pass


# Định nghĩa Interface TokenFallbackInterface
# Hàm tokenFallback được gọi khi có 1 lượng token được gửi vào địa chỉ contract (tương tự như fallback function xử lý việc nhận ICX)
class TokenFallbackInterface(InterfaceScore):
    @interface
    def tokenFallback(self, _from: Address, _value: int, _data: bytes):
        pass

# Implement
class SampleToken(IconScoreBase, TokenStandard):

    _BALANCES = 'balances'
    _TOTAL_SUPPLY = 'total_supply'
    _DECIMALS = 'decimals'
    
    # Event được emit khi hàm _transfer được gọi
    @eventlog(indexed=3)
    def Transfer(self, _from: Address, _to: Address, _value: int, _data: bytes):
        pass
    
    # Định nghĩa các biến cần lưu trữ trong contract
    # `_total_supply` dạng int (Tổng số token được tạo ra)
    # `_decimals` dạng int, dùng để tính toán ra `_total_supply`
    # `_balances` dạng (key => value) lưu số dư token của các tài khoản
    def __init__(self, db: IconScoreDatabase) -> None:
        super().__init__(db)
        self._total_supply = VarDB(self._TOTAL_SUPPLY, db, value_type=int)
        self._decimals = VarDB(self._DECIMALS, db, value_type=int)
        self._balances = DictDB(self._BALANCES, db, value_type=int)
    
    # Tính toán số lượng token phát hành
    # Tất cả lượng token ban đầu được nắm giữ bởi địa chỉ deploy contract
    def on_install(self, _initialSupply: int, _decimals: int) -> None:
        super().on_install()

        if _initialSupply < 0:
            revert("Initial supply cannot be less than zero")

        if _decimals < 0:
            revert("Decimals cannot be less than zero")

        total_supply = _initialSupply * 10 ** _decimals
        Logger.debug(f'on_install: total_supply={total_supply}', TAG)

        self._total_supply.set(total_supply)
        self._decimals.set(_decimals)
        self._balances[self.msg.sender] = total_supply

    def on_update(self) -> None:
        super().on_update()
    
    # Tên của token
    @external(readonly=True)
    def name(self) -> str:
        return "SampleToken"
    
    # Ký hiệu của token
    @external(readonly=True)
    def symbol(self) -> str:
        return "ST"

    @external(readonly=True)
    def decimals(self) -> int:
        return self._decimals.get()

    @external(readonly=True)
    def totalSupply(self) -> int:
        return self._total_supply.get()
    
    # truy vấn số dư của địa chỉ
    @external(readonly=True)
    def balanceOf(self, _owner: Address) -> int:
        return self._balances[_owner]

    @external
    def transfer(self, _to: Address, _value: int, _data: bytes = None):
        if _data is None:
            _data = b'None'
        self._transfer(self.msg.sender, _to, _value, _data)
    
    # Chuyển tokne từ `_from` sang `_to`
    def _transfer(self, _from: Address, _to: Address, _value: int, _data: bytes):

        # Nếu giá trị âm hoặc địa chỉ gửi không đủ số dư thì giao dịch sẽ bị revert
        if _value < 0:
            revert("Transferring value cannot be less than zero")
        if self._balances[_from] < _value:
            revert("Out of balance")

        self._balances[_from] = self._balances[_from] - _value
        self._balances[_to] = self._balances[_to] + _value
        
          # Nếu địa chỉ đích là 1 contract thì sẽ gọi đến hàm tokenFallback
        if _to.is_contract:
            recipient_score = self.create_interface_score(_to, TokenFallbackInterface)
            recipient_score.tokenFallback(_from, _value, _data)

        # Emits an event log `Transfer`
        self.Transfer(_from, _to, _value, _data)
        Logger.debug(f'Transfer({_from}, {_to}, {_value}, {_data})', TAG)

Deploy

Contract đã xong xuôi, bây giờ chúng ta cùng tìm hiểu các deploy chúng lên mạng testnet Yeouido với tools tbears.

B1: Tạo file config deploy

deploy_hello.json

{
  "uri": "https://bicon.net.solidwallet.io/api/v3",
  "nid": "0x3",
  "keyStore": null,
  "from": "ĐỊA CHỈ CỦA BẠN",
  "to": "cx0000000000000000000000000000000000000000",
  "stepLimit": "0x5a000000",
  "deploy": {
    "contentType": "tbears",
    "mode": "install"
  },
  "txresult": {},
  "transfer": {}
}

deploy_sample_token.json

{
  "uri": "https://bicon.net.solidwallet.io/api/v3",
  "nid": "0x3",
  "keyStore": null,
  "from": "ĐỊA CHỈ CỦA BẠN",
  "to": "cx0000000000000000000000000000000000000000",
  "stepLimit": "0x5a000000",
  "deploy": {
    "contentType": "tbears",
    "mode": "install",
    "scoreParams": {
      "_decimals": "0x12",
      "_initialSupply": "0x3e8"
    }
  },
  "txresult": {},
  "transfer": {}
}

Hàm on_install trong contract SampleToken cần truyền 2 đối số để _decimals_initialSupply để thực thi.

B2: Tạo ví

Chúng ta cài đặt extension ICONex (ví giống như Metamask) để tạo địa chỉ trên mạng ICON. Với trình duyệt Chrome, các bạn có thể tại tại đây

Ấn vào phần tạo ví mới, các bạn sẽ tải file keystore và về đổi đuôi thành file json, chẳng hạn như keystore.json.

B3: Deploy lên testnet vơi tbears

Thư mục hiện tại chúng ta có

/hello
/sample-token
deploy-hello.json
deploy_sample_token.json
keystore.json

Trước khi deploy chúng ta cần faucet ICX cho tài khoản trên testnet tại

tbears deploy hello -k keystore.json -c deploy-hello.json
tbears deploy sample-token -k keystore.json -c deploy-sample_token.json

Transaction hash sẽ được trả về sau khi deploy, chúng ta có thể kiểm tra giao dịch có thành công hay không cũng như địa chỉ của contract trên trang bicon.tracker.solidwallet.io

4. ICON SDK (Javascript SDK)

Mỗi node ICON đều cung cấp các API JSON-RPC. Để tương tác với một node ICON, chúng ta có thể gửi yêu cầu JSON-RPC thô hoặc sử dụng ICON SDK bằng nhiều ngôn ngữ khác nhau. ICON chính thức hỗ trợ Java, Python, JavaScript và Swift. Trong khuôn khổ bài viết, chúng ta sẽ chỉ tìm hiểu qua về 1 loại SDK đó là Javascript SDK.

Cài đặt

Javascript SDK được tích hợp trong 1 package node.js, giúp chúng ta dễ dàng cài đặt và sử dụng

Cài đặt qua npm

npm install --save icon-sdk-js

Hoặc link CDN

<script src="https://cdn.jsdelivr.net/gh/icon-project/icon-sdk-js@latest/build/icon-sdk-js.web.min.js"></script>

Dùng SDK tương tác với contract đã deploy

Tạo thư mục sdk và tạo biến môi trường

mkdir sdk
cd sdk
touch .env
# .env
API_ENPOINT='https://bicon.net.solidwallet.io/api/v3'
PRIVATE_KEY='PRIVATE KEY ĐỊA CHỈ CỦA BẠN'
ADDRESS_CONTRACT_SAMPLE_TOKEN='ĐỊA CHRI CONTRACT SAMPLE TOKEN ĐÃ DEPLOY'
OWNER='ĐỊA CHỈ DÙNG ĐỂ DEPLOY'

Với các hàm readonly, khi gọi không cần phải ký giao dịch gì cả

// getBalance.js
const IconService = require('icon-sdk-js');
const { argv } = require('yargs');
require('dotenv').config();
const { HttpProvider, IconBuilder } = IconService;

// Kết nối đến mạng testnet
const provider = new HttpProvider(process.env.API_ENPOINT);
const iconService = new IconService(provider);
const { CallBuilder } = IconBuilder;

const owner = process.env.OWNER;
const instanceContract = process.env.ADDRESS_CONTRACT_SAMPLE_TOKEN;
let account = owner;

if (argv.address) {
 account = argv.address;
}

async function getBalance() {
 try {
    // class CallBuilder dùng để build thành các transaction object chuyên gọi các hàm read-only
    // https://link.sun-asterisk.vn/FPM4jf 
   const txObj = new CallBuilder()
     .from(owner)
     .to(instanceContract)
     .method('balanceOf')
     .params({
       _owner: account,
     })
     .build();

   let balance = await iconService.call(txObj).execute();
   balance = parseInt(balance);
   console.log({ balance });
   return balance;
 } catch (err) {
   console.log({ err });
 }
}

getBalance(); 

Với những hàm khác, khi gọi qua SDK chúng ta cần thực hiện thao tác ký

// transfer.js
const IconService = require('icon-sdk-js');
const { argv } = require('yargs');
require('dotenv').config();
const { IconWallet, HttpProvider, SignedTransaction, IconBuilder, IconConverter } = IconService;
const provider = new HttpProvider(process.env.API_ENPOINT);
const iconService = new IconService(provider);
const { CallTransactionBuilder } = IconBuilder;

// Import private key cảu account vào wallet, dùng cho việc ký giao dịch
const wallet = IconWallet.loadPrivateKey(process.env.PRIVATE_KEY);
const owner = process.env.OWNER;
const instanceContract = process.env.ADDRESS_CONTRACT_SAMPLE_TOKEN;
const to = argv.to;
const value = parseInt(argv.value);

async function transfer() {
 try {
 // Class CallTransactionBuilder dùng để build các giao dịch gọi đến các hàm thay đổi trạng thái của blockchain
 // Hàm transfer nhận vào 2 params là `_to` và `_value`
   const txObj = new CallTransactionBuilder()
     .from(owner)
     .to(instanceContract)
     .stepLimit(IconConverter.toBigNumber('2000000'))
     .nid(IconConverter.toBigNumber('3'))
     .nonce(IconConverter.toBigNumber('1'))
     .version(IconConverter.toBigNumber('3'))
     .timestamp(new Date().getTime() * 1000)
     .method('transfer')
     .params({
       _to: to,
       _value: IconConverter.toHex(value),
     })
     .build();

   const signedTransaction = new SignedTransaction(txObj, wallet);
   const txHash = await iconService.sendTransaction(signedTransaction).execute();

   console.log({ txHash });
 } catch (err) {
   console.log({ err });
 }
}

transfer();

Chạy:

node getBalance --address=<tài khoản bạn cần truy vấn số dư token>
node transfer --to=<địa chỉ nhận token> --value=

Chú thích: Để tìm hiểu các năng của thể của các module ở trong Javascript SDK, các bạn có thể tham khảo ở trên Github

Tài liệu tham khảo

ICON Documentation


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí