Skip to content

Commit b423bf0

Browse files
committed
ETCM-167: Testing the RLP codecs on the EIP8 test vectors.
1 parent 123160a commit b423bf0

File tree

4 files changed

+126
-96
lines changed

4 files changed

+126
-96
lines changed

src/main/scala/io/iohk/ethereum/network/discovery/codecs/RLPCodecs.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,16 @@ object RLPCodecs {
115115
(bits: BitVector) => {
116116
bits.consumeThen(8)(
117117
err => Attempt.failure(Err(err)),
118-
(packetType, rest) => {
118+
(packetType, packetData) => {
119119

120120
val tryPayload: Try[Payload] = Try {
121121
packetType.toByte() match {
122-
case 0x01 => rlp.decode[Payload.Ping](rest)
123-
case 0x02 => rlp.decode[Payload.Pong](rest)
124-
case 0x03 => rlp.decode[Payload.FindNode](rest)
125-
case 0x04 => rlp.decode[Payload.Neighbors](rest)
126-
case 0x05 => rlp.decode[Payload.ENRRequest](rest)
127-
case 0x06 => rlp.decode[Payload.ENRResponse](rest)
122+
case 0x01 => rlp.decode[Payload.Ping](packetData)
123+
case 0x02 => rlp.decode[Payload.Pong](packetData)
124+
case 0x03 => rlp.decode[Payload.FindNode](packetData)
125+
case 0x04 => rlp.decode[Payload.Neighbors](packetData)
126+
case 0x05 => rlp.decode[Payload.ENRRequest](packetData)
127+
case 0x06 => rlp.decode[Payload.ENRResponse](packetData)
128128
case other => throw new RuntimeException(s"Unknown packet type: ${other}")
129129
}
130130
}

src/main/scala/io/iohk/ethereum/rlp/RLPImplicitDerivations.scala

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.iohk.ethereum.rlp
33
import shapeless.{HList, HNil, Lazy, ::, LabelledGeneric, <:!<, Witness}
44
import shapeless.labelled.{FieldType, field}
55
import scala.util.control.NonFatal
6+
import scala.reflect.ClassTag
67

78
/** Automatically derive RLP codecs for case classes. */
89
object RLPImplicitDerivations {
@@ -40,8 +41,10 @@ object RLPImplicitDerivations {
4041

4142
override def decode(rlp: RLPEncodeable): T =
4243
rlp match {
43-
case list: RLPList => decodeList(list.items.toList)._1
44-
case _ => throw new RuntimeException("Expected to decode an RLPList.")
44+
case list: RLPList =>
45+
decodeList(list.items.toList)._1
46+
case other =>
47+
throw new RuntimeException("Expected to decode an RLPList.")
4548
}
4649
}
4750
object RLPListDecoder {
@@ -166,7 +169,7 @@ object RLPImplicitDerivations {
166169
}
167170
} catch {
168171
case NonFatal(ex) =>
169-
throw new RuntimeException(s"Cannot decode optional '$fieldName' from RLP value: $ex")
172+
throw new RuntimeException(s"Cannot decode optional '$fieldName' from RLP value: ${ex.getMessage}")
170173
}
171174

172175
val head: FieldType[K, H] = field[K](value)
@@ -194,7 +197,7 @@ object RLPImplicitDerivations {
194197
hDecoder.value.decode(rlps.head)
195198
} catch {
196199
case NonFatal(ex) =>
197-
throw new RuntimeException(s"Cannot decode '$fieldName' from RLP value: $ex")
200+
throw new RuntimeException(s"Cannot decode '$fieldName' from RLP value: ${ex.getMessage}")
198201
}
199202
val head: FieldType[K, H] = field[K](value)
200203
val (tail, tInfos) = tDecoder.value.decodeList(rlps.tail)
@@ -207,16 +210,23 @@ object RLPImplicitDerivations {
207210
// Auto-derived by Shapeless.
208211
generic: LabelledGeneric.Aux[T, Rec],
209212
// Derived by `deriveOptionHListRLPListDecoder` and `deriveNonOptionHListRLPListDecoder`.
210-
recDecoder: Lazy[RLPDecoder[Rec]]
213+
recDecoder: Lazy[RLPDecoder[Rec]],
214+
ct: ClassTag[T]
211215
): RLPDecoder[T] = RLPDecoder { rlp =>
212-
generic.from(recDecoder.value.decode(rlp))
216+
try {
217+
generic.from(recDecoder.value.decode(rlp))
218+
} catch {
219+
case NonFatal(ex) =>
220+
throw new RuntimeException(s"Could not decode ${ct.runtimeClass.getSimpleName}: ${ex.getMessage}")
221+
}
213222
}
214223

215224
/** Derive both encoder and decoder. */
216225
implicit def deriveLabelledGenericRLPCodec[T, Rec](implicit
217226
generic: LabelledGeneric.Aux[T, Rec],
218227
recEncoder: Lazy[RLPEncoder[Rec]],
219-
recDecoder: Lazy[RLPDecoder[Rec]]
228+
recDecoder: Lazy[RLPDecoder[Rec]],
229+
ct: ClassTag[T]
220230
): RLPCodec[T] =
221231
RLPCodec[T](deriveLabelledGenericRLPEncoder, deriveLabelledGenericRLPDecoder)
222232
}

src/main/scala/io/iohk/ethereum/rlp/package.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ package object rlp {
9292

9393
override def decode(rlp: RLPEncodeable): T =
9494
if (dec.isDefinedAt(rlp)) dec(rlp)
95-
else throw new RuntimeException(s"Cannot decode ${ct.getClass.getSimpleName} from RLP.")
95+
else throw new RuntimeException(s"Cannot decode ${ct.runtimeClass.getSimpleName} from RLP.")
9696
}
9797

9898
def apply[T](enc: RLPEncoder[T], dec: RLPDecoder[T]): RLPCodec[T] =

src/test/scala/io/iohk/ethereum/network/discovery/codecs/RLPCodecsSpec.scala

Lines changed: 101 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import scodec.Codec
1111
import java.net.InetAddress
1212
import io.iohk.ethereum.rlp.RLPValue
1313
import io.iohk.ethereum.rlp.RLPDecoder
14+
import org.scalatest.compatible.Assertion
1415

1516
class RLPCodecsSpec extends AnyFlatSpec {
1617

@@ -19,99 +20,118 @@ class RLPCodecsSpec extends AnyFlatSpec {
1920
implicit val packetCodec: Codec[Packet] =
2021
Packet.packetCodec(allowDecodeOverMaxPacketSize = false)
2122

22-
implicit def payloadCodec: Codec[Payload] = ???
2323
implicit def sigalg: SigAlg = ???
2424

2525
val localhost = InetAddress.getByName("127.0.0.1")
2626

2727
behavior of "RLPCodecs"
2828

2929
// Test vectors from https://github.com/ethereum/EIPs/blob/b883968936d83aa1c458d48fdc81bc59e8095da5/EIPS/eip-8.md#rlpx-discovery-protocol
30-
trait EIP8Fixture {
31-
def data: String
32-
33-
val privateKey = PrivateKey(
34-
BitVector.fromHex("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291").get
30+
case class EIP8TestVector(
31+
description: String,
32+
data: String,
33+
test: Payload => Assertion
34+
)
35+
val EIP8TestVectors = Vector(
36+
EIP8TestVector(
37+
"ping packet with version 4, additional list elements",
38+
"""
39+
|e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663a
40+
|aa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a
41+
|4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000
42+
|000000000000000000018208ae820d058443b9a3550102
43+
""".stripMargin,
44+
payload => {
45+
payload shouldBe a[Payload.Ping]
46+
payload.asInstanceOf[Payload.Ping].version shouldBe 4
47+
}
48+
),
49+
EIP8TestVector(
50+
"ping packet with version 555, additional list elements and additional random data",
51+
"""
52+
|577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e
53+
|7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3
54+
|d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef
55+
|12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203
56+
|040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba7602
57+
|3fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee191
58+
|7084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c7
59+
|6d922dc3
60+
""".stripMargin,
61+
payload => {
62+
payload shouldBe a[Payload.Ping]
63+
payload.asInstanceOf[Payload.Ping].version shouldBe 555
64+
}
65+
),
66+
EIP8TestVector(
67+
"pong packet with additional list elements and additional random data",
68+
"""
69+
|09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b206
70+
|9869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2
71+
|216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208
72+
|ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9
73+
|a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f0555
74+
|42124e
75+
""".stripMargin,
76+
_ shouldBe a[Payload.Pong]
77+
),
78+
EIP8TestVector(
79+
"findnode packet with additional list elements and additional random data",
80+
"""
81+
|c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91
82+
|831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe
83+
|04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d
84+
|115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be0081290476
85+
|7bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260a
86+
|dd7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396
87+
""".stripMargin,
88+
_ shouldBe a[Payload.FindNode]
89+
),
90+
EIP8TestVector(
91+
"neighbours packet with additional list elements and additional random data",
92+
"""
93+
|c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8
94+
|d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1
95+
|b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db84031
96+
|55e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa8291
97+
|15d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422
98+
|cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e82
99+
|9f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05
100+
|820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2
101+
|d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d3
102+
|13198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811
103+
|197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73
104+
|8443b9a355010203b525a138aa34383fec3d2719a0
105+
""".stripMargin,
106+
_ shouldBe a[Payload.Neighbors]
35107
)
36-
37-
val bits = BitVector.fromHex(data).get
38-
val packet = Codec[Packet].decodeValue(bits).require
39-
val (payload, publicKey) = Packet.unpack(packet).require
40-
41-
sigalg.toPublicKey(privateKey) shouldBe publicKey
42-
}
43-
44-
ignore should "decode a ping packet with version 4, additional list elements" in new EIP8Fixture {
45-
override val data = """
46-
|e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663a
47-
|aa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a
48-
|4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000
49-
|000000000000000000018208ae820d058443b9a3550102
50-
""".stripMargin
51-
52-
payload shouldBe a[Payload.Ping]
53-
payload.asInstanceOf[Payload.Ping].version shouldBe 4
54-
}
55-
56-
ignore should "decode a ping packet with version 555, additional list elements and additional random data" in new EIP8Fixture {
57-
override val data = """
58-
|577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e
59-
|7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3
60-
|d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef
61-
|12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203
62-
|040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba7602
63-
|3fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee191
64-
|7084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c7
65-
|6d922dc3
66-
""".stripMargin
67-
68-
payload shouldBe a[Payload.Ping]
69-
payload.asInstanceOf[Payload.Ping].version shouldBe 555
108+
)
109+
110+
// Test the RLP decoders in isolation, without crypto.
111+
EIP8TestVectors.foreach { case EIP8TestVector(description, data, test) =>
112+
it should s"decode/encode a ${description}" in {
113+
val bits = BitVector.fromHex(data).get
114+
val packet = Codec[Packet].decodeValue(bits).require
115+
val payload = Codec[Payload].decodeValue(packet.data).require
116+
test(payload)
117+
}
70118
}
71119

72-
ignore should "decode a pong packet with additional list elements and additional random data" in new EIP8Fixture {
73-
override val data = """
74-
|09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b206
75-
|9869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2
76-
|216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208
77-
|ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9
78-
|a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f0555
79-
|42124e
80-
""".stripMargin
81-
82-
payload shouldBe a[Payload.Pong]
83-
}
120+
// Test the whole Packet unpack function, with RLP and crypto.
121+
EIP8TestVectors.foreach { case EIP8TestVector(description, data, test) =>
122+
ignore should s"unpack a ${description}" in {
123+
val privateKey = PrivateKey(
124+
BitVector.fromHex("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291").get
125+
)
126+
val bits = BitVector.fromHex(data).get
127+
val packet = Codec[Packet].decodeValue(bits).require
128+
val (payload, publicKey) = Packet.unpack(packet).require
84129

85-
ignore should "decode a findnode packet with additional list elements and additional random data" in new EIP8Fixture {
86-
override val data = """
87-
|c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91
88-
|831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe
89-
|04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d
90-
|115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be0081290476
91-
|7bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260a
92-
|dd7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396
93-
""".stripMargin
94-
95-
payload shouldBe a[Payload.FindNode]
96-
}
130+
sigalg.toPublicKey(privateKey) shouldBe publicKey
97131

98-
ignore should "decode a neighbours packet with additional list elements and additional random data" in new EIP8Fixture {
99-
override val data = """
100-
|c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8
101-
|d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1
102-
|b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db84031
103-
|55e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa8291
104-
|15d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422
105-
|cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e82
106-
|9f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05
107-
|820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2
108-
|d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d3
109-
|13198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811
110-
|197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73
111-
|8443b9a355010203b525a138aa34383fec3d2719a0
112-
""".stripMargin
113-
114-
payload shouldBe a[Payload.Neighbors]
132+
// Test again to make sure `unpack` works correctly.
133+
test(payload)
134+
}
115135
}
116136

117137
ignore should "decode an ENR" in {

0 commit comments

Comments
 (0)