티스토리 뷰

🥝 Wiki

 

Protocol Wiki

EPF Study Group Wiki

epf.wiki


😎 Speaker

lightclient - Ethereum core developer, Geth team member


🚀Overview of the execution layer node

Block Validation

  • 'The Merge' 업데이트로 인해 실행 레이어(EL)와 합의 레이어(CL)가 분리됨
이하의 함수들은 실제로 정의되어 있다기보다는 설명을 위한 예제들로 작성되었다. 함수명, 파라미터, 실행 로직 등이 전혀 다를 수 있으므로 주의.

Concensus Layer (CL)

def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
    payload = body.execution_payload

    # Verify consistency of the parent hash with respect to the previous execution payload header
    assert payload.parent_hash == state.latest_execution_payload_header.block_hash
    # Verify prev_randao
    assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state))
    # Verify timestamp
    assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)

    # [New in Deneb:EIP4844] Verify commitments are under limit
    assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK

    # Verify the execution payload is valid
    # [Modified in Deneb:EIP4844] Pass `versioned_hashes` to Execution Engine
    # [Modified in Deneb:EIP4788] Pass `parent_beacon_block_root` to Execution Engine
    versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments]
    assert execution_engine.verify_and_notify_new_payload(
        NewPayloadRequest(
            execution_payload=payload,
            versioned_hashes=versioned_hashes,
            parent_beacon_block_root=state.latest_block_header.parent_root,
        )
    )

    # Cache execution payload header
    state.latest_execution_payload_header = ExecutionPayloadHeader(
        parent_hash=payload.parent_hash,
        fee_recipient=payload.fee_recipient,
        state_root=payload.state_root,
        receipts_root=payload.receipts_root,
        logs_bloom=payload.logs_bloom,
        prev_randao=payload.prev_randao,
        block_number=payload.block_number,
        gas_limit=payload.gas_limit,
        gas_used=payload.gas_used,
        timestamp=payload.timestamp,
        extra_data=payload.extra_data,
        base_fee_per_gas=payload.base_fee_per_gas,
        block_hash=payload.block_hash,
        transactions_root=hash_tree_root(payload.transactions),
        withdrawals_root=hash_tree_root(payload.withdrawals),
        blob_gas_used=payload.blob_gas_used,  # [New in Deneb:EIP4844]
        excess_blob_gas=payload.excess_blob_gas,  # [New in Deneb:EIP4844]
    )
def notify_new_payload(self: ExecutionEngine,
                       execution_payload: ExecutionPayload,
                       parent_beacon_block_root: Root) -> bool:
    """
    Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``.
    """
    ...

Execution Layer (EL)

  • 상태를 변경하기 위해 호출되는 State Transition 함수(STF)는 다음 세 개의 파라미터를 받는다.
    • parent : 부모 블록. 새로운 블록의 헤더를 검증하기 위해 필요.
    • block : 상태 전환에 사용할 새로운 블록.
    • state : 상태가 저장되는 데이터베이스 (상태 트라이)
  • 상태 전환 과정은 다음과 같다.
    1. 블록 헤더를 검증한다. 헤더가 유효하지 않거나 이전 블록 정보에 기반하여 동적으로 계산되는 값들(gas limit, base fee) 등이 유효하지 않은 경우 오류를 반환한다.
    2. 헤더가 유효하면 트랜잭션을 실행하여 상태를 변경한다. 하나라도 잘못된 트랜잭션이 존재할 경우 상태를 변경하지 않으며 블록은 유효하지 않은 것으로 간주되어 오류를 반환한다.
func stf(parent, block *types.Block, state *state.StateDB) (*state.StateDB, error) {
	if err := core.VerifyHeaders(parent, block); err != nil {
		// header error detected
		return nil, err
	}

	for _, tx := range block.Transactions() {
		res, err := vm.Run(block.Header(), tx, state)
		if err != nil {
			// transaction is invalid => block is invalid
			return nil, err
		}
		state = res
	}
	return state, nil
}
  • 비콘 체인에서 Engine API를 통해 실행 레이어로 전달된 페이로드는 newPayload 함수로 전달되고 stf 함수를 호출하여 상태를 변경한 다음, 다시 Engine API를 통해 비콘 체인에게 결과를 전달한다.
func newPayload(execPayload engine.ExecutionPayload) bool {
	if _, err := stf(..); err != nil {
		return false
	}
	return true
}

Q & A

  1. Why pass block.Header() into the vm.Run()?
    • 트랜잭션을 실행하기 위해서는 2가지 콘텍스트가 필요하다.
      1. 상태 : 컨트랙트 코드, 스토리지, 계정 등 상태 변경에 필요한 콘텍스트
      2. 블록: block.blockhash, block.timestamp, block.basefee 등 환경 변수로 읽히는 콘텍스트
  2. The STF is called by the CL and gets returned whether it's valid. If it's not valid, what happens to CL?
    • 코드 상에서 assert를 사용해 true 값을 받지 못하면 블록 생성은 거절된다.
assert execution_engine.verify_and_notify_new_payload(
    NewPayloadRequest(
        execution_payload=payload,
        versioned_hashes=versioned_hashes,
        parent_beacon_block_root=state.latest_block_header.parent_root,
    )
)

Block Building

  • 블록을 생성하기 위해 호출되는 'build' 함수는 다음 세 개의 파라미터를 받는다.
    • env : 타임스탬프, 블록 번호, 이전 블록의 해시 등 트랜잭션 실행 및 블록 생성에 필요한 환경 변수를 담고 있다.
    • pool : 블록에 포함되지 못한 트랜잭션들이 임시로 보관되어 있는 풀. value(지불한 가스비를 이르는 듯)에 따라 정렬되어 있다.
    • state : 상태를 저장하는 데이터베이스.
  • 함수 실행의 결과로 새롭게 생성된 블록, 상태 데이터베이스 그리고 오류가 반환된다.
  • 함수의 실행 과정은 다음과 같다.
    1. 우선 블록 당 가스 한도가 정해져 있기 때문에 트랜잭션 실행에 사용된 가스 양을 트래킹해야 한다. 메인넷의 경우 가스 한도는 30만.
    2. 트랜잭션 풀에서 value가 가장 높은 트랜잭션을 꺼낸 뒤, 트랜잭션이 유효한지 실행해 본다. 트랜잭션이 유효하지 않더라도 블록에 포함하지만 않으면 되므로 반복문을 빠져나가지 않고 다시 실행한다.
    3. core.Finalize 함수를 호출하여 블록을 생성한다.
func build(env Environment, pool txpool.Pool, state state.StateDB) (types.Block, state.StateDB, error) {
    var (
        gasUsed = 0
        txs []types.Transactions
    )
    for ; gasUsed < env.GasLimit || !pool.Empty(); {
        tx := pool.Pop()
        res, gas, err := vm.Run(env, tx, state)
        if err != nil {
            // tx invalid
            continue
        }
        gasUsed += gas
        txs = append(txs, tx)
        state = res
    }
    return core.Finalize(env, txs, state)
}

Q & A

  1. Is the txpool ordered in any way? If not, how do we ensure maximal profit when using pool.Pop?
    • 트랜잭션은 지불한 가스에 따라 내림차순으로 정렬되어 있다.
    • 따라서 Pop()을 실행할 때마다 가스비를 가장 높게 측정한 트랜잭션을 가져오게 된다.
  2. When building the block, does the EL reject any tx before sending it to the CL?
    • 트랜잭션을 거절하는 경우는 해당 트랜잭션이 유효하지 않은 경우에만 해당한다. 일반적으로 트랜잭션 풀에서 자체적으로 트랜잭션을 검증하는 과정을 거치기 때문에 실행 레이어에서 블록을 생성할 때 트랜잭션이 거절되는 경우는 자주 발생하지 않는다. 그리고 이것이 실행 레이어에서 유일하게 트랜잭션을 거절하는 경우이다.
  3. Encrypted mempools: 1. How viable is that? 2. Since block txs are ordered by gas price, is gas unencrypted under such design? 
    • 매우 어려운 문제. 많은 아이디어들이 있지만, 결론만 말하면 어찌 되었든 일부 정보는 드러나게 되며 완전한 암호화는 어려운 단계.
    • 이더리움 개발이 단계적으로 진행되다 보면 언젠가는? 암호화된 멤풀을 구현할 수 있을 것이다.
  4. Whether there are any erase conditions to worry about here? like transaction from the mempool being included in the block and then be deleted before you build another block?
    • 트랜잭션 풀은 트랜잭션을 검증하도록 설계되어 있고, 따라서 유효한 트랜잭션들이 들어있어야 한다. 그러나 트랜잭션 풀이 항상 동기화되어 있지 않으므로 일부 트랜잭션이 유효하지 않을 수도 있고 블록을 생성하는 과정에서 유효하지 않은 트랜잭션이 발견될 수 있다.

🎢 State Transition

  • geth 코드 참고.
 

GitHub - ethereum/go-ethereum: Official Go implementation of the Ethereum protocol

Official Go implementation of the Ethereum protocol - ethereum/go-ethereum

github.com

newPayload 메서드

더보기
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
	// The locking here is, strictly, not required. Without these locks, this can happen:
	//
	// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
	//      api.eth.BlockChain().InsertBlockWithoutSetHead, where it is blocked on
	//      e.g database compaction.
	// 2. The call times out on the CL layer, which issues another NewPayload (execdata-N) call.
	//    Similarly, this also get stuck on the same place. Importantly, since the
	//    first call has not gone through, the early checks for "do we already have this block"
	//    will all return false.
	// 3. When the db compaction ends, then N calls inserting the same payload are processed
	//    sequentially.
	// Hence, we use a lock here, to be sure that the previous call has finished before we
	// check whether we already have the block locally.
	api.newPayloadLock.Lock()
	defer api.newPayloadLock.Unlock()

	log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
	block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot)
	if err != nil {
		log.Warn("Invalid NewPayload params", "params", params, "error", err)
		return api.invalid(err, nil), nil
	}
	// Stash away the last update to warn the user if the beacon client goes offline
	api.lastNewPayloadLock.Lock()
	api.lastNewPayloadUpdate = time.Now()
	api.lastNewPayloadLock.Unlock()

	// If we already have the block locally, ignore the entire execution and just
	// return a fake success.
	if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil {
		log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
		hash := block.Hash()
		return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
	}
	// If this block was rejected previously, keep rejecting it
	if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil {
		return *res, nil
	}
	// If the parent is missing, we - in theory - could trigger a sync, but that
	// would also entail a reorg. That is problematic if multiple sibling blocks
	// are being fed to us, and even more so, if some semi-distant uncle shortens
	// our live chain. As such, payload execution will not permit reorgs and thus
	// will not trigger a sync cycle. That is fine though, if we get a fork choice
	// update after legit payload executions.
	parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
	if parent == nil {
		return api.delayPayloadImport(block)
	}
	// We have an existing parent, do some sanity checks to avoid the beacon client
	// triggering too early
	var (
		ptd  = api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64())
		ttd  = api.eth.BlockChain().Config().TerminalTotalDifficulty
		gptd = api.eth.BlockChain().GetTd(parent.ParentHash(), parent.NumberU64()-1)
	)
	if ptd.Cmp(ttd) < 0 {
		log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
		return engine.INVALID_TERMINAL_BLOCK, nil
	}
	if parent.Difficulty().BitLen() > 0 && gptd != nil && gptd.Cmp(ttd) >= 0 {
		log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
		return engine.INVALID_TERMINAL_BLOCK, nil
	}
	if block.Time() <= parent.Time() {
		log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
		return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
	}
	// Another corner case: if the node is in snap sync mode, but the CL client
	// tries to make it import a block. That should be denied as pushing something
	// into the database directly will conflict with the assumptions of snap sync
	// that it has an empty db that it can fill itself.
	if api.eth.SyncMode() != downloader.FullSync {
		return api.delayPayloadImport(block)
	}
	if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
		api.remoteBlocks.put(block.Hash(), block.Header())
		log.Warn("State not available, ignoring new payload")
		return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
	}
	log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
	if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
		log.Warn("NewPayloadV1: inserting block failed", "error", err)

		api.invalidLock.Lock()
		api.invalidBlocksHits[block.Hash()] = 1
		api.invalidTipsets[block.Hash()] = block.Header()
		api.invalidLock.Unlock()

		return api.invalid(err, parent.Header()), nil
	}
	// We've accepted a valid payload from the beacon client. Mark the local
	// chain transitions to notify other subsystems (e.g. downloader) of the
	// behavioral change.
	if merger := api.eth.Merger(); !merger.TDDReached() {
		merger.ReachTTD()
		api.eth.Downloader().Cancel()
	}
	hash := block.Hash()
	return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
}
  • ExecutableData는 블록에 들어있는 모든 데이터가 포함되어 있으며, 실행 레이어는 이를 통해 블록을 재구성하고 일련의 검증 절차를 거친다.
// ExecutableData is the data necessary to execute an EL payload.
type ExecutableData struct {
	ParentHash    common.Hash         `json:"parentHash"    gencodec:"required"`
	FeeRecipient  common.Address      `json:"feeRecipient"  gencodec:"required"`
	StateRoot     common.Hash         `json:"stateRoot"     gencodec:"required"`
	ReceiptsRoot  common.Hash         `json:"receiptsRoot"  gencodec:"required"`
	LogsBloom     []byte              `json:"logsBloom"     gencodec:"required"`
	Random        common.Hash         `json:"prevRandao"    gencodec:"required"`
	Number        uint64              `json:"blockNumber"   gencodec:"required"`
	GasLimit      uint64              `json:"gasLimit"      gencodec:"required"`
	GasUsed       uint64              `json:"gasUsed"       gencodec:"required"`
	Timestamp     uint64              `json:"timestamp"     gencodec:"required"`
	ExtraData     []byte              `json:"extraData"     gencodec:"required"`
	BaseFeePerGas *big.Int            `json:"baseFeePerGas" gencodec:"required"`
	BlockHash     common.Hash         `json:"blockHash"     gencodec:"required"`
	Transactions  [][]byte            `json:"transactions"  gencodec:"required"`
	Withdrawals   []*types.Withdrawal `json:"withdrawals"`
	BlobGasUsed   *uint64             `json:"blobGasUsed"`
	ExcessBlobGas *uint64             `json:"excessBlobGas"`
}
  • 검증 절차를 거치고 나면 InsertBlockWithoutSetHead 함수를 호출하여 블록을 체인에 추가한다. 그러나 이 블록체인은 canonical 블록체인이 아니기 때문에 SetCanonical 메서드를 호출하여야 완결성을 추가해줘야 한다.
// InsertBlockWithoutSetHead executes the block, runs the necessary verification
// upon it and then persist the block and the associate state into the database.
// The key difference between the InsertChain is it won't do the canonical chain
// updating. It relies on the additional SetCanonical call to finalize the entire
// procedure.
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error {
	if !bc.chainmu.TryLock() {
		return errChainStopped
	}
	defer bc.chainmu.Unlock()

	_, err := bc.insertChain(types.Blocks{block}, false)
	return err
}
// insertChain is the internal implementation of InsertChain, which assumes that
// 1) chains are contiguous, and 2) The chain mutex is held.
//
// This method is split out so that import batches that require re-injecting
// historical blocks can do so without releasing the lock, which could lead to
// racey behaviour. If a sidechain import is in progress, and the historic state
// is imported, but then new canon-head is added before the actual sidechain
// completes, then the historic state could be pruned again
func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) {
	...
    abort, results := bc.engine.VerifyHeaders(bc, headers)
	defer close(abort)
    
    ...
    
    for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {
    	...
    	// Process block using the parent state as reference point
    	pstart := time.Now()
    	receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
    	if err != nil {
    		bc.reportBlock(block, receipts, err)
    		followupInterrupt.Store(true)
        	return it.index, err
    	}
        ...
    }
    ...
}
  • 체인에 블록을 추가하는 과정에서 호출되는 VerifyHeaders 메서드는 Engine API를 통해 합의 레이어로부터 블록 헤더가 합의 규칙에 부합한 지 확인한다. (gas limit, gas used, base fee 등 모든 필드가 유효해야 함)
  • 블록 헤더를 검증하고 나면 Process 메서드를 호출하여 체인에 추가된 블록으로 상태를 변경한다.
// Engine is an algorithm agnostic consensus engine.
type Engine interface {
	// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
	// concurrently. The method returns a quit channel to abort the operations and
	// a results channel to retrieve the async verifications (the order is that of
	// the input slice).
	VerifyHeaders(chain ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error)
}
// Processor is an interface for processing blocks using a given initial state.
type Processor interface {
	// Process processes the state changes according to the Ethereum rules by running
	// the transaction messages using the statedb and applying any rewards to both
	// the processor (coinbase) and any included uncles.
	Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error)
}

Q & A

  1. What a receipt is?
    • 트랜잭션에 대한 정보. 트랜잭션이 실행되고 난 뒤에 결정되고 검증된다.
  2. About environment of multiple transactions which result in multiple other transactions: How is the context environment used? How is it fetched?
    • EVM의 실행 과정에서 블록 콘텍스트는 블록에 포함된 모든 트랜잭션 실행에 동일하게 적용된다. 
    • 반면 트랜잭션 콘텍스트는 서로 다를 수 있다.
    • 또 다른 콘텍스트로는 ScopeContext가 있는데, 이는 실행 중인 트랜잭션의 범위 내에서 접근 가능한 메모리와 스택 그리고 컨트랙트를 포함하고 있다. 
// EVM is the Ethereum Virtual Machine base object and provides
// the necessary tools to run a contract on the given state with
// the provided context. It should be noted that any error
// generated through any of the calls should be considered a
// revert-state-and-consume-all-gas operation, no checks on
// specific errors should ever be performed. The interpreter makes
// sure that any errors generated are to be considered faulty code.
//
// The EVM should never be reused and is not thread safe.
type EVM struct {
	// Context provides auxiliary blockchain related information
	Context BlockContext
	TxContext
	// StateDB gives access to the underlying state
	StateDB StateDB
	// Depth is the current call stack
	depth int

	// chainConfig contains information about the current chain
	chainConfig *params.ChainConfig
	// chain rules contains the chain rules for the current epoch
	chainRules params.Rules
	// virtual machine configuration options used to initialise the
	// evm.
	Config Config
	// global (to this context) ethereum virtual machine
	// used throughout the execution of the tx.
	interpreter *EVMInterpreter
	// abort is used to abort the EVM calling operations
	abort atomic.Bool
	// callGasTemp holds the gas available for the current call. This is needed because the
	// available gas is calculated in gasCall* according to the 63/64 rule and later
	// applied in opCall*.
	callGasTemp uint64
}
// ScopeContext contains the things that are per-call, such as stack and memory,
// but not transients like pc and gas
type ScopeContext struct {
	Memory   *Memory
	Stack    *Stack
	Contract *Contract
}

💻 EVM

EVM 구조

  • PC (Program Counter) : 1 바이트 단위의 오프셋으로 calldata의 특정 위치를 가리킨다.
  • stack : evm은 스택 기반 가상 머신으로, opcode 실행에 필요한 데이터를 임시로 저장한다.
  • memory : 임시 데이터 저장 공간. 지역 변수를 선언할 때 메모리에 저장된 다음 스택으로 푸시된다.
  • gas remaining : 코드를 실행하는 데 사용되는 잔여 가스 양.
  • code : 실행되는 코드. calldata라고도 함.
  • block context, tx context : 코드 실행 과정에서 참조하는 콘텍스트.
  • state : 글로벌 상태. 상태를 불러오거나 저장하기 위해 필요.

EVM 명령어 종류

  • 산술 연산
  • 비트 연산
  • 환경 (caller, address, balance etc..)
  • 흐름 제어(EIP-4788)
  • 스택 명령어 (push, pop, swap etc..)
  • 시스템 (call, create, create2, sstore, return etc..)
  • 메모리 (mload, mstore etc..)
 

EVM Codes

An Ethereum Virtual Machine Opcodes Interactive Reference

www.evm.codes

Q & A

  1. How were various instruction costs determined?
    • 초기에는 gas per second 목표치가 있어서 이를 벤치마킹 기준으로 삼았다. (수치는 기억나지 않음) 현재는 많은 명령어가 존재하고 이들 대다수는 비슷한 명령어를 벤치마킹하여 비용을 책정하였다.

🤼‍♂️ P2P

  • 실행 레이어는 devp2p 명세에 따른 여러 가지 구현체 위에서 동작하고 있으며, devp2p의 현재 버전은 eth/68이다.
 

GitHub - ethereum/devp2p: Ethereum peer-to-peer networking specifications

Ethereum peer-to-peer networking specifications. Contribute to ethereum/devp2p development by creating an account on GitHub.

github.com

Responsibility

  • 이전의 블록체인 데이터(historical data)에 언제든지 접근할 수 있어야 한다.
  • 새로운 트랜잭션(pending txs)을 피어에게 전파할 수 있어야 한다.
  • 다른 피어들과 동일한 상태(state)를 유지할 수 있어야 한다.

Accessing Historical data

  • GetBlockHeader
  • GetBlockBodies
  • GetReceipts

Pending Txs

  • Transactions
  • NewPooledTransactionHashes
  • GetPooledTransactions

State

  • SNAP(Snapshot Protocol) Sync
    1. contiguous state retrieval : semi real-time data retrieval
    2. healing phase : sync the state trie

Q & A

  • How do you know you are not downloading the wrong chain(wrong data)?
    • SNAP Sync를 시작하 과정은 다음과 같다.
      1. weak subjectivity checkpoint에서 시작. 블록해시를 받아온다.
      2. 블록해시와 관련된 블록 데이터를 받아온다.
      3. 블록 상태를 스냅다. (start snap against that block state)
    • 블록해시를 블록 데이터로 검증하는 과정을 거치기 때문에 상태 데이터의 추가적인 검증없이 블록해시가 올바르다고 가정한다.
    • 이 때 블록해시만을 검증함으로 인해 잘못 계산된 상태를 내려받는 문제가 발생할 수 있는데, 경제적 다수(economic majority)가 체인을 검증하고 있기 때문에 경우의 수는 극히 드물다.
    • 만약 블록해시를 검증하는 것만으로 부족하다면 full sync를 통해 완전한 검증 절차를 거칠 수 있다.

🔮 JSON-RPC

  • 이더리움에 대한 인터페이스 (the interface to ethereum)
    • 모든 클라이언트가 동일한 API를 제공하며 사용자들은 어떤 클라이언트에 대해 JSON RPC 호출을 실행하더라도 완전히 동일한 결과를 얻을 수 있어야 함.

📖 마무리

 

'교육 과정 > EPF' 카테고리의 다른 글

[EPF] Study Group 1주차 Protocol Intro  (0) 2024.03.04
최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday
글 보관함