Skip to content

Commit 99dbbec

Browse files
[ETCM-844] Validate peer fork id
1 parent 66c1920 commit 99dbbec

File tree

3 files changed

+63
-5
lines changed

3 files changed

+63
-5
lines changed

src/main/scala/io/iohk/ethereum/forkid/ForkIdValidator.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ case object Connect extends ForkIdValidationResult
2222
case object ErrRemoteStale extends ForkIdValidationResult
2323
case object ErrLocalIncompatibleOrStale extends ForkIdValidationResult
2424

25+
import cats.effect._
26+
2527
object ForkIdValidator {
2628

27-
implicit val unsafeLogger: SelfAwareStructuredLogger[Task] = Slf4jLogger.getLogger[Task]
29+
implicit val taskLogger: SelfAwareStructuredLogger[Task] = Slf4jLogger.getLogger[Task]
30+
implicit val syncIoLogger: SelfAwareStructuredLogger[SyncIO] = Slf4jLogger.getLogger[SyncIO]
2831

2932
val maxUInt64: BigInt = (BigInt(0x7fffffffffffffffL) << 1) + 1 // scalastyle:ignore magic.number
3033

src/main/scala/io/iohk/ethereum/network/handshaker/EthNodeStatus64ExchangeState.scala

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package io.iohk.ethereum.network.handshaker
22

3+
import cats.effect._
4+
5+
import io.iohk.ethereum.forkid.Connect
36
import io.iohk.ethereum.forkid.ForkId
7+
import io.iohk.ethereum.forkid.ForkIdValidator
48
import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo
59
import io.iohk.ethereum.network.EtcPeerManagerActor.RemoteStatus
610
import io.iohk.ethereum.network.p2p.Message
711
import io.iohk.ethereum.network.p2p.MessageSerializable
812
import io.iohk.ethereum.network.p2p.messages.Capability
913
import io.iohk.ethereum.network.p2p.messages.ETH64
14+
import io.iohk.ethereum.network.p2p.messages.WireProtocol.Disconnect
1015

1116
case class EthNodeStatus64ExchangeState(
1217
handshakerConfiguration: EtcHandshakerConfiguration
@@ -15,8 +20,18 @@ case class EthNodeStatus64ExchangeState(
1520
import handshakerConfiguration._
1621

1722
def applyResponseMessage: PartialFunction[Message, HandshakerState[PeerInfo]] = { case status: ETH64.Status =>
18-
// TODO: validate fork id of the remote peer
19-
applyRemoteStatusMessage(RemoteStatus(status))
23+
import ForkIdValidator.syncIoLogger
24+
(for {
25+
validationResult <-
26+
ForkIdValidator.validatePeer[SyncIO](blockchainReader.genesisHeader.hash, blockchainConfig)(
27+
blockchainReader.getBestBlockNumber(),
28+
status.forkId
29+
)
30+
_ = log.info("Fork id validation result was: {}", validationResult)
31+
} yield validationResult match {
32+
case Connect => applyRemoteStatusMessage(RemoteStatus(status))
33+
case _ => DisconnectedState[PeerInfo](Disconnect.Reasons.UselessPeer)
34+
}).unsafeRunSync()
2035
}
2136

2237
override protected def createStatusMsg(): MessageSerializable = {

src/test/scala/io/iohk/ethereum/network/handshaker/EtcHandshakerSpec.scala

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ class EtcHandshakerSpec extends AnyFlatSpec with Matchers {
190190
}
191191
}
192192

193-
it should "send status with fork id when peer supports ETH64" in new LocalPeerETH64Setup with RemotePeerETH64Setup {
193+
it should "connect correctly after validating fork id when peer supports ETH64" in new LocalPeerETH64Setup
194+
with RemotePeerETH64Setup {
194195

195196
val newChainWeight = ChainWeight.zero.increase(genesisBlock.header).increase(firstBlock.header)
196197

@@ -223,6 +224,45 @@ class EtcHandshakerSpec extends AnyFlatSpec with Matchers {
223224
}
224225
}
225226

227+
it should "disconnect from a useless peer after validating fork id when peer supports ETH64" in new LocalPeerETH64Setup
228+
with RemotePeerETH64Setup {
229+
230+
val newChainWeight = ChainWeight.zero.increase(genesisBlock.header).increase(firstBlock.header)
231+
232+
blockchainWriter.save(firstBlock, Nil, newChainWeight, saveAsBestBlock = true)
233+
234+
val newLocalStatusMsg =
235+
localStatusMsg
236+
.copy(
237+
bestHash = firstBlock.header.hash,
238+
totalDifficulty = newChainWeight.totalDifficulty,
239+
forkId = ForkId(0xfc64ec04L, Some(1150000))
240+
)
241+
242+
initHandshakerWithoutResolver.nextMessage.map(_.messageToSend) shouldBe Right(localHello: HelloEnc)
243+
244+
val newRemoteStatusMsg =
245+
remoteStatusMsg
246+
.copy(
247+
forkId = ForkId(1, None) // ForkId that is incompatible with our chain
248+
)
249+
250+
val handshakerAfterHelloOpt = initHandshakerWithoutResolver.applyMessage(remoteHello)
251+
assert(handshakerAfterHelloOpt.isDefined)
252+
253+
handshakerAfterHelloOpt.get.nextMessage.map(_.messageToSend.underlyingMsg) shouldBe Right(newLocalStatusMsg)
254+
255+
val handshakerAfterStatusOpt = handshakerAfterHelloOpt.get.applyMessage(newRemoteStatusMsg)
256+
assert(handshakerAfterStatusOpt.isDefined)
257+
258+
handshakerAfterStatusOpt.get.nextMessage match {
259+
case Left(HandshakeFailure(Disconnect.Reasons.UselessPeer)) => succeed
260+
case other =>
261+
fail(s"Invalid handshaker state: $other")
262+
}
263+
264+
}
265+
226266
it should "fail if a timeout happened during hello exchange" in new TestSetup {
227267
val handshakerAfterTimeout = initHandshakerWithoutResolver.processTimeout
228268
handshakerAfterTimeout.nextMessage.map(_.messageToSend) shouldBe Left(
@@ -447,7 +487,7 @@ class EtcHandshakerSpec extends AnyFlatSpec with Matchers {
447487
totalDifficulty = 0,
448488
bestHash = genesisBlock.header.hash,
449489
genesisHash = genesisBlock.header.hash,
450-
forkId = ForkId(2L, Some(3L))
490+
forkId = ForkId(0xfc64ec04L, Some(1150000))
451491
)
452492

453493
val remoteStatus: RemoteStatus = RemoteStatus(remoteStatusMsg)

0 commit comments

Comments
 (0)