Luniversity에 오신 여러분을 환영합니다!😆 이번 시간에는 이더리움과의 상호작용을 통해 이더리움의 기본적인 구조를 알아보고 Luniverse가 제공하는 Multichain API를 이용하여 반환받는 response와 Block의 실제 structure를 비교해보며 블록체인을 이해하는 시간을 갖도록 하겠습니다.


Blockchain이란?

Blockchain에서는 여러 개의 Transaction(유저가 발생시키는 상태의 변화)을 묶어 하나의 Block을 생성합니다. Block은 Header와 body로 구성되며, 새로운 블록이 생성될 때 마다 이전 Block의 해시를 새롭게 생성된 Block의 Header에 저장하게 되는데 이러한 모습이 마치 체인으로 연결된 것 처럼 보여 Blockchain이라고 합니다.

Blockchain은 탈중앙화된 시스템으로 설계되어있기 때문에 모든 Transaction을 Node가 검증하고 “합의”를 통해 Block을 생성하게 됩니다. 이렇게 생성된 Block은 네트워크의 모든 Node에 복제되어 분산 원장을 형성하며 이를 통해 거래의 안정성과 신뢰성을 보장하게 됩니다.

Blockchain을 자세히 이해하기 위해 우리는 지금부터 실제 Ethereum의 Block, Transaction, Account 등 가장 기본적인 개념의 구조부터 자세히 살펴보고 실제로 구조를 살펴볼 수 있는 Method를 호출해보며 이해를 높이도록 하겠습니다.


Block의 Structure 및 분석

이번 시간에는 Block이 어떠한 구조로 구성되어 있는지 확인해 보도록 하겠습니다. Block은 크게 Block Header와 Block Body로 구성되어 있습니다. 이더리움 Client인 Go-Ethereum(Geth)에서 확인할 수 있는 Block 구조는 다음과 같습니다.

type **Header** struct {
	ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
	UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
	Coinbase    common.Address `json:"miner"`
	Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
	TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
	ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
	Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
	Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
	Number      *big.Int       `json:"number"           gencodec:"required"`
	GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`
	GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`
	Time        uint64         `json:"timestamp"        gencodec:"required"`
	Extra       []byte         `json:"extraData"        gencodec:"required"`
	MixDigest   common.Hash    `json:"mixHash"`
	Nonce       BlockNonce     `json:"nonce"`

	// BaseFee was added by EIP-1559 and is ignored in legacy headers.
	BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`

	// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.
	WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
}

type **Body** struct {
	Transactions []*Transaction
	Uncles       []*Header
	Withdrawals  []*Withdrawal `rlp:"optional"`
}

type **Block** struct {
	header       *Header
	uncles       []*Header
	transactions Transactions
	withdrawals  Withdrawals

	// caches
	hash atomic.Value
	size atomic.Value

	// These fields are used by package eth to track
	// inter-peer block relay.
	ReceivedAt   time.Time
	ReceivedFrom interface{}
}

참조 : https://github.com/ethereum/go-ethereum/blob/master/core/types/block.go

Block Header

Block Header에는 Block을 구성하고 있는 다양한 값이 기록됩니다. Block Header에 기록되는 값은 다음과 같습니다.

  • ParentHash: 이전 블록의 해시입니다.
  • UnclesHash: 모든 uncle 블록의 해시 값을 포함하는 블록 해시입니다.
  • CoinBase : 블록 채굴 보상이 지급될 주소입니다.
  • StateRoot: 현재 블록의 상태 트리 루트입니다.
  • TxHash: 현재 블록의 모든 트랜잭션에 대한 Merkle 트리의 루트 값 입니다.
  • ReceiptHash: 현재 블록의 모든 트랜잭션 Receipt에 대한 Merkle 트리 루트입니다
  • Bloom: 블록의 모든 로그에 대한 필터값을 의미합니다. 블룸 필터를 사용하여 로그를 검색할 수 있습니다.
  • Difficulty: 현재 블록의 난이도를 의미합니다.
  • Number: 현재 블록의 순서 번호입니다.
  • GasLimit: 현재 블록의 최대 가스 양 입니다.
  • GasUsed: 현재 블록에 포함된 모든 트랜잭션에서 사용된 총 가스 양 입니다.
  • Time: 현재 블록이 생성된 시간을 나타내며 format은 Unix Timestamp 입니다.
  • Extra: 블록의 추가 데이터입니다. 해당 블록의 속성을 나타내는 정보를 저장하며 블록 생성자가 필요에 따라 입력한 값으로 optional한 값 입니다.
  • MixDigest: 채굴에 사용된 mix hash입니다. 마이너는 mixHash 값과 nonce를 변경하며 계산하여 최종적으로 얻어진 블록 해시 값이 난이도 보다 작은 값이 나오도록 계산을 해야 합니다. 만약 mixHash 값이 올바르지 않다면 해시 값이 난이도 보다 낮아질 확률이 거의 없기 떄문에 해당 블록은 유효하지 않은 것으로 간주됩니다.
  • Nonce: 블록 채굴에 사용되는 값으로 MixDigest와 Nonce를 계산하여 얻어진 블록의 해시 값이 difficulty에 지정된 값 보다 작은 값이 나오면 성공적으로 블록을 생성하게 됩니다.
  • baseFeePerGas: London 하드포크 이후 생성된 값으로 프로토콜에 의해 지정되는 가스당 기본 수수료 입니다. (Optional 한 값입니다.)
  • totalDifficulty: 현재 블록의 누적 난이도 값입니다.

Block Body

Block Body에는 현재 블록에 저장되어 있는 트랜잭션이 기록됩니다 Block Body에 기록되는 값은 다음과 같습니다.

  • transactions: 현재 블록에 포함된 모든 트랜잭션 배열입니다. transaction struct를 참조합니다.
  • uncles: 현재 블록의 모든 uncle 블록을 나타내는 블록 배열입니다. uncle 블록이란 채굴에 성공하였으나 블록체인에 포함되지 못한 블록을 의미합니다. 이더리움에서는 이러한 블록에도 일부 보상을 제공하여 채굴자가 공정한 보상을 받을 수 있도록 보완하고 있습니다. Block Header struct를 참조합니다.

Ethereum JSON-RPC를 이용하여 getBlock 호출

RPC는 Remote Procedure Call의 약자로 네트워크를 통해 떨어져 있는 다른 컴퓨터나 서버에 있는 함수나 프로시저를 호출하는 프로토콜입니다. Ethereum JSON-RPC를 이용하여 이더리움과 상호작용이 가능합니다. Ethereum JSON-RPC 중 하나인 eth_getBlockByNumber method를 호출하여 가장 최신 블록의 데이터를 확인해 보도록 하겠습니다.

📘

JSON-RPC 호출을 위한 Node RPC-Endpoint 구하기

Node를 이용하기 위해서는 해당 Node를 특정하는 RPC-Endpoint가 필요합니다. 아래 링크를 통해 Nova에서 제공하는 RPC-Endpoint를 얻을 수 있습니다. RPC-Endpont는 네트워크 별로 달라질 수 있으며 현재 Luniverse에서 제공하는 Ethereum의 네트워크는 Mainnet, Goerli, Sepolia 입니다.

노드 연동하여 RPC-Endpoint 구하는 방법(클릭)

curl -X POST "https://ethereum-mainnnet.luniverse.io/{Your Node ID}" \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc":"2.0",
    "method":"eth_getBlockByNumber",
    "params":["latest", true],
    "id":1
}'

다음은 eth_getBlockByNumber의 응답으로 반환받는 값 입니다.

💡 **Response** eth_getBlockByNumber
{
  jsonrpc: '2.0',
  id: 1,
  result: {
    baseFeePerGas: '0x42bd6afa6',
    difficulty: '0x0',
    extraData: '0x6d616e74612d6275696c646572',
    gasLimit: '0x1c9c380',
    gasUsed: '0xdd40cf',
    hash: '0xe5ad8ce4e8b98e578ff8adc1db37abaaa59f0c9768abcf18404faeea8db45fa8',
    logsBloom: '0x6b39547e838c97036419426ebf3c7da213e2db49651e21006a81ae846cbf3f3afad5c151e019cf2a8af5cb16aaffb52e42392ef2de27bcbde0554c17937f0bec24544b54467c86fbee49c26bea39f8bb784faa454d44da8a98d6ba95b4a1a931f61c4a4c131eaae391259c20ad984ebde614cbdf320f6c9e57874ad38ef80a90ce2101eead569408c9d3915797c2c0caa8479ae1ef5c455c2f2634f7629343f89f7d11ef5fa37e09f81adce6de5df2f3bff8ca4564ae49fb9ffd77d6b4bd0041fb7e36ea61482bb0c5d90e932e5f4b54d875a266fb673e748d91fcb3ab1e661e93d6f2b9bedc048d12379ab3e38982e0972a7654fd7ce553819d753faab06d47',
    miner: '0x5f927395213ee6b95de97bddcb1b2b1c0f16844f',
    mixHash: '0xc3e1ad139a26d9dff9450411a997a2c5f4d4e7bd770d4d52b0b18ed533df5ce3',
    nonce: '0x0000000000000000',
    number: '0x102e6c4',
    parentHash: '0xc725f989050cd8ceb8d2190ec38c45c30c37293a40755d1f41c28fff948e7b67',
    receiptsRoot: '0x1115efbc1c86c55f6c15f447ed47717df0858b9df3a6ff46ae6043323ab07f86',
    sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
    size: '0x22129',
    stateRoot: '0x12046ad361b63ae23c5778c1809e6801589848f209ad8d2fff5b6418e232994d',
    timestamp: '0x642a902b',
    totalDifficulty: '0xc70d815d562d3cfa955',
    transactions: [
      [Object], ... , [Object],
      ... 30 more items
    ],
    transactionsRoot: '0x0b9beb7315a47aff52d19b3c6e3fccdf4906c5ad84fff1a09723c158c525e716',
    uncles: []
  }
}

실제 호출해서 받은 응답 값과 Block struct의 Field 값을 매핑하면 다음과 같습니다. Block Structure와 실제 호출한 Block의 Field가 일치하는 것이 보이시나요?

      Go-Ethereum struct                eth_getBlockByNumber의 Response Field	

	ParentHash  common.Hash         =        parentHash
	UncleHash   common.Hash         =        sha3Uncles
	Coinbase    common.Address      =        miner
	Root        common.Hash         =        stateRoot
	TxHash      common.Hash         =        transactionsRoot
	ReceiptHash common.Hash         =        receiptsRoot
	Bloom       Bloom               =        logsBloom
	Difficulty  *big.Int            =        totalDifficulty
	Number      *big.Int            =        number
	GasLimit    uint64              =        gasLimit
	GasUsed     uint64              =        gasUsed
	Time        uint64              =        timestamp
	Extra       []byte              =        extraData
	MixDigest   common.Hash         =        mixHash
	Nonce       BlockNonce          =        nonce

  Transactions                    =        transactions
  Uncles                          =        sha3Uncles
	BaseFee *big.Int                =        baseFeePerGas

Luniverse를 이용하여 Block 구조 확인

Luniverse는 Public Chain을 더욱 편리하게 사용할 수 있도록 다양한 API를 제공하고 있습니다. Luniverse가 제공하는 다양한 API를 이용하여 Block과 관련된 정보를 조회해 보도록 하겠습니다. 이번 시간에 진행할 API의 목록은 다음과 같습니다.

📘

How to get Access Token?

Luniverse Multichain API를 이용하기 위해서는 Header에 Access Token을 포함하여 API를 호출해야 합니다. Access Token을 생성하기 위한 방법은 아래 링크를 클릭하여 확인할 수 있습니다.

Access Token 생성하기(클릭)

🚧

INSTALLATION

Node.js 환경에서 API를 실행하기 위해 axios 라이브러리가 필요합니다. 터미널에서 아래 명령어를 실행하여 axios 라이브러리를 설치해 주세요.

$ npm install axios —save

앞서 실행한 eth_getBlock과 같은 데이터를 Luniverse listBlocks API를 이용해 조회할 수 있습니다. Luniverse Multichain API는 아래의 방법으로 호출할 수 있습니다.

const axios = require('axios');

const options = {
  method: 'GET',
  url: 'https://web3.luniverse.io/v1/ethereum/mainnet/blocks',
  headers: {
    accept: 'application/json',
    Authorization: 'Bearer {Your Access Token}'
  }
};

axios
  .request(options)
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.error(error);
  });

listBlocks의 응답은 다음과 같습니다. listBlocks의 응답은 rpp에 지정된 수 만큼의 최근 Block의 데이터를 반환합니다. eth_getBlockByNumber의 응답으로 반환받는 것과 같은 값을 개발자가 사용하기 편리하도록 가공하여 제공합니다.

{
  "code": "SUCCESS",
  "data": {
    "count": 16996719,
    "page": 1,
    "rpp": 10,
    "path": "/ethereum/mainnet/blocks",
    "items": [
      {
        "path": "/ethereum/mainnet/blocks/0xc791ead6d5bc5a876314add4a7b7a7c5aad98366939a0ca83365ccf37e16ecbd",
        "hash": "0xc791ead6d5bc5a876314add4a7b7a7c5aad98366939a0ca83365ccf37e16ecbd",
        "number": 16996720,
        "timestamp": 1680871847,
        "parent": "0x233a34262f8dbf2bfd0ce794e5ff91cb27595e3a18a066661a418b133c0a715b",
        "size": "0x33eb7",
        "gasLimit": "0x1c9c380",
        "gasUsed": "0x1435581",
        "transactions": {
          "count": 227,
          "items": [
            "0x7fa025c5b5b90aad42b0a8ef58cafa7ec726656e6b966ec36f178855a1587a2c",
            ...  
            "0x720a9480b4af0c1fb3b6c09fcddca50de962dd60cd44a67eec721f17a4a5c33d"
          ]
        }
     }, 
     {
     ...
     }

이 외에도 blockNumber 혹은 blockHash로 해당 블록의 정보를 불러올 수 있는 getBlockByHashOrNumber API도 제공합니다. getBlockByHashOrNumber API 사용 방법은 다음과 같습니다.

const axios = require('axios');

const blockNumber = "{Your Block Number}"
const options = {
  method: 'GET',
  url: 'https://web3.luniverse.io/v1/ethereum/mainnet/blocks/${blockNumber}',
  headers: {
    accept: 'application/json',
    Authorization: 'Bearer {Your Access Token}'
  }
};

axios
  .request(options)
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.error(error);
  });

getBlockByHashOrNumber는 Hash나 Number로 특정한 Block 1개의 데이터를 반환합니다. JSON-RPC로 호출했던 eth_getBlockByNumber와 같은 동작을 하는 API 입니다. getBlockByHashOrNumber의 응답은 다음과 같습니다.

{
  "code": "SUCCESS",
  "data": {
    "path": "/ethereum/mainnet/blocks/0xc791ead6d5bc5a876314add4a7b7a7c5aad98366939a0ca83365ccf37e16ecbd",
    "hash": "0xc791ead6d5bc5a876314add4a7b7a7c5aad98366939a0ca83365ccf37e16ecbd",
    "number": 16996720,
    "timestamp": 1680871847,
    "parent": "0x233a34262f8dbf2bfd0ce794e5ff91cb27595e3a18a066661a418b133c0a715b",
    "size": "0x33eb7",
    "gasLimit": "0x1c9c380",
    "gasUsed": "0x1435581",
    "transactions": {
      "count": 227,
      "items": [
        "0x7fa025c5b5b90aad42b0a8ef58cafa7ec726656e6b966ec36f178855a1587a2c",
         ...
        "0x720a9480b4af0c1fb3b6c09fcddca50de962dd60cd44a67eec721f17a4a5c33d"
      ]
    }
  }
}

이렇게 Block의 구조는 어떻게 구성되어 있는지 배워보고 Nova와 ethereum JSON-RPC method를 이용하여 실제 Block 정보 호출 시 어떠한 값으로 반환되는지 확인해 보았습니다. Block이 어떠한 구조로 구성되어 있는지 이해되셨나요?

다음 시간에는 Blockchain의 상태를 변화시키는 Transaction에 대해 학습을 하도록 하겠습니다.


Luniverse는 Public Blockchain을 손 쉽게 이용할 수 있도록 다양한 API와 Node Provider Service를 제공하고 있습니다. 오늘 학습한 내용 외에도 다양한 API를 제공하고 있으며 **[여기(Luniverse Multichain API 확인하기)]()**를 클릭하여 Nova가 제공하는 다양한 API를 확인해 볼 수 있습니다! 😄😄