Ethereum là gì? Tìm hiểu cơ bản về Blockchain (P1)

Kì trước, tôi đã có bài viết Solidity cơ bản. Nó được thiết kế để dùng cho Ethereum Virtual machine (EVM). Trong bài này, chúng ta sẽ đi sâu hơn tìm hiểu Ethereum là gì và dùng test environment cho contract của mình. Từ đó chúng ta sẽ hiểu được cách thức Ethereum hoạt động như thế nào.

  32 cuốn sách học lập trình bạn nhất định phải đọc

Ethereum là gì?

Ethereum (ETH) là một loại cryptocurrency được xây dựng vào năm 2013 bởi Vitalik Buterin, thường được gọi là Bitcoin 2.0. Đây không chỉ là một đồng tiền tệ mà nó còn là nền tảng tạo ra nhiều ứng dụng khác thông qua ngôn ngữ lập trình của mình.

Nó hoạt động trên một Blockchain tương tự như Bitcoin, chúng ta có thể khai thác ETH thông qua đơn vị tiền tệ Ether.

Ethereum còn là một nền tảng ứng dụng hữu ích và đã tạo ra được một hệ sinh thái tài chính phân tán cho riêng mình.

Lí do Ethereum ra đời là dựa trên ý tưởng Vitalik Buterin muốn khắc phục những nhược điểm của Bitcoin như phí thanh toán, thời gian thanh toán chậm và khuyến khích khai thác thông qua các mining-pool thay vì khai thác riêng lẻ như Bitcoin.

>>> Xem thêm Blockchain là gì?

Các tool

Trong bài này, chúng ta sử dụng truffle và ganache-cli. Truffle là một framework của lập trình Ethereum, cho phép tạo test environment, viết test cho contract và nhiều thứ khác nữa. Bài viết này chúng ta chỉ dùng nó để tạo test environment.

ganache-cli sẽ được dùng kết hợp với truffle để tạo ra một test environment đầy đủ tính năng. Phần chúng ta chuẩn bị làm sẽ không thật sự giống với blockchain trên thực tế, nhưng sẽ phần nào hình dung được cách thức hoạt động của nó.

Install các tool

  • truffle: [sudo] npm install -g truffle
  • ganache-cli: [sudo] npm install -g ganache-cli

Chuẩn bị

Đầu tiên, chúng ta cần mở một truffle project. Để làm được, tạo một directory mới, và trong nó, hãy run truffle init.

Bây giờ bạn sẽ có khá nhiều folder. Mở truffle-config.js và dán đoạn này vào:

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
    // to customize your Truffle configuration!
    networks: {
        development: {
            host: '127.0.0.1',
            port: 7545,
            network_id: '*'
        }
    }
}

Đối với người Windows, bạn có thể remove file truffle.js để tránh conflict. Đối với những hệ điều hành khác, bạn có thể giữ lại cả hai, và bỏ đoạn code này vào truffle.js, hoặc cứ làm như người dùng Windows không sao cả.

File này thể hiện rằng development network của chúng ta sẽ chạy trên localhost:7545.

Tiếp theo, trong folder contracts, tạo một file DeveloperFactory.solĐây cũng là chỗ chúng ta sẽ viết contract của mình. Hãy bỏ đoạn code này vào:

pragma solidity ^0.4.18;

contract DeveloperFactory {
    // Let's create a Developer!

    event NewDeveloper(uint devId, string name, uint age);

    uint maxAge = 100;
    uint minAge = 5; 

    struct Developer {
        string name;
        uint id;
        uint age;
    }

    Developer[] public developers;

    mapping (uint => address) public devToOwner;
    mapping (address => uint) public ownerDevCount;

    function _createDeveloper( string _name, uint _id, uint _age ) private{
        uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
        ownerDevCount[msg.sender]++;
        devToOwner[id] = msg.sender;
        NewDeveloper(id, _name, _age);
    }

    function _generateRandomId( string _str ) private pure returns (uint){
        uint rand = uint(keccak256(_str));
        return rand;
    }

    function createRandomDeveloper( string _name, uint _age ) public payable {
        require(_age > minAge);
        require(_age < maxAge);
    require(msg.value == 5000000000000000000);
        uint randId = _generateRandomId( _name );
        _createDeveloper(_name, randId, _age );
    }

    function getAllDevelopers() public view returns (uint) {
    return developers.length;
    }
}

Nếu bạn muốn biết chi tiết chuyện gì đang xảy ra hãy xem bài viết về Solidity của tôi. Nói ngắn gọn thì, contract này được call để tạo một Developer struct có name và tag. Trong ví dụ này, để tạo một Developer mới cần 5 ether (5000000000000000000 wei, hạng thấp nhất trên Ethereum).

Tiếp theo, vào folder migrations và tạo ra một file 2_deploy_contracts.js:

const DeveloperFactory = artifacts.require('./DeveloperFactory.sol')

module.exports = function(deployer){
    deployer.deploy(DeveloperFactory)
}

Trong file này, ta sẽ import contract và deploy nó trên blockchain.

Launch test environment

Mở một cửa sổ terminal mới và chạy ganache-cli -p 7545. Nó sẽ chạy ganache-cli trên  port 7545 (tương tự như cái chúng ta xác định trên file truffle-config.js) và tạo một vài accounts. Mỗi account có 100 ether mặc định.

Bạn sẽ thấy trong console một cái giống thế này:

Available Accounts
==================
(0) 0x473c0be352f997aa0b194786c27d26e29a3f75b1
(1) 0x9657290da5570b17a03198f490b0a2d7eea84ecf
(2) 0x516c0e0152d7b85facb7e3da2d30f67e42a80ca9
(3) 0xf81be8bbe99d2302b85f7cb0f60103c435ae703b
(4) 0xcfacf5ac5567cfdd70ee5a8a9fe4bf7f74d80b02
(5) 0x623e18e34b2de07933fe179862f038230cc69012
(6) 0xd7100dbc1d6f72777ae2a6f5d95c4b8d71f7ce07
(7) 0x7f40df6c6042888a37124821130910e77051b1cf
(8) 0x26a2c2be1f31571f289b7fb60e41f31f7c57a5be
(9) 0x08a945825a28166466987d5fc77b016fe3d80aa5

Dĩ nhiên, các address của account sẽ khác nhau, nhưng bạn sẽ có khoảng 10 giây để tìm hiểu.

Bây giờ, hãy quay lại cửa sổ terminal ban đầu. Hãy đảm bảo rằng bạn đã ở trong folder bạn tạo và chạy: truffle compile, và chạy truffle migrate --network development. Nó compile code chúng ta thành một ngôn ngữ mà Ethereum Virtual Machine (EVM) có thể hiểu được, ở đây ganache sẽ mô phỏng EVM.

Nếu mọi thứ suôn sẻ thì terminal của bạn sẽ trông như sau:

truffle migrate --network development
Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xc83617394674cd65f751ff9c05438e16339414ccf1e1662ba66479d79335af13
  Migrations: 0xe982e78028e0dfcbdb135e7a3c1e1ed3d98e36e5
Saving successful migration to network...
  ... 0x78bdff98e4dac310de4650048a0856075a460bed9de0c4d4ea879ea399d142c4
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying DeveloperFactory...
  ... 0x0a314c5ed99c772019ea358ac98e002a1442e26903122528d705bf3ff7ed02ed
  DeveloperFactory: 0xc34cc3e53850673db1dea31d267ea1738edc629f
Saving successful migration to network...
  ... 0x95a3cd861f067cdbe8c96f13477526eacd2a7936662f31724e1354922af49664
Saving artifacts...

Chú ý ganache-cli output mà contract đã tạo ra:

Transaction: 0xc83617394674cd65f751ff9c05438e16339414ccf1e1662ba66479d79335af13
  Contract created: 0xe982e78028e0dfcbdb135e7a3c1e1ed3d98e36e5
  Gas usage: 269607
  Block Number: 1
  Block Time: Thu May 03 2018 21:04:55 GMT+0200 (CEST)

Console

Hãy chạy truffle console --network development ngay trên terminal window mà bạn đã chạy các truffle command. Nó sẽ launch truffle console và cho phép bạn tương tác với blockchain của mình.

Chúng ta sẽ dùng Web3 Javascript API cho dễ. Đầu tiên, hãy dùng một account và bỏ nó vào 1 variable:

account = web3.eth.accounts[4]

Rồi chạy command sau:

DeveloperFactory.deployed().then(inst => {Factory = inst})

Nó sẽ assign contract vào Factory variable. Nhớ rằng account có 100 ether:

truffle(development)> web3.fromWei(web3.eth.getBalance(account).toNumber())
'100'
truffle(development)>

Method getBalance sẽ trả về kiểu BigNumbertoNumber() sẽ đưa balance về cho account trong Wei. Sau đó ta sẽ covert nó về ether bằng fromWei().

  • Tạo một Developer

Hãy gọi function này là createRandomDeveloper. Như bạn thấy, function này cần 2 parameter, một string _name và một uint _age. Vì chúng ta cần đến 5 ether để call function này, ta phải làm rõ nó trong function call:

truffle(development)> Factory.createRandomDeveloper('Damien', 26, {from: account, value: web3.toWei(5, "ether")})

Factory là một contract instance. Chúng ta đưa vào 3 parameters vào function. Damien là _name, 26 là _age. Cái thứ ba là một object có key from để biết được account nào đang call nó, và một key value để xác định value được gửi bởi account đó.

Ở đây, from value là account, variable chúng ta đã tạo trước đó. Chúng ta sẽ covert 5 ether Wei để tương thích với value cần trong contract.

Terminal của bạn sẽ hiện như sau:

{ tx: '0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e',
  receipt:
   { transactionHash: '0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e',
     transactionIndex: 0,
     blockHash: '0x3ef1c41cbc79d65c1282a86da3a68120a5c069709a6a5bd3e206ed85d9c270c5',
     blockNumber: 5,
     gasUsed: 148160,
     cumulativeGasUsed: 148160,
     contractAddress: null,
     logs: [ [Object] ],
     status: '0x01',
     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000200000000000000000000000000000000000000000000000000000002000000000000000000000' },
  logs:
   [ { logIndex: 0,
       transactionIndex: 0,
       transactionHash: '0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e',
       blockHash: '0x3ef1c41cbc79d65c1282a86da3a68120a5c069709a6a5bd3e206ed85d9c270c5',
       blockNumber: 5,
       address: '0x033711f6fd408b10cc94a21a3e8c20f0e75a4615',
       type: 'mined',
       event: 'NewDeveloper',
       args: [Object] } ] }
truffle(development)>

Việc chuyển đổi đã thành công. Có rất nhiều info ở đây. Chúng ta có thể thấy event ‘NewDeveloper’ đã bị bỏ đi như dự kiến, có thêm hash, block hash, lượng gas đã dùng,…

Còn balance của account là như sau:

truffle(development)> web3.fromWei(web3.eth.getBalance(account).toNumber())
'94.985184'

Chú ý rằng nó không phải 95 ether. Đó là do khi bạn tương tác với contract, bạn cũng phải trả thêm ether để thực hiện chuyển đổi. Chúng ta có cumulativeGasUsed (148160) trong thông tin của giao dịch, có nghĩa là 148160 Wei đã được dùng để thực hiện giao dịch này. Chúng ta lấy số này nhân với gasPrice. Mọi giao dịch đều có gasPrice.

Có thể lấy giá trị này bằng transactionHash, rồi nhân nó với cumulativeGasUsed để biết chi phí giao dịch bằng đơn vị ether:

truffle(development)> web3.eth.getTransaction('0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e').gasPrice.toNumber() * 148160
14816000000000000
truffle(development)> web3.fromWei(14816000000000000)
'0.014816'
truffle(development)> 100 - 0.014816
99.985184

Và chúng ta có thể tính balance như vậy. Chúng ta còn có thể đảm bảo balance của account là 5 ether như sau:

truffle(development)> web3.fromWei(web3.eth.getBalance('0x033711f6fd408b10cc94a21a3e8c20f0e75a4615').toNumber())
'5'

Bạn có thể lấy address của contract trong log field address của giao dịch trên. Cuối cùng, nếu như chúng ta không gửi 5 ether trong contract thì sao? Hãy lấy một account mới:

truffle(development)> account1 = web3.eth.accounts[9]
'0x5e273389dba808789a27cb792faaf31429c8de8c'
truffle(development)> web3.fromWei(web3.eth.getBalance(account1).toNumber())
'100'

Hãy call function createRandomDeveloper:

truffle(development)> Factory.createRandomDeveloper('Johnny', 43, {from: account1, value: web3.toWei(10, "ether")})
Error: VM Exception while processing transaction: revert
    at Object.InvalidResponse (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:41484:16)

truffle(development)> web3.fromWei(web3.eth.getBalance(account1).toNumber())
'99.9976828'

Có một lỗi xảy ra. Tuy nhiên, gas dùng để bắt đầu giao dịch kiểu gì chẳng bị mất đi! Bạn có thể thấy balance của account không còn là 100 ether nữa.

To be contiuned…

Có thể bạn quan tâm:

Xem thêm tuyển dụng lập trình blockchain hấp dẫn trên TopDev