Skip to content

Commit 2da90e4

Browse files
authored
[ETCM-1095] eip2930 receipt (#1103)
* [ETCM-1095] Split Receipt into LegacyReceipt and Type01Receipt * [ETCM-1095] Unify TransactionGen/ReceiptGen with other generators style
1 parent 20c817f commit 2da90e4

File tree

16 files changed

+238
-81
lines changed

16 files changed

+238
-81
lines changed

src/main/scala/io/iohk/ethereum/db/storage/ReceiptStorage.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import akka.util.ByteString
55
import boopickle.Default.Pickle
66
import boopickle.Default.Unpickle
77
import boopickle.DefaultBasic._
8+
import boopickle.Pickler
89

10+
import io.iohk.ethereum.crypto.ECDSASignature
911
import io.iohk.ethereum.db.dataSource.DataSource
1012
import io.iohk.ethereum.db.storage.ReceiptStorage._
1113
import io.iohk.ethereum.domain.Address
@@ -63,11 +65,21 @@ object ReceiptStorage {
6365
TxLogEntry(address, topics, data)
6466
}(entry => (entry.loggerAddress, entry.logTopics, entry.data))
6567

66-
implicit val receiptPickler: Pickler[Receipt] =
67-
transformPickler[Receipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] {
68-
case (state, gas, filter, logs) => new Receipt(state, gas, filter, logs)
68+
implicit val legacyReceiptPickler: Pickler[LegacyReceipt] =
69+
transformPickler[LegacyReceipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] {
70+
case (state, gas, filter, logs) => LegacyReceipt(state, gas, filter, logs)
6971
} { receipt =>
7072
(receipt.postTransactionStateHash, receipt.cumulativeGasUsed, receipt.logsBloomFilter, receipt.logs)
7173
}
7274

75+
implicit val type01ReceiptPickler: Pickler[Type01Receipt] =
76+
transformPickler[Type01Receipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] {
77+
case (state, gas, filter, logs) => Type01Receipt(LegacyReceipt(state, gas, filter, logs))
78+
} { receipt =>
79+
(receipt.postTransactionStateHash, receipt.cumulativeGasUsed, receipt.logsBloomFilter, receipt.logs)
80+
}
81+
82+
implicit val receiptPickler: Pickler[Receipt] = compositePickler[Receipt]
83+
.addConcreteType[LegacyReceipt]
84+
.addConcreteType[Type01Receipt]
7385
}

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

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,51 @@ import org.bouncycastle.util.encoders.Hex
66

77
import io.iohk.ethereum.mpt.ByteArraySerializable
88

9+
sealed trait Receipt {
10+
def postTransactionStateHash: TransactionOutcome
11+
def cumulativeGasUsed: BigInt
12+
def logsBloomFilter: ByteString
13+
def logs: Seq[TxLogEntry]
14+
}
15+
16+
// shared structure for EIP-2930, EIP-1559
17+
abstract class TypedLegacyReceipt(transactionTypeId: Byte, val delegateReceipt: LegacyReceipt) extends Receipt {
18+
def postTransactionStateHash: TransactionOutcome = delegateReceipt.postTransactionStateHash
19+
def cumulativeGasUsed: BigInt = delegateReceipt.cumulativeGasUsed
20+
def logsBloomFilter: ByteString = delegateReceipt.logsBloomFilter
21+
def logs: Seq[TxLogEntry] = delegateReceipt.logs
22+
}
23+
924
object Receipt {
1025

1126
val byteArraySerializable: ByteArraySerializable[Receipt] = new ByteArraySerializable[Receipt] {
27+
1228
import io.iohk.ethereum.network.p2p.messages.ETH63.ReceiptImplicits._
1329

1430
override def fromBytes(bytes: Array[Byte]): Receipt = bytes.toReceipt
1531

1632
override def toBytes(input: Receipt): Array[Byte] = input.toBytes
1733
}
34+
}
1835

36+
object LegacyReceipt {
1937
def withHashOutcome(
2038
postTransactionStateHash: ByteString,
2139
cumulativeGasUsed: BigInt,
2240
logsBloomFilter: ByteString,
2341
logs: Seq[TxLogEntry]
24-
): Receipt =
25-
Receipt(HashOutcome(postTransactionStateHash), cumulativeGasUsed, logsBloomFilter, logs)
42+
): LegacyReceipt =
43+
LegacyReceipt(HashOutcome(postTransactionStateHash), cumulativeGasUsed, logsBloomFilter, logs)
44+
}
2645

46+
object Type01Receipt {
47+
def withHashOutcome(
48+
postTransactionStateHash: ByteString,
49+
cumulativeGasUsed: BigInt,
50+
logsBloomFilter: ByteString,
51+
logs: Seq[TxLogEntry]
52+
): Type01Receipt =
53+
Type01Receipt(LegacyReceipt.withHashOutcome(postTransactionStateHash, cumulativeGasUsed, logsBloomFilter, logs))
2754
}
2855

2956
/** @param postTransactionStateHash For blocks where block.number >= byzantium-block-number (from config),
@@ -35,24 +62,33 @@ object Receipt {
3562
*
3663
* More description: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md
3764
*/
38-
case class Receipt(
65+
case class LegacyReceipt(
3966
postTransactionStateHash: TransactionOutcome,
4067
cumulativeGasUsed: BigInt,
4168
logsBloomFilter: ByteString,
4269
logs: Seq[TxLogEntry]
43-
) {
44-
override def toString: String = {
70+
) extends Receipt {
71+
def toPrettyString(prefix: String): String = {
4572
val stateHash = postTransactionStateHash match {
4673
case HashOutcome(hash) => hash.toArray[Byte]
4774
case SuccessOutcome => Array(1.toByte)
4875
case _ => Array(0.toByte)
4976
}
5077

51-
s"Receipt{ " +
78+
s"${prefix}{ " +
5279
s"postTransactionStateHash: ${Hex.toHexString(stateHash)}, " +
5380
s"cumulativeGasUsed: $cumulativeGasUsed, " +
5481
s"logsBloomFilter: ${Hex.toHexString(logsBloomFilter.toArray[Byte])}, " +
5582
s"logs: $logs" +
5683
s"}"
5784
}
85+
86+
override def toString: String = toPrettyString("LegacyReceipt")
87+
}
88+
89+
/** EIP-2930 receipt for Transaction type 1
90+
* @param legacyReceipt
91+
*/
92+
case class Type01Receipt(legacyReceipt: LegacyReceipt) extends TypedLegacyReceipt(Transaction.Type01, legacyReceipt) {
93+
override def toString: String = legacyReceipt.toPrettyString("Type01Receipt")
5894
}

src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,25 @@ class BlockPreparator(
311311
HashOutcome(newWorld.stateRootHash)
312312
}
313313

314-
val receipt = Receipt(
315-
postTransactionStateHash = transactionOutcome,
316-
cumulativeGasUsed = acumGas + gasUsed,
317-
logsBloomFilter = BloomFilter.create(logs),
318-
logs = logs
319-
)
314+
val receipt = stx.tx match {
315+
case _: LegacyTransaction =>
316+
LegacyReceipt(
317+
postTransactionStateHash = transactionOutcome,
318+
cumulativeGasUsed = acumGas + gasUsed,
319+
logsBloomFilter = BloomFilter.create(logs),
320+
logs = logs
321+
)
322+
323+
case _: TransactionWithAccessList =>
324+
Type01Receipt(
325+
LegacyReceipt(
326+
postTransactionStateHash = transactionOutcome,
327+
cumulativeGasUsed = acumGas + gasUsed,
328+
logsBloomFilter = BloomFilter.create(logs),
329+
logs = logs
330+
)
331+
)
332+
}
320333

321334
log.debug(s"Receipt generated for tx ${stx.hash.toHex}, $receipt")
322335

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

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,12 @@ object ETH63 {
171171
case SuccessOutcome => 1.toByte
172172
case _ => 0.toByte
173173
}
174-
RLPList(stateHash, cumulativeGasUsed, logsBloomFilter, RLPList(logs.map(_.toRLPEncodable): _*))
174+
val legacyRLPReceipt =
175+
RLPList(stateHash, cumulativeGasUsed, logsBloomFilter, RLPList(logs.map(_.toRLPEncodable): _*))
176+
receipt match {
177+
case _: LegacyReceipt => legacyRLPReceipt
178+
case _: Type01Receipt => PrefixedRLPEncodable(Transaction.Type01, legacyRLPReceipt)
179+
}
175180
}
176181
}
177182

@@ -180,25 +185,39 @@ object ETH63 {
180185
}
181186

182187
implicit class ReceiptDec(val bytes: Array[Byte]) extends AnyVal {
183-
def toReceipt: Receipt = ReceiptRLPEncodableDec(rawDecode(bytes)).toReceipt
188+
import BaseETH6XMessages.TypedTransaction._
189+
190+
def toReceipt: Receipt = {
191+
val first = bytes(0)
192+
(first match {
193+
case Transaction.Type01 => PrefixedRLPEncodable(Transaction.Type01, rawDecode(bytes.tail))
194+
case _ => rawDecode(bytes)
195+
}).toReceipt
196+
}
184197

185198
def toReceipts: Seq[Receipt] = rawDecode(bytes) match {
186-
case RLPList(items @ _*) => items.map(_.toReceipt)
199+
case RLPList(items @ _*) => items.toTypedRLPEncodables.map(_.toReceipt)
187200
case _ => throw new RuntimeException("Cannot decode Receipts")
188201
}
189202
}
190203

191204
implicit class ReceiptRLPEncodableDec(val rlpEncodeable: RLPEncodeable) extends AnyVal {
192-
def toReceipt: Receipt = rlpEncodeable match {
205+
206+
def toLegacyReceipt: LegacyReceipt = rlpEncodeable match {
193207
case RLPList(postTransactionStateHash, cumulativeGasUsed, logsBloomFilter, logs: RLPList) =>
194208
val stateHash = postTransactionStateHash match {
195209
case RLPValue(bytes) if bytes.length > 1 => HashOutcome(ByteString(bytes))
196210
case RLPValue(bytes) if bytes.length == 1 && bytes.head == 1 => SuccessOutcome
197211
case _ => FailureOutcome
198212
}
199-
Receipt(stateHash, cumulativeGasUsed, logsBloomFilter, logs.items.map(_.toTxLogEntry))
213+
LegacyReceipt(stateHash, cumulativeGasUsed, logsBloomFilter, logs.items.map(_.toTxLogEntry))
200214
case _ => throw new RuntimeException("Cannot decode Receipt")
201215
}
216+
217+
def toReceipt: Receipt = rlpEncodeable match {
218+
case PrefixedRLPEncodable(Transaction.Type01, legacyReceipt) => Type01Receipt(legacyReceipt.toLegacyReceipt)
219+
case other => other.toLegacyReceipt
220+
}
202221
}
203222
}
204223

@@ -217,10 +236,12 @@ object ETH63 {
217236

218237
implicit class ReceiptsDec(val bytes: Array[Byte]) extends AnyVal {
219238
import ReceiptImplicits._
239+
import BaseETH6XMessages.TypedTransaction._
220240

221241
def toReceipts: Receipts = rawDecode(bytes) match {
222-
case rlpList: RLPList => Receipts(rlpList.items.collect { case r: RLPList => r.items.map(_.toReceipt) })
223-
case _ => throw new RuntimeException("Cannot decode Receipts")
242+
case rlpList: RLPList =>
243+
Receipts(rlpList.items.collect { case r: RLPList => r.items.toTypedRLPEncodables.map(_.toReceipt) })
244+
case _ => throw new RuntimeException("Cannot decode Receipts")
224245
}
225246
}
226247
}

src/test/scala/io/iohk/ethereum/ObjectGenerators.scala

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,28 +69,33 @@ trait ObjectGenerators {
6969
arrayList <- Gen.nonEmptyListOf(byteArrayOfNItemsGen(size))
7070
} yield byteStringList.zip(arrayList)
7171

72-
def receiptGen(): Gen[Receipt] = for {
72+
def receiptGen: Gen[Receipt] =
73+
Gen.oneOf(legacyReceiptGen, type01ReceiptGen)
74+
75+
def legacyReceiptGen: Gen[LegacyReceipt] = for {
7376
postTransactionStateHash <- byteArrayOfNItemsGen(32)
7477
cumulativeGasUsed <- bigIntGen
7578
logsBloomFilter <- byteArrayOfNItemsGen(256)
76-
} yield Receipt.withHashOutcome(
79+
} yield LegacyReceipt.withHashOutcome(
7780
postTransactionStateHash = ByteString(postTransactionStateHash),
7881
cumulativeGasUsed = cumulativeGasUsed,
7982
logsBloomFilter = ByteString(logsBloomFilter),
8083
logs = Seq()
8184
)
8285

86+
def type01ReceiptGen: Gen[Type01Receipt] = legacyReceiptGen.map(Type01Receipt(_))
87+
8388
def addressGen: Gen[Address] = byteArrayOfNItemsGen(20).map(Address(_))
8489

85-
def accessListItemGen(): Gen[AccessListItem] = for {
90+
def accessListItemGen: Gen[AccessListItem] = for {
8691
address <- addressGen
8792
storageKeys <- Gen.listOf(bigIntGen)
8893
} yield AccessListItem(address, storageKeys)
8994

90-
def transactionGen(): Gen[Transaction] =
91-
Gen.oneOf(legacyTransactionGen(), typedTransactionGen())
95+
def transactionGen: Gen[Transaction] =
96+
Gen.oneOf(legacyTransactionGen, typedTransactionGen)
9297

93-
def legacyTransactionGen(): Gen[LegacyTransaction] = for {
98+
def legacyTransactionGen: Gen[LegacyTransaction] = for {
9499
nonce <- bigIntGen
95100
gasPrice <- bigIntGen
96101
gasLimit <- bigIntGen
@@ -106,15 +111,15 @@ trait ObjectGenerators {
106111
payload
107112
)
108113

109-
def typedTransactionGen(): Gen[TransactionWithAccessList] = for {
114+
def typedTransactionGen: Gen[TransactionWithAccessList] = for {
110115
chainId <- bigIntGen
111116
nonce <- bigIntGen
112117
gasPrice <- bigIntGen
113118
gasLimit <- bigIntGen
114119
receivingAddress <- addressGen
115120
value <- bigIntGen
116121
payload <- byteStringOfLengthNGen(256)
117-
accessList <- Gen.listOf(accessListItemGen())
122+
accessList <- Gen.listOf(accessListItemGen)
118123
} yield TransactionWithAccessList(
119124
chainId,
120125
nonce,
@@ -126,7 +131,7 @@ trait ObjectGenerators {
126131
accessList
127132
)
128133

129-
def receiptsGen(n: Int): Gen[Seq[Seq[Receipt]]] = Gen.listOfN(n, Gen.listOf(receiptGen()))
134+
def receiptsGen(n: Int): Gen[Seq[Seq[Receipt]]] = Gen.listOfN(n, Gen.listOf(receiptGen))
130135

131136
def branchNodeGen: Gen[BranchNode] = for {
132137
children <- Gen
@@ -167,7 +172,7 @@ trait ObjectGenerators {
167172

168173
def signedTxSeqGen(length: Int, secureRandom: SecureRandom, chainId: Option[Byte]): Gen[Seq[SignedTransaction]] = {
169174
val senderKeys = crypto.generateKeyPair(secureRandom)
170-
val txsSeqGen = Gen.listOfN(length, transactionGen())
175+
val txsSeqGen = Gen.listOfN(length, transactionGen)
171176
txsSeqGen.map { txs =>
172177
txs.map { tx =>
173178
SignedTransaction.sign(tx, senderKeys, chainId)
@@ -178,7 +183,7 @@ trait ObjectGenerators {
178183
def signedTxGen(secureRandom: SecureRandom, chainId: Option[Byte]): Gen[SignedTransaction] = {
179184
val senderKeys = crypto.generateKeyPair(secureRandom)
180185
for {
181-
tx <- transactionGen()
186+
tx <- transactionGen
182187
} yield SignedTransaction.sign(tx, senderKeys, chainId)
183188
}
184189

src/test/scala/io/iohk/ethereum/consensus/validators/std/StdBlockValidatorSpec.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,28 +179,28 @@ class StdBlockValidatorSpec extends AnyFlatSpec with Matchers with SecureRandomB
179179
.header
180180

181181
val validReceipts: Seq[Receipt] = Seq(
182-
Receipt.withHashOutcome(
182+
LegacyReceipt.withHashOutcome(
183183
postTransactionStateHash =
184184
ByteString(Hex.decode("ce0ac687bb90d457b6573d74e4a25ea7c012fee329eb386dbef161c847f9842d")),
185185
cumulativeGasUsed = 21000,
186186
logsBloomFilter = ByteString(Hex.decode("0" * 512)),
187187
logs = Seq[TxLogEntry]()
188188
),
189-
Receipt.withHashOutcome(
189+
LegacyReceipt.withHashOutcome(
190190
postTransactionStateHash =
191191
ByteString(Hex.decode("b927d361126302acaa1fa5e93d0b7e349e278231fe2fc2846bfd54f50377f20a")),
192192
cumulativeGasUsed = 42000,
193193
logsBloomFilter = ByteString(Hex.decode("0" * 512)),
194194
logs = Seq[TxLogEntry]()
195195
),
196-
Receipt.withHashOutcome(
196+
LegacyReceipt.withHashOutcome(
197197
postTransactionStateHash =
198198
ByteString(Hex.decode("1e913d6bdd412d71292173d7908f8792adcf958b84c89575bc871a1decaee56d")),
199199
cumulativeGasUsed = 63000,
200200
logsBloomFilter = ByteString(Hex.decode("0" * 512)),
201201
logs = Seq[TxLogEntry]()
202202
),
203-
Receipt.withHashOutcome(
203+
LegacyReceipt.withHashOutcome(
204204
postTransactionStateHash =
205205
ByteString(Hex.decode("0c6e052bc83482bafaccffc4217adad49f3a9533c69c820966d75ed0154091e6")),
206206
cumulativeGasUsed = 84000,

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

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,20 @@ class SignedLegacyTransactionSpec
1818
with ScalaCheckPropertyChecks
1919
with SecureRandomBuilder {
2020
"SignedTransaction" should "correctly set pointSign for chainId with chain specific signing schema" in {
21-
forAll(Generators.transactionGen(), Arbitrary.arbitrary[Unit].map(_ => generateKeyPair(secureRandom))) {
22-
(tx, key) =>
23-
val chainId: Byte = 0x3d
24-
val allowedPointSigns = Set((chainId * 2 + 35).toByte, (chainId * 2 + 36).toByte)
25-
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
26-
val address = Address(
27-
crypto
28-
.kec256(key.getPublic.asInstanceOf[ECPublicKeyParameters].getQ.getEncoded(false).tail)
29-
.drop(FirstByteOfAddress)
30-
)
31-
val signedTransaction = SignedTransaction.sign(tx, key, Some(chainId))
32-
val result = SignedTransactionWithSender(signedTransaction, Address(key))
21+
forAll(Generators.transactionGen, Arbitrary.arbitrary[Unit].map(_ => generateKeyPair(secureRandom))) { (tx, key) =>
22+
val chainId: Byte = 0x3d
23+
val allowedPointSigns = Set((chainId * 2 + 35).toByte, (chainId * 2 + 36).toByte)
24+
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
25+
val address = Address(
26+
crypto
27+
.kec256(key.getPublic.asInstanceOf[ECPublicKeyParameters].getQ.getEncoded(false).tail)
28+
.drop(FirstByteOfAddress)
29+
)
30+
val signedTransaction = SignedTransaction.sign(tx, key, Some(chainId))
31+
val result = SignedTransactionWithSender(signedTransaction, Address(key))
3332

34-
allowedPointSigns should contain(result.tx.signature.v)
35-
address shouldEqual result.senderAddress
33+
allowedPointSigns should contain(result.tx.signature.v)
34+
address shouldEqual result.senderAddress
3635
}
3736
}
3837
}

src/test/scala/io/iohk/ethereum/jsonrpc/EthTxServiceSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ class EthTxServiceSpec
428428

429429
val contractCreatingTransactionSender: Address = SignedTransaction.getSender(contractCreatingTransaction).get
430430

431-
val fakeReceipt: Receipt = Receipt.withHashOutcome(
431+
val fakeReceipt: LegacyReceipt = LegacyReceipt.withHashOutcome(
432432
postTransactionStateHash = ByteString(Hex.decode("01" * 32)),
433433
cumulativeGasUsed = 43,
434434
logsBloomFilter = ByteString(Hex.decode("00" * 256)),

0 commit comments

Comments
 (0)