
Web3: 탈중앙화 웹
Web3의 개념과 활용 방법을 경험을 통해 이해한 과정

Web3의 개념과 활용 방법을 경험을 통해 이해한 과정
공개 API를 운영하다 보면 예상치 못한 대량 요청에 시달릴 수 있다. Rate Limiting과 API Key 관리로 API를 보호하는 방법을 정리했다.

admin/user 두 역할로 시작했는데, 요구사항이 복잡해지면서 RBAC만으로 부족해졌다. ABAC까지 고려한 권한 설계를 정리했다.

서비스가 3개로 늘어나면서 각각 로그인을 구현하는 게 지옥이었다. SSO로 한 번의 인증으로 모든 서비스에 접근하게 만든 이야기.

비밀번호 찾기가 CS의 절반을 차지했는데, Passkey를 도입하니 비밀번호 자체가 필요 없어졌다. 근데 구현이 생각보다 복잡했다.

솔직히 말하면, 처음엔 NFT 열풍에 끌려서 시작했다. 2021년이었나. 주변에서 "원숭이 그림 한 장에 수억 원"이라는 얘기가 넘쳐났고, 개발자인 나로서는 그게 기술적으로 어떻게 가능한 건지 도무지 이해가 안 됐다. 그냥 JPEG가 왜 그렇게 비싸지?
그걸 파고들다 보니 블록체인을 공부하게 됐고, 블록체인을 이해하려니 스마트 컨트랙트가 나왔고, 스마트 컨트랙트를 쓰다 보니 자연스럽게 Web3 전체 개념으로 확장됐다. 이 글은 그 과정에서 내가 이해한 방식 그대로 정리해본다.
Web2는 지금 우리가 매일 쓰는 인터넷이다. 카카오톡, 인스타그램, 구글. 이 모든 서비스의 공통점은 중앙 서버가 데이터를 통제한다는 것이다. 내가 올린 사진은 결국 메타(Meta)의 서버에 있고, 메타가 "지워" 하면 사라진다. 내 계정은 내 것 같지만 사실 플랫폼의 것이다.
Web3는 이걸 뒤집자는 아이디어에서 출발했다. 데이터의 소유권을 플랫폼이 아닌 사용자에게 돌려주자. 중앙 서버 없이 신뢰를 만들자. 처음 들었을 때는 "그게 어떻게 가능해?"라는 생각이었는데, 블록체인의 작동 원리를 이해하고 나서야 비로소 와닿았다.
| 특징 | Web1 | Web2 | Web3 |
|---|---|---|---|
| 시기 | 1990-2004 | 2004-현재 | 현재-미래 |
| 특징 | 읽기 전용 | 읽기+쓰기 | 읽기+쓰기+소유 |
| 예시 | 정적 웹사이트 | SNS, 클라우드 | DApp, NFT |
| 데이터 | 서버 | 중앙 플랫폼 | 블록체인 |
| 소유권 | 웹마스터 | 플랫폼 | 사용자 |
Web1이 "읽기만 하던 인터넷", Web2가 "내가 직접 올리고 참여하는 인터넷"이라면, Web3는 "내가 진짜로 소유하는 인터넷"이다. 그 차이를 만드는 핵심이 블록체인이다.
Web2: 클라이언트 → 중앙 서버 → 데이터베이스
Web3: 클라이언트 → 블록체인 (분산 노드들)
내가 은행 계좌에서 친구한테 돈을 보내면 어떤 일이 벌어지나. 은행 서버가 "A 잔고 -10만원, B 잔고 +10만원"을 기록한다. 이걸 믿는 이유는 은행이라는 기관을 신뢰하기 때문이다.
블록체인은 다르다. 은행 서버 역할을 수천 개의 노드가 나눠서 한다. 누군가 장부를 조작하려면 전 세계 노드 절반 이상을 동시에 해킹해야 한다. 현실적으로 불가능하다. 기관을 신뢰하는 게 아니라 수학과 암호학을 신뢰하는 거다. 이게 "트러스트리스(Trustless)"라는 개념이다. 처음엔 이 말이 어색했는데, "신뢰가 필요 없는 게 아니라, 제3자를 신뢰할 필요가 없다"는 뜻이라고 이해했다.
Web2에서 서비스를 쓰려면 아이디와 비밀번호로 로그인한다. Web3에서는 지갑(Wallet)이 그 역할을 대신한다. MetaMask 같은 지갑이 대표적이다.
// MetaMask 연결
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
console.log('Connected:', accounts[0]);
// 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
지갑에는 두 개의 키가 있다. 공개키(Public Key)와 개인키(Private Key). 공개키는 누구에게나 알려줄 수 있는 내 주소고, 개인키는 절대 유출해선 안 되는 비밀번호다. 트랜잭션을 보낼 때 개인키로 서명하면, 네트워크는 공개키로 "이 사람이 진짜 보낸 게 맞다"를 검증한다.
처음 MetaMask를 설치했을 때 시드 구문(Seed Phrase) 12단어를 적으라고 했다. "이게 뭐가 중요해" 싶었는데, 나중에 이해했다. 이 12단어가 내 모든 자산에 접근하는 마스터키다. 잃어버리면 아무도 복구해줄 수 없다. 진짜 아무도. 은행에 전화해서 비밀번호 찾기 같은 게 없다.
스마트 컨트랙트는 블록체인에 올라가는 코드다. 한번 배포되면 수정할 수 없고, 조건이 충족되면 자동으로 실행된다. "코드가 법(Code is Law)"이라는 말이 여기서 나왔다.
contract Token {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
require(balances[msg.sender] >= amount, "잔액 부족");
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
이걸 처음 봤을 때 "그냥 일반 코드잖아?" 싶었다. 근데 차이가 있다. 이 코드는 이더리움 네트워크 전체에 배포된다. 내가 서버를 끄거나 코드를 바꿀 수 없다. 누구도 멈출 수 없다. 그래서 require 체크가 중요하다. 버그가 있으면 돈이 날아가고 되돌릴 수 없기 때문이다.
Solidity를 배우면서 가장 낯설었던 건 msg.sender다. 함수를 호출한 사람의 주소가 자동으로 넘어온다. 별도 인증 없이 누가 이 함수를 호출했는지 바로 알 수 있다. 지갑 서명이 인증 역할을 대신하는 거다. 이 구조가 이해됐을 때 "아, 이래서 로그인 없이 돌아가는 거구나"라고 이해했다.
Vitalik Buterin이 제안한 개념이다. 블록체인은 세 가지 특성을 동시에 완벽하게 가질 수 없다.
비트코인과 이더리움은 탈중앙화와 보안을 택했다. 느리다. 이더리움 메인넷은 초당 15~30개 트랜잭션 정도 처리한다. 비자카드가 초당 수만 건을 처리하는 것과 비교하면 참담한 숫자다.
이 문제를 해결하려는 게 Layer 2다. 실제 처리는 레이어 2에서 하고, 결과(증명)만 이더리움 메인넷에 올린다. 은행 비유를 다시 쓰면 — 수많은 소액 거래는 장부에 따로 기록해두고, 일정 주기로 "총액 이만큼" 하고 은행에 한 번만 알리는 식이다.
처음 Layer 2 얘기를 들었을 때 "그럼 결국 중앙화되는 거 아닌가?" 싶었다. 근데 증명(Proof)이 메인넷에 올라가기 때문에, 레이어 2 운영자가 악의적으로 행동하면 누구나 이의를 제기할 수 있다. 완전한 탈중앙화는 아니지만 훨씬 실용적인 타협점이라고 이해했다.
블록체인은 중앙 관리자가 없는데 어떻게 모두가 같은 장부를 공유할까. 합의(Consensus) 알고리즘이 그 역할을 한다.
비트코인이 쓰는 방식이다. 수학 문제를 가장 빨리 푼 노드가 새 블록을 기록할 권한을 얻는다. 이 과정이 "채굴(Mining)"이다.
이더리움이 2022년 "The Merge"로 전환한 방식이다. 코인을 예치(Stake)한 만큼 검증 권한을 얻는다. 악의적으로 행동하면 예치한 코인이 삭감(Slash)된다.
이더리움이 PoW에서 PoS로 전환하는 과정을 지켜봤다. 몇 년에 걸친 프로젝트였는데, 실제로 블록체인이 살아 있는 채로 합의 알고리즘 자체를 갈아치운 거다. 기술적으로 얼마나 어려운 일인지 공부하면서 정말 대단하다고 느꼈다.
이더리움 생태계에는 약속된 인터페이스 규격이 있다.
NFT 가격이 수억 원이 되는 게 처음엔 이해가 안 됐다. 근데 ERC-721 코드를 보고 나서 이해됐다. JPEG 자체가 블록체인에 올라가는 게 아니다. JPEG는 IPFS에 저장되고, 블록체인에는 "이 고유한 ID가 이 해시를 가리키고, 소유자는 이 주소"라는 기록만 올라간다. 희소성과 소유권 증명이 핵심인 거다. 가치가 있냐 없냐는 별개 문제지만.
DeFi(Decentralized Finance)는 은행, 증권사 같은 금융 기관 없이 금융 서비스를 제공한다.
비유하자면 이렇다. 전통 금융은 은행이라는 중개자가 있다. 내가 예금하면 은행이 그걸 다른 사람한테 대출해주고 이자 차익을 먹는다. DeFi는 이 중개자를 스마트 컨트랙트로 대체한다. 코드가 자동으로 유동성을 관리하고 이자를 분배한다.
x * y = k 공식으로 두 자산의 가격을 자동으로 조정하는 유동성 풀이 있다. 내가 ETH를 팔면 풀의 ETH가 늘고 USDC가 줄면서 가격이 바뀐다DeFi 코드를 처음 직접 써봤을 때 신기했던 건, "허가"가 필요 없다는 점이었다. Uniswap 사용 신청서 같은 게 없다. 그냥 지갑 연결하고 함수 호출하면 끝이다. 세계 어디서든, 누구든. 반대로 문제가 생겨도 전화할 고객센터가 없다는 뜻이기도 하다.
처음 DApp을 만들어보려고 했을 때 막막했다. 어디서 시작해야 하는지조차 몰랐다. 지금 돌아보면 핵심 연결 고리가 세 개였다.
import { ethers } from 'ethers';
// 프로바이더: 블록체인 네트워크에 연결
const provider = new ethers.providers.Web3Provider(window.ethereum);
// 서명자: 트랜잭션에 서명할 지갑
const signer = provider.getSigner();
// 컨트랙트 인스턴스: 배포된 컨트랙트와 대화
const contract = new ethers.Contract(contractAddress, ABI, signer);
// 읽기 (가스비 없음)
const balance = await contract.balances(address);
// 쓰기 (가스비 발생)
const tx = await contract.transfer(toAddress, amount);
await tx.wait(); // 트랜잭션 확정 대기
ABI(Application Binary Interface)가 처음엔 뭔지 몰랐다. 컨트랙트를 컴파일하면 Bytecode(EVM이 실행하는 기계어)와 ABI(사람과 서비스가 컨트랙트와 대화하는 인터페이스)가 나온다. ABI가 있어야 어떤 함수가 있는지, 파라미터가 뭔지 알 수 있다. 이게 없으면 그냥 바이너리 덩어리다.
테스트넷에서 처음 트랜잭션을 성공시켰을 때가 기억난다. Sepolia 테스트넷에서 faucet으로 가짜 ETH 받아서, 내가 짠 컨트랙트에 전송했더니 정말로 잔고가 바뀌었다. 코드가 중앙 서버 없이 실행된다는 게 그때 처음으로 피부에 와닿았다.
블록체인에 직접 파일을 올리면 비용이 어마어마하다. 이더리움에 1GB 저장하면 수백만 달러가 든다. 그래서 파일은 IPFS에 저장하고, 블록체인에는 해시(CID)만 올린다.
import { create } from 'ipfs-http-client';
const ipfs = create({ url: 'https://ipfs.infura.io:5001' });
// 파일 업로드
const { cid } = await ipfs.add('Hello Web3!');
console.log('IPFS CID:', cid.toString());
// bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
// 파일 다운로드
const data = await ipfs.cat(cid);
IPFS의 핵심은 콘텐츠 주소 방식(Content Addressing)이다. URL처럼 위치가 아니라 내용의 해시로 파일을 찾는다. 파일 내용이 1비트라도 바뀌면 CID가 완전히 달라진다. 이래서 NFT 메타데이터의 불변성을 보장할 수 있다. 단, 파일을 "핀(Pin)"하는 노드가 없으면 사라질 수 있다. Pinata 같은 서비스가 그 역할을 한다.
// ERC-721 NFT
contract MyNFT is ERC721 {
uint256 public tokenCounter;
constructor() ERC721("MyNFT", "MNFT") {
tokenCounter = 0;
}
function mint(string memory tokenURI) public returns (uint256) {
uint256 newTokenId = tokenCounter;
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI);
tokenCounter++;
return newTokenId;
}
}
tokenURI는 IPFS에 올린 JSON 메타데이터의 CID다. 그 JSON 안에 이미지 CID, 이름, 속성이 들어있다. 결국 NFT는 "이 주소가 이 메타데이터 포인터를 소유한다"는 블록체인 기록이다. 이미지 자체가 아니라.
"코드는 법이다"라는 말은 버그도 법이라는 뜻이다. 한번 배포하면 되돌릴 수 없다.
코드 감사(Audit)가 중요한 이유가 여기 있다. 수백억 원이 담긴 컨트랙트도 코드 몇 줄의 실수로 전부 날아간다.
가스비는 코드 실행에 드는 수수료다. 채굴자(검증자)에게 지급한다. 무한 루프를 방지하는 역할도 한다.
스토리지 비용이 가장 비싸다. 상태 변수를 변경하는 SSTORE 연산은 20,000 가스, 읽는 SLOAD는 200~800 가스다. 임시 변수는 memory나 calldata를 쓰는 게 훨씬 싸다.
변수 패킹도 중요하다.
// 비효율: 3개의 32바이트 슬롯 사용
uint128 a;
uint256 b;
uint128 c;
// 효율: 2개의 32바이트 슬롯 사용
uint128 a;
uint128 c; // a와 같은 슬롯에 패킹
uint256 b;
EVM은 32바이트 단위로 스토리지를 다룬다. 같은 슬롯에 패킹할 수 있는 작은 변수들을 연속으로 선언하면 스토리지 비용을 줄일 수 있다. 이런 최적화를 신경 써야 하는 개발 환경이 참 독특하다.
맞다. 아주 느리고 비싼 데이터베이스다. 모든 데이터를 블록체인에 올리는 건 비효율적이다. 중요한 해시값과 소유권 증명만 온체인에 두고, 실제 데이터는 IPFS나 AWS S3 같은 오프체인에 저장하는 하이브리드 방식이 현실적이다.
Q. 가스비는 왜 내나요?코드를 실행하는 검증자들에게 주는 인센티브다. 동시에 "이 연산이 전 세계 노드에서 실행된다"는 비용이다. 무한 루프 코드를 짜서 네트워크를 마비시키는 걸 방지하는 이유도 있다.
Q. 프론트엔드 개발자도 Web3를 배워야 하나요?필수는 아니다. 하지만 ethers.js로 지갑 연결하고 컨트랙트 함수 호출하는 경험은 기술 스택을 확실히 넓혀준다. "로그인" 대신 "지갑 연결"을 구현해보는 것부터 시작하면 된다.
Web3를 처음 공부할 때 "이게 실제로 쓰일까?"라는 의구심이 있었다. 지금도 투기 열풍과 스캠이 넘치는 건 사실이고, 대부분의 NFT 프로젝트는 실패했다.
그런데 기술 자체는 진지하게 볼 필요가 있다고 이해했다. 신뢰할 수 있는 제3자 없이 두 사람이 직접 거래할 수 있다는 것, 코드가 법처럼 강제된다는 것, 사용자가 데이터를 진짜로 소유할 수 있다는 것. 이 아이디어들은 분명히 의미 있다.
어디에 쓰느냐가 문제다. 원숭이 JPEG보다는 국경 없는 금융 접근성, 검열 저항성, 탈중앙화 자율 조직 같은 곳에서 진짜 가치가 나온다고 생각한다. 기술은 도구고, 도구가 어떻게 쓰이느냐는 결국 사람이 결정한다.