From ba0b8cccb0f29e5de888432da0d3de134f720b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=9B=84=EB=8B=88?= Date: Tue, 14 Apr 2026 00:09:34 +0900 Subject: [PATCH] docs(week-04): complete network/block + wagmi quiz - Answer all 11 quiz questions (Block header, MPT, wagmi hooks) --- week-04/quiz/quiz-04-solution.md | 388 +++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 week-04/quiz/quiz-04-solution.md diff --git a/week-04/quiz/quiz-04-solution.md b/week-04/quiz/quiz-04-solution.md new file mode 100644 index 00000000..60d10788 --- /dev/null +++ b/week-04/quiz/quiz-04-solution.md @@ -0,0 +1,388 @@ +# Week 4 Quiz: Network/Block + wagmi + +> **제출 방법:** 이 파일을 복사하여 답변을 작성한 후, PR로 제출하세요. +> **평가 기준:** 개념 이해도 중심 - 문법 오류보다 논리적 설명을 중시합니다. + +--- + +## 문제 1: 블록 헤더 필드 (객관식) + +다음 상황을 고려하세요: + +``` +블록 100의 해시: 0xabc123... +블록 101의 해시: 0xdef456... +``` + +블록 101의 `parentHash` 필드에는 어떤 값이 저장되어 있나요? 그리고 **왜** 이런 방식으로 연결하나요? + +**보기:** +A) 0xdef456... - 자기 자신의 해시를 저장하여 무결성을 보장한다 +B) 0xabc123... - 이전 블록의 해시를 저장하여 체인 연결과 불변성을 보장한다 +C) 블록 번호 100 - 숫자로 순서를 추적한다 +D) 빈 값 - 헤더에는 해시가 저장되지 않는다 + +**답변:** +B) 블록 101의 parentHash에는 바로 이전 블록인 블록 100의 해시 `0xabc123...`이 저장됩니다. 이것이 "블록체인"이라 불리는 이유입니다 — 각 블록이 이전 블록의 해시를 참조하여 체인처럼 연결됩니다. A는 자기 자신의 해시를 저장하는 것으로, 해시는 블록 내용 전체로부터 계산되므로 자기 참조는 불가능합니다. C는 단순 번호로는 데이터 무결성을 보장할 수 없습니다. D는 사실이 아닙니다. parentHash를 통한 연결은 한 블록만 변조해도 그 뒤의 모든 블록 해시가 불일치하게 만들어, 변조를 즉시 감지할 수 있는 불변성의 핵심 메커니즘입니다. + + +--- + +## 문제 2: MPT 목적 (객관식) + +이더리움에서 Merkle Patricia Trie(MPT)를 사용하는 **가장 중요한 이유**는 무엇인가요? + +**보기:** +A) 데이터를 암호화하여 외부에서 읽을 수 없게 한다 +B) 트랜잭션 처리 속도를 10배 이상 높인다 +C) 전체 데이터 없이도 특정 데이터의 존재와 정확성을 효율적으로 증명한다 +D) 블록 크기를 줄여서 저장 공간을 절약한다 + +**답변:** +C) MPT의 핵심 가치는 **효율적인 증명(proof)**입니다. Light Node는 블록 헤더의 Root Hash만 가지고 있어도, 전체 상태 데이터(수백 GB)를 다운로드하지 않고 특정 계정의 잔액이나 트랜잭션이 실제로 존재하는지 검증할 수 있습니다. Merkle Proof라는 짧은 경로 데이터만 있으면 Root Hash와 대조하여 데이터의 정확성을 수학적으로 입증할 수 있습니다. 이 덕분에 모바일 지갑이나 저사양 기기에서도 블록체인을 신뢰성 있게 사용할 수 있습니다. A는 틀렸습니다 — 블록체인 데이터는 공개됩니다. + + +--- + +## 문제 3: 체인 연결과 보안 (객관식) + +공격자가 블록 50의 트랜잭션을 수정하려고 합니다. 현재 체인의 최신 블록은 100입니다. 이 공격이 **왜** 어려운가요? + +**보기:** +A) 블록 50은 너무 오래되어서 시스템에서 접근할 수 없다 +B) 블록 50을 수정하면 해시가 바뀌고, 블록 51부터 100까지 모든 블록의 parentHash가 불일치하게 된다 +C) 블록 50은 이미 암호화되어 있어서 복호화 키가 필요하다 +D) 네트워크 관리자만 과거 블록을 수정할 수 있다 + +**답변:** +B) 블록 50의 내용을 수정하면 블록 50의 해시가 변합니다. 그러면 블록 51의 parentHash가 더 이상 블록 50의 새 해시와 일치하지 않으므로, 블록 51도 수정해야 합니다. 블록 51을 수정하면 블록 51의 해시도 변하므로 블록 52도 수정해야 하고... 이렇게 블록 100까지 총 50개 블록을 모두 다시 계산해야 합니다. PoS에서는 이 모든 블록에 대해 검증자들의 서명을 위조해야 하므로, 전체 스테이킹의 2/3 이상을 장악하지 않는 한 사실상 불가능합니다. 이것이 블록체인의 불변성(immutability)이 작동하는 원리입니다. + + +--- + +## 문제 4: MPT 진화 과정 (단답형) + +MPT(Merkle Patricia Trie)는 세 가지 자료구조의 장점을 결합한 것입니다: +1. **Trie** -> 2. **Patricia Trie** -> 3. **Merkle Patricia Trie** + +**왜** 각 단계의 발전이 필요했나요? 각 단계가 해결하는 문제를 간단히 설명하세요. + +**답변:** +1. **Trie가 해결하는 문제:** + 키-값 데이터를 효율적으로 검색할 수 있는 트리 구조를 제공합니다. 키의 각 문자(이더리움에서는 hex nibble)가 트리의 한 레벨이 되어, 키를 따라가면 해당 값을 찾을 수 있습니다. 해시 테이블과 달리 순서가 보장되어 같은 데이터 집합은 항상 같은 트리 구조를 만듭니다. + +2. **Patricia Trie가 해결하는 문제 (Trie의 한계):** + 일반 Trie는 공통 접두사가 긴 키들이 많으면 빈 노드가 과도하게 생겨 공간 낭비가 심합니다. Patricia Trie는 공통 접두사를 하나의 노드로 **압축(path compression)**하여 공간 효율성을 크게 향상시킵니다. 이더리움 주소는 40자리 hex로 매우 길어서 이 압축이 필수적입니다. + +3. **Merkle Patricia Trie가 해결하는 문제 (Patricia Trie의 한계):** + Patricia Trie만으로는 데이터의 무결성을 검증할 수 없습니다. Merkle Tree의 해시 연결 방식을 결합하여, 루트 해시(Root Hash) 하나로 전체 트리의 무결성을 증명할 수 있게 됩니다. Light Node가 전체 데이터 없이도 특정 데이터의 존재를 효율적으로 검증(Merkle Proof)할 수 있습니다. + + +--- + +## 문제 5: Eclipse Attack 방어 (단답형) + +Eclipse Attack은 공격자가 피해자 노드의 **모든 피어 연결**을 자신이 통제하는 노드로 바꾸는 공격입니다. + +1) 이 공격이 성공하면 피해자에게 **어떤 피해**가 발생할 수 있나요? +2) 개인 노드 운영자가 이 공격을 **방어**하기 위해 할 수 있는 행동은 무엇인가요? + +**답변:** +1) 가능한 피해: + - **이중 지불 공격**: 공격자가 피해자에게 가짜 트랜잭션(이미 다른 곳에서 사용된 ETH)을 보여주고, 피해자는 이를 유효하다고 믿습니다. + - **검열**: 피해자의 트랜잭션을 네트워크에 전파하지 않거나, 특정 트랜잭션을 숨길 수 있습니다. + - **셀피시 마이닝 지원**: 공격자가 만든 블록만 보여주어 피해자의 해시파워를 공격자의 체인에 낭비하게 합니다. + - **잘못된 체인 정보**: 피해자가 실제 정규 체인이 아닌 공격자의 포크를 따르게 됩니다. + +2) 방어 방법: + - **신뢰할 수 있는 노드를 수동으로 피어 목록에 추가**: 알려진 부트노드나 신뢰할 수 있는 피어를 고정(static peer)으로 설정합니다. + - **다양한 네트워크 경로 사용**: 여러 ISP나 VPN을 통해 연결하여 단일 경로 차단을 방지합니다. + - **피어 연결 수 모니터링**: 비정상적으로 같은 IP 대역에서 많은 연결이 들어오면 의심합니다. + - **피어 다양성 제한 설정**: 같은 /24 서브넷에서 연결되는 피어 수를 제한하여 하나의 공격자가 모든 슬롯을 차지하지 못하게 합니다. + + +--- + +## 문제 6: 노드 종류 선택 (단답형) + +친구가 이더리움 개발을 시작하려고 합니다. 다음 세 가지 상황에서 각각 어떤 노드 타입(Full, Light, Archive)을 추천하시겠습니까? **왜** 그 노드를 추천하는지도 설명하세요. + +**답변:** +1) 모바일 지갑 앱: + 추천 노드: **Light Node** + 이유: 모바일 기기는 저장 공간과 대역폭이 제한적입니다. Light Node는 블록 헤더만 저장하므로 몇 MB 수준이며, Merkle Proof를 통해 필요한 데이터를 검증합니다. 실시간 잔액 확인과 트랜잭션 전송에 충분합니다. + +2) 블록체인 데이터 분석 서비스: + 추천 노드: **Archive Node** + 이유: 데이터 분석에는 과거 모든 블록의 모든 상태(특정 블록 높이에서의 잔액, 컨트랙트 상태 등)가 필요합니다. Archive Node는 모든 역사적 상태를 보존하므로 "100번째 블록에서 이 계정의 잔액은?"같은 쿼리가 가능합니다. 수 TB의 저장 공간이 필요하지만 분석에는 필수입니다. + +3) 일반적인 dApp 백엔드: + 추천 노드: **Full Node** + 이유: dApp 백엔드는 현재 상태 조회, 트랜잭션 전송, 이벤트 모니터링 등이 주요 기능입니다. Full Node는 최신 상태와 최근 블록 데이터를 모두 보유하면서도 Archive Node보다 저장 공간이 적습니다(약 500GB~1TB). 과거 모든 상태가 필요 없다면 Full Node가 비용 대비 효율적입니다. + + +--- + +## 문제 7: useAccount Hook (빈칸 채우기) + +다음 코드의 빈칸을 채워서 지갑 연결 상태를 표시하는 컴포넌트를 완성하세요: + +```typescript +import { _________________ } from 'wagmi'; + +function WalletStatus() { + // TODO: useAccount hook에서 필요한 값들을 가져오세요 + const { _________________, _________________ } = useAccount(); + + if (!isConnected) { + return
지갑이 연결되지 않았습니다
; + } + + return ( +
+

연결된 주소: {address}

+
+ ); +} +``` + +**답변:** +```typescript +import { useAccount } from 'wagmi'; + +function WalletStatus() { + const { address, isConnected } = useAccount(); + + if (!isConnected) { + return
지갑이 연결되지 않았습니다
; + } + + return ( +
+

연결된 주소: {address}

+
+ ); +} +``` + +**왜 이렇게 작성했나요:** +`useAccount`는 wagmi에서 현재 연결된 지갑의 정보를 가져오는 Hook입니다. 주요 반환값: +- `address`: 연결된 지갑의 이더리움 주소 (`0x...` 형식). 연결되지 않으면 `undefined`. +- `isConnected`: 지갑 연결 여부를 나타내는 boolean. 연결되면 `true`. +- 그 외: `chain` (연결된 네트워크), `connector` (사용 중인 지갑 커넥터), `status` (연결 상태) 등도 제공합니다. +코드에서는 `isConnected`로 연결 여부를 먼저 확인하고, 연결된 경우에만 `address`를 표시합니다. + + +--- + +## 문제 8: useReadContract Hook (빈칸 채우기) + +다음 코드의 빈칸을 채워서 컨트랙트의 `getCount` 함수 결과를 화면에 표시하세요: + +```typescript +import { useReadContract } from 'wagmi'; + +const counterABI = [ + { + name: 'getCount', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'count', type: 'uint256' }], + }, +] as const; + +function CountDisplay() { + const { data, isLoading, error } = useReadContract({ + // TODO: 필요한 설정을 채우세요 + address: '0x1234...5678', + _________________, + _________________, + }); + + if (isLoading) return
로딩 중...
; + if (error) return
에러 발생
; + + return
현재 카운트: {_________________}
; +} +``` + +**답변:** +```typescript +function CountDisplay() { + const { data, isLoading, error } = useReadContract({ + address: '0x1234...5678', + abi: counterABI, + functionName: 'getCount', + }); + + if (isLoading) return
로딩 중...
; + if (error) return
에러 발생
; + + return
현재 카운트: {data?.toString()}
; +} +``` + +**왜 이렇게 작성했나요:** +`useReadContract`는 컨트랙트의 view/pure 함수를 호출하는 Hook입니다. 필수 설정: +- `abi`: 컨트랙트의 ABI (Application Binary Interface). 함수의 이름, 파라미터, 반환 타입 등을 정의합니다. 이것이 있어야 wagmi가 올바른 함수 시그니처를 인코딩할 수 있습니다. +- `functionName`: 호출할 함수 이름. ABI에 정의된 함수명과 일치해야 합니다. + +`data`를 표시할 때 `data?.toString()`을 사용하는 이유는, 컨트랙트에서 반환되는 uint256 값이 JavaScript의 `BigInt` 타입이기 때문입니다. BigInt는 직접 JSX에 렌더링할 수 없으므로 문자열로 변환해야 합니다. `?.`는 data가 undefined일 수 있으므로 옵셔널 체이닝을 사용합니다. + + +--- + +## 문제 9: useWriteContract 버그 (취약점 찾기) + +다음 코드에서 **문제점**을 찾고 수정하세요: + +```typescript +// BAD CODE - 문제점 찾기 +import { useWriteContract } from 'wagmi'; + +function IncrementButton() { + const { writeContract, isPending } = useWriteContract(); + + const handleClick = () => { + // 문제가 있는 코드 + writeContract({ + address: '0x1234...5678', + functionName: 'increment', + // abi가 없음! + }); + }; + + return ( + + ); +} +``` + +**1) 발견한 문제점:** +`writeContract` 호출에 `abi`가 빠져 있습니다. ABI는 wagmi에서 컨트랙트 함수를 호출할 때 필수 파라미터입니다. + + +**2) 왜 이것이 문제인가:** +ABI가 없으면 wagmi는 `increment` 함수의 시그니처(파라미터 타입, 반환 타입 등)를 알 수 없습니다. 함수를 호출하려면 함수 시그니처를 keccak256으로 해시하여 함수 셀렉터(4바이트)를 만들어야 하는데, ABI 없이는 이 과정이 불가능합니다. TypeScript 단계에서 타입 에러가 발생하거나, 런타임에서 인코딩 실패 에러가 발생합니다. + + +**3) 올바른 수정 방법:** +```typescript +import { useWriteContract } from 'wagmi'; + +const counterABI = [ + { + name: 'increment', + type: 'function', + stateMutability: 'nonpayable', + inputs: [], + outputs: [], + }, +] as const; + +function IncrementButton() { + const { writeContract, isPending } = useWriteContract(); + + const handleClick = () => { + writeContract({ + address: '0x1234...5678', + abi: counterABI, + functionName: 'increment', + }); + }; + + return ( + + ); +} +``` + +--- + +## 문제 10: 블록 연결 구조 (다이어그램 해석) + +```mermaid +graph LR + subgraph B0["제네시스 블록"] + H0["hash: 0xabc..."] + end + subgraph B1["블록 1"] + PH1["parent: 0xabc..."] + H1["hash: 0xdef..."] + end + subgraph B2["블록 2"] + PH2["parent: 0xdef..."] + H2["hash: 0x123..."] + end + subgraph B3["블록 3"] + PH3["parent: ???"] + H3["hash: 0x789..."] + end + + B0 --> B1 --> B2 --> B3 +``` + +**질문:** + +1) 블록 3의 `parent: ???` 에 들어갈 값은 무엇인가요? + +**답변:** `0x123...` — 블록 2의 해시입니다. 각 블록의 parentHash는 바로 직전 블록의 해시를 참조합니다. + + +2) 만약 블록 1의 내용이 수정되면, 블록 2와 블록 3에 **어떤 영향**이 있나요? 왜 그런가요? + +**답변:** 블록 1의 내용이 수정되면 블록 1의 해시(0xdef...)가 변합니다. 그러면 블록 2의 parentHash(0xdef...)가 더 이상 블록 1의 새 해시와 일치하지 않아 블록 2가 무효화됩니다. 블록 2를 수정하면 블록 2의 해시(0x123...)도 변하므로 블록 3의 parentHash와 불일치가 발생합니다. 결국 블록 1 이후의 모든 블록을 다시 계산해야 합니다. 이것이 블록체인이 과거 데이터 변조에 강한 이유입니다. + + +3) 제네시스 블록(블록 0)의 parentHash는 어떤 특별한 값을 가지나요? 왜 그런가요? + +**답변:** 제네시스 블록의 parentHash는 `0x0000...0000` (64개의 0, 32바이트 영값)입니다. 제네시스 블록은 체인의 첫 번째 블록이므로 참조할 이전 블록이 존재하지 않습니다. 따라서 규약상 영(zero) 해시를 parentHash로 사용하여 "이전 블록이 없음"을 나타냅니다. + + +--- + +## 문제 11: MPT 트리 구조 (다이어그램 해석) + +```mermaid +graph TD + ROOT["Root Hash: 0xfff..."] --> EXT1["Extension Node
path: 0a"] + ROOT --> EXT2["Extension Node
path: 0b"] + + EXT1 --> BRANCH["Branch Node
(16개 슬롯)"] + BRANCH --> LEAF1["Leaf: 계정 A
주소: 0a1234..."] + BRANCH --> LEAF2["Leaf: 계정 B
주소: 0a5678..."] + + EXT2 --> LEAF3["Leaf: 계정 C
주소: 0b9999..."] +``` + +**질문:** + +1) 계정 A와 계정 B가 같은 Branch Node 아래에 있는 이유는 무엇인가요? + +**답변:** 계정 A(0a1234...)와 계정 B(0a5678...)는 주소의 앞부분 `0a`가 동일합니다. Extension Node가 공통 접두사 `0a`를 처리하고, 그 이후 분기가 필요한 시점(A는 `1`, B는 `5`)에서 Branch Node가 사용됩니다. Branch Node는 16개의 hex nibble(0-f) 슬롯을 가지며, A는 슬롯 `1`로, B는 슬롯 `5`로 분기합니다. + + +2) Extension Node가 하는 역할은 무엇인가요? 없다면 어떤 문제가 생기나요? + +**답변:** Extension Node는 여러 키가 공유하는 공통 경로를 하나의 노드로 압축(path compression)합니다. Extension Node가 없다면, `0a`라는 2자리 경로를 표현하기 위해 2개의 Branch Node(첫째는 `0`, 둘째는 `a`)가 필요합니다. 이더리움 주소는 40자리 hex이므로 공통 접두사가 길 경우 수십 개의 불필요한 Branch Node가 생겨 트리의 깊이와 저장 공간이 크게 낭비됩니다. Extension Node 덕분에 트리가 훨씬 효율적으로 됩니다. + + +3) Root Hash만 알면 어떻게 특정 계정의 데이터 존재를 **증명**할 수 있나요? + +**답변:** Light Client는 Full Node에게 특정 계정에 대한 **Merkle Proof**를 요청합니다. 이 Proof는 Root에서 해당 Leaf까지의 경로상 모든 노드의 해시값입니다. 예를 들어 계정 A를 증명하려면: Root Hash → Extension Node(0a) 해시 → Branch Node 해시 → Leaf A 해시 경로를 받습니다. Light Client는 Leaf A의 데이터를 해시하고, 경로를 따라 올라가면서 각 해시를 계산하여 최종적으로 Root Hash와 일치하는지 확인합니다. 일치하면 해당 데이터가 전체 상태 트리에 실제로 포함되어 있음이 수학적으로 증명됩니다. 전체 상태 데이터 없이 경로 데이터(수 KB)만으로 검증이 가능합니다. + + +--- + +## 제출 전 체크리스트 + +- [x] 모든 문제에 답변을 작성했는가? +- [x] 객관식 문제: 정답 선택 **이유**를 설명했는가? +- [x] 단답형 문제: 2-3문장 이상으로 충분히 설명했는가? +- [x] 코드 문제: 완성된 코드와 **왜 그렇게 작성했는지** 설명했는가? +- [x] 다이어그램 문제: 각 질문에 논리적으로 답변했는가?