Skip to content

Commit 8e3dbef

Browse files
committed
[ETCM-921] properly decode SignedTransaction in BlockBody + scaladoc
1 parent 0ce25c8 commit 8e3dbef

File tree

6 files changed

+75
-36
lines changed

6 files changed

+75
-36
lines changed

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,22 @@ package object rlp {
4444
override def toString: String = s"RLPValue(${Hex.toHexString(bytes)})"
4545
}
4646

47-
case class PrefixedRLPEncodable(prefix: Byte, prefixedRLPEncodeable: RLPEncodeable) extends RLPEncodeable
47+
/** Modelise a RLPEncodable that should be binary prefixed by a raw byte.
48+
*
49+
* When converting this RLPEncodable to byte, the resulting value will be:
50+
* prefix || prefixedRLPEncodable.toByte
51+
* where || is the binary concatenation symbol.
52+
*
53+
* To be able to read back the data, use TypedTransaction.TypedTransactionsRLPAggregator
54+
*
55+
* This is for example used for typed transaction and typed receipt.
56+
*
57+
* @param prefix the raw byte
58+
* @param prefixedRLPEncodeable the RLPEncodable to prefix with
59+
*/
60+
case class PrefixedRLPEncodable(prefix: Byte, prefixedRLPEncodeable: RLPEncodeable) extends RLPEncodeable {
61+
require(prefix <= 0x07f, "prefix should be lower than 0x7f")
62+
}
4863

4964
trait RLPEncoder[T] {
5065
def encode(obj: T): RLPEncodeable

src/main/scala/io/iohk/ethereum/domain/Block.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ object Block {
4343

4444
implicit class BlockDec(val bytes: Array[Byte]) extends AnyVal {
4545
import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.SignedTransactions._
46+
import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.TypedTransaction._
4647
def toBlock: Block = rawDecode(bytes) match {
4748
case RLPList(header: RLPList, stx: RLPList, uncles: RLPList) =>
4849
Block(
4950
header.toBlockHeader,
5051
BlockBody(
51-
stx.items.map(_.toSignedTransaction),
52+
stx.items.toTypedRLPEncodables.map(_.toSignedTransaction),
5253
uncles.items.map(_.toBlockHeader)
5354
)
5455
)

src/main/scala/io/iohk/ethereum/domain/BlockBody.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ object BlockBody {
2323

2424
val empty: BlockBody = BlockBody(Seq.empty, Seq.empty)
2525

26+
import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.TypedTransaction._
27+
2628
def blockBodyToRlpEncodable(
2729
blockBody: BlockBody,
2830
signedTxToRlpEncodable: SignedTransaction => RLPEncodeable,
@@ -57,7 +59,7 @@ object BlockBody {
5759
rlpEncodeable match {
5860
case RLPList((transactions: RLPList), (uncles: RLPList)) =>
5961
BlockBody(
60-
transactions.items.map(rlpEncodableToSignedTransaction),
62+
transactions.items.toTypedRLPEncodables.map(rlpEncodableToSignedTransaction),
6163
uncles.items.map(rlpEncodableToBlockHeader)
6264
)
6365
case _ => throw new RuntimeException("Cannot decode BlockBody")

src/main/scala/io/iohk/ethereum/network/p2p/messages/BaseETH6XMessages.scala

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,17 @@ object BaseETH6XMessages {
117117

118118
implicit class NewBlockDec(val bytes: Array[Byte]) extends AnyVal {
119119
import SignedTransactions._
120+
import TypedTransaction._
120121

121122
def toNewBlock: NewBlock = rawDecode(bytes) match {
122123
case RLPList(RLPList(blockHeader, transactionList: RLPList, uncleNodesList: RLPList), totalDifficulty) =>
123124
NewBlock(
124125
Block(
125126
blockHeader.toBlockHeader,
126-
BlockBody(transactionList.items.map(_.toSignedTransaction), uncleNodesList.items.map(_.toBlockHeader))
127+
BlockBody(
128+
transactionList.items.toTypedRLPEncodables.map(_.toSignedTransaction),
129+
uncleNodesList.items.map(_.toBlockHeader)
130+
)
127131
),
128132
totalDifficulty
129133
)
@@ -154,6 +158,36 @@ object BaseETH6XMessages {
154158
override def code: Int = Codes.NewBlockCode
155159
}
156160

161+
object TypedTransaction {
162+
implicit class TypedTransactionsRLPAggregator(val encodables: Seq[RLPEncodeable]) extends AnyVal {
163+
164+
/** Convert a Seq of RLPEncodable containing TypedTransaction informations into a Seq of
165+
* Prefixed RLPEncodable.
166+
*
167+
* PrefixedRLPEncodable(prefix, prefixedRLPEncodable) generates binary data
168+
* as prefix || RLPEncodable(prefixedRLPEncodable).
169+
*
170+
* As prefix is a byte value lower than 0x7f, it is read back as RLPValue(prefix),
171+
* thus PrefixedRLPEncodable is binary equivalent to RLPValue(prefix), RLPEncodable
172+
*
173+
* The method aggregates back the typed transaction prefix with the following heuristic:
174+
* - a RLPValue(byte) with byte < 07f + the following RLPEncodable are associated as a PrefixedRLPEncodable
175+
* - all other RLPEncodable are kept unchanged
176+
*
177+
* This is the responsibility of the RLPDecoder to insert this meaning into its RLPList, when appropriate.
178+
*
179+
* @return a Seq of TypedTransaction enriched RLPEncodable
180+
*/
181+
def toTypedRLPEncodables: Seq[RLPEncodeable] =
182+
encodables match {
183+
case Seq() => Seq()
184+
case Seq(RLPValue(v), rlpList: RLPList, tail @ _*) if v.length == 1 && v.head < 0x7f =>
185+
PrefixedRLPEncodable(v.head, rlpList) +: tail.toTypedRLPEncodables
186+
case Seq(head, tail @ _*) => head +: tail.toTypedRLPEncodables
187+
}
188+
}
189+
}
190+
157191
object SignedTransactions {
158192

159193
implicit class SignedTransactionEnc(val signedTx: SignedTransaction) extends RLPSerializable {
@@ -207,23 +241,25 @@ object BaseETH6XMessages {
207241

208242
implicit class SignedTransactionsDec(val bytes: Array[Byte]) extends AnyVal {
209243

210-
def mergeTransactionType(encodables: Seq[RLPEncodeable]): Seq[RLPEncodeable] =
211-
encodables match {
212-
case Seq() => Seq()
213-
case Seq(RLPValue(v), rlpList: RLPList, tail @ _*) if v.length == 1 =>
214-
PrefixedRLPEncodable(v.head, rlpList) +: mergeTransactionType(tail)
215-
case Seq(head, tail @ _*) => head +: mergeTransactionType(tail)
216-
}
217-
def toSignedTransactions: SignedTransactions =
218-
rawDecode(bytes) match {
219-
case rlpList: RLPList => SignedTransactions(mergeTransactionType(rlpList.items).map(_.toSignedTransaction))
220-
case _ => throw new RuntimeException("Cannot decode SignedTransactions")
221-
}
244+
import TypedTransaction._
245+
246+
def toSignedTransactions: SignedTransactions = rawDecode(bytes) match {
247+
case rlpList: RLPList => SignedTransactions(rlpList.items.toTypedRLPEncodables.map(_.toSignedTransaction))
248+
case _ => throw new RuntimeException("Cannot decode SignedTransactions")
249+
}
222250
}
223251

224252
implicit class SignedTransactionRlpEncodableDec(val rlpEncodeable: RLPEncodeable) extends AnyVal {
225253

226254
// scalastyle:off method.length
255+
256+
/** A signed transaction is either a RLPList representing a Legacy SignedTransaction
257+
* or a PrefixedRLPEncodable(transactionType, RLPList of typed transaction envelope)
258+
*
259+
* @see TypedTransaction.TypedTransactionsRLPAggregator
260+
*
261+
* @return a SignedTransaction
262+
*/
227263
def toSignedTransaction: SignedTransaction = rlpEncodeable match {
228264
case PrefixedRLPEncodable(
229265
Transaction.Type01,

src/main/scala/io/iohk/ethereum/network/p2p/messages/ETC64.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ object ETC64 {
105105

106106
implicit class NewBlockDec(val bytes: Array[Byte]) extends AnyVal {
107107
import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.SignedTransactions._
108+
import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.TypedTransaction._
108109

109110
def toNewBlock: NewBlock = rawDecode(bytes) match {
110111
case RLPList(
@@ -115,7 +116,10 @@ object ETC64 {
115116
NewBlock(
116117
Block(
117118
blockHeader.toBlockHeader,
118-
BlockBody(transactionList.items.map(_.toSignedTransaction), uncleNodesList.items.map(_.toBlockHeader))
119+
BlockBody(
120+
transactionList.items.toTypedRLPEncodables.map(_.toSignedTransaction),
121+
uncleNodesList.items.map(_.toBlockHeader)
122+
)
119123
),
120124
ChainWeight(lastCheckpointNumber, totalDifficulty)
121125
)

src/test/scala/io/iohk/ethereum/domain/TransactionSpec.scala

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,12 @@ class TransactionSpec
4343
forAll(signedTxSeqGen(2, secureRandom, None)) { (originalSignedTransactionSeq: Seq[SignedTransaction]) =>
4444
// encode it
4545
import SignedTransactions.SignedTransactionsEnc
46-
47-
// SignedTransactionsEnc is the Sequence version of SignedTransactionEnc
48-
// the SignedTransactionsEnc.toBytes calls
49-
// -> SignedTransactionsEnc.toRLPEncodable which maps over
50-
// -> SignedTransactionEnc.toRLPEncodable (single signed transaction encoder)
51-
// without going through the SignedTransactionEnc.toByte, which is the actual part where the 01 prefix is inserted
5246
val encodedSignedTransactionSeq: Array[Byte] = SignedTransactions(originalSignedTransactionSeq).toBytes
5347

5448
// decode it
5549
import SignedTransactions.SignedTransactionsDec
56-
// likewise, the SignedTransactionsDec.toSignedTransactions maps over
57-
// -> SignedTransactionRlpEncodableDec.toSignedTransaction (single signed transaction)
58-
// while ignoring the SignedTransactionDec(Array[Byte]), which is responsible to parse the prefix if available
5950
val SignedTransactions(decodedSignedTransactionSeq) = encodedSignedTransactionSeq.toSignedTransactions
6051

61-
// The test is working because both encoding and decoding are skipping the prefix part,
62-
// and the rlp encoded transaction is different enough to recognize a LegacyTransaction from a TX1
63-
64-
// I see two problems:
65-
// - the encoding is not compatible with other clients
66-
// - this is not working for receipt, where legacy and tx1 receipt payload are the same. As such we can't
67-
// distinguish which one it is without the prefix
68-
69-
// The root cause seems to be that the prefix stuff is done on a byte[] level,
70-
// whereas RLPList are working on RLPEncodable
7152
decodedSignedTransactionSeq shouldEqual originalSignedTransactionSeq
7253
}
7354
}

0 commit comments

Comments
 (0)