Skip to content

Commit d3e4a43

Browse files
committed
ETCM-167: Add FieldInfo to know we're in a trailing position.
1 parent d2e7f3c commit d3e4a43

File tree

2 files changed

+46
-24
lines changed

2 files changed

+46
-24
lines changed

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

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,64 @@ import shapeless.labelled.FieldType
66
/** Automatically derive RLP codecs for case classes. */
77
object RLPImplicitDerivations {
88

9+
/** Support introspecting on what happened during encoding the tail. */
10+
case class FieldInfo(isOptional: Boolean)
11+
912
/** Case classes get encoded as lists, not values,
1013
* which is an extra piece of information we want
1114
* to be able to rely on during derivation.
1215
*/
1316
trait RLPListEncoder[T] extends RLPEncoder[T] {
14-
override def encode(obj: T): RLPList
17+
def encodeList(obj: T): (RLPList, List[FieldInfo])
18+
19+
override def encode(obj: T): RLPEncodeable =
20+
encodeList(obj)._1
1521
}
1622
object RLPListEncoder {
17-
def apply[T](f: T => RLPList): RLPListEncoder[T] =
23+
def apply[T](f: T => (RLPList, List[FieldInfo])): RLPListEncoder[T] =
1824
new RLPListEncoder[T] {
19-
override def encode(obj: T) = f(obj)
25+
override def encodeList(obj: T) = f(obj)
2026
}
2127
}
2228

2329
/** Encoder for the empty list of fields. */
2430
implicit val deriveHNilRLPListEncoder: RLPListEncoder[HNil] =
25-
RLPListEncoder(_ => RLPList())
31+
RLPListEncoder(_ => RLPList() -> Nil)
2632

2733
/** Encoder that takes a list of fields which are the labelled generic
2834
* representation of a case class and turns it into an RLPList by
2935
* combining the RLP encoding of the head with the RLPList encoding of
3036
* the tail of the field list.
3137
*
3238
* This variant deals with trailing optional fields in the case classes,
33-
* which are omitted from the RLP list, instead of being as empty list items.
39+
* which can be omitted from the RLP list, instead of being added as empty lists.
3440
*/
3541
implicit def deriveOptionHListRLPListEncoder[K, H, T <: HList](implicit
3642
hEncoder: Lazy[RLPEncoder[H]],
3743
tEncoder: Lazy[RLPListEncoder[T]],
3844
ev: H <:< Option[_]
3945
): RLPListEncoder[FieldType[K, H] :: T] = {
40-
// Create an encoder that takes a list of fields.
46+
val hInfo = FieldInfo(isOptional = true)
47+
// Create an encoder that takes a list of field values.
4148
RLPListEncoder { case head :: tail =>
42-
val tRLP = tEncoder.value.encode(tail)
43-
// If the fields is empty and the tail serialized to an RLPList is
44-
// also empty then it looks like a trailing field which is either
45-
// the last field of the class, or is followed by empty fields.
46-
if (head.isEmpty && tRLP.items.isEmpty)
47-
tRLP
48-
else {
49-
val hRLP = hEncoder.value.encode(head)
50-
hRLP :: tRLP
51-
}
49+
val (tRLP, tInfos) = tEncoder.value.encodeList(tail)
50+
val htRLP =
51+
if (tInfos.forall(_.isOptional)) {
52+
// This is still a trailing optional field, so we can insert it as a value or omit it.
53+
hEncoder.value.encode(head) match {
54+
case RLPList(hRLP) =>
55+
hRLP :: tRLP
56+
case RLPList() if tRLP.items.isEmpty =>
57+
tRLP
58+
case hRLP =>
59+
hRLP :: tRLP
60+
}
61+
} else {
62+
// We're no longer in a trailing position, so insert it as a list of 0 or 1 items.
63+
hEncoder.value.encode(head) :: tRLP
64+
}
65+
66+
htRLP -> (hInfo :: tInfos)
5267
}
5368
}
5469

@@ -58,21 +73,22 @@ object RLPImplicitDerivations {
5873
tEncoder: Lazy[RLPListEncoder[T]],
5974
ev: H <:!< Option[_]
6075
): RLPListEncoder[FieldType[K, H] :: T] = {
76+
val hInfo = FieldInfo(isOptional = false)
6177
RLPListEncoder { case head :: tail =>
6278
val hRLP = hEncoder.value.encode(head)
63-
val tRLP = tEncoder.value.encode(tail)
64-
hRLP :: tRLP
79+
val (tRLP, tInfos) = tEncoder.value.encodeList(tail)
80+
(hRLP :: tRLP, hInfo :: tInfos)
6581
}
6682
}
6783

6884
/** Derive an encoder for a case class based on its labelled generic record representation. */
6985
implicit def deriveLabelledGenericRLPListEncoder[T, Rec](implicit
7086
// Auto-derived by Shapeless.
7187
generic: LabelledGeneric.Aux[T, Rec],
72-
// Derived by `deriveHListRLPListEncoder`.
88+
// Derived by `deriveOptionHListRLPListEncoder` and `deriveNonOptionHListRLPListEncoder`.
7389
recEncoder: Lazy[RLPListEncoder[Rec]]
7490
): RLPListEncoder[T] = RLPListEncoder { value =>
75-
recEncoder.value.encode(generic.to(value))
91+
recEncoder.value.encodeList(generic.to(value))
7692
}
7793

7894
}

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.iohk.ethereum.rlp.{RLPList, RLPEncoder}
99
import scodec.bits.BitVector
1010
import scodec.Codec
1111
import java.net.InetAddress
12+
import io.iohk.ethereum.rlp.RLPValue
1213

1314
class RLPCodecsSpec extends AnyFlatSpec {
1415

@@ -127,8 +128,11 @@ class RLPCodecsSpec extends AnyFlatSpec {
127128
)
128129

129130
RLPEncoder.encode(ping) match {
130-
case list: RLPList => list.items should have size 5
131-
case other => fail(s"Expected RLPList; got $other")
131+
case list: RLPList =>
132+
list.items should have size 5
133+
list.items.last shouldBe an[RLPValue]
134+
case other =>
135+
fail(s"Expected RLPList; got $other")
132136
}
133137
}
134138

@@ -142,8 +146,10 @@ class RLPCodecsSpec extends AnyFlatSpec {
142146
)
143147

144148
RLPEncoder.encode(ping) match {
145-
case list: RLPList => list.items should have size 4
146-
case other => fail(s"Expected RLPList; got $other")
149+
case list: RLPList =>
150+
list.items should have size 4
151+
case other =>
152+
fail(s"Expected RLPList; got $other")
147153
}
148154
}
149155
}

0 commit comments

Comments
 (0)