From ripples to waves
스마트 컨트랙트와 RPC: 블록체인 네트워크와 상호작용하는 방법 본문
스마트 컨트랙트와 RPC는 블록체인 네트워크 상에서 데이터를 읽고 쓰거나, 트랜잭션을 발생시키기 위해 프로그래머가 반드시 이해해야 할 필수 개념이다.
스마트 컨트랙트는 네트워크에 특정 함수를 배포하는 거라고 보면 된다. 트랜잭션을 발생시키고, 해당 트랜잭션 정보에 코드를 컴파일한 코드 데이터를 첨부한다. 그럼 해당 네트워크 상에 영구히 수정할 수 없는 함수 집합이 배포되는 것이다. 퍼블릭 네트워크에서는 누구든지 이 컨트랙트 주소만 알고 있다면 해당 컨트랙트의 함수들을 조회하고 실행할 수 있다.
RPC는 새로운 함수를 만들어서 네트워크에 배포하는 것이 아니라, 이미 존재하는 함수(예: 스마트컨트랙트의 view 함수 또는 블록체인 시스템 함수)를 호출할 때 사용된다. RPC(Remote Procedure Call)라는 개념은 원래 분산 시스템 전반에서 사용되던 통신 방식으로, 블록체인에 한정된 기술은 아니다. 일반적인 현대 웹 개발에서는 REST API를 많이 사용하지만, 블록체인에서는 함수 단위의 직접 호출이 필요한 경우가 많기 때문에, JSON-RPC 형식으로 메서드 호출을 하는 방식이 더 자연스럽고 적합하다.
RPC Call 중에서도 read 용도가 있고, write 용도가 있을 것이다. read는 단순히 네트워크 상태(예: 잔액, 토큰 보유량)를 조회하는 것으로, 트랜잭션이 아니며, 수수료나 서명이 필요 없다. 반면 write는 상태를 변경하는 호출로, 실제 트랜잭션이 발생하며 서명 및 가스 수수료가 요구된다. 이 경우 호출 내역은 블록에 기록되고, 누구나 검증할 수 있다.
RPC (Remote Procedure Call)
스마트컨트랙트나 블록체인 노드에 접근할 때, 일반적으로 HTTP + JSON-RPC 프로토콜을 사용한다.
POST / HTTP/1.1
Host: mainnet.infura.io
Content-Type: application/json
{
"jsonrpc":"2.0",
"method":"eth_getBalance",
"params":["0xabc123...", "latest"],
"id":1
}
- 이건 이더리움 메인넷 노드에 잔액을 조회하는 요청이다
- 이렇게 RPC를 통해 블록체인 노드에 직접 메서드 호출을 하는 구조
- 흔히 쓰는 클라이언트로는 ethers.js, web3.js 등이 있다. 얘네는 내부적으로 이런 RPC 요청을 날린다.
ABI (Application Binary Interface)
스마트컨트랙트를 호출하거나 배포할 때 필요한 인터페이스 정의서
쉽게 말해, 컨트랙트의 함수와 변수에 어떻게 접근할 수 있는지 설명한 JSON 파일이다.
[
{
"constant":true,
"inputs":[],
"name":"totalSupply",
"outputs":[{"name":"","type":"uint256"}],
"type":"function"
}
]
- 이 ABI 정보를 기반으로 프론트엔드가 스마트컨트랙트의 특정 함수(e.g., totalSupply())를 호출할 수 있다.
- ABI는 컴파일된 스마트컨트랙트에서 자동 생성되며, 프론트에서 ethers.Contract 등에 넣어서 사용한다
왜 블록체인 제품 개발에선 RPC를 주로 쓸까?
RPC 는 원격에서 프로시저(함수) 호출.
API, 특히 REST API 는 자원 중심적 행위 (특정 자원을 GET, POST, PATCH, DELETE ..)
어찌보면 RPC가 훨씬 Atomic 한 개념이라고 볼 수 있음. API 대비 보통은 low-level 하고 명령적인 단위.
RPC (Remote Procedure Call) vs API (REST API 등)
| 목적 | 함수 호출처럼 원격 명령 실행 | 자원(Resource)에 접근하거나 조작 |
| 구조 | 메서드명 + 파라미터로 요청 (eth_getBalance) | HTTP URL 기반으로 요청 (GET /users/123) |
| 주 사용처 | 블록체인 노드, 시스템 간 내부 통신 | 웹 서비스, 서버–클라이언트 구조 |
| 형식 | JSON-RPC, gRPC 등 | RESTful HTTP, GraphQL 등 |
| 철학 | "저 함수 좀 실행시켜줘" | "저 리소스 좀 보여줘/바꿔줘" |
블록체인은 하나의 큰 상태 머신 (state machine) 임.
- 상태를 읽거나,
- 트랜잭션을 보내서 상태를 바꾸거나
이런 일들을 하는데 트랜잭션 = 함수 호출 단위로 처리된다.
- 상태 질의: eth_getBalance, eth_blockNumber
- 상태 변경: eth_sendRawTransaction
그래서 REST보다는 RPC처럼 메서드를 직접 실행시키는 구조가 자연스럽다.
익스플로러와 같은 제품을 만든다면?
(익스플로러 제품이란?: 블록체인 네트워크는 기본적으로 투명하게 공개되어있으나, 일반적인 사람들이 이를 까보기는 사실상 어렵다. 개발자라고 할지라도 많은 노력이 필요하며 큰 구조를 한눈에 보기는 사실상 어렵다. 따라서 특정 메인넷 상에서 발생하는 Transaction, Block, 지갑 정보 등을 보기 편하도록 시각화해서 보여주는 서비스가 등장했는데, 그게 이더스캔, mempool 등의 익스플로러이다.)
이더스캔 등 익스플로러 제품을 만든다고 하면, 일반적으로는 다음과 같은 단계를 거칠 것이다.
굳이 스마트 컨트랙트 배포 없이, RPC 만으로도 기초적인 제품은 만들 수 있을 거라 생각한다.
1. [백엔드] RPC 호출해서 노드 정보를 가져오기
- Alchemy, Infura 등에서 제공하는 JSON-RPC API를 사용해 새로운 블록 / 트랜잭션 정보를 읽어온다
- getBlockByNumber, getTransactionByHash, getLogs 등의 RPC 메서드를 호출
2. [백엔드] 내부 DB 에 저장해서 자체 인덱스 만들기
- 블록이 새로 생기면 RPC로 블록 데이터 + 트랜잭션 데이터를 파싱
- 내부 DB에 저장 (예: blocks, transactions, wallets 테이블 등)
- 즉, 블록체인을 스캔하며 자체 인덱스를 만드는 과정
| blocks | blockNumber, timestamp, miner, txCount... |
| transactions | hash, from, to, value, gas, blockNumber... |
| wallets | address, txCount, lastActiveTime... |
| tokens | contractAddress, name, symbol, holders... (선택) |
3. [백엔드] JSON으로 잘 말아서 REST API, GraphQL 등으로 프론트에 제공
4. [프론트] 마크업 개발
5. [프론트] API 연동 및 화면에 뿌려주기
스마트 컨트랙트
그럼 스마트 컨트랙트는 언제 필요한가?
바로 블록체인 상에서 "새로운 규칙"이나 "서비스 로직"을 만들고자 할 때. 예를 들어 다음과 같은 경우엔 스마트 컨트랙트 개발이 필요하다.
1. DeFi 프로토콜을 만들고 싶을 때
- 예: 유동성 풀, 스왑, 렌딩 등
- Uniswap, Aave 같은 서비스는 대부분 스마트컨트랙트로 로직을 구현
2. NFT 프로젝트를 런칭할 때
- ERC-721 또는 ERC-1155 스마트컨트랙트를 직접 작성해서 배포
- 민팅, 메타데이터 URI 관리, 로열티 로직 등
3. DAO (탈중앙 조직) 를 구성할 때
- 의사결정, 투표, 거버넌스 로직 등을 스마트컨트랙트로 설계
4. 온체인 게임이나 인증 시스템을 만들 때
- 게임 규칙, 자산 상태 관리, 승패 판정 등을 온체인에서 처리
5. 스테이블코인, 토큰 이코노미 설계
- 예: 리베이스 토큰, 수익 공유 구조, 발행량 조절 로직 등
6. Web2 + Web3 연동 서비스
- 예: 사용자가 Web2에서 가입하면 NFT를 발급해주는 등
- 스마트컨트랙트를 통해 블록체인 상의 액션을 트리거
배포된 컨트랙트는 누구나 접근 가능
이더리움 메인넷은 퍼블릭 블록체인이라서 컨트랙트 주소와 ABI만 알면, 누구든 RPC를 통해 다음 작업이 가능하다.
| view 함수 호출 | eth_call | 잔액 확인, 상태 조회 등 무료 |
| 트랜잭션 전송 | eth_sendTransaction, eth_sendRawTransaction | 실제 상태 변경 (예: 입금, 민팅 등) |
| 이벤트 조회 | eth_getLogs | 이벤트 로그 필터링해서 보기 가능 |
예를 들어
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, provider)
const balance = await contract.getBalance()
스마트 컨트랙트 간단한 예제 (Solidity)
사용자가 이더(ETH)를 보내면, 그에 따라 내부 카운터가 증가하는 식의 기초 입금 스마트 컨트랙트 예제를 살펴보자.
pragma solidity ^0.8.0;
contract SimpleDeposit {
address public owner;
uint public totalDeposits;
constructor() {
owner = msg.sender;
}
// 입금받기 (이더를 받을 수 있는 함수)
receive() external payable {
totalDeposits += msg.value;
}
// 잔액 조회
function getBalance() public view returns (uint) {
return address(this).balance;
}
// 오너만 출금 가능
function withdraw(uint amount) public {
require(msg.sender == owner, "Not the owner");
require(amount <= address(this).balance, "Insufficient balance");
payable(owner).transfer(amount);
}
}
주요 구성함수는 다음 3가지이다.
| receive() | msg.value를 통해 들어온 이더를 받고, 합계 저장 |
| getBalance() | 현재 컨트랙트가 보유 중인 이더 잔액 반환 |
| withdraw() | 소유자(owner)만 인출 가능 |
스마트 컨트랙트 배포 == 트랜잭션 발생시키기 (but 코드를 담은)
컨트랙트를 배포하는 것도 하나의 트랜잭션이다. 단지 일반적인 송금 트랜잭션과는 달리, 이 트랜잭션에는 코드를 담은 바이트코드가 실려 있다.
배포 트랜잭션의 구조는 다음과 같다.
| 필드 | 내용 |
| to | 없음 (컨트랙트 생성 트랜잭션이라 비워둠) |
| data | Solidity 코드가 EVM 바이트코드로 컴파일된 결과 |
| from | 배포자 주소 |
| value | (선택) 컨트랙트에 초기 이더 전송 가능 |
| gas | 컴파일된 바이트코드를 실행하고 저장하는 데 필요한 연산 비용 |
배포한 컨트랙트는 바꿀 수 없기 때문에 아래 사항을 잘 신경 써야 한다.
| 접근 제한 (onlyOwner) | 누구나 호출할 수 있으므로 함수마다 접근 제어 필요 |
| 재진입 방지 (nonReentrant) | 입출금 관련 함수는 재진입 공격 위험 있음 |
| 가스 최적화 | 메인넷 수수료 부담 큼 |
| 업그레이드 가능성 | 일반 컨트랙트는 불변이므로, 필요시 UUPS 패턴 등 도입 |
'Blockchain, Web3' 카테고리의 다른 글
| 스테이블코인에 가지는 우려 (0) | 2025.07.10 |
|---|---|
| Stripe | 크립토 관련 전략 정리 (0) | 2025.07.09 |
| EVM 호환성에 대한 개발자, 사용자 측면 이해 (0) | 2025.07.06 |
| UTXO 모델, Account/Balance 모델 (1) | 2025.07.03 |
| 스테이블 코인 Follow up (0) | 2025.07.01 |