λ²μ : CometBFT v0.38+ / v1.0 κΈ°μ€ | μ΅μ’ μ λ°μ΄νΈ: 2025λ
- CometBFT κ°μ
- Tendermint μκ³ λ¦¬μ¦
- State ꡬ쑰체
- 컨μΌμμ€ νλ¦
- ABCI 2.0 (ABCI++)
- WAL (Write-Ahead Log)
- Proposer μ μ
- νμμμ μ€μ
- Evidence μ²λ¦¬
- λΈλ‘ μ‘°κ°κ³Ό P2P
- Mempool
- μ 리
CometBFTλ Tendermint Coreμ 곡μ νμ νλ‘μ νΈλ‘, Byzantine Fault Tolerant 컨μΌμμ€ μμ§μ λλ€. Cosmos SDKμ ν¨κ» μ¬μ©λμ΄ μλ°± κ°μ λΈλ‘체μΈμ μ§μν©λλ€.
- μ¦μ μ΅μ’ μ±(Instant Finality): λΈλ‘μ΄ μ»€λ°λλ©΄ λλ릴 μ μμ
- Byzantine Fault Tolerance: μ΅λ f < n/3 μ μμ λ Έλ νμ© (n λ Έλ μ€ f μ μ±)
- Partial Synchrony: GST(Global Stabilization Time) μ΄ν λκΈ°ν κ°μ
- ABCI 2.0: PrepareProposal, ProcessProposal, Vote Extensions μ§μ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CometBFT Node β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Consensus Engine β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β β β State β β Reactor β β WAL β β Evidence β β β
β β β Machine β β (P2P) β β β β Pool β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β β ABCI 2.0 (gRPC/Socket) β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Application (Cosmos SDK) β β
β β PrepareProposal β ProcessProposal β ExtendVote β FinalizeBlockβ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββ΄ββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Mempool β β Block Store β β State Store β β
β β (TxPool) β β (Blocks) β β (AppState) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β P2P Network Layer β β
β β Gossip Protocol / Connection Management β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
github.com/cometbft/cometbft
cometbft/
βββ // ν΅μ¬ 컨μΌμμ€
βββ consensus/
β βββ state.go // State ꡬ쑰체, enterPropose, enterPrevote λ±
β βββ reactor.go // P2P λ©μμ§ μ²λ¦¬
β βββ wal.go // Write-Ahead Log
β βββ types/
β βββ round_state.go // RoundState, HeightVoteSet
β
βββ // ABCI μΈν°νμ΄μ€
βββ abci/
β βββ types/
β β βββ types.pb.go // ABCI λ©μμ§ μ μ
β βββ client/ // ABCI ν΄λΌμ΄μΈνΈ
β
βββ // μν κ΄λ¦¬
βββ state/
β βββ state.go // λΈλ‘μ²΄μΈ μν
β βββ execution.go // λΈλ‘ μ€ν
β βββ store.go // μν μ μ₯μ
β
βββ // νμ
μ μ
βββ types/
β βββ block.go // Block, Header
β βββ vote.go // Vote, VoteSet
β βββ proposal.go // Proposal
β βββ validator.go // Validator, ValidatorSet
β βββ evidence.go // Evidence (μ΄μ€μλͺ
μ¦κ±°)
β
βββ // P2P λ€νΈμνΉ
βββ p2p/
β βββ switch.go // P2P Switch (νΌμ΄ κ΄λ¦¬)
β βββ conn/ // MConnection (λ©ν°νλ μ€)
β βββ pex/ // Peer Exchange
β
βββ // Mempool
βββ mempool/
β βββ clist_mempool.go // κΈ°λ³Έ Mempool ꡬν
β βββ reactor.go // Mempool P2P
β
βββ // Evidence
evidence/
βββ pool.go // Evidence Pool
βββ reactor.go // Evidence P2P
| v0.34 (Legacy) | v0.37 | v0.38+ / v1.0 (μ΅μ ) |
|---|---|---|
| BeginBlock | + PrepareProposal | PrepareProposal |
| DeliverTx Γ N | + ProcessProposal | ProcessProposal |
| EndBlock | BeginBlock/EndBlock | ExtendVote |
| Commit | VerifyVoteExtension | |
| FinalizeBlock | ||
| Commit |
Tendermintλ PBFT κ³μ΄μ BFT 컨μΌμμ€ μκ³ λ¦¬μ¦μΌλ‘, DLS(Dwork-Lynch-Stockmeyer) μκ³ λ¦¬μ¦μμ μκ°μ λ°μμ΅λλ€.
| μμ± | μ€λͺ | 보μ₯ 쑰건 |
|---|---|---|
| Safety | λ κ°μ λ€λ₯Έ λΈλ‘μ΄ κ°μ λμ΄μμ 컀λ°λμ§ μμ | f < n/3 (νμ) |
| Liveness | κ²°κ΅ μ λΈλ‘μ΄ μ»€λ°λ¨ | f < n/3 + λΆλΆ λκΈ° (GST μ΄ν) |
| Validity | 컀λ°λ λΈλ‘μ μ ν¨ν νΈλμμ λ§ ν¬ν¨ | μ μ§ν μ μμ |
Height Hμ 컨μΌμμ€:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Round 0 β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β NewHeight βββ Propose βββ Prevote βββ Precommit βββ Commit β
β β β β β β β
β β νλ‘ν¬μ κ° λΈλ‘ κ²μ¦ ν Prevote κ²°κ³Ό 2/3+ λͺ¨μ΄λ©΄ β
β β λΈλ‘ μ μ 1μ°¨ ν¬ν κΈ°λ° 2μ°¨ ν¬ν λΈλ‘ νμ β
β β β
β β (μ€ν¨ μ) β
β β β β
β ββββββββ Round 1 βββ Round 2 βββ ... β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
νμμμ:
β’ ProposeTimeout: μ μ λκΈ° (κΈ°λ³Έ 3μ΄)
β’ PrevoteTimeout: Prevote μμ§ λκΈ° (+2/3 Any ν)
β’ PrecommitTimeout: Precommit μμ§ λκΈ° (+2/3 Any ν)
- Polka: νΉμ λΈλ‘(λλ nil)μ λν +2/3 Prevote μ§ν©
- PoLC (Proof of Lock Change): Lockμ λ³κ²½νκ±°λ ν΄μ ν μ μλ μ¦κ±°
- Lock: κ²μ¦μκ° νΉμ λΈλ‘μ "μ κΉ" (λ€λ₯Έ λΈλ‘μ Prevote λΆκ°)
// Lock κ·μΉ:
// 1. +2/3 Prevoteλ₯Ό λ³Έ λΈλ‘μ Lock
// 2. Lockλ λΈλ‘μλ§ Precommit κ°λ₯
// 3. λ λμ λΌμ΄λμ PoLCκ° μμ΄μΌ Lock ν΄μ κ°λ₯
func (cs *State) enterPrecommit(height int64, round int32) {
// Prevote κ²°κ³Ό νμΈ
blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
if !ok {
// +2/3 μμ β nil Precommit
cs.signAddVote(PrecommitType, nil)
return
}
if blockID.IsNil() {
// +2/3κ° nil μ ν β Lock ν΄μ
cs.LockedRound = -1
cs.LockedBlock = nil
cs.signAddVote(PrecommitType, nil)
return
}
// +2/3κ° νΉμ λΈλ‘ μ ν β Lock & Precommit!
if cs.ProposalBlock.HashesTo(blockID.Hash) {
cs.LockedRound = round
cs.LockedBlock = cs.ProposalBlock
cs.signAddVote(PrecommitType, blockID.Hash)
}
}μλ리μ€: Round 0μμ Block Aμ Lock
Round 0:
β’ κ²μ¦μ 1,2,3μ΄ Block Aμ Prevote (+2/3)
β’ κ²μ¦μ 1,2,3μ΄ Block Aμ Lock
β’ λ€νΈμν¬ μ§μ°μΌλ‘ Precommit μμ§ μ€ν¨
Round 1:
β’ μ μ μμκ° Block B μ μ
β’ κ²μ¦μ 1,2,3: "λλ Block Aμ Lockλμ΄ μμ΄!"
β’ Block Aμ λν PoLC μμ΄λ Block Bμ Prevote λΆκ°
κ²°κ³Ό:
β’ Safety 보μ₯: Block Aμ Block B λμ μ»€λ° λΆκ°λ₯
β’ μ μ§ν λ
Έλκ° 2/3 μ΄μμ΄λ©΄, κ²°κ΅ κ°μ λΈλ‘μ ν©μ
consensus/state.go - defaultDoPrevote
func (cs *State) defaultDoPrevote(height int64, round int32) {
// κ·μΉ 1: Lockλ λΈλ‘μ΄ μμΌλ©΄ κ·Έ λΈλ‘μ ν¬ν
if cs.LockedBlock != nil {
cs.signAddVote(PrevoteType, cs.LockedBlock.Hash())
return
}
// κ·μΉ 2: μ μλ λΈλ‘μ΄ μμΌλ©΄ nil ν¬ν
if cs.ProposalBlock == nil {
cs.signAddVote(PrevoteType, nil)
return
}
// κ·μΉ 3: λΈλ‘ κ²μ¦
err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock)
if err != nil {
cs.signAddVote(PrevoteType, nil)
return
}
// κ·μΉ 4: μ ν리μΌμ΄μ
κ²μ¦ (ProcessProposal)
isAppValid, err := cs.blockExec.ProcessProposal(cs.ProposalBlock, cs.state)
if err != nil || !isAppValid {
cs.signAddVote(PrevoteType, nil)
return
}
// λͺ¨λ κ²μ¦ ν΅κ³Ό! λΈλ‘μ ν¬ν
cs.signAddVote(PrevoteType, cs.ProposalBlock.Hash())
}consensus/types/round_state.go
// RoundStateλ 컨μΌμμ€μ νμ¬ μνλ₯Ό λνλ
λλ€
type RoundState struct {
// μμΉ μ 보
Height int64 // νμ¬ λΈλ‘ λμ΄
Round int32 // νμ¬ λΌμ΄λ (0λΆν° μμ)
Step RoundStepType // νμ¬ λ¨κ³
// μκ° μ 보
StartTime time.Time // λΌμ΄λ μμ μκ°
CommitTime time.Time // μ»€λ° μκ°
// κ²μ¦μ μ 보
Validators *types.ValidatorSet
Proposer *types.Validator
// μ μ μ 보
Proposal *types.Proposal
ProposalBlock *types.Block
ProposalBlockParts *types.PartSet
// Lock μ 보 (Safety ν΅μ¬!)
LockedRound int32
LockedBlock *types.Block
LockedBlockParts *types.PartSet
// Valid μ 보 (Precommit μμ΄ +2/3 Prevote λ³Έ κ²½μ°)
ValidRound int32
ValidBlock *types.Block
ValidBlockParts *types.PartSet
// ν¬ν μ 보
Votes *HeightVoteSet
// μ΄μ λμ΄ μ»€λ°
LastCommit *types.ExtendedCommit
}
// RoundStepType μ μ
const (
RoundStepNewHeight = 0x01 // μ λμ΄ λκΈ°
RoundStepNewRound = 0x02 // μ λΌμ΄λ μμ
RoundStepPropose = 0x03 // μ μ λ¨κ³
RoundStepPrevote = 0x04 // Prevote λ¨κ³
RoundStepPrevoteWait = 0x05 // +2/3 Any λκΈ°
RoundStepPrecommit = 0x06 // Precommit λ¨κ³
RoundStepPrecommitWait = 0x07 // +2/3 Any λκΈ°
RoundStepCommit = 0x08 // μ»€λ° μλ£
)types/vote_set.go
// VoteSetμ νΉμ (Height, Round, Type)μ λͺ¨λ ν¬νλ₯Ό κ΄λ¦¬
type VoteSet struct {
chainID string
height int64
round int32
signedMsgType SignedMsgType // Prevote or Precommit
valSet *ValidatorSet
mtx sync.Mutex
votesBitArray *bits.BitArray // μ΄λ€ κ²μ¦μκ° ν¬ννλμ§
votes []*Vote // μΈλ±μ€ = κ²μ¦μ μΈλ±μ€
sum int64 // μ΄ ν¬νλ ₯
// λΈλ‘λ³ ν¬νλ ₯ μΆμ
votesByBlock map[string]*blockVotes
// +2/3 λ¬μ± μ¬λΆ
maj23 *BlockID // nilμ΄ μλλ©΄ +2/3 λ¬μ±
}
// +2/3 νμΈ λ©μλ
func (voteSet *VoteSet) TwoThirdsMajority() (BlockID, bool) {
if voteSet == nil {
return BlockID{}, false
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
if voteSet.maj23 != nil {
return *voteSet.maj23, true
}
return BlockID{}, false
}
// +2/3 Any (nil ν¬ν¨ μ무거λ) νμΈ
func (voteSet *VoteSet) HasTwoThirdsAny() bool {
if voteSet == nil {
return false
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.sum > voteSet.valSet.TotalVotingPower()*2/3
}consensus/types/height_vote_set.go
// HeightVoteSetμ ν λμ΄μ λͺ¨λ λΌμ΄λ ν¬νλ₯Ό κ΄λ¦¬
type HeightVoteSet struct {
chainID string
height int64
valSet *types.ValidatorSet
mtx sync.Mutex
round int32 // μλ €μ§ μ΅λ λΌμ΄λ
roundVoteSets map[int32]RoundVoteSet // λΌμ΄λλ³ VoteSet
peerCatchupRounds map[p2p.ID][]int32 // νΌμ΄λ³ μΊμΉμ
λΌμ΄λ
}
type RoundVoteSet struct {
Prevotes *types.VoteSet
Precommits *types.VoteSet
}
// νΉμ λΌμ΄λμ Prevote κ°μ Έμ€κΈ°
func (hvs *HeightVoteSet) Prevotes(round int32) *types.VoteSet {
hvs.mtx.Lock()
defer hvs.mtx.Unlock()
return hvs.getVoteSet(round, types.PrevoteType)
}consensus/state.go
// receiveRoutineμ 컨μΌμμ€μ μ¬μ₯λΆμ
λλ€
func (cs *State) receiveRoutine(maxSteps int) {
defer func() {
if r := recover(); r != nil {
cs.Logger.Error("CONSENSUS FAILURE!", "err", r)
}
}()
for {
rs := cs.RoundState
var mi msgInfo
select {
// 1. νΈλμμ
μ¬μ© κ°λ₯ μλ¦Ό
case <-cs.txNotifier.TxsAvailable():
cs.handleTxsAvailable()
// 2. νΌμ΄λ‘λΆν° λ©μμ§ (Proposal, Vote λ±)
case mi = <-cs.peerMsgQueue:
cs.wal.Write(mi) // WALμ κΈ°λ‘ (λΉλκΈ°)
cs.handleMsg(mi)
// 3. λ΄λΆ λ©μμ§ (μμ μ ν¬ν)
case mi = <-cs.internalMsgQueue:
cs.wal.WriteSync(mi) // WALμ κΈ°λ‘ (λκΈ° - fsync!)
cs.handleMsg(mi)
// 4. νμμμ λ°μ
case ti := <-cs.timeoutTicker.Chan():
cs.wal.Write(ti)
cs.handleTimeout(ti, rs)
// 5. μ’
λ£ μ νΈ
case <-cs.Quit():
return
}
}
}func (cs *State) enterNewRound(height int64, round int32) {
// μν κ²μ¦
if cs.Height != height || round < cs.Round ||
(cs.Round == round && cs.Step != RoundStepNewHeight) {
return
}
cs.Logger.Info("enterNewRound",
"height", height,
"round", round)
// λΌμ΄λ μν μ
λ°μ΄νΈ
cs.Round = round
cs.Step = RoundStepNewRound
cs.Proposal = nil
cs.ProposalBlock = nil
cs.ProposalBlockParts = nil
// μ λΌμ΄λμ μ μμ κ²°μ
cs.Validators.IncrementProposerPriority(round)
cs.Proposer = cs.Validators.GetProposer()
// μ¦μ Propose λ¨κ³λ‘
cs.enterPropose(height, round)
}func (cs *State) enterPropose(height int64, round int32) {
// Propose νμμμ μ€μ
cs.scheduleTimeout(cs.config.Propose(round), height, round, RoundStepPropose)
cs.Step = RoundStepPropose
// λ΄κ° μ΄ λΌμ΄λμ μ μμμΈκ°?
if cs.isProposer(cs.privValidatorPubKey.Address()) {
cs.Logger.Info("enterPropose: Our turn to propose")
cs.decideProposal(height, round)
} else {
cs.Logger.Info("enterPropose: Not our turn",
"proposer", cs.Proposer.Address)
}
}
func (cs *State) decideProposal(height int64, round int32) {
var block *types.Block
var blockParts *types.PartSet
// ValidBlockμ΄ μμΌλ©΄ κ·Έκ² μ¬μ© (μ΄μ λΌμ΄λμμ +2/3 Prevote λ°μ λΈλ‘)
if cs.ValidBlock != nil {
block = cs.ValidBlock
blockParts = cs.ValidBlockParts
} else {
// μ λΈλ‘ μμ± (PrepareProposal νΈμΆ)
block, blockParts = cs.createProposalBlock()
}
// Proposal μμ± λ° μλͺ
proposal := types.NewProposal(height, round, cs.ValidRound, blockParts.Header())
cs.privValidator.SignProposal(cs.state.ChainID, proposal)
// λΈλ‘λμΊμ€νΈ
cs.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""})
for i := 0; i < int(blockParts.Total()); i++ {
part := blockParts.GetPart(i)
cs.sendInternalMessage(msgInfo{&BlockPartMessage{height, round, part}, ""})
}
}func (cs *State) enterPrecommit(height int64, round int32) {
cs.Step = RoundStepPrecommit
// Prevote κ²°κ³Ό νμΈ
blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
if !ok {
// +2/3 Prevote μμ β nil Precommit
cs.signAddVote(PrecommitType, nil)
return
}
// *** Vote Extension μ²λ¦¬ (v0.38+) ***
if cs.state.ConsensusParams.ABCI.VoteExtensionsEnabled(height) {
// ExtendVote νΈμΆ
extension, err := cs.blockExec.ExtendVote(
cs.ProposalBlock,
cs.state,
)
if err == nil {
vote.Extension = extension
}
}
// Lock μ
λ°μ΄νΈ λ° Precommit
if !blockID.IsNil() && cs.ProposalBlock.HashesTo(blockID.Hash) {
cs.LockedRound = round
cs.LockedBlock = cs.ProposalBlock
cs.signAddVote(PrecommitType, blockID.Hash)
} else {
cs.LockedRound = -1
cs.LockedBlock = nil
cs.signAddVote(PrecommitType, nil)
}
}func (cs *State) finalizeCommit(height int64) {
block := cs.ProposalBlock
blockParts := cs.ProposalBlockParts
// 1. λΈλ‘ μ μ₯
cs.blockStore.SaveBlock(block, blockParts, cs.Votes.Precommits(cs.CommitRound))
// 2. WALμ EndHeight κΈ°λ‘
cs.wal.WriteSync(EndHeightMessage{height})
// 3. λΈλ‘ μ€ν! (FinalizeBlock νΈμΆ)
stateCopy, err := cs.blockExec.ApplyVerifiedBlock(
cs.state,
types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()},
block,
)
// 4. μν μ
λ°μ΄νΈ
cs.updateToState(stateCopy)
// 5. Mempool μ
λ°μ΄νΈ
cs.mempool.Update(...)
// 6. λ€μ λμ΄λ‘
cs.scheduleRound0(&cs.RoundState)
} βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Height H β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Round R β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β βNewRound β β β PROPOSE β β
β βββββββββββ β β’ μ μμ: PrepareProposal β λΈλ‘ μμ± β β
β β β’ λΉμ μμ: νμμμ λκΈ° (ProposeTimeout) β β
β ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ β
β β λΈλ‘ μμ λλ νμμμ β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PREVOTE (1μ°¨ ν¬ν) β β
β β β’ ProcessProposalλ‘ λΈλ‘ κ²μ¦ β β
β β β’ κ²μ¦ μ±κ³΅: λΈλ‘ ν΄μμ ν¬ν / μ€ν¨: nil ν¬ν β β
β β β’ Lockλ λΈλ‘ μμΌλ©΄ κ·Έ λΈλ‘μ ν¬ν β β
β ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β
β β ν¬ν μμ§ β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β +2/3 Prevotes μμ§ μλ£? β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ β
β β β β β
β +2/3 Block A +2/3 nil +2/3 λ―Έλ¬μ± β
β β β β β
β βΌ βΌ βΌ β
β Lock Block A Unlock PrevoteWait β
β β β (νμμμ) β
β ββββββββββββββββββ΄βββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PRECOMMIT (2μ°¨ ν¬ν) β β
β β β’ +2/3 Prevote κ²°κ³Ό κΈ°λ° ν¬ν β β
β β β’ ExtendVoteλ‘ Vote Extension μΆκ° (v0.38+) β β
β ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β
β β ν¬ν μμ§ β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β +2/3 Precommits μμ§ μλ£? β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ β
β β β β β
β +2/3 Block A +2/3 nil +2/3 λ―Έλ¬μ± β
β β β β β
β βΌ βΌ βΌ β
β COMMIT! λ€μ λΌμ΄λ PrecommitWait β
β β (Round R+1) (νμμμ) β
β β β β
β β βΌ β
β β λ€μ λΌμ΄λ β
β β (Round R+1) β
β βΌ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β COMMIT β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. λΈλ‘ μ μ₯ (BlockStore) β
β 2. FinalizeBlock νΈμΆ β νΈλμμ
μ€ν β
β 3. Commit νΈμΆ β μν μꡬ μ μ₯ β
β 4. Mempool μ
λ°μ΄νΈ (μ€νλ TX μ κ±°) β
β 5. Height H+1λ‘ μ΄λ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ABCI 2.0μ μ ν리μΌμ΄μ μ΄ μ»¨μΌμμ€μ λ κΉμ΄ κ΄μ¬ν μ μκ² ν©λλ€.
| PrepareProposal | ProcessProposal | |
|---|---|---|
| νΈμΆ μμ | μ μμκ° λΈλ‘μ λ§λ€ λ | μ μλ°μ λΈλ‘ κ²μ¦ μ |
| μν | TX μ¬μ λ ¬/μΆκ°/μ κ±° μμλ£ κΈ°λ° μ λ ¬ λ°°μΉ μ΅μ ν |
λΈλ‘ μ ν¨μ± κ²μ¦ Accept/Reject κ²°μ μ¦μ μ€ν (μ΅μ ) |
| κ²°μ λ‘ μ ? | μλμ€ (λΉκ²°μ μ νμ©) | μ (λ°λμ κ²°μ λ‘ μ ) |
abci/types/types.pb.go
type RequestPrepareProposal struct {
MaxTxBytes int64 // μ΅λ TX λ°μ΄νΈ
Txs [][]byte // Mempoolμ TXλ€
LocalLastCommit ExtendedCommitInfo // μ΄μ μ»€λ° + Vote Extensions
Misbehavior []Misbehavior // μ
μ± νμ μ¦κ±°
Height int64
Time time.Time
NextValidatorsHash []byte
ProposerAddress []byte
}
type ResponsePrepareProposal struct {
Txs [][]byte // μμ λ TX λͺ©λ‘ (μ¬μ λ ¬, μΆκ°, μ κ±° κ°λ₯)
}
// μμ: μμλ£ κΈ°λ° μ λ ¬
func (app *MyApp) PrepareProposal(
req *RequestPrepareProposal,
) *ResponsePrepareProposal {
txs := req.Txs
// μμλ£λ‘ μ λ ¬
sort.Slice(txs, func(i, j int) bool {
feeI := extractFee(txs[i])
feeJ := extractFee(txs[j])
return feeI > feeJ // λμ μμλ£ μ°μ
})
// Vote Extension λ°μ΄ν°λ₯Ό "νΉλ³ TX"λ‘ μ£Όμ
κ°λ₯
if len(req.LocalLastCommit.Votes) > 0 {
oracleData := aggregateVoteExtensions(req.LocalLastCommit)
txs = append([][]byte{oracleData}, txs...)
}
return &ResponsePrepareProposal{Txs: txs}
}κ²μ¦μκ° Precommit ν¬νμ μΆκ° λ°μ΄ν°λ₯Ό 첨λΆν μ μλ κΈ°λ₯μ λλ€.
μ¬μ© μ¬λ‘: μ€λΌν΄ κ°κ²© νΌλ, μνΈνλ λ©€ν, μκ³κ° μλͺ λ±
Height H:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Precommit λ¨κ³ β
β β
β Validator A: Precommit + VoteExtension(price: $100) β
β Validator B: Precommit + VoteExtension(price: $101) β
β Validator C: Precommit + VoteExtension(price: $99) β
β β
β β λΈλ‘ μ»€λ° (Extensionsλ ν¨κ» μ μ₯) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
Height H+1:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PrepareProposal (μ μμλ§) β
β β
β LocalLastCommitμμ Extensions μ κ·Ό: β
β β’ Validator A: $100 β
β β’ Validator B: $101 β
β β’ Validator C: $99 β
β β
β μ€μκ° κ³μ°: $100 β λΈλ‘μ "νΉλ³ TX"λ‘ ν¬ν¨ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// ExtendVote: Precommit μ Vote Extension μμ±
type RequestExtendVote struct {
Hash []byte // λΈλ‘ ν΄μ
Height int64
Time time.Time
Txs [][]byte
ProposedLastCommit CommitInfo
Misbehavior []Misbehavior
NextValidatorsHash []byte
ProposerAddress []byte
}
type ResponseExtendVote struct {
VoteExtension []byte // ν¬νμ 첨λΆν λ°μ΄ν°
}
// μμ: μ€λΌν΄ κ°κ²© νΌλ
func (app *OracleApp) ExtendVote(req *RequestExtendVote) *ResponseExtendVote {
// μΈλΆ APIμμ κ°κ²© μ‘°ν (λΉκ²°μ λ‘ μ OK)
price := fetchPriceFromAPI("ETH/USD")
ext := &OraclePriceExtension{
Price: price,
Timestamp: time.Now(),
}
extBytes, _ := proto.Marshal(ext)
return &ResponseExtendVote{VoteExtension: extBytes}
}
// VerifyVoteExtension: λ€λ₯Έ κ²μ¦μμ Extension κ²μ¦
func (app *OracleApp) VerifyVoteExtension(
req *RequestVerifyVoteExtension,
) *ResponseVerifyVoteExtension {
var ext OraclePriceExtension
if err := proto.Unmarshal(req.VoteExtension, &ext); err != nil {
return &ResponseVerifyVoteExtension{
Status: ResponseVerifyVoteExtension_REJECT,
}
}
// κ°κ²©μ΄ ν©λ¦¬μ μΈ λ²μμΈμ§ νμΈ (κ²°μ λ‘ μ μ΄μ΄μΌ ν¨)
if ext.Price < 0 || ext.Price > 1000000 {
return &ResponseVerifyVoteExtension{
Status: ResponseVerifyVoteExtension_REJECT,
}
}
return &ResponseVerifyVoteExtension{
Status: ResponseVerifyVoteExtension_ACCEPT,
}
}μ£Όμμ¬ν
- ν¬κΈ° μ ν: λ무 ν¬λ©΄ λ μ΄ν΄μ μ¦κ°
- VerifyVoteExtensionμ κ²°μ λ‘ μ : λͺ¨λ λ Έλκ° κ°μ κ²°κ³Ό
- λ무 λ§μ REJECTλ Liveness μ ν
- Vote Extensionμ λΈλ‘ ν¬κΈ°μ μν₯: κ²μ¦μλΉ μΆκ° λ°μ΄ν° λ°μ
- Extension ν¬κΈ°λ ν©λ¦¬μ μΌλ‘ μ μ§ (κΆμ₯: μ KB μ΄λ΄)
- λ€νΈμν¬ λμν κ³ λ € νμ
v0.38+μμ BeginBlock + DeliverTx[] + EndBlockμ΄ FinalizeBlock νλλ‘ ν΅ν©λμμ΅λλ€.
ν΅ν© μ΄μ
- API λ¨μν: 3λ² νΈμΆ β 1λ² νΈμΆ
- μ±λ₯ κ°μ : λ€νΈμν¬ μ볡 νμ κ°μ
- μμμ±: λΈλ‘ μ€νμ΄ λ¨μΌ νΈλμμ μΌλ‘ μ²λ¦¬
- μ μ°μ±: μ ν리μΌμ΄μ μ΄ TX μ€ν μμλ₯Ό λ μ μ μ΄ κ°λ₯
FinalizeBlock ꡬ쑰
type RequestFinalizeBlock struct {
Txs [][]byte // μ€νν νΈλμμ
λ€
DecidedLastCommit CommitInfo // μ΄μ λμ΄ μ»€λ° μ 보
Misbehavior []Misbehavior // μ
μ± νμ μ¦κ±° (μ΄μ€ μλͺ
λ±)
Hash []byte // λΈλ‘ ν΄μ (κ²μ¦μ©)
Height int64 // λΈλ‘ λμ΄
Time time.Time // λΈλ‘ νμμ€ν¬ν
NextValidatorsHash []byte // λ€μ κ²μ¦μ μΈνΈ ν΄μ
ProposerAddress []byte // μ μμ μ£Όμ
}
type ResponseFinalizeBlock struct {
Events []Event // λΈλ‘ λ 벨 μ΄λ²€νΈ
TxResults []ExecTxResult // κ° TX μ€ν κ²°κ³Ό
ValidatorUpdates []ValidatorUpdate // κ²μ¦μ μΈνΈ λ³κ²½
ConsensusParamUpdates *ConsensusParams // 컨μΌμμ€ νλΌλ―Έν° λ³κ²½
AppHash []byte // μ± μν ν΄μ (Merkle Root)
}
type ExecTxResult struct {
Code uint32 // 0 = success, 1+ = error code
Data []byte // TX μ€ν κ²°κ³Ό λ°μ΄ν°
Log string // λ‘κ·Έ λ©μμ§
Info string // μΆκ° μ 보
GasWanted int64 // μμ²ν κ°μ€
GasUsed int64 // μ€μ μ¬μ©ν κ°μ€
Events []Event // TX λ 벨 μ΄λ²€νΈ
Codespace string // μλ¬ μ½λ λ€μμ€νμ΄μ€
}ꡬν μμ
// μμ ꡬν
func (app *MyApp) FinalizeBlock(
req *RequestFinalizeBlock,
) *ResponseFinalizeBlock {
// 1. λΈλ‘ μμ μ²λ¦¬ (κΈ°μ‘΄ BeginBlock λ‘μ§)
app.logger.Info("FinalizeBlock", "height", req.Height, "txs", len(req.Txs))
// λΈλ‘ λ 벨 μ΄λ²€νΈ
blockEvents := []Event{
{
Type: "begin_block",
Attributes: []EventAttribute{
{Key: "height", Value: fmt.Sprintf("%d", req.Height)},
{Key: "proposer", Value: hex.EncodeToString(req.ProposerAddress)},
},
},
}
// 2. κ° νΈλμμ
μ€ν (κΈ°μ‘΄ DeliverTx λ‘μ§)
txResults := make([]ExecTxResult, len(req.Txs))
for i, txBytes := range req.Txs {
// νΈλμμ
λμ½λ©
tx, err := app.decodeTx(txBytes)
if err != nil {
txResults[i] = ExecTxResult{
Code: 1,
Log: fmt.Sprintf("failed to decode tx: %v", err),
}
continue
}
// νΈλμμ
μ€ν (κ²°μ λ‘ μ μ΄μ΄μΌ ν¨!)
result := app.executeTx(tx)
txResults[i] = result
// μν μ
λ°μ΄νΈ
if result.Code == 0 {
app.state.ApplyTx(tx)
}
}
// 3. Evidence μ²λ¦¬ (μ
μ± κ²μ¦μ μ¬λμ±)
for _, evidence := range req.Misbehavior {
app.handleEvidence(evidence)
blockEvents = append(blockEvents, Event{
Type: "evidence",
Attributes: []EventAttribute{
{Key: "type", Value: evidence.Type.String()},
{Key: "validator", Value: hex.EncodeToString(evidence.Validator.Address)},
{Key: "height", Value: fmt.Sprintf("%d", evidence.Height)},
},
})
}
// 4. λΈλ‘ μ’
λ£ μ²λ¦¬ (κΈ°μ‘΄ EndBlock λ‘μ§)
// 보μ λΆλ°°, μΈνλ μ΄μ
λ±
app.distributeRewards(req.DecidedLastCommit)
// κ²μ¦μ μΈνΈ μ
λ°μ΄νΈ (μ΅μ
)
var validatorUpdates []ValidatorUpdate
if app.shouldUpdateValidators() {
validatorUpdates = app.getValidatorUpdates()
}
// 5. μ± ν΄μ κ³μ°
appHash := app.state.ComputeHash()
return &ResponseFinalizeBlock{
Events: blockEvents,
TxResults: txResults,
ValidatorUpdates: validatorUpdates,
AppHash: appHash,
}
}κΈ°μ‘΄ λ²μ κ³Όμ λΉκ΅
v0.34 (Legacy):
BeginBlock(height=100)
β λΈλ‘ λ 벨 μ΄λ²€νΈ, 보μ λ±
DeliverTx(tx1)
β TX μ€ν
DeliverTx(tx2)
β TX μ€ν
...
EndBlock(height=100)
β κ²μ¦μ μ
λ°μ΄νΈ, 컨μΌμμ€ νλΌλ―Έν° λ³κ²½
Commit()
β AppHash λ°ν
v0.38+ (ABCI 2.0):
FinalizeBlock(height=100, txs=[tx1, tx2, ...])
β λͺ¨λ λ‘μ§μ ν λ²μ μ²λ¦¬
β AppHash ν¬ν¨νμ¬ λ°ν
Commit()
β μνλ₯Ό λμ€ν¬μ μꡬ μ μ₯ (AppHashλ FinalizeBlockμμ μ΄λ―Έ λ°ν)
μ₯μ
- μ±λ₯: gRPC νΈμΆ νμ κ°μ (n+2 β 2)
- μμμ±: λΈλ‘ μ€νμ΄ λ¨μΌ μμ μΌλ‘ μ²λ¦¬
- λ¨μμ±: μ ν리μΌμ΄μ μ½λκ° λ κ°κ²°
- μ μ°μ±: TX κ° μμ‘΄μ± μ²λ¦¬κ° λ μ¬μ
WALμ ν¬λμ 볡ꡬλ₯Ό μν ν΅μ¬ λ©μ»€λμ¦μ λλ€. λͺ¨λ 컨μΌμμ€ λ©μμ§κ° μ²λ¦¬λκΈ° μ μ λμ€ν¬μ κΈ°λ‘λ©λλ€.
consensus/wal.go
// WAL λ©μμ§ νμ
type WALMessage interface{}
type msgInfo struct {
Msg Message
PeerID p2p.ID
}
type timeoutInfo struct {
Duration time.Duration
Height int64
Round int32
Step RoundStepType
}
type EndHeightMessage struct {
Height int64
}
// WAL μΈν°νμ΄μ€
type WAL interface {
Write(WALMessage) error // λΉλκΈ° μ°κΈ°
WriteSync(WALMessage) error // λκΈ° μ°κΈ° (fsync)
FlushAndSync() error // λ²νΌ νλ¬μ
SearchForEndHeight(height int64, options *WALSearchOptions) (
*WALReader, bool, error) // λ³΅κ΅¬μ© κ²μ
}1. λ
Έλ ν¬λμ λ°μ
βββββββββββββββββββββββββββββββββββββββββββ
β Height 100, Round 0, Step Precommit β
β λ΄ Precommit ν¬ν μ μ‘ μ§ν ν¬λμ β
βββββββββββββββββββββββββββββββββββββββββββ
2. λ
Έλ μ¬μμ
βββββββββββββββββββββββββββββββββββββββββββ
β WAL νμΌ μ½κΈ°: β
β - msgInfo (Proposal) β
β - msgInfo (Prevote from peer1) β
β - msgInfo (λ΄ Prevote) β WriteSync β
β - msgInfo (Precommit from peer2) β
β - msgInfo (λ΄ Precommit) β WriteSync β
β β
β EndHeightMessage μμ = μμ§ μ»€λ° μλ¨ β
βββββββββββββββββββββββββββββββββββββββββββ
3. μν 볡ꡬ
βββββββββββββββββββββββββββββββββββββββββββ
β WAL λ©μμ§λ€μ μμλλ‘ μ¬μ€ν β
β β ν¬λμ μ§μ μνλ‘ λ³΅κ΅¬ β
β β 컨μΌμμ€ κ³μ μ§ν β
βββββββββββββββββββββββββββββββββββββββββββ
Write vs WriteSync μ°¨μ΄μ
-
Write: λ²νΌμ μ°κΈ° (λΉ λ¦, νΌμ΄ λ©μμ§μ©)
- λ©λͺ¨λ¦¬ λ²νΌμλ§ κΈ°λ‘
- λμ€μ μΌκ΄ fsync (λ°°μΉ μ²λ¦¬)
- μ±λ₯ μ΅μ νλ₯Ό μν΄ μ¬μ©
- Proposal, Vote μμ λ±μ μ¬μ©
-
WriteSync: λμ€ν¬μ fsync (λλ¦Ό, μμ μ ν¬νμ©)
- μ¦μ λμ€ν¬μ flush
- μ΄μ체μ μΊμλ₯Ό κ±°μΉμ§ μκ³ λ¬Όλ¦¬ λμ€ν¬κΉμ§ κΈ°λ‘
- ν¬λμ μμλ λ°μ΄ν° 보μ₯
- μμ μ ν¬νλ λ°λμ WriteSync μ¬μ©
- μ΄μ€ ν¬ν λ°©μ§λ₯Ό μν νμ λ©μ»€λμ¦
μ μμ μ ν¬νλ§ WriteSync?
μμ μ ν¬νλ λ€μ μμ±ν μ μκ³ , λ§μ½ λ€λ₯Έ ν¬νλ₯Ό μ¬μ μ‘νλ©΄ μ΄μ€ μλͺ (equivocation)μ΄ λ©λλ€. λ°λΌμ ν¬λμ μ μ λμ€ν¬μ κΈ°λ‘λμ΄μΌ ν©λλ€.
CometBFTλ κ°μ€ λΌμ΄λ λ‘λΉ(Weighted Round Robin) μκ³ λ¦¬μ¦μ μ¬μ©ν©λλ€.
types/validator_set.go
// ValidatorSetμ κ²μ¦μ μ§ν©κ³Ό μ μμ μ μ μ κ΄λ¦¬
type ValidatorSet struct {
Validators []*Validator
Proposer *Validator
totalVotingPower int64
}
type Validator struct {
Address Address
PubKey crypto.PubKey
VotingPower int64 // ν¬νλ ₯ (μ€ν
μ΄νΉλ)
ProposerPriority int64 // νμ¬ μ°μ μμ
}
// μ μμ μ μ : κ°μ€ λΌμ΄λ λ‘λΉ
func (vals *ValidatorSet) IncrementProposerPriority(times int32) {
for i := int32(0); i < times; i++ {
// 1. λͺ¨λ κ²μ¦μμ μ°μ μμμ VotingPower μΆκ°
for _, val := range vals.Validators {
val.ProposerPriority += val.VotingPower
}
// 2. κ°μ₯ λμ μ°μ μμ κ²μ¦μκ° μ μμ
vals.Proposer = vals.findProposer()
// 3. μ μμμ μ°μ μμμμ TotalVotingPower μ°¨κ°
vals.Proposer.ProposerPriority -= vals.TotalVotingPower()
}
}
func (vals *ValidatorSet) findProposer() *Validator {
var proposer *Validator
for _, val := range vals.Validators {
if proposer == nil || val.ProposerPriority > proposer.ProposerPriority {
proposer = val
}
}
return proposer
}3 κ²μ¦μ (ν¬νλ ₯: A=4, B=3, C=2, μ΄ν©=9)
μ΄κΈ° μν: Priority = [0, 0, 0]
Round 0:
+VotingPower: [4, 3, 2]
Proposer: A (μ΅κ³ μ°μ μμ)
-Total: [4-9, 3, 2] = [-5, 3, 2]
Round 1:
+VotingPower: [-5+4, 3+3, 2+2] = [-1, 6, 4]
Proposer: B (μ΅κ³ μ°μ μμ)
-Total: [-1, 6-9, 4] = [-1, -3, 4]
Round 2:
+VotingPower: [-1+4, -3+3, 4+2] = [3, 0, 6]
Proposer: C (μ΅κ³ μ°μ μμ)
-Total: [3, 0, 6-9] = [3, 0, -3]
Round 3:
+VotingPower: [3+4, 0+3, -3+2] = [7, 3, -1]
Proposer: A (μ΅κ³ μ°μ μμ)
...
β ν¬νλ ₯μ λΉλ‘νμ¬ μ μ κΈ°ν λΆλ°°!
β κ²°μ λ‘ μ : λͺ¨λ λ
Έλκ° κ°μ μ μμ κ³μ°
config/config.go - ConsensusConfig
type ConsensusConfig struct {
// Propose νμμμ (λΈλ‘ μ μ λκΈ°)
TimeoutPropose time.Duration // κΈ°λ³Έ: 3s
TimeoutProposeDelta time.Duration // κΈ°λ³Έ: 500ms (λΌμ΄λλΉ μ¦κ°)
// Prevote νμμμ (+2/3 Any ν λλ¨Έμ§ λκΈ°)
TimeoutPrevote time.Duration // κΈ°λ³Έ: 1s
TimeoutPrevoteDelta time.Duration // κΈ°λ³Έ: 500ms
// Precommit νμμμ (+2/3 Any ν λλ¨Έμ§ λκΈ°)
TimeoutPrecommit time.Duration // κΈ°λ³Έ: 1s
TimeoutPrecommitDelta time.Duration // κΈ°λ³Έ: 500ms
// Commit νμμμ (λ€μ λμ΄ μ λκΈ°)
TimeoutCommit time.Duration // κΈ°λ³Έ: 1s
}
// λΌμ΄λμ λ°λ₯Έ νμμμ κ³μ°
func (cfg *ConsensusConfig) Propose(round int32) time.Duration {
return cfg.TimeoutPropose +
cfg.TimeoutProposeDelta*time.Duration(round)
}| νμμμ | κΈ°λ³Έκ° | μ©λ | λ°μ ν λμ |
|---|---|---|---|
| TimeoutPropose | 3s | μ μ λκΈ° | nil Prevote ν λ€μ λ¨κ³ |
| TimeoutPrevote | 1s | +2/3 Any ν λλ¨Έμ§ λκΈ° | Precommit λ¨κ³λ‘ |
| TimeoutPrecommit | 1s | +2/3 Any ν λλ¨Έμ§ λκΈ° | λ€μ λΌμ΄λλ‘ |
| TimeoutCommit | 1s | μ»€λ° ν λ€μ λμ΄ μ λκΈ° | νΌμ΄ λκΈ°ν μκ° |
λΉ λ₯Έ λΈλ‘ μκ° (Low Latency)
- λͺ©ν: 1-3μ΄ λΈλ‘ μκ°
- μ€μ :
TimeoutPropose = 1s TimeoutPrevote = 500ms TimeoutPrecommit = 500ms TimeoutCommit = 500ms - μ μ© νκ²½: μ’μ λ€νΈμν¬, μ μ κ²μ¦μ (<50κ°)
λλ¦° μ ν리μΌμ΄μ (Heavy Computation)
- λͺ©ν: PrepareProposal/ProcessProposal μκ° ν보
- μ€μ :
TimeoutPropose = 10s β μ¦κ° TimeoutPrevote = 2s TimeoutPrecommit = 2s TimeoutCommit = 2s - μ μ© νκ²½: 볡μ‘ν TX κ²μ¦, λμ©λ λΈλ‘
λΆμμ ν λ€νΈμν¬ (High Latency)
- λͺ©ν: λ€νΈμν¬ μ§μ° 보μ
- μ€μ :
TimeoutPropose = 5s TimeoutProposeDelta = 1s β Delta μ¦κ° TimeoutPrevote = 2s TimeoutPrevoteDelta = 1s β Delta μ¦κ° TimeoutPrecommit = 2s TimeoutPrecommitDelta = 1s β Delta μ¦κ° TimeoutCommit = 3s - μ μ© νκ²½: κΈλ‘λ² λΆμ° λ€νΈμν¬, λμ λ μ΄ν΄μ
λ§μ κ²μ¦μ (Large Validator Set)
- λͺ©ν: ν¬ν μμ§ μκ° ν보
- μ€μ :
TimeoutPropose = 5s TimeoutPrevote = 3s β μ¦κ° TimeoutPrecommit = 3s β μ¦κ° TimeoutCommit = 2s - μ μ© νκ²½: 100+ κ²μ¦μ
μ€μ ν
- λͺ¨λν°λ§ νμ: κ° λ¨κ³λ³ μ€μ μμ μκ° μΈ‘μ
- μ μ§μ μ‘°μ : μμ λ³κ²½ ν ν¨κ³Ό κ΄μ°°
- λΌμ΄λ μ§ν νμΈ: λΌμ΄λκ° μμ£Ό μ¦κ°νλ©΄ νμμμ λΆμ‘± μ νΈ
- λ€νΈμν¬ λͺ¨λν°λ§: P2P λ©μμ§ μ ν μκ° μΈ‘μ
- Delta νμ©: λΌμ΄λκ° μ¦κ°ν μλ‘ μλμΌλ‘ νμμμ μ¦κ°
Evidenceλ κ²μ¦μμ μ μ± νμ (μ£Όλ‘ μ΄μ€ μλͺ ) μ¦κ±°μ λλ€.
types/evidence.go
// Evidence μΈν°νμ΄μ€
type Evidence interface {
Height() int64 // λ°μ λμ΄
Bytes() []byte // μ§λ ¬ν
Hash() []byte // ν΄μ
ValidateBasic() error // κΈ°λ³Έ κ²μ¦
String() string
}
// DuplicateVoteEvidence: κ°μ (H,R)μμ λ€λ₯Έ λΈλ‘μ ν¬ν
type DuplicateVoteEvidence struct {
VoteA *Vote
VoteB *Vote
TotalVotingPower int64
ValidatorPower int64
Timestamp time.Time
}
// LightClientAttackEvidence: λΌμ΄νΈ ν΄λΌμ΄μΈνΈ 곡격
type LightClientAttackEvidence struct {
ConflictingBlock *LightBlock
CommonHeight int64
}Height 100, Round 0:
κ²μ¦μ Xκ° λ κ°μ λ€λ₯Έ Prevote μ μ‘:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VoteA: Prevote for Block A β
β Height: 100, Round: 0 β
β BlockID: 0xAAA... β
β Signature: sig_A β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VoteB: Prevote for Block B (λ€λ₯Έ λΈλ‘!) β
β Height: 100, Round: 0 (κ°μ H,R!) β
β BlockID: 0xBBB... β
β Signature: sig_B β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
κ°μ§ λ
Έλ:
β DuplicateVoteEvidence μμ±
β Evidence Poolμ μΆκ°
β λ€μ λΈλ‘μ ν¬ν¨
β μ ν리μΌμ΄μ
μμ μ¬λμ± μ²λ¦¬
evidence/pool.go
// EvidencePoolμ κ²μ¦λ Evidenceλ₯Ό κ΄λ¦¬
type Pool struct {
evidenceStore dbm.DB
evidenceList *clist.CList // λκΈ° μ€μΈ Evidence
state sm.State
stateDB sm.Store
blockStore *store.BlockStore
}
// Evidence μΆκ°
func (evpool *Pool) AddEvidence(ev types.Evidence) error {
// 1. κΈ°λ³Έ κ²μ¦
if err := ev.ValidateBasic(); err != nil {
return err
}
// 2. μ΄λ―Έ μ‘΄μ¬νλμ§ νμΈ
if evpool.isPending(ev) {
return ErrEvidenceAlreadyStored
}
// 3. μ ν¨ κΈ°κ° νμΈ (MaxAgeNumBlocks, MaxAgeDuration)
if !evpool.isWithinMaxAge(ev) {
return ErrEvidenceTooOld
}
// 4. κ²μ¦μκ° μ€μ λ‘ ν΄λΉ λμ΄μ μ‘΄μ¬νλμ§ νμΈ
if err := evpool.verify(ev); err != nil {
return err
}
// 5. Poolμ μΆκ°
evpool.evidenceList.PushBack(ev)
return nil
}types/part_set.go
const (
BlockPartSizeBytes = 65536 // 64KB
)
type PartSet struct {
total uint32 // μ΄ μ‘°κ° μ
hash []byte // Merkle Root
mtx sync.Mutex
parts []*Part
partsBitArray *bits.BitArray // μ΄λ€ μ‘°κ°μ κ°μ§κ³ μλμ§
count uint32 // νμ¬ κ°μ§ μ‘°κ° μ
}
type Part struct {
Index uint32 // μ‘°κ° μΈλ±μ€
Bytes []byte // μ‘°κ° λ°μ΄ν°
Proof merkle.Proof // Merkle μ¦λͺ
}
// λΈλ‘μ μ‘°κ°μΌλ‘ λΆν
func NewPartSetFromData(data []byte, partSize uint32) *PartSet {
total := (uint32(len(data)) + partSize - 1) / partSize
parts := make([]*Part, total)
partsBytes := make([][]byte, total)
for i := uint32(0); i < total; i++ {
start := i * partSize
end := min(uint32(len(data)), (i+1)*partSize)
part := &Part{Index: i, Bytes: data[start:end]}
parts[i] = part
partsBytes[i] = part.Bytes
}
// Merkle Root λ° κ° μ‘°κ°μ Proof μμ±
root, proofs := merkle.ProofsFromByteSlices(partsBytes)
for i := 0; i < int(total); i++ {
parts[i].Proof = *proofs[i]
}
return &PartSet{total: total, hash: root, parts: parts}
}p2p/conn/connection.go
// MConnection: λ©ν°νλ μ€ μ°κ²°
type MConnection struct {
conn net.Conn
bufConnReader *bufio.Reader
bufConnWriter *bufio.Writer
channels []*Channel // μ±λλ³ ν
channelsIdx map[byte]*Channel
send chan struct{} // μ μ‘ μ νΈ
pong chan struct{} // Pong μλ΅
}
// κ° μ°κ²°λ§λ€ 2κ° κ³ λ£¨ν΄
func (c *MConnection) OnStart() error {
go c.sendRoutine() // μ‘μ κ³ λ£¨ν΄
go c.recvRoutine() // μμ κ³ λ£¨ν΄
return nil
}
// μ‘μ 루ν΄
func (c *MConnection) sendRoutine() {
for {
select {
case <-c.send:
// κ° μ±λμμ ν¨ν· κ°μ Έμμ μ μ‘
for _, ch := range c.channels {
if ch.canSend() {
pkt := ch.nextPacket()
c.sendPacket(pkt)
}
}
case <-c.quit:
return
}
}
}
// μμ 루ν΄
func (c *MConnection) recvRoutine() {
for {
pkt, err := c.recvPacket()
if err != nil {
c.stopForError(err)
return
}
// μ±λμ λ°λΌ λΆλ°°
ch := c.channelsIdx[pkt.ChannelID]
ch.recvPacket(pkt)
}
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MConnection β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Channel 0x20 (StateChannel) β
β ββ 컨μΌμμ€ μν λ©μμ§ (NewRoundStep, HasVote λ±) β
β β
β Channel 0x21 (DataChannel) β
β ββ λΈλ‘ μ‘°κ° (BlockPart) μ μ‘ β
β β
β Channel 0x22 (VoteChannel) β
β ββ ν¬ν λ©μμ§ (Prevote, Precommit) β
β β
β Channel 0x23 (VoteSetBitsChannel) β
β ββ ν¬ν λΉνΈλ§΅ (μ΄λ€ ν¬νλ₯Ό κ°μ§κ³ μλμ§) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
100κ° νΌμ΄ μ°κ²° μ:
β’ 200κ° κ³ λ£¨ν΄ (κ° μ°κ²°λΉ send/recv 2κ°)
β’ κ° μ±λμ λ
립μ μΌλ‘ λ³λ ¬ μ μ‘
mempool/clist_mempool.go
// CListMempool: κΈ°λ³Έ Mempool ꡬν (μ°κ²° 리μ€νΈ κΈ°λ°)
type CListMempool struct {
config *config.MempoolConfig
proxyAppConn proxy.AppConnMempool // ABCI μ°κ²°
txs *clist.CList // TX μ°κ²° 리μ€νΈ (μμ μ μ§)
txsMap map[TxKey]*clist.CElement // λΉ λ₯Έ κ²μμ©
txsBytes int64 // μ΄ TX λ°μ΄νΈ
cache TxCache // μ΄λ―Έ λ³Έ TX μΊμ
height int64 // νμ¬ λμ΄
notifiedTxsAvailable bool // TX μμ μλ¦Ό μ¬λΆ
}
// CheckTx: TX κ²μ¦ ν Mempoolμ μΆκ°
func (mem *CListMempool) CheckTx(tx types.Tx) (*abciclient.ReqRes, error) {
// 1. ν¬κΈ° 체ν¬
if len(tx) > mem.config.MaxTxBytes {
return nil, ErrTxTooLarge
}
// 2. Mempool μ©λ 체ν¬
if mem.txsBytes+int64(len(tx)) > mem.config.MaxTxsBytes {
return nil, ErrMempoolIsFull
}
// 3. μΊμ μ²΄ν¬ (μ΄λ―Έ λ³Έ TX?)
if !mem.cache.Push(tx) {
return nil, ErrTxInCache
}
// 4. ABCI CheckTx νΈμΆ
reqRes, err := mem.proxyAppConn.CheckTxAsync(
context.TODO(),
&abci.RequestCheckTx{Tx: tx},
)
return reqRes, err
}
// ReapMaxBytesMaxGas: λΈλ‘μ ν¬ν¨ν TX κ°μ Έμ€κΈ°
func (mem *CListMempool) ReapMaxBytesMaxGas(
maxBytes, maxGas int64,
) types.Txs {
mem.updateMtx.RLock()
defer mem.updateMtx.RUnlock()
var (
totalBytes int64
totalGas int64
txs []types.Tx
)
for e := mem.txs.Front(); e != nil; e = e.Next() {
memTx := e.Value.(*mempoolTx)
// ν¬κΈ°/κ°μ€ μ ν 체ν¬
if totalBytes+int64(len(memTx.tx)) > maxBytes {
break
}
if maxGas > 0 && totalGas+memTx.gasWanted > maxGas {
continue
}
totalBytes += int64(len(memTx.tx))
totalGas += memTx.gasWanted
txs = append(txs, memTx.tx)
}
return txs
}
// Update: λΈλ‘ μ€ν ν Mempool μ
λ°μ΄νΈ
func (mem *CListMempool) Update(
height int64,
txs types.Txs,
txResults []*abci.ExecTxResult,
preCheck PreCheckFunc,
postCheck PostCheckFunc,
) error {
mem.updateMtx.Lock()
defer mem.updateMtx.Unlock()
// μ€νλ TX μ κ±°
for i, tx := range txs {
if e, ok := mem.txsMap[TxKey(tx)]; ok {
mem.removeTx(tx, e, false)
}
}
mem.height = height
// λ¨μ TX μ¬κ²μ¦ (μ΅μ
)
if mem.config.Recheck {
mem.recheckTxs()
}
return nil
}mempool/v1/mempool.go
// TxPriorityλ TXμ μ°μ μμλ₯Ό λνλ
λλ€
type TxPriority struct {
Priority int64 // μ°μ μμ (λμμλ‘ λ¨Όμ )
Nonce uint64 // κ°μ λ°μ μμ TX μμ
Sender string // λ°μ μ μ£Όμ
}
// μ°μ μμ ν κΈ°λ° Mempool
type PriorityMempool struct {
txs *TxPriorityQueue // ν κΈ°λ° μ°μ μμ ν
// ...
}
// μ ν리μΌμ΄μ
μμ μ°μ μμ μ€μ (CheckTx μλ΅)
type ResponseCheckTx struct {
Code uint32
Data []byte
Log string
GasWanted int64
GasUsed int64
Priority int64 // β μ°μ μμ!
Sender string // β λ°μ μ
}| μ»΄ν¬λνΈ | νμΌ μμΉ | μν | ν΅μ¬ ν¬μΈνΈ |
|---|---|---|---|
| State | consensus/state.go | 컨μΌμμ€ μν λ¨Έμ | enterPropose, enterPrevote, enterPrecommit |
| VoteSet | types/vote_set.go | ν¬ν μμ§ λ° μ§κ³ | TwoThirdsMajority, HasTwoThirdsAny |
| WAL | consensus/wal.go | ν¬λμ 볡ꡬ | Write vs WriteSync |
| ABCI | abci/types/ | μ± μΈν°νμ΄μ€ | PrepareProposal, ProcessProposal, FinalizeBlock |
| Evidence | evidence/pool.go | μ μ± νμ κ°μ§ | DuplicateVoteEvidence |
| Mempool | mempool/ | TX λκΈ°μ΄ | CheckTx, ReapMaxBytesMaxGas, Update |
- InitChain - μ λ€μμ€ μ 1ν νΈμΆ. μ΄κΈ° κ²μ¦μ μ€μ .
- PrepareProposal (μ μμλ§) - λΈλ‘ μ μ μ . TX μ¬μ λ ¬/μΆκ°/μ κ±° κ°λ₯. λΉκ²°μ λ‘ μ OK.
- ProcessProposal (κ²μ¦μ) - μ μλ°μ λΈλ‘ κ²μ¦. Accept/Reject. κ²°μ λ‘ μ νμ!
- ExtendVote (κ²μ¦μ) - Precommit μ. Vote Extension μμ±. λΉκ²°μ λ‘ μ OK.
- VerifyVoteExtension (κ²μ¦μ) - λ€λ₯Έ κ²μ¦μμ Extension κ²μ¦. κ²°μ λ‘ μ νμ!
- FinalizeBlock - λΈλ‘ νμ μ. TX μ€ν. κ²°μ λ‘ μ νμ!
- Commit - μν μꡬ μ μ₯. AppHash λ°ν.
CometBFTλ Safetyλ₯Ό μ°μ ν©λλ€.
- Safety: 1/3 μ΄μμ μ μ± λ Έλκ° μμ΄λ ν¬ν¬ λΆκ°λ₯
- Liveness: 1/3 μ΄μμ μ μ± λ Έλκ° μ²΄μΈμ λ©μΆ μ μμ
ν¬ν¬λ³΄λ€ λ©μΆ€μ΄ λ«λ€λ μ² νμ λλ€.
κ²°μ λ‘ μ μ€ν νμ
// λμ μ: λΉκ²°μ λ‘ μ
func (app *MyApp) ProcessProposal(req *RequestProcessProposal) *ResponseProcessProposal {
// νμ¬ μκ° μ¬μ© - λ
Έλλ§λ€ λ€λ₯Ό μ μμ
if time.Now().Unix() > deadline {
return &ResponseProcessProposal{Status: ResponseProcessProposal_REJECT}
}
// ...
}
// μ’μ μ: κ²°μ λ‘ μ
func (app *MyApp) ProcessProposal(req *RequestProcessProposal) *ResponseProcessProposal {
// λΈλ‘ νμμ€ν¬ν μ¬μ© - λͺ¨λ λ
Έλκ° κ°μ
if req.Time.Unix() > deadline {
return &ResponseProcessProposal{Status: ResponseProcessProposal_REJECT}
}
// ...
}μν λ¨Έμ 볡μ
- κ°μ μ λ ₯(λΈλ‘)μ΄λ©΄ κ°μ μΆλ ₯(AppHash)
- λμ, μκ°, μΈλΆ API νΈμΆ κΈμ§
- λ§΅ μν μ ν€ μ λ ¬ νμ
λ‘κ·Έ λ 벨 μ€μ
# config.toml
[log]
level = "consensus:debug,state:info,*:error"μ μ©ν λ‘κ·Έ λ©μμ§
enterNewRound: λΌμ΄λ μ§ν μΆμ addVote: ν¬ν μμ§ μν©finalizeCommit: λΈλ‘ νμ νμΈapplyBlock: λΈλ‘ μ€ν μκ°
λ©νΈλ¦ λͺ¨λν°λ§
# Prometheus μλν¬μΈνΈ: http://localhost:26660/metrics
tendermint_consensus_height # νμ¬ λΈλ‘ λμ΄
tendermint_consensus_rounds # λΌμ΄λ νμ (λ§μΌλ©΄ λ¬Έμ )
tendermint_consensus_validators # κ²μ¦μ μ
tendermint_mempool_size # Mempool TX μ
tendermint_p2p_peers # μ°κ²°λ νΌμ΄ μ
Mempool μ€μ
[mempool]
size = 5000 # Mempool ν¬κΈ°
cache_size = 10000 # TX μΊμ
max_tx_bytes = 1048576 # 1MB per TX
max_txs_bytes = 1073741824 # 1GB totalν©μ μ€μ
[consensus]
timeout_propose = 3s
timeout_propose_delta = 500ms
timeout_prevote = 1s
timeout_prevote_delta = 500ms
timeout_precommit = 1s
timeout_precommit_delta = 500ms
timeout_commit = 1s
# λΉ λ₯Έ λΈλ‘ (λ€νΈμν¬ μ’μ λ)
# timeout_commit = 100msP2P μ€μ
[p2p]
max_num_inbound_peers = 40
max_num_outbound_peers = 10
send_rate = 5120000 # 5MB/s
recv_rate = 5120000 # 5MB/sλ¬Έμ : λΌμ΄λκ° κ³μ μ¦κ°
μμΈ: νμμμ λΆμ‘±, λ€νΈμν¬ μ§μ°, λλ¦° μ ν리μΌμ΄μ
ν΄κ²°:
1. νμμμ μ¦κ°
2. PrepareProposal/ProcessProposal μ΅μ ν
3. λ€νΈμν¬ μν μ κ²
λ¬Έμ : λΈλ‘ μκ°μ΄ λΆκ·μΉ
μμΈ: μ μμκ° μμ£Ό λ°λ, μΌλΆ κ²μ¦μ μ€νλΌμΈ
ν΄κ²°:
1. κ²μ¦μ λͺ¨λν°λ§ κ°ν
2. TimeoutCommit μ‘°μ
3. νΌμ΄ μ°κ²° μν νμΈ
λ¬Έμ : Mempoolμ΄ κ°λ μ°Έ
μμΈ: λΈλ‘ ν¬κΈ° λΆμ‘±, TX μ²λ¦¬ μλ λλ¦Ό
ν΄κ²°:
1. ConsensusParams.Block.MaxBytes μ¦κ°
2. μ ν리μΌμ΄μ
TX μ²λ¦¬ μ΅μ ν
3. Mempool ν¬κΈ° μ¦κ°
λ¬Έμ : μν λκΈ°ν μ€ν¨
μμΈ: λΈλ‘ μ μ₯μ μμ, AppHash λΆμΌμΉ
ν΄κ²°:
1. unsafe-reset-allλ‘ μ΄κΈ°ν (μ£Όμ!)
2. State sync μ¬μ©
3. μ€λ
μ·μμ 볡ꡬ
ν€ κ΄λ¦¬
- κ²μ¦μ ν€λ νλμ¨μ΄ 보μ λͺ¨λ(HSM) μ¬μ©
- priv_validator_key.json μνΈν μ μ₯
- μ κΈ°μ μΈ ν€ λ‘ν μ΄μ
λ€νΈμν¬ λ³΄μ
- μΌνΈλ¦¬ λ Έλ μν€ν μ² μ¬μ©
- κ²μ¦μλ κ³΅κ° IP λ ΈμΆ κΈμ§
- VPN λλ νλΌμ΄λΉ λ€νΈμν¬ μ¬μ©
λͺ¨λν°λ§
- μ΄μ€ μλͺ κ°μ§ μμ€ν
- ν¬ν μ°Έμ¬μ¨ λͺ¨λν°λ§
- λΈλ‘ μμ± μκ° μλ
μννΈ ν¬ν¬ (νλ°© νΈν)
// μ ν리μΌμ΄μ
λ²μ 체ν¬
func (app *MyApp) ProcessProposal(req *RequestProcessProposal) *ResponseProcessProposal {
if req.Height >= UPGRADE_HEIGHT {
// μ λ‘μ§
return app.processProposalV2(req)
}
// κΈ°μ‘΄ λ‘μ§
return app.processProposalV1(req)
}νλ ν¬ν¬ (λΉνΈν λ³κ²½)
- κ±°λ²λμ€ μ μ ν΅κ³Ό
- μ κ·Έλ μ΄λ λμ΄ ν©μ
- λͺ¨λ κ²μ¦μ λμ μ κ·Έλ μ΄λ
- λμ΄ λλ¬ μ μλ μ ν
- κ²°μ λ‘ μ μ€ν: κ°μ λΈλ‘ = κ°μ AppHash
- Safety First: ν¬ν¬λ³΄λ€λ λ©μΆ€
- 2/3 λ€μκ²°: λΉμν΄ νμ© νκ³
- μ¦μ μ΅μ’ μ±: μ»€λ° = λλ릴 μ μμ
- WAL νμ©: ν¬λμ 볡ꡬ 보μ₯
- Cosmos SDK: IBC, Staking, Governance λͺ¨λ
- IBC: ν¬λ‘μ€μ²΄μΈ ν΅μ νλ‘ν μ½
- CosmWasm: μ€λ§νΈ 컨νΈλνΈ νλ μμν¬
- Ignite CLI: λΈλ‘μ²΄μΈ κ°λ° λꡬ
λ§μ§λ§ μ λ°μ΄νΈ: 2025λ | κΈ°μ€ λ²μ : CometBFT v0.38+ / v1.0
μ΄ λ¬Έμλ Cosmos μνκ³ νμ΅μ μν΄ μμ±λμμ΅λλ€.