Klaytn consunsus delay에 관한 질문

안녕하세요, 지난 2020년 3월 발생했던 Klaytn consunsus delay의 원인을 이해하고자 코드를 살펴보는 중에, 질문이 생겨 글 남깁니다.

당시에는 총 26개의 CN이 운영되었고, 특정 라운드에서 8개는 hashlock에 걸리고, 14개는 hashlock이 걸리지 않은 채로 round change가 발생하여 이후 15 라운드도 동안 합의가 이루어지지 않은 것으로 알고 있습니다.

그에 관련하여 코드를 읽어보았는데 아래와 같은 의문이 생겼습니다 .

해당 사건이 발생하기 위해서는,15 라운드 동안 lock이 걸린 8개의 CN들이 validator로 계속해서 선정되어야 합니다.
그 8개 block들은 handlePreprepare()에서 preprepare.Proposal.Hash() != c.current.GetLockedHash() 이므로, c.sendNextRoundChange를 호출하게 됩니다.

즉, 8개의 block이 roundchange를 호출하는데, f + 1 = 8입니다. 따라서, 나머지 14개의 node들은 8개의 round change msg를 받게 됩니다.

8개의 Roundchange msg를 받은 CN들이 다시 다른 validator들에게 roundchange msg를 broadcast하게 되고, 결국에 빠른 시간내에 대부분의 노드들이 2F+1의 round change msg를 받아 다음 라운드로 넘어갈 거라고 생각했습니다.

저의 이해대로라면, 해당 사건은 각 round마다 설정된 timer에 큰 영향을 받지 않고 빠르게 끝나야한다고 생각합니다.

제가 잘못 이해한 부분이 있다면 수정부탁드리겠습니다.

감사합니다 :slight_smile:

안녕하세요.
질문 주셔서 감사합니다.
당시에 재직 중이지는 않았지만, 추석 연휴로 답변이 늦어져서 제가 답변 남깁니다.
당시에 총 26개의 CN이 운영되었고, 중 매 블록마다 랜덤하게 선정된 22개의 CN이 합의를 이루면서 동작 되고 있었습니다.
delay 상황에서는 8개 CN은 hashlock 된 상태로, 14개 CN은 hashlock 되지 않은 상태로 각각 round change가 발생하였습니다.
여기까지는 문제가 되지 않을 수 있는데요.
같은 block 생성시 round마다 22개의 Committee 구조가 변경되지 않았고(이는 v1.7.0에서 개선 된 것으로 알고 있습니다.)
22개의 CN 중 hashlock이 되지 않은 14개 CN 중 하나가 Proposer로 선정되다보니,
lock 된 8개의 CN들이 합의를 거부하였습니다.
15라운드 동안 낮은 확률로 위의 상황이 지속이 되어서 급하게 CN을 재시작하게 되었습니다.

코드상으로 확인해보면, 말씀 주신 sendNextRoundChange → sendRoundChange → cactchUpRound → newRoundChangeTimer에서
timer가 완료되기를 기다리는 함수가 있어서, timer에 영향을 받을 것으로 보이네요.

제가 다뤘던 이슈가 아니여서 잘못 이해한 부분이 있을 수 있습니다.
더 질문이 있으시면 태깅 후 이어서 주시면 감사하겠습니다.

1 Like

답변 감사합니다. 이해하는데 많은 도움이 되었습니다.
추가적인 질문이 생겨 글 남깁니다.

  1. 현재는 같은 블럭 생성시에도 Proposer 및 Committee를 뽑을 때, Proposer 리스트를 순회하며 새롭게 선정하고, 그에 따라 Validator set도 바뀐다고 생각해도 될까요? 즉, startNewRound가 실행되어 새롭게 committee를 구성하는 방식과 같은 방식으로, 같은 블럭 생성 시에도 Committee가 선정된다고 생각하는게 맞을까요?
  2. 제가 이해한 바로는 8개의 lock에 걸린 CN들은 preprepare.Proposal.Hash() != c.current.GetLockedHash() 조건에 의해 catchUpRound가 호출됩니다. 그리고 lock에 걸리지 않은 CN들은 (f+1)개의 roundChangeMessage를 수신했으므로, sendRoundChange → catchUpRound가 호출됩니다. catchUpRound에서 updateRoundState를 호출하여, 해당 CN core의 round를 우선 다음 라운드로 update하는 것 같습니다. 그리고, 새로운 라운드가 시작되었으니 새로운 roundchangetimer를 재시작하고, startNewRound의 조건이 만족할때까지 (즉, 2f+1개의 roundChangeMessage를 수신할때까지) 기다렸다가 바로 새로운 Proposer 선정으로 이어지게 될 것 같습니다.

모든 CN들이 roundchangemessage를 서로에게 보내니 곧 startNewRound 상태로 진입하게 됩니다. 그러면 catupRound에서 시작되었던 timer를 끄고, backend의 view를 변경하며, newRoundChangeTimer를 호출하고 새로운 Proposer을 선정하게 됩니다.

제가 의문이 드는 점은 해당 사건은 Timeout이 발생할때까지 모든 CN들이 대기하게 되는 상황이 지속적으로 발생하여 지연이 길어진 것으로 알고 있습니다. 하지만 제가 이해한 바로는, Timeout 시간까지 도달하기 전에 이미 새로운 Proposer가 계속해서 선정되고 빠르게 roundchange가 계속해서 발생할 것 같습니다.

newRoundChangeTimer에서 timer가 완료되기를 기다리는 부분이 어디인지 잘 모르겠습니다. 제가 이해한 바로는, startNewRound가 시작하면 catchUpRound에서 설정되었던 timer가 멈추고 새로운 타이머가 다시 동작한다고 생각했습니다.

제가 잘못 이해한 부분이 있다면 답변 부탁드리겠습니다 :slight_smile:

답변이 늦었네요.

  1. Committee를 선정하는 로직이 이전 block의 hash를 seed로 이용하여 선정하는 방식이었습니다.
    v.1.7.0 이후부터는 이전 block의 hash뿐 아니라 round 값도 함께 seed로 사용하게 변경되는 것으로 알고 있습니다.
    같은 Block이더라도 round가 달라지면 Committee도 달라지겠네요.
  2. newRoundChangeTimer 내부 코드 중에
	round := c.current.Round().Uint64()
	if round > 0 {
		timeout += time.Duration(math.Pow(2, float64(round))) * time.Second
	}

	current := c.current
	proposer := c.valSet.GetProposer()

	c.roundChangeTimer.Store(time.AfterFunc(timeout, func() {

이런 로직이 있는데요.
time.AfterFunc(timeout, func()) 함수에서
여기서 timeout이 만료될 떄 까지 대기할 것으로 판단했습니다.

역시 잘못 파악한 부분이 있을 수도 있어서, 팀에도 한번 확인해보겠습니다.

추가적인 질문이 있을시 태깅 부탁드립니다.
감사합니다 :slight_smile:

안녕하세요, 답변 감사합니다.

저도 저 코드를 확인해봤었는데요, 제가 잘못 이해한 부분이 있을수도 있어 다시 질문남깁니다.

newRoundChangeTimer안에서 timer를 다시 설정하면, 이후에 어떤 응답이 들어와 다음 라운드로 넘어가야하는 상황이 되어도 timer가 끝날때까지 계속 기다리게 되는 걸까요?

감사합니다.

@11150 답변이 늦어 죄송합니다. 지속적으로 Klaytn에 관심을 가져주셔서 감사합니다.
마지막 댓글의 질문보단 두번째 질문에 재 답변을 드리는게 이해하기 좋을것 같아 해당 내용 답변드립니다.

  1. 현재는 매 블록마다 Committee 가 변경되고 round change 시에는 Commitee 중 2개 노드 (proposer, next proposer)만 변경됩니다. v1.7.0 에 있는 protocol upgrade 내용이 적용되면, 매 라운드마다 Commitee도 랜덤하게 변경될 예정입니다.

  2. 아래의 코드를 보고 질문을 주신것 같습니다. 아래 코드의 else if문을 잘 보면 c.waitingForRoundChangetrue 인 조건이 있습니다. 이 값은 catchUpRound를 실행하며 true 로 설정되는 값입니다. 따라서, f+1 개의 roundChangeMessage를 수신하였더라도 해당 시점의 proposal을 정상적으로 수용했던 노드들은 이 로직에 의해 roundChangeMessgae를 추가적으로 gossiping하지 않습니다. 그러인하여 Timer가 모두 종료되어 자체적으로 timeoutEvent를 처리한 후에야 roundChangeMessage가 추가적으로 gossiping될 수 있습니다.

2 Likes

확인이 늦었네요. 답변 감사합니다.

그러면 현재의 proposal을 수용할 수 없는 상태 (즉, 이미 round change message)를 보낸 상태이면서 f+1개의 메시지를 받으면 다시 round change message를 보내는 것 같습니다.
그리고, f+1개의 조건을 확인하는 이유는 빠른 round 전환을 위한 것으로 알고 있습니다.

제가 궁금한 점은 c.waitingForRoundChange == true 조건을 확인하는 이유입니다.
이미 round change message를 보내고 나서, else if문에 걸리게 되면 다시 round change를 보내게 되는데,
동일한 round change message를 다시 broadcast하는 것이 어떤 의미가 있는 것인지 궁금합니다.