diff --git a/src/main/scala/io/iohk/ethereum/db/storage/ReceiptStorage.scala b/src/main/scala/io/iohk/ethereum/db/storage/ReceiptStorage.scala index c03ae6bfab..ffd68e6171 100644 --- a/src/main/scala/io/iohk/ethereum/db/storage/ReceiptStorage.scala +++ b/src/main/scala/io/iohk/ethereum/db/storage/ReceiptStorage.scala @@ -5,7 +5,9 @@ import akka.util.ByteString import boopickle.Default.Pickle import boopickle.Default.Unpickle import boopickle.DefaultBasic._ +import boopickle.Pickler +import io.iohk.ethereum.crypto.ECDSASignature import io.iohk.ethereum.db.dataSource.DataSource import io.iohk.ethereum.db.storage.ReceiptStorage._ import io.iohk.ethereum.domain.Address @@ -63,11 +65,21 @@ object ReceiptStorage { TxLogEntry(address, topics, data) }(entry => (entry.loggerAddress, entry.logTopics, entry.data)) - implicit val receiptPickler: Pickler[Receipt] = - transformPickler[Receipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] { - case (state, gas, filter, logs) => new Receipt(state, gas, filter, logs) + implicit val legacyReceiptPickler: Pickler[LegacyReceipt] = + transformPickler[LegacyReceipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] { + case (state, gas, filter, logs) => LegacyReceipt(state, gas, filter, logs) } { receipt => (receipt.postTransactionStateHash, receipt.cumulativeGasUsed, receipt.logsBloomFilter, receipt.logs) } + implicit val type01ReceiptPickler: Pickler[Type01Receipt] = + transformPickler[Type01Receipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] { + case (state, gas, filter, logs) => Type01Receipt(LegacyReceipt(state, gas, filter, logs)) + } { receipt => + (receipt.postTransactionStateHash, receipt.cumulativeGasUsed, receipt.logsBloomFilter, receipt.logs) + } + + implicit val receiptPickler: Pickler[Receipt] = compositePickler[Receipt] + .addConcreteType[LegacyReceipt] + .addConcreteType[Type01Receipt] } diff --git a/src/main/scala/io/iohk/ethereum/domain/Receipt.scala b/src/main/scala/io/iohk/ethereum/domain/Receipt.scala index 9565eb13ee..3d4e1e8a0d 100644 --- a/src/main/scala/io/iohk/ethereum/domain/Receipt.scala +++ b/src/main/scala/io/iohk/ethereum/domain/Receipt.scala @@ -6,24 +6,51 @@ import org.bouncycastle.util.encoders.Hex import io.iohk.ethereum.mpt.ByteArraySerializable +sealed trait Receipt { + def postTransactionStateHash: TransactionOutcome + def cumulativeGasUsed: BigInt + def logsBloomFilter: ByteString + def logs: Seq[TxLogEntry] +} + +// shared structure for EIP-2930, EIP-1559 +abstract class TypedLegacyReceipt(transactionTypeId: Byte, val delegateReceipt: LegacyReceipt) extends Receipt { + def postTransactionStateHash: TransactionOutcome = delegateReceipt.postTransactionStateHash + def cumulativeGasUsed: BigInt = delegateReceipt.cumulativeGasUsed + def logsBloomFilter: ByteString = delegateReceipt.logsBloomFilter + def logs: Seq[TxLogEntry] = delegateReceipt.logs +} + object Receipt { val byteArraySerializable: ByteArraySerializable[Receipt] = new ByteArraySerializable[Receipt] { + import io.iohk.ethereum.network.p2p.messages.ETH63.ReceiptImplicits._ override def fromBytes(bytes: Array[Byte]): Receipt = bytes.toReceipt override def toBytes(input: Receipt): Array[Byte] = input.toBytes } +} +object LegacyReceipt { def withHashOutcome( postTransactionStateHash: ByteString, cumulativeGasUsed: BigInt, logsBloomFilter: ByteString, logs: Seq[TxLogEntry] - ): Receipt = - Receipt(HashOutcome(postTransactionStateHash), cumulativeGasUsed, logsBloomFilter, logs) + ): LegacyReceipt = + LegacyReceipt(HashOutcome(postTransactionStateHash), cumulativeGasUsed, logsBloomFilter, logs) +} +object Type01Receipt { + def withHashOutcome( + postTransactionStateHash: ByteString, + cumulativeGasUsed: BigInt, + logsBloomFilter: ByteString, + logs: Seq[TxLogEntry] + ): Type01Receipt = + Type01Receipt(LegacyReceipt.withHashOutcome(postTransactionStateHash, cumulativeGasUsed, logsBloomFilter, logs)) } /** @param postTransactionStateHash For blocks where block.number >= byzantium-block-number (from config), @@ -35,24 +62,33 @@ object Receipt { * * More description: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md */ -case class Receipt( +case class LegacyReceipt( postTransactionStateHash: TransactionOutcome, cumulativeGasUsed: BigInt, logsBloomFilter: ByteString, logs: Seq[TxLogEntry] -) { - override def toString: String = { +) extends Receipt { + def toPrettyString(prefix: String): String = { val stateHash = postTransactionStateHash match { case HashOutcome(hash) => hash.toArray[Byte] case SuccessOutcome => Array(1.toByte) case _ => Array(0.toByte) } - s"Receipt{ " + + s"${prefix}{ " + s"postTransactionStateHash: ${Hex.toHexString(stateHash)}, " + s"cumulativeGasUsed: $cumulativeGasUsed, " + s"logsBloomFilter: ${Hex.toHexString(logsBloomFilter.toArray[Byte])}, " + s"logs: $logs" + s"}" } + + override def toString: String = toPrettyString("LegacyReceipt") +} + +/** EIP-2930 receipt for Transaction type 1 + * @param legacyReceipt + */ +case class Type01Receipt(legacyReceipt: LegacyReceipt) extends TypedLegacyReceipt(Transaction.Type01, legacyReceipt) { + override def toString: String = legacyReceipt.toPrettyString("Type01Receipt") } diff --git a/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala b/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala index 62d6105cf6..197ad68d2d 100644 --- a/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala +++ b/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala @@ -311,12 +311,25 @@ class BlockPreparator( HashOutcome(newWorld.stateRootHash) } - val receipt = Receipt( - postTransactionStateHash = transactionOutcome, - cumulativeGasUsed = acumGas + gasUsed, - logsBloomFilter = BloomFilter.create(logs), - logs = logs - ) + val receipt = stx.tx match { + case _: LegacyTransaction => + LegacyReceipt( + postTransactionStateHash = transactionOutcome, + cumulativeGasUsed = acumGas + gasUsed, + logsBloomFilter = BloomFilter.create(logs), + logs = logs + ) + + case _: TransactionWithAccessList => + Type01Receipt( + LegacyReceipt( + postTransactionStateHash = transactionOutcome, + cumulativeGasUsed = acumGas + gasUsed, + logsBloomFilter = BloomFilter.create(logs), + logs = logs + ) + ) + } log.debug(s"Receipt generated for tx ${stx.hash.toHex}, $receipt") diff --git a/src/main/scala/io/iohk/ethereum/network/p2p/messages/ETH63.scala b/src/main/scala/io/iohk/ethereum/network/p2p/messages/ETH63.scala index 37ffb2cc36..72d45a16cb 100644 --- a/src/main/scala/io/iohk/ethereum/network/p2p/messages/ETH63.scala +++ b/src/main/scala/io/iohk/ethereum/network/p2p/messages/ETH63.scala @@ -171,7 +171,12 @@ object ETH63 { case SuccessOutcome => 1.toByte case _ => 0.toByte } - RLPList(stateHash, cumulativeGasUsed, logsBloomFilter, RLPList(logs.map(_.toRLPEncodable): _*)) + val legacyRLPReceipt = + RLPList(stateHash, cumulativeGasUsed, logsBloomFilter, RLPList(logs.map(_.toRLPEncodable): _*)) + receipt match { + case _: LegacyReceipt => legacyRLPReceipt + case _: Type01Receipt => PrefixedRLPEncodable(Transaction.Type01, legacyRLPReceipt) + } } } @@ -180,25 +185,39 @@ object ETH63 { } implicit class ReceiptDec(val bytes: Array[Byte]) extends AnyVal { - def toReceipt: Receipt = ReceiptRLPEncodableDec(rawDecode(bytes)).toReceipt + import BaseETH6XMessages.TypedTransaction._ + + def toReceipt: Receipt = { + val first = bytes(0) + (first match { + case Transaction.Type01 => PrefixedRLPEncodable(Transaction.Type01, rawDecode(bytes.tail)) + case _ => rawDecode(bytes) + }).toReceipt + } def toReceipts: Seq[Receipt] = rawDecode(bytes) match { - case RLPList(items @ _*) => items.map(_.toReceipt) + case RLPList(items @ _*) => items.toTypedRLPEncodables.map(_.toReceipt) case _ => throw new RuntimeException("Cannot decode Receipts") } } implicit class ReceiptRLPEncodableDec(val rlpEncodeable: RLPEncodeable) extends AnyVal { - def toReceipt: Receipt = rlpEncodeable match { + + def toLegacyReceipt: LegacyReceipt = rlpEncodeable match { case RLPList(postTransactionStateHash, cumulativeGasUsed, logsBloomFilter, logs: RLPList) => val stateHash = postTransactionStateHash match { case RLPValue(bytes) if bytes.length > 1 => HashOutcome(ByteString(bytes)) case RLPValue(bytes) if bytes.length == 1 && bytes.head == 1 => SuccessOutcome case _ => FailureOutcome } - Receipt(stateHash, cumulativeGasUsed, logsBloomFilter, logs.items.map(_.toTxLogEntry)) + LegacyReceipt(stateHash, cumulativeGasUsed, logsBloomFilter, logs.items.map(_.toTxLogEntry)) case _ => throw new RuntimeException("Cannot decode Receipt") } + + def toReceipt: Receipt = rlpEncodeable match { + case PrefixedRLPEncodable(Transaction.Type01, legacyReceipt) => Type01Receipt(legacyReceipt.toLegacyReceipt) + case other => other.toLegacyReceipt + } } } @@ -217,10 +236,12 @@ object ETH63 { implicit class ReceiptsDec(val bytes: Array[Byte]) extends AnyVal { import ReceiptImplicits._ + import BaseETH6XMessages.TypedTransaction._ def toReceipts: Receipts = rawDecode(bytes) match { - case rlpList: RLPList => Receipts(rlpList.items.collect { case r: RLPList => r.items.map(_.toReceipt) }) - case _ => throw new RuntimeException("Cannot decode Receipts") + case rlpList: RLPList => + Receipts(rlpList.items.collect { case r: RLPList => r.items.toTypedRLPEncodables.map(_.toReceipt) }) + case _ => throw new RuntimeException("Cannot decode Receipts") } } } diff --git a/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala b/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala index 9e7d72e1bd..68536ab311 100644 --- a/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala +++ b/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala @@ -69,17 +69,22 @@ trait ObjectGenerators { arrayList <- Gen.nonEmptyListOf(byteArrayOfNItemsGen(size)) } yield byteStringList.zip(arrayList) - def receiptGen(): Gen[Receipt] = for { + def receiptGen(): Gen[Receipt] = + Gen.oneOf(legacyReceiptGen(), type01ReceiptGen()) + + def legacyReceiptGen(): Gen[LegacyReceipt] = for { postTransactionStateHash <- byteArrayOfNItemsGen(32) cumulativeGasUsed <- bigIntGen logsBloomFilter <- byteArrayOfNItemsGen(256) - } yield Receipt.withHashOutcome( + } yield LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(postTransactionStateHash), cumulativeGasUsed = cumulativeGasUsed, logsBloomFilter = ByteString(logsBloomFilter), logs = Seq() ) + def type01ReceiptGen(): Gen[Type01Receipt] = legacyReceiptGen().map(Type01Receipt(_)) + def addressGen: Gen[Address] = byteArrayOfNItemsGen(20).map(Address(_)) def accessListItemGen(): Gen[AccessListItem] = for { diff --git a/src/test/scala/io/iohk/ethereum/consensus/validators/std/StdBlockValidatorSpec.scala b/src/test/scala/io/iohk/ethereum/consensus/validators/std/StdBlockValidatorSpec.scala index aa1fa666b8..523bc5f571 100644 --- a/src/test/scala/io/iohk/ethereum/consensus/validators/std/StdBlockValidatorSpec.scala +++ b/src/test/scala/io/iohk/ethereum/consensus/validators/std/StdBlockValidatorSpec.scala @@ -179,28 +179,28 @@ class StdBlockValidatorSpec extends AnyFlatSpec with Matchers with SecureRandomB .header val validReceipts: Seq[Receipt] = Seq( - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("ce0ac687bb90d457b6573d74e4a25ea7c012fee329eb386dbef161c847f9842d")), cumulativeGasUsed = 21000, logsBloomFilter = ByteString(Hex.decode("0" * 512)), logs = Seq[TxLogEntry]() ), - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("b927d361126302acaa1fa5e93d0b7e349e278231fe2fc2846bfd54f50377f20a")), cumulativeGasUsed = 42000, logsBloomFilter = ByteString(Hex.decode("0" * 512)), logs = Seq[TxLogEntry]() ), - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("1e913d6bdd412d71292173d7908f8792adcf958b84c89575bc871a1decaee56d")), cumulativeGasUsed = 63000, logsBloomFilter = ByteString(Hex.decode("0" * 512)), logs = Seq[TxLogEntry]() ), - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("0c6e052bc83482bafaccffc4217adad49f3a9533c69c820966d75ed0154091e6")), cumulativeGasUsed = 84000, diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthTxServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthTxServiceSpec.scala index c572d79740..bfead1a14c 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthTxServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthTxServiceSpec.scala @@ -428,7 +428,7 @@ class EthTxServiceSpec val contractCreatingTransactionSender: Address = SignedTransaction.getSender(contractCreatingTransaction).get - val fakeReceipt: Receipt = Receipt.withHashOutcome( + val fakeReceipt: LegacyReceipt = LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("01" * 32)), cumulativeGasUsed = 43, logsBloomFilter = ByteString(Hex.decode("00" * 256)), diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/FilterManagerSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/FilterManagerSpec.scala index db0fb31d0e..a2801d8314 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/FilterManagerSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/FilterManagerSpec.scala @@ -102,7 +102,7 @@ class FilterManagerSpec .returning( Some( Seq( - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(), cumulativeGasUsed = 0, logsBloomFilter = BloomFilter.create(logs2), @@ -191,13 +191,13 @@ class FilterManagerSpec .returning( Some( Seq( - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(), cumulativeGasUsed = 0, logsBloomFilter = BloomFilter.create(Seq(log4_1)), logs = Seq(log4_1) ), - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(), cumulativeGasUsed = 0, logsBloomFilter = BloomFilter.create(Seq(log4_2)), @@ -266,7 +266,7 @@ class FilterManagerSpec .returning( Some( Seq( - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(), cumulativeGasUsed = 0, logsBloomFilter = BloomFilter.create(logs), @@ -305,7 +305,7 @@ class FilterManagerSpec PendingBlock( block2, Seq( - Receipt.withHashOutcome( + LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(), cumulativeGasUsed = 0, logsBloomFilter = BloomFilter.create(logs2), diff --git a/src/test/scala/io/iohk/ethereum/ledger/BlockExecutionSpec.scala b/src/test/scala/io/iohk/ethereum/ledger/BlockExecutionSpec.scala index e05fcd5866..82a6c5b53a 100644 --- a/src/test/scala/io/iohk/ethereum/ledger/BlockExecutionSpec.scala +++ b/src/test/scala/io/iohk/ethereum/ledger/BlockExecutionSpec.scala @@ -270,7 +270,7 @@ class BlockExecutionSpec extends AnyWordSpec with Matchers with ScalaCheckProper // Check valid receipts resultingReceipts.size shouldBe 1 - val Receipt(rootHashReceipt, gasUsedReceipt, logsBloomFilterReceipt, logsReceipt) = resultingReceipts.head + val LegacyReceipt(rootHashReceipt, gasUsedReceipt, logsBloomFilterReceipt, logsReceipt) = resultingReceipts.head rootHashReceipt shouldBe HashOutcome(expectedStateRoot) gasUsedReceipt shouldBe resultingGasUsed logsBloomFilterReceipt shouldBe BloomFilter.create(Nil) @@ -348,7 +348,8 @@ class BlockExecutionSpec extends AnyWordSpec with Matchers with ScalaCheckProper // Check valid receipts resultingReceipts.size shouldBe 1 - val Receipt(rootHashReceipt, gasUsedReceipt, logsBloomFilterReceipt, logsReceipt) = resultingReceipts.head + val LegacyReceipt(rootHashReceipt, gasUsedReceipt, logsBloomFilterReceipt, logsReceipt) = + resultingReceipts.head rootHashReceipt shouldBe HashOutcome(expectedStateRoot) gasUsedReceipt shouldBe resultingGasUsed logsBloomFilterReceipt shouldBe BloomFilter.create(logs) @@ -607,7 +608,7 @@ class BlockExecutionSpec extends AnyWordSpec with Matchers with ScalaCheckProper ) val expectedStateRootTx1 = applyChanges(validBlockParentHeader.stateRoot, changesTx1) - val Receipt(rootHashReceipt1, gasUsedReceipt1, logsBloomFilterReceipt1, logsReceipt1) = receipt1 + val LegacyReceipt(rootHashReceipt1, gasUsedReceipt1, logsBloomFilterReceipt1, logsReceipt1) = receipt1 rootHashReceipt1 shouldBe HashOutcome(expectedStateRootTx1) gasUsedReceipt1 shouldBe stx1.tx.tx.gasLimit logsBloomFilterReceipt1 shouldBe BloomFilter.create(Nil) @@ -622,7 +623,7 @@ class BlockExecutionSpec extends AnyWordSpec with Matchers with ScalaCheckProper ) val expectedStateRootTx2 = applyChanges(expectedStateRootTx1, changesTx2) - val Receipt(rootHashReceipt2, gasUsedReceipt2, logsBloomFilterReceipt2, logsReceipt2) = receipt2 + val LegacyReceipt(rootHashReceipt2, gasUsedReceipt2, logsBloomFilterReceipt2, logsReceipt2) = receipt2 rootHashReceipt2 shouldBe HashOutcome(expectedStateRootTx2) gasUsedReceipt2 shouldBe (transaction1.gasLimit + transaction2.gasLimit) logsBloomFilterReceipt2 shouldBe BloomFilter.create(Nil) diff --git a/src/test/scala/io/iohk/ethereum/ledger/BlockValidationSpec.scala b/src/test/scala/io/iohk/ethereum/ledger/BlockValidationSpec.scala index ae202a6a54..faeffe35bf 100644 --- a/src/test/scala/io/iohk/ethereum/ledger/BlockValidationSpec.scala +++ b/src/test/scala/io/iohk/ethereum/ledger/BlockValidationSpec.scala @@ -127,7 +127,7 @@ class BlockValidationSpec extends AnyWordSpec with Matchers with MockFactory { ) ) - def mkReceipt(stateHash: String, gas: BigInt): Receipt = Receipt.withHashOutcome( + def mkReceipt(stateHash: String, gas: BigInt): Receipt = LegacyReceipt.withHashOutcome( postTransactionStateHash = hash2ByteString(stateHash), cumulativeGasUsed = gas, logsBloomFilter = bloomFilter, diff --git a/src/test/scala/io/iohk/ethereum/ledger/BloomFilterSpec.scala b/src/test/scala/io/iohk/ethereum/ledger/BloomFilterSpec.scala index bb0f8b59f4..789fe435c9 100644 --- a/src/test/scala/io/iohk/ethereum/ledger/BloomFilterSpec.scala +++ b/src/test/scala/io/iohk/ethereum/ledger/BloomFilterSpec.scala @@ -7,6 +7,7 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import io.iohk.ethereum.domain.Address +import io.iohk.ethereum.domain.LegacyReceipt import io.iohk.ethereum.domain.Receipt import io.iohk.ethereum.domain.TxLogEntry @@ -28,7 +29,7 @@ class BloomFilterSpec extends AnyFlatSpec with Matchers { } //From tx 0xe9e91f1ee4b56c0df2e9f06c2b8c27c6076195a88a7b8537ba8313d80e6f124e - val receiptWithoutLogs: Receipt = Receipt.withHashOutcome( + val receiptWithoutLogs: Receipt = LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("fa28ef92787192b577a8628e520b546ab58b72102572e08191ddecd51d0851e5")), cumulativeGasUsed = 50244, @@ -41,7 +42,7 @@ class BloomFilterSpec extends AnyFlatSpec with Matchers { ) //From tx 0x864f61c4fbf1952bfb55d4617e4bde3a0338322b37c832119ed1e8717b502530 - val receiptOneLogOneTopic: Receipt = Receipt.withHashOutcome( + val receiptOneLogOneTopic: Receipt = LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("d74e64c4beb7627811f456baedfe05d26364bef11136b922b8c44769ad1e6ac6")), cumulativeGasUsed = BigInt("1674016"), @@ -64,7 +65,7 @@ class BloomFilterSpec extends AnyFlatSpec with Matchers { ) //From tx 0x0bb157f90f918fad96d6954d9e620a4aa490da57a66303a6b41e855fd0f19a59 - val receiptWithManyLogs: Receipt = Receipt.withHashOutcome( + val receiptWithManyLogs: Receipt = LegacyReceipt.withHashOutcome( postTransactionStateHash = ByteString(Hex.decode("fe375456a6f22f90f2f55bd57e72c7c663ef7733d5795f091a06496ad5895c67")), cumulativeGasUsed = 319269, diff --git a/src/test/scala/io/iohk/ethereum/ledger/LedgerTestSetup.scala b/src/test/scala/io/iohk/ethereum/ledger/LedgerTestSetup.scala index 6566afc6b3..8b3951459f 100644 --- a/src/test/scala/io/iohk/ethereum/ledger/LedgerTestSetup.scala +++ b/src/test/scala/io/iohk/ethereum/ledger/LedgerTestSetup.scala @@ -337,7 +337,7 @@ trait TestSetupWithVmAndValidators extends EphemBlockchainTestSetup { def getChainHeadersNel(from: BigInt, to: BigInt, parent: ByteString = randomHash()): NonEmptyList[BlockHeader] = NonEmptyList.fromListUnsafe(getChainHeaders(from, to, parent)) - val receipts: Seq[Receipt] = Seq(Receipt.withHashOutcome(randomHash(), 50000, randomHash(), Nil)) + val receipts: Seq[Receipt] = Seq(LegacyReceipt.withHashOutcome(randomHash(), 50000, randomHash(), Nil)) val currentWeight: ChainWeight = ChainWeight.totalDifficultyOnly(99999) diff --git a/src/test/scala/io/iohk/ethereum/network/p2p/messages/ReceiptsSpec.scala b/src/test/scala/io/iohk/ethereum/network/p2p/messages/ReceiptsSpec.scala index 1eb844e63d..c9fd22a2b7 100644 --- a/src/test/scala/io/iohk/ethereum/network/p2p/messages/ReceiptsSpec.scala +++ b/src/test/scala/io/iohk/ethereum/network/p2p/messages/ReceiptsSpec.scala @@ -8,10 +8,12 @@ import org.scalatest.matchers.should.Matchers import io.iohk.ethereum.crypto._ import io.iohk.ethereum.domain.Address +import io.iohk.ethereum.domain.LegacyReceipt import io.iohk.ethereum.domain.Receipt +import io.iohk.ethereum.domain.Transaction import io.iohk.ethereum.domain.TxLogEntry +import io.iohk.ethereum.domain.Type01Receipt import io.iohk.ethereum.network.p2p.EthereumMessageDecoder -import io.iohk.ethereum.network.p2p.messages.Capability import io.iohk.ethereum.network.p2p.messages.ETH63.Receipts import io.iohk.ethereum.rlp.RLPImplicitConversions._ import io.iohk.ethereum.rlp.RLPImplicits._ @@ -30,16 +32,20 @@ class ReceiptsSpec extends AnyFlatSpec with Matchers { val cumulativeGas: BigInt = 0 - val receipt: Receipt = Receipt.withHashOutcome( + val legacyReceipt: Receipt = LegacyReceipt.withHashOutcome( postTransactionStateHash = exampleHash, cumulativeGasUsed = cumulativeGas, logsBloomFilter = exampleLogsBloom, logs = Seq(exampleLog) ) - val receipts: Receipts = Receipts(Seq(Seq(receipt))) + val type01Receipt: Receipt = Type01Receipt(legacyReceipt.asInstanceOf[LegacyReceipt]) - val encodedReceipts: RLPList = + val legacyReceipts: Receipts = Receipts(Seq(Seq(legacyReceipt))) + + val type01Receipts: Receipts = Receipts(Seq(Seq(type01Receipt))) + + val encodedLegacyReceipts: RLPList = RLPList( RLPList( RLPList( @@ -51,22 +57,57 @@ class ReceiptsSpec extends AnyFlatSpec with Matchers { ) ) - "Receipts" should "encode receipts" in { - (receipts.toBytes: Array[Byte]) shouldBe encode(encodedReceipts) + val encodedType01Receipts: RLPList = + RLPList( + RLPList( + PrefixedRLPEncodable( + Transaction.Type01, + RLPList( + exampleHash, + cumulativeGas, + exampleLogsBloom, + RLPList(RLPList(loggerAddress.bytes, logTopics, logData)) + ) + ) + ) + ) + + "Legacy Receipts" should "encode legacy receipts" in { + (legacyReceipts.toBytes: Array[Byte]) shouldBe encode(encodedLegacyReceipts) } - it should "decode receipts" in { + it should "decode legacy receipts" in { EthereumMessageDecoder .ethMessageDecoder(Capability.ETH63) .fromBytes( Codes.ReceiptsCode, - encode(encodedReceipts) - ) shouldBe Right(receipts) + encode(encodedLegacyReceipts) + ) shouldBe Right(legacyReceipts) } - it should "decode encoded receipts" in { + it should "decode encoded legacy receipts" in { EthereumMessageDecoder .ethMessageDecoder(Capability.ETH63) - .fromBytes(Codes.ReceiptsCode, receipts.toBytes) shouldBe Right(receipts) + .fromBytes(Codes.ReceiptsCode, legacyReceipts.toBytes) shouldBe Right(legacyReceipts) + } + + "Type 01 Receipts" should "encode type 01 receipts" in { + (type01Receipts.toBytes: Array[Byte]) shouldBe encode(encodedType01Receipts) } + + it should "decode type 01 receipts" in { + EthereumMessageDecoder + .ethMessageDecoder(Capability.ETH64) + .fromBytes( + Codes.ReceiptsCode, + encode(encodedType01Receipts) + ) shouldBe Right(type01Receipts) + } + + it should "decode encoded type 01 receipts" in { + EthereumMessageDecoder + .ethMessageDecoder(Capability.ETH64) + .fromBytes(Codes.ReceiptsCode, type01Receipts.toBytes) shouldBe Right(type01Receipts) + } + } diff --git a/src/test/scala/io/iohk/ethereum/rlp/RLPSpec.scala b/src/test/scala/io/iohk/ethereum/rlp/RLPSpec.scala new file mode 100644 index 0000000000..fe007e770e --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/rlp/RLPSpec.scala @@ -0,0 +1,26 @@ +package io.iohk.ethereum.rlp + +import org.scalacheck.Arbitrary +import org.scalacheck.Gen +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +import io.iohk.ethereum.domain.Transaction + +class RLPSpec extends AnyFlatSpec with ScalaCheckPropertyChecks with Matchers { + + "PrefixedRLPEncodable" should "reject invalid transaction type outside of [0, 0x7f]" in { + forAll(Arbitrary.arbitrary[Byte].suchThat(b => b < Transaction.MinAllowedType || b > Transaction.MaxAllowedType)) { + transactionType => + an[RuntimeException] shouldBe thrownBy(PrefixedRLPEncodable(transactionType, RLPList())) + } + + } + + "PrefixedRLPEncodable" should "accept valid transaction type [0, 0x7f]" in { + forAll(Gen.choose[Byte](Transaction.MinAllowedType, Transaction.MaxAllowedType)) { transactionType => + PrefixedRLPEncodable(transactionType, RLPList()) + } + } +} diff --git a/src/test/scala/io/iohk/ethereum/transactions/LegacyTransactionHistoryServiceSpec.scala b/src/test/scala/io/iohk/ethereum/transactions/LegacyTransactionHistoryServiceSpec.scala index 28e5efad93..1c58d5a126 100644 --- a/src/test/scala/io/iohk/ethereum/transactions/LegacyTransactionHistoryServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/transactions/LegacyTransactionHistoryServiceSpec.scala @@ -50,15 +50,15 @@ class LegacyTransactionHistoryServiceSpec val blockWithTx1 = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx1))) - val blockTx1Receipts = Seq(Receipt(HashOutcome(ByteString("foo")), 42, ByteString.empty, Nil)) + val blockTx1Receipts = Seq(LegacyReceipt(HashOutcome(ByteString("foo")), 42, ByteString.empty, Nil)) val blockWithTxs2and3 = Block( Fixtures.Blocks.Block3125369.header.copy(number = 3125370), Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx2, tx3)) ) val blockTx2And3Receipts = Seq( - Receipt(HashOutcome(ByteString("bar")), 43, ByteString.empty, Nil), - Receipt(HashOutcome(ByteString("baz")), 43 + 44, ByteString.empty, Nil) + LegacyReceipt(HashOutcome(ByteString("bar")), 43, ByteString.empty, Nil), + LegacyReceipt(HashOutcome(ByteString("baz")), 43 + 44, ByteString.empty, Nil) ) val expectedTxs = Seq( @@ -160,7 +160,9 @@ class LegacyTransactionHistoryServiceSpec ) def makeReceipts(block: Block): Seq[Receipt] = - block.body.transactionList.map(_ => Receipt(HashOutcome(block.hash), BigInt(21000), ByteString("foo"), Nil)) + block.body.transactionList.map(_ => + LegacyReceipt(HashOutcome(block.hash), BigInt(21000), ByteString("foo"), Nil) + ) for { _ <- Task {