From 8f8793450201d637467d9c87ca0e43bb51ccbf4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=9E=AC?= <161708856+ghwo336@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:08:32 +0900 Subject: [PATCH 1/2] feat(week-04): complete week-04 assignment --- week-04/dev/my-dapp/app/layout.tsx | 57 +++ week-04/dev/my-dapp/app/page.tsx | 34 ++ .../dev/my-dapp/components/ContractReader.tsx | 36 ++ .../dev/my-dapp/components/ContractWriter.tsx | 56 +++ .../dev/my-dapp/components/EventListener.tsx | 88 ++++ .../dev/my-dapp/components/WalletConnect.tsx | 90 ++++ week-04/theory/quiz-04-solution.md | 456 ++++++++++++++++++ 7 files changed, 817 insertions(+) create mode 100644 week-04/dev/my-dapp/app/layout.tsx create mode 100644 week-04/dev/my-dapp/app/page.tsx create mode 100644 week-04/dev/my-dapp/components/ContractReader.tsx create mode 100644 week-04/dev/my-dapp/components/ContractWriter.tsx create mode 100644 week-04/dev/my-dapp/components/EventListener.tsx create mode 100644 week-04/dev/my-dapp/components/WalletConnect.tsx create mode 100644 week-04/theory/quiz-04-solution.md diff --git a/week-04/dev/my-dapp/app/layout.tsx b/week-04/dev/my-dapp/app/layout.tsx new file mode 100644 index 0000000..178df6a --- /dev/null +++ b/week-04/dev/my-dapp/app/layout.tsx @@ -0,0 +1,57 @@ +'use client'; + +// ============================================================ +// RainbowKit 스타일 import +// ============================================================ +// RainbowKit의 UI 컴포넌트가 제대로 표시되려면 +// 반드시 이 스타일시트를 import해야 합니다. +import '@rainbow-me/rainbowkit/styles.css'; + +import { RainbowKitProvider } from '@rainbow-me/rainbowkit'; +import { WagmiProvider } from 'wagmi'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; +import { config } from '@/config/wagmi'; + +// ============================================================ +// React Query 클라이언트 설정 +// ============================================================ +// wagmi v2는 내부적으로 TanStack Query를 사용합니다. +// 이 클라이언트가 데이터 캐싱, 리페칭, 동기화를 담당합니다. +const queryClient = new QueryClient(); + +// ============================================================ +// Root Layout +// ============================================================ +// Next.js App Router의 루트 레이아웃입니다. +// 모든 페이지가 이 레이아웃을 공유합니다. +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {/* ============================================================ + Provider 순서가 중요합니다! + ============================================================ + + WagmiProvider > QueryClientProvider > RainbowKitProvider + + 1. WagmiProvider: 가장 바깥. wagmi config를 전체 앱에 제공 + 2. QueryClientProvider: 데이터 페칭 상태 관리 + 3. RainbowKitProvider: 지갑 UI 및 연결 상태 관리 + + 순서가 바뀌면 "Cannot find WagmiContext" 같은 오류 발생! + ============================================================ */} + + + + {children} + + + + + + ); +} diff --git a/week-04/dev/my-dapp/app/page.tsx b/week-04/dev/my-dapp/app/page.tsx new file mode 100644 index 0000000..ef41b94 --- /dev/null +++ b/week-04/dev/my-dapp/app/page.tsx @@ -0,0 +1,34 @@ +import { WalletConnect } from '@/components/WalletConnect'; +import { ContractReader } from '@/components/ContractReader'; +import { ContractWriter } from '@/components/ContractWriter'; +import { EventListener } from '@/components/EventListener'; +// ============================================================ +// 메인 페이지 +// ============================================================ +// 이 페이지는 서버 컴포넌트입니다. +// 클라이언트 전용 기능(지갑 연결 등)은 WalletConnect 컴포넌트에서 처리합니다. +export default function Home() { + const contractAddress="0x4941Cd5D052b06c356aB0BFf90635D6A613E33C5" + return ( +
+

Bay-17th dApp

+ + {/* 지갑 연결 컴포넌트 */} + + + + + {/* ============================================================ + TODO: 여기에 컨트랙트 상호작용 컴포넌트를 추가하세요 + ============================================================ + + 예시: + - : 컨트랙트 상태 읽기 + - : 컨트랙트 함수 호출 + - : 이벤트 구독 및 표시 + + 참고: eth-materials/week-04/dev/wagmi-basics.md + ============================================================ */} +
+ ); +} diff --git a/week-04/dev/my-dapp/components/ContractReader.tsx b/week-04/dev/my-dapp/components/ContractReader.tsx new file mode 100644 index 0000000..05c3dde --- /dev/null +++ b/week-04/dev/my-dapp/components/ContractReader.tsx @@ -0,0 +1,36 @@ +'use client' +import { useReadContract } from 'wagmi' + +const counterAbi = [ + { + type: 'function', + name: 'getCount', + inputs: [], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + }, +] as const; + +type Props = { + contractAddress:`0x${string}`; +} + +export function ContractReader({contractAddress}:Props){ + const {data , isLoading , error} = useReadContract({ + address:contractAddress, + abi:counterAbi, + functionName:'getCount', + query:{ + refetchInterval:2_000, + } + }); + if (isLoading) return
로딩중....
+ if (error) return
에러 발생: {error.message}
+ + return ( +
+

Counter

+

Count: {data?.toString()}

+
+ ) +} diff --git a/week-04/dev/my-dapp/components/ContractWriter.tsx b/week-04/dev/my-dapp/components/ContractWriter.tsx new file mode 100644 index 0000000..760a226 --- /dev/null +++ b/week-04/dev/my-dapp/components/ContractWriter.tsx @@ -0,0 +1,56 @@ +'use client' +import { getContractAddress } from 'viem'; +import { useWriteContract , useWaitForTransactionReceipt } from 'wagmi'; + +const counterAbi = [ + { + type: 'function', + name: 'increment', + inputs: [], + stateMutability:'nonpayable', + outputs:[] + }, { + type: 'function', + name: 'decrement', + inputs: [], + stateMutability:'nonpayable', + outputs:[] + }, { + type: 'function', + name: 'reset', + inputs: [], + stateMutability:'nonpayable', + outputs:[] + } +] as const; +type Props = { + contractAddress:`0x${string}`; +} + +export function ContractWriter({contractAddress}:Props){ + const {data:hash,isPending,error,writeContract} = useWriteContract(); + const {isLoading:isConfirming,isSuccess} = useWaitForTransactionReceipt({hash}); + const handleWrite = (functionName: 'increment'|'decrement'|'reset') =>{ + writeContract({ + address:contractAddress, + abi:counterAbi, + functionName, + }); + } + return ( +
+

Counter Controls

+
+ + + +
+ {isPending &&
지갑 서명 대기 중 ...
} + {isConfirming &&
트랜잭션 확정 대기 중...
} + {isSuccess &&
처리 성공
} + {error &&
에러 발생
} + {hash && (
Tx Hash:{hash}
)} +
+ ) + +} \ No newline at end of file diff --git a/week-04/dev/my-dapp/components/EventListener.tsx b/week-04/dev/my-dapp/components/EventListener.tsx new file mode 100644 index 0000000..db9cf0c --- /dev/null +++ b/week-04/dev/my-dapp/components/EventListener.tsx @@ -0,0 +1,88 @@ +'use client' +import { usePublicClient } from 'wagmi' +import { useState, useEffect, useRef } from 'react' + +const counterAbi = [ + { type:'event', + name:'CountChanged', + inputs:[{name:'newCount',type:'uint256',indexed:false}] + } +] as const + +type Props = { + contractAddress:`0x${string}`; +} + +export function EventListener({contractAddress}:Props){ + const [events,setEvents] = useState([]); + const publicClient = usePublicClient(); + const lastBlockRef = useRef(); + + useEffect(()=>{ + if(!publicClient) return; + + // 과거 이벤트 로드 + const loadPastEvents = async ()=>{ + try { + const currentBlock = await publicClient.getBlockNumber(); + const fromBlock = currentBlock > 9n ? currentBlock - 9n : 0n; + const logs = await publicClient.getContractEvents({ + address: contractAddress, + abi: counterAbi, + eventName:'CountChanged', + fromBlock, + toBlock: currentBlock, + }); + if(logs.length > 0){ + const pastEvents = logs.map((log)=>`Count -> ${log.args.newCount}`).reverse(); + setEvents(pastEvents); + } + lastBlockRef.current = currentBlock; + } catch(e){ + console.error('Past events error:', e); + } + }; + loadPastEvents(); + + const poll = async ()=>{ + try { + const currentBlock = await publicClient.getBlockNumber(); + if(!lastBlockRef.current){ + lastBlockRef.current = currentBlock; + return; + } + if(currentBlock <= lastBlockRef.current) return; + + const logs = await publicClient.getContractEvents({ + address: contractAddress, + abi: counterAbi, + eventName:'CountChanged', + fromBlock: lastBlockRef.current + 1n, + toBlock: currentBlock, + }); + + if(logs.length > 0){ + const newEvents = logs.map((log)=>`Count -> ${log.args.newCount}`); + setEvents((prev)=>[...newEvents,...prev]); + } + lastBlockRef.current = currentBlock; + } catch(e){ + console.error('Poll error:', e); + } + }; + + const interval = setInterval(poll, 4_000); + return ()=> clearInterval(interval); + },[publicClient, contractAddress]); + + return( +
+

Event Logs

+ {events.length===0?( +

이벤트 대기중

+ ):(
    + {events.map((e,i)=>
  • {e}
  • )} +
)} +
+ ) +} diff --git a/week-04/dev/my-dapp/components/WalletConnect.tsx b/week-04/dev/my-dapp/components/WalletConnect.tsx new file mode 100644 index 0000000..3421442 --- /dev/null +++ b/week-04/dev/my-dapp/components/WalletConnect.tsx @@ -0,0 +1,90 @@ +'use client'; + +// ============================================================ +// WalletConnect 컴포넌트 +// ============================================================ +// RainbowKit의 ConnectButton과 wagmi의 useAccount를 활용하여 +// 지갑 연결 UI를 제공합니다. + +import { ConnectButton } from '@rainbow-me/rainbowkit'; +import { useAccount, useBalance } from 'wagmi'; + +export function WalletConnect() { + // ============================================================ + // useAccount Hook + // ============================================================ + // 연결된 지갑의 정보를 가져옵니다. + // - address: 지갑 주소 (0x...) + // - isConnected: 연결 상태 (boolean) + // - isConnecting: 연결 중 상태 (boolean) + // - isDisconnected: 연결 해제 상태 (boolean) + const { address, isConnected } = useAccount(); + + // ============================================================ + // useBalance Hook + // ============================================================ + // 지갑의 ETH 잔액을 조회합니다. + // - data: { formatted, symbol, decimals, value } + // - isLoading: 로딩 상태 + // - isError: 에러 상태 + // + // enabled 옵션: isConnected가 true일 때만 쿼리 실행 + const { data: balance, isLoading: isBalanceLoading } = useBalance({ + address: address, + query: { + enabled: isConnected, + }, + }); + + return ( +
+ {/* ============================================================ + RainbowKit의 ConnectButton + ============================================================ + 지갑 연결 UI를 자동으로 제공합니다: + - 연결되지 않음: "Connect Wallet" 버튼 + - 연결됨: 주소, 잔액, 네트워크 표시 + 드롭다운 메뉴 + + 커스터마이징: + - showBalance={false} : 잔액 숨기기 + - chainStatus="icon" : 체인을 아이콘으로만 표시 + - accountStatus="avatar" : 주소를 아바타로만 표시 + ============================================================ */} + + + {/* ============================================================ + 연결된 지갑 정보 표시 + ============================================================ + isConnected가 true일 때만 렌더링됩니다. + 이 섹션을 커스터마이징하여 원하는 정보를 표시하세요. + ============================================================ */} + {isConnected && ( +
+

연결된 지갑

+ + {/* 지갑 주소 */} +

+ 주소: {address?.slice(0, 6)}...{address?.slice(-4)} +

+ + {/* ETH 잔액 */} +

+ 잔액: {isBalanceLoading + ? '로딩 중...' + : `${balance?.formatted ?? '0'} ${balance?.symbol ?? 'ETH'}` + } +

+ + {/* ============================================================ + TODO: 추가 기능 구현 + ============================================================ + - 컨트랙트 상태 읽기 (useReadContract) + - 컨트랙트 함수 호출 (useWriteContract) + - 트랜잭션 히스토리 표시 + - 토큰 잔액 표시 (ERC20) + ============================================================ */} +
+ )} +
+ ); +} diff --git a/week-04/theory/quiz-04-solution.md b/week-04/theory/quiz-04-solution.md new file mode 100644 index 0000000..f2fee8d --- /dev/null +++ b/week-04/theory/quiz-04-solution.md @@ -0,0 +1,456 @@ +# Week 4 Quiz: Network/Block + wagmi + +> **제출 방법:** 이 파일을 복사하여 답변을 작성한 후, PR로 제출하세요. +> **평가 기준:** 개념 이해도 중심 - 문법 오류보다 논리적 설명을 중시합니다. + +--- + +## 문제 1: 블록 헤더 필드 (객관식) + +다음 상황을 고려하세요: + +``` +블록 100의 해시: 0xabc123... +블록 101의 해시: 0xdef456... +``` + +블록 101의 `parentHash` 필드에는 어떤 값이 저장되어 있나요? 그리고 **왜** 이런 방식으로 연결하나요? + +**보기:** +A) 0xdef456... - 자기 자신의 해시를 저장하여 무결성을 보장한다 +B) 0xabc123... - 이전 블록의 해시를 저장하여 체인 연결과 불변성을 보장한다 +C) 블록 번호 100 - 숫자로 순서를 추적한다 +D) 빈 값 - 헤더에는 해시가 저장되지 않는다 + +**답변:** + +B 이전 블록의 해시를 포함시켜 과거 블록이 변경되면 이후 블록 해시가 연쇄적으로 변하게 되어 체인의 불변성을 보장한다. +다른 값들은 이전 블록과 연결 되어있다는것을 보장하지 않는다. + + +--- + +## 문제 2: MPT 목적 (객관식) + +이더리움에서 Merkle Patricia Trie(MPT)를 사용하는 **가장 중요한 이유**는 무엇인가요? + +**보기:** +A) 데이터를 암호화하여 외부에서 읽을 수 없게 한다 +B) 트랜잭션 처리 속도를 10배 이상 높인다 +C) 전체 데이터 없이도 특정 데이터의 존재와 정확성을 효율적으로 증명한다 +D) 블록 크기를 줄여서 저장 공간을 절약한다 + +**답변:** + +C, MPT는 블록헤더의 stateRoot를 통해 Light Node가 전체 상태를 저장하지 않고도 특정 데이터의 존재와 무결성을 검증 하기 위해 사용된다. + + +--- + +## 문제 3: 체인 연결과 보안 (객관식) + +공격자가 블록 50의 트랜잭션을 수정하려고 합니다. 현재 체인의 최신 블록은 100입니다. 이 공격이 **왜** 어려운가요? + +**보기:** +A) 블록 50은 너무 오래되어서 시스템에서 접근할 수 없다 +B) 블록 50을 수정하면 해시가 바뀌고, 블록 51부터 100까지 모든 블록의 parentHash가 불일치하게 된다 +C) 블록 50은 이미 암호화되어 있어서 복호화 키가 필요하다 +D) 네트워크 관리자만 과거 블록을 수정할 수 있다 + +**답변:** + +B, 블록 50을 수정하면 해시가 바뀌고, 이후 블록의 parentHash가 연쇄적으로 무효화 된다. +공격자는 51~100까지 모든 블록을 다시 채굴하고 동시에 네트워크의 정직한 체인을 추월해야 하므로 현실적으로 매우 어렵다. + +--- + +## 문제 4: MPT 진화 과정 (단답형) + +MPT(Merkle Patricia Trie)는 세 가지 자료구조의 장점을 결합한 것입니다: +1. **Trie** -> 2. **Patricia Trie** -> 3. **Merkle Patricia Trie** + +**왜** 각 단계의 발전이 필요했나요? 각 단계가 해결하는 문제를 간단히 설명하세요. + +**답변:** + +1. Trie는 공통 prefix는 하나의 경로로 공유하게끔하여 키 기반 검색을 효율적으로 하게 해주었다. +2. Patricia는 단일 경로를 하나의 노드로 압축하여 단일 자식 노드가 길게 이어지면 노드 낭비가 발생하는 것을 해결 +3. Merkle Patricia는 각 노드에 해시를 추가 하고 부모 노드는 자식 노드들의 해시를 포함해 해시하여 데이터의 무결성을 증명할 수 있음 이를 통해 Light Node가 전체 데이터를 저장하지 않고도 상태를 검증할 수 있게됨 + +--- + +## 문제 5: Eclipse Attack 방어 (단답형) + +Eclipse Attack은 공격자가 피해자 노드의 **모든 피어 연결**을 자신이 통제하는 노드로 바꾸는 공격입니다. + +1) 이 공격이 성공하면 피해자에게 **어떤 피해**가 발생할 수 있나요? +2) 개인 노드 운영자가 이 공격을 **방어**하기 위해 할 수 있는 행동은 무엇인가요? + +**답변:** + +1. 가능한 피해 +- 공격자가 가짜 체인을 보여줘 오래된 체인에 머무르게 할 수 있음 +- 채굴자를 고립시켜 블록 전파 지연을 유도한다. +- 공격자가 특정 트랜잭션을 전달하지 않는다. + +2. 방어방법 +- 다양한 IP 대역 피어를 연결한다. (계속해서 같은 피어만 연결하지 않기) +- 피어 수를 충분히 유지한다. (연결 수 적으면 공격 쉬움) + + +--- + +## 문제 6: 노드 종류 선택 (단답형) + +친구가 이더리움 개발을 시작하려고 합니다. 다음 세 가지 상황에서 각각 어떤 노드 타입(Full, Light, Archive)을 추천하시겠습니까? **왜** 그 노드를 추천하는지도 설명하세요. + +1) 모바일 지갑 앱 개발 +2) 블록체인 데이터 분석 서비스 개발 +3) 일반적인 dApp 백엔드 개발 + +**답변:** + +1) 모바일 지갑 앱: + 추천 노드: Light 노드 + 이유: 모바일 환경은 저장 공간과 메모리 자원이 제한적이므로 풀 노드를 돌리긴 어렵다. 라이트 노드는 블록 헤더와 Merkle Proof를 통해 전체 데이터를 저장하지 않고도 계정 잔액과 트랜잭션 상태를 검증할 수 있어 모바일 지갑에 적합하다. + +2) 블록체인 데이터 분석: + 추천 노드: Archive 노드 + 이유:데이터 분석 서비스는 과거 특정 블록 시점의 계정 상태나 스마트 컨트랙트 storage 값을 조회해야 하는 경우가 많다. 아카이브 노드는 모든 과거 상태를 보존하므로 히스토리 데이터 분석, 잔액 변화 추적, 온체인 통계 계산에 좋다. + +3) dApp 백엔드: + 추천 노드: Full 노드 + 이유: 일반적인 dApp 백엔드는 최신상태 조회, 트랜잭션 전송, 이벤트 로그 구독 등 현재 체인 상태를 기반으로 동작한다. 과거 모든 상태는 필요 없지만, 최신 상태를 독립적으로 검증하고 안정적으로 RPC 요청을 처리하기 위해 풀노드가 적절하다. + + +--- + +## 문제 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() { + // TODO: useAccount hook에서 필요한 값들을 가져오세요 + const { address, isConnected } = useAccount(); + + if (!isConnected) { + return
지갑이 연결되지 않았습니다
; + } + + return ( +
+

연결된 주소: {address}

+
+ ); +} + +``` + +**왜 이렇게 작성했나요:** + +useAccount 는 wagmi 에서 제공하는 리액트 훅으로 현재 연결된 지갑의 계정상태를 관리함 +이 훅을 통해 지갑 주소, 연결 여부등의 상태를 리액트 컴포넌트에서 쉽게 사용가능 +- address: 현재 연결된 지갑의 address를 나타냄 +- isConnected: 지갑의 연결상태를 나타내는 boolean 값 + +isConnected 가 true가 되면 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 +// 완성된 코드를 여기에 작성하세요 +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', + abi: counterABI, + functionName:'getCount', + }); + + if (isLoading) return
로딩 중...
; + if (error) return
에러 발생
; + + return
현재 카운트: {data?.toString()}
; +} + +``` + +**왜 이렇게 작성했나요:** + +useReadContract는 컨트랙트의 값을 읽어오는 훅으로 +contract의 주소가 필요하고 +abi는 컨트랙트 인터페이스를 설명하는 메타데이터이다. +functionName은 호출할 컨트랙트 함수의 이름이다. +data를 표시할때는 로딩일때는 로딩 error일 때는 error를 표시해주게끔하고 +data가 비었을때를 감안하여 ? 를 사용하여 runtime error을 방지한다. + + +--- + +## 문제 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) 발견한 문제점:** + +abi 가 정의되지 않았다. + + + +**2) 왜 이것이 문제인가:** + +abi는 컨트랙트 함수의 인터페이스 정의로, +프론트엔드가 함수 호출을 인코딩하고 결과를 디코딩하기 위해 필요하다. + + +**3) 올바른 수정 방법:** +```typescript +// GOOD CODE - 수정된 버전을 작성하세요 +import { useWriteContract } from 'wagmi'; + +function IncrementButton() { + const { writeContract, isPending } = useWriteContract(); + const counterAbi = [ + { + type: 'function', + name:'increment', + inputs: [], + stateMutability:'nonpayable', + outputs:[] + } + ] as const; + + const handleClick = () => { + // 문제가 있는 코드 + writeContract({ + address: '0x1234...5678', + functionName: 'increment', + abi:counterAbi + }); + }; + + 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) 만약 블록 1의 내용이 수정되면, 블록 2와 블록 3에 **어떤 영향**이 있나요? 왜 그런가요? + 블록1의 내용이 수정되면 블록1의 해더가 바뀌어 H1 의 해시값과 B2의 PH2의 해시값이 다르게 된다. 그래서 B2의 PH2 값을 수정하면 B2의 H2의 해시값이 또 바뀌게 되어 B3의 PH3의 해시값도 바꿔주어야한다. + +3) 제네시스 블록(블록 0)의 parentHash는 어떤 특별한 값을 가지나요? 왜 그런가요? + 0x000...000 제네시스 블록은 부모 블록이 없기때문에 부모블록 값을 0 으로 정의해준다. + +--- + +## 문제 11: MPT 트리 구조 (다이어그램 해석) + +다음 다이어그램은 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와 계정 B가 모두 주소가 0a로 시작하기 때문이다. + + +2) Extension Node가 하는 역할은 무엇인가요? 없다면 어떤 문제가 생기나요? + 주소가 0a1234,0a1235 가 있다고 가정할 때 extention node는 path=0a123 이런식으로 경로를 압축한다. 이렇게 하지않으면 0->a->1->2->3>4,5로 분기 이런식으로 depth가 깊어져 노드 수가 많아지고 storage의 효율이 안좋아진다, + + +3) Root Hash만 알면 어떻게 특정 계정의 데이터 존재를 **증명**할 수 있나요? (Light Client 관점에서) +서버가 Extention Node, Branch Node,Leaf Node 만 보내주면 +client는 Leaf Hash 계산 -> Branch Hash 계산 -> Extention Hash 계산 ->Root Hash 계산 한 후 계산된 Root Hash와 블록에 있는 Root Hash를 비교해 증명할 수 있다. + + +--- + +## 제출 전 체크리스트 + +- [ ] 모든 문제에 답변을 작성했는가? +- [ ] 객관식 문제: 정답 선택 **이유**를 설명했는가? +- [ ] 단답형 문제: 2-3문장 이상으로 충분히 설명했는가? +- [ ] 코드 문제: 완성된 코드와 **왜 그렇게 작성했는지** 설명했는가? +- [ ] 다이어그램 문제: 각 질문에 논리적으로 답변했는가? From 20271c1bddd953c0979ee8b40869fa7ebc67b8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=9E=AC?= <161708856+ghwo336@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:10:35 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20solution=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week-04/theory/quiz-04-solution.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/week-04/theory/quiz-04-solution.md b/week-04/theory/quiz-04-solution.md index f2fee8d..748d4b2 100644 --- a/week-04/theory/quiz-04-solution.md +++ b/week-04/theory/quiz-04-solution.md @@ -449,8 +449,8 @@ client는 Leaf Hash 계산 -> Branch Hash 계산 -> Extention Hash 계산 ->Root ## 제출 전 체크리스트 -- [ ] 모든 문제에 답변을 작성했는가? -- [ ] 객관식 문제: 정답 선택 **이유**를 설명했는가? -- [ ] 단답형 문제: 2-3문장 이상으로 충분히 설명했는가? -- [ ] 코드 문제: 완성된 코드와 **왜 그렇게 작성했는지** 설명했는가? -- [ ] 다이어그램 문제: 각 질문에 논리적으로 답변했는가? +- [x] 모든 문제에 답변을 작성했는가? +- [x] 객관식 문제: 정답 선택 **이유**를 설명했는가? +- [x] 단답형 문제: 2-3문장 이상으로 충분히 설명했는가? +- [x] 코드 문제: 완성된 코드와 **왜 그렇게 작성했는지** 설명했는가? +- [x] 다이어그램 문제: 각 질문에 논리적으로 답변했는가?