Skip to content

Commit 413ec3e

Browse files
committed
ETCM-167: RLP codec for all payloads.
1 parent cefb87b commit 413ec3e

File tree

4 files changed

+105
-17
lines changed

4 files changed

+105
-17
lines changed
Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,100 @@
11
package io.iohk.ethereum.network.discovery.codecs
22

3-
import io.iohk.scalanet.discovery.ethereum.Node
3+
import io.iohk.scalanet.discovery.crypto.{PublicKey, Signature}
4+
import io.iohk.scalanet.discovery.ethereum.{Node, EthereumNodeRecord}
45
import io.iohk.scalanet.discovery.ethereum.v4.Payload
5-
import io.iohk.ethereum.rlp.RLPCodec
6+
import io.iohk.scalanet.discovery.hash.Hash
7+
import io.iohk.ethereum.rlp
8+
import io.iohk.ethereum.rlp.{RLPCodec, RLPList}
9+
import io.iohk.ethereum.rlp.RLPCodec.Ops
610
import io.iohk.ethereum.rlp.RLPImplicits._
711
import io.iohk.ethereum.rlp.RLPImplicitConversions._
812
import io.iohk.ethereum.rlp.RLPImplicitDerivations._
913
import scodec.Codec
14+
import scodec.bits.{BitVector, ByteVector}
1015
import java.net.InetAddress
16+
import scala.collection.SortedMap
1117

1218
/** RLP codecs based on https://github.com/ethereum/devp2p/blob/master/discv4.md */
1319
object RLPCodecs {
1420

1521
implicit val policy: DerivationPolicy = DerivationPolicy(omitTrailingOptionals = true)
1622

1723
implicit val inetAddressRLPCodec: RLPCodec[InetAddress] =
18-
RLPCodec.instance[InetAddress](
19-
ip => ip.getAddress,
20-
// Implicit conversion to `Array[Byte]`
21-
{ case rlp => InetAddress.getByAddress(rlp) }
22-
)
24+
implicitly[RLPCodec[Array[Byte]]].xmap(InetAddress.getByAddress(_), _.getAddress)
25+
26+
implicit val bitVectorRLPCodec: RLPCodec[BitVector] =
27+
implicitly[RLPCodec[Array[Byte]]].xmap(BitVector(_), _.toByteArray)
28+
29+
implicit val byteVectorRLPCodec: RLPCodec[ByteVector] =
30+
implicitly[RLPCodec[Array[Byte]]].xmap(ByteVector(_), _.toArray)
31+
32+
implicit val hashRLPCodec: RLPCodec[Hash] =
33+
implicitly[RLPCodec[BitVector]].xmap(Hash(_), identity)
34+
35+
implicit val publicKeyRLPCodec: RLPCodec[PublicKey] =
36+
implicitly[RLPCodec[BitVector]].xmap(PublicKey(_), identity)
37+
38+
implicit val signatureRLPCodec: RLPCodec[Signature] =
39+
implicitly[RLPCodec[BitVector]].xmap(Signature(_), identity)
2340

2441
implicit val nodeAddressRLPCodec: RLPCodec[Node.Address] =
25-
RLPCodec[Node.Address](
26-
deriveLabelledGenericRLPListEncoder,
27-
deriveLabelledGenericRLPListDecoder
42+
deriveLabelledGenericRLPCodec
43+
44+
implicit val nodeRLPCodec: RLPCodec[Node] =
45+
deriveLabelledGenericRLPCodec
46+
47+
// https://github.com/ethereum/devp2p/blob/master/enr.md#rlp-encoding
48+
// `record = [signature, seq, k, v, ...]`
49+
implicit val enrRLPCodec: RLPCodec[EthereumNodeRecord] =
50+
RLPCodec.instance(
51+
{ case EthereumNodeRecord(signature, EthereumNodeRecord.Content(seq, attrs)) =>
52+
val kvs = attrs
53+
.map { case (k, v) =>
54+
RLPList(k.toArray, v.toArray)
55+
}
56+
.foldRight(RLPList())(_ ++ _)
57+
58+
signature.toByteArray :: seq :: kvs
59+
},
60+
{ case RLPList(signature, seq, kvs @ _*) =>
61+
val attrs = kvs
62+
.grouped(2)
63+
.collect { case Seq(k, v) =>
64+
rlp.decode[ByteVector](k) -> rlp.decode[ByteVector](v)
65+
}
66+
.toSeq
67+
68+
// TODO: Should have a constructor for key-value pairs.
69+
import EthereumNodeRecord.byteOrdering
70+
71+
EthereumNodeRecord(
72+
rlp.decode[Signature](signature),
73+
EthereumNodeRecord.Content(
74+
seq,
75+
SortedMap(attrs: _*)
76+
)
77+
)
78+
}
2879
)
2980

3081
implicit val pingRLPCodec: RLPCodec[Payload.Ping] =
31-
RLPCodec[Payload.Ping](
32-
deriveLabelledGenericRLPListEncoder,
33-
deriveLabelledGenericRLPListDecoder
34-
)
82+
deriveLabelledGenericRLPCodec
83+
84+
implicit val pongRLPCodec: RLPCodec[Payload.Pong] =
85+
deriveLabelledGenericRLPCodec
86+
87+
implicit val findNodeRLPCodec: RLPCodec[Payload.FindNode] =
88+
deriveLabelledGenericRLPCodec
89+
90+
implicit val neighborsRLPCodec: RLPCodec[Payload.Neighbors] =
91+
deriveLabelledGenericRLPCodec
92+
93+
implicit val enrRequestRLPCodec: RLPCodec[Payload.ENRRequest] =
94+
deriveLabelledGenericRLPCodec
95+
96+
implicit val enrResponseRLPCodec: RLPCodec[Payload.ENRResponse] =
97+
deriveLabelledGenericRLPCodec
3598

3699
implicit def payloadCodec: Codec[Payload] = ???
37100
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ object RLPImplicitDerivations {
109109
}
110110

111111
/** Encoder for a case class based on its labelled generic record representation. */
112-
implicit def deriveLabelledGenericRLPListEncoder[T, Rec](implicit
112+
implicit def deriveLabelledGenericRLPEncoder[T, Rec](implicit
113113
// Auto-derived by Shapeless.
114114
generic: LabelledGeneric.Aux[T, Rec],
115115
// Derived by `deriveOptionHListRLPListEncoder` and `deriveNonOptionHListRLPListEncoder`.
@@ -203,12 +203,20 @@ object RLPImplicitDerivations {
203203
}
204204

205205
/** Decoder for a case class based on its labelled generic record representation. */
206-
implicit def deriveLabelledGenericRLPListDecoder[T, Rec](implicit
206+
implicit def deriveLabelledGenericRLPDecoder[T, Rec](implicit
207207
// Auto-derived by Shapeless.
208208
generic: LabelledGeneric.Aux[T, Rec],
209209
// Derived by `deriveOptionHListRLPListDecoder` and `deriveNonOptionHListRLPListDecoder`.
210210
recDecoder: Lazy[RLPDecoder[Rec]]
211211
): RLPDecoder[T] = RLPDecoder { rlp =>
212212
generic.from(recDecoder.value.decode(rlp))
213213
}
214+
215+
/** Derive both encoder and decoder. */
216+
implicit def deriveLabelledGenericRLPCodec[T, Rec](implicit
217+
generic: LabelledGeneric.Aux[T, Rec],
218+
recEncoder: Lazy[RLPEncoder[Rec]],
219+
recDecoder: Lazy[RLPDecoder[Rec]]
220+
): RLPCodec[T] =
221+
RLPCodec[T](deriveLabelledGenericRLPEncoder, deriveLabelledGenericRLPDecoder)
214222
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.iohk.ethereum.rlp
33
import akka.util.ByteString
44
import io.iohk.ethereum.rlp.RLP._
55
import io.iohk.ethereum.utils.BigIntExtensionMethods.BigIntAsUnsigned
6+
import RLPCodec.Ops
67

78
object RLPImplicits {
89

@@ -108,6 +109,9 @@ object RLPImplicits {
108109
}
109110
}
110111

112+
implicit def listEncDec[T: RLPEncoder: RLPDecoder]: RLPCodec[List[T]] =
113+
seqEncDec[T]().xmap(_.toList, _.toSeq)
114+
111115
implicit def optionEnc[T](implicit enc: RLPEncoder[T]): RLPEncoder[Option[T]] = {
112116
case None => RLPList()
113117
case Some(value) => RLPList(enc.encode(value))

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ package object rlp {
1212

1313
case class RLPList(items: RLPEncodeable*) extends RLPEncodeable {
1414
def ::(item: RLPEncodeable): RLPList =
15-
RLPList((item :: items.toList): _*)
15+
RLPList((item +: items): _*)
16+
17+
def ++(other: RLPList): RLPList =
18+
RLPList((items ++ other.items): _*)
1619
}
1720

1821
case class RLPValue(bytes: Array[Byte]) extends RLPEncodeable {
@@ -97,5 +100,15 @@ package object rlp {
97100
override def encode(obj: T): RLPEncodeable = enc.encode(obj)
98101
override def decode(rlp: RLPEncodeable): T = dec.decode(rlp)
99102
}
103+
104+
implicit class Ops[A](val codec: RLPCodec[A]) extends AnyVal {
105+
106+
/** Given a codec for type A, make a coded for type B. */
107+
def xmap[B](f: A => B, g: B => A): RLPCodec[B] =
108+
new RLPEncoder[B] with RLPDecoder[B] {
109+
override def encode(obj: B): RLPEncodeable = codec.encode(g(obj))
110+
override def decode(rlp: RLPEncodeable): B = f(codec.decode(rlp))
111+
}
112+
}
100113
}
101114
}

0 commit comments

Comments
 (0)