Ethereum Basics - Event/Logs
블록체인과 이더리움을 이해하기 위한 기초 개념인 Event와 Log 대해 알아봅니다.
Event란?
Event란 스마트 컨트랙트에서 지정된 Method가 실행될 때 생성되는 상태 변화를 블록체인에 저장할 수 있는 기능입니다. Event 기능을 이용하면 데이터를 검색과 이력 추적이 용이해집니다. 이 외에도 Event가 발생할 떄 마다 외부 어플리케이션에서 이를 감지하여 활용할 수 있어 스마트 컨트랙트와 외부 시스템과의 상호작용을 강화하는데 사용됩니다.
Event를 호출하면 Transaction이 실행된 후, Transaction Receipt의 log에 Topics[0], Topics[1], Topics[2], Topics[3] 이라는 이름으로 최대 4개의 값으로 저장된 것을 확인할 수 있습니다. Topics[0]은 Event의 signature를, Topics[1] ~ Topics[3]은 컨트랙트 작성자가 지정하여 Indexed된 값을 의미합니다. 예를 들어 스마트 컨트랙트에 다음과 같은 코드가 있다고 가정해 보겠습니다.
event Transfer(address indexed from, address indexed to, uint amount);
이 이벤트는 Token Transfer에 대한 Event로 Transfer Method를 실행하여 트랜잭션이 발생할 경우, eventLog로 블록체인에 해당 값을 저장합니다. Topics[0]에는 transfer에 대한 Event Signature가, Topics[1]에는 from의 address, Topics[2]에는 to의 address가 저장됩니다. amount의 경우, 별도의 Index가 없으므로 일반적인 data field에 저장됩니다.
Event가 어떤 것인지 이해가 되시나요? 그렇다면 한 번 실제로 Event가 작성된 컨트랙트를 배포하고 실제 Method를 호출한 뒤 Event Log를 확인해 보도록 하겠습니다! 🙋
Contract 작성 및 배포
Event를 생성하기 위해서는 컨트랙트에 Event를 명시해야 합니다. 그렇기 때문에 다음과 같이 컨트랙트를 작성하였습니다. 컨트랙트는 Message를 확인하는 getMessage Method와 Message를 변경하는 setMessage Method 2가지로 구성되었습니다.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract MessageContract {
string private message;
event MessageSet(address indexed from, address indexed to, string message);
constructor() {
message = "Hello World";
}
function getMessage() public view returns (string memory) {
return message;
}
function setMessage(string memory newMessage) public {
require(keccak256(abi.encodePacked(message)) != keccak256(abi.encodePacked(newMessage)), "New message is same as current message");
emit MessageSet(msg.sender, address(this), newMessage);
message = newMessage;
}
}
최초 Message는 Hello World로 작성되었고 event는 MessageSet이라는 이름으로 from, to address를 Index하는 것을 확인할 수 있습니다. emit MessageSet(msg.sender, address(this), newMessage);
이 코드를 통해 이 event는 setMessage Method를 실행할 때 생성되는 것을 확인할 수 있습니다.
작성한 컨트랙트를 컴파일하여 bytecode와 ABI를 반환 받습니다. 컴파일하기 가장 간편한 방법은 Remix를 이용하는 방법입니다. 반환받은 bytecode와 ABI는 다음과 같습니다.
Remix란?
solidity로 컨트랙트를 개발하고 배포하기 위한 무료 온라인 IDE 입니다. Remix를 이용하면 편리하게 컨트랙트를 컴파일 할 수 있으며 배포 및 테스트까지 가능합니다.
컨트랙트 파일 생성하기
위의 링크를 통해 Remix 사이트에 접속한 뒤, 좌측 메뉴에서 [contracts] 탭을 우클릭한 후 [New File]을 클릭합니다. 이후 생성할 파일의 이름을 입력한 뒤, .sol 이라는 확장자 명을 입력합니다컨트랙트 파일 컴파일하기
생성한 컨트랙트 파일을 클릭하면 코드를 입력할 수 있는 화면이 나타납니다. 코드를 작성한 후 좌측의 아이콘 중 위에서 3번째 아이콘을 클릭한 뒤 [Compile {Your Contract Name}.sol] 버튼을 클릭하면 컨트랙트 파일이 컴파일되고 Bytecode와 ABI를 반환합니다.
Bytecode, ABI 복사하기
컴파일이 완료되면 컴파일된 컨트랙트 파일을 Publish하는 버튼이 생성되고 아래에 ABI, Bytecode를 복사할 수 있는 버튼이 생성됩니다. 이 버튼을 이용하여 Bytecode와 ABI를 복사할 수 있습니다.
혹은 [artifacts] 폴더의 JSON 파일에서 Bytecode와 ABI를 확인하실 수 있습니다.
bytecode : 60806040523480156200001157600080fd5b506040518060400160405280600b81526020017f48656c6c6f20576f726c6400000000000000000000000000000000000000000081525060009081620000589190620002d9565b50620003c0565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620000e157607f821691505b602082108103620000f757620000f662000099565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620001617fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000122565b6200016d868362000122565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620001ba620001b4620001ae8462000185565b6200018f565b62000185565b9050919050565b6000819050919050565b620001d68362000199565b620001ee620001e582620001c1565b8484546200012f565b825550505050565b600090565b62000205620001f6565b62000212818484620001cb565b505050565b5b818110156200023a576200022e600082620001fb565b60018101905062000218565b5050565b601f82111562000289576200025381620000fd565b6200025e8462000112565b810160208510156200026e578190505b620002866200027d8562000112565b83018262000217565b50505b505050565b600082821c905092915050565b6000620002ae600019846008026200028e565b1980831691505092915050565b6000620002c983836200029b565b9150826002028217905092915050565b620002e4826200005f565b67ffffffffffffffff8111156200030057620002ff6200006a565b5b6200030c8254620000c8565b620003198282856200023e565b600060209050601f8311600181146200035157600084156200033c578287015190505b620003488582620002bb565b865550620003b8565b601f1984166200036186620000fd565b60005b828110156200038b5784890151825560018201915060208501945060208101905062000364565b86831015620003ab5784890151620003a7601f8916826200029b565b8355505b6001600288020188555050505b505050505050565b6108ef80620003d06000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063368b87721461003b578063ce6d41de14610057575b600080fd5b61005560048036038101906100509190610368565b610075565b005b61005f61017c565b60405161006c9190610430565b60405180910390f35b80604051602001610086919061048e565b6040516020818303038152906040528051906020012060006040516020016100ae919061059d565b6040516020818303038152906040528051906020012003610104576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100fb90610626565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167ffb2c8bec470118dbfe75e428c8fa4007bb843bb1078e7195255c351f6e54afe1836040516101619190610430565b60405180910390a3806000908161017891906107e7565b5050565b60606000805461018b906104d4565b80601f01602080910402602001604051908101604052809291908181526020018280546101b7906104d4565b80156102045780601f106101d957610100808354040283529160200191610204565b820191906000526020600020905b8154815290600101906020018083116101e757829003601f168201915b5050505050905090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6102758261022c565b810181811067ffffffffffffffff821117156102945761029361023d565b5b80604052505050565b60006102a761020e565b90506102b3828261026c565b919050565b600067ffffffffffffffff8211156102d3576102d261023d565b5b6102dc8261022c565b9050602081019050919050565b82818337600083830152505050565b600061030b610306846102b8565b61029d565b90508281526020810184848401111561032757610326610227565b5b6103328482856102e9565b509392505050565b600082601f83011261034f5761034e610222565b5b813561035f8482602086016102f8565b91505092915050565b60006020828403121561037e5761037d610218565b5b600082013567ffffffffffffffff81111561039c5761039b61021d565b5b6103a88482850161033a565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156103eb5780820151818401526020810190506103d0565b60008484015250505050565b6000610402826103b1565b61040c81856103bc565b935061041c8185602086016103cd565b6104258161022c565b840191505092915050565b6000602082019050818103600083015261044a81846103f7565b905092915050565b600081905092915050565b6000610468826103b1565b6104728185610452565b93506104828185602086016103cd565b80840191505092915050565b600061049a828461045d565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806104ec57607f821691505b6020821081036104ff576104fe6104a5565b5b50919050565b60008190508160005260206000209050919050565b60008154610527816104d4565b6105318186610452565b9450600182166000811461054c576001811461056157610594565b60ff1983168652811515820286019350610594565b61056a85610505565b60005b8381101561058c5781548189015260018201915060208101905061056d565b838801955050505b50505092915050565b60006105a9828461051a565b915081905092915050565b7f4e6577206d6573736167652069732073616d652061732063757272656e74206d60008201527f6573736167650000000000000000000000000000000000000000000000000000602082015250565b60006106106026836103bc565b915061061b826105b4565b604082019050919050565b6000602082019050818103600083015261063f81610603565b9050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026106937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610656565b61069d8683610656565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006106e46106df6106da846106b5565b6106bf565b6106b5565b9050919050565b6000819050919050565b6106fe836106c9565b61071261070a826106eb565b848454610663565b825550505050565b600090565b61072761071a565b6107328184846106f5565b505050565b5b818110156107565761074b60008261071f565b600181019050610738565b5050565b601f82111561079b5761076c81610505565b61077584610646565b81016020851015610784578190505b61079861079085610646565b830182610737565b50505b505050565b600082821c905092915050565b60006107be600019846008026107a0565b1980831691505092915050565b60006107d783836107ad565b9150826002028217905092915050565b6107f0826103b1565b67ffffffffffffffff8111156108095761080861023d565b5b61081382546104d4565b61081e82828561075a565b600060209050601f831160018114610851576000841561083f578287015190505b61084985826107cb565b8655506108b1565b601f19841661085f86610505565b60005b8281101561088757848901518255600182019150602085019450602081019050610862565b868310156108a457848901516108a0601f8916826107ad565b8355505b6001600288020188555050505b50505050505056fea2646970667358221220d3f9e590b08dda07fb86689b4ea433337a5b927800fdd7c45db1593113de89bb64736f6c63430008120033
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "message",
"type": "string"
}
],
"name": "MessageSet",
"type": "event"
},
{
"inputs": [],
"name": "getMessage",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "newMessage",
"type": "string"
}
],
"name": "setMessage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
bytecode와 ABI 그리고 ethers.js를 이용하여 이더리움 네트워크에 컨트랙트를 배포합니다. (컨트랙트 배포에는 ETH가 필요하므로 테스트를 위해 무료로 손쉽게 ETH를 구할 수 있는 이더리움 Sepolia 테스트넷을 이용하여 배포합니다.)
const { ethers } = require("ethers");
const bytecode = "{ Your Bytecode }";
const abi = "{ Your ABI }";
const provider = new ethers.providers.JsonRpcProvider("https://ethereum-sepolia.nodit.io/{nodeId}");
const privateKey = "{Your Private Key}";
const wallet = new ethers.Wallet(privateKey, provider);
const contractFactory = new ethers.ContractFactory(abi, bytecode, wallet);
contractFactory.deploy()
.then((deployedContract) => {
console.log(`Contract deployed at address: ${deployedContract.address}`);
})
.catch((error) => {
console.log(`Error deploying contract: ${error}`);
});
이더리움 네트워크에 연결한 후, Private Key를 이용하여 새로운 wallet 객체를 생성, 이를 이용해서 컨트랙트를 이더리움 네트워크에 배포하게 됩니다. 배포에 성공할 경우 컨트랙트 주소가 반환됩니다.
Contract deployed at address: 0x1EBE2a6D4986a6Af1f211bf3D58C138E29b09Ad6
해당 주소를 Sepolia Scan에 검색하여 배포가 잘 되었는지 확인해 봅니다.
Method 실행 및 Event 확인
컨트랙트가 잘 배포되었는지 확인하기 위해 getMessage Method를 이용하여 확인합니다. ethers.js를 이용하여 컨트랙트의 Method를 호출하는 방법은 다음과 같습니다.
const { ethers } = require("ethers");
const nodeId = "{Your Node ID}";
const privateKey = "{Your Private Key}";
const contractAddress = "{Your Contract Address}";
const abi = "{Your ABI}";
const provider = new ethers.providers.JsonRpcProvider(`https://ethereum-sepolia.nodit.io/{nodeId}`);
const wallet = new ethers.Wallet(privateKey, provider);
const contract = new ethers.Contract(contractAddress, abi, wallet);
contract.getMessage()
.then((result) => {
console.log("Current message:", result);
})
.catch((err) => {
console.error("Failed to get message:", err);
});
ABI와 컨트랙트의 주소, 그리고 유저의 계정으로 Contract 인스턴스를 생성, Method를 호출합니다. 컨트랙트에 작성한 초기 message 값인 Hello World가 반환되는 것을 확인할 수 있습니다.
Current message: Hello World
이번에는 setMessage Method를 이용해 Message를 변경하고 Event Log를 받아보도록 하겠습니다.
const { ethers } = require("ethers");
const nodeId = "{ Your Node ID }"
const privateKey = "{ Your Private Key }";
const contractAddress = "{ Your Contract Address }";
const abi = "{ Your ABI }";
const provider = new ethers.providers.JsonRpcProvider(`https://ethereum-sepolia.nodit.io/{nodeId}`);
const wallet = new ethers.Wallet(privateKey, provider);
const contract = new ethers.Contract(contractAddress, abi, wallet);
const newMessage = "Hello Ethereum";
contract.setMessage(newMessage)
.then((tx) => {
console.log("Transaction hash:", tx.hash);
contract.once("MessageSet", (from, to, message) => {
console.log("from :", from, "to :", to, "message :", message);
});
})
.catch((err) => {
console.error("Failed to set message:", err);
});
newMessage를 인자로 하여 setMessage Method를 실행합니다. 실행이 완료된 후, Transaction Hash를 반환하고 생성된 event를 반환합니다. 아래와 같이 Transaction Hash, Message set이 확인되시나요?
Transaction hash: 0x4142dfc253fe9ab990d48ef1e45249e27d452a23193414c94e74990512e21dc0
Message set: Hello Ethereum
이렇게 생성된 log는 Etherscan에서도 확인이 가능합니다. Topics[0]은 event signature로 MessageSet의 signature가 저장되고 Topics[1]에는 이벤트를 호출한 EOA의 Address, Topics[2]에는 해당 트랜잭션을 받는 Contract의 Address가 저장됩니다. 별도로 Index하지 않은 message의 경우, Data Field에 작성되어 있는 것을 확인할 수 있습니다.
Topics는 Transaction 실행된 후, Receipt를 이용하여 조회할 수 있습니다. Receipt를 불러오는 JSON_RPC는 eth_getTransactionReceipt
입니다. Params로는 Transaction Hash가 필요하며 다음과 같이 작성하여 호출할 수 있습니다.
curl -X POST "https://ethereum-sepolia.nodit.io/{nodeId}" \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "eth_getTransactionReceipt",
"params": ["0x4142dfc253fe9ab990d48ef1e45249e27d452a23193414c94e74990512e21dc0"],
"id": 1
}'
Topics는 배열로 구성되어 있으며 logs에서 Topics의 데이터를 확인할 수 있습니다.
{
"jsonrpc":"2.0",
"id":1,
"result":
{
"blockHash":"0xe0ee8dec1f689b59c7f2a9e527f6c412b48624aa88638960c0c04b7dfe2e4e67",
"blockNumber":"0x3352da",
"contractAddress":null,
"cumulativeGasUsed":"0x7f9c0a",
"effectiveGasPrice":"0x59682f08",
"from":"0x78d3d552841415fe367f08acd869d0f0aa93e815",
"gasUsed":"0x7d58",
"logs":[
{
"address":"0x1ebe2a6d4986a6af1f211bf3d58c138e29b09ad6",
"topics":
[
"0xfb2c8bec470118dbfe75e428c8fa4007bb843bb1078e7195255c351f6e54afe1",
"0x00000000000000000000000078d3d552841415fe367f08acd869d0f0aa93e815",
"0x0000000000000000000000001ebe2a6d4986a6af1f211bf3d58c138e29b09ad6"
],
"data":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e48656c6c6f20457468657265756d000000000000000000000000000000000000",
"blockNumber":"0x3352da",
"transactionHash":"0x4142dfc253fe9ab990d48ef1e45249e27d452a23193414c94e74990512e21dc0",
"transactionIndex":"0x12",
"blockHash":"0xe0ee8dec1f689b59c7f2a9e527f6c412b48624aa88638960c0c04b7dfe2e4e67",
"logIndex":"0x17",
"removed":false
}
],
"logsBloom":"0x000000000000000000000000000000000200000400000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000004000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000004040000000000000000000000000004000",
"status":"0x1","to":"0x1ebe2a6d4986a6af1f211bf3d58c138e29b09ad6",
"transactionHash":"0x4142dfc253fe9ab990d48ef1e45249e27d452a23193414c94e74990512e21dc0",
"transactionIndex":"0x12",
"type":"0x2"
}
}
Web3 Data API를 이용하여 Event Log 확인하기
Event Log를 확인하는 것은 Transaction을 분석 및 검증하기 위해 필요한 중요한 작업입니다. 그렇기 때문에 Nodit도 Event Log를 확인할 수 있도록 searchEvent
라는 API를 제공하고 있습니다. 아래 링크의 API 문서 페이지에서 ABI를 입력하여 Event 정보를 확인해보세요!
Updated 2 months ago