go-ethereum은 유구한 역사를 지니고 있습니다.
이 프로젝트는 go-ethereum 코드의 주석을 번역하고, 이해한 내용을 정리합니다.
2024년 1월 22일 시작, goerli 테스트넷에 덴쿤 업그레이드 적용 이후의 시점임을 알립니다.
GithubOverview
소개
go-ethereum의 common 패키지는 프로젝트에서 공통적으로 사용되는 타입 및 유틸리티 함수들을 정의합니다.
주요 기능
- Hash, Address와 같은 자주 사용되는 타입들을 정의합니다.
- 빠른 비트 연산을 제공합니다.
solc
컴파일러의 컴파일 결과를 파싱하는 기능을 제공합니다.- RPC API에서 사용되는 JSON 형식의 페이로드를 읽고 쓰는 도구를 제공합니다.
- 큰 정수 연산을 위한 유틸리티 함수들을 제공합니다.
common 패키지
big.go
- 자주 사용되는 big.Int 타입의 상수들을 정의합니다.
bytes.go
- 바이트열을 다루는 함수들을 정의합니다. (Conversion, Padding, Trimming, Copy, Slicing)
주요 함수
FromHex 함수
// FromHex는 16진수 문자열 s로부터 바이트열을 반환합니다.
// s는 "0x"로 시작할 수 있습니다.
func FromHex(s string) []byte {
if has0xPrefix(s) { // 0x로 시작하는 경우 - 0x 제거 (hex.DecodeString은 16진수 문자열이 아닌 경우 에러 반환 (x는 16진수가 아님))
s = s[2:]
}
if len(s)%2 == 1 { // 홀수인 경우 - 앞에 0 추가
s = "0" + s
}
return Hex2Bytes(s) // 16진수 문자열을 바이트열로 변환
}
debug.go
- 디버깅을 위한 함수들을 정의합니다.
format.go
- time.Duration을 예쁘게 출력하기 위한 타입들과 그 타입들의 메서드들을 정의합니다.
path.go
- 파일 경로를 다루는 함수들을 정의합니다.
size.go
- float64 타입의 사용자 친화적인 출력 형식을 지원하기 위해 StorageSize 타입을 정의합니다.
types.go
- 자주 사용되는 타입들을 정의합니다.
주요 타입들
// 해시와 주소의 길이 (바이트 단위)
const (
// 해시의 길이는 32바이트
HashLength = 32
// 주소의 길이는 20바이트
AddressLength = 20
)
// Hash는 임의의 데이터의 Keccak256 해시를 나타냅니다. (32바이트)
type Hash [HashLength]byte
// Address는 이더리움 계정의 20바이트 주소를 나타냅니다.
type Address [AddressLength]byte
공통 기능
- SQL Database 지원을 위한 database/sql 패키지의 Scanner, database/sql/driver 패키지의 Valuer 인터페이스를 구현합니다.
- GraphQL 지원을 위한 메서드들을 정의합니다.
EIP-55 주소 체크섬
// Hex는 EIP55 호환성을 갖는 16진수 문자열 표현을 반환합니다.
func (a Address) Hex() string {
return string(a.checksumHex())
}
// checksumHex는 EIP55 호환성을 갖는 16진수 문자열 표현을 바이트열로 반환합니다.
func (a *Address) checksumHex() []byte {
buf := a.hex() // 16진수 형식의 주소 (0x 접두사 포함, 모두 소문자)
// compute checksum
sha := sha3.NewLegacyKeccak256()
sha.Write(buf[2:]) // 0x를 제외한 소문자 주소를 해시 함수의 입력으로 사용
hash := sha.Sum(nil) // keccak256 해시
for i := 2; i < len(buf); i++ { // 접두사 0x를 제외하고 EIP55 호환성을 위해 16진수 문자열을 대문자로 변환
hashByte := hash[(i-2)/2] // buf[i]의 대응하는 해시 바이트
if i%2 == 0 { // 짝수 인덱스의 경우
hashByte = hashByte >> 4 // 상위 4비트 추출
} else {
hashByte &= 0xf // 하위 4비트 추출
}
if buf[i] > '9' && hashByte > 7 { // buf[i]가 숫자가 아니고, hashByte가 0x8 이상인 경우
buf[i] -= 32 // 대문자로 변환
}
}
return buf[:]
}
// hex는 0x 접두사를 포함한 16진수 문자열 표현의 바이트열을 반환합니다.
func (a Address) hex() []byte {
var buf [len(a)*2 + 2]byte
copy(buf[:2], "0x")
hex.Encode(buf[2:], a[:])
return buf[:]
}
common/bitutil 패키지
bitutil.go
- 빠른 비트 연산을 위한 함수들을 제공합니다.
- 비정렬 메모리 접근(Unaligned memory access)이 가능한 아키텍처와 아닌 아키텍처를 구분하여 함수를 실행합니다.
compress.go
- Sparse Bitset 알고리즘을 구현한 함수들을 제공합니다. (대부분의 비트가 0인 경우에 효율적인 알고리즘)
- `용도``: ???
common/compiler 패키지
helper.go
- 실행 가능한 컨트랙트의 정보를 담는 구조체인
Contract
를 정의합니다. - 컴파일된 컨트랙트의 정보를 담는 구조체인
ContractInfo
를 정의합니다.
solidity.go
solc
컴파일러의 컴파일 결과를 담는 구조체인solcOutput
과solcOutputV8
를 정의합니다.
💡 solidity v.0.8 이상부터는 ABI, Devdoc 및 Userdoc가 직렬화되는 방식이 변경되었으므로
solcOutputV8
을 사용합니다.
- ❓ 의문점:
solc
컴파일러의 컴파일 결과 json 파일을 파싱할 때 문자열을 굳이 interface{} 타입으로 파싱하는 이유는 무엇일까?
common/fdlimit 패키지
common/hexutil 패키지
hexutil.go
- 0x 접두사가 있는 16진수 인코딩을 구현합니다.
- 이더리움 RPC API에서 바이너리 데이터를 JSON 형식의 페이로드로 전송하는 데 사용됩니다.
💡 encoding/hex 패키지와의 차이점은 0x 접두사가 필수적으로 붙는다는 점입니다.
인코딩 규칙
-
모든 16진수 데이터는 "0x" 접두사를 가져야 합니다.
-
바이트 슬라이스의 경우 16진수 데이터는 짝수 길이여야 합니다. 빈 바이트 슬라이스는 "0x"로 인코딩됩니다.
-
정수는 최소한의 숫자를 사용하여 인코딩됩니다.(앞에 0이 붙지 않은 숫자). 그들의 인코딩은 홀수 길이일 수 있습니다. 숫자 0은 "0x0"으로 인코딩됩니다.
json.go
- 이더리움 RPC API에서 JSON 형식의 페이로드를 읽고 쓰는 데 사용되는 유틸리티 함수들을 정의합니다.
- JSON 형식으로 마샬링/언마샬링을 위한
Bytes
,Big
,U256
,Uint64
,Uint
같은 별도의 타입들을 정의합니다.
사용된 외부 패키지
- github.com/holiman/uint256 : 256비트 정수 타입을 정의합니다.
common/lru 패키지
common/math 패키지
big.go
big.Int
타입과 관련된 타입과 유틸리티 함수들이 정의되어 있습니다.
2의 보수
- 어떤 수를 커다란 2의 제곱수에서 빼서 얻은 2진수를 2의 보수라고 합니다.
- 2의 보수를 사용하는 이유는 음수를 부호가 없는 정수(unsigned integer)로 표현하기 위해서입니다.
- 이 때 부호가 없는 정수는 이더리움에서 사용하는 256비트 uint256 타입의 정수입니다.
// U256은 큰 정수를 256비트 2의 보수 숫자로 인코딩합니다. 이 연산은 파괴적(원본값을 변경)입니다.
func U256(x *big.Int) *big.Int {
// x가 양수인 경우: x & tt256m1 -> x 자기 자신을 반환
// x가 음수인 경우: (2**256 - 1) & (-x) = (2**256 - 1) &^ (x - 1) -> x의 2의 보수를 반환 (여기서 x - 1은 양수 x의 모든 비트를 반전시킨 값)
// 빼기 연산을 사용하지 않은 이유: x가 양수인 경우 2**256 - x는 완전히 다른 값이 되므로
return x.And(x, tt256m1)
}
// S256은 2의 보수 x를 부호가 있는 256비트 숫자로 인코딩합니다.
// x는 256비트를 초과해서는 안됩니다(그렇게 되면 결과는 정의되지 않음).
// 이 연산은 파괴적(원본값을 변경)이지 않습니다.
//
// S256(0) = 0
// S256(1) = 1
// S256(2**255) = -2**255
// S256(2**256-1) = -1
func S256(x *big.Int) *big.Int {
if x.Cmp(tt255) < 0 { // x < 2**255 (왜 2**255보다 작으면 양수로 취급하는가?)
return x
}
return new(big.Int).Sub(x, tt256) // x - 2**256
}
integer.go
- 정수와 관련된 타입과 도우미 함수들이 정의되어 있습니다.
common/mclock 패키지
Overview
소개
go-ethereum의 core 패키지는 Ethereum의 핵심 타입과 관련된 기능을 구현한 패키지입니다.
types 패키지
- 이더리움 프로토콜을 구현하는 데 필요한 핵심 타입들이 정의되어 있습니다.
block.go
- 블록, 블록 헤더, 블록 바디를 정의한 파일입니다.
Header
필드명 | 설명 | 크기 | json | 특이사항 |
---|---|---|---|---|
ParentHash | 이전 블록의 해시 | 32 bytes | parentHash | |
UncleHash | 엉클 블록의 해시 | 32 bytes | sha3Uncles | |
Coinbase | 블록 생성자의 주소 | 20 bytes | miner | |
Root | 상태 머클 패트리샤 트리의 머클루트 | 32 bytes | stateRoot | |
TxHash | 트랜잭션의 머클루트 | 32 bytes | transactionsRoot | |
ReceiptHash | 트랜잭션 영수증의 머클루트 | 32 bytes | receiptsRoot | |
Bloom | 블룸필터 | 256 bytes | logsBloom | |
Difficulty | 난이도 | ?? bytes | difficulty | |
Number | 블록 번호 | ?? bytes | number | |
GasLimit | 블록의 gas limit | ?? bytes | gasLimit | |
GasUsed | 블록에서 사용된 gas | ?? bytes | gasUsed | |
Time | 블록 생성 시간 | ?? bytes | timestamp | |
Extra | 블록의 extra data | ?? bytes | extraData | |
MixDigest | 믹스해시 | 32 bytes | mixHash | |
Nonce | 블록의 nonce | 8 bytes | nonce | |
BaseFee | 블록의 base fee | ?? bytes | baseFeePerGas | EIP-1559에서 추가(optional) |
WithdrawalHash | validator의 인출 요청 시 발생하는 트랜잭션의 머클루트 | 32 bytes | withdrawalHash | EIP-4895에서 추가(optional) |
BlobGasUsed | 블록에서 사용된 blob gas의 합 | ?? bytes | blobGasUsed | EIP-4844에서 추가(optional) |
ExcessBlobGas | 블록 당 타깃 사용량을 초과한 blob gas의 양 | ?? bytes | excessBlobGas | EIP-4844에서 추가(optional) |
ParentBeaconRoot | 이전 비콘 블록의 머클루트 | 32 bytes | parentBeaconRoot | EIP-4788에서 추가(optional) |
- 블록 해시는 RLP 인코딩된 블록 헤더의 keccak256 해시입니다.
?? bytes
는 RLP 인코딩에 대해 공부한 후에 다시 살펴보겠습니다.
Body
필드명 | 설명 | 특이사항 |
---|---|---|
Transactions | 트랜잭션 목록 | |
Uncles | 엉클 블록 목록 | |
Withdrawals | 출금 작업 목록 | EIP-4895에서 추가(optional) |
Block
필드명 | 설명 | 특이사항 |
---|---|---|
header | 블록 헤더 | |
uncles | 엉클 블록 목록 | |
transactions | 트랜잭션 목록 | |
withdrawals | 출금 작업 목록 | EIP-4895에서 추가(optional) |
hash | 블록 해시 | 처음 호출 시 계산되고 이후에는 캐싱됩니다. |
size | 블록의 크기 | 처음 호출 시 계산되고 이후에는 캐싱됩니다. |
ReceivedAt | 블록이 받아들여진 시간 | 피어 간 블록 릴레이에 사용됩니다. |
ReceivedFrom | 블록을 보낸 피어의 정보 | 피어 간 블록 릴레이에 사용됩니다. |
블록 불변성 규칙
- 블록 생성 시에 입력되는 모든 값은 복사되어 블록에 저장됩니다. 따라서 입력값이 변경되더라도 블록에 저장된 값은 변경되지 않습니다.
- 블록의 헤더는 블록 해시와 크기에 영향을 미치기 때문에 항상 복사하여 사용합니다.
- 새로운 바디 데이터가 블록에 추가될 때는 블록의 얕은 복사본을 만들어 사용합니다.
- 블록의 바디는 블록 해시와 크기에 영향을 미치지 않고 비용이 많이 들기 때문에 참조하여 사용합니다.
gen_header_json.go
- 블록 헤더의 JSON 인코딩/디코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
gen_header_rlp.go
- 블록 헤더의 RLP 인코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
bloom9.go
- 블룸필터를 정의한 파일입니다.
- 블록 헤더의
Bloom
필드에 사용되는 로그 블룸필터는 블록에 포함된 트랜잭션의 로그에 대한 빠른 검색을 위해 사용됩니다. - 비트 코인의 블룸 필터는 관심사를 직접 드러내는 것을 방지하기 위해 프라이버시 보호를 위한 용도로 사용됩니다.
const (
// BloomByteLength는 블록 헤더의 로그 블룸에 사용되는 바이트 수를 나타냅니다.
BloomByteLength = 256
// BloomBitLength는 헤더 로그 블룸에 사용되는 비트 수를 나타냅니다.
BloomBitLength = 8 * BloomByteLength
)
// Bloom은 2048 비트 블룸 필터를 나타냅니다.
type Bloom [BloomByteLength]byte
// bloomValues는 주어진 데이터에 대해 설정할 바이트 (인덱스-값 쌍)를 반환합니다.
// hashbuf는 6바이트 이상의 임시 버퍼여야 합니다.
func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) {
sha := hasherPool.Get().(crypto.KeccakState) // keccak256 해시 함수를 풀에서 가져옵니다.
sha.Reset() // 해시 함수를 초기화합니다.
sha.Write(data) // 데이터를 해싱합니다. (한 번만 해싱합니다.)
sha.Read(hashbuf) // 해시를 읽습니다. (Sum보다 Read가 더 빠릅니다.)
hasherPool.Put(sha) // 해시 함수를 풀에 반환합니다.
// 필터에 추가되는 비트 자리를 구합니다. (1을 0~7만큼 왼쪽으로 시프트한 값)
v1 := byte(1 << (hashbuf[1] & 0x7))
v2 := byte(1 << (hashbuf[3] & 0x7))
v3 := byte(1 << (hashbuf[5] & 0x7))
// 데이터를 필터에 추가하기 위해 OR 연산할 바이트의 인덱스
i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 // v1의 바이트 인덱스는 hashbuf를 uint16 빅 엔디언으로 읽은 값의 하위 11비트를 3만큼 오른쪽으로 시프트한 값을 uint로 변환한 값에서 1을 뺀 값입니다.
i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
return i1, v1, i2, v2, i3, v3
}
- 정확히 어떤 알고리즘을 사용하는지는 모르겠습니다. (아시는 분은 알려주세요.)
hashes.go
- 비어있는 트리의 루트를 상황에 맞게 사용할 수 있게 변수로 정의해둔 파일입니다.
hashing.go
-
keccak256 해시 함수의 인스턴스를 재사용하기 위한 풀과 머클루트를 계산하기 위해 구현해야 하는 인터페이스를 정의한 파일입니다.
-
해시 함수 및 임시 버퍼 풀은
sync.Pool
을 사용하여 구현되어 있습니다. -
풀에서 가져온 객체는 사용 후에는 반드시 풀에 반환해야 합니다.
-
TrieHasher
,DerivableList
는 머클루트를 계산하기 위해 구현해야 하는 인터페이스입니다. -
StackTrie
가 무엇인지 의문. 이해하기 어려운 주석이 달려있습니다.
log.go
- 이더리움의 이벤트 로그 타입을 정의한 파일입니다.
컨센서스 필드: 이더리움 황서에 정의된 필드
필드명 | 설명 | json | 특이사항 |
---|---|---|---|
Address | 이벤트 로그를 생성한 컨트랙트의 주소 | address | |
Topics | 이벤트 로그의 토픽 | topics | |
Data | 이벤트 로그의 데이터 | data | abi 인코딩된 데이터 |
파생 필드: 노드 구현체에서 추가한 필드
필드명 | 설명 | json | 특이사항 |
---|---|---|---|
BlockNumber | 이벤트 로그가 포함된 블록의 번호 | blockNumber | |
TxHash | 이벤트 로그가 포함된 트랜잭션의 해시 | transactionHash | |
TxIndex | 이벤트 로그가 포함된 트랜잭션의 인덱스 | transactionIndex | |
BlockHash | 이벤트 로그가 포함된 블록의 해시 | blockHash | |
Index | 이벤트 로그의 인덱스 | logIndex | |
Removed | 이벤트 로그가 제거되었는지 여부 | removed | 체인 재구성으로 인해 이벤트 로그가 제거되었을 때 true로 설정됩니다. |
gen_log_json.go
- 로그의 JSON 인코딩/디코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run github.com/fjl/gencodec -type Log -field-override logMarshaling -out gen_log_json.go
gen_log_rlp.go
- 로그의 RLP 인코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run ../../rlp/rlpgen -type Log -out gen_log_rlp.go
receipt.go
- 트랜잭션 결과에 대한 영수증을 정의한 파일입니다.
- 컨센서스 필드만으로 RLP 인코딩/디코딩을 하는 컨센서스 방식
- 컨센서스 필드에서 블룸 필터를 제외한 필드만으로 RLP 인코딩하고 디코딩할 때 재계산하는 스토리지 방식
- 왜 두 가지 방식으로 나누어져 있는지는 모르겠습니다.
컨센서스 필드: 이더리움 황서에 정의된 필드
필드명 | 설명 | json | 특이사항 |
---|---|---|---|
Type | 영수증의 타입 | type | |
PostState | 트랜잭션 실행 후의 상태 | postState | |
Status | 트랜잭션의 실행 결과 | status | 0: 실패, 1: 성공 |
CumulativeGasUsed | 블록에서 사용된 gas의 누적량 | cumulativeGasUsed | |
Bloom | 로그 블룸필터 | logsBloom | |
Logs | 이벤트 로그 목록 | logs |
파생 필드: 노드 구현체에서 추가한 필드
필드명 | 설명 | json | 특이사항 |
---|---|---|---|
TxHash | 트랜잭션의 해시 | transactionHash | |
ContractAddress | 컨트랙트 생성 트랜잭션의 경우 생성된 컨트랙트의 주소 | contractAddress | |
GasUsed | 트랜잭션 실행 시 사용된 gas의 양 | gasUsed | |
EffectiveGasPrice | 트랜잭션의 effective gas price | effectiveGasPrice | |
BlobGasUsed | 트랜잭션 실행 시 사용된 blob gas의 양 | blobGasUsed | |
BlobGasPrice | 트랜잭션의 blob gas price | blobGasPrice | |
BlockHash | 트랜잭션이 포함된 블록의 해시 | blockHash | |
BlockNumber | 트랜잭션이 포함된 블록의 번호 | blockNumber | |
TransactionIndex | 트랜잭션의 인덱스 | transactionIndex |
gen_receipt_json.go
- 영수증의 JSON 인코딩/디코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run github.com/fjl/gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go
state_account.go
- 이더리움 계정을 정의한 파일입니다.
- 계정에는 slim 형식과 full-consensus 형식이 있습니다.
// StateAccount는 이더리움 컨센서스 계정입니다.
// 이 객체들은 메인 계정 트라이에 저장됩니다.
type StateAccount struct {
Nonce uint64 // 계정의 nonce
Balance *big.Int // 계정의 잔액
Root common.Hash // 스토리지 트라이의 머클루트
CodeHash []byte // EVM 코드 해시 (Externally Owned Account는 nil)
}
gen_account_rlp.go
- 계정의 RLP 인코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run ../../rlp/rlpgen -type StateAccount -out gen_account_rlp.go
transaction.go
- 트랜잭션을 정의한 파일입니다.
트랜잭션 타입
- 트랜잭션 타입은 4가지가 있습니다. (Legacy, AccessList, DynamicFee, Blob)
const (
LegacyTxType = 0x00 // Legacy
AccessListTxType = 0x01 // EIP-2930
DynamicFeeTxType = 0x02 // EIP-1559
BlobTxType = 0x03 // EIP-4844
)
TxData 인터페이스
// TxData는 트랜잭션의 기본 데이터를 나타내기 위한 인터페이스입니다.
//
// 이 인터페이스는 DynamicFeeTx, LegacyTx, AccessListTx에 의해 구현됩니다.
type TxData interface {
txType() byte // 트랜잭션 타입 ID를 반환합니다.
copy() TxData // 깊은 복사본을 만들어 새로운 TxData를 반환합니다.
chainID() *big.Int
accessList() AccessList
data() []byte
gas() uint64
gasPrice() *big.Int
gasTipCap() *big.Int
gasFeeCap() *big.Int
value() *big.Int
nonce() uint64
to() *common.Address
rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int)
// effectiveGasPrice는 트랜잭션이 지불하는 가스 가격을 계산합니다. 트랜잭션이 포함된 블록의 baseFee가 주어집니다.
//
// 다른 TxData 메서드와 달리, 반환된 *big.Int는 계산된 값의 독립적인 복사본이어야 합니다.
// 즉, 호출자는 결과를 변경할 수 있습니다. 메서드 구현은 'dst'를 사용하여 결과를 저장할 수도 있습니다.
effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int
encode(*bytes.Buffer) error
decode([]byte) error
}
transaction_marshaling.go
- 트랜잭션의 JSON 인코딩/디코딩을 위한 코드가 정의되어 있습니다.
tx_legacy.go
- Legacy 트랜잭션을 정의한 파일입니다.
TxData
인터페이스를 구현합니다.
// LegacyTx는 근본 이더리움 트랜잭션 데이터입니다.
type LegacyTx struct {
Nonce uint64 // 발신자 계정의 nonce
GasPrice *big.Int // 가스당 wei
Gas uint64 // 가스 한도
To *common.Address `rlp:"nil"` // 수신자의 주소. nil이면 컨트랙트 생성 트랜잭션
Value *big.Int // wei 단위의 이더량
Data []byte // 컨트랙트 생성 트랜잭션의 경우 생성자의 바이트코드. 그 외의 경우 호출 데이터
V, R, S *big.Int // 서명 값
}
tx_access_list.go
- EIP-2930에서 사용되는 접근 목록과 접근 목록 트랜잭션을 정의한 파일입니다.
// AccessList은 EIP-2930에 정의된 접근 목록입니다.
type AccessList []AccessTuple
// AccessTuple은 접근 목록의 요소입니다.
type AccessTuple struct {
Address common.Address `json:"address" gencodec:"required"`
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"`
}
// AccessListTx는 EIP-2930 접근 목록 트랜잭션의 데이터입니다.
type AccessListTx struct {
ChainID *big.Int // 대상 체인 ID
Nonce uint64 // 발신자 계정의 nonce
GasPrice *big.Int // 가스당 wei
Gas uint64 // 가스 한도
To *common.Address `rlp:"nil"` // 수신자의 주소. nil이면 컨트랙트 생성 트랜잭션
Value *big.Int // wei 단위의 이더량
Data []byte // 컨트랙트 생성 트랜잭션의 경우 생성자의 바이트코드. 그 외의 경우 호출 데이터
AccessList AccessList // 접근 목록
V, R, S *big.Int // 서명 값
}
gen_access_tuple.go
- 접근 목록의 요소를 JSON 인코딩/디코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run github.com/fjl/gencodec -type AccessTuple -out gen_access_tuple.go
tx_dynamic_fee.go
- EIP-1559에서 사용되는 gas fee cap과 gas tip cap을 필드로 추가된 트랜잭션을 정의한 파일입니다.
// DynamicFeeTx는 EIP-1559 트랜잭션을 나타냅니다.
type DynamicFeeTx struct {
ChainID *big.Int
Nonce uint64
GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas
GasFeeCap *big.Int // a.k.a. maxFeePerGas
Gas uint64
To *common.Address `rlp:"nil"` // nil이면 컨트랙트 생성 트랜잭션
Value *big.Int
Data []byte
AccessList AccessList
// 서명 값
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
}
tx_blob.go
- EIP-4844에서 사용되는 blob gas와 sidecar가 필드로 추가된 트랜잭션을 정의한 파일입니다.
// BlobTx는 EIP-4844 트랜잭션을 나타냅니다.
type BlobTx struct {
ChainID *uint256.Int
Nonce uint64
GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas
GasFeeCap *uint256.Int // a.k.a. maxFeePerGas
Gas uint64
To common.Address
Value *uint256.Int
Data []byte
AccessList AccessList
BlobFeeCap *uint256.Int // a.k.a. maxFeePerBlobGas
BlobHashes []common.Hash
// blob 트랜잭션은 선택적으로 blob을 포함할 수 있습니다. BlobTx가 서명을 위해 트랜잭션을 생성하는 데 사용될 때 이 필드를 설정해야만 합니다.
Sidecar *BlobTxSidecar `rlp:"-"`
// 서명 값
V *uint256.Int `json:"v" gencodec:"required"`
R *uint256.Int `json:"r" gencodec:"required"`
S *uint256.Int `json:"s" gencodec:"required"`
}
// BlobTxSidecar는 blob 트랜잭션의 blob을 포함합니다.
type BlobTxSidecar struct {
Blobs []kzg4844.Blob // blob 풀이 필요한 blob
Commitments []kzg4844.Commitment // blob 풀이 필요한 Commitments
Proofs []kzg4844.Proof // blob 풀이 필요한 Proofs
}
transaction_signing.go
- 트랜잭션에 서명하거나 서명자를 확인하는 코드가 정의되어 있습니다.
서명 함수
// SignTx는 주어진 서명자와 개인 키를 사용하여 트랜잭션에 서명합니다.
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := s.Hash(tx) // 서명 해시 생성 (Signer에 따라 다르게 생성됨)
sig, err := crypto.Sign(h[:], prv) // 개인 키로 서명 (직렬화된 서명 데이터 반환)
if err != nil {
return nil, err
}
return tx.WithSignature(s, sig) // 트랜잭션에 서명 데이터 추가 (V, R, S 값 설정 + 서명자의 체인 ID 설정)
}
Signer 인터페이스
// Signer는 트랜잭션 서명 처리 기능을 캡슐화합니다. 이 타입의 이름은 약간 오해의 소지가 있습니다.
// 왜냐하면 Signer는 실제로 서명하지 않고 서명을 검증하고 처리하기 위한 것이기 때문입니다.
//
// 참고로 이 인터페이스는 안정적인 API가 아니며 새로운 프로토콜 규칙을 수용하기 위해 언제든지 변경될 수 있습니다.
type Signer interface {
// Sender는 트랜잭션의 발신자 주소를 반환합니다.
Sender(tx *Transaction) (common.Address, error)
// SignatureValues는 주어진 서명에 해당하는 원시 R, S, V 값을 반환합니다.
SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error)
ChainID() *big.Int
// Hash는 '서명 해시'를 반환합니다. 즉, 개인 키를 사용하여 서명되기 전의 트랜잭션 해시입니다.
// 이 해시는 트랜잭션을 고유하게 식별하지는 않습니다.
Hash(tx *Transaction) common.Hash
// Equal은 주어진 서명자가 수신자와 동일한지 여부를 반환합니다.
Equal(Signer) bool
}
Signer 인터페이스를 구현하는 타입
- 각 구현체는 하위 버전의 구현체를 임베딩하여 상위 버전의 구현체를 만듭니다.
type cancunSigner struct{ londonSigner }
func NewCancunSigner(chainId *big.Int) Signer {
return cancunSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}}
}
type londonSigner struct{ eip2930Signer }
func NewLondonSigner(chainId *big.Int) Signer {
return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}
}
type eip2930Signer struct{ EIP155Signer }
func NewEIP2930Signer(chainId *big.Int) Signer {
return eip2930Signer{NewEIP155Signer(chainId)}
}
type EIP155Signer struct {
chainId, chainIdMul *big.Int
}
func NewEIP155Signer(chainId *big.Int) EIP155Signer {
if chainId == nil {
chainId = new(big.Int)
}
return EIP155Signer{
chainId: chainId,
chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)),
}
}
- 단,
HomesteadSigner
와FrontierSigner
는 별도로 구현되어 있습니다.
type HomesteadSigner struct{ FrontierSigner }
type FrontierSigner struct{}
서명 해시 생성 방식의 차이
withdrawal.go
- EIP-4895에서 사용되는 출금 작업을 정의한 파일입니다.
// Withdrawal은 합의 레이어로부터 검증자의 출금 작업을 나타냅니다.
type Withdrawal struct {
Index uint64 `json:"index"` // 합의 레이어에 의해 발행된 단조 증가식 식별자
Validator uint64 `json:"validatorIndex"` // 출금과 관련된 검증자의 인덱스
Address common.Address `json:"address"` // 출금된 이더가 전송되는 주소
Amount uint64 `json:"amount"` // 출금액 (Gwei 단위)
}
gen_withdrawal_json.go
- 출금 작업의 JSON 인코딩/디코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run github.com/fjl/gencodec -type Withdrawal -field-override withdrawalMarshaling -out gen_withdrawal_json.go
gen_withdrawal_rlp.go
- 출금 작업의 RLP 인코딩을 위한 코드가 정의되어 있습니다.
//go:generate go run ../../rlp/rlpgen -type Withdrawal -out gen_withdrawal_rlp.go
타입들 간의 관계 정리
읽어보기
EIP-155: Simple replay attack protection
단순 재생 공격(Simple Replay Attack)을 방지하기 위해 트랜잭션에 체인 ID를 추가하는 EIP입니다.
Spurious Dragon
포크와 함께 적용되었습니다.
적용일시: Nov 22, 2016, 4:15:44 AM +UTC
등장 배경
DAO
하드 포크 이후, 이더리움 메인넷이 이더리움 클래식
과 이더리움
으로 분리되었습니다. 이더리움 클래식은 이더리움 메인넷의 하드 포크 이전 상태를 유지하고 있습니다. 이더리움 클래식의 블록체인을 이더리움 메인넷의 블록체인과 공유하는 것은 불가능합니다. 하지만 이더리움 클래식의 트랜잭션을 이더리움 메인넷에서 재생할 수 있습니다. 소프트웨어 사양이 크게 다르지 않고 포크가 진행될 때 기존의 이더리움 메인넷에서 사용한 계정과 이더 잔액을 그대로 이더리움 클래식에서 사용할 수 있기 때문입니다.
예를 들어, Alice가 이더리움 클래식에서 Bob에게 10 이더를 전송하는 트랜잭션을 생성합니다. Bob은 이 트랜잭션(서명된)을 이더리움 메인넷에서 재생하여 Alice의 비밀키를 모르고도 Alice 계정으로부터 10 이더를 탈취할 수 있습니다. 이를 단순 재생 공격(Simple Replay Attack)이라고 하며, 이 문제를 해결하기 위해 트랜잭션에 체인 ID를 추가하는 EIP-155이 제안되었습니다.
세부 사항
파라미터
FORK_BLOCK_NUMBER
: 하드 포크 블록 번호,2675000
CHAIN_ID
: 체인 ID,1
(메인넷)
설명
현재 block.number
가 FORK_BLOCK_NUMBER
보다 크거나 같으면, 트랜잭션에 CHAIN_ID
를 추가할 수 있습니다. 그리고 블록 해시를 계산할 때 기존에는 6개의 요소 (nonce, gasprice, startgas, to, value, data)
를 rlp 인코딩한 것과 달리, 9개의 요소 (nonce, gasprice, startgas, to, value, data, chainid, 0, 0)
를 rlp 인코딩하여 사용해야만 합니다. 이렇게 할 경우, 서명의 v
값은 반드시 {0,1} + CHAIN_ID * 2 + 35
로 계산되어야 합니다. 이 때 {0,1}
은 서명에 사용된 secp256k1 타원 곡선 위의 점의 x좌표 r
로부터 y좌표를 계산할 때 y 좌표가 짝수인지 홀수인지를 나타내는 패리티 갑입니다. 만약 기존처럼 6개의 요소를 rlp 인코딩하여 사용한다면, 서명의 v
값은 반드시 {0,1} + 27
로 계산되어야 합니다.
현재 block.number
가 FORK_BLOCK_NUMBER
보다 크거나 같고 v
가 CHAIN_ID * 2 + 35
또는 CHAIN_ID * 2 + 36
이면, 6개의 요소를 RLP 인코딩하는 대신 9개의 요소를 RLP 인코딩하여 사용해야 합니다. 현존하는 서명 스키마는 하위 호환성을 보장하기 위해 여전히 v
가 27
또는 28
인 경우에도 유효하다고 판단합니다. 그러나 한편으로 이는 여전히 재생 공격이 발생할 수 있다는 것을 의미합니다.
논리적 근거
ETC
(이더리움 클래식)나 테스트넷 등 서로 다른 체인 ID를 가지게 함으로써 이더리움 네트워크에서 발생하는 단순 재생 공격을 방지할 수 있습니다. 또한 향후 이더리움 메인넷에 기반한 다른 블록체인이 등장할 경우, 고유한 체인 ID를 부여하도록 합니다.
Chain ID | Network |
---|---|
1 | Mainnet |
5 | Görli |
11155111 | Sepolia |
1337 | Geth private chains |
참고 자료
EIP-2718: Typed Transaction Envelope
새로운 트랜잭션 유형을 추가할 때 복잡성을 낮추고 하위 호환성을 보장하기 위해 인벨로프(Envelope) 트랜잭션 유형을 도입하는 EIP입니다.
Berlin
포크와 함께 적용되었습니다.
적용일시: Apr 15, 2021, 10:07:03 AM +UTC
초록
TransactionType || TransactionPayload
는 유효한 트랜잭션이며 TransactionType || ReceiptPayload
는 유효한 영수증입니다.
TransactionType
은 트랜잭션의 형식을 식별하며, *Payload
는 향후 EIP에 의해 정의될 수 있는 트랜잭션/영수증의 콘텐츠입니다.
등장 배경
과거의 문제
이더리움에서 새로운 트랜잭션 유형을 추가하려고 할 때, 기존의 모든 트랜잭션과의 하위 호환성을 보장해야 했습니다. 즉, 새로운 트랜잭션을 다른 트랜잭션과 구분할 수 있어야 했는데, 이는 인코딩된 페이로드(payload)만을 기준으로 하는 것이었습니다. EIP-155에서 이와 관련된 문제가 발생했는데, 새로운 값을 인코딩된 필드 중 하나에 비트 패킹하여 해결하려고 했습니다.
다양한 제안
현재 여러 EIP에서는 새로운 트랜잭션 유형을 정의하려는 논의가 진행 중입니다. 예를 들면, EOA(Externally Owned Account) 계정이 자신의 컨텍스트에서 직접 코드를 실행할 수 있는 트랜잭션, 메시지 전송자(msg.sender) 이외의 누군가가 가스를 지불할 수 있는 트랜잭션, Layer 1에서의 다중 서명(multi-sig) 트랜잭션과 관련된 제안 등이 있습니다.
문제 해결 방안
EIP-2718은 이러한 다양한 트랜잭션 유형을 상호 호환성을 유지하면서 정의해야 한다는 복잡한 문제를 해결하기 위해 제안되었습니다. 인벨로프 트랜잭션 유형을 도입함으로써, 새로운 트랜잭션은 기존 트랜잭션과의 하위 호환성만을 고려하면 됩니다. 그 후로는 단순히 TransactionType 간의 번호 충돌이 없도록 하는 간단한 문제만 해결하면 됩니다.
세부 사항
정의
||
은 바이트/바이트열을 연결하는 연산자입니다.
파라미터
FORK_BLOCK_NUMBER
: 하드 포크 블록 번호,12244000
트랜잭션
FORK_BLOCK_NUMBER
를 기준으로, 블록 헤더의 트랜잭션 루트는 MUST patriciaTrie(rlp(Index) => Transaction)
의 루트 해시여야 합니다.
Index
는 블록에 포함된 트랜잭션의 인덱스입니다.Transaction
은TransactionType || TransactionPayload
또는LegacyTransaction
입니다.TransactionType
은0
과0x7f
사이의 부호가 없는 8비트 정수입니다.TransactionPayload
는TransactionType
에 따라 다른 구조를 가지는 바이트열입니다. 이는 향후 EIP에 의해 정의될 수 있습니다.LegacyTransaction
은rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])
= rlp 인코딩된 레거시 트랜잭션입니다.
향후 트랜잭션 유형에 대한 모든 서명은 TransactionType
을 서명된 데이터의 첫 바이트로 SHOULD 사용해야 합니다. 그럼으로써 하나의 트랜잭션 유형에 대한 서명을 다른 트랜잭션 유형에 재사용되는 것을 방지할 수 있습니다.
영수증
FORK_BLOCK_NUMBER
를 기준으로, 블록 헤더의 영수증 루트는 MUST patriciaTrie(rlp(Index) => Receipt)
의 루트 해시여야 합니다.
Index
는 영수증이 참조하는 블록에 포함된 트랜잭션의 인덱스입니다.Receipt
는TransactionType || ReceiptPayload
또는LegacyReceipt
입니다.TransactionType
은0
과0x7f
사이의 부호가 없는 8비트 정수입니다.ReceiptPayload
는TransactionType
에 따라 다른 구조를 가지는 바이트열입니다. 이는 향후 EIP에 의해 정의될 수 있습니다.LegacyReceipt
는rlp([status, cumulativeGasUsed, logsBloom, logs])
= rlp 인코딩된 레거시 영수증입니다.
영수증의 TransactionType
은 MUST 해당 트랜잭션의 TransactionType
과 일치해야 합니다.
논리적 근거
TransactionType은 최대 0x7f까지만 사용합니다.
미래의 안정성을 위해, TransactionType
은 최대 0x7f까지만 사용합니다. 이는 충분히 많은 트랜잭션 유형을 지원할 수 있습니다. 최상위 비트가 남아있기 때문에 향후 확장이 가능합니다. 또한 항상 첫 바이트가 0xc0
이상인 레거시 트랜잭션과 충돌하지 않습니다.
TransactionType을 서명된 데이터의 첫 바이트로 사용하는데 있어 MUST가 아닌 SHOULD인 이유
권장되는 사항이기는 하지만, 모든 경우에 대해 이 방법을 적용하기 어려운 경우가 있습니다. 예를 들어, 레거시 서명 스키마가 적용된 레거시 트랜잭션의 래핑 트랜잭션인 경우 TransactionType을 서명된 데이터의 첫 바이트로 사용할 수 없습니다. 또 다른 예로, 트랜잭션이 서명을 가지고 있지 않고 다른 메커니즘으로 유효성을 검증하는 경우도 있습니다.
TransactionType 선택 알고리즘
표준화된 알고리즘은 없습니다. 이와 관련해서는 향후 EIP에서 도입될 수 있습니다.
RLP 배열이 아닌 불투명 바이트열(Opaque Byte Array)을 사용하는 이유
향후 트랜잭션 페이로드에 대해 SSZ, LEB128 또는 고정 너비 형식과 같은 다양한 인코딩을 지원하기 위해 RLP 배열 대신 불투명 바이트열을 사용합니다.
ORIGIN과 CALLER
ORIGIN
과 CALLER
opcode가 트랜잭션 유형에 의존하도록 하여, 각 트랜잭션 유형이 이 opcode들이 반환하는 값을 정의하도록 하자는 논의가 있었습니다. 그러나 컨트랙트가 트랜잭션 유형을 식별하지 못하게 하여 컨트랙트가 트랜잭션 유형에 따라 다른 로직을 실행는 것이 바람직하지 않다는 의견이 지배적이었습니다. 또한, 이미 존재하는 컨트랙트에 대한 하위 호한성 문제도 제기되었습니다.
따라서 ORIGIN
과 CALLER
opcode는 트랜잭션 유형에 의존하지 않도록 하였습니다. 만약 트랜잭션 유형이 추가적인 정보를 컨트랙트에 전달핟록 하려면, 새로운 opcode를 도입하는 것이 더 바람직합니다.
하위 호환성(Backwards Compatibility)
첫 번째 바이트를 살펴봄으로써, 레거시 트랜잭션과 인벨로프 트랜잭션을 구분할 수 있습니다. 인벨로프 트랜잭션은 [0, 0x7f]
사이의 TransactionType
을 사용하고, 레거시 트랜잭션은 [0xc0, 0xfe]
사이의 값을 가집니다. 0xff
는 현 시점에서 예약되어 있지 않습니다. 향후 확장을 위해 남겨두었습니다.
보안 고려사항(Security Considerations)
새로운 트랜잭션 유형을 추가할 때, 서명된 페이로드의 첫 바이트를 TransactionType
으로 사용할 것을 STRONGLY 권장합니다. 그렇게 하지 않으면 다른 트랜잭션과 상호호환되는 서명을 사용한 트랜잭션을 사용해야 할텐데, 이는 사용자의 보안적 취약점을 노출시킬 수 있습니다.
참고 자료
EIP-2930: Optional access lists
트랜잭션이 접근할 수 있는 계정 주소와 스토리지 슬롯을 명시적으로 지정할 수 있도록 하는 접근 목록(access list)을 도입하는 EIP입니다.
EIP-2718과 함께 적용되어 새로운 트랜잭션 유형인 0x01
을 사용합니다.
Berlin
포크와 함께 적용되었습니다.
적용일시: Apr 15, 2021, 10:07:03 AM +UTC
초록
0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, signatureYParity, signatureR, signatureS])
형식의 새로운 EIP-2718 트랜잭션 유형을 도입합니다.
accessList
는 계정 주소와 스토리지 키의 목록을 지정합니다. 이 주소와 스토리지 키는 EIP-2929에서 도입된 accessed_addresses
와 accessed_storage_keys
글로벌 집합(global set)에 추가됩니다. 접근 목록 밖의 항목에 접근하는 것과 비교하여 상대적으로 할인된 가스 비용으로 접근 목록 안의 항목에 접근할 수 있습니다.
등장 배경
이 EIP는 두 가지 기능을 제공합니다.
-
EIP-2929에서 소개된 컨트랙트 손상 위험을 완화합니다. 접근 목록 트랜잭션은 사용하고자 하는 계정 주소와 스토리지 슬롯을 사전에 지정하고 비용을 지불할 수 있습니다. 그 결과, 실제 트랜잭션 실행에서는
SLOAD
와EXT*
opcode들의 비용이 100 gas 밖에 들지 않습니다. 이는 컨트랙트 손상 위험을 낮출 뿐만 아니라, EIP-1884로 인해 멈춘 컨트랙트를 다시 동작하게 만들 수 있을 만큼 낮은 비용입니다. -
접근 목록 형식 및 해당 형식을 처리하는 로직을 소개합니다. 이는 향후에 다양한 용도로 용도 변경이 될 수 있습니다. (including block-wide witnesses, use in ReGenesis, moving toward static state access over time)
세부 사항
정의
TransactionType
:0x01
(EIP-2718)ChainId
: 트랜잭션은 해당 체인의 ID와 일치하는 경우에만 유효합니다.YParity
: 서명 데이터의Y
좌표의 패리티입니다.0x00
또는0x01
입니다.
파라미터
Constant | Value |
---|---|
FORK_BLOCK | 12244000 |
ACCESS_LIST_STORAGE_KEY_COST | 1900 |
ACCESS_LIST_ADDRESS_COST | 2400 |
설명
FORK_BLOCK
을 기준으로, 새로운 EIP-2718 트랜잭션 유형이 적용됩니다. 이 트랜잭션의TransactionType
은0x01
입니다.- 이 유형의 EIP-2718
TransactionPayload
는rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, signatureYParity, signatureR, signatureS])
형식입니다. signatureYParity, signatureR, signatureS
는keccak256(0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList]))
에 secp256k1 서명을 적용한 결과를 나타냅니다.- 이 유형의 EIP-2718
ReceiptPayload
는rlp([status, cumulativeGasUsed, logsBloom, logs])
형식입니다.
이 유형의 트랜잭션이 유효하려면 accessList
의 타입은 [[{20 bytes}, [{32 bytes}...]]...]
이어야 합니다. 이 때 ...
은 0개 이상의 요소를 의미합니다. 예를 들어, 다음과 같은 형식이 유효합니다.
[
[
"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", # address
[
"0x0000000000000000000000000000000000000000000000000000000000000003", # storage key
"0x0000000000000000000000000000000000000000000000000000000000000007"
]
],
[
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
[]
]
]
트랜잭션이 실행되는 시점(EIP-2028에 따라 21000 + 4 * zeroes + 16 * nonzeroes
시작 가스가 청구되는 시점과 동일)에, 접근 목록에 대한 추가 비용이 청구됩니다. 이 비용은 주소당 ACCESS_LIST_ADDRESS_COST
와 스토리지 키당 ACCESS_LIST_STORAGE_KEY_COST
입니다. 이 비용은 ACCESS_LIST
로 지정된 주소와 스토리지 키의 수에 따라 달라집니다. 예를 들어, 위의 예시에서는 ACCESS_LIST_ADDRESS_COST * 2 + ACCESS_LIST_STORAGE_KEY_COST * 2
가 청구됩니다.
중복된 계정 주소와 스토리지 키가 접근 목록에 포함될 수 있지만, 추가 비용이 발생합니다. 그러나 중복된 값이 적용되지 않은 경우와 비교해 트랜잭션 실행 결과에는 아무런 영향을 미치지 않습니다. (가스비만 더 나간다는 의미)
계정 주소와 슽리지 키는 즉시 accessed_addresses
그리고 accessed_storage_keys
글로벌 집합으로 로드됩니다. 이는 다음의 로직으로 구현됩니다.
def process_access_list(access_list) -> Tuple[List[Set[Address], Set[Pair[Address, Bytes32]]], int]:
accessed_addresses = set()
accessed_storage_keys = set()
gas_cost = 0
assert isinstance(access_list, list)
for item in access_list:
assert isinstance(item, list) and len(item) == 2
# Validate and add the address
address = item[0]
assert isinstance(address, bytes) and len(address) == 20
accessed_addresses.add(address)
gas_cost += ACCESS_LIST_ADDRESS_COST
# Validate and add the storage keys
assert isinstance(item[1], list)
for key in item[1]:
assert isinstance(key, bytes) and len(key) == 32
accessed_storage_keys.add((address, key))
gas_cost += ACCESS_LIST_STORAGE_KEY_COST
return (
accessed_addresses,
accessed_storage_keys,
gas_cost
)
접근 목록은 트랜잭션 데이터처럼 바이트당 요금으로 청구되지 않습니다. 대신, 항목당 비용으로 청구되는데, 이는 트랜잭션을 평가할 때 각 항목에 접근하는 비용에 더해 접근 목록 데이터의 대역폭 비용을 포함합니다.
논리적 근거
접근 목록 안의 항목에 접근하는 것은 더 저렴하다
이는 가능한 접근 목록을 사용하는 것을 권장하기 위함입니다. 왜냐하면 스토리지 읽기 작업이 예측이 가능해지면 트랜잭션을 처리하는 것이 더 쉬워지기 때문입니다. (클라이언트가 트랜잭션을 받으면 데이터베이스에서 미리 로드, 트랜잭션을 수신했을 때 증인을 요청, 또는 최소한 데이터를 병렬로 로드할 수 있습니다.)
복사를 허용한다
최대한 간단하게 구현하기 위해 중복 항목을 허용합니다 (그렇지 않으면 왜 중복을 허용하지 않는지에 대해 구구절절 설명해야 합니다.) 항목이 중복되더라도 집합에 추가하면 중복된 값은 적용되지 않기 때문에 실제로는 체인에 저장되는 데이터는 부풀려지지 않습니다. (가스비만 더 나간다는 의미)
서명은 트랜잭션 유형과 데이터에 적용된다
트랜잭션이 다른 유형으로 재해석되는 것을 방지하기 위해 트랜잭션 유형을 서명에 포함시킵니다.
하위 호환성(Backwards Compatibility)
이 EIP은 "예상치 못한" SLOAD 및 계정 액세스를 수행하는 데 더 많은 가스를 필요로 합니다. 가스는 선결제되어 있으므로 고정 가스 로컬 호출(fixed-gas local calls)에는 영향을 미치지 않습니다. 따라서 이전에 가스 비용 증가가 컨트랙트를 멈추게 만들었던 경우만큼 심각한 영향을 미치지는 않습니다. 그러나 이는 스토리지 접근에 크게 의존하는 어플리케이션들의 경제적인 측면에 악영향을 미칠 수 있습니다.
보안 고려사항(Security Considerations)
접근 목록 생성
액세스 목록은 많은 상황에서 실시간으로 구성하기 어려우며, 이는 트랜잭션 생성과 서명 사이에 높은 시간 지연이 있거나 트랜잭션 생성기의 단순성이 높게 평가되는 환경에서 더욱 악화됩니다(예: 하드웨어 지갑에서 둘 중 하나 또는 둘 다 적용될 수 있음).
- 그러나 이 EIP는 접근 목록에 대해 오직 10%의 초기 할인만을 제공하므로, 접근 목록을 구태여 생성할 필요는 없습니다. 참고로 앞으로 도구들이 개발되고 액세스 목록 생성이 더 성숙해지면 액세스 목록 외부의 상태에 액세스하는 비용이 시간이 지남에 따라 증가할 것으로 예상됩니다.
트랜잭션 크기 팽창
액세스 목록 사용으로 인해 평균 블록 크기가 증가할 것으로 예상됩니다. 접근 목록의 바이트당 비용은 스토리지 키가 1900 / 32 = 59.375, 주소가 2400 / 20 = 120으로 calldata보다 훨씬 비싸기 때문에 최악의 경우(?) 블록 크기는 증가하지 않을 것입니다. 또한 평균 블록 크기의 증가로 인한 비용은 접근 목록을 활용하여 스토리지를 읽는 것이 더 쉬워지고, 따라서 블록을 처리하는 데 더 적은 리소스가 필요하게 됨으로써 상쇄될 것입니다.
참고 자료
EIP-1559: Fee market change for ETH 1.0 chain
이더리움 가스 수수료 모델을 개선하는 EIP입니다.
EIP-2718 트랜잭션 유형인 0x02
을 추가합니다.
블록당 기본 수수료(base fee)를 도입합니다.
London
포크와 함께 적용되었습니다.
적용일시: Aug 5, 2021, 12:33:42 PM +UTC
초록
0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])
형식의 새로운 EIP-2718 트랜잭션 유형을 도입합니다.
프로토콜에는 가스당 기본 수수료(base fee)가 포함되며 이는 블록마다 공식에 의해 오르거나 내릴 수 있습니다. 이때, 공식은 부모 블록에서 사용된 가스와 부모 블록의 가스 타겟(블록 가스 리미트를 탄성 계수로 나눈 값)에 의해 결정됩니다. 이 알고리즘은 부모 가스 타겟보다 높을 때 기본 수수료가 증가하고, 낮을 때 기본 수수료가 감소하도록 합니다. 기본 수수료는 소각됩니다. 트랜잭션은 채굴자가 트랜잭션을 포함시키도록 채굴자에게 제공하는 가스당 최대 수수료(우선 순위 수수료)를 지정합니다. 또한 총 수수료(최대 수수료)를 지정합니다. 이는 우선 순위 수수료와 블록의 네트워크 수수료(기본 수수료)를 모두 포함합니다. 발신자는 트랜잭션이 포함된 블록의 가스당 기본 수수료를 항상 지불하며, 트랜잭션에 설정된 우선 순위 수수료를 지불합니다. 이 때, 두 수수료의 합은 트랜잭션의 최대 수수료를 초과하지 않아야 합니다.
등장 배경
이더리움은 과거에 간단한 경매 메커니즘을 사용하여 트랜잭션 수수료를 책정했습니다. 사용자는 입찰(가스 가격)을 지정하여 트랜잭션을 전송하고, 채굴자는 가장 높은 입찰가를 포함한 트랜잭션을 선택하며, 블록에 포함된 트랜잭션은 지정한 입찰금을 지불합니다. 이는 여러 가지 큰 비효율성을 야기합니다.
트랜잭션 수수료 수준의 변동성과 트랜잭션의 사회적 비용 사이의 불일치
성숙한 공개형 블록체인(많은 사용량으로 인해 블록이 가득 차는)에서 트랜잭션을 포함하기 위한 입찰은 극단적으로 변동성이 높습니다. 네트워크에서 제시하는 가스당 비용이 1 nanoeth일 때보다 10 nanoeth일 때 실제로 10배 더 많은 비용을 지불해야 한다는 것은 터무니 없는 주장입니다. 8백만 가스와 8백만 2천 가스 정도의 차이일 뿐입니다.
사용자에게 불필요한 지연 시간
고정된 블록당 가스 제한과 트랜잭션량의 자연스러운 변동성으로 인해 트랜잭션은 종종 몇 블록 동안 포함되기를 기다려야 하지만 이는 사회적으로 비생산적입니다. 블록별 수요의 차이를 충족하기 위해 한 블록이 더 커지고 다음 블록이 더 작아질 수 있는 "slack" 메커니즘이 없기 때문에 누구에게도 이득이 없습니다.
최초 가격 경매의 비효율성
현재 접근 방식은 거래 송신자가 입찰 및 최대 수수료를 포함하는 거래를 게시하고, 채굴자가 가장 높은 가격의 거래를 선택하면 송신자가 입찰한 가격을 지불하는 것입니다. 이는 메커니즘 디자인 문헌에서 매우 비효율적으로 알려져 있으며 따라서 복잡한 수수료 추정 알고리즘이 필요합니다. 그러나 이러한 알고리즘도 종종 잘 작동하지 않아 자주 수수료 과다 지불을 야기합니다.
블록 채굴 보상이 존재하지 않는 블록체인의 불안정성
장기적으로, 채굴 보상의 발행이 없는 블록체인(비트코인 및 Zcash 포함)은 현재로써는 채굴자에게 전적으로 거래 수수료로 보상을 지급할 계획이지만, 이는 많은 불안정성을 유발할 가능성이 있습니다. 거래 수수료를 훔치는 "자매 블록" 채굴을 장려하거나 더 강력한 이기적 채굴 공격 벡터(selfish mining attack vectors)를 여는 등의 문제가 발생할 수 있지만, 현재로써는 이를 완화할 수 있는 좋은 방법이 없습니다.
이 EIP의 제안은 네트워크가 혼잡함에 따라 프로토콜에 의해 조정되는 기본 수수료를 시작점으로 삼는 것입니다. 네트워크가 블록당 가스 사용량 목표를 초과하면 기본 수수료가 약간 증가하고, 용량이 목표보다 낮으면 약간 감소합니다. 이러한 기본 수수료 변경은 제한되어 있으므로 블록마다 기본 수수료의 최대 차이가 예측 가능합니다. 이를 통해 지갑은 사용자의 가스 수수료를 매우 신뢰할 수 있는 방식으로 자동으로 설정할 수 있습니다. 대부분의 사용자는 네트워크 활동이 많은 기간에도 가스 수수료를 수동으로 조정할 필요가 없을 것으로 예상됩니다. 대부분의 사용자는 지갑에 의해 기본 수수료가 추정되고, 고아 위험에 처한 채굴자에 대한 보상을 지불하는 작은 우선 순위 수수료가 자동으로 설정됩니다. 사용자는 또한 총 비용을 제한하기 위해 트랜잭션 최대 수수료를 수동으로 설정할 수 있습니다. 이 요금 시스템의 중요한 측면은 채굴자가 우선 순위 수수료만 받을 수 있다는 것입니다. 기본 수수료는 항상 소각됩니다(즉, 프로토콜에 의해 제거됩니다). 이는 이더리움에서 트랜잭션을 지불하는 데 ETH만 사용할 수 있으며, 이더리움 플랫폼 내에서 ETH의 경제적 가치를 확립하고 채굴자가 추출할 수 있는 가치(MEV)와 관련된 위험을 줄입니다. 또한 기본 수수료의 소각은 이더리움 인플레이션을 상쇄시키면서도 채굴자에게 블록 보상과 우선 순위 수수료를 제공합니다. 마지막으로, 블록의 채굴자가 기본 수수료를 받지 못하도록 하는 것은 채굴자가 수수료를 조작하여 사용자로부터 더 많은 수수료를 추출하는 동기를 제거하는 데 중요합니다.
세부 사항
정의
TransactionType
:0x02
(EIP-2718)
파라미터
Constant | Value |
---|---|
FORK_BLOCK | 12965000 |
설명
GASPRICE
opcode는 MUSTeffective_gas_price
를 반환합니다.FORK_BLOCK
을 기준으로, 새로운 EIP-2718 트랜잭션 유형이 적용됩니다. 이 트랜잭션의TransactionType
은0x02
입니다.- The intrinsic cost of the new transaction is inherited from EIP-2930, specifically 21000 + 16 * non-zero calldata bytes + 4 * zero calldata bytes + 1900 * access list storage key count + 2400 * access list address count.
- 새로운 트랜잭션의 고정 비용은 EIP-2930에서 상속됩니다. 구체적으로
21000 + 16 * 0이 아닌 calldata 바이트 + 4 * 0 calldata 바이트 + 1900 * access list storage key 개수 + 2400 * access list address
입니다. - 이 유형의 EIP-2718
TransactionPayload
는rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])
입니다. signature_y_parity, signature_r, signature_s
는keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list]))
에 secp256k1 서명을 적용한 결과를 나타냅니다.- 이 유형의 EIP-2718
ReceiptPayload
는rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])
형식입니다.
코드는 생략
하위 호환성
기존 이더리움 트랜잭션은 여전히 작동하며 블록에 포함될 수 있지만 새로운 요금 체계에서 직접적인 혜택을 받지는 못할 것입니다. 이는 기존 트랜잭션에서 새 트랜잭션으로 업그레이드하면 기존 트랜잭션의 gas_price
가 base_fee_per_gas
또는 priority_fee_per_gas
에 의해 완전히 소모되기 때문입니다.
블록 해시 변경
블록 해시를 계산하기 위해 keccak256에 전달되는 데이터 구조가 변경되고, 블록을 유효성 검사하거나 블록 내용을 확인하기 위해 블록 해시를 사용하는 모든 애플리케이션은 새로운 데이터 구조를 지원하도록 변경사항을 적용해야 합니다. 블록 헤더 바이트만 가져와 해시하는 경우 여전히 올바른 해시를 얻을 것입니다. 그러나 블록 헤더를 구성 요소로부터 직접 구성하는 경우 새로운 구성 요소를 결국에는 추가해야 합니다.
GASPRICE
이 변경 이전에 GASPRICE
는 트랜잭션에서 가스당 서명자가 지불하는 ETH와 채굴자가 가스당 받는 ETH를 모두 나타냈습니다. 이 변경 이후로 GASPRICE
는 이제 서명자가 가스당 지불하는 ETH만 나타내며, 채굴자가 트랜잭션으로 받는 금액은 더 이상 EVM에서 직접 접근할 수 없습니다.
보안 고려사항
최대 블록 크기 및 복잡성 증가
이 EIP는 최대 블록 크기를 증가시킬 것이며, 이로 인해 채굴자가 블록을 충분히 빠르게 처리할 수 없는 경우 빈 블록을 채굴하도록 강제될 수 있습니다. 시간이 지남에 따라 평균 블록 크기는 이 EIP 없을 때와 거의 동일하게 유지해야 하므로 이는 단기간의 크기 폭증에 대한 문제일 뿐입니다. 일부 클라이언트가 단기간의 크기 폭증을 처리하지 못하고 오류(메모리 부족 등)를 발생시킬 수 있으므로 클라이언트 구현은 개별 블록이 최대 크기까지 적절히 처리할 수 있도록 보장해야 합니다.
트랜잭션 정렬
대부분의 사람들이 우선 순위 수수료 경쟁없이 기본 수수료를 사용할 것이므로, 트랜잭션의 정렬은 이제 클라이언트 내부 구현 세부 사항에 따라 달라집니다. 동일한 우선 순위 수수료를 가진 트랜잭션은 트랜잭션이 수신된 시간에 따라 정렬되는 것이 좋으며, 이는 공격자가 여러 트랜잭션을 대기 중인 풀에 던져서 적어도 하나가 유리한 위치에 도착하도록 하는 스팸 공격으로부터 네트워크를 보호하는 데 도움이 됩니다. 채굴자는 순수하게 이기적인 채굴 관점에서 가스 프리미엄이 더 높은 트랜잭션을 선호해야 합니다.
빈 블록 채굴
채굴자들이 기본 수수료가 매우 낮아질 때까지 빈 블록을 채굴하고 그 후 반만 찬 블록을 채굴한 다음 트랜잭션을 우선 순위 수수료로 정렬하는 방식으로 되돌아갈 수 있습니다. 이러한 공격은 가능하지만 채굴이 탈중앙화된어있는 한 안정적인 방식은 아닙니다. 이 공격이 계속되는 한 공격을 포기하고 (기본 수수료 방식으로) 귀순한 채굴자들은 공격을 계속하는 채굴자보다 더 많은 이익을 얻을 것입니다. (기본 수수료가 0이 되었을 때도) 카르텔에서 누구든 익명으로 귀순할 수 있고, 특정 채굴자가 귀순했다는 것을 증명할 방법이 없기 때문에, 이 공격을 실행할 수 있는 유일한 방법은 50% 이상의 해시파워를 통제하는 것입니다. 만약 공격자가 정확히 50%의 해시파워를 가지고 있다면, 공격자는 우선 순위 수수료로부터 이더를 얻지 못할 것이고, 귀순한 채굴자들은 우선 순위 수수료로부터 이더를 2배로 얻을 것입니다. 공격자가 이득을 보려면 50%를 초과하는 해시파워를 보유해야 하며, 이쯤 되면 공격자는 이중 지불 공격을 실행하거나 다른 채굴자들을 무시함으로써 훨씬 더 큰 이익을 얻을 수 있습니다. (이 방법으로 공격하는 것은 의미가 없다는 뜻)
채굴자가 기어코 공격을 해야만 한다면, 우리는 탄성 계수(현재 2배)를 증가시켜 공격자가 공격을 실행하기 전에 더 많은 해시파워를 사용하도록 할 수 있습니다.
ETH 소각으로 인한 고정 공급 방지
기본 수수료를 소각함으로써 이제 더 이상 고정된 이더 공급을 보장할 수 없게 되었습니다. 이는 장기적으로 ETH의 공급이 더 이상 시간이 지남에 따라 일정하지 않을 수 있음을 의미합니다. 이는 우려할만한 부분이지만, 이러한 영향이 얼마나 큰지 정량화하기가 어렵습니다. 만약 기본 수수료로 소각되는 양이 채굴 보상으로 생성되는 양보다 많다면 ETH는 화폐희소성(deflationary)을 가질 것이며, 반대로 채굴 보상으로 생성되는 양이 소각되는 양보다 많다면 ETH는 화폐팽창성(inflationary)을 가질 것입니다. 블록 공간에 대한 사용자 수요를 통제할 수 없기 때문에 현재로서는 ETH가 화폐팽창성이나 화폐희소성을 가져갈지 단언할 수 없습니다. 따라서 이러한 변경으로 인해 핵심 개발자들은 이더의 장기적인 수량에 대한 일부 통제력을 상실하게 됩니다.
참고 자료
Overview
소개
go-ethereum의 rlp 패키지는 Ethereum의 RLP 인코딩과 관련된 기능을 구현한 패키지입니다.
rlp 패키지
- RLP(Recursive Length Prefix)는 이더리움에서 사용하는 데이터 인코딩 방식입니다.
doc.go
- go doc 문서 생성을 위한 내용을 정리해놓은 파일입니다.
RLP 인코딩 규칙
- 리플렉션(reflection)을 사용하여 값이 가지는 Go 타입에 따라 인코딩 규칙을 적용합니다.
Encoder
인터페이스를 구현하는 타입은EncodeRLP
메서드 호출을 통해 인코딩 규칙을 적용합니다.
데이터 타입 | RLP 인코딩 규칙 |
---|---|
포인터 | 포인터가 가리키는 값에 대해 인코딩 규칙을 적용합니다. 구조체, 슬라이스, 배열의 nil 포인터는 항상 빈 RLP 리스트로 인코딩됩니다. (단, 슬라이스나 배열의 원소 타입이 byte인 경우는 제외) 다른 타입의 nil 포인터는 빈 RLP 문자열로 인코딩됩니다. |
구조체 | 구조체의 모든 공개된(public) 필드를 RLP 리스트로 인코딩합니다. 재귀적인 구조체도 지원합니다. |
슬라이스, 배열 | 슬라이스나 배열의 모든 원소를 RLP 리스트로 인코딩합니다. 단, 원소 타입이 uint8 또는 byte인 경우 RLP 문자열로 인코딩합니다. |
Go 문자열 | RLP 문자열로 인코딩합니다. |
부호가 없는 정수 | RLP 문자열로 인코딩합니다. 0은 빈 RLP 문자열로 인코딩합니다. |
부호가 있는 정수 | 지원하지 않으며 인코딩 시 에러가 발생합니다. |
big.Int | 정수와 동일하게 취급합니다. |
불리언 | 부호가 없는 정수 0 또는 1로 인코딩합니다. |
인터페이스 | 인터페이스가 가리키는 값에 대해 인코딩 규칙을 적용합니다. |
부동 소수점, 맵, 채널, 함수 | 지원하지 않습니다. |
RLP 디코딩 규칙
Decoder
인터페이스를 구현하는 타입은DecodeRLP
메서드 호출을 통해 디코딩 규칙을 적용합니다.
대상 타입 | 입력 값 | 디코딩 규칙 |
---|---|---|
포인터 | 포인터가 가리키는 타입에 해당하는 RLP 형식 | 포인터가 가리키는 타입에 따라 디코딩 규칙을 적용합니다. 포인터가 nil인 경우, 포인터가 가리키는 타입의 새로운 값을 생성하여 할당합니다. nil이 아닌 경우, 기존의 값을 재사용합니다. 포인터 타입의 구조체 필드는 nil을 가지지 않습니다. (단 nil 태그가 있는 경우는 예외) |
구조체 | RLP 리스트 | 디코딩된 RLP 리스트의 원소를 구조체 공개 필드에 할당합니다. 만약 입력 리스트의 원소 개수가 구조체 필드 개수보다 적거나 많은 경우, 에러가 발생합니다. |
슬라이스 | RLP 리스트 (원소 타입이 byte인 경우 RLP 문자열) | 디코딩된 RLP 리스트의 원소를 순서대로 슬라이스에 추가합니다. |
배열 | RLP 리스트 (원소 타입이 byte인 경우 RLP 문자열) | 디코딩된 RLP 리스트의 원소를 순서대로 배열에 추가합니다. 슬라이스와 달리 배열의 길이는 고정되어 있으므로, 입력 리스트의 원소 개수는 배열의 길이와 일치해야 합니다. |
Go 문자열 | RLP 문자열 | RLP 문자열을 Go 문자열로 디코딩합니다. |
부호가 없는 정수 | RLP 문자열 | 문자열의 바이트 표현은 빅 엔디언(big endian)으로 해석하며 만약 RLP 문자열이 타입이 가지는 비트 크기보다 큰 경우, 에러가 발생합니다. |
big.Int | RLP 문자열 | 부호가 없는 정수와 동일하게 취급합니다. 다만 길이의 제한이 없습니다. |
불리언 | 부호가 없는 정수 0 또는 1 | 정수 0은 false로, 정수 1은 true로 디코딩합니다. |
인터페이스 | 인터페이스가 가리키는 타입에 해당하는 RLP 형식 | []interface{} 인 경우는 RLP 리스트, []byte 인 경우는 RLP 문자열을 사용하여 디코딩합니다. 그 외의 경우는 지원하지 않습니다. |
구조체 필드 태그
태그 | 설명 |
---|---|
rlp:"-" | 해당 필드를 무시합니다. |
rlp:"tail" | 해당 필드가 마지막 공개 필드임을 나타냅니다. 리스트의 나머지 원소를 해당 필드에 슬라이스로 묶어서 할당합니다. |
rlp:"optional" | 해당 필드가 타입의 기본값을 가지는 경우, RLP 리스트에 추가하지 않습니다. optional 태그를 사용할 경우, 후행 필드에 대해서도 optional 태그를 사용해야 합니다. |
rlp:"nil" , rlp:"nilList" , rlp:"nilString" | 크기가 0인 RLP 문자열 또는 리스트를 nil로 디코딩합니다. 그렇지 않은 경우는 타입에 따라 디코딩 규칙을 적용합니다. |
raw.go
RawValue
타입은 RLP 인코딩된 값을 저장하는 타입입니다.- 크기를 계산하는
**Size
함수와 인코딩된 값을 분리하는**Split
함수를 제공합니다.
safe.go / unsafe.go
reflect.Value
타입의 값을 바이트 슬라이스로 안전하게/불안전하게 변환하는 함수를 제공합니다.
safe인 경우
//go:build nacl || js || !cgo
// +build nacl js !cgo
- cgo를 사용하지 않는 경우, 또는 nacl, js 환경인 경우 safe.go 파일이 컴파일됩니다.
unsafe인 경우
//go:build nacl || js || !cgo
// +build nacl js !cgo
- cgo를 사용하는 경우, unsafe.go 파일이 컴파일됩니다.
typecache.go
typeCache
타입 정보와 관련 디코더, 인코더를 캐싱하는 구조체입니다.- 동적으로 인코딩/디코딩을 수행하기 위해
reflect.Type
타입을 사용합니다. rlp/internal/rlpstruct
패키지를 사용하여 구조체 필드 정보를 캐싱합니다.
typeCache 타입
type typeCache struct {
cur atomic.Value
// 이 뮤텍스는 쓰기를 동기화합니다.
mu sync.Mutex
next map[typekey]*typeinfo
}
var theTC = newTypeCache() // 싱글톤 패턴으로 단일 인스턴스를 사용합니다.
typeinfo, typekey 타입
// typeinfo는 타입 캐시의 항목입니다.
type typeinfo struct {
decoder decoder
decoderErr error // makeDecoder의 오류
writer writer
writerErr error // makeWriter의 오류
}
// typekey는 typeCache의 타입 키입니다. 구조체 태그는 다른 디코더를 생성할 수 있기 때문에 포함됩니다.
type typekey struct {
reflect.Type
rlpstruct.Tags
}
type decoder func(*Stream, reflect.Value) error // decoder는 디코딩 규칙을 적용하는 함수입니다.
type writer func(reflect.Value, *encBuffer) error // writer는 인코딩 규칙을 적용하는 함수입니다.
iterator.go
deprecated
encbuffer.go
EncoderBuffer
와encBuffer
타입은 RLP 인코딩된 값을 저장하는 버퍼입니다.encBuffer
는sync.Pool
을 사용하여 재사용합니다.
EncoderBuffer 타입
// EncoderBuffer는 점진적 인코딩을 위한 버퍼입니다.
//
// 각 타입의 zero value는 사용할 수 없습니다.
// 사용 가능한 버퍼를 얻으려면 NewEncoderBuffer를 사용하거나 Reset을 호출하십시오.
type EncoderBuffer struct {
buf *encBuffer
dst io.Writer
ownBuffer bool
}
encBuffer 타입
type encBuffer struct {
str []byte // 문자열 데이터, 리스트 헤더를 제외한 모든 것을 포함
lheads []listhead // 모든 리스트 헤더
lhsize int // 모든 인코딩된 리스트 헤더의 크기의 합
sizebuf [9]byte // uint 인코딩을 위한 보조 버퍼
}
encReader 타입
// encReader는 EncodeToReader에 의해 반환된 io.Reader입니다.
// EOF에서 encbuf를 해제합니다.
type encReader struct {
buf *encBuffer // 읽기 작업을 수행하는 버퍼. EOF일 때 nil이 된다.
lhpos int // 읽고 있는 리스트 헤더의 인덱스
strpos int // 문자열 버퍼에서 현재 위치
piece []byte // 다음 읽을 조각
}
리스트 인코딩 순서
NewEncoderBuffer(dst)
또는Reset(dst)
을 호출하여EncoderBuffer
를 초기화합니다.NewEncoderBuffer
의 인수로 전달한io.Writer
가*encBuffer
를 가지고 있다면 이를 사용하고, 그렇지 않은 경우 버퍼 풀에서*encBuffer
를 가져옵니다.
w.List()
를 호출하여 RLP 리스트를 시작합니다. 이 때 리스트 헤더의 인덱스가 반환됩니다.w.Write***(...)
를 호출하여 리스트에 아이템을 추가합니다.w.ListEnd(idx)
를 호출하여 리스트를 종료합니다. 이 때 리스트 헤더의 인덱스를 전달합니다.w.Flush()
를 호출하여 버퍼에 쓴 데이터를dst
에 씁니다. (*encBuffer의 writeTo 메서드가 호출됩니다.)- 사용이 끝난
*encBuffer
를 버퍼 풀에 반환합니다.
encode.go
- 커스텀 인코딩을 규칙을 적용하기 위한
Encoder
인터페이스를 제공합니다. reflect.Type
에 따라 동적으로 인코딩 규칙을 적용하기 위해Encode
함수와write***
함수가 정의되어 있습니다.
Encoder 인터페이스
// Encoder는 사용자 정의 인코딩 규칙이 필요한 타입이나
// private 필드를 인코딩하고 싶은 타입에 의해 구현됩니다.
type Encoder interface {
// EncodeRLP는 리시버의 RLP 인코딩을 io.Writer에 씁니다.
// 포인터 메서드로 구현된 경우 nil 포인터에 대해서도 호출될 수 있습니다.
//
// 구현체는 유효한 RLP를 생성해야 합니다. io.Writer에 쓰인 데이터는
// 현시점에서 검증되지는 않지만, 향후 버전에서는 검증될 수 있습니다.
// 하나의 값만 쓰는 것을 권장하지만, 그렇지 않은 경우도 허용됩니다.
EncodeRLP(io.Writer) error
}
Encode 함수
val
에 대한 동적인 인코딩 규칙을 적용하여w
에 씁니다.
// Encode는 val의 RLP 인코딩을 w에 씁니다. Encode는 경우에 따라
// 많은 작은 쓰기 작업을 수행할 수 있습니다. w를 버퍼링하는 것을 고려하세요.
//
// 인코딩 규칙에 대한 패키지 수준의 문서를 참조하세요.
func Encode(w io.Writer, val interface{}) error {
// 최적화: EncodeRLP에 의해 호출될 때 *encBuffer를 재사용합니다.
if buf := encBufferFromWriter(w); buf != nil { // w가 *encBuffer를 구현하는 경우
return buf.encode(val)
}
buf := getEncBuffer() // pool에서 *encBuffer를 가져옵니다.
defer encBufferPool.Put(buf) // *encBuffer를 pool에 반환합니다.
if err := buf.encode(val); err != nil { // 인코딩을 수행합니다.
return err
}
return buf.writeTo(w) // 인코딩된 데이터를 w에 씁니다.
}
*encBuffer
의encode
메서드를 호출하여 인코딩을 수행합니다.
func (buf *encBuffer) encode(val interface{}) error {
rval := reflect.ValueOf(val)
writer, err := cachedWriter(rval.Type())
if err != nil {
return err
}
return writer(rval, buf)
}
cachedWriter
함수는reflect.Type
에 따라 동적으로 인코딩 규칙을 적용하는 함수를 반환합니다.
var theTC = newTypeCache()
func cachedWriter(typ reflect.Type) (writer, error) {
info := theTC.info(typ)
return info.writer, info.writerErr
}
- 캐시된
typeinfo
가 존재하지 않는 경우,encode.go
파일에 정의된makeWriter
함수와makeDecoder
함수를 호출하여 새로운typeinfo
를 생성합니다.
listHead 타입
type listhead struct {
offset int // 문자열 데이터에서 이 헤더의 오프셋
size int // 인코딩된 데이터의 총 크기(리스트 헤더 포함)
}
encBuffer
에 저장된 리스트 헤더의 정보를 저장하는 타입입니다.
decode.go
- 커스텀 디코딩을 규칙을 적용하기 위한
Decoder
인터페이스를 제공합니다. Stream
타입을 사용하여 RLP 인코딩된 데이터를 구문 분석합니다.reflect.Type
에 따라 동적으로 디코딩 규칙을 적용하기 위해Decode
함수와decode***
함수가 정의되어 있습니다.
Decoder 인터페이스
// Decoder는 사용자 정의 RLP 디코딩 규칙이 필요한 유형 또는 내부(private) 필드로 디코딩해야하는 유형에 의해 구현됩니다.
//
// DecodeRLP 메서드는 주어진 Stream에서 하나의 값을 읽어야합니다. 덜 읽거나 더 읽는 것은 금지되지 않았지만 혼란을 야기할 수 있습니다.
type Decoder interface {
DecodeRLP(*Stream) error
}
Decode 함수
r
에서 RLP로 인코딩된 데이터를 구문 분석하고val
이 가리키는 값에 결과를 저장합니다.
// Decode는 r에서 RLP로 인코딩된 데이터를 구문 분석하고 val이 가리키는 값에 결과를 저장합니다.
// 디코딩 규칙에 대한 것은 패키지 수준 문서를 참조하십시오. Val은 nil이 아닌 포인터여야합니다.
//
// r이 ByteReader를 구현하지 않으면 Decode는 자체 버퍼링을 수행합니다.
//
// Decode는 모든 리더에 대해 입력 제한을 설정하지 않으며, 따라서 거대한 값 크기로 인한 패닉에 취약할 수 있습니다.
// 입력 제한이 필요한 경우 다음을 사용하십시오.
//
// NewStream(r, limit).Decode(val)
func Decode(r io.Reader, val interface{}) error {
stream := streamPool.Get().(*Stream) // 스트림 풀에서 스트림 가져오기
defer streamPool.Put(stream) // 스트림 풀에 스트림 반환
stream.Reset(r, 0) // 스트림을 r로 초기화
return stream.Decode(val) // val에 스트림을 디코딩
}
Stream
입력 스트림을 초기화하고Decode
메서드를 호출하여 디코딩을 수행합니다.
Stream 타입
// Stream은 입력 스트림의 파편적 디코딩에 사용할 수 있습니다.
// 이는 입력이 매우 크거나 유형에 대한 디코딩 규칙이 입력 구조에 따라 다른 경우 유용합니다.
// Stream은 내부 버퍼를 유지하지 않습니다. 값을 디코딩 한 후 입력 리더는 다음 값에 대한 유형 정보 바로 앞에 위치합니다.
//
// 리스트를 디코딩하다가 입력 위치가 목록의 선언 된 길이에 도달하면 모든 작업은 EOL 오류를 반환합니다.
// 리스트의 마지막은 ListEnd를 사용하여 알려야합니다.
//
// Stream은 동시 접근에 대해 안전하지 않습니다.
type Stream struct {
r ByteReader
remaining uint64 // r에서 읽어야하는 남은 바이트 수
size uint64 // 캐시된 값의 크기
kinderr error // 지난 readKind에서 발생한 오류
stack []uint64 // 리스트 크기
uintbuf [32]byte // 정수 디코딩을 위한 보조 버퍼
kind Kind // 캐시된 값의 종류
byteval byte // 타입 태그의 단일 바이트 값
limited bool // 입력 제한이 적용되는 경우 true
}
// Decode는 값을 디코딩하고 그 결과를 val이 가리키는 값에 저장합니다.
// 디코딩 규칙에 대한 설명은 Decode 함수에 대한 문서를 참조하십시오.
func (s *Stream) Decode(val interface{}) error {
if val == nil { // val은 nil이 아닌 포인터 유형이어야합니다.
return errDecodeIntoNil
}
rval := reflect.ValueOf(val) // val의 값을 가져옵니다.
rtyp := rval.Type() // val의 유형을 가져옵니다.
if rtyp.Kind() != reflect.Ptr {
return errNoPointer
}
if rval.IsNil() {
return errDecodeIntoNil
}
decoder, err := cachedDecoder(rtyp.Elem()) // 유형에 대한 디코더를 가져옵니다.
if err != nil {
return err
}
err = decoder(s, rval.Elem()) // 값을 디코딩합니다.
if decErr, ok := err.(*decodeError); ok && len(decErr.ctx) > 0 {
// 디코딩 대상 유형을 오류에 추가하여 컨텍스트가 더 의미 있도록합니다.
decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")"))
}
return err
}
cachedDecoder
함수는reflect.Type
에 따라 동적으로 디코딩 규칙을 적용하는 함수를 반환합니다.- 캐시된
typeinfo
가 존재하지 않는 경우,decode.go
파일에 정의된makeDecoder
함수와makeWriter
함수를 호출하여 새로운typeinfo
를 생성합니다.
internal/rlpstruct 패키지
rlpstruct.go
- 구조체의 각 필드의 (타입, 태그)에 대한 규칙을 처리하고 필드를 필터링하는 데 사용되는 타입과 함수를 정의합니다.
타입별 인코딩/디코딩
Header
인코딩 (EncodeRLP)
> 리스트 시작
> 헤더 1바이트 (0x80 + 0x20) + 부모 해시 32바이트
> 헤더 1바이트 (0x80 + 0x20) + 엉클 해시 32바이트
> 헤더 1바이트 (0x80 + 0x14) + 코인베이스 20바이트
> 헤더 1바이트 (0x80 + 0x20) + 상태 루트 32바이트
> 헤더 1바이트 (0x80 + 0x20) + 트랜잭션 루트 32바이트
> 헤더 1바이트 (0x80 + 0x20) + 영수증 루트 32바이트
> 헤더 3바이트 (0xb8 + 0x02 /0x01/0x00) + 로그 블룸 256바이트
> 난이도 동적 처리 (big.Int)
> 블록 번호 동적 처리 (big.Int)
> 가스 한도 동적 처리 (uint64)
> 가스 사용량 동적 처리 (uint64)
> 타임스탬프 동적 처리 (uint64)
> 추가 데이터 동적 처리 ([]byte)
> 헤더 1바이트 (0x80 + 0x20) + 혼합 다이제스트 32바이트
> 헤더 1바이트 (0x80 + 0x08) + 논스 8바이트
> (이하 필드 중 하나라도 nil이 아니면) 기본 비용 동적 처리 (big.Int) || 0x80
> (이하 필드 중 하나라도 nil이 아니면) 헤더 1바이트 (0x80 + 0x20) + 출금 해시 32바이트 || 0x80
> (이하 필드 중 하나라도 nil이 아니면) 블롭 가스 사용량 동적 처리 (uint64) || 0x80
> (이하 필드 중 하나라도 nil이 아니면) 블롭 가스 초과량 동적 처리 (uint64) || 0x80
> (nil이 아니면) 헤더 1바이트 (0x80 + 0x20) + 부모 비콘 루트 32바이트 || 0x80
> 리스트 끝
Log
인코딩 (EncodeRLP)
> 리스트1 시작
> 헤더 1바이트 (0x80 + 0x20) + 주소 20바이트
> 리스트2 시작
> 헤더 1바이트 (0x80 + 0x20) + 토픽 0 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 토픽 n-1 32바이트
> 리스트2 끝
> 데이터 동적 처리 ([]byte)
> 리스트1 끝
StateAccount
인코딩 (EncodeRLP)
> 리스트 시작
> 논스 동적 처리 (uint64)
> 잔액 동적 처리 (big.Int)
> 헤더 1바이트 (0x80 + 0x20) + 상태 루트 32바이트
> 헤더 1바이트 (0x80 + 0x20) + 코드 해시 32바이트
> 리스트 끝
Receipt
type receiptRLP struct {
PostStateOrStatus []byte
CumulativeGasUsed uint64
Bloom Bloom
Logs []*Log
}
인코딩 (EncodeRLP)
Legacy
> 리스트1 시작
> 영수증 상태 1 바이트 (failed: 0x80, success: 0x01)
> 누적 가스 사용량 동적 처리 (uint64)
> 헤더 3바이트 (0xb8 + 0x02 /0x01/0x00) + 로그 블룸 256바이트
> 리스트2 시작
> 로그0 동적 처리
...
> 로그n-1 동적 처리
> 리스트2 끝
> 리스트1 끝
EIP-2718
> 리스트1 시작
> 트랜잭션 유형 1 바이트
> 리스트2 시작
> 영수증 상태 1 바이트 (failed: 0x80, success: 0x01)
> 누적 가스 사용량 동적 처리 (uint64)
> 헤더 3바이트 (0xb8 + 0x02 /0x01/0x00) + 로그 블룸 256바이트
> 리스트3 시작
> 로그0 동적 처리
...
> 로그n-1 동적 처리
> 리스트3 끝
> 리스트2 끝
> 리스트1 끝
Transaction
Legacy
panic!!
Access List
> 리스트1 시작
> 체인 ID 동적 처리 (big.Int)
> 헤더 1바이트 (0x80 + 바이트 길이) + uint64 논스를 바이트로 인코딩
> 가스 비용 동적 처리 (big.Int)
> 헤더 1바이트 (0x80 + 바이트 길이) + 가스량을 바이트로 인코딩
> 헤더 1바이트 (0x80 + 0x14) + 수신자 20바이트
> 이더 금액 동적 처리 (big.Int)
> 데이터 동적 처리 ([]byte)
> 리스트2 시작: 접근 목록
> 헤더 1바이트 (0x80 + 0x14) + 접근 목록 주소1 20바이트
> 리스트3 시작: 접근 목록 저장 위치
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치1 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치n-1 32바이트
> 리스트3 끝
...
> 헤더 1바이트 (0x80 + 0x14) + 접근 목록 주소n 20바이트
> 리스트m+2 시작: 접근 목록 저장 위치
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치1 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치n-1 32바이트
> 리스트m+2 끝
> 리스트2 끝
> V 동적 처리 (big.Int)
> R 동적 처리 (big.Int)
> S 동적 처리 (big.Int)
> 리스트1 끝
Dynamic Fee
> 리스트1 시작
> 체인 ID 동적 처리 (big.Int)
> 헤더 1바이트 (0x80 + 바이트 길이) + uint64 논스를 바이트로 인코딩
> 가스 팁 캡 동적 처리 (big.Int)
> 가스 비용 캡 동적 처리 (big.Int)
> 헤더 1바이트 (0x80 + 바이트 길이) + 가스량을 바이트로 인코딩
> 헤더 1바이트 (0x80 + 0x14) + 수신자 20바이트
> 이더 금액 동적 처리 (big.Int)
> 데이터 동적 처리 ([]byte)
> 리스트2 시작: 접근 목록
> 헤더 1바이트 (0x80 + 0x14) + 접근 목록 주소1 20바이트
> 리스트3 시작: 접근 목록 저장 위치
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치1 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치n-1 32바이트
> 리스트3 끝
...
> 헤더 1바이트 (0x80 + 0x14) + 접근 목록 주소n 20바이트
> 리스트3 시작: 접근 목록 저장 위치
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치1 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치n-1 32바이트
> 리스트3 끝
> 리스트2 끝
> V 동적 처리 (big.Int)
> R 동적 처리 (big.Int)
> S 동적 처리 (big.Int)
> 리스트1 끝
Blob
type blobTxWithBlobs struct {
BlobTx *BlobTx
Blobs []kzg4844.Blob
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
}
> 리스트1 시작
> 체인 ID 동적 처리 (big.Int)
> 헤더 1바이트 (0x80 + 바이트 길이) + uint64 논스를 바이트로 인코딩
> 가스 팁 캡 동적 처리 (big.Int)
> 가스 비용 캡 동적 처리 (big.Int)
> 헤더 1바이트 (0x80 + 바이트 길이) + 가스량을 바이트로 인코딩
> 헤더 1바이트 (0x80 + 0x14) + 수신자 20바이트
> 이더 금액 동적 처리 (big.Int)
> 데이터 동적 처리 ([]byte)
> 리스트2 시작: 접근 목록
> 헤더 1바이트 (0x80 + 0x14) + 접근 목록 주소1 20바이트
> 리스트3 시작: 접근 목록 저장 위치
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치1 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치n-1 32바이트
> 리스트3 끝
...
> 헤더 1바이트 (0x80 + 0x14) + 접근 목록 주소n 20바이트
> 리스트3 시작: 접근 목록 저장 위치
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치1 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 저장 위치n-1 32바이트
> 리스트3 끝
> 리스트2 끝
> 블롭 비용 캡 동적 처리 (uint256.Int)
> 리스트4 시작: 블룸 해시
> 헤더 1바이트 (0x80 + 0x20) + 블룸 해시1 32바이트
...
> 헤더 1바이트 (0x80 + 0x20) + 블룸 해시n-1 32바이트
> 리스트4 끝
> V 동적 처리 (big.Int)
> R 동적 처리 (big.Int)
> S 동적 처리 (big.Int)
(optional - 사이드카가 nil이 아닌 경우)
> 리스트5 시작: 블롭
> 헤더 4바이트 (0xb8 + 0x03 /0x02/0x00/0x00) + 블롭 131072바이트
...
> 헤더 4바이트 (0xb8 + 0x03 /0x02/0x00/0x00) + 블롭 131072바이트
> 리스트5 끝
> 리스트6 시작: 커밋먼트
> 헤더 1바이트 (0x80 + 0x30) + 커밋먼트1 48바이트
...
> 헤더 1바이트 (0x80 + 0x30) + 커밋먼트n-1 48바이트
> 리스트6 끝
> 리스트7 시작: 프루프
> 헤더 1바이트 (0x80 + 0x30) + 프루프1 48바이트
...
> 헤더 1바이트 (0x80 + 0x30) + 프루프n-1 48바이트
> 리스트7 끝
> 리스트1 끝
블롭을 사용하면 블록의 크기가 굉장히 커질 수도 있을 것 같은데 무슨 용도이고 어떻게 사용하는지 궁금하다. 덴쿤 업그레이드에 대해 더 알아봐야겠다.
Withdrawal
인코딩 (EncodeRLP)
> 리스트 시작
> 헤더 1바이트 (0x80 + 바이트 길이) + uint64 인덱스를 바이트로 인코딩
> 헤더 1바이트 (0x80 + 바이트 길이) + uint64 검증자 인덱스를 바이트로 인코딩
> 헤더 1바이트 (0x80 + 0x14) + 출금 주소 20바이트
> 헤더 1바이트 (0x80 + 바이트 길이) + uint64 출금 금액을 바이트로 인코딩
> 리스트 끝
Block
type extblock struct {
Header *Header
Txs []*Transaction
Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"`
}
인코딩 (EncodeRLP)
> 리스트1 시작
> 헤더 인코딩
> 리스트2 시작
> 트랜잭션 인코딩
...
> 트랜잭션 인코딩
> 리스트2 끝
> 리스트3 시작
> 엉클 인코딩
...
> 엉클 인코딩
> 리스트3 끝
(optional)
> 리스트4 시작
> 출금 인코딩
...
> 출금 인코딩
> 리스트4 끝
> 리스트1 끝
Overview
소개
go-ethereum의 crypto 패키지는 암호화 관련 기능을 제공합니다.
패키지
crypto
: 이더리움에서 사용하는keccak256
해시 생성기의 인터페이스, 비밀키(secp256k1)를 생성, 읽기 및 내보내기 함수를 정의secp256k1
: secp256k1 타원 곡선 및 곡선 상의 점에 대한 연산, 서명 생성 및 검증 함수를 정의
crypto 패키지
crypto.go
- 이더리움에서 사용하는
keccak256
해시 생성기의 인터페이스를 정의합니다. - 비밀키(secp256k1)를 생성, 읽기 및 내보내기 함수를 정의합니다.
KeccakState 인터페이스
// KeccakState는 sha3.state를 래핑합니다. 일반적인 해시 메서드 외에도, 해시 상태에서 가변 길이의 데이터를 얻는 데도 지원합니다.
// Read는 내부 상태를 복사하지 않기 때문에 Sum보다 빠르지만 내부 상태를 수정합니다.
type KeccakState interface {
hash.Hash
Read([]byte) (int, error)
}
// NewKeccakState는 새로운 KeccakState를 생성합니다.
func NewKeccakState() KeccakState {
return sha3.NewLegacyKeccak256().(KeccakState)
}
개인키 관련 함수
// GenerateKey는 새로운 개인 키를 생성합니다.
func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(S256(), rand.Reader)
}
// HexToECDSA는 secp256k1 개인 키를 구문 분석합니다.
func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) {
b, err := hex.DecodeString(hexkey) // 0x 접두사가 없어야 합니다.
if byteErr, ok := err.(hex.InvalidByteError); ok {
return nil, fmt.Errorf("invalid hex character %q in private key", byte(byteErr))
} else if err != nil {
return nil, errors.New("invalid hex data for private key")
}
return ToECDSA(b)
}
signature_**.go
- ECDSA 서명을 생성하고 검증하는 함수를 정의합니다.
cgo
geth
이더리움 구현체의github.com/ethereum/go-ethereum/crypto/secp256k1
패키지를 사용합니다.
//go:build !nacl && !js && cgo && !gofuzz
// +build !nacl,!js,cgo,!gofuzz
nocgo
btcd
비트코인 구현체의github.com/btcsuite/btcd/btcec/v2/ecdsa
패키지를 사용합니다.
//go:build nacl || js || !cgo || gofuzz
// +build nacl js !cgo gofuzz
secp256k1 패키지
- secp256k1 타원 곡선 및 곡선 상의 점에 대한 연산을 수행하는 함수를 제공합니다.
curve.go
- 코블리츠 타원 곡선과 곡선 상의 점에 대한 연산(덧셈, 2배, 스칼라 곱)을 수행하는 함수를 정의합니다.
- 자코비안 좌표계를 사용합니다.
BitCurve 구조체
- secp256k1
y²=x³+7
방정식을 사용합니다. - 싱글톤 패턴을 사용합니다.
// BitCurve는 a=0인 코블리츠 타원곡선을 나타냅니다. (y²=x³+B)
// http://www.hyperelliptic.org/EFD/g1p/auto-shortw.html 참조
type BitCurve struct {
P *big.Int // 체의 위수
N *big.Int // 생성점의 위수
B *big.Int // BitCurve 방정식의 상수 (y²=x³+B)
Gx, Gy *big.Int // 생성점의 (x,y)
BitSize int // 유한체의 비트 수
}
var theCurve = new(BitCurve)
func init() {
// See SEC 2 section 2.7.1
// curve parameters taken from:
// http://www.secg.org/sec2-v2.pdf
theCurve.P, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 0) // 유한체의 위수
theCurve.N, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 0) // 생성점의 위수
theCurve.B, _ = new(big.Int).SetString("0x0000000000000000000000000000000000000000000000000000000000000007", 0) // BitCurve 방정식의 상수 7 (y²=x³+7)
theCurve.Gx, _ = new(big.Int).SetString("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 0) // 생성점의 x좌표
theCurve.Gy, _ = new(big.Int).SetString("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 0) // 생성점의 y좌표
theCurve.BitSize = 256 // 유한체의 비트 수
}
scalar_mult_cgo.go
C
라이브러리를 임포트하여 스칼라 곱셈을 수행합니다.
//go:build !gofuzz && cgo
// +build !gofuzz,cgo
사이드 채널 공격에 대해 언급.
scalar_mult_nocgo.go
- 미구현.
//go:build gofuzz || !cgo
// +build gofuzz !cgo
secp256.go
C
라이브러리를 임포트하여 secp256k1 타원 곡선 상의 점에 대한 서명 및 검증 함수를 정의합니다.
그 외
libsecp256k1
C 라이브러리를 불러와 사용하기 위한 파일들로 보입니다.
Overview
소개
go-ethereum의 params 패키지는 버전 정보, 설정 정보 및 다양한 상수들을 정의하고 있습니다.
params 패키지
version.go
- go-ethereum의 버전 정보를 담고 있는 상수들이 정의되어 있습니다.
- 시맨틱 버저닝(Semantic Versioning)을 따릅니다.
bootnodes.go
- go-ethereum의 부트스트랩 노드의 URI 정보를 담고 있는 상수들이 정의되어 있습니다.
enode://
로 시작하는 URI 형식을 따릅니다.- DNS를 통해 노드의 IP 주소를 얻어오는 기능도 제공합니다. (EIP-1459)
config.go
- go-ethereum의 기본 설정 정보를 담고 있는 상수들이 정의되어 있습니다.
- 제네시스 해시에 따라 일부 설정 정보가 달라집니다.
ChainConfig 구조체
// ChainConfig는 블록 체인 설정을 결정하는 핵심 구성입니다.
//
// ChainConfig는 블록에 따라 데이터베이스에 저장됩니다. 이는
// 제네시스 블록으로 식별되는 모든 네트워크는 자체 설정을 가질 수 있음을 의미합니다.
type ChainConfig struct {
ChainID *big.Int `json:"chainId"` // chainId는 현재 체인을 식별하고 재생 방지를 위해 사용됩니다.
HomesteadBlock *big.Int `json:"homesteadBlock,omitempty"` // Homestead 전환 블록 (nil = 포크 없음, 0 = 이미 홈스테드)
DAOForkBlock *big.Int `json:"daoForkBlock,omitempty"` // TheDAO 하드 포크 전환 블록 (nil = 포크 없음)
DAOForkSupport bool `json:"daoForkSupport,omitempty"` // 노드가 DAO 하드 포크를 지원하거나 반대하는지 여부
// EIP150 implements the Gas price changes (https://github.com/ethereum/EIPs/issues/150)
EIP150Block *big.Int `json:"eip150Block,omitempty"` // EIP150 HF 블록 (nil = 포크 없음)
EIP155Block *big.Int `json:"eip155Block,omitempty"` // EIP155 HF 블록
EIP158Block *big.Int `json:"eip158Block,omitempty"` // EIP158 HF 블록
ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium 전환 블록 (nil = 포크 없음, 0 = 이미 byzantium)
ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"` // Constantinople 전환 블록 (nil = 포크 없음, 0 = 이미 constantinople)
PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg 전환 블록 (nil = constantinople과 동일)
IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul 전환 블록 (nil = 포크 없음, 0 = 이미 istanbul)
MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (난이도 폭탄 지연) 스위치 블록 (nil = 포크 없음, 0 = 이미 활성화됨)
BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin 스위치 블록 (nil = 포크 없음, 0 = 이미 berlin)
LondonBlock *big.Int `json:"londonBlock,omitempty"` // London 스위치 블록 (nil = 포크 없음, 0 = 이미 london)
ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (난이도 폭탄 지연) 스위치 블록 (nil = 포크 없음, 0 = 이미 활성화됨)
GrayGlacierBlock *big.Int `json:"grayGlacierBlock,omitempty"` // Eip-5133 (난이도 폭탄 지연) 스위치 블록 (nil = 포크 없음, 0 = 이미 활성화됨)
MergeNetsplitBlock *big.Int `json:"mergeNetsplitBlock,omitempty"` // The Merge 이후의 가상 포크를 네트워크 분할기로 사용
// 포크 스케줄링은 블록에서 타임 스탬프로 전환되었습니다.
ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai 스위치 시간 (nil = 포크 없음, 0 = 이미 shanghai)
CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun 스위치 시간 (nil = 포크 없음, 0 = 이미 cancun)
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague 스위치 시간 (nil = 포크 없음, 0 = 이미 prague)
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle 스위치 시간 (nil = 포크 없음, 0 = 이미 verkle)
// TerminalTotalDifficulty는 컨센서스 업그레이드를 트리거하는 네트워크가 도달한 총 난이도량입니다.
TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"`
// TerminalTotalDifficultyPassed는 네트워크가 이미 터미널 총 난이도를 통과했음을 지정하는 플래그입니다.
// 그 목적은 TTD를 로컬로 보지 않고도 레거시 동기화를 비활성화하는 것입니다(장기적으로 안전함).
TerminalTotalDifficultyPassed bool `json:"terminalTotalDifficultyPassed,omitempty"`
// 다양한 컨센서스 엔진
Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"`
}
dao.go
- go-ethereum의 DAO 하드 포크 관련 상수들이 정의되어 있습니다.
denomination.go
- 이더 단위를 나타내는 상수들이 정의되어 있습니다.
const (
Wei = 1
GWei = 1e9
Ether = 1e18
)
network_params.go
- 이더리움 클라이언트 사이의 네트워크 통신에 사용되는 상수들이 정의되어 있습니다.
protocol_params.go
- 이더리움 프로토콜에 사용되는 상수들이 정의되어 있습니다.
- EVM의 가스 계산(storae, tx, call, keccak256, )에 사용되는 고정값들도 정의되어 있습니다.