<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Primrose</title>
        <link>https://paragraph.com/@primrose</link>
        <description>Smart Contract Developer, Web3 Backend Developer</description>
        <lastBuildDate>Sat, 18 Apr 2026 17:11:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Primrose</title>
            <url>https://storage.googleapis.com/papyrus_images/43f82c1ebcee3f424fb0362f7296b95b7badfa8b193fdb09532da31dbe5dfcce.png</url>
            <link>https://paragraph.com/@primrose</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[GORM+MySQL Connection Pool]]></title>
            <link>https://paragraph.com/@primrose/gorm-mysql-connection-pool</link>
            <guid>6yuYnBVS4QIkUBDrG6xA</guid>
            <pubDate>Tue, 12 Mar 2024 12:39:18 GMT</pubDate>
            <description><![CDATA[GORM 의 Connection pool회사에서 마주친 에러$ [mysql] 2024/03/11 18:37:09 packets.go:122: closing bad idle connection: unexpected read from socket $ [mysql] 2024/03/11 18:37:09 packets.go:122: closing bad idle connection: unexpected read from socket Batch 처리를 요하는 작업들이 모여있는 인스턴스에서 발생했다. 비즈니스 로직 자체에는 문제가 없었고, 커넥션 관련 설정도 디폴트로 되어있는 상황이었다. 사실 서비스 전체적으로 보면 MySQL 자체를 많이 쓰는 상황은 아니었고, 동시에 실행되는 job 이 MySQL 기반이었어서 커넥션 관련 문제로 좁혀놓고 디버깅을 시작했다. 이 과정에서 새롭게 알게된 사실에 대한 기록.MaxLifeTimegorm 은 sql 패키지의 DB 객체를 wrapping 하고 있다....]]></description>
            <content:encoded><![CDATA[<h1 id="h-gorm-connection-pool" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">GORM 의 Connection pool</h1><p>회사에서 마주친 에러</p><pre data-type="codeBlock" text="$ [mysql] 2024/03/11 18:37:09 packets.go:122: closing bad idle connection: unexpected read from socket
$ [mysql] 2024/03/11 18:37:09 packets.go:122: closing bad idle connection: unexpected read from socket
"><code>$ [mysql] <span class="hljs-number">2024</span><span class="hljs-operator">/</span>03<span class="hljs-operator">/</span><span class="hljs-number">11</span> <span class="hljs-number">18</span>:<span class="hljs-number">37</span>:09 packets.go:<span class="hljs-number">122</span>: closing bad idle connection: unexpected read <span class="hljs-keyword">from</span> socket
$ [mysql] <span class="hljs-number">2024</span><span class="hljs-operator">/</span>03<span class="hljs-operator">/</span><span class="hljs-number">11</span> <span class="hljs-number">18</span>:<span class="hljs-number">37</span>:09 packets.go:<span class="hljs-number">122</span>: closing bad idle connection: unexpected read <span class="hljs-keyword">from</span> socket
</code></pre><p>Batch 처리를 요하는 작업들이 모여있는 인스턴스에서 발생했다.</p><p>비즈니스 로직 자체에는 문제가 없었고, 커넥션 관련 설정도 디폴트로 되어있는 상황이었다.</p><p>사실 서비스 전체적으로 보면 MySQL 자체를 많이 쓰는 상황은 아니었고, 동시에 실행되는 job 이 MySQL 기반이었어서 커넥션 관련 문제로 좁혀놓고 디버깅을 시작했다.</p><p>이 과정에서 새롭게 알게된 사실에 대한 기록.</p><h3 id="h-maxlifetime" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">MaxLifeTime</h3><p>gorm 은 sql 패키지의 <code>DB</code> 객체를 wrapping 하고 있다.</p><p><code>sql.DB</code> 구조체에는 Stats 라는 메소드가 있다.</p><pre data-type="codeBlock" text="var db := &amp;sql.DB{}
        
stats := db.Stats() // stats ?
"><code><span class="hljs-keyword">var</span> db :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>sql.DB{}
        
stats :<span class="hljs-operator">=</span> db.Stats() <span class="hljs-comment">// stats ?</span>
</code></pre><p>Stats 구조체는 아래와 같이 생겼다.</p><pre data-type="codeBlock" text="// DBStats contains database statistics.
type DBStats struct {
    MaxOpenConnections int // Maximum number of open connections to the database.

    // Pool Status
    OpenConnections int // The number of established connections both in use and idle.
    InUse           int // The number of connections currently in use.
    Idle            int // The number of idle connections.

    // Counters
    WaitCount         int64         // The total number of connections waited for.
    WaitDuration      time.Duration // The total time blocked waiting for a new connection.
    MaxIdleClosed     int64         // The total number of connections closed due to SetMaxIdleConns.
    MaxIdleTimeClosed int64         // The total number of connections closed due to SetConnMaxIdleTime.
    MaxLifetimeClosed int64         // The total number of connections closed due to SetConnMaxLifetime.
}
"><code><span class="hljs-comment">// DBStats contains database statistics.</span>
<span class="hljs-keyword">type</span> DBStats <span class="hljs-keyword">struct</span> {
    MaxOpenConnections <span class="hljs-keyword">int</span> <span class="hljs-comment">// Maximum number of open connections to the database.</span>

    <span class="hljs-comment">// Pool Status</span>
    OpenConnections <span class="hljs-keyword">int</span> <span class="hljs-comment">// The number of established connections both in use and idle.</span>
    InUse           <span class="hljs-keyword">int</span> <span class="hljs-comment">// The number of connections currently in use.</span>
    Idle            <span class="hljs-keyword">int</span> <span class="hljs-comment">// The number of idle connections.</span>

    <span class="hljs-comment">// Counters</span>
    WaitCount         <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections waited for.</span>
    WaitDuration      time.Duration <span class="hljs-comment">// The total time blocked waiting for a new connection.</span>
    MaxIdleClosed     <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections closed due to SetMaxIdleConns.</span>
    MaxIdleTimeClosed <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections closed due to SetConnMaxIdleTime.</span>
    MaxLifetimeClosed <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections closed due to SetConnMaxLifetime.</span>
}
</code></pre><p>위에서 부터 읽어보자.</p><ul><li><p>최대 커넥션 개수</p></li><li><p>현재 열려있는 커넥션 개수</p></li><li><p>사용중인 커넥션 개수</p></li><li><p>유휴 커넥션 개수</p></li><li><p>커넥션을 대기하는 개수</p></li><li><p>총 대기 시간</p></li><li><p><code>SetMaxIdleConns</code> 로 인해 닫힌 유휴 커넥션 개수</p></li><li><p><code>SetConnMaxIdleTime</code> 로 인해 닫힌 유휴 커넥션 개수</p></li><li><p><code>SetConnMaxLifetime</code> 로 인해 닫힌 커넥션 개수</p></li></ul><p>go 의 <code>*sql.DB</code> 구조체에서는 아래와 같이 설정들을 커스터마이징 할 수 있다.</p><pre data-type="codeBlock" text="// 값은 예시 
db.SetConnMaxLifetime(time.Hour)
db.SetMaxIdleConns(100)
db.SetConnMaxIdleTime(time.Hour)
db.SetMaxOpenConns(100)
"><code><span class="hljs-comment">// 값은 예시 </span>
db.SetConnMaxLifetime(time.Hour)
db.SetMaxIdleConns(<span class="hljs-number">100</span>)
db.SetConnMaxIdleTime(time.Hour)
db.SetMaxOpenConns(<span class="hljs-number">100</span>)
</code></pre><p>참고로 기본값은 0으로 들어가는데, 이 경우 제한이 없다는 말이다.</p><blockquote><p>물론 제한이 없다고해서 커넥션을 무한대로 만들어낼수는 없다. 또한 어지간한 경우에는 비즈니스 로직을 먼저 검토해보는 것이 좋다.</p></blockquote><p>GORM 에서는 아래와 같이 커스터마이징 할 것을 권고하고 있다.</p><pre data-type="codeBlock" text="// Get generic database object sql.DB to use its functions
sqlDB, err := db.DB()
// SetMaxIdleConns sets the maximum number of connections in the idle connection 
pool.sqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns sets the maximum number of open connections to the 
database.sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
sqlDB.SetConnMaxLifetime(time.Hour)
"><code><span class="hljs-comment">// Get generic database object sql.DB to use its functions</span>
sqlDB, err :<span class="hljs-operator">=</span> db.DB()
<span class="hljs-comment">// SetMaxIdleConns sets the maximum number of connections in the idle connection </span>
pool.sqlDB.SetMaxIdleConns(<span class="hljs-number">10</span>)
<span class="hljs-comment">// SetMaxOpenConns sets the maximum number of open connections to the </span>
database.sqlDB.SetMaxOpenConns(<span class="hljs-number">100</span>)
<span class="hljs-comment">// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.</span>
sqlDB.SetConnMaxLifetime(time.Hour)
</code></pre><p>위에서 언급했듯이, 커넥션 관련 문제로 보고 모니터링을 시작했다.</p><p>주기적으로 Stats 를 찍어봤을때 MaxConn 이 많아도 20을 넘어가지 않았고, 특이사항으로 보기에는 어려웠다.</p><p>다만 특정 작업이 실행될때마다 OpenConn 이 늘어나고 줄어들지 않는 문제가 발견되었다.</p><p>이유는 다음과 같았다.</p><pre data-type="codeBlock" text="var db := &amp;gorm.DB{}
    
    
db.Transaction(func(tx *gorm.DB) error {		
    db.Where(&quot;~~~&quot;).Find(&amp;Something{})
})
"><code><span class="hljs-keyword">var</span> db :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>gorm.DB{}
    
    
db.Transaction(func(<span class="hljs-built_in">tx</span> <span class="hljs-operator">*</span>gorm.DB) <span class="hljs-function"><span class="hljs-keyword">error</span> </span>{		
    db.Where(<span class="hljs-string">"~~~"</span>).Find(<span class="hljs-operator">&#x26;</span>Something{})
})
</code></pre><p>트랜잭션 안에서 트랜잭션 대신 다른 커넥션을 계속해서 집어오는 레거시가 있었다.</p><p>이러한 Job 이 실행되고 나면 커넥션 개수가 하나 늘어나는데, 이런 작업이 몇 개가 있다보니 일종의 커넥션 누수가 발생한 것으로 추정했다.</p><p>특이한 것은 강제로 재실행을 했을때 SavePoint 로그가 찍히는 것도 아니고, 에러가 찍힌 뒤에 실행이 되지 않는 경우와 실행이 되는 경우 등 불규칙적인 행태를 보인다는 것이다.</p><p>우선 해당 레거시를 모두 수정하고, <code>MaxConnLifetime</code> 을 한 시간으로 설정해서 계속해서 좀비 커넥션이 계속해서 살아남는 것을 방지했다.</p><p>아직 작업은 못했지만 추후 커넥션 풀에서 하나씩 폴링해서 받아오는 구조로 변경을 고려중이다.</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">참고 링크</h2><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/go-sql-driver/mysql/issues/1120">https://github.com/go-sql-driver/mysql/issues/1120</a> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gorm.io/docs/generic_interface.html">https://gorm.io/docs/generic_interface.html</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Ethereum Cancun Upgrade Explained]]></title>
            <link>https://paragraph.com/@primrose/ethereum-cancun-upgrade-explained</link>
            <guid>DhlmqH9gu61Nw9sOXVRX</guid>
            <pubDate>Tue, 12 Mar 2024 12:37:09 GMT</pubDate>
            <description><![CDATA[Ethereum Cancun Upgrade ExplainedEthereum Cancun-Deneb(Dencun) 업그레이드는 2024년 3월 13일에 적용된다. 무엇을 위한 업그레이드인지 살펴보자.… Ethereum 네트워크의 확장성, 보안 및 유용성을 향상시키기 위한 중요한 하드 포크 업그레이드 세트입니다. 이 업그레이드는 상하이 업그레이드와 같은 이전 업그레이드의 성공을 기반으로 하는 이더리움의 지속적인 발전의 일부입니다. Dencun 업그레이드는 실행 계층(Cancun)과 합의 계층(Deneb) 모두에 대한 변경으로 구성되며 네트워크 기능 최적화를 목표로 하는 일련의 Ethereum 개선 제안(EIP)을 도입합니다.요지는 확장성 + 보안 및 유용성의 향상이라고 볼 수 있겠다. 어떻게 해서 이 목표를 달성했을까?proto-dankshardingproto-danksharding 기술(EIP-4844)을 활용해 체인을 더 작은 Blob 으로 분할하는 것이라고 한다. 이를 통해 ...]]></description>
            <content:encoded><![CDATA[<h1 id="h-ethereum-cancun-upgrade-explained" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Ethereum Cancun Upgrade Explained</strong></h1><p>Ethereum Cancun-Deneb(Dencun) 업그레이드는 2024년 3월 13일에 적용된다.</p><p>무엇을 위한 업그레이드인지 살펴보자.</p><blockquote><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.datawallet.com/crypto/ethereum-cancun-upgrade-explained">… Ethereum 네트워크의 확장성, 보안 및 유용성을 향상시키기 위한 중요한 하드 포크 업그레이드 세트입니다. 이 업그레이드는 상하이 업그레이드와 같은 이전 업그레이드의 성공을 기반으로 하는 이더리움의 지속적인 발전의 일부입니다. Dencun 업그레이드는 실행 계층(Cancun)과 합의 계층(Deneb) 모두에 대한 변경으로 구성되며 네트워크 기능 최적화를 목표로 하는 일련의 Ethereum 개선 제안(EIP)을 도입합니다.</a></p></blockquote><p>요지는 확장성 + 보안 및 유용성의 향상이라고 볼 수 있겠다.</p><p>어떻게 해서 이 목표를 달성했을까?</p><h3 id="h-proto-danksharding" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">proto-danksharding</h3><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/roadmap/danksharding/">proto-danksharding</a> 기술(EIP-4844)을 활용해 체인을 더 작은 Blob 으로 분할하는 것이라고 한다. 이를 통해 Transaction 의 병렬 처리가 가능해지고, 가스비가 절감된다고 한다.</p><p>danksharding 은, 롤업이 더 저렴한 데이터를 블록에 추가하는 방법이라고 한다.</p><p>롤업에는 짧은 시간 동안만 데이터가 필요하더라도 모든 Ethereum 노드에서 처리되고 영원히 체인에 저장되기 때문에 비용이 많이 든다.</p><p>Proto-Danksharding은 블록에 전송되고 첨부될 수 있는 데이터 blob을 도입한다.</p><p>Blob의 데이터는 EVM에 액세스할 수 없으며 고정된 기간(현재 4096 에포크 or 약 18일) 후에 자동으로 삭제된다.</p><p>이는 롤업이 데이터를 훨씬 더 저렴하게 보낼 수 있고 사용자에게 비용 절감 효과를 전달할 수 있음이다.</p><p>정리하자면, Blob은 EVM에 액세스 할 수 없고, 조금 이따가 사라지는 데이터라는 뜻이다.</p><p>롤업은 오프체인에서 트랜잭션을 일괄 처리한 다음 결과를 이더리움에 게시하여 이더리움을 확장한다.</p><p>롤업은 기본적으로 1)데이터와 2)실행 확인 의 두 부분으로 구성된다.</p><p>데이터는 이더리움에 게시되는 상태 변경을 생성하기 위해 처리되는 전체 트랜잭션 시퀀스다.</p><p>실행 확인은 제안된 상태 변경이 올바른지 확인하기 위해 밸리데이터가 트랜잭션을 다시 실행하는 것이다.</p><p>실행 확인을 수행하려면 누구나 다운로드하고 확인할 수 있을 만큼 오랫동안 거래 데이터를 사용할 수 있어야 한다.</p><p>그러나 영원히 있을 필요는 없기 때문에 이번 업데이트는 의미가 있다.</p><p>이 외에도 다양한 EIP 도입으로 가스 효율성 향상, 거래 비용 절감을 목표로 하고 있다.</p><ul><li><p>EIP-1559(수수료 시장 변화)</p></li><li><p>EIP-2929(상태 접근 가스 비용 증가)</p></li><li><p>EIP-2537(BLS 곡선 연산)</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">참조 링크</h2><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/roadmap/danksharding/">https://ethereum.org/en/roadmap/danksharding/</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.datawallet.com/crypto/ethereum-cancun-upgrade-explained">https://www.datawallet.com/crypto/ethereum-cancun-upgrade-explained</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Ethereum Beacon chain]]></title>
            <link>https://paragraph.com/@primrose/ethereum-beacon-chain</link>
            <guid>hzekxhjeigRpgENSNkBm</guid>
            <pubDate>Sun, 21 Jan 2024 07:20:13 GMT</pubDate>
            <description><![CDATA[Beacon chain이더리움은 확장성 문제를 해결하기 위해 샤딩과 POS를 도입하기로 했다. 샤딩이란, 데이터베이스를 분할해서 데이터베이스의 부하와 체인의 지나친 중앙화를 막는 전략이다. POS란 지분 증명으로, 기존 POW와는 다르게 속도면에서 빠르다. POW는 컴퓨팅 자원을 소모해야만 합의 과정에 참여할 수 있지만, POS는 가지고 있는 ETH로 합의 과정에 참여할 수 있기 때문이다. 이더리움의 비콘 체인은 POS가 적용된 블록체인이다. 기존의 POW 가 아닌 이더리움 2.0을 위한 POS 가 적용된 블록체인이다. 비콘체인은 참여자들을 관리하는데, 어떤 검증자가 블록을 제안해야 할지, 위원회가 되어 블록을 증명해야 하는지 등의 역할을 한다.그림처럼 POW로 블록을 생성하는 메인 체인과 평행하게, POS 로 블록을 생성한다. 그러다 The Merge 업그레이드로 POW 가 아닌 POS 로 Mainnet 의 TX 처리를 시작하면서 블록 전파 및 합의 로직까지 담당하게 되었다....]]></description>
            <content:encoded><![CDATA[<h1 id="h-beacon-chain" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Beacon chain</h1><p>이더리움은 확장성 문제를 해결하기 위해 샤딩과 POS를 도입하기로 했다.</p><p>샤딩이란, 데이터베이스를 분할해서 데이터베이스의 부하와 체인의 지나친 중앙화를 막는 전략이다.</p><p>POS란 지분 증명으로, 기존 POW와는 다르게 속도면에서 빠르다.</p><p>POW는 컴퓨팅 자원을 소모해야만 합의 과정에 참여할 수 있지만, POS는 가지고 있는 ETH로 합의 과정에 참여할 수 있기 때문이다.</p><p>이더리움의 비콘 체인은 POS가 적용된 블록체인이다.</p><p>기존의 POW 가 아닌 이더리움 2.0을 위한 POS 가 적용된 블록체인이다.</p><p>비콘체인은 참여자들을 관리하는데, 어떤 검증자가 블록을 제안해야 할지, 위원회가 되어 블록을 증명해야 하는지 등의 역할을 한다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4f21fb5a1fe466ef2e954b3df4d69f58b890b3b6456415c17bab2b42408ad560.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>그림처럼 POW로 블록을 생성하는 메인 체인과 평행하게, POS 로 블록을 생성한다.</p><p>그러다 The Merge 업그레이드로 POW 가 아닌 POS 로 Mainnet 의 TX 처리를 시작하면서 블록 전파 및 합의 로직까지 담당하게 되었다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0b0e73da58335473f1c265978656993a7a9e89869468e23c6b9e67f3e81d9d08.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>비콘체인은 슬롯과 에포크에 맞춰 작동하는데, 슬롯은 12초, 에포크는 32개의 슬롯이다.</p><p>비콘체인에서 슬롯에는 하나의 블록이 추가될 수 있다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c664f01f4e79536cc9a458fc433c19058130de1da0115625d6445ea7fdb23493.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>비콘체인의 블록은 샤드체인의 블록과 연결되는데, 비콘 체인의 블록에는 최대 64개의 샤드 블록이 추가될 수 있다.</p><p>즉 시스템이 최적으로 실행되고 있다면 매 12초마다, 64개의 샤드 블록이 1개의 비콘 블록에 기록된다.</p><p>슬롯은 정해진 시간일 뿐이며, 비콘 블록과 정확하게 일치하지 않는다.</p><p>1024 슬롯이라고 해서 1024개의 비콘 블록이 있지 않을 수 도 있다.</p><p>슬롯을 기록해주어야 할 검증자가 오프라인이 될 경우, 슬롯이 비어있을 수 있기 때문이다.</p><p>비콘 체인은 매 에포크마다 검증자를 슬롯으로 고르게 나누고, 각 슬롯당 최소 128개의 검증자를 위원회로 할당한다.</p><p>만약 16,384(512 * 32)개의 검증자가 있다면, 1개의 에포크를 구성하는 32개 슬롯에 512개의 검증자가 배정된다.</p><blockquote><p>비콘체인은 매 에포크마다 검증자를 슬롯으로 고르게 나누고, 각 슬롯당 128개의 검증자를 위원회로 할당한다.</p><p>시스템이 최적으로 실행되고 있다면 매 12초마다(1슬롯), 64개의 샤드 블록이 1개의 비콘 블록에 기록된다.</p></blockquote><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">출처</h1><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/decipher-media/%EC%9D%B4%EB%8D%94%EB%A6%AC%EC%9B%80-%EB%B9%84%EC%BD%98-%EC%B2%B4%EC%9D%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-c0d6a80f3ecf">https://medium.com/decipher-media/%EC%9D%B4%EB%8D%94%EB%A6%AC%EC%9B%80-%EB%B9%84%EC%BD%98-%EC%B2%B4%EC%9D%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-c0d6a80f3ecf</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://velog.io/@youngju307/%EC%9D%B4%EB%8D%94%EB%A6%AC%EC%9B%80-%EB%B9%84%EC%BD%98%EC%B2%B4%EC%9D%B8Beacon-Chain">https://velog.io/@youngju307/%EC%9D%B4%EB%8D%94%EB%A6%AC%EC%9B%80-%EB%B9%84%EC%BD%98%EC%B2%B4%EC%9D%B8Beacon-Chain</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethos.dev/beacon-chain/">https://ethos.dev/beacon-chain/</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Socket programming]]></title>
            <link>https://paragraph.com/@primrose/socket-programming</link>
            <guid>NqQuoIMPEI16uzZLRjla</guid>
            <pubDate>Sun, 21 Jan 2024 05:51:48 GMT</pubDate>
            <description><![CDATA[Socket"소켓(Socket)"은 사전적으로 "구멍", "연결", "콘센트" 등의 의미를 가진다. 네트워크 프로그래밍에서의 소켓(Socket)에 대한 의미도, 사전적 의미를 크게 벗어나지 않는다. 프로그램이 네트워크에서 데이터를 송수신할 수 있도록, "네트워크 환경에 연결할 수 있게 만들어진 연결부"가 바로 "네트워크 소켓(Socket)"이다.https://recipes4dev.tistory.com/153하지만 엄밀히 따지자면, "네트워크 소켓"이라는 용어가 정확한 표현은 아니다. 전기 소켓이 전기를 공급받기 위해 정해진 규격(110V, 220V 등)에 맞게 만들어져야 하듯, 네트워크에 연결하기 위한 소켓 또한 정해진 규약, 즉, 통신을 위한 프로토콜(Protocol)에 맞게 만들어져야 한다. 보통 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 ...]]></description>
            <content:encoded><![CDATA[<h1 id="h-socket" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Socket</h1><p>&quot;소켓(Socket)&quot;은 사전적으로 &quot;구멍&quot;, &quot;연결&quot;, &quot;콘센트&quot; 등의 의미를 가진다.</p><p>네트워크 프로그래밍에서의 소켓(Socket)에 대한 의미도, 사전적 의미를 크게 벗어나지 않는다.</p><p>프로그램이 네트워크에서 데이터를 송수신할 수 있도록, &quot;네트워크 환경에 연결할 수 있게 만들어진 연결부&quot;가 바로 &quot;네트워크 소켓(Socket)&quot;이다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d04e9762d06fc456f268e3cf66fc673c4698933d06cde2dfc4453409409f12a8.png" alt="https://recipes4dev.tistory.com/153" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">https://recipes4dev.tistory.com/153</figcaption></figure><p>하지만 엄밀히 따지자면, &quot;네트워크 소켓&quot;이라는 용어가 정확한 표현은 아니다.</p><p>전기 소켓이 전기를 공급받기 위해 정해진 규격(110V, 220V 등)에 맞게 만들어져야 하듯, 네트워크에 연결하기 위한 소켓 또한 정해진 규약, 즉, 통신을 위한 프로토콜(Protocol)에 맞게 만들어져야 한다.</p><p>보통 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 소켓을 주로 사용하는데, 이를 &quot;TCP 소켓&quot; 또는 &quot;TCP/IP 소켓&quot;이라고 부른다. (UDP에서 동작하는 소켓은 &quot;UDP 소켓&quot;)</p><h2 id="h-tcpip" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">TCP/IP 소켓 프로그래밍</h2><p>소켓(Socket)을 사용하여 네트워크 통신 기능을 구현하는 과정은 그 개념만큼 아주 단순하지만은 않다.</p><p>소켓(Socket)으로 네트워크 통신 기능을 구현하기 위해서는 소켓을 만드는 것과, 만들어진 소켓을 통해 데이터를 주고 받는 절차에 대한 이해가 필요하고, 운영체제 및 프로그래밍 언어에 종속적으로 제공되는 소켓 API 사용법을 숙지해야 하기 때문이다.</p><p>케이블 분리로 인한 네트워크 단절, 트래픽 증가에 따른 데이터 전송 지연, 시스템 리소스 관리 문제로 인한 에러 등, 네트워크 환경에서 발생할 수 있는 다양한 예외사항에 대해서도 처리가 필요하기 때문에 더욱 어렵다.</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">클라이언트 소켓, 서버 소켓</h2><p>두 개의 시스템(또는 프로세스)이 소켓을 통해 네트워크 연결(Connection)을 만들기 위해서는, 최초 어느 한 곳에서 그 대상이 되는 곳으로 연결을 요청해야 한다.</p><p>IP 주소와 포트 번호로 식별되는 대상에게, 자신이 데이터 송수신을 위한 네트워크 연결을 수립할 의사가 있음을 알리는 것이다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/aef3070e3ca7b60aa200e80adb778af499b275e2c143aba7e9939609d1083703.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>무작정 연결을 시도한다고 해서, 그 요청이 무조건 받아들여지고 연결이 만들어져 데이터를 주고 받을 수 있게 되는 것은 아니다.</p><p>한 곳에서 연결 요청을 보낸다고 하더라도 그 대상 시스템이 그 요청을 받아들일 준비가 되어 있지 않다면, 해당 요청은 무시되고 연결은 만들어지지 않는다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ca23ed7052829502dd05450323624c5e7bf6158e0a80bb1b3f64c835788f34eb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>그러므로 요청을 받아들이는 곳에서는 어떤 연결 요청(일반적으로 포트 번호로 식별)을 받아들일 것인지를 미리 시스템에 등록하여, 요청이 수신되었을 때 해당 요청을 처리할 수 있도록 준비해야 한다.</p><p>이렇듯 두 개의 시스템(또는 프로세스)이 소켓을 통해 데이터 통신을 위한 연결(Connection)을 만들기 위해서는, 연결 요청을 보내는지 또는 요청을 받아들이는지에 따라 소켓의 역할이 나뉘게 되는데, 전자에 사용되는 소켓을 클라이언트 소켓(Client Socket), 후자에 사용되는 소켓을 서버 소켓(Server Socket)이라고 한다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/53a604ef4a19123d07b39f4db93cf5aa5cbfab1c338a54f3638b2fe0fec7b471.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>두 소켓은 동일하다. 역할과 구현 절차에 따라 다르게 부르는 것일 뿐, 순서가 바뀐다면 역할도 바뀌는 것.</p><h2 id="h-socket-api" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Socket API</h2><p>클라이언트 소켓(Client Socket)은 처음 소켓(Socket)을 생성(create)한 다음, 서버 측에 연결(connect)을 요청한다.</p><p>그리고 서버 소켓에서 연결이 받아들여지면 데이터를 송수신(send/recv)하고, 모든 처리가 완료되면 소켓(Socket)을 닫는다(close).</p><p>서버 소켓(Server Socket)은 일단 클라이언트와 마찬가지로 첫 번째 단계는 소켓(Socket)을 생성(create)한다.</p><p>그리고 서버가 사용할 IP 주소와 포트 번호를 생성한 소켓에 결합(bind)시킨다.</p><p>그런 다음 클라이언트로부터 연결 요청이 수신되는지 주시(listen)하고, 요청이 수신되면 요청을 받아들여(accept) 데이터 통신을 위한 소켓을 생성한다.</p><p>일단 새로운 소켓을 통해 연결이 수립(ESTABLISHED)되면, 클라이언트와 마찬가지로 데이터를 송수신(send/recv)할 수 있다.</p><p>마지막으로 데이터 송수신이 완료되면, 소켓(Socket)을 닫는다(close).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/815429ee16ae1c89a478c68a9446d4bf88cb38eb544ac2ec0c0c584555cd6ea3.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">출처</h1><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://recipes4dev.tistory.com/153">https://recipes4dev.tistory.com/153</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[K8S SIGTERM]]></title>
            <link>https://paragraph.com/@primrose/k8s-sigterm</link>
            <guid>2MECAeA0EwxD9SXJTzoD</guid>
            <pubDate>Fri, 19 Jan 2024 15:27:24 GMT</pubDate>
            <description><![CDATA[Graceful Shutdown대부분의 서버/엔진을 구현할 때, 무중단 배포시 혹은 임의 종료시에 정상적으로 종료되도록 하는 것이 좋다. Graceful shutdown 은 프로그램이 종료될 때 최대한 Side effect 가 없도록 로직들을 잘 갈무리하고 종료하도록 하는 것을 뜻한다. 로직이 진행되고 있는 와중에 프로세스가 종료되어버리면 처리중인 데이터가 증발할 수도 있고, 어디까지 처리중에 멈추었는지 추적하기 힘들어지는 경우도 있다. 보통 SIGTERM 을 이용해서 구현한다. 아래는 graceful shutdown 을 위해 Go 에서 주로 사용하는 방법이다.func main() { stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) &#x3C;-stop // ... } 위와 같이 구현하면, SIGINT 와 SIGTERM 신호가 왔을때 바로 종료되는 것이 아니라 Catch 해서...]]></description>
            <content:encoded><![CDATA[<h1 id="h-graceful-shutdown" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Graceful Shutdown</h1><p>대부분의 서버/엔진을 구현할 때, 무중단 배포시 혹은 임의 종료시에 정상적으로 종료되도록 하는 것이 좋다.</p><p>Graceful shutdown 은 프로그램이 종료될 때 최대한 Side effect 가 없도록 로직들을 잘 갈무리하고 종료하도록 하는 것을 뜻한다.</p><p>로직이 진행되고 있는 와중에 프로세스가 종료되어버리면 처리중인 데이터가 증발할 수도 있고, 어디까지 처리중에 멈추었는지 추적하기 힘들어지는 경우도 있다.</p><p>보통 SIGTERM 을 이용해서 구현한다. 아래는 graceful shutdown 을 위해 Go 에서 주로 사용하는 방법이다.</p><pre data-type="codeBlock" text="func main() {
  stop := make(chan os.Signal, 1)
  signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
  &lt;-stop
  // ... 
}
"><code>func main() {
  stop :<span class="hljs-operator">=</span> make(chan os.Signal, <span class="hljs-number">1</span>)
  signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
  <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span>stop
  <span class="hljs-comment">// ... </span>
}
</code></pre><p>위와 같이 구현하면, SIGINT 와 SIGTERM 신호가 왔을때 바로 종료되는 것이 아니라 Catch 해서 이후 로직을 이어가겠다는 뜻이다.</p><p>최근 회사에서 EKS 환경에서 다음과 같은 상황이 있었다.</p><blockquote><p>A 서버는 multi pod 로 동작하며, 일반적으로 3개의 pod가 항시 동작함.</p><p>이 때, 하나의 pod 에서만 특정 작업을 수행해야 하고, 해당 pod 가 종료되지 않는 한 다른 pod 는 그 작업을 시도하면 안됨.</p><p>이를 보장하기 위해 redis 의 SETNX 를 이용해 락을 집는 방식.</p><p>작업을 수행하는 서버는 SIGTERM 신호를 받으면 해당 락을 삭제하고 <code>os.Exit(1)</code> 종료.</p></blockquote><p>문제는 여기서 시작됐는데, pod 가 교체되면서 해당 작업을 수행하지 못하고 바로 종료되었다.</p><p>애초에 로그조차도 찍히지 않았기때문에, SIGKILL 을 받은것으로 보였다.</p><p>k8s 는 SIGTERM 을 보낸지 30초가 지나도 해당 컨테이너가 종료되지 않으면 SIGKILL을 보낸다.</p><p>서버가 SIGTERM 을 받지 못했다고 가정하고 디버깅을 시작했다.</p><h1 id="h-sigterm" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">왜 SIGTERM 을 못받았을까?</h1><p>우선 아래 명령어를 이용해서 실행중인 pod 에서 상태를 체크해보기로 했다.</p><pre data-type="codeBlock" text="kubectl exec -it &quot;...&quot; -- /bin/sh 

$ &gt; ps -ef
"><code>kubectl exec <span class="hljs-operator">-</span>it <span class="hljs-string">"..."</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>sh 

$ <span class="hljs-operator">></span> ps <span class="hljs-operator">-</span>ef
</code></pre><p>확인해보니 이상한 점이 보였다.</p><p>1번 프로세스가 /bin/sh ./run</p><p>1번 프로세스를 부모로 가지는 자식 프로세스 /bin/bash ./server</p><p>1번 프로세스의 손주(위의 자식 프로세스) ./server</p><blockquote><p>./server 는 컴파일 후 나온 outfile (binary)</p></blockquote><p>k8s 는 SIGTERM 을 1번 프로세스에 보낸다.</p><p>내 서버는 손주 프로세스가 되어있기 때문에 받지 못한 것이고, Dockerfile 을 확인해보니 회사의 모든 서버는 아래와 같이 실행되고 있었다.</p><pre data-type="codeBlock" text="...

CMD ./run
"><code>...

CMD ./run
</code></pre><p><code>run</code> 이라는 스크립트를 CMD 를 이용해서 실행하기 때문에, 1번 프로세스가 /bin/sh 가 되는 것.</p><p>사실 해당 스크립트는 레거시에 가깝고 프로세스 실행 외에 역할이 아예 없었기 때문에 아래와 같이 바꾸고 실행해보았다.</p><pre data-type="codeBlock" text="...

ENTRYPOINT ./server
"><code>...

ENTRYPOINT ./server
</code></pre><p>위와 같이 ENTRYPOINT 로 실행하게 되면 1번 프로세스가 나의 서버가 되기 때문에, SIGTERM 을 받을 수 있게 된다.</p><p>사실 해결 방법으로 Helm chart 에서 <code>preStop</code> 등을 이용해 SIGTERM 을 전파할까도 싶었지만, 굳이 그럴 필요가 있나 싶어서 필자는 위와 같이 해결했다.</p><p>테스트 하고 싶다면 직접 배포를 다시 해봐도 되고 <code>kubectl delete pod “…”</code> 커맨드를 실행해서 로그를 살펴봐도 되고, deployment 를 수정하면서 테스트 하던가… 뭐 방법이야 많을 것 같다.</p><p>편한 방식대로 하자.</p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[CORS]]></title>
            <link>https://paragraph.com/@primrose/cors</link>
            <guid>mVhPl5ksh5Z6T8cFkYSv</guid>
            <pubDate>Wed, 18 Oct 2023 14:56:29 GMT</pubDate>
            <description><![CDATA[웹 개발자라면 한번쯤은 봤을 법한 CORS(Cross-Origin Resource Sharing) 정책에 대한 이야기를 해보자.DefinitionCORS는 Cross-Origin Resource Sharing의 줄임말로, 한국어로 직역하면 교차 출처 리소스 공유라고 해석할 수 있다. 브라우저에서는 보안적인 이유로 cross-origin HTTP 요청들을 제한한다. 그래서 cross-origin(교차 출처) 요청을 하려면 서버의 동의가 필요하다. 만약 서버가 동의한다면 브라우저에서는 요청을 허락하고, 동의하지 않는다면 브라우저에서 거절한다. 따라서 CORS 관련 이슈는 모두 CORS 정책을 위반했기 때문에 발생하는 것이다.Cross-origincross-origin이란 다음 중 한 가지라도 다른 경우를 말한다.프로토콜 : http vs https도메인 : abc.com vs def.com포트 번호 : 80 vs 8080같은 프로토콜, 호스트, 포트를 사용한다면, 그 뒤의 다른 ...]]></description>
            <content:encoded><![CDATA[<p>웹 개발자라면 한번쯤은 봤을 법한 <code>CORS(Cross-Origin Resource Sharing)</code> 정책에 대한 이야기를 해보자.</p><h1 id="h-definition" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Definition</h1><p>CORS는 <code>Cross-Origin Resource Sharing</code>의 줄임말로, 한국어로 직역하면 교차 출처 리소스 공유라고 해석할 수 있다.</p><p>브라우저에서는 보안적인 이유로 <code>cross-origin</code> HTTP 요청들을 제한한다.</p><p>그래서 <code>cross-origin(교차 출처)</code> 요청을 하려면 서버의 동의가 필요하다.</p><p>만약 서버가 동의한다면 브라우저에서는 요청을 허락하고, 동의하지 않는다면 브라우저에서 거절한다.</p><p>따라서 CORS 관련 이슈는 모두 CORS 정책을 위반했기 때문에 발생하는 것이다.</p><h1 id="h-cross-origin" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Cross-origin</h1><p><code>cross-origin</code>이란 다음 중 한 가지라도 다른 경우를 말한다.</p><ol><li><p>프로토콜 : http vs https</p></li><li><p>도메인 : abc.com vs def.com</p></li><li><p>포트 번호 : 80 vs 8080</p></li></ol><p>같은 프로토콜, 호스트, 포트를 사용한다면, 그 뒤의 다른 요소는 다르더라도 같은 출처로 인정된다.</p><p>반대로 프로토콜, 호스트, 포트 중 하나라도 자신의 출처와 다를경우 브라우저는 정책상 차단하게 된다.</p><p>출처를 비교하는 로직은 서버에 구현된 스펙이 아닌 <strong>브라우저에 구현된 스펙</strong>이다.</p><p>따라서 브라우저를 통하지 않고 서버 간에 통신을 할때는 정책이 적용되지 않는다는 말과 같다.</p><h1 id="h-operation" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Operation</h1><h3 id="h-1-client-request-header-origin" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>1. Client에서 Request Header 에 Origin 정보 추가</strong></h3><h3 id="h-2-server-response-header-access-control-allow-origin" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2. Server는 Response Header에 Access-Control-Allow-Origin 정보 추가</h3><h3 id="h-3-client-origin-origin" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3. Client는 보낸 Origin과 받은 Origin 비교</h3><h1 id="h-source" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Source</h1><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F#CORS_%EA%B8%B0%EB%B3%B8_%EB%8F%99%EC%9E%91%EA%B3%BC%EC%A0%95">https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F#CORS_%EA%B8%B0%EB%B3%B8_%EB%8F%99%EC%9E%91%EA%B3%BC%EC%A0%95</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[solidity: patterns]]></title>
            <link>https://paragraph.com/@primrose/solidity-patterns</link>
            <guid>A5xvKwpOvrhkaRdPq0wy</guid>
            <pubDate>Wed, 20 Sep 2023 13:10:51 GMT</pubDate>
            <description><![CDATA[Solidity patterns솔리디티의 다양한 패턴에 대해서 다루어보려고 한다. 아래 링크를 참고해서 쓰여진 글이며, 본 글에서는 모든 내용을 담고 있지는 않다. https://fravoll.github.io/solidity-patterns/Guard CheckGuard를 통해 스마트 컨트랙트 및 해당 입력 매개변수의 유효성을 검증. 스마트 컨트랙트의 바람직한 동작은 필요한 모든 상황을 확인하고 모든 것이 의도한 대로인 경우에만 진행하는 것이다. 이를 Guard Check Pattern이라고 한다. 단순 require 문을 활용해도 좋고, modifier를 활용해도 좋다.// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; contract GuardCheck { modifier addressGuard(address addr) { require(addr != address(0), "address should be a v...]]></description>
            <content:encoded><![CDATA[<h1 id="h-solidity-patterns" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Solidity patterns</h1><p>솔리디티의 다양한 패턴에 대해서 다루어보려고 한다.</p><p>아래 링크를 참고해서 쓰여진 글이며, 본 글에서는 모든 내용을 담고 있지는 않다.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://fravoll.github.io/solidity-patterns/">https://fravoll.github.io/solidity-patterns/</a></p><h2 id="h-guard-check" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Guard Check</h2><p>Guard를 통해 스마트 컨트랙트 및 해당 입력 매개변수의 유효성을 검증.</p><p>스마트 컨트랙트의 바람직한 동작은 필요한 모든 상황을 확인하고 모든 것이 의도한 대로인 경우에만 진행하는 것이다.</p><p>이를 <code>Guard Check Pattern</code>이라고 한다.</p><p>단순 require 문을 활용해도 좋고, <code>modifier</code>를 활용해도 좋다.</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract GuardCheck {
    modifier addressGuard(address addr) {
        require(addr != address(0), &quot;address should be a valid address&quot;);

        _;
    }

    modifier valueGuard(uint value) {
        require(msg.value != 0, &quot;msg.value should be a valid value&quot;);

        _;
    }

    function donate(
        address addr
    ) public payable addressGuard(addr) valueGuard(msg.value) {
        // ...
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: UNLICENSED</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">GuardCheck</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">addressGuard</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> addr</span>) </span>{
        <span class="hljs-built_in">require</span>(addr <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"address should be a valid address"</span>);

        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">valueGuard</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> value</span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"msg.value should be a valid value"</span>);

        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">donate</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> addr
    </span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title">addressGuard</span>(<span class="hljs-params">addr</span>) <span class="hljs-title">valueGuard</span>(<span class="hljs-params"><span class="hljs-built_in">msg</span>.value</span>) </span>{
        <span class="hljs-comment">// ...</span>
    }
}
</code></pre><h2 id="h-state-machine" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">State Machine</h2><p>스마트 컨트랙트에서 서비스 로직을 구현하는 경우, 생애 주기를 관리해야 하는 경우가 있다.</p><p>시간에 따라서, 단계에 따라서 등 다양한 경우가 있을 수 있다.</p><p>다음과 같은 경우에 상태를 가지도록 구현할 수 있다.</p><ul><li><p>스마트 컨트랙트는 수명 주기동안 여러 단계로 전환해야한다.</p></li><li><p>스마트 컨트랙트의 기능은 특정 단계에서만 접근 가능해야한다.</p></li><li><p>사용자의 행동에 따라서 스마트 컨트랙트의 상태가 변경되어야 한다.</p></li></ul><p>Solidity 에서는 다양한 단계를 모델링하기 위해 <code>enum</code>을 사용할 수 있다.</p><p>특정 단계에 대한 기능 액세스 제한은 뒤에서 다룰 <code>Access Restriction</code>을 활용하면 된다.</p><p>위에서 다룬 <code>Guard Check</code> 패턴과 관련이 있으나 중구난방으로 진행하면 정신없으니 일단 <code>State Machine</code>의 코드를 보고 넘어가보자.</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.21;

contract StateMachine {
    // Stage에 대한 enum을 정의한다.
    enum Stages {
        AcceptingBlindBids,
        RevealBids,
        WinnerDetermined,
        Finished
    }

    Stages public stage = Stages.AcceptingBlindBids;

    uint public creationTime = now;

    modifier atStage(Stages _stage) {
        require(stage == _stage);
        _;
    }

    modifier transitionAfter() {
        _;
        nextStage();
    }

    modifier timedTransitions() {
        if (stage == Stages.AcceptingBlindBids &amp;&amp; now &gt;= creationTime + 6 days) {
            nextStage();
        }
        if (stage == Stages.RevealBids &amp;&amp; now &gt;= creationTime + 10 days) {
            nextStage();
        }
        _;
    }

    function bid() public payable timedTransitions atStage(Stages.AcceptingBlindBids) {
        // Implement biding here
    }

    function reveal() public timedTransitions atStage(Stages.RevealBids) {
        // Implement reveal of bids here
    }

    function claimGoods() public timedTransitions atStage(Stages.WinnerDetermined) transitionAfter {
        // Implement handling of goods here
    }

    function cleanup() public atStage(Stages.Finished) {
        // Implement cleanup of auction here
    }

    function nextStage() internal {
        stage = Stages(uint(stage) + 1);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: UNLICENSED</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.21;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">StateMachine</span> </span>{
    <span class="hljs-comment">// Stage에 대한 enum을 정의한다.</span>
    <span class="hljs-keyword">enum</span> <span class="hljs-title">Stages</span> {
        AcceptingBlindBids,
        RevealBids,
        WinnerDetermined,
        Finished
    }

    Stages <span class="hljs-keyword">public</span> stage <span class="hljs-operator">=</span> Stages.AcceptingBlindBids;

    <span class="hljs-keyword">uint</span> <span class="hljs-keyword">public</span> creationTime <span class="hljs-operator">=</span> <span class="hljs-built_in">now</span>;

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">atStage</span>(<span class="hljs-params">Stages _stage</span>) </span>{
        <span class="hljs-built_in">require</span>(stage <span class="hljs-operator">=</span><span class="hljs-operator">=</span> _stage);
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">transitionAfter</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">_</span>;
        nextStage();
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">timedTransitions</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (stage <span class="hljs-operator">=</span><span class="hljs-operator">=</span> Stages.AcceptingBlindBids <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-built_in">now</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> creationTime <span class="hljs-operator">+</span> <span class="hljs-number">6</span> <span class="hljs-literal">days</span>) {
            nextStage();
        }
        <span class="hljs-keyword">if</span> (stage <span class="hljs-operator">=</span><span class="hljs-operator">=</span> Stages.RevealBids <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-built_in">now</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> creationTime <span class="hljs-operator">+</span> <span class="hljs-number">10</span> <span class="hljs-literal">days</span>) {
            nextStage();
        }
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bid</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title">timedTransitions</span> <span class="hljs-title">atStage</span>(<span class="hljs-params">Stages.AcceptingBlindBids</span>) </span>{
        <span class="hljs-comment">// Implement biding here</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reveal</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">timedTransitions</span> <span class="hljs-title">atStage</span>(<span class="hljs-params">Stages.RevealBids</span>) </span>{
        <span class="hljs-comment">// Implement reveal of bids here</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">claimGoods</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">timedTransitions</span> <span class="hljs-title">atStage</span>(<span class="hljs-params">Stages.WinnerDetermined</span>) <span class="hljs-title">transitionAfter</span> </span>{
        <span class="hljs-comment">// Implement handling of goods here</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cleanup</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">atStage</span>(<span class="hljs-params">Stages.Finished</span>) </span>{
        <span class="hljs-comment">// Implement cleanup of auction here</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">nextStage</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
        stage <span class="hljs-operator">=</span> Stages(<span class="hljs-keyword">uint</span>(stage) <span class="hljs-operator">+</span> <span class="hljs-number">1</span>);
    }
}
</code></pre><p>위의 예시는 Stage에 따라 컨트랙트 자체의 상태가 바뀐다.</p><h2 id="h-access-restriction" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Access Restriction</h2><p>스마트 컨트랙트는 블록체인에 배포되면 누구나 접근할 수 있다.</p><p>공개적인 특성으로 인해 스마트 컨트랙트에 대한 완전한 정보 보호를 보장하는 것은 불가능에 가깝다.</p><p>모든 정보가 모두에게 표시되기때문에 누군가가 블록체인에서 컨트랙트 상태를 읽는 것을 막을수가 없다.</p><p>함수를 <code>private</code>으로 선언하는 선택지도 있지만, 그렇게 하면 모든 사람이 함수를 호출할 수 없게된다.</p><p>이런 경우, <code>GuardCheck</code>, <code>StateMachine</code> 패턴과 함께 사용하면 좋다.</p><blockquote><p><code>private</code> 은 smart contract의 인터페이스로 비공개한다. 컨트랙트 내부에서만 사용한다. 상속 받은 컨트랙트에서도 사용 불가능하다.<code>external</code>은 smart contract의 인터페이스로 공개한다. 컨트랙트 내부에서 호출할 경우 this를 사용해서 접근해야 한다.<code>internal</code>은 smart contract의 인터페이스로 비공개한다. 컨트랙트 내부에서만 사용한다. 상속 받은 컨트랙트에서도 사용 가능하다.<code>public</code>은 smart contract의 인터페이스로 공개한다. 컨트랙트의 내부와 외부에서 모두 호출할 수 있다. 컨트랙트 내부에서 호출할 경우 this를 사용해서 접근해야 한다.</p></blockquote><pre data-type="codeBlock" text="// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract AccessRestriction {
    address public owner = msg.sender;
    uint public lastOwnerChange = now;

    modifier onlyBy(address _account) {
        require(msg.sender == _account);
        _;
    }

    modifier onlyAfter(uint _time) {
        require(now &gt;= _time);
        _;
    }

    modifier costs(uint _amount) {
        require(msg.value &gt;= _amount);
        _;
        if (msg.value &gt; _amount) {
            msg.sender.transfer(msg.value - _amount);
        }
    }

    function changeOwner(address _newOwner) public onlyBy(owner) {
        owner = _newOwner;
    }

    function buyContract() public payable onlyAfter(lastOwnerChange + 4 weeks) costs(1 ether) {
        owner = msg.sender;
        lastOwnerChange = now;
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: UNLICENSED</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">AccessRestriction</span> </span>{
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
    <span class="hljs-keyword">uint</span> <span class="hljs-keyword">public</span> lastOwnerChange <span class="hljs-operator">=</span> <span class="hljs-built_in">now</span>;

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyBy</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _account</span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> _account);
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyAfter</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _time</span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">now</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> _time);
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">costs</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _amount</span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> _amount);
        <span class="hljs-keyword">_</span>;
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span> _amount) {
            <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> - _amount);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">changeOwner</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _newOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyBy</span>(<span class="hljs-params">owner</span>) </span>{
        owner <span class="hljs-operator">=</span> _newOwner;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">buyContract</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title">onlyAfter</span>(<span class="hljs-params">lastOwnerChange + <span class="hljs-number">4</span> <span class="hljs-literal">weeks</span></span>) <span class="hljs-title">costs</span>(<span class="hljs-params"><span class="hljs-number">1</span> <span class="hljs-literal">ether</span></span>) </span>{
        owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
        lastOwnerChange <span class="hljs-operator">=</span> <span class="hljs-built_in">now</span>;
    }
}
</code></pre><p>OpenZeppelin의 <code>Ownable</code>을 활용하면 더욱 간단하게 구현할 수 있다.</p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Context in golang]]></title>
            <link>https://paragraph.com/@primrose/context-in-golang</link>
            <guid>8qSRKCcOgCjgtiSXv5Tk</guid>
            <pubDate>Sat, 09 Sep 2023 17:29:47 GMT</pubDate>
            <description><![CDATA[Contextgolang을 써봤으면 다들 한 번쯤 사용하게 되는것이 바로 context.Context 이다. https://pkg.go.dev/context go에서는 context를 어떻게 사용하고 어떻게 설계했을지 살펴보자. 공식문서를 읽으면서 이해도를 높여보자.Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. context 패키지는 API 경계를 넘어 프로세스 간에 deadline, 취소 signal 및 기타 요청 범위 값을 전달하는 컨텍스트 유형을 정의합니다. 컨텍스트는 우리 말로 문맥, 맥락을 뜻한다. REST 요청이건, 소켓 요청을하건, 요청-응답의 흐름 안에서 유지해야 할 상태를 context를 통해 공유한다.Incomin...]]></description>
            <content:encoded><![CDATA[<h1 id="h-context" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Context</h1><p>golang을 써봤으면 다들 한 번쯤 사용하게 되는것이 바로 <code>context.Context</code> 이다.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pkg.go.dev/context">https://pkg.go.dev/context</a></p><p>go에서는 context를 어떻게 사용하고 어떻게 설계했을지 살펴보자.</p><p>공식문서를 읽으면서 이해도를 높여보자.</p><pre data-type="codeBlock" text="Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

context 패키지는 API 경계를 넘어 프로세스 간에 deadline, 취소 signal 및 기타 요청 범위 값을 전달하는 컨텍스트 유형을 정의합니다.
"><code>Package context defines the Context <span class="hljs-keyword">type</span>, which carries deadlines, cancellation signals, and other request<span class="hljs-operator">-</span>scoped values across API boundaries and between processes.

context 패키지는 API 경계를 넘어 프로세스 간에 deadline, 취소 signal 및 기타 요청 범위 값을 전달하는 컨텍스트 유형을 정의합니다.
</code></pre><blockquote><p>컨텍스트는 우리 말로 문맥, 맥락을 뜻한다. REST 요청이건, 소켓 요청을하건, <strong><em>요청-응답의 흐름 안에서 유지해야 할 상태를 context를 통해 공유한다.</em></strong></p></blockquote><pre data-type="codeBlock" text="Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. 

서버로 들어오는 요청은 컨텍스트를 생성하고 서버로 나가는 호출은 컨텍스트를 수락해야 합니다. 

The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. 

이들 사이의 함수 호출 체인은 컨텍스트를 전파해야 하며 선택적으로 WithCancel, WithDadline, WithTimeout 또는 WithValue를 사용하여 만든 파생 컨텍스트로 대체해야 합니다. 

When a Context is canceled, all Contexts derived from it are also canceled.

컨텍스트가 취소되면 컨텍스트에서 파생된 모든 컨텍스트도 취소됩니다.
"><code>Incoming requests <span class="hljs-selector-tag">to</span> <span class="hljs-selector-tag">a</span> server should create <span class="hljs-selector-tag">a</span> Context, and outgoing calls <span class="hljs-selector-tag">to</span> servers should accept <span class="hljs-selector-tag">a</span> Context. 

서버로 들어오는 요청은 컨텍스트를 생성하고 서버로 나가는 호출은 컨텍스트를 수락해야 합니다. 

The chain of function calls between them must propagate the Context, optionally replacing it with <span class="hljs-selector-tag">a</span> derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. 

이들 사이의 함수 호출 체인은 컨텍스트를 전파해야 하며 선택적으로 WithCancel, WithDadline, WithTimeout 또는 WithValue를 사용하여 만든 파생 컨텍스트로 대체해야 합니다. 

When <span class="hljs-selector-tag">a</span> Context is canceled, <span class="hljs-attribute">all</span> Contexts derived <span class="hljs-selector-tag">from</span> it are also canceled.

컨텍스트가 취소되면 컨텍스트에서 파생된 모든 컨텍스트도 취소됩니다.
</code></pre><blockquote><p>즉, 요청이 들어오면 컨텍스트를 생성하고, 요청이 끝나면 컨텍스트를 종료시키겠다는 뜻이다.</p><p>위에서 얘기했던 요청-응답의 흐름을 중점으로 보자. 모든 API는 요청과 응답이 있다. 하나의 요청 안에서도 분기해서 많은 로직을 수행할 수 있다. 그렇게되면 하나의 요청-응답 사이에도 수많은 요청2-응답2, 요청3-응답3… 이 생기게 된다.</p><p>하나의 요청-응답이 하나의 lifecycle을 가진다고 했을때 root로 부터 context를 파생시켜가면 많은 API의 lifecycle을 관리할 수가 있다. <code>WithCancel</code>, <code>WithDeadline</code>, <code>WithTimeout</code>, <code>WithValue</code>를 이용해서 파생시키고, 관리하면 될 것이다.</p></blockquote><pre data-type="codeBlock" text="The WithCancel, WithDeadline, and WithTimeout functions take a Context (the parent) and return a derived Context (the child) and a CancelFunc. 

WithCancel, WithDedline, WithTimeout 기능은 컨텍스트(부모)를 선택하고 파생 컨텍스트(자녀)와 CancelFunc를 반환합니다.

Calling the CancelFunc cancels the child and its children, removes the parent&apos;s reference to the child, and stops any associated timers. 

CancelFunc를 호출하면 본인과 자녀가 취소되고, 자녀에 대한 부모의 참조가 제거되며, 연관된 타이머가 중지됩니다.

Failing to call the CancelFunc leaks the child and its children until the parent is canceled or the timer fires. 

CancelFunc를 호출하지 못하면 부모가 취소되거나 타이머가 실행될 때까지 자녀 컨텍스트들에 누수가 발생됩니다.

The go vet tool checks that CancelFuncs are used on all control-flow paths.

go vet 는 CancelFuncs가 모든 제어 흐름 경로에서 사용되는지 확인합니다.
"><code>The WithCancel, WithDeadline, <span class="hljs-built_in">and</span> WithTimeout functions <span class="hljs-keyword">take</span> a Context (the parent) <span class="hljs-built_in">and</span> <span class="hljs-keyword">return</span> a derived Context (the child) <span class="hljs-built_in">and</span> a CancelFunc. 

WithCancel, WithDedline, WithTimeout 기능은 컨텍스트(부모)를 선택하고 파생 컨텍스트(자녀)와 CancelFunc를 반환합니다.

Calling the CancelFunc cancels the child <span class="hljs-built_in">and</span> its children, removes the parent<span class="hljs-comment">'s reference to the child, and stops any associated timers. </span>

CancelFunc를 호출하면 본인과 자녀가 취소되고, 자녀에 대한 부모의 참조가 제거되며, 연관된 타이머가 중지됩니다.

Failing <span class="hljs-keyword">to</span> <span class="hljs-keyword">call</span> the CancelFunc leaks the child <span class="hljs-built_in">and</span> its children <span class="hljs-keyword">until</span> the parent <span class="hljs-built_in">is</span> canceled <span class="hljs-built_in">or</span> the timer fires. 

CancelFunc를 호출하지 못하면 부모가 취소되거나 타이머가 실행될 때까지 자녀 컨텍스트들에 누수가 발생됩니다.

The go vet tool checks that CancelFuncs are used <span class="hljs-keyword">on</span> all control-flow paths.

go vet 는 CancelFuncs가 모든 제어 흐름 경로에서 사용되는지 확인합니다.
</code></pre><p><code>cancel()</code> 함수를 실행하면 자식들이 전부 취소될까?</p><pre data-type="codeBlock" text="func childCtx(ctx context.Context, cancelFunc context.CancelFunc) {
    defer cancelFunc()
    time.Sleep(3 * time.Second)
    log.Println(&quot;childCtx done&quot;, ctx.Err())
}

func parentsCtx(c context.Context) {
    ctx, cancel := context.WithCancel(c)
    defer cancel()
    go childCtx(ctx, cancel)

    time.Sleep(time.Second * 1)
    log.Println(&quot;parentsCtx done&quot;)
}


// 2023/09/09 23:00:30 parentsCtx done
// 2023/09/09 23:00:32 childCtx done context canceled
"><code>func childCtx(ctx context.Context, cancelFunc context.CancelFunc) {
    defer cancelFunc()
    time.Sleep(<span class="hljs-number">3</span> <span class="hljs-operator">*</span> time.Second)
    log.Println(<span class="hljs-string">"childCtx done"</span>, ctx.Err())
}

func parentsCtx(c context.Context) {
    ctx, cancel :<span class="hljs-operator">=</span> context.WithCancel(c)
    defer cancel()
    go childCtx(ctx, cancel)

    time.Sleep(time.Second <span class="hljs-operator">*</span> <span class="hljs-number">1</span>)
    log.Println(<span class="hljs-string">"parentsCtx done"</span>)
}


<span class="hljs-comment">// 2023/09/09 23:00:30 parentsCtx done</span>
<span class="hljs-comment">// 2023/09/09 23:00:32 childCtx done context canceled</span>
</code></pre><blockquote><p>실제로 동작 자체를 멈추는 것은 아니고, context가 취소된다. 자식 함수가 바로 종료되지는 않는다.</p></blockquote><pre data-type="codeBlock" text="The WithCancelCause function returns a CancelCauseFunc, which takes an error and records it as the cancellation cause. Calling Cause on the canceled context or any of its children retrieves the cause. If no cause is specified, Cause(ctx) returns the same value as ctx.Err().

WithCancelCause 함수는 CancelCauseFunc를 반환하며, 이 함수는 오류를 가져와 취소 원인으로 기록합니다. 취소된 컨텍스트 또는 해당 하위 컨텍스트에서 원인을 호출하면 원인을 검색합니다. 원인이 지정되지 않으면 Cause(ctx)는 ctx.Err()와 동일한 값을 반환합니다.
"><code>The WithCancelCause <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> <span class="hljs-title">a</span> <span class="hljs-title">CancelCauseFunc</span>, <span class="hljs-title">which</span> <span class="hljs-title">takes</span> <span class="hljs-title">an</span> <span class="hljs-title"><span class="hljs-keyword">error</span></span> <span class="hljs-title">and</span> <span class="hljs-title">records</span> <span class="hljs-title">it</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">the</span> <span class="hljs-title">cancellation</span> <span class="hljs-title">cause</span>. <span class="hljs-title">Calling</span> <span class="hljs-title">Cause</span> <span class="hljs-title">on</span> <span class="hljs-title">the</span> <span class="hljs-title">canceled</span> <span class="hljs-title">context</span> <span class="hljs-title">or</span> <span class="hljs-title">any</span> <span class="hljs-title">of</span> <span class="hljs-title">its</span> <span class="hljs-title">children</span> <span class="hljs-title">retrieves</span> <span class="hljs-title">the</span> <span class="hljs-title">cause</span>. <span class="hljs-title">If</span> <span class="hljs-title">no</span> <span class="hljs-title">cause</span> <span class="hljs-title"><span class="hljs-keyword">is</span></span> <span class="hljs-title">specified</span>, <span class="hljs-title">Cause</span>(<span class="hljs-params">ctx</span>) <span class="hljs-title"><span class="hljs-keyword">returns</span></span> <span class="hljs-title">the</span> <span class="hljs-title">same</span> <span class="hljs-title">value</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">ctx</span>.<span class="hljs-title">Err</span>(<span class="hljs-params"></span>).

<span class="hljs-title">WithCancelCause</span> 함수는 <span class="hljs-title">CancelCauseFunc</span>를 반환하며, 이 함수는 오류를 가져와 취소 원인으로 기록합니다. 취소된 컨텍스트 또는 해당 하위 컨텍스트에서 원인을 호출하면 원인을 검색합니다. 원인이 지정되지 않으면 <span class="hljs-title">Cause</span>(<span class="hljs-params">ctx</span>)는 <span class="hljs-title">ctx</span>.<span class="hljs-title">Err</span>(<span class="hljs-params"></span>)와 동일한 값을 반환합니다.
</span></code></pre><blockquote><p>컨텍스트 자체를 취소 처리하고 에러를 기록한다.</p></blockquote><pre data-type="codeBlock" text="Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:

컨텍스트를 사용하는 프로그램은 패키지 간 인터페이스를 일관되게 유지하고 정적 분석 도구가 컨텍스트 전파를 확인할 수 있도록 다음 규칙을 따라야 합니다:

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx:

구조체 유형 안에 컨텍스트를 저장하지 말고 컨텍스트를 필요한 각 함수에 명시적으로 전달합니다. 컨텍스트는 일반적으로 ctx로 명명되는 첫 번째 매개 변수여야 합니다:

func DoSomething(ctx context.Context, arg Arg) error {
    // ... use ctx ...
}

Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.

함수가 허용하더라도 nil 컨텍스트를 전달하지 마십시오. 사용할 컨텍스트가 확실하지 않으면 context.TODO를 전달합니다.

Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

context Values는 프로세스 및 API를 전송하는 요청 범위 데이터에만 사용하며, 옵션 파라미터를 함수에 전달하는 경우에는 사용하지 않습니다.

The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.

동일한 컨텍스트를 다른 경로에서 실행 중인 함수에 전달할 수 있습니다. 컨텍스트는 여러 goroutine에서 동시에 사용하기에 안전합니다.
"><code>Programs that use Contexts should follow these rules <span class="hljs-keyword">to</span> keep interfaces consistent across packages <span class="hljs-built_in">and</span> enable <span class="hljs-keyword">static</span> analysis tools <span class="hljs-keyword">to</span> check context propagation:

컨텍스트를 사용하는 프로그램은 패키지 간 인터페이스를 일관되게 유지하고 정적 분석 도구가 컨텍스트 전파를 확인할 수 있도록 다음 규칙을 따라야 합니다:

<span class="hljs-keyword">Do</span> <span class="hljs-built_in">not</span> store Contexts inside a struct type; instead, pass a Context explicitly <span class="hljs-keyword">to</span> <span class="hljs-keyword">each</span> <span class="hljs-keyword">function</span> that needs it. The Context should be the first parameter, typically named ctx:

구조체 유형 안에 컨텍스트를 저장하지 말고 컨텍스트를 필요한 각 함수에 명시적으로 전달합니다. 컨텍스트는 일반적으로 ctx로 명명되는 첫 번째 매개 변수여야 합니다:

func DoSomething(ctx context.Context, arg Arg) <span class="hljs-keyword">error</span> {
    // ... use ctx ...
}

<span class="hljs-keyword">Do</span> <span class="hljs-built_in">not</span> pass a nil Context, even <span class="hljs-keyword">if</span> a <span class="hljs-keyword">function</span> permits it. Pass context.TODO <span class="hljs-keyword">if</span> you are unsure about which Context <span class="hljs-keyword">to</span> use.

함수가 허용하더라도 nil 컨텍스트를 전달하지 마십시오. 사용할 컨텍스트가 확실하지 않으면 context.TODO를 전달합니다.

Use context Values only <span class="hljs-keyword">for</span> request-scoped data that transits processes <span class="hljs-built_in">and</span> APIs, <span class="hljs-built_in">not</span> <span class="hljs-keyword">for</span> passing <span class="hljs-keyword">optional</span> parameters <span class="hljs-keyword">to</span> functions.

context Values는 프로세스 및 API를 전송하는 요청 범위 데이터에만 사용하며, 옵션 파라미터를 함수에 전달하는 경우에는 사용하지 않습니다.

The same Context may be passed <span class="hljs-keyword">to</span> functions running <span class="hljs-keyword">in</span> different goroutines; Contexts are safe <span class="hljs-keyword">for</span> simultaneous use <span class="hljs-keyword">by</span> multiple goroutines.

동일한 컨텍스트를 다른 경로에서 실행 중인 함수에 전달할 수 있습니다. 컨텍스트는 여러 goroutine에서 동시에 사용하기에 안전합니다.
</code></pre><blockquote><p>컨텍스트를 사용하는 예제에 대한 것이다. <code>context.Context</code> 자체가 인터페이스다. 따라서 본인이 구현한 자체 Context를 사용하더라도 이러한 사항에 대해서 지키며 개발을 하면 좋을 것이다.</p></blockquote><p>go 언어의 철학중에 이런 말이 있다.</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">메모리 공유를 통해 커뮤니케이션 하지 말고, 커뮤니케이션을 통해 메모리를 공유하라.</h3><p>서로 다른 프로세스, API, 함수 등에서 메모리를 공유하게 하는 것이 아니라, 컨텍스트를 통해 커뮤니케이션을 형성하면 이러한 철학을 달성할 수 있을것이다.</p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[How to sign Ethereum EIP-1559 transactions using AWS KMS
]]></title>
            <link>https://paragraph.com/@primrose/how-to-sign-ethereum-eip-1559-transactions-using-aws-kms</link>
            <guid>WcUVyzoZ5xPbtns3kKQF</guid>
            <pubDate>Tue, 22 Aug 2023 18:58:56 GMT</pubDate>
            <description><![CDATA[How to sign Ethereum EIP-1559 transactions using AWS KMSAWS의 KMS로 이더리움 트랜잭션 사인이 가능하다는 것을 얼마전에 알았다. 마침 조사를 해보니 좋은 예시들이 몇 개 있어서 공유하고자 한다. 첫 번째로 KMS에서 ECDSA 키를 생성해주어야 한다. 다음과 같은 형태로 만들어주면 된다.ECDSA Key 생성이제부터 코드로 보면 된다. 다음과 같이 kms 서비스를 생성한다.func main() { accessKey := "&#x3C;access-key>" secretKey := "&#x3C;secret-key>" sess, err := session.NewSession(&#x26;aws.Config{ Region: aws.String("&#x3C;region>"), Credentials: credentials.NewStaticCredentialsFromCreds(credentials.Value{ AccessKeyID: access...]]></description>
            <content:encoded><![CDATA[<h1 id="h-how-to-sign-ethereum-eip-1559-transactions-using-aws-kms" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>How to sign Ethereum EIP-1559 transactions using AWS KMS</strong></h1><p>AWS의 KMS로 이더리움 트랜잭션 사인이 가능하다는 것을 얼마전에 알았다.</p><p>마침 조사를 해보니 좋은 예시들이 몇 개 있어서 공유하고자 한다.</p><p>첫 번째로 KMS에서 ECDSA 키를 생성해주어야 한다.</p><p>다음과 같은 형태로 만들어주면 된다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/18c58f6845873564a6bd6654f988ada68b225bb8979ace2acec114a998004d6f.png" alt="ECDSA Key 생성" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">ECDSA Key 생성</figcaption></figure><p>이제부터 코드로 보면 된다. 다음과 같이 kms 서비스를 생성한다.</p><pre data-type="codeBlock" text="func main() {
    accessKey := &quot;&lt;access-key&gt;&quot;
    secretKey := &quot;&lt;secret-key&gt;&quot;

    sess, err := session.NewSession(&amp;aws.Config{
        Region: aws.String(&quot;&lt;region&gt;&quot;),
        Credentials: credentials.NewStaticCredentialsFromCreds(credentials.Value{
            AccessKeyID:     accessKey,
            SecretAccessKey: secretKey,
        }),
    })
    if err != nil {
        panic(err)
    }

    svc := kms.New(sess)
}
"><code>func main() {
    accessKey :<span class="hljs-operator">=</span> <span class="hljs-string">"&#x3C;access-key>"</span>
    secretKey :<span class="hljs-operator">=</span> <span class="hljs-string">"&#x3C;secret-key>"</span>

    sess, err :<span class="hljs-operator">=</span> session.NewSession(<span class="hljs-operator">&#x26;</span>aws.Config{
        Region: aws.String(<span class="hljs-string">"&#x3C;region>"</span>),
        Credentials: credentials.NewStaticCredentialsFromCreds(credentials.Value{
            AccessKeyID:     accessKey,
            SecretAccessKey: secretKey,
        }),
    })
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        panic(err)
    }

    svc :<span class="hljs-operator">=</span> kms.New(sess)
}
</code></pre><p>바로 KMS에 생성한 리소스에 접근해보자.</p><pre data-type="codeBlock" text="key, err := svc.GetPublicKey(&amp;kms.GetPublicKeyInput{
  KeyId: aws.String(&quot;&lt;key-id&gt;&quot;),
})
if err != nil {
  panic(err)
}
"><code>key, err :<span class="hljs-operator">=</span> svc.GetPublicKey(<span class="hljs-operator">&#x26;</span>kms.GetPublicKeyInput{
  KeyId: aws.String(<span class="hljs-string">"&#x3C;key-id>"</span>),
})
<span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
  panic(err)
}
</code></pre><p>잘 안된다면 변수 값을 확인해보거나, 리소스 권한 등을 의심해보자.</p><p>다음으로 <code>encoding/asn1</code> 라이브러리를 사용해서 퍼블릭 키를 추출해오자.</p><pre data-type="codeBlock" text="import &quot;encoding/asn1&quot;

type asn1EcPublicKey struct {
    EcPublicKeyInfo asn1EcPublicKeyInfo
    PublicKey       asn1.BitString
}

type asn1EcPublicKeyInfo struct {
    Algorithm  asn1.ObjectIdentifier
    Parameters asn1.ObjectIdentifier
}


func main() {
  
  // ...

  key, err := svc.GetPublicKey(&amp;kms.GetPublicKeyInput{
        KeyId: aws.String(&quot;&lt;key-id&gt;&quot;),
    })
    if err != nil {
        panic(err)
    }

    var asn1Key asn1EcPublicKey
    if _, err := asn1.Unmarshal(key.PublicKey, &amp;asn1Key); err != nil {
        panic(err)
    }
}
"><code><span class="hljs-keyword">import</span> <span class="hljs-string">"encoding/asn1"</span>

<span class="hljs-title"><span class="hljs-keyword">type</span></span> <span class="hljs-title">asn1EcPublicKey</span> <span class="hljs-title"><span class="hljs-keyword">struct</span></span> {
    <span class="hljs-title">EcPublicKeyInfo</span> <span class="hljs-title">asn1EcPublicKeyInfo</span>
    <span class="hljs-title">PublicKey</span>       <span class="hljs-title">asn1</span>.<span class="hljs-title">BitString</span>
}

<span class="hljs-title"><span class="hljs-keyword">type</span></span> <span class="hljs-title">asn1EcPublicKeyInfo</span> <span class="hljs-title"><span class="hljs-keyword">struct</span></span> {
    <span class="hljs-title">Algorithm</span>  <span class="hljs-title">asn1</span>.<span class="hljs-title">ObjectIdentifier</span>
    <span class="hljs-title">Parameters</span> <span class="hljs-title">asn1</span>.<span class="hljs-title">ObjectIdentifier</span>
}


<span class="hljs-title">func</span> <span class="hljs-title">main</span>() {
  
  <span class="hljs-comment">// ...</span>

  <span class="hljs-title">key</span>, <span class="hljs-title">err</span> :<span class="hljs-operator">=</span> <span class="hljs-title">svc</span>.<span class="hljs-title">GetPublicKey</span>(<span class="hljs-operator">&#x26;</span><span class="hljs-title">kms</span>.<span class="hljs-title">GetPublicKeyInput</span>{
        <span class="hljs-title">KeyId</span>: <span class="hljs-title">aws</span>.<span class="hljs-title">String</span>(<span class="hljs-string">"&#x3C;key-id>"</span>),
    })
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-title">nil</span> {
        <span class="hljs-title">panic</span>(<span class="hljs-title">err</span>)
    }

    <span class="hljs-title"><span class="hljs-keyword">var</span></span> <span class="hljs-title">asn1Key</span> <span class="hljs-title">asn1EcPublicKey</span>
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title"><span class="hljs-keyword">_</span></span>, <span class="hljs-title">err</span> :<span class="hljs-operator">=</span> <span class="hljs-title">asn1</span>.<span class="hljs-title">Unmarshal</span>(<span class="hljs-title">key</span>.<span class="hljs-title">PublicKey</span>, <span class="hljs-operator">&#x26;</span><span class="hljs-title">asn1Key</span>); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        panic(err)
    }
}
</code></pre><p>로그를 찍어보면 알겠지만 우리가 아는 퍼블릭 키와 많이 다르다.</p><p><code>go-ethereum</code>의 <code>crypto</code> 라이브러리를 사용해보자.</p><p>이제 이 public key를 통해 지갑 주소를 얻어낼 수 있다.</p><pre data-type="codeBlock" text="import &quot;github.com/ethereum/go-ethereum/crypto&quot;

var asn1Key asn1EcPublicKey
if _, err := asn1.Unmarshal(key.PublicKey, &amp;asn1Key); err != nil {
  panic(err)
}

log.Println(string(asn1Key.PublicKey.Bytes))

pubKey, err := crypto.UnmarshalPubkey(asn1Key.PublicKey.Bytes)
if err != nil {
  panic(err)
}

log.Println(pubKey)

keyAddr := crypto.PubkeyToAddress(*pubKey)
log.Println(keyAddr.Hex())
"><code><span class="hljs-keyword">import</span> <span class="hljs-string">"github.com/ethereum/go-ethereum/crypto"</span>

<span class="hljs-title"><span class="hljs-keyword">var</span></span> <span class="hljs-title">asn1Key</span> <span class="hljs-title">asn1EcPublicKey</span>
<span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title"><span class="hljs-keyword">_</span></span>, <span class="hljs-title">err</span> :<span class="hljs-operator">=</span> <span class="hljs-title">asn1</span>.<span class="hljs-title">Unmarshal</span>(<span class="hljs-title">key</span>.<span class="hljs-title">PublicKey</span>, <span class="hljs-operator">&#x26;</span><span class="hljs-title">asn1Key</span>); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
  panic(err)
}

log.Println(<span class="hljs-keyword">string</span>(asn1Key.PublicKey.Bytes))

pubKey, err :<span class="hljs-operator">=</span> crypto.UnmarshalPubkey(asn1Key.PublicKey.Bytes)
<span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
  panic(err)
}

log.Println(pubKey)

keyAddr :<span class="hljs-operator">=</span> crypto.PubkeyToAddress(<span class="hljs-operator">*</span>pubKey)
log.Println(keyAddr.Hex())
</code></pre><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://aws.amazon.com/ko/blogs/database/how-to-sign-ethereum-eip-1559-transactions-using-aws-kms/">https://aws.amazon.com/ko/blogs/database/how-to-sign-ethereum-eip-1559-transactions-using-aws-kms/</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/welthee/go-ethereum-aws-kms-tx-signer/tree/main">https://github.com/welthee/go-ethereum-aws-kms-tx-signer/tree/main</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[solidity: 0.8.21 version]]></title>
            <link>https://paragraph.com/@primrose/solidity-0-8-21-version</link>
            <guid>fNgEixxsTe0L4L2AvO5x</guid>
            <pubDate>Tue, 22 Aug 2023 18:05:33 GMT</pubDate>
            <description><![CDATA[솔리디티는 스마트 컨트랙트를 구현하기 위한 객체 지향 언어이다. 솔리디티는 EVM을 대상으로 설계되었으며, C++, 파이썬, 자바스크립트 등의 언어에 영향을 받아 만들어졌다. 솔리디티는 정적 타이핑 언어이고, 상속이나 라이브러리 등을 지원한다. 스마트 컨트랙트를 배포할 때는 최신 버전의 Solidity를 사용하는 것이 좋다. 현재 Solidity의 최신 버전은 0.8.21 버전이다. 2023년 7월 19일에 배포되었는데, 뭐가 바뀌었는지 읽어보면 좋겠다. 이 글에서는 간단하게 몇 가지만 다루겠다. https://soliditylang.org/blog/2023/07/19/solidity-0.8.21-release-announcement/Stack-to-memory mover always enabled via-IRIR이란 intermediate representation의 약어이다. 중간표현이라는 뜻으로, 컴파일러의 맥락에서 사용되는 개념이라고 한다. 이는 상위 수준 소스 코드와 ...]]></description>
            <content:encoded><![CDATA[<p>솔리디티는 스마트 컨트랙트를 구현하기 위한 객체 지향 언어이다.</p><p>솔리디티는 EVM을 대상으로 설계되었으며, C++, 파이썬, 자바스크립트 등의 언어에 영향을 받아 만들어졌다.</p><p>솔리디티는 정적 타이핑 언어이고, 상속이나 라이브러리 등을 지원한다.</p><p>스마트 컨트랙트를 배포할 때는 최신 버전의 Solidity를 사용하는 것이 좋다.</p><p>현재 Solidity의 최신 버전은 0.8.21 버전이다.</p><p>2023년 7월 19일에 배포되었는데, 뭐가 바뀌었는지 읽어보면 좋겠다.</p><p>이 글에서는 간단하게 몇 가지만 다루겠다.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://soliditylang.org/blog/2023/07/19/solidity-0.8.21-release-announcement/">https://soliditylang.org/blog/2023/07/19/solidity-0.8.21-release-announcement/</a></p><h1 id="h-stack-to-memory-mover-always-enabled-via-ir" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Stack-to-memory mover always enabled via-IR</h1><p>IR이란 intermediate representation의 약어이다.</p><p>중간표현이라는 뜻으로, 컴파일러의 맥락에서 사용되는 개념이라고 한다.</p><p>이는 상위 수준 소스 코드와 컴퓨터가 실행할 수 있는 기계어 코드 사이에 있는 소스 코드의 하위 수준 또는 추상적 표현을 나타낸다.</p><p>컴파일러가 소스 코드를 기계어 코드로 변환할 때 여러 단계를 거치는 경우가 많다.</p><p>소스 코드는 처음에 AST(추상 구문 트리) 또는 기타 상위 수준 구조로 구문 분석된다.</p><p>그런 다음 컴파일러는 이를 중간 표현으로 변환한다.</p><p>이 IR은 다음과 같은 다양한 목적으로 사용될 수 있다.</p><ol><li><p><strong>최적화</strong> : 많은 컴파일러는 IR 수준에서 최적화를 수행하여 불필요한 계산을 제거하거나 명령을 재정렬하여 보다 효율적으로 실행되도록 한다.</p></li><li><p><strong>이식성</strong> : 컴파일러는 소스 코드를 IR로 변환함으로써 IR을 다양한 아키텍처에 대한 다양한 기계 코드로 변환할 수 있다. 이를 통해 동일한 컴파일러가 여러 플랫폼을 대상으로 할 수 있다.</p></li><li><p><strong>분석</strong> : IR은 디버깅이나 프로파일링과 같은 다양한 목적을 위해 보다 쉽게 ​​분석할 수 있는 구조화되고 단순화된 코드 표현을 제공한다.</p></li></ol><p>IR을 사용하면 Frontend(언어별 구문 분석 및 의미 분석을 처리)와 Backend(기계별 코드 생성 및 최적화를 처리)를 분리하여 컴파일러 설계를 단순화할 수 있다.</p><p>사실 글을 몇 번이고 읽어봤지만 아직 와닿을만큼 이해가 되지는 않았다.</p><p>그나마 다음 구절에 집중해서 읽어보면 좋을 것 같다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/118ca41df9c464e67b0e05f398d59bb6bdededb26772c6ef6dbe5251051d9ecd.png" alt="사용되지 않는 변수 제거" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">사용되지 않는 변수 제거</figcaption></figure><h1 id="h-qualified-access-to-foreign-events" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Qualified access to foreign events</h1><p>Solidity 0.8.21 에서는 외부 컨트랙트와 인터페이스의 이벤트를 허용한다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/23481e759c4d40c4f0f4a8158aea4476ec95b4c85f29ad0ce96801e84acc95b5.png" alt="인터페이스, 라이브러리, 컨트랙트 모두 가능" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">인터페이스, 라이브러리, 컨트랙트 모두 가능</figcaption></figure><h1 id="h-relaxed-restrictions-on-initialization-of-immutable-variables" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Relaxed restrictions on initialization of immutable variables</h1><p>immutable 변수 초기화에 대한 제한이 완화되었다.</p><p>아래 사진을 참고하자.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7ab5faf228ccd0b01c27ac0b3978728fb10f3d9f9af9aba97801307112a9aa1f.png" alt="불변변수 초기화 완화" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">불변변수 초기화 완화</figcaption></figure>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[go: json-iterator]]></title>
            <link>https://paragraph.com/@primrose/go-json-iterator</link>
            <guid>nUhAox6OSBb4ZUVT1igJ</guid>
            <pubDate>Sat, 19 Aug 2023 14:17:10 GMT</pubDate>
            <description><![CDATA[jsonitergo에서 어떤 구조체를 byte array로 만들때 encoding/json 라이브러리를 많이들 사용한다. json-iterator 패키지는 encoding/json 패키지와 완벽하게 호환되며, 성능은 훨씬 좋다고 한다. 물론 encoding/json이 표준 패키지이기 때문에 별 일 없으면 그대로 쓰면 된다.package main import ( jsoniter "github.com/json-iterator/go" ) func main() { var data = struct { Name string Age int }{Name: "John Doe", Age: 20} var json = jsoniter.ConfigCompatibleWithStandardLibrary b, err := json.Marshal(data) if err != nil { panic(err) } println(string(b)) } 사용법은 위와 같이 사용하면 된다. jsoniter의 Confi...]]></description>
            <content:encoded><![CDATA[<h1 id="h-jsoniter" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">jsoniter</h1><p>go에서 어떤 구조체를 byte array로 만들때 <code>encoding/json</code> 라이브러리를 많이들 사용한다.</p><p>json-iterator 패키지는 <code>encoding/json</code> 패키지와 완벽하게 호환되며, 성능은 훨씬 좋다고 한다.</p><p>물론 <code>encoding/json</code>이 표준 패키지이기 때문에 별 일 없으면 그대로 쓰면 된다.</p><pre data-type="codeBlock" text="package main

import (
    jsoniter &quot;github.com/json-iterator/go&quot;
)

func main() {
    var data = struct {
        Name string
        Age  int
    }{Name: &quot;John Doe&quot;, Age: 20}

    var json = jsoniter.ConfigCompatibleWithStandardLibrary

    b, err := json.Marshal(data)
    if err != nil {
        panic(err)
    }

    println(string(b))
}
"><code><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    jsoniter <span class="hljs-string">"github.com/json-iterator/go"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> data = <span class="hljs-keyword">struct</span> {
        Name <span class="hljs-type">string</span>
        Age  <span class="hljs-type">int</span>
    }{Name: <span class="hljs-string">"John Doe"</span>, Age: <span class="hljs-number">20</span>}

    <span class="hljs-keyword">var</span> json = jsoniter.ConfigCompatibleWithStandardLibrary

    b, err := json.Marshal(data)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    <span class="hljs-built_in">println</span>(<span class="hljs-type">string</span>(b))
}
</code></pre><p>사용법은 위와 같이 사용하면 된다.</p><p>jsoniter의 <code>ConfigCompatibleWithStandardLibrary</code> 를 해석해보면 딱봐도 완전호환이 되는 라이브러리라는 뜻이다. 이 외에도 여러 옵션이 있다.</p><p><code>ConfigCompatibleWithStandardLibrary</code>는 아래와 같이 생겼다.</p><pre data-type="codeBlock" text="// ConfigCompatibleWithStandardLibrary tries to be 100% compatible with standard library behavior
var ConfigCompatibleWithStandardLibrary = Config{
    EscapeHTML:             true,
    SortMapKeys:            true,
    ValidateJsonRawMessage: true,
}.Froze()
"><code><span class="hljs-comment">// ConfigCompatibleWithStandardLibrary tries to be 100% compatible with standard library behavior</span>
<span class="hljs-keyword">var</span> ConfigCompatibleWithStandardLibrary <span class="hljs-operator">=</span> Config{
    EscapeHTML:             <span class="hljs-literal">true</span>,
    SortMapKeys:            <span class="hljs-literal">true</span>,
    ValidateJsonRawMessage: <span class="hljs-literal">true</span>,
}.Froze()
</code></pre>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[go-ethereum: gas estimate]]></title>
            <link>https://paragraph.com/@primrose/go-ethereum-gas-estimate</link>
            <guid>011an4rD0Cp4okgtyNig</guid>
            <pubDate>Sat, 19 Aug 2023 07:07:51 GMT</pubDate>
            <description><![CDATA[Smart contract gas fee우리가 스마트 컨트랙트의 코드를 실행할 때, 해당 코드는 EVM에서 실행되고 gas estimate를 거친 뒤, 특정 가스비가 소모된다. geth 클라이언트는 몇 차례 써본 적이 있으나 사실 가스비가 정확하게 어떻게 책정되고 어떻게 소모되는지에 대해서는 모른다. 그러나 분명히 어딘가에 코드로 해당 비즈니스 로직을 구현한 곳이 있을 것이다. 이에 대해서 go-ethereum 코드를 분석하면서 알아보려고 한다. 코드가 워낙 방대해서 시행착오가 꽤 많았다. 우선 geth 패키지를 이용해서 gas estimate를 하는 부분에 대해서 찾아봤다. gas estimate를 하는 함수는 다음과 같다.var conn *ethclient.Client var err error // Dial rpc endpoint if conn, err = ethclient.Dial(rpcEndpoint); err != nil { return nil, err } conn.E...]]></description>
            <content:encoded><![CDATA[<h1 id="h-smart-contract-gas-fee" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Smart contract gas fee</h1><p>우리가 스마트 컨트랙트의 코드를 실행할 때, 해당 코드는 EVM에서 실행되고 gas estimate를 거친 뒤, 특정 가스비가 소모된다.</p><p>geth 클라이언트는 몇 차례 써본 적이 있으나 사실 가스비가 정확하게 어떻게 책정되고 어떻게 소모되는지에 대해서는 모른다.</p><p>그러나 분명히 어딘가에 코드로 해당 비즈니스 로직을 구현한 곳이 있을 것이다.</p><p>이에 대해서 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/go-ethereum">go-ethereum</a> 코드를 분석하면서 알아보려고 한다.</p><p>코드가 워낙 방대해서 시행착오가 꽤 많았다.</p><p>우선 geth 패키지를 이용해서 gas estimate를 하는 부분에 대해서 찾아봤다.</p><p>gas estimate를 하는 함수는 다음과 같다.</p><pre data-type="codeBlock" text="var conn *ethclient.Client
var err error

// Dial rpc endpoint
if conn, err = ethclient.Dial(rpcEndpoint); err != nil {
    return nil, err
}

conn.EstimateGas(context.Background(), ethereum.CallMsg{
    ... 
})
"><code><span class="hljs-keyword">var</span> conn <span class="hljs-operator">*</span>ethclient.Client
<span class="hljs-keyword">var</span> err <span class="hljs-function"><span class="hljs-keyword">error</span>

<span class="hljs-comment">// Dial rpc endpoint</span>
<span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">conn</span>, <span class="hljs-title">err</span> = <span class="hljs-title">ethclient</span>.<span class="hljs-title">Dial</span>(<span class="hljs-params">rpcEndpoint</span>)</span>; err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    <span class="hljs-keyword">return</span> nil, err
}

conn.EstimateGas(context.Background(), ethereum.CallMsg{
    ... 
})
</code></pre><p><code>EstimatGas</code> 함수의 definition을 찾아보면 아래와 같다.</p><pre data-type="codeBlock" text="package ethclient

// EstimateGas tries to estimate the gas needed to execute a specific transaction based on
// the current pending state of the backend blockchain. There is no guarantee that this is
// the true gas limit requirement as other transactions may be added or removed by miners,
// but it should provide a basis for setting a reasonable default.
func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
  var hex hexutil.Uint64
  err := ec.c.CallContext(ctx, &amp;hex, &quot;eth_estimateGas&quot;, toCallArg(msg))
  if err != nil {
    return 0, err
  }
  return uint64(hex), nil
}
"><code>package ethclient

<span class="hljs-comment">// EstimateGas tries to estimate the gas needed to execute a specific transaction based on</span>
<span class="hljs-comment">// the current pending state of the backend blockchain. There is no guarantee that this is</span>
<span class="hljs-comment">// the true gas limit requirement as other transactions may be added or removed by miners,</span>
<span class="hljs-comment">// but it should provide a basis for setting a reasonable default.</span>
func (ec <span class="hljs-operator">*</span>Client) EstimateGas(ctx context.Context, <span class="hljs-built_in">msg</span> ethereum.CallMsg) (<span class="hljs-keyword">uint64</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
  <span class="hljs-keyword">var</span> hex hexutil.Uint64
  err :<span class="hljs-operator">=</span> ec.c.CallContext(ctx, <span class="hljs-operator">&#x26;</span>hex, <span class="hljs-string">"eth_estimateGas"</span>, toCallArg(<span class="hljs-built_in">msg</span>))
  <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, err
  }
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">uint64</span>(hex), nil
}
</code></pre><p>봐야할 부분이 굉장히 많다. 천리 길도 한 걸음 부터라고 하니까 하나하나 뜯어보자.</p><p><code>hexutil</code>은 단순히 16진수를 위해 go-ethereum에서 사용하는 패키지이다.</p><p>Client 객체 자체는 아래와 같이 생겼다. <code>ec.c</code> 부분의 <code>c</code>는 rpc client를 말한다.</p><pre data-type="codeBlock" text="package ethclient

// Client defines typed wrappers for the Ethereum RPC API.
type Client struct {
  c *rpc.Client
}
"><code>package ethclient

<span class="hljs-comment">// Client defines typed wrappers for the Ethereum RPC API.</span>
<span class="hljs-keyword">type</span> Client <span class="hljs-keyword">struct</span> {
  c <span class="hljs-operator">*</span>rpc.Client
}
</code></pre><p>rpc client를 통해서 <code>CallContext</code>라는 함수를 실행한다.</p><p>해당 메소드를 보자.</p><pre data-type="codeBlock" text="package rpc

// CallContext performs a JSON-RPC call with the given arguments. If the context is
// canceled before the call has successfully returned, CallContext returns immediately.
//
// The result must be a pointer so that package json can unmarshal into it. You
// can also pass nil, in which case the result is ignored.
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
  if result != nil &amp;&amp; reflect.TypeOf(result).Kind() != reflect.Ptr {
    return fmt.Errorf(&quot;call result parameter must be pointer or nil interface: %v&quot;, result)
  }
  msg, err := c.newMessage(method, args...)
  if err != nil {
    return err
  }
  op := &amp;requestOp{ids: []json.RawMessage{msg.ID}, resp: make(chan *jsonrpcMessage, 1)}

  if c.isHTTP {
    err = c.sendHTTP(ctx, op, msg)
  } else {
    err = c.send(ctx, op, msg)
  }
  if err != nil {
    return err
  }

  // dispatch has accepted the request and will close the channel when it quits.
  switch resp, err := op.wait(ctx, c); {
  case err != nil:
    return err
  case resp.Error != nil:
    return resp.Error
  case len(resp.Result) == 0:
    return ErrNoResult
  default:
    if result == nil {
      return nil
    }
    return json.Unmarshal(resp.Result, result)
  }
}
"><code>package rpc

<span class="hljs-comment">// CallContext performs a JSON-RPC call with the given arguments. If the context is</span>
<span class="hljs-comment">// canceled before the call has successfully returned, CallContext returns immediately.</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">// The result must be a pointer so that package json can unmarshal into it. You</span>
<span class="hljs-comment">// can also pass nil, in which case the result is ignored.</span>
func (c <span class="hljs-operator">*</span>Client) CallContext(ctx context.Context, result <span class="hljs-class"><span class="hljs-keyword">interface</span></span>{}, method <span class="hljs-keyword">string</span>, args ...interface{}) <span class="hljs-function"><span class="hljs-keyword">error</span> </span>{
  <span class="hljs-keyword">if</span> result <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> reflect.TypeOf(result).Kind() <span class="hljs-operator">!</span><span class="hljs-operator">=</span> reflect.Ptr {
    <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"call result parameter must be pointer or nil interface: %v"</span>, result)
  }
  <span class="hljs-built_in">msg</span>, err :<span class="hljs-operator">=</span> c.newMessage(method, args...)
  <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    <span class="hljs-keyword">return</span> err
  }
  op :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>requestOp{ids: []json.RawMessage{<span class="hljs-built_in">msg</span>.ID}, resp: make(chan <span class="hljs-operator">*</span>jsonrpcMessage, <span class="hljs-number">1</span>)}

  <span class="hljs-keyword">if</span> c.isHTTP {
    err <span class="hljs-operator">=</span> c.sendHTTP(ctx, op, <span class="hljs-built_in">msg</span>)
  } <span class="hljs-keyword">else</span> {
    err <span class="hljs-operator">=</span> c.<span class="hljs-built_in">send</span>(ctx, op, <span class="hljs-built_in">msg</span>)
  }
  <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    <span class="hljs-keyword">return</span> err
  }

  <span class="hljs-comment">// dispatch has accepted the request and will close the channel when it quits.</span>
  switch resp, err :<span class="hljs-operator">=</span> op.wait(ctx, c); {
  case err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil:
    <span class="hljs-keyword">return</span> err
  case resp.Error <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil:
    <span class="hljs-keyword">return</span> resp.Error
  case len(resp.Result) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>:
    <span class="hljs-keyword">return</span> ErrNoResult
  default:
    <span class="hljs-keyword">if</span> result <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
      <span class="hljs-keyword">return</span> nil
    }
    <span class="hljs-keyword">return</span> json.Unmarshal(resp.Result, result)
  }
}
</code></pre><p>여기서의 Client는 rpc.Client이다.</p><p>컨텍스트와 결과값, 메소드, 인자를 받아서 rpc 호출을 시도한다.</p><p>좀 전에 <code>EstimateGas</code> 에서 <code>CallContext</code>를 호출할 때 result로 hex 변수의 주소값을 넘겼다.</p><p>따라서 result에 결과값을 담는 방식으로 진행이 될 것이다.</p><p>여기서 중요한 것은 message를 통해서 결국 “eth_estimateGas” 라는 메소드를 실행한다는 것인데, 결국 가스비를 estimate하는 로직은 해당 메소드에 있을거라는 것이다.</p><p>따라서 우리가 찾아야하는 것은 “eth_estimateGas” 의 구현체이다.</p><p>go-ethereum의 <code>ethapi</code> 패키지를 찾아보면, 다음 메소드를 찾을 수 있다.</p><pre data-type="codeBlock" text="package ethapi
// EstimateGas returns the lowest possible gas limit that allows the transaction to run
// successfully at block `blockNrOrHash`, or the latest block if `blockNrOrHash` is unspecified. It
// returns error if the transaction would revert or if there are unexpected failures. The returned
// value is capped by both `args.Gas` (if non-nil &amp; non-zero) and the backend&apos;s RPCGasCap
// configuration (if non-zero).
func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) {
    bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
    if blockNrOrHash != nil {
        bNrOrHash = *blockNrOrHash
    }
    return DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides, s.b.RPCGasCap())
}
"><code>package ethapi
<span class="hljs-comment">// EstimateGas returns the lowest possible gas limit that allows the transaction to run</span>
<span class="hljs-comment">// successfully at block `blockNrOrHash`, or the latest block if `blockNrOrHash` is unspecified. It</span>
<span class="hljs-comment">// returns error if the transaction would revert or if there are unexpected failures. The returned</span>
<span class="hljs-comment">// value is capped by both `args.Gas` (if non-nil &#x26; non-zero) and the backend's RPCGasCap</span>
<span class="hljs-comment">// configuration (if non-zero).</span>
func (s <span class="hljs-operator">*</span>BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash <span class="hljs-operator">*</span>rpc.BlockNumberOrHash, overrides <span class="hljs-operator">*</span>StateOverride) (hexutil.Uint64, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    bNrOrHash :<span class="hljs-operator">=</span> rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
    <span class="hljs-keyword">if</span> blockNrOrHash <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        bNrOrHash <span class="hljs-operator">=</span> <span class="hljs-operator">*</span>blockNrOrHash
    }
    <span class="hljs-keyword">return</span> DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides, s.b.RPCGasCap())
}
</code></pre><p>블록 넘버/해시를 받아서 <code>DoEstimateGas</code> 함수를 실행한다.</p><p><code>DoEstimatsGas</code> 역시 같은 파일에 위치하는데, 함수가 좀 길다.</p><pre data-type="codeBlock" text="package ethapi

// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run
// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if
// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil &amp;
// non-zero) and `gasCap` (if non-zero).
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) {
  // Binary search the gas limit, as it may need to be higher than the amount used
  var (
    lo uint64 // lowest-known gas limit where tx execution fails
    hi uint64 // lowest-known gas limit where tx execution succeeds
  )
  // Use zero address if sender unspecified.
  if args.From == nil {
    args.From = new(common.Address)
  }
  // Determine the highest gas limit can be used during the estimation.
  if args.Gas != nil &amp;&amp; uint64(*args.Gas) &gt;= params.TxGas {
    hi = uint64(*args.Gas)
  } else {
    // Retrieve the block to act as the gas ceiling
    block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
    if err != nil {
      return 0, err
    }
    if block == nil {
      return 0, errors.New(&quot;block not found&quot;)
    }
    hi = block.GasLimit()
  }
  // Normalize the max fee per gas the call is willing to spend.
  var feeCap *big.Int
  if args.GasPrice != nil &amp;&amp; (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
    return 0, errors.New(&quot;both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified&quot;)
  } else if args.GasPrice != nil {
    feeCap = args.GasPrice.ToInt()
  } else if args.MaxFeePerGas != nil {
    feeCap = args.MaxFeePerGas.ToInt()
  } else {
    feeCap = common.Big0
  }

  state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
  if state == nil || err != nil {
    return 0, err
  }
  if err := overrides.Apply(state); err != nil {
    return 0, err
  }

  // Recap the highest gas limit with account&apos;s available balance.
  if feeCap.BitLen() != 0 {
    balance := state.GetBalance(*args.From) // from can&apos;t be nil
    available := new(big.Int).Set(balance)
    if args.Value != nil {
      if args.Value.ToInt().Cmp(available) &gt;= 0 {
        return 0, core.ErrInsufficientFundsForTransfer
      }
      available.Sub(available, args.Value.ToInt())
    }
    allowance := new(big.Int).Div(available, feeCap)

    // If the allowance is larger than maximum uint64, skip checking
    if allowance.IsUint64() &amp;&amp; hi &gt; allowance.Uint64() {
      transfer := args.Value
      if transfer == nil {
        transfer = new(hexutil.Big)
      }
      log.Warn(&quot;Gas estimation capped by limited funds&quot;, &quot;original&quot;, hi, &quot;balance&quot;, balance,
        &quot;sent&quot;, transfer.ToInt(), &quot;maxFeePerGas&quot;, feeCap, &quot;fundable&quot;, allowance)
      hi = allowance.Uint64()
    }
  }
  // Recap the highest gas allowance with specified gascap.
  if gasCap != 0 &amp;&amp; hi &gt; gasCap {
    log.Warn(&quot;Caller gas above allowance, capping&quot;, &quot;requested&quot;, hi, &quot;cap&quot;, gasCap)
    hi = gasCap
  }

  // We first execute the transaction at the highest allowable gas limit, since if this fails we
  // can return error immediately.
  failed, result, err := executeEstimate(ctx, b, args, state.Copy(), header, gasCap, hi)
  if err != nil {
    return 0, err
  }
  if failed {
    if result != nil &amp;&amp; result.Err != vm.ErrOutOfGas {
      if len(result.Revert()) &gt; 0 {
        return 0, newRevertError(result)
      }
      return 0, result.Err
    }
    return 0, fmt.Errorf(&quot;gas required exceeds allowance (%d)&quot;, hi)
  }
  // For almost any transaction, the gas consumed by the unconstrained execution above
  // lower-bounds the gas limit required for it to succeed. One exception is those txs that
  // explicitly check gas remaining in order to successfully execute within a given limit, but we
  // probably don&apos;t want to return a lowest possible gas limit for these cases anyway.
  lo = result.UsedGas - 1

  // Binary search for the smallest gas limit that allows the tx to execute successfully.
  for lo+1 &lt; hi {
    mid := (hi + lo) / 2
    if mid &gt; lo*2 {
      // Most txs don&apos;t need much higher gas limit than their gas used, and most txs don&apos;t
      // require near the full block limit of gas, so the selection of where to bisect the
      // range here is skewed to favor the low side.
      mid = lo * 2
    }
    failed, _, err = executeEstimate(ctx, b, args, state.Copy(), header, gasCap, mid)
    if err != nil {
      // This should not happen under normal conditions since if we make it this far the
      // transaction had run without error at least once before.
      log.Error(&quot;execution error in estimate gas&quot;, &quot;err&quot;, err)
      return 0, err
    }
    if failed {
      lo = mid
    } else {
      hi = mid
    }
  }
  return hexutil.Uint64(hi), nil
}
"><code>package ethapi

<span class="hljs-comment">// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run</span>
<span class="hljs-comment">// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if</span>
<span class="hljs-comment">// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil &#x26;</span>
<span class="hljs-comment">// non-zero) and `gasCap` (if non-zero).</span>
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides <span class="hljs-operator">*</span>StateOverride, gasCap <span class="hljs-keyword">uint64</span>) (hexutil.Uint64, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
  <span class="hljs-comment">// Binary search the gas limit, as it may need to be higher than the amount used</span>
  <span class="hljs-keyword">var</span> (
    lo <span class="hljs-keyword">uint64</span> <span class="hljs-comment">// lowest-known gas limit where tx execution fails</span>
    hi <span class="hljs-keyword">uint64</span> <span class="hljs-comment">// lowest-known gas limit where tx execution succeeds</span>
  )
  <span class="hljs-comment">// Use zero address if sender unspecified.</span>
  <span class="hljs-keyword">if</span> args.From <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
    args.From <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(common.Address)
  }
  <span class="hljs-comment">// Determine the highest gas limit can be used during the estimation.</span>
  <span class="hljs-keyword">if</span> args.Gas <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-keyword">uint64</span>(<span class="hljs-operator">*</span>args.Gas) <span class="hljs-operator">></span><span class="hljs-operator">=</span> params.TxGas {
    hi <span class="hljs-operator">=</span> <span class="hljs-keyword">uint64</span>(<span class="hljs-operator">*</span>args.Gas)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Retrieve the block to act as the gas ceiling</span>
    <span class="hljs-built_in">block</span>, err :<span class="hljs-operator">=</span> b.BlockByNumberOrHash(ctx, blockNrOrHash)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, err
    }
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">block</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, errors.New(<span class="hljs-string">"block not found"</span>)
    }
    hi <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.GasLimit()
  }
  <span class="hljs-comment">// Normalize the max fee per gas the call is willing to spend.</span>
  <span class="hljs-keyword">var</span> feeCap <span class="hljs-operator">*</span>big.Int
  <span class="hljs-keyword">if</span> args.GasPrice <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> (args.MaxFeePerGas <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">|</span><span class="hljs-operator">|</span> args.MaxPriorityFeePerGas <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil) {
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, errors.New(<span class="hljs-string">"both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"</span>)
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> args.GasPrice <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    feeCap <span class="hljs-operator">=</span> args.GasPrice.ToInt()
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> args.MaxFeePerGas <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    feeCap <span class="hljs-operator">=</span> args.MaxFeePerGas.ToInt()
  } <span class="hljs-keyword">else</span> {
    feeCap <span class="hljs-operator">=</span> common.Big0
  }

  state, header, err :<span class="hljs-operator">=</span> b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
  <span class="hljs-keyword">if</span> state <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">|</span><span class="hljs-operator">|</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, err
  }
  <span class="hljs-keyword">if</span> err :<span class="hljs-operator">=</span> overrides.Apply(state); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, err
  }

  <span class="hljs-comment">// Recap the highest gas limit with account's available balance.</span>
  <span class="hljs-keyword">if</span> feeCap.BitLen() <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> {
    balance :<span class="hljs-operator">=</span> state.GetBalance(<span class="hljs-operator">*</span>args.From) <span class="hljs-comment">// from can't be nil</span>
    available :<span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(big.Int).Set(balance)
    <span class="hljs-keyword">if</span> args.Value <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
      <span class="hljs-keyword">if</span> args.Value.ToInt().Cmp(available) <span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, core.ErrInsufficientFundsForTransfer
      }
      available.Sub(available, args.Value.ToInt())
    }
    allowance :<span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(big.Int).Div(available, feeCap)

    <span class="hljs-comment">// If the allowance is larger than maximum uint64, skip checking</span>
    <span class="hljs-keyword">if</span> allowance.IsUint64() <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> hi <span class="hljs-operator">></span> allowance.Uint64() {
      transfer :<span class="hljs-operator">=</span> args.Value
      <span class="hljs-keyword">if</span> transfer <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
        transfer <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(hexutil.Big)
      }
      log.Warn(<span class="hljs-string">"Gas estimation capped by limited funds"</span>, <span class="hljs-string">"original"</span>, hi, <span class="hljs-string">"balance"</span>, balance,
        <span class="hljs-string">"sent"</span>, transfer.ToInt(), <span class="hljs-string">"maxFeePerGas"</span>, feeCap, <span class="hljs-string">"fundable"</span>, allowance)
      hi <span class="hljs-operator">=</span> allowance.Uint64()
    }
  }
  <span class="hljs-comment">// Recap the highest gas allowance with specified gascap.</span>
  <span class="hljs-keyword">if</span> gasCap <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> hi <span class="hljs-operator">></span> gasCap {
    log.Warn(<span class="hljs-string">"Caller gas above allowance, capping"</span>, <span class="hljs-string">"requested"</span>, hi, <span class="hljs-string">"cap"</span>, gasCap)
    hi <span class="hljs-operator">=</span> gasCap
  }

  <span class="hljs-comment">// We first execute the transaction at the highest allowable gas limit, since if this fails we</span>
  <span class="hljs-comment">// can return error immediately.</span>
  failed, result, err :<span class="hljs-operator">=</span> executeEstimate(ctx, b, args, state.Copy(), header, gasCap, hi)
  <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, err
  }
  <span class="hljs-keyword">if</span> failed {
    <span class="hljs-keyword">if</span> result <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> result.Err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> vm.ErrOutOfGas {
      <span class="hljs-keyword">if</span> len(result.Revert()) <span class="hljs-operator">></span> <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, newRevertError(result)
      }
      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, result.Err
    }
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, fmt.Errorf(<span class="hljs-string">"gas required exceeds allowance (%d)"</span>, hi)
  }
  <span class="hljs-comment">// For almost any transaction, the gas consumed by the unconstrained execution above</span>
  <span class="hljs-comment">// lower-bounds the gas limit required for it to succeed. One exception is those txs that</span>
  <span class="hljs-comment">// explicitly check gas remaining in order to successfully execute within a given limit, but we</span>
  <span class="hljs-comment">// probably don't want to return a lowest possible gas limit for these cases anyway.</span>
  lo <span class="hljs-operator">=</span> result.UsedGas <span class="hljs-operator">-</span> <span class="hljs-number">1</span>

  <span class="hljs-comment">// Binary search for the smallest gas limit that allows the tx to execute successfully.</span>
  <span class="hljs-keyword">for</span> lo<span class="hljs-operator">+</span><span class="hljs-number">1</span> <span class="hljs-operator">&#x3C;</span> hi {
    mid :<span class="hljs-operator">=</span> (hi <span class="hljs-operator">+</span> lo) <span class="hljs-operator">/</span> <span class="hljs-number">2</span>
    <span class="hljs-keyword">if</span> mid <span class="hljs-operator">></span> lo<span class="hljs-operator">*</span><span class="hljs-number">2</span> {
      <span class="hljs-comment">// Most txs don't need much higher gas limit than their gas used, and most txs don't</span>
      <span class="hljs-comment">// require near the full block limit of gas, so the selection of where to bisect the</span>
      <span class="hljs-comment">// range here is skewed to favor the low side.</span>
      mid <span class="hljs-operator">=</span> lo <span class="hljs-operator">*</span> <span class="hljs-number">2</span>
    }
    failed, <span class="hljs-keyword">_</span>, err <span class="hljs-operator">=</span> executeEstimate(ctx, b, args, state.Copy(), header, gasCap, mid)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
      <span class="hljs-comment">// This should not happen under normal conditions since if we make it this far the</span>
      <span class="hljs-comment">// transaction had run without error at least once before.</span>
      log.Error(<span class="hljs-string">"execution error in estimate gas"</span>, <span class="hljs-string">"err"</span>, err)
      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, err
    }
    <span class="hljs-keyword">if</span> failed {
      lo <span class="hljs-operator">=</span> mid
    } <span class="hljs-keyword">else</span> {
      hi <span class="hljs-operator">=</span> mid
    }
  }
  <span class="hljs-keyword">return</span> hexutil.Uint64(hi), nil
}
</code></pre><p>하나하나 살펴보자.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c34135a983f76d7ccf5b36f07b03e5e1d7e5dd30f0417d7d40c6c225e0ebbbc1.png" alt="트랜잭션이 성공했을 때와 실패했을 때로 나누어 선언" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">트랜잭션이 성공했을 때와 실패했을 때로 나누어 선언</figcaption></figure><p>lo, hi 변수를 각각 선언하고, 성공했을 때와 실패했을 때로 나누어서 사용하는 것 같다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/71a33a4e578efc13180c86b7af7774a4812f3553058f90cf523609727d600e4a.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><code>args.From</code> 이 nil인 경우 <code>new(common.Address)</code>를 통해서 <code>ZeroAddress</code>를 생성한다.</p><p>예외처리로 에러를 반환하는게 아니라 0주소값을 할당하고 그대로 진행하는듯 하다.</p><p>그 다음 구문의 조건문을 해석해보자.</p><h4 id="h-argsgas-nil-argsgas-value-uint64-paramstxgas" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><code>args.Gas</code>가 nil이 아니고, args.Gas의 value를 uint64로 캐스팅했을때 params.TxGas보다 높다면</h4><p>이라는 조건이다. 이쯤에서 함수의 프로토타입을 다시보자.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/af27e9e254f9304bb3e982608d0618493f4791b8602737eeb4b2dd3bc434b41b.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><code>args</code>는 트랜잭션 인자이다. params는 어디에 있을까? go-ethereum의 <code>params</code>패키지의 TxGas라는 constant 값이 있다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/813c2e683d4cb74db3081f787cf3eee982a869ebafb92aa84b9d3e40b208fcd3.png" alt="각종 constant값. 이제 구글링 하지말고 이거 보면 될 것 같다." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">각종 constant값. 이제 구글링 하지말고 이거 보면 될 것 같다.</figcaption></figure><p>주석을 참고하면, 컨트랙트 생성이 아닌 트랜잭션에 대한 기본 가스비로 생각할 수 있다.</p><p>그러나 payable은 불가능하다는 뜻으로 보인다.</p><p>아무튼 돌아와서 다시 살펴보자.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/80d02f2013940531c001c4eee386af9db537ff68e43e00e63beee202329d3559.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>이제는 한 번에 알 수 있다. 트랜잭션 인자의 가스가 기본 가스비 (21,000)보다 높다면</p><p>hi(트랜잭션이 성공했을 때의 가스)를 해당 가스비로 설정하라는 뜻이다.</p><p><code>else …</code> 구문에 해당되려면 <code>args.Gas</code>가 nil이거나, <code>args.Gas</code>가 <code>params.TxGas</code>보다 낮아야 한다.</p><p>그런 경우에는 현재 블록을 가져오고, 블록의 <code>GasLimit</code>으로 적용한다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/86570430fbc89e8e04247fcb336af20c4a640dccc58fa28d50f48c1888b47e18.png" alt="feeCap을 설정하는 부분으로 보인다." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">feeCap을 설정하는 부분으로 보인다.</figcaption></figure><p>방금 위에서 한 것은 <code>hi</code>를 설정하는 가정이었다. 이제 feeCap을 설정해야한다.</p><blockquote><p>GasFeeCap: 트랜잭션에 대해 지불하고자 하는 최대 금액 (base fee + priority fee)</p></blockquote><p>첫 번째 if문은 전체 nil guard에 대한 부분이고, 그 다음은 세 가지 경우로 나뉜다.</p><ol><li><p>GasPrice가 nil이 아니다.</p></li><li><p>MaxFeePerGas가 nil이 아니다.</p></li><li><p>MaxPriorityFeePerGas가 nil이 아니다.</p></li></ol><p>1번의 경우에는 feeCap이 GasPrice로 설정된다.</p><p>2번의 경우에는 Max 상한선이 설정되어있으므로 그대로 설정해준다.</p><p>3번의 경우에는 0으로 설정한다. (의아하지만 일단 넘어간다)</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ff403ec9dea263dc31c106d4829b75503044bce62fcb3bd8963020ab90d21025.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>그 다음 쭉쭉 넘어가서, from 주소의 잔고와 비교해서 변수들을 재 점검하는 로직인듯하다.</p><p>allowance를 설정하는 부분을 보면 현재 계정의 잔고를 feeCap으로 나눈다.</p><p>그리고 그 allowance가 uint64 범위 이상인 경우에는 체크를 멈춘다.</p><p>그 밑을 보면 <code>hi</code>가 <code>gasCap</code>보다 높은 경우에는 <code>gasCap</code>으로 다시 설정해준다.</p><blockquote><p>결국 여기까지의 로직은 모두 가스 한도를 설정해주는 로직이다.</p></blockquote><p>그 다음부터의 로직이 우리가 원하던, 실제로 코드를 실행시켜서 가스비를 계산하는 로직이 나온다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/89feb67b3d96cba139535f72db7b5794206534ad945a740a2836b30c96d8812a.png" alt="이분 탐색이 사용된 것을 볼 수 있다. " blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">이분 탐색이 사용된 것을 볼 수 있다.</figcaption></figure><p>맨 위에서 우선 allowance의 범위 내에서 가장 높은 가스비를 사용해서 코드를 실행하고, 실패할 경우 곧장 에러를 반환한다.</p><p>만약 성공한 경우에는 이분탐색으로 가장 낮은 가스비를 찾는다.</p><p>즉 <code>eth_estimateGas</code>는 코드를 EVM 상에서 여러 번 수행해봄으로써 가장 낮은 가스비를 찾아주는 것이다.</p><p>이제 그럼 <code>executeEstimate</code> 함수를 들춰보자.</p><pre data-type="codeBlock" text="// executeEstimate is a helper that executes the transaction under a given gas limit and returns
// true if the transaction fails for a reason that might be related to not enough gas. A non-nil
// error means execution failed due to reasons unrelated to the gas limit.
func executeEstimate(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, gasCap uint64, gasLimit uint64) (bool, *core.ExecutionResult, error) {
    args.Gas = (*hexutil.Uint64)(&amp;gasLimit)
    result, err := doCall(ctx, b, args, state, header, nil, nil, 0, gasCap)
    if err != nil {
        if errors.Is(err, core.ErrIntrinsicGas) {
            return true, nil, nil // Special case, raise gas limit
        }
        return true, nil, err // Bail out
    }
    return result.Failed(), result, nil
}
"><code><span class="hljs-comment">// executeEstimate is a helper that executes the transaction under a given gas limit and returns</span>
<span class="hljs-comment">// true if the transaction fails for a reason that might be related to not enough gas. A non-nil</span>
<span class="hljs-comment">// error means execution failed due to reasons unrelated to the gas limit.</span>
func executeEstimate(ctx context.Context, b Backend, args TransactionArgs, state <span class="hljs-operator">*</span>state.StateDB, header <span class="hljs-operator">*</span>types.Header, gasCap <span class="hljs-keyword">uint64</span>, gasLimit <span class="hljs-keyword">uint64</span>) (<span class="hljs-keyword">bool</span>, <span class="hljs-operator">*</span>core.ExecutionResult, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    args.Gas <span class="hljs-operator">=</span> (<span class="hljs-operator">*</span>hexutil.Uint64)(<span class="hljs-operator">&#x26;</span>gasLimit)
    result, err :<span class="hljs-operator">=</span> doCall(ctx, b, args, state, header, nil, nil, <span class="hljs-number">0</span>, gasCap)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">if</span> errors.Is(err, core.ErrIntrinsicGas) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>, nil, nil <span class="hljs-comment">// Special case, raise gas limit</span>
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>, nil, err <span class="hljs-comment">// Bail out</span>
    }
    <span class="hljs-keyword">return</span> result.Failed(), result, nil
}
</code></pre><p>일단 로직만 살펴보자.</p><p><code>doCall</code> 함수를 이용해서 실패인지 아닌지에 대한 부분만 리턴한다.</p><p><code>doCall</code> 함수도 까보자.</p><pre data-type="codeBlock" text="func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
    if err := overrides.Apply(state); err != nil {
        return nil, err
    }
    // Setup context so it may be cancelled the call has completed
    // or, in case of unmetered gas, setup a context with a timeout.
    var cancel context.CancelFunc
    if timeout &gt; 0 {
        ctx, cancel = context.WithTimeout(ctx, timeout)
    } else {
        ctx, cancel = context.WithCancel(ctx)
    }
    // Make sure the context is cancelled when the call has completed
    // this makes sure resources are cleaned up.
    defer cancel()

    // Get a new instance of the EVM.
    msg, err := args.ToMessage(globalGasCap, header.BaseFee)
    if err != nil {
        return nil, err
    }
    blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
    if blockOverrides != nil {
        blockOverrides.Apply(&amp;blockCtx)
    }
    evm, vmError := b.GetEVM(ctx, msg, state, header, &amp;vm.Config{NoBaseFee: true}, &amp;blockCtx)

    // Wait for the context to be done and cancel the evm. Even if the
    // EVM has finished, cancelling may be done (repeatedly)
    go func() {
        &lt;-ctx.Done()
        evm.Cancel()
    }()

    // Execute the message.
    gp := new(core.GasPool).AddGas(math.MaxUint64)
    result, err := core.ApplyMessage(evm, msg, gp)
    if err := vmError(); err != nil {
        return nil, err
    }

    // If the timer caused an abort, return an appropriate error message
    if evm.Cancelled() {
        return nil, fmt.Errorf(&quot;execution aborted (timeout = %v)&quot;, timeout)
    }
    if err != nil {
        return result, fmt.Errorf(&quot;err: %w (supplied gas %d)&quot;, err, msg.GasLimit)
    }
    return result, nil
}
"><code>func doCall(ctx context.Context, b Backend, args TransactionArgs, state <span class="hljs-operator">*</span>state.StateDB, header <span class="hljs-operator">*</span>types.Header, overrides <span class="hljs-operator">*</span>StateOverride, blockOverrides <span class="hljs-operator">*</span>BlockOverrides, timeout time.Duration, globalGasCap <span class="hljs-keyword">uint64</span>) (<span class="hljs-operator">*</span>core.ExecutionResult, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    <span class="hljs-keyword">if</span> err :<span class="hljs-operator">=</span> overrides.Apply(state); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }
    <span class="hljs-comment">// Setup context so it may be cancelled the call has completed</span>
    <span class="hljs-comment">// or, in case of unmetered gas, setup a context with a timeout.</span>
    <span class="hljs-keyword">var</span> cancel context.CancelFunc
    <span class="hljs-keyword">if</span> timeout <span class="hljs-operator">></span> <span class="hljs-number">0</span> {
        ctx, cancel <span class="hljs-operator">=</span> context.WithTimeout(ctx, timeout)
    } <span class="hljs-keyword">else</span> {
        ctx, cancel <span class="hljs-operator">=</span> context.WithCancel(ctx)
    }
    <span class="hljs-comment">// Make sure the context is cancelled when the call has completed</span>
    <span class="hljs-comment">// this makes sure resources are cleaned up.</span>
    defer cancel()

    <span class="hljs-comment">// Get a new instance of the EVM.</span>
    <span class="hljs-built_in">msg</span>, err :<span class="hljs-operator">=</span> args.ToMessage(globalGasCap, header.BaseFee)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }
    blockCtx :<span class="hljs-operator">=</span> core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
    <span class="hljs-keyword">if</span> blockOverrides <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        blockOverrides.Apply(<span class="hljs-operator">&#x26;</span>blockCtx)
    }
    evm, vmError :<span class="hljs-operator">=</span> b.GetEVM(ctx, <span class="hljs-built_in">msg</span>, state, header, <span class="hljs-operator">&#x26;</span>vm.Config{NoBaseFee: <span class="hljs-literal">true</span>}, <span class="hljs-operator">&#x26;</span>blockCtx)

    <span class="hljs-comment">// Wait for the context to be done and cancel the evm. Even if the</span>
    <span class="hljs-comment">// EVM has finished, cancelling may be done (repeatedly)</span>
    go func() {
        <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span>ctx.Done()
        evm.Cancel()
    }()

    <span class="hljs-comment">// Execute the message.</span>
    gp :<span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(core.GasPool).AddGas(math.MaxUint64)
    result, err :<span class="hljs-operator">=</span> core.ApplyMessage(evm, <span class="hljs-built_in">msg</span>, gp)
    <span class="hljs-keyword">if</span> err :<span class="hljs-operator">=</span> vmError(); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    <span class="hljs-comment">// If the timer caused an abort, return an appropriate error message</span>
    <span class="hljs-keyword">if</span> evm.Cancelled() {
        <span class="hljs-keyword">return</span> nil, fmt.Errorf(<span class="hljs-string">"execution aborted (timeout = %v)"</span>, timeout)
    }
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> result, fmt.Errorf(<span class="hljs-string">"err: %w (supplied gas %d)"</span>, err, <span class="hljs-built_in">msg</span>.GasLimit)
    }
    <span class="hljs-keyword">return</span> result, nil
}
</code></pre><p>일단 <code>overrides.Apply(state)</code> 함수를 추측해보건데, 아마 컨트랙트의 현재 상태를 가져오는 것이 아닐까? 라는 생각이 든다.</p><pre data-type="codeBlock" text="
// StateOverride is the collection of overridden accounts.
type StateOverride map[common.Address]OverrideAccount

// Apply overrides the fields of specified accounts into the given state.
func (diff *StateOverride) Apply(state *state.StateDB) error {
    if diff == nil {
        return nil
    }
    for addr, account := range *diff {
        // Override account nonce.
        if account.Nonce != nil {
            state.SetNonce(addr, uint64(*account.Nonce))
        }
        // Override account(contract) code.
        if account.Code != nil {
            state.SetCode(addr, *account.Code)
        }
        // Override account balance.
        if account.Balance != nil {
            state.SetBalance(addr, (*big.Int)(*account.Balance))
        }
        if account.State != nil &amp;&amp; account.StateDiff != nil {
            return fmt.Errorf(&quot;account %s has both &apos;state&apos; and &apos;stateDiff&apos;&quot;, addr.Hex())
        }
        // Replace entire state if caller requires.
        if account.State != nil {
            state.SetStorage(addr, *account.State)
        }
        // Apply state diff into specified accounts.
        if account.StateDiff != nil {
            for key, value := range *account.StateDiff {
                state.SetState(addr, key, value)
            }
        }
    }
    // Now finalize the changes. Finalize is normally performed between transactions.
    // By using finalize, the overrides are semantically behaving as
    // if they were created in a transaction just before the tracing occur.
    state.Finalise(false)
    return nil
}
"><code>
<span class="hljs-comment">// StateOverride is the collection of overridden accounts.</span>
<span class="hljs-keyword">type</span> StateOverride map[common.Address]OverrideAccount

<span class="hljs-comment">// Apply overrides the fields of specified accounts into the given state.</span>
func (diff <span class="hljs-operator">*</span>StateOverride) Apply(state <span class="hljs-operator">*</span>state.StateDB) <span class="hljs-function"><span class="hljs-keyword">error</span> </span>{
    <span class="hljs-keyword">if</span> diff <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil
    }
    <span class="hljs-keyword">for</span> addr, account :<span class="hljs-operator">=</span> range <span class="hljs-operator">*</span>diff {
        <span class="hljs-comment">// Override account nonce.</span>
        <span class="hljs-keyword">if</span> account.Nonce <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            state.SetNonce(addr, <span class="hljs-keyword">uint64</span>(<span class="hljs-operator">*</span>account.Nonce))
        }
        <span class="hljs-comment">// Override account(contract) code.</span>
        <span class="hljs-keyword">if</span> account.Code <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            state.SetCode(addr, <span class="hljs-operator">*</span>account.Code)
        }
        <span class="hljs-comment">// Override account balance.</span>
        <span class="hljs-keyword">if</span> account.Balance <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            state.SetBalance(addr, (<span class="hljs-operator">*</span>big.Int)(<span class="hljs-operator">*</span>account.Balance))
        }
        <span class="hljs-keyword">if</span> account.State <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> account.StateDiff <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"account %s has both 'state' and 'stateDiff'"</span>, addr.Hex())
        }
        <span class="hljs-comment">// Replace entire state if caller requires.</span>
        <span class="hljs-keyword">if</span> account.State <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            state.SetStorage(addr, <span class="hljs-operator">*</span>account.State)
        }
        <span class="hljs-comment">// Apply state diff into specified accounts.</span>
        <span class="hljs-keyword">if</span> account.StateDiff <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            <span class="hljs-keyword">for</span> key, value :<span class="hljs-operator">=</span> range <span class="hljs-operator">*</span>account.StateDiff {
                state.SetState(addr, key, value)
            }
        }
    }
    <span class="hljs-comment">// Now finalize the changes. Finalize is normally performed between transactions.</span>
    <span class="hljs-comment">// By using finalize, the overrides are semantically behaving as</span>
    <span class="hljs-comment">// if they were created in a transaction just before the tracing occur.</span>
    state.Finalise(<span class="hljs-literal">false</span>)
    <span class="hljs-keyword">return</span> nil
}
</code></pre><p>아무래도 보기좋게 빗나간 것 같다. 일단 상태를 오버라이딩 하는 것 까지는 이름에서 충분히 추측할 수 있었지만, 컨트랙트의 상태가 아니라 계정의 상태를 말하는 것 같다.</p><p>그 다음은 timeout이 설정되어 있으면 Context WithTimeout으로, 없을 경우 Context WithCancel 함수로 컨텍스트와 cancel func를 생성해준다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2b3e2c5e62f7d53fa9088e9d962ec7733b70a65a64da17b82a1cad0cd45c1e24.png" alt="timeout &gt; 0 or zero" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">timeout &gt; 0 or zero</figcaption></figure><p>그 다음 코드로 넘어가보자.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3333eae72a64cfb4d49e8fd62f547e54bc055dfd680086d95a95e06503abc29d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>일단 <code>globalGasCap</code>, <code>header.BaseFee</code> 를 사용해서 들어온 트랜잭션 argument를 메세지 타입으로 만들어준다.</p><p>그 이후에 <code>core.NewEVMBlockContext</code>가 바로 새로운 EVM 인스턴스를 생성하는 부분이다.</p><p>여기서 일단 Header 구조체를 살펴보고 가자.</p><p>주석을 보면, 이더리움의 블록 헤더에 대한 구조체라고 나와있다.</p><pre data-type="codeBlock" text="// Header represents a block header in the Ethereum blockchain.
type Header struct {
    ParentHash  common.Hash    `json:&quot;parentHash&quot;       gencodec:&quot;required&quot;`
    UncleHash   common.Hash    `json:&quot;sha3Uncles&quot;       gencodec:&quot;required&quot;`
    Coinbase    common.Address `json:&quot;miner&quot;`
    Root        common.Hash    `json:&quot;stateRoot&quot;        gencodec:&quot;required&quot;`
    TxHash      common.Hash    `json:&quot;transactionsRoot&quot; gencodec:&quot;required&quot;`
    ReceiptHash common.Hash    `json:&quot;receiptsRoot&quot;     gencodec:&quot;required&quot;`
    Bloom       Bloom          `json:&quot;logsBloom&quot;        gencodec:&quot;required&quot;`
    Difficulty  *big.Int       `json:&quot;difficulty&quot;       gencodec:&quot;required&quot;`
    Number      *big.Int       `json:&quot;number&quot;           gencodec:&quot;required&quot;`
    GasLimit    uint64         `json:&quot;gasLimit&quot;         gencodec:&quot;required&quot;`
    GasUsed     uint64         `json:&quot;gasUsed&quot;          gencodec:&quot;required&quot;`
    Time        uint64         `json:&quot;timestamp&quot;        gencodec:&quot;required&quot;`
    Extra       []byte         `json:&quot;extraData&quot;        gencodec:&quot;required&quot;`
    MixDigest   common.Hash    `json:&quot;mixHash&quot;`
    Nonce       BlockNonce     `json:&quot;nonce&quot;`

    // BaseFee was added by EIP-1559 and is ignored in legacy headers.
    BaseFee *big.Int `json:&quot;baseFeePerGas&quot; rlp:&quot;optional&quot;`

    // WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.
    WithdrawalsHash *common.Hash `json:&quot;withdrawalsRoot&quot; rlp:&quot;optional&quot;`

    // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
    BlobGasUsed *uint64 `json:&quot;blobGasUsed&quot; rlp:&quot;optional&quot;`

    // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
    ExcessBlobGas *uint64 `json:&quot;excessBlobGas&quot; rlp:&quot;optional&quot;`
}
"><code><span class="hljs-comment">// Header represents a block header in the Ethereum blockchain.</span>
<span class="hljs-keyword">type</span> Header <span class="hljs-keyword">struct</span> {
    ParentHash  common.Hash    `json:<span class="hljs-string">"parentHash"</span>       gencodec:<span class="hljs-string">"required"</span>`
    UncleHash   common.Hash    `json:<span class="hljs-string">"sha3Uncles"</span>       gencodec:<span class="hljs-string">"required"</span>`
    Coinbase    common.Address `json:<span class="hljs-string">"miner"</span>`
    Root        common.Hash    `json:<span class="hljs-string">"stateRoot"</span>        gencodec:<span class="hljs-string">"required"</span>`
    TxHash      common.Hash    `json:<span class="hljs-string">"transactionsRoot"</span> gencodec:<span class="hljs-string">"required"</span>`
    ReceiptHash common.Hash    `json:<span class="hljs-string">"receiptsRoot"</span>     gencodec:<span class="hljs-string">"required"</span>`
    Bloom       Bloom          `json:<span class="hljs-string">"logsBloom"</span>        gencodec:<span class="hljs-string">"required"</span>`
    Difficulty  <span class="hljs-operator">*</span>big.Int       `json:<span class="hljs-string">"difficulty"</span>       gencodec:<span class="hljs-string">"required"</span>`
    Number      <span class="hljs-operator">*</span>big.Int       `json:<span class="hljs-string">"number"</span>           gencodec:<span class="hljs-string">"required"</span>`
    GasLimit    <span class="hljs-keyword">uint64</span>         `json:<span class="hljs-string">"gasLimit"</span>         gencodec:<span class="hljs-string">"required"</span>`
    GasUsed     <span class="hljs-keyword">uint64</span>         `json:<span class="hljs-string">"gasUsed"</span>          gencodec:<span class="hljs-string">"required"</span>`
    Time        <span class="hljs-keyword">uint64</span>         `json:<span class="hljs-string">"timestamp"</span>        gencodec:<span class="hljs-string">"required"</span>`
    Extra       []<span class="hljs-keyword">byte</span>         `json:<span class="hljs-string">"extraData"</span>        gencodec:<span class="hljs-string">"required"</span>`
    MixDigest   common.Hash    `json:<span class="hljs-string">"mixHash"</span>`
    Nonce       BlockNonce     `json:<span class="hljs-string">"nonce"</span>`

    <span class="hljs-comment">// BaseFee was added by EIP-1559 and is ignored in legacy headers.</span>
    BaseFee <span class="hljs-operator">*</span>big.Int `json:<span class="hljs-string">"baseFeePerGas"</span> rlp:<span class="hljs-string">"optional"</span>`

    <span class="hljs-comment">// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.</span>
    WithdrawalsHash <span class="hljs-operator">*</span>common.Hash `json:<span class="hljs-string">"withdrawalsRoot"</span> rlp:<span class="hljs-string">"optional"</span>`

    <span class="hljs-comment">// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.</span>
    BlobGasUsed <span class="hljs-operator">*</span><span class="hljs-keyword">uint64</span> `json:<span class="hljs-string">"blobGasUsed"</span> rlp:<span class="hljs-string">"optional"</span>`

    <span class="hljs-comment">// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.</span>
    ExcessBlobGas <span class="hljs-operator">*</span><span class="hljs-keyword">uint64</span> `json:<span class="hljs-string">"excessBlobGas"</span> rlp:<span class="hljs-string">"optional"</span>`
}
</code></pre><p>그 다음은 NewChainContext 인자로 backend(API 인터페이스)와 컨텍스트를 넘긴다.</p><p>이제 <code>NewEVMBlockContext</code>를 한 번 코드로 살펴보자.</p><pre data-type="codeBlock" text="// NewEVMBlockContext creates a new context for use in the EVM.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
    var (
        beneficiary common.Address
        baseFee     *big.Int
        random      *common.Hash
    )

    // If we don&apos;t have an explicit author (i.e. not mining), extract from the header
    if author == nil {
        beneficiary, _ = chain.Engine().Author(header) // Ignore error, we&apos;re past header validation
    } else {
        beneficiary = *author
    }
    if header.BaseFee != nil {
        baseFee = new(big.Int).Set(header.BaseFee)
    }
    if header.Difficulty.Cmp(common.Big0) == 0 {
        random = &amp;header.MixDigest
    }
    return vm.BlockContext{
        CanTransfer:   CanTransfer,
        Transfer:      Transfer,
        GetHash:       GetHashFn(header, chain),
        Coinbase:      beneficiary,
        BlockNumber:   new(big.Int).Set(header.Number),
        Time:          header.Time,
        Difficulty:    new(big.Int).Set(header.Difficulty),
        BaseFee:       baseFee,
        GasLimit:      header.GasLimit,
        Random:        random,
        ExcessBlobGas: header.ExcessBlobGas,
    }
}
"><code><span class="hljs-comment">// NewEVMBlockContext creates a new context for use in the EVM.</span>
func NewEVMBlockContext(header <span class="hljs-operator">*</span>types.Header, chain ChainContext, author <span class="hljs-operator">*</span>common.Address) vm.BlockContext {
    <span class="hljs-keyword">var</span> (
        beneficiary common.Address
        baseFee     <span class="hljs-operator">*</span>big.Int
        random      <span class="hljs-operator">*</span>common.Hash
    )

    <span class="hljs-comment">// If we don't have an explicit author (i.e. not mining), extract from the header</span>
    <span class="hljs-keyword">if</span> author <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
        beneficiary, <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> chain.Engine().Author(header) <span class="hljs-comment">// Ignore error, we're past header validation</span>
    } <span class="hljs-keyword">else</span> {
        beneficiary <span class="hljs-operator">=</span> <span class="hljs-operator">*</span>author
    }
    <span class="hljs-keyword">if</span> header.BaseFee <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        baseFee <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(big.Int).Set(header.BaseFee)
    }
    <span class="hljs-keyword">if</span> header.Difficulty.Cmp(common.Big0) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> {
        random <span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>header.MixDigest
    }
    <span class="hljs-keyword">return</span> vm.BlockContext{
        CanTransfer:   CanTransfer,
        Transfer:      Transfer,
        GetHash:       GetHashFn(header, chain),
        Coinbase:      beneficiary,
        BlockNumber:   <span class="hljs-keyword">new</span>(big.Int).Set(header.Number),
        Time:          header.Time,
        Difficulty:    <span class="hljs-keyword">new</span>(big.Int).Set(header.Difficulty),
        BaseFee:       baseFee,
        GasLimit:      header.GasLimit,
        Random:        random,
        ExcessBlobGas: header.ExcessBlobGas,
    }
}
</code></pre><p>author가 nil인지 아닌지로 분기처리를 한 모습이 보이는데, 위에서 author에 nil을 집어넣었다.</p><p>따라서 estimate를 할 때는 항상 <code>author==nil</code> 조건을 충족할 것이다.</p><p>나머지는 단순히 기본 값으로 넣어주는 듯 하다.</p><p>그 이후로 <code>GetEvm</code>, <code>ApplyMessage</code> 등으로 실행한다.</p><p>결국 가장 중요한 것이 아래 부분이 된다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8ffd641c1b8dfce17f55ef357193b52ca6491cf608cb6db11f0390c03f3111e4.png" alt="코드를 실행" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">코드를 실행</figcaption></figure><p>누가봐도 ApplyMessage가 정말 의심스럽다. go-ethereum에서 추상화를 매우 잘해놔서 함수를 몇 번 타고 들어가다보면 다음 메소드를 찾을 수 있다.</p><pre data-type="codeBlock" text="// TransitionDb will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
//   - used gas: total gas used (including gas being refunded)
//   - returndata: the returned data from evm
//   - concrete execution error: various EVM errors which abort the execution, e.g.
//     ErrOutOfGas, ErrExecutionReverted
//
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
    // First check this message satisfies all consensus rules before
    // applying the message. The rules include these clauses
    //
    // 1. the nonce of the message caller is correct
    // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
    // 3. the amount of gas required is available in the block
    // 4. the purchased gas is enough to cover intrinsic usage
    // 5. there is no overflow when calculating intrinsic gas
    // 6. caller has enough balance to cover asset transfer for **topmost** call

    // Check clauses 1-3, buy gas if everything is correct
    if err := st.preCheck(); err != nil {
        return nil, err
    }

    if tracer := st.evm.Config.Tracer; tracer != nil {
        tracer.CaptureTxStart(st.initialGas)
        defer func() {
            tracer.CaptureTxEnd(st.gasRemaining)
        }()
    }

    var (
        msg              = st.msg
        sender           = vm.AccountRef(msg.From)
        rules            = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
        contractCreation = msg.To == nil
    )

    // Check clauses 4-5, subtract intrinsic gas if everything is correct
    gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
    if err != nil {
        return nil, err
    }
    if st.gasRemaining &lt; gas {
        return nil, fmt.Errorf(&quot;%w: have %d, want %d&quot;, ErrIntrinsicGas, st.gasRemaining, gas)
    }
    st.gasRemaining -= gas

    // Check clause 6
    if msg.Value.Sign() &gt; 0 &amp;&amp; !st.evm.Context.CanTransfer(st.state, msg.From, msg.Value) {
        return nil, fmt.Errorf(&quot;%w: address %v&quot;, ErrInsufficientFundsForTransfer, msg.From.Hex())
    }

    // Check whether the init code size has been exceeded.
    if rules.IsShanghai &amp;&amp; contractCreation &amp;&amp; len(msg.Data) &gt; params.MaxInitCodeSize {
        return nil, fmt.Errorf(&quot;%w: code size %v limit %v&quot;, ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
    }

    // Execute the preparatory steps for state transition which includes:
    // - prepare accessList(post-berlin)
    // - reset transient storage(eip 1153)
    st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)

    var (
        ret   []byte
        vmerr error // vm errors do not effect consensus and are therefore not assigned to err
    )
    if contractCreation {
        ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, msg.Value)
    } else {
        // Increment the nonce for the next transaction
        st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
        ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
    }

    if !rules.IsLondon {
        // Before EIP-3529: refunds were capped to gasUsed / 2
        st.refundGas(params.RefundQuotient)
    } else {
        // After EIP-3529: refunds are capped to gasUsed / 5
        st.refundGas(params.RefundQuotientEIP3529)
    }
    effectiveTip := msg.GasPrice
    if rules.IsLondon {
        effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
    }

    if st.evm.Config.NoBaseFee &amp;&amp; msg.GasFeeCap.Sign() == 0 &amp;&amp; msg.GasTipCap.Sign() == 0 {
        // Skip fee payment when NoBaseFee is set and the fee fields
        // are 0. This avoids a negative effectiveTip being applied to
        // the coinbase when simulating calls.
    } else {
        fee := new(big.Int).SetUint64(st.gasUsed())
        fee.Mul(fee, effectiveTip)
        st.state.AddBalance(st.evm.Context.Coinbase, fee)
    }

    return &amp;ExecutionResult{
        UsedGas:    st.gasUsed(),
        Err:        vmerr,
        ReturnData: ret,
    }, nil
}
"><code><span class="hljs-comment">// TransitionDb will transition the state by applying the current message and</span>
<span class="hljs-comment">// returning the evm execution result with following fields.</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">//   - used gas: total gas used (including gas being refunded)</span>
<span class="hljs-comment">//   - returndata: the returned data from evm</span>
<span class="hljs-comment">//   - concrete execution error: various EVM errors which abort the execution, e.g.</span>
<span class="hljs-comment">//     ErrOutOfGas, ErrExecutionReverted</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">// However if any consensus issue encountered, return the error directly with</span>
<span class="hljs-comment">// nil evm execution result.</span>
func (st <span class="hljs-operator">*</span>StateTransition) TransitionDb() (<span class="hljs-operator">*</span>ExecutionResult, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    <span class="hljs-comment">// First check this message satisfies all consensus rules before</span>
    <span class="hljs-comment">// applying the message. The rules include these clauses</span>
    <span class="hljs-comment">//</span>
    <span class="hljs-comment">// 1. the nonce of the message caller is correct</span>
    <span class="hljs-comment">// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)</span>
    <span class="hljs-comment">// 3. the amount of gas required is available in the block</span>
    <span class="hljs-comment">// 4. the purchased gas is enough to cover intrinsic usage</span>
    <span class="hljs-comment">// 5. there is no overflow when calculating intrinsic gas</span>
    <span class="hljs-comment">// 6. caller has enough balance to cover asset transfer for **topmost** call</span>

    <span class="hljs-comment">// Check clauses 1-3, buy gas if everything is correct</span>
    <span class="hljs-keyword">if</span> err :<span class="hljs-operator">=</span> st.preCheck(); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    <span class="hljs-keyword">if</span> tracer :<span class="hljs-operator">=</span> st.evm.Config.Tracer; tracer <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        tracer.CaptureTxStart(st.initialGas)
        defer func() {
            tracer.CaptureTxEnd(st.gasRemaining)
        }()
    }

    <span class="hljs-keyword">var</span> (
        <span class="hljs-built_in">msg</span>              <span class="hljs-operator">=</span> st.msg
        sender           <span class="hljs-operator">=</span> vm.AccountRef(<span class="hljs-built_in">msg</span>.From)
        rules            <span class="hljs-operator">=</span> st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil, st.evm.Context.Time)
        contractCreation <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.To <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil
    )

    <span class="hljs-comment">// Check clauses 4-5, subtract intrinsic gas if everything is correct</span>
    gas, err :<span class="hljs-operator">=</span> IntrinsicGas(<span class="hljs-built_in">msg</span>.Data, <span class="hljs-built_in">msg</span>.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }
    <span class="hljs-keyword">if</span> st.gasRemaining <span class="hljs-operator">&#x3C;</span> gas {
        <span class="hljs-keyword">return</span> nil, fmt.Errorf(<span class="hljs-string">"%w: have %d, want %d"</span>, ErrIntrinsicGas, st.gasRemaining, gas)
    }
    st.gasRemaining <span class="hljs-operator">-</span><span class="hljs-operator">=</span> gas

    <span class="hljs-comment">// Check clause 6</span>
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">msg</span>.Value.Sign() <span class="hljs-operator">></span> <span class="hljs-number">0</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-operator">!</span>st.evm.Context.CanTransfer(st.state, <span class="hljs-built_in">msg</span>.From, <span class="hljs-built_in">msg</span>.Value) {
        <span class="hljs-keyword">return</span> nil, fmt.Errorf(<span class="hljs-string">"%w: address %v"</span>, ErrInsufficientFundsForTransfer, <span class="hljs-built_in">msg</span>.From.Hex())
    }

    <span class="hljs-comment">// Check whether the init code size has been exceeded.</span>
    <span class="hljs-keyword">if</span> rules.IsShanghai <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> contractCreation <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> len(<span class="hljs-built_in">msg</span>.Data) <span class="hljs-operator">></span> params.MaxInitCodeSize {
        <span class="hljs-keyword">return</span> nil, fmt.Errorf(<span class="hljs-string">"%w: code size %v limit %v"</span>, ErrMaxInitCodeSizeExceeded, len(<span class="hljs-built_in">msg</span>.Data), params.MaxInitCodeSize)
    }

    <span class="hljs-comment">// Execute the preparatory steps for state transition which includes:</span>
    <span class="hljs-comment">// - prepare accessList(post-berlin)</span>
    <span class="hljs-comment">// - reset transient storage(eip 1153)</span>
    st.state.Prepare(rules, <span class="hljs-built_in">msg</span>.From, st.evm.Context.Coinbase, <span class="hljs-built_in">msg</span>.To, vm.ActivePrecompiles(rules), <span class="hljs-built_in">msg</span>.AccessList)

    <span class="hljs-keyword">var</span> (
        ret   []<span class="hljs-keyword">byte</span>
        vmerr <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-comment">// vm errors do not effect consensus and are therefore not assigned to err</span>
    )
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">contractCreation</span> </span>{
        ret, <span class="hljs-keyword">_</span>, st.gasRemaining, vmerr <span class="hljs-operator">=</span> st.evm.Create(sender, <span class="hljs-built_in">msg</span>.Data, st.gasRemaining, <span class="hljs-built_in">msg</span>.Value)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Increment the nonce for the next transaction</span>
        st.state.SetNonce(<span class="hljs-built_in">msg</span>.From, st.state.GetNonce(sender.Address())<span class="hljs-operator">+</span><span class="hljs-number">1</span>)
        ret, st.gasRemaining, vmerr <span class="hljs-operator">=</span> st.evm.Call(sender, st.to(), <span class="hljs-built_in">msg</span>.Data, st.gasRemaining, <span class="hljs-built_in">msg</span>.Value)
    }

    <span class="hljs-keyword">if</span> <span class="hljs-operator">!</span>rules.IsLondon {
        <span class="hljs-comment">// Before EIP-3529: refunds were capped to gasUsed / 2</span>
        st.refundGas(params.RefundQuotient)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// After EIP-3529: refunds are capped to gasUsed / 5</span>
        st.refundGas(params.RefundQuotientEIP3529)
    }
    effectiveTip :<span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.GasPrice
    <span class="hljs-keyword">if</span> rules.IsLondon {
        effectiveTip <span class="hljs-operator">=</span> cmath.BigMin(<span class="hljs-built_in">msg</span>.GasTipCap, <span class="hljs-keyword">new</span>(big.Int).Sub(<span class="hljs-built_in">msg</span>.GasFeeCap, st.evm.Context.BaseFee))
    }

    <span class="hljs-keyword">if</span> st.evm.Config.NoBaseFee <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-built_in">msg</span>.GasFeeCap.Sign() <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-built_in">msg</span>.GasTipCap.Sign() <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> {
        <span class="hljs-comment">// Skip fee payment when NoBaseFee is set and the fee fields</span>
        <span class="hljs-comment">// are 0. This avoids a negative effectiveTip being applied to</span>
        <span class="hljs-comment">// the coinbase when simulating calls.</span>
    } <span class="hljs-keyword">else</span> {
        fee :<span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(big.Int).SetUint64(st.gasUsed())
        fee.Mul(fee, effectiveTip)
        st.state.AddBalance(st.evm.Context.Coinbase, fee)
    }

    <span class="hljs-keyword">return</span> <span class="hljs-operator">&#x26;</span>ExecutionResult{
        UsedGas:    st.gasUsed(),
        Err:        vmerr,
        ReturnData: ret,
    }, nil
}
</code></pre><p>드디어 그토록 찾던 실행부 구현체이다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/20542c282b5409ebacba360a275c8085df2f51669d4292523784a2838920f5e4.png" alt="preCheck" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">preCheck</figcaption></figure><p>맨 위에서 부터 살펴보면, 6가지 컨센서스 조건을 모두 충족해야 메세지를 적용한다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/55b31016d5029ea253b9ed6db7c1a873ee5c4e078717e45bd0e3de504831b8c1.png" alt="4-6 condition" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">4-6 condition</figcaption></figure><p>아래 사진을 보면, 컨트랙트 생성인 경우와 아닌 경우로 나뉘어서 다시 실행된다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fbea9ee49f62511091ec1c627517aaa530cc3177e13c65f43eacd49447ce4479.png" alt="분기처리" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">분기처리</figcaption></figure><p>그 뒤로도 계속해서 추상화를 통해 각종 예외처리와, 상태 DB에서 현재 상태를 가져오고, 코드를 컴파일 하는 등의 코드들이 나온다.</p><p>그렇게 실행한 코드의 결과값 등이 차례로 리턴되고, 실행에 소요된 가스값이 리턴된다.</p><p>최초의 <code>ethClient.EstimateGas</code>로 부터 거의 10개가 넘는 인터페이스를 지나서 도달할 수 있었다.</p><p>go-ethereum의 코드를 보면서 느낀 것은 코드 자체의 퀄리티가 높은 것도 있지만, 개인적으로 다음 5가지에 주목이 됐다.</p><ol><li><p>관심사에 따라 인터페이스로 추상화를 잘 해둔 것 같다.</p></li><li><p>함수 호출에 필요한 인자가 많아지더라도 새로운 구조체를 생성하지 않고 가독성을 해치지 않도록 각 변수들을 주입하도록 했다.</p></li><li><p>예외처리가 매우 엄격하게 이루어졌다.</p></li><li><p>Getter, Setter를 적극적으로 활용했다.</p></li><li><p>함수 내에서 사용할 변수들을 <code>var ()</code>로 묶어서 선언, 주석을 추가함으로써 가독성을 높이고 앞으로 함수 내에서 진행될 비즈니스 로직을 예상 가능하게끔 한다.</p></li></ol>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Mongo DB in golang]]></title>
            <link>https://paragraph.com/@primrose/mongo-db-in-golang</link>
            <guid>N3SP4YikK54CrC3RO95m</guid>
            <pubDate>Fri, 18 Aug 2023 18:47:00 GMT</pubDate>
            <description><![CDATA[MongoDBMongoDB는 C++로 작성된 대표적인 NoSQL이다. 보통 AWS Document DB나 Mongo Atlas를 이용하곤하는데, 본 예제에서는 Mongo Atlas를 이용했다. 공식 문서에서는 Document oriented database라고 소개하고 있는데, 여기서 말하는 Document는 RDMS의 record와 비슷한 개념이다.{ "_id": ObjectId("5099803df3f4948bd2f98391"), "username": "velopert", "name": { first: "M.J.", last: "Kim" } } 위와 같은 json 형태의 데이터가 있다고 생각해보자. 실제로 MongoDB에 어떤 데이터를 저장하면 위와 같이 생성되는 것을 확인할 수 있다. Document는 어떤 형태의 데이터를 문서로 보는 관점이라고 생각하면 좋을 것 같다.CollectionCollection은 Document의 그룹이다. RDMS의 테이블과 비슷하다고 볼 수 ...]]></description>
            <content:encoded><![CDATA[<h1 id="h-mongodb" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">MongoDB</h1><p>MongoDB는 C++로 작성된 대표적인 NoSQL이다.</p><p>보통 AWS Document DB나 Mongo Atlas를 이용하곤하는데, 본 예제에서는 Mongo Atlas를 이용했다.</p><p>공식 문서에서는 <code>Document oriented database</code>라고 소개하고 있는데, 여기서 말하는 <code>Document</code>는 RDMS의 record와 비슷한 개념이다.</p><pre data-type="codeBlock" text="{
    &quot;_id&quot;: ObjectId(&quot;5099803df3f4948bd2f98391&quot;),
    &quot;username&quot;: &quot;velopert&quot;,
    &quot;name&quot;: { first: &quot;M.J.&quot;, last: &quot;Kim&quot; }
}
"><code>{
    "_id": <span class="hljs-built_in">ObjectId</span>(<span class="hljs-string">"5099803df3f4948bd2f98391"</span>),
    <span class="hljs-string">"username"</span>: <span class="hljs-string">"velopert"</span>,
    <span class="hljs-string">"name"</span>: { first: <span class="hljs-string">"M.J."</span>, last: <span class="hljs-string">"Kim"</span> }
}
</code></pre><p>위와 같은 json 형태의 데이터가 있다고 생각해보자.</p><p>실제로 MongoDB에 어떤 데이터를 저장하면 위와 같이 생성되는 것을 확인할 수 있다.</p><p>Document는 어떤 형태의 데이터를 문서로 보는 관점이라고 생각하면 좋을 것 같다.</p><h2 id="h-collection" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Collection</h2><p>Collection은 Document의 그룹이다. RDMS의 테이블과 비슷하다고 볼 수 있지만 조금 다른게, Document 자체는 동적인 schema를 가지고 있다. (schema가 없다)</p><p>간단하게 생각하면 다음과 같은 일이 가능하다.</p><pre data-type="codeBlock" text="Collection 1
{
    {
        &quot;_id&quot;: ObjectId(&quot;5099803df3f4948bd2f98391&quot;),
        &quot;username&quot;: &quot;velopert&quot;,
        &quot;name&quot;: { first: &quot;M.J.&quot;, last: &quot;Kim&quot; }
    },
    {
        &quot;_id&quot;: ObjectId(&quot;5099803df3f4948bd2f98391&quot;),
        &quot;name&quot;: { first: &quot;M.J.&quot;, last: &quot;Kim&quot; }
    }
}
"><code>Collection <span class="hljs-number">1</span>
{
    {
        "_id": <span class="hljs-built_in">ObjectId</span>(<span class="hljs-string">"5099803df3f4948bd2f98391"</span>),
        <span class="hljs-string">"username"</span>: <span class="hljs-string">"velopert"</span>,
        <span class="hljs-string">"name"</span>: { first: <span class="hljs-string">"M.J."</span>, last: <span class="hljs-string">"Kim"</span> }
    },
    {
        "_id": <span class="hljs-built_in">ObjectId</span>(<span class="hljs-string">"5099803df3f4948bd2f98391"</span>),
        <span class="hljs-string">"name"</span>: { first: <span class="hljs-string">"M.J."</span>, last: <span class="hljs-string">"Kim"</span> }
    }
}
</code></pre><blockquote><p>같은 Collection에서 각기 다른 json 스키마를 허용한다.</p></blockquote><h2 id="h-database" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Database</h2><p>MongoDB Atlas에서 Database를 생성하고, 그 이후에 Collection을 생성할 수가 있는데, Database는 Collection들의 물리적인 컨테이너이다.</p><h2 id="h-usage" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Usage</h2><p>Mongo DB Atlas를 들어가보고 느낀건데, 정말 잘 되어있다.</p><p>코드 예제, 클러스터 생성부터 개발자 친화적으로 잘 만들어줬다는 생각이 들었다.</p><blockquote><p>또한 Mongo Atlas를 이용해서 AWS에 서버리스 형태로 배포하더라도 Document DB를 쓰는 것 보다 요금이 싼 것 같다.</p></blockquote><p>서버리스 형태로 AWS에 클러스터를 생성할 수 있었고, Go에서는 다음과 같이 연결할 수 있다.</p><pre data-type="codeBlock" text="package db

import (
    &quot;context&quot;
    &quot;fmt&quot;
    &quot;go.mongodb.org/mongo-driver/mongo&quot;
    &quot;go.mongodb.org/mongo-driver/mongo/options&quot;
    &quot;os&quot;
)

func MongoURI() string {
    return fmt.Sprintf(&quot;mongodb+srv://%s:%s@%s.subnw.mongodb.net/?retryWrites=true&amp;w=majority&quot;,
        os.Getenv(&quot;MONGODB_USERNAME&quot;), os.Getenv(&quot;MONGODB_PASSWORD&quot;), os.Getenv(&quot;MONGODB_CLUSTER&quot;))
}

func MongoOpts() *options.ServerAPIOptions {
    return options.ServerAPI(options.ServerAPIVersion1)
}

func NewMongoDB() *mongo.Client {
    serverAPI := MongoOpts()
    uri := MongoURI()
    opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)
    client, err := mongo.Connect(context.Background(), opts)
    if err != nil {
        panic(err)
    }
    return client
}
"><code><span class="hljs-keyword">package</span> db

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"go.mongodb.org/mongo-driver/mongo"</span>
    <span class="hljs-string">"go.mongodb.org/mongo-driver/mongo/options"</span>
    <span class="hljs-string">"os"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">MongoURI</span><span class="hljs-params">()</span></span> <span class="hljs-type">string</span> {
    <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"mongodb+srv://%s:%s@%s.subnw.mongodb.net/?retryWrites=true&#x26;w=majority"</span>,
        os.Getenv(<span class="hljs-string">"MONGODB_USERNAME"</span>), os.Getenv(<span class="hljs-string">"MONGODB_PASSWORD"</span>), os.Getenv(<span class="hljs-string">"MONGODB_CLUSTER"</span>))
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">MongoOpts</span><span class="hljs-params">()</span></span> *options.ServerAPIOptions {
    <span class="hljs-keyword">return</span> options.ServerAPI(options.ServerAPIVersion1)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewMongoDB</span><span class="hljs-params">()</span></span> *mongo.Client {
    serverAPI := MongoOpts()
    uri := MongoURI()
    opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)
    client, err := mongo.Connect(context.Background(), opts)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
    <span class="hljs-keyword">return</span> client
}
</code></pre><p>꽤나 단순하게 연결이 가능하다. 예제 코드는 전부 MongoDB Atlas에서 제공해준 코드이다.</p><blockquote><p><code>ServerAPIVersion1</code> 이라는 코드를 보면 왜 버전1을 쓴거지? 라는 생각이 들 수 있지만 Version1밖에 없고 Stable이라고 하니 안심하고 써도 될 것 같다.</p></blockquote><h2 id="h-source" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Source</h2><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://velopert.com/436">https://velopert.com/436</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Golang HTTP Package]]></title>
            <link>https://paragraph.com/@primrose/golang-http-package</link>
            <guid>dFycu0tAkUU5P2zVVMZi</guid>
            <pubDate>Fri, 11 Aug 2023 18:06:59 GMT</pubDate>
            <description><![CDATA[HTTP Client Package최근 회사에서 NestJS 를 이용해서 외부 API를 연동해야 하는 일들이 많았다. Go 기반 서버에서도 외부 API를 이용하는 로직이 있어서, HTTP 요청을 보내는 것 자체를 패키지화해서 관리를 하고 있는데, 최근 개선된 부분에 대해서 기록하고자 한다. 먼저 기존의 코드를 보자.type WebClient interface { WebClientMetadata WebClientFactory WebClientRequest } type WebClientMetadata interface { URI(uri string) WebClient QueryParams(values map[string]string) WebClient Headers(values map[string]string) WebClient Body(values map[string]string) WebClient Resp(resp *http.Response, err error) ([]byte,...]]></description>
            <content:encoded><![CDATA[<h1 id="h-http-client-package" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">HTTP Client Package</h1><p>최근 회사에서 NestJS 를 이용해서 외부 API를 연동해야 하는 일들이 많았다.</p><p>Go 기반 서버에서도 외부 API를 이용하는 로직이 있어서, HTTP 요청을 보내는 것 자체를 패키지화해서 관리를 하고 있는데, 최근 개선된 부분에 대해서 기록하고자 한다.</p><p>먼저 기존의 코드를 보자.</p><pre data-type="codeBlock" text="type WebClient interface {
    WebClientMetadata
    WebClientFactory
    WebClientRequest
}

type WebClientMetadata interface {
    URI(uri string) WebClient
    QueryParams(values map[string]string) WebClient
    Headers(values map[string]string) WebClient
    Body(values map[string]string) WebClient
    Resp(resp *http.Response, err error) ([]byte, error)
}

type WebClientRequest interface {
    Get() ([]byte, error)
    Post() ([]byte, error)
    Put() ([]byte, error)
    Patch() ([]byte, error)
    Delete() ([]byte, error)
}

type WebClientFactory interface {
    Create() WebClient
}
"><code><span class="hljs-keyword">type</span> WebClient <span class="hljs-class"><span class="hljs-keyword">interface</span> </span>{
    WebClientMetadata
    WebClientFactory
    WebClientRequest
}

<span class="hljs-keyword">type</span> WebClientMetadata <span class="hljs-class"><span class="hljs-keyword">interface</span> </span>{
    URI(uri <span class="hljs-keyword">string</span>) WebClient
    QueryParams(values map[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>) WebClient
    Headers(values map[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>) WebClient
    Body(values map[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>) WebClient
    Resp(resp <span class="hljs-operator">*</span>http.Response, err <span class="hljs-function"><span class="hljs-keyword">error</span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>)
}

<span class="hljs-title"><span class="hljs-keyword">type</span></span> <span class="hljs-title">WebClientRequest</span> <span class="hljs-title"><span class="hljs-keyword">interface</span></span> </span>{
    Get() ([]<span class="hljs-keyword">byte</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>)
    <span class="hljs-title">Post</span>(<span class="hljs-params"></span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>)
    <span class="hljs-title">Put</span>(<span class="hljs-params"></span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>)
    <span class="hljs-title">Patch</span>(<span class="hljs-params"></span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>)
    <span class="hljs-title">Delete</span>(<span class="hljs-params"></span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>)
}

<span class="hljs-title"><span class="hljs-keyword">type</span></span> <span class="hljs-title">WebClientFactory</span> <span class="hljs-title"><span class="hljs-keyword">interface</span></span> </span>{
    Create() WebClient
}
</code></pre><p>크게 네 가지의 인터페이스로 구성되어 있다.</p><p><code>WebClientFactory</code> 인터페이스는 최초에 WebClient 구조체를 생성한다.</p><p><code>WebClientRequest</code>를 통해서 각 HTTP Method에 따라서 Request를 Send하는 로직이 담긴다.</p><p>요청에 필요한 Body, Header 등은 <code>WebClientMetadata</code> 라는 인터페이스를 통해서 value를 구조체에 set 하고 자기 자신을 반환하는 방식으로 진행된다.</p><p>간단한 사용 예제는 다음과 같다.</p><pre data-type="codeBlock" text="client := http.Client{}

responseBody, err := client.Create().URI(&quot;https://www.naver.com&quot;).Get()
if err != nil {
// DO SOMETHING...
}

log.Println(string(responseBody))
"><code>client :<span class="hljs-operator">=</span> http.Client{}

responseBody, err :<span class="hljs-operator">=</span> client.Create().URI(<span class="hljs-string">"https://www.naver.com"</span>).Get()
<span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
<span class="hljs-comment">// DO SOMETHING...</span>
}

log.Println(<span class="hljs-keyword">string</span>(responseBody))
</code></pre><p>일단 NestJS에 있던 패키지를 그대로 따온 것이라서, 뭔가 go 스럽지 않다는 느낌도 든다.</p><p>추가로 내부 구현 코드를 보면,</p><pre data-type="codeBlock" text="func (c Client) Get() ([]byte, error) {
    if c.queryParams != nil {
        c.uri += &quot;?&quot;
        for k, v := range c.queryParams {
            c.uri += fmt.Sprintf(&quot;%s=%s&quot;, k, v)
        }
    }

    request, err := http.NewRequest(http.MethodGet, c.uri, nil)
    if err != nil {
        return nil, err
    }

    if c.headers != nil {
        for k, v := range c.headers {
            request.Header.Add(k, v)
        }
    }

    return c.Resp(c.sender.Do(request))
}

func (c Client) Post() ([]byte, error) {
    var body []byte
    var err error

    if c.body != nil {
        body, err = json.Marshal(c.body)
        if err != nil {
            return nil, errors.Join(constants.MarshalError, err)
        }
    }

    request, err := http.NewRequest(http.MethodGet, c.uri, bytes.NewBuffer(body))
    if err != nil {
        return nil, err
    }

    if c.headers != nil {
        for k, v := range c.headers {
            request.Header.Add(k, v)
        }
    }

    return c.Resp(c.sender.Do(request))
}

func (c Client) Put() ([]byte, error) {
    var body []byte
    var err error

    if c.body != nil {
        body, err = json.Marshal(c.body)
        if err != nil {
            return nil, errors.Join(constants.MarshalError, err)
        }
    }

    request, err := http.NewRequest(http.MethodPut, c.uri, bytes.NewBuffer(body))
    if err != nil {
        return nil, err
    }

    if c.headers != nil {
        for k, v := range c.headers {
            request.Header.Add(k, v)
        }
    }

    return c.Resp(c.sender.Do(request))
}

func (c Client) Patch() ([]byte, error) {
    var body []byte
    var err error

    if c.body != nil {
        body, err = json.Marshal(c.body)
        if err != nil {
            return nil, errors.Join(constants.MarshalError, err)
        }
    }

    request, err := http.NewRequest(http.MethodPatch, c.uri, bytes.NewBuffer(body))
    if err != nil {
        return nil, err
    }

    if c.headers != nil {
        for k, v := range c.headers {
            request.Header.Add(k, v)
        }
    }

    return c.Resp(c.sender.Do(request))
}

func (c Client) Delete() ([]byte, error) {
    var body []byte
    var err error

    if c.queryParams != nil {
        c.uri += &quot;?&quot;
        for k, v := range c.queryParams {
            c.uri += fmt.Sprintf(&quot;%s=%s&quot;, k, v)
        }
    }

    request, err := http.NewRequest(http.MethodDelete, c.uri, bytes.NewBuffer(body))
    if err != nil {
        return nil, err
    }

    if c.headers != nil {
        for k, v := range c.headers {
            request.Header.Add(k, v)
        }
    }

    return c.Resp(c.sender.Do(request))
}

func (c Client) Resp(resp *http.Response, err error) ([]byte, error) {
    if err != nil {
        return nil, err
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    defer func(Body io.ReadCloser) {
        _ = Body.Close()
    }(resp.Body)

    return body, nil
}
"><code>func (c Client) Get() ([]<span class="hljs-keyword">byte</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    <span class="hljs-keyword">if</span> c.queryParams <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        c.uri <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-string">"?"</span>
        <span class="hljs-keyword">for</span> k, v :<span class="hljs-operator">=</span> range c.queryParams {
            c.uri <span class="hljs-operator">+</span><span class="hljs-operator">=</span> fmt.Sprintf(<span class="hljs-string">"%s=%s"</span>, k, v)
        }
    }

    request, err :<span class="hljs-operator">=</span> http.NewRequest(http.MethodGet, c.uri, nil)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    <span class="hljs-keyword">if</span> c.headers <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">for</span> k, v :<span class="hljs-operator">=</span> range c.headers {
            request.Header.Add(k, v)
        }
    }

    <span class="hljs-keyword">return</span> c.Resp(c.sender.Do(request))
}

func (c Client) Post() ([]<span class="hljs-keyword">byte</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    <span class="hljs-keyword">var</span> body []<span class="hljs-keyword">byte</span>
    <span class="hljs-keyword">var</span> err <span class="hljs-function"><span class="hljs-keyword">error</span>

    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">c</span>.<span class="hljs-title">body</span> != <span class="hljs-title">nil</span> </span>{
        body, err <span class="hljs-operator">=</span> json.Marshal(c.body)
        <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            <span class="hljs-keyword">return</span> nil, errors.Join(constants.MarshalError, err)
        }
    }

    request, err :<span class="hljs-operator">=</span> http.NewRequest(http.MethodGet, c.uri, <span class="hljs-built_in">bytes</span>.NewBuffer(body))
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    <span class="hljs-keyword">if</span> c.headers <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">for</span> k, v :<span class="hljs-operator">=</span> range c.headers {
            request.Header.Add(k, v)
        }
    }

    <span class="hljs-keyword">return</span> c.Resp(c.sender.Do(request))
}

func (c Client) Put() ([]<span class="hljs-keyword">byte</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    <span class="hljs-keyword">var</span> body []<span class="hljs-keyword">byte</span>
    <span class="hljs-keyword">var</span> err <span class="hljs-function"><span class="hljs-keyword">error</span>

    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">c</span>.<span class="hljs-title">body</span> != <span class="hljs-title">nil</span> </span>{
        body, err <span class="hljs-operator">=</span> json.Marshal(c.body)
        <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            <span class="hljs-keyword">return</span> nil, errors.Join(constants.MarshalError, err)
        }
    }

    request, err :<span class="hljs-operator">=</span> http.NewRequest(http.MethodPut, c.uri, <span class="hljs-built_in">bytes</span>.NewBuffer(body))
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    <span class="hljs-keyword">if</span> c.headers <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">for</span> k, v :<span class="hljs-operator">=</span> range c.headers {
            request.Header.Add(k, v)
        }
    }

    <span class="hljs-keyword">return</span> c.Resp(c.sender.Do(request))
}

func (c Client) Patch() ([]<span class="hljs-keyword">byte</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    <span class="hljs-keyword">var</span> body []<span class="hljs-keyword">byte</span>
    <span class="hljs-keyword">var</span> err <span class="hljs-function"><span class="hljs-keyword">error</span>

    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">c</span>.<span class="hljs-title">body</span> != <span class="hljs-title">nil</span> </span>{
        body, err <span class="hljs-operator">=</span> json.Marshal(c.body)
        <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            <span class="hljs-keyword">return</span> nil, errors.Join(constants.MarshalError, err)
        }
    }

    request, err :<span class="hljs-operator">=</span> http.NewRequest(http.MethodPatch, c.uri, <span class="hljs-built_in">bytes</span>.NewBuffer(body))
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    <span class="hljs-keyword">if</span> c.headers <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">for</span> k, v :<span class="hljs-operator">=</span> range c.headers {
            request.Header.Add(k, v)
        }
    }

    <span class="hljs-keyword">return</span> c.Resp(c.sender.Do(request))
}

func (c Client) Delete() ([]<span class="hljs-keyword">byte</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    <span class="hljs-keyword">var</span> body []<span class="hljs-keyword">byte</span>
    <span class="hljs-keyword">var</span> err <span class="hljs-function"><span class="hljs-keyword">error</span>

    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">c</span>.<span class="hljs-title">queryParams</span> != <span class="hljs-title">nil</span> </span>{
        c.uri <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-string">"?"</span>
        <span class="hljs-keyword">for</span> k, v :<span class="hljs-operator">=</span> range c.queryParams {
            c.uri <span class="hljs-operator">+</span><span class="hljs-operator">=</span> fmt.Sprintf(<span class="hljs-string">"%s=%s"</span>, k, v)
        }
    }

    request, err :<span class="hljs-operator">=</span> http.NewRequest(http.MethodDelete, c.uri, <span class="hljs-built_in">bytes</span>.NewBuffer(body))
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    <span class="hljs-keyword">if</span> c.headers <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">for</span> k, v :<span class="hljs-operator">=</span> range c.headers {
            request.Header.Add(k, v)
        }
    }

    <span class="hljs-keyword">return</span> c.Resp(c.sender.Do(request))
}

func (c Client) Resp(resp <span class="hljs-operator">*</span>http.Response, err <span class="hljs-function"><span class="hljs-keyword">error</span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>) </span>{
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    body, err :<span class="hljs-operator">=</span> io.ReadAll(resp.Body)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> nil, err
    }

    defer func(Body io.ReadCloser) {
        <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> Body.Close()
    }(resp.Body)

    <span class="hljs-keyword">return</span> body, nil
}
</code></pre><p>뭔가 각 메소드가 비슷비슷하고 중복 코드가 꽤 많이 있다.</p><p>리팩토링을 진행한 코드는 다음과 같다.</p><pre data-type="codeBlock" text="package main

type WebClient interface {
    WebClientMetadata
    WebClientRequest
}

type WebClientMetadata interface {
    URI(uri string) WebClient
    Body(values map[string]string) WebClient
    Resp(resp *http.Response, err error) ([]byte, error)
    Headers(values map[string]string) WebClient
    ContentType(contentType string) WebClient
    QueryParams(values map[string]string) WebClient
}

type WebClientRequest interface {
    Get() WebClient
    Post() WebClient
    Put() WebClient
    Patch() WebClient
    Delete() WebClient
    Retrieve() ([]byte, error)
}
"><code>package <span class="hljs-selector-tag">main</span>

type WebClient interface {
    WebClientMetadata
    WebClientRequest
}

type WebClientMetadata interface {
    <span class="hljs-built_in">URI</span>(uri string) WebClient
    <span class="hljs-selector-tag">Body</span>(values map[string]string) WebClient
    <span class="hljs-built_in">Resp</span>(resp *http.Response, err error) ([]byte, error)
    <span class="hljs-built_in">Headers</span>(values map[string]string) WebClient
    <span class="hljs-built_in">ContentType</span>(contentType string) WebClient
    <span class="hljs-built_in">QueryParams</span>(values map[string]string) WebClient
}

type WebClientRequest interface {
    <span class="hljs-built_in">Get</span>() WebClient
    <span class="hljs-built_in">Post</span>() WebClient
    <span class="hljs-built_in">Put</span>() WebClient
    <span class="hljs-built_in">Patch</span>() WebClient
    <span class="hljs-built_in">Delete</span>() WebClient
    <span class="hljs-built_in">Retrieve</span>() ([]byte, error)
}
</code></pre><p>우선 Factory interface를 삭제하고, 실제 Request를 수행하는 코드를 <code>Retrieve()</code>라는 메소드를 이용해서 통일했다.</p><p>사용 코드를 보자.</p><pre data-type="codeBlock" text="package main

func main() {
    client := http.NewWebClient()

    responseBody, err := client.URI(&quot;https://www.google.com&quot;).Get().Retrieve()
    if err != nil {
        // DO SOMETHING...
    }

    log.Println(string(responseBody))
}
"><code>package main

func main() {
    client :<span class="hljs-operator">=</span> http.NewWebClient()

    responseBody, err :<span class="hljs-operator">=</span> client.URI(<span class="hljs-string">"https://www.google.com"</span>).Get().Retrieve()
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-comment">// DO SOMETHING...</span>
    }

    log.Println(<span class="hljs-keyword">string</span>(responseBody))
}
</code></pre><p>내 눈에는 조금 나아진 것 같았다. 그래도 조금 거슬리는 부분이 있다면, URI를 따로 메소드를 구현해서 굳이 저렇게 호출해야할까? 하는 부분이었다.</p><pre data-type="codeBlock" text="package main

func (c Client) Get() WebClient {
    request, _ := http.NewRequest(http.MethodGet, c.uri, nil)
    c.request = request
    return c
}

func (c Client) Post() WebClient {
    request, _ := http.NewRequest(http.MethodPost, c.uri, nil)
    c.request = request
    return c
}

func (c Client) Put() WebClient {
    request, _ := http.NewRequest(http.MethodPut, c.uri, nil)
    c.request = request
    return c
}

func (c Client) Patch() WebClient {
    request, _ := http.NewRequest(http.MethodPatch, c.uri, nil)
    c.request = request
    return c
}

func (c Client) Delete() WebClient {
    request, _ := http.NewRequest(http.MethodDelete, c.uri, nil)
    c.request = request
    return c
}
"><code>package main

func (c Client) Get() WebClient {
    request, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> http.NewRequest(http.MethodGet, c.uri, nil)
    c.request <span class="hljs-operator">=</span> request
    <span class="hljs-keyword">return</span> c
}

func (c Client) Post() WebClient {
    request, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> http.NewRequest(http.MethodPost, c.uri, nil)
    c.request <span class="hljs-operator">=</span> request
    <span class="hljs-keyword">return</span> c
}

func (c Client) Put() WebClient {
    request, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> http.NewRequest(http.MethodPut, c.uri, nil)
    c.request <span class="hljs-operator">=</span> request
    <span class="hljs-keyword">return</span> c
}

func (c Client) Patch() WebClient {
    request, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> http.NewRequest(http.MethodPatch, c.uri, nil)
    c.request <span class="hljs-operator">=</span> request
    <span class="hljs-keyword">return</span> c
}

func (c Client) Delete() WebClient {
    request, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> http.NewRequest(http.MethodDelete, c.uri, nil)
    c.request <span class="hljs-operator">=</span> request
    <span class="hljs-keyword">return</span> c
}
</code></pre><p>위와 같이 바뀌어서, Method에 따라서 Request를 새로이 교체해주는 방식을 사용했다.</p><p>그러다보니 uri를 그때그때 주입해줘도 상관 없을 것 같다는 생각이 들었다.</p><p>최종적으로는 다음과 같다.</p><pre data-type="codeBlock" text="package main

func main() {
    client := http.NewWebClient()

    responseBody, err := client.Get(&quot;https://www.naver.com&quot;).Retrieve()
    if err != nil {
        // DO SOMETHING...
    }

    log.Println(string(responseBody))
}
"><code>package main

func main() {
    client :<span class="hljs-operator">=</span> http.NewWebClient()

    responseBody, err :<span class="hljs-operator">=</span> client.Get(<span class="hljs-string">"https://www.naver.com"</span>).Retrieve()
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-comment">// DO SOMETHING...</span>
    }

    log.Println(<span class="hljs-keyword">string</span>(responseBody))
}
</code></pre>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Smart Contract Audit Process]]></title>
            <link>https://paragraph.com/@primrose/smart-contract-audit-process</link>
            <guid>fGNUxVqUzejl2bh0wr4K</guid>
            <pubDate>Thu, 10 Aug 2023 17:27:52 GMT</pubDate>
            <description><![CDATA[AuditAudit이란 스마트 컨트랙트 코드를 점검하여, 보안 취약점을 찾아내고 안정성을 분석하는 것이다. 업그레이드가 가능한 스마트 컨트랙트를 작성하더라도, 이미 한 번 공격당해 금전적인 손해가 발생한 이후에는 돌이킬 수가 없다.요구 사항 이해 : 감사자는 스마트 계약이 실행하려는 전체 기능과 비즈니스 논리를 이해해야 합니다. 여기에는 프로젝트 문서 검토 및 개발 팀과의 의사 소통이 포함됩니다.수동 코드 검토 : 감사자는 코드를 수동으로 검토하여 취약성, 코드 품질 문제 또는 로직의 불일치를 식별합니다. 이 프로세스는 시간이 많이 소요될 수 있지만 잠재적인 위험에 대한 포괄적인 이해를 제공합니다.자동 분석 : 알려진 취약점에 대해 스마트 계약 코드를 자동으로 스캔하도록 설계된 다양한 도구가 있습니다. 자동화된 분석은 프로세스 속도를 높일 수 있지만 미묘한 차이나 새로운 문제를 놓칠 수 있으므로 수동 검토를 대체할 수는 없습니다.테스트 : 감사자는 일반적으로 정적 및 동적 테스...]]></description>
            <content:encoded><![CDATA[<h1 id="h-audit" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Audit</h1><p>Audit이란 스마트 컨트랙트 코드를 점검하여, 보안 취약점을 찾아내고 안정성을 분석하는 것이다.</p><p>업그레이드가 가능한 스마트 컨트랙트를 작성하더라도, 이미 한 번 공격당해 금전적인 손해가 발생한 이후에는 돌이킬 수가 없다.</p><ol><li><p><strong>요구 사항 이해</strong> : 감사자는 스마트 계약이 실행하려는 전체 기능과 비즈니스 논리를 이해해야 합니다. 여기에는 프로젝트 문서 검토 및 개발 팀과의 의사 소통이 포함됩니다.</p></li><li><p><strong>수동 코드 검토</strong> : 감사자는 코드를 수동으로 검토하여 취약성, 코드 품질 문제 또는 로직의 불일치를 식별합니다. 이 프로세스는 시간이 많이 소요될 수 있지만 잠재적인 위험에 대한 포괄적인 이해를 제공합니다.</p></li><li><p><strong>자동 분석</strong> : 알려진 취약점에 대해 스마트 계약 코드를 자동으로 스캔하도록 설계된 다양한 도구가 있습니다. 자동화된 분석은 프로세스 속도를 높일 수 있지만 미묘한 차이나 새로운 문제를 놓칠 수 있으므로 수동 검토를 대체할 수는 없습니다.</p></li><li><p><strong>테스트</strong> : 감사자는 일반적으로 정적 및 동적 테스트를 모두 수행합니다. 정적 테스트는 코드를 실행하지 않고 분석하는 반면 동적 테스트는 코드를 실행하고 동작을 관찰하는 것입니다. 여기에는 스마트 계약에서 발생할 수 있는 다양한 시나리오를 시뮬레이션하기 위한 사용자 지정 테스트 사례 작성이 포함됩니다.</p></li><li><p><strong>보안 분석</strong> : 보안은 스마트 계약에서 가장 중요하며 감사자는 재진입, 오버플로 등과 같은 일반적인 공격에 대한 잠재적 취약성을 포함하여 계약이 최상의 보안 관행을 준수하는지 평가합니다.</p></li></ol><h1 id="h-audit-process" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Audit Process</h1><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">요구사항 이해</h3><p>감사를 하게 되면 우선 컨트랙트가 실행하려는 전체 기능과 비즈니스 로직에 대한 명세가 필요하다.</p><p>예를 들어 백서나 백서 발행 이후 개정된 내용들에 대해 정보를 수집하는 과정 등이 있다.</p><p>이러한 작업을 통해 명세를 구체화하고, 스마트 컨트랙트가 명세에 맞게 구현되었는지 확인하기 위한 준비를 한다.</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">테스트</h3><p>명세가 확인되었다면, 스마트 컨트랙트에 대한 테스트 코드를 작성한다.</p><p>모든 예외 상황과 라이브러리들이 올바르게 작동하는지 검증하며, 필요로 하는 조건들을 달리하여도 예외 상황을 일으키지 않는지에 대해 검사하는 작업이다.</p><p>이는 각각의 로직들이 명세에 따라 구현되었는지 확인하는 과정이 된다.</p><p>또한 명세를 통해 유추할 수 있는 사용자들의 악의적인 패턴들을 분석하는 시나리오 테스팅까지 해당 과정에서 수행된다.</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">코드 분석</h3><p>테스트 코드가 작성되었고 모든 코드의 예외 상황을 점검했을 때, 정적 분석을 수행한다.</p><p>코드에는 취약점을 가지는 구현 패턴이 존재할 수 있으며, <strong>테스트 코드로 검출되지 않는다</strong>는 특성을 가지고 있다.</p><p>TheDAO의 해킹 사례에서 공격자가 스마트 컨트랙트인 경우에 새로운 로직이 중간에 끼어들 수 있다는 점은 테스트 코드로 검출되기 어렵다.</p><p>이러한 구현 패턴은 보안 감사 도구를 통해서 검증하는 것이 훨씬 정확하다.</p><p>대부분 알려진 스마트 컨트랙트 코드의 취약점들은 Smart Contract Weakness Classification(이하 SWC)라는 이름으로 잘 정리되어 있다. 이 중에서 분석되어야 하는 SWC 항목에 대해 검증하게 된다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/faabc01c8c3872c814cbc6e51500b98395f9b2c25e0d57602be9e7e1e08632dc.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">출처</h2><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://velog.io/@jhin/%EC%BB%A8%ED%8A%B8%EB%9E%99%ED%8A%B8-%EC%98%A4%EB%94%A7audit">https://velog.io/@jhin/%EC%BB%A8%ED%8A%B8%EB%9E%99%ED%8A%B8-%EC%98%A4%EB%94%A7audit</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[DNS]]></title>
            <link>https://paragraph.com/@primrose/dns</link>
            <guid>YgzbEiEgRsqYcGd6AL9g</guid>
            <pubDate>Sun, 23 Jul 2023 18:11:23 GMT</pubDate>
            <description><![CDATA[DNSDNS를 아냐고 물어보면 대부분 그렇다고 하지만, 군더더기 없이 설명할 수 있는 사람은 잘 없는 것 같다. DNS를 구글에 검색하면 다음과 같은 결과가 나온다.... DNS는 IP 주소 및 기타 데이터를 저장하고 이름별로 쿼리할 수 있게 해주는 계층형 분산 데이터베이스입니다. 즉, DNS는 컴퓨터가 서로 통신하는 데 사용하는 숫자 IP 주소로 변환되는, 쉽게 읽을 수 있는 도메인 이름의 디렉터리입니다.벌써 머리가 아프다. 쉽게쉽게 가보자. 일단 DNS는 Domain Name System의 약자이다. 당연히 도메인 이름에 대한 것일터. 위의 글을 보면 계층형 분산 데이터베이스라고 되어있다. 계층형(Hierarchical), 분산 데이터베이스로 미루어보아 “IP로 도메인을 찾거나 그 반대” 라고 추측이 가능하다. 잘 생각해보면, 세상에 그렇게 많은 도메인이 있는데, 대체 어디에 저장되어 있는지, 어떻게 이렇게 빠르게 찾는지에 대해서 궁금해질법하다. 우리는 어떤 사이트에 접속할...]]></description>
            <content:encoded><![CDATA[<h1 id="h-dns" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">DNS</h1><p>DNS를 아냐고 물어보면 대부분 그렇다고 하지만, 군더더기 없이 설명할 수 있는 사람은 잘 없는 것 같다.</p><p>DNS를 구글에 검색하면 다음과 같은 결과가 나온다.</p><blockquote><p>... DNS는 IP 주소 및 기타 데이터를 저장하고 이름별로 쿼리할 수 있게 해주는 계층형 분산 데이터베이스입니다. 즉, DNS는 컴퓨터가 서로 통신하는 데 사용하는 숫자 IP 주소로 변환되는, 쉽게 읽을 수 있는 도메인 이름의 디렉터리입니다.</p></blockquote><p>벌써 머리가 아프다. 쉽게쉽게 가보자.</p><p>일단 DNS는 <code>Domain Name System</code>의 약자이다. 당연히 도메인 이름에 대한 것일터.</p><p>위의 글을 보면 계층형 분산 데이터베이스라고 되어있다. 계층형(Hierarchical), 분산 데이터베이스로 미루어보아 “IP로 도메인을 찾거나 그 반대” 라고 추측이 가능하다.</p><p>잘 생각해보면, 세상에 그렇게 많은 도메인이 있는데, 대체 어디에 저장되어 있는지, 어떻게 이렇게 빠르게 찾는지에 대해서 궁금해질법하다.</p><p>우리는 어떤 사이트에 접속할 때, 보통 IP대신 도메인으로 접속을 한다.</p><p>도메인 이름을 사용하면, 입력한 도메인을 실제 네트워크 상에서 사용하는 IP 주소로 바꾸고 해당 IP 주소로 접속하는 과정이 필요하다.</p><p>이러한 과정 + 전체 시스템을 DNS라고 한다. 이 시스템은 전 세계적으로 약속된 규칙을 공유한다.</p><h1 id="h-dns" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">DNS 구성 요소</h1><p>DNS는 아래 세가지 요소로 구성되어있다.</p><ol><li><p>도메인 네임 스페이스(Domain Name Space)</p></li><li><p>네임 서버(Name Server) = 권한 있는 DNS 서버</p></li><li><p>리졸버(Resolver) = 권한 없는 DNS 서버</p></li></ol><p>개발자라면 다 들어봤을법한 이름들이다.</p><p>이해를 돕기 위해 그냥 인터넷을 개발한다고 해보자.</p><ol><li><p>우선 특정 도메인으로 특정 IP를 찾는(Key-Value처럼) 데이터베이스가 필요하다.</p></li><li><p>그리고 데이터가 어디 저장이 됐는지 찾을 프로그램들이 필요할 것이다.</p></li><li><p>IP를 찾았으면 당연히 해당 IP 주소로 이동시켜주는 프로그램도 필요하다.</p></li></ol><p>과거의 천재들은 이 말도 안되는 요구사항을 충족하기 위해서 위의 3가지를 개발했다.</p><ul><li><p><code>Domain Name Space</code>라는 규칙으로 도메인 이름 저장을 분산한다.</p></li><li><p><code>Name Server</code>가 해당 도메인 이름의 IP 주소를 찾는다.</p></li><li><p><code>Resolver</code>가 DNS 클라이언트 요청을 네임서버로 전달하고, 찾은 정보를 클라이언트에게 전달한다.</p></li></ul><blockquote><p>필자는 대충 애플리케이션이라고 생각하고 네임 스페이스가 DB, 네임 서버가 서버, Resolver가 프론트라고 이해했다.</p></blockquote><p>솔직히 쓸 말은 더 많지만, 추후에 다른 주제와 같이 더 deep 하게 다루는 글을 작성하겠다.</p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[URL, URI]]></title>
            <link>https://paragraph.com/@primrose/url-uri</link>
            <guid>798jh76CwahO5qRflVru</guid>
            <pubDate>Sun, 23 Jul 2023 17:48:00 GMT</pubDate>
            <description><![CDATA[URL과 URIURL과 URI에 대한 얘기가 나왔을 때 직장동료가 “하나는 식별자고 하나는 아니다” 라는 얘기를 했는데, 속으로 “그렇게 쉽게 표현하다니, 대단한데?” 라고 생각했다. 개인적으로 좀 더 이해도를 높이고자 글로 남기려고 한다.URI(Uniform Resource Identifier)URI에 대해 검색해보면 다음과 같은 설명들이 쭉 나온다.인터넷에 있는 자원을 나타내는 유일한 주소이다.웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스다.URI의 하위 개념으로 URL과 URN이 있다.어떤 형식이 있다기 보다는 특정 자원을 식별하는 문자열을 의미한다.구구절절 말 들이 많은데, 단어를 뜯어보면 쉽게 이해할 수 있다.Uniform유니폼은 리소스를 식별하는 통일된 방식을 말한다. 축구팀 유니폼처럼.Resource리소스는 직역해도 자원이고 실제로 여기서도 자원을 뜻한다. URI로 식별이 가능한 모든 종류의 자원(웹 브라우저 파일 및 그 외)를 전...]]></description>
            <content:encoded><![CDATA[<h1 id="h-url-uri" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">URL과 URI</h1><p>URL과 URI에 대한 얘기가 나왔을 때 직장동료가 “하나는 식별자고 하나는 아니다” 라는 얘기를 했는데, 속으로 “그렇게 쉽게 표현하다니, 대단한데?” 라고 생각했다.</p><p>개인적으로 좀 더 이해도를 높이고자 글로 남기려고 한다.</p><h1 id="h-uriuniform-resource-identifier" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">URI(Uniform Resource Identifier)</h1><p>URI에 대해 검색해보면 다음과 같은 설명들이 쭉 나온다.</p><ul><li><p>인터넷에 있는 자원을 나타내는 유일한 주소이다.</p></li><li><p>웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스다.</p></li><li><p>URI의 하위 개념으로 URL과 URN이 있다.</p></li><li><p>어떤 형식이 있다기 보다는 특정 자원을 식별하는 문자열을 의미한다.</p></li></ul><p>구구절절 말 들이 많은데, 단어를 뜯어보면 쉽게 이해할 수 있다.</p><h3 id="h-uniform" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Uniform</h3><p>유니폼은 리소스를 식별하는 통일된 방식을 말한다. 축구팀 유니폼처럼.</p><h3 id="h-resource" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Resource</h3><p>리소스는 직역해도 자원이고 실제로 여기서도 자원을 뜻한다. URI로 식별이 가능한 모든 종류의 자원(웹 브라우저 파일 및 그 외)를 전부 지칭하는 말이다.</p><h3 id="h-identifier" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Identifier</h3><p>식별자.</p><p>즉 URI는 인터넷 상의 리소스를 식별하는 문자열이다. 그래서 쉽게 “URI는 식별자이다” 라고 말해도 무리가 없을 것 같다.</p><h1 id="h-url-uniform-resource-locator" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">URL (Uniform Resource Locator)</h1><p>시간낭비 하지말고 같은 방식으로 접근해보자. Uniform과 Resource는 위에서 했으니 지나가자.</p><h3 id="h-locator" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Locator</h3><p><code>Location</code>이 위치를 뜻하는 것은 다들 알 것이다. Locator를 검색하면 뭔 이상한 말이 나오는데, 그냥 “위치를 가리키는 어떤 것” 정도로 이해해보자.</p><p>즉 URL은 네트워크상에서 자원의 위치를 나타내기 위한 규약이다. 리소스를 식별할 뿐만 아니라 위치까지 표현해야한다.</p><blockquote><p>웹 사이트 주소 + 컴퓨터 네트워크 상의 자원</p></blockquote><p>네이버에 들어갈 때 우리는 <code>https://www.naver.com</code>로 접속한다.</p><p>자연스럽게 앞에 프로토콜을 붙여주는데, 이러면 URL이다.</p><p>특정 웹 페이지의 주소에 접속하기 위해서는 주소뿐만 아니라 프로토콜(https, http, sftp, smp 등)을 함께 알아야 접속이 가능한데, 이들을 모두 나타내는 것이 URL이다.</p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[UUPS vs Transparent]]></title>
            <link>https://paragraph.com/@primrose/uups-vs-transparent</link>
            <guid>Eh0J9cVM0ORpNOj2wxIx</guid>
            <pubDate>Sat, 22 Jul 2023 15:03:50 GMT</pubDate>
            <description><![CDATA[Upgradeable Contract업그레이드 가능한 스마트 컨트랙트는 보통 proxy pattern을 사용해서 배포된다. 한 번 배포된 스마트 컨트랙트의 코드를 변경할 수 없기 때문에, 저장을 담당하는 Storage Contract와 실제 비즈니스 로직을 포함하는 Logic Contract로 분할하여 작동한다. 자세한 내용은 이전에 작성한 글을 참고하면 좋을 것 같다. https://mirror.xyz/0xA1d9f681B25C14C1eE7B87f1CF102E73cA3ad4d9/tCzymqSMhpU5h3y0cuN5oz58O7eVu6r6hr17WxUskZY 아무튼 요약 정리하면 다음과 같다.Upgradeable contract는 코드를 추론하기 어렵게 만들고, 버그의 위험이 증가되므로 충분한 테스트와 Audit process가 이루어져야 한다.Contract를 업그레이드 할 수 있으므로 업그레이드를 수행할 권한이 있는 관리자 계정이 있어야한다. (역으로 말하자면 관리자 계정으로...]]></description>
            <content:encoded><![CDATA[<h1 id="h-upgradeable-contract" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Upgradeable Contract</h1><p>업그레이드 가능한 스마트 컨트랙트는 보통 proxy pattern을 사용해서 배포된다.</p><p>한 번 배포된 스마트 컨트랙트의 코드를 변경할 수 없기 때문에, 저장을 담당하는 <code>Storage Contract</code>와 실제 비즈니스 로직을 포함하는 <code>Logic Contract</code>로 분할하여 작동한다.</p><p>자세한 내용은 이전에 작성한 글을 참고하면 좋을 것 같다.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/0xA1d9f681B25C14C1eE7B87f1CF102E73cA3ad4d9/tCzymqSMhpU5h3y0cuN5oz58O7eVu6r6hr17WxUskZY">https://mirror.xyz/0xA1d9f681B25C14C1eE7B87f1CF102E73cA3ad4d9/tCzymqSMhpU5h3y0cuN5oz58O7eVu6r6hr17WxUskZY</a></p><p>아무튼 요약 정리하면 다음과 같다.</p><ul><li><p>Upgradeable contract는 코드를 추론하기 어렵게 만들고, 버그의 위험이 증가되므로 충분한 테스트와 Audit process가 이루어져야 한다.</p></li><li><p>Contract를 업그레이드 할 수 있으므로 업그레이드를 수행할 권한이 있는 관리자 계정이 있어야한다. (역으로 말하자면 관리자 계정으로 중앙 집중화가 된다).</p></li><li><p>자료구조의 레이아웃이 변경되어서는 안된다.</p></li></ul><h1 id="h-uups-universal-upgradeable-proxy-standard" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">UUPS (Universal Upgradeable Proxy Standard)</h1><p>EIP-1822에 해당하는 이 표준은 간단하고 효율적으로 업그레이드를 할 수 있도록 해준다.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-1822">https://eips.ethereum.org/EIPS/eip-1822</a></p><p>이 패턴에선 구현 관리 및 fallback 기능을 포함한 모든 작업이 한 컨트랙트에 포함되어 있다.</p><p>UUPS 패턴에서는 업그레이드 기능을 호출할 수 있는 사람을 제한하기 위해 modifier를 사용하여 업그레이드 기능을 보호할 수 있다.</p><p>또한 UUPS 패턴은 단순성 때문에 더 가스 효율적이다.</p><h1 id="h-transparent-openzeppelin" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Transparent (Openzeppelin)</h1><p>Transparent 에서는 Proxy Contract와 Implementation Contract로 나뉜다.</p><p>당연히 배포비용이 많이 들지만, 유지 관리가 쉽다.</p><p>오픈제플린에서는 UUPS를 권장한다. 말 듣는게 좋을 것 같다.</p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Cross compile]]></title>
            <link>https://paragraph.com/@primrose/cross-compile</link>
            <guid>ixDgptRieQemzkiADvHt</guid>
            <pubDate>Fri, 07 Jul 2023 17:22:32 GMT</pubDate>
            <description><![CDATA[Cross compile크로스 컴파일이라는 말을 들어본 적이 있을 것이다. 컴파일러가 실행되는 플랫폼이 아닌, 다른 플랫폼에서 실행 가능한 코드를 생성할 수 있는 컴파일러를 뜻한다.운영체제를 지원하지 않는 마이크로 컨트롤러와 같이 컴파일 불가능한 플랫폼에 컴파일 하는데 사용된다.https://haneepark.github.io/2018/09/15/what-is-cross-compile/A 머신에서 컴파일러를 작동해서 B 머신에서 동작하는 바이너리를 만드는 것이라고 생각하면 쉽다.Linkhttps://kkhipp.tistory.com/160 https://blankspace-dev.tistory.com/242 https://haneepark.github.io/2018/09/15/what-is-cross-compile/]]></description>
            <content:encoded><![CDATA[<h1 id="h-cross-compile" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Cross compile</h1><p>크로스 컴파일이라는 말을 들어본 적이 있을 것이다.</p><p>컴파일러가 실행되는 플랫폼이 아닌, 다른 플랫폼에서 실행 가능한 코드를 생성할 수 있는 컴파일러를 뜻한다.</p><blockquote><p>운영체제를 지원하지 않는 마이크로 컨트롤러와 같이 컴파일 불가능한 플랫폼에 컴파일 하는데 사용된다.</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/59218515565935bcb8f586b8a52cc4654cf813b7e03ace6ab22ec520eefba92e.png" alt="https://haneepark.github.io/2018/09/15/what-is-cross-compile/" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">https://haneepark.github.io/2018/09/15/what-is-cross-compile/</figcaption></figure><p>A 머신에서 컴파일러를 작동해서 B 머신에서 동작하는 바이너리를 만드는 것이라고 생각하면 쉽다.</p><h1 id="h-link" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Link</h1><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://kkhipp.tistory.com/160">https://kkhipp.tistory.com/160</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blankspace-dev.tistory.com/242">https://blankspace-dev.tistory.com/242</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://haneepark.github.io/2018/09/15/what-is-cross-compile/">https://haneepark.github.io/2018/09/15/what-is-cross-compile/</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
        <item>
            <title><![CDATA[Index]]></title>
            <link>https://paragraph.com/@primrose/index</link>
            <guid>JRvOkGu5lezxKMFLFhPq</guid>
            <pubDate>Fri, 23 Jun 2023 16:36:09 GMT</pubDate>
            <description><![CDATA[IndexDatabase에서 데이터를 검색할 때, 적재된 데이터의 양이 매우 많거나, 많아질 것이 자명한 경우. 보통 Index를 특정 column에 적용함으로써 쿼리의 성능을 해결하려는 경우가 많다. 오늘은 다음 두 단골 질문에 대해서 정리하려고 한다.Index를 적용하면 어떤 원리로 쿼리 성능이 향상되는 것일까?Index를 적용하는 것이 무조건 좋은가?About Index인덱스는 데이터베이스 테이블에 대한 검색 성능의 속도를 높여주는 자료 구조다. 특정 컬럼에 인덱스를 생성하면, 해당 컬럼의 데이터들을 정렬하여 별도의 메모리 공간에 데이터의 물리적 주소와 함께 저장된다. 아래 그림을 보면 특정 컬럼에 인덱스가 생성됐을 때 컬럼의 데이터들을 오름차순으로 정렬한 모습을 볼 수 있다.https://choicode.tistory.com/27인덱스의 가장 큰 특징은 데이터들이 정렬이 되어있다는 점이다. 이 특징으로 인해 조건 검색이라는 영역에서 굉장한 장점이 된다.WHERE 절의 효...]]></description>
            <content:encoded><![CDATA[<h1 id="h-index" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Index</h1><p>Database에서 데이터를 검색할 때, 적재된 데이터의 양이 매우 많거나, 많아질 것이 자명한 경우.</p><p>보통 Index를 특정 column에 적용함으로써 쿼리의 성능을 해결하려는 경우가 많다.</p><p>오늘은 다음 두 단골 질문에 대해서 정리하려고 한다.</p><ul><li><p>Index를 적용하면 어떤 원리로 쿼리 성능이 향상되는 것일까?</p></li><li><p>Index를 적용하는 것이 무조건 좋은가?</p></li></ul><h1 id="h-about-index" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">About Index</h1><p><strong>인덱스는 데이터베이스 테이블에 대한 검색 성능의 속도를 높여주는 자료 구조다.</strong></p><p>특정 컬럼에 인덱스를 생성하면, 해당 컬럼의 데이터들을 정렬하여 별도의 메모리 공간에 데이터의 물리적 주소와 함께 저장된다.</p><p>아래 그림을 보면 특정 컬럼에 인덱스가 생성됐을 때 컬럼의 데이터들을 오름차순으로 정렬한 모습을 볼 수 있다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cbf9d5d53838bae74d059a7533168b73958d3221158ae0fa22d826b7f274f90d.png" alt="https://choicode.tistory.com/27" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">https://choicode.tistory.com/27</figcaption></figure><p>인덱스의 가장 큰 특징은 데이터들이 정렬이 되어있다는 점이다.</p><p>이 특징으로 인해 조건 검색이라는 영역에서 굉장한 장점이 된다.</p><h3 id="h-where" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">WHERE 절의 효율성</h3><p>테이블을 만들고 안에 데이터가 쌓이게 되면 테이블의 레코드(row : 행)는 내부적으로 순서가 없이 뒤죽박죽으로 저장이 된다.</p><p>그 결과, WHERE 절에 특정 조건에 맞는 데이터들을 검색할 때 풀스캔을 해서 비교한다.</p><p>하지만 인덱스 테이블 스캔(Index Table Scan) 시 인덱스 테이블은 데이터들이 정렬되어 저장되어 있기 때문에 해당 조건(WHERE)에 맞는 데이터들을 빠르게 찾아낼 수 있는 것이다.</p><p>이것이 인덱스를 사용하는 가장 큰 이유이다.</p><h3 id="h-order-by" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">ORDER BY 절의 효율성</h3><p>인덱스를 사용하면 ORDER BY에 의한 정렬(Sort) 과정을 피할 수가 있다.</p><p>ORDER BY는 굉장히 부하가 많이 걸리는 작업이다.</p><p>정렬과 동시에 1차적으로 메모리에서 정렬이 이루어지고 메모리보다 큰 작업이 필요하다면 디스크 I/O도 추가적으로 발생되기 때문이다.</p><blockquote><p>디스크 I/O는 데이터를 작성하고 변경할 때 HDD에 저장되는 것을 말한다.</p></blockquote><p>하지만 인덱스를 사용하면 이러한 전반적인 자원의 소모를 하지 않아도 된다.</p><h3 id="h-min-max" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">MIN, MAX의 효율적인 처리</h3><p>이것 또한 데이터가 정렬되어 있기에 얻을 수 있는 장점이다.</p><p>MIN값과 MAX값을 레코드의 시작 값과 끝 값만 가져오면 되기 때문에 풀스캔으로 작업하는 것보다 훨씬 효율적이다.</p><h2 id="h-cons-of-index" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Cons of Index</h2><p>인덱스의 가장 큰 문제점은 정렬된 상태를 계속 유지시켜줘야 한다는 점이다.</p><h3 id="h-dml" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">DML</h3><p>INSERT, UPDATE, DELETE를 통해 데이터가 추가되거나 값이 바뀐다면 인덱스 테이블 내에 있는 값들을 다시 정렬을 해야 한다.</p><p>그렇기 때문에 DML이 빈번한 테이블보다 검색을 위주로 하는 테이블에 인덱스를 생성하는 것이 좋다.</p><blockquote><p>주문데이터, 거래데이터와 같이 DML이 빈번한 테이블에 인덱스를 생성하게 되면 오히려 성능이 저하될 수 있다.</p></blockquote><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">속도 향상을 위해 인덱스를 많이 만드는 것은 좋지 않다.</h3><p>인덱스를 관리하기 위해서는 데이터베이스의 약 10%에 해당하는 저장공간이 추가로 필요하다.</p><p>무턱대고 인덱스를 만들어서는 결코 안 된다는 것이다.</p><p>즉, 속도 향상에 비해 단점들의 COST를 비교해서 인덱스를 만들지 말지를 정해야 한다.</p><h1 id="h-data-structure-of-index" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Data structure of Index</h1><p>우선 <code>B+ Tree</code>에 대해서 알아보자.</p><p>B+Tree는 이름처럼 트리 구조로 되어 있다. 바이너리 트리랑 개념적으로는 비슷하다.</p><p>따라서 Key 값으로 정렬되어 있는 형태인데, 다른 점은 <code>Children 노드가 여러 개</code>라는 점이다.</p><p>또한 각각의 노드가 메모리가 아닌 디스크에 있다.</p><p>아래 그림을 참조하자. 각 노드들이 디스크에 저장되어 있고 포인터로 찾아가는 방식이다.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fc889da93e761686830b8ac008ca7ff2eba9a8735d182f5c172552ed024fb986.png" alt="https://commons.wikimedia.org/w/index.php?curid=10758840" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">https://commons.wikimedia.org/w/index.php?curid=10758840</figcaption></figure><p>또한 각 leaf노드는 형제 노드(sibling)으로 가는 포인터가 있기 때문에 range query에 효율적이다.</p><h1 id="h-link" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Link</h1><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://choicode.tistory.com/27">https://choicode.tistory.com/27</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://coding-factory.tistory.com/746">https://coding-factory.tistory.com/746</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://velog.io/@alicesykim95/DB-%EC%9D%B8%EB%8D%B1%EC%8A%A4Index%EB%9E%80">https://velog.io/@alicesykim95/DB-%EC%9D%B8%EB%8D%B1%EC%8A%A4Index%EB%9E%80</a></p>]]></content:encoded>
            <author>primrose@newsletter.paragraph.com (Primrose)</author>
        </item>
    </channel>
</rss>