Skip to content

Commit

Permalink
[ETCM-1095] eip2930 receipt (#1103)
Browse files Browse the repository at this point in the history
* [ETCM-1095] Split Receipt into LegacyReceipt and Type01Receipt

* [ETCM-1095] Unify TransactionGen/ReceiptGen with other generators style
  • Loading branch information
strauss-m authored Sep 8, 2021
1 parent 20c817f commit 2da90e4
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 81 deletions.
18 changes: 15 additions & 3 deletions src/main/scala/io/iohk/ethereum/db/storage/ReceiptStorage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
}
48 changes: 42 additions & 6 deletions src/main/scala/io/iohk/ethereum/domain/Receipt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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")
}
25 changes: 19 additions & 6 deletions src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
35 changes: 28 additions & 7 deletions src/main/scala/io/iohk/ethereum/network/p2p/messages/ETH63.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand All @@ -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
}
}
}

Expand All @@ -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")
}
}
}
Expand Down
27 changes: 16 additions & 11 deletions src/test/scala/io/iohk/ethereum/ObjectGenerators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,33 @@ 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 {
def accessListItemGen: Gen[AccessListItem] = for {
address <- addressGen
storageKeys <- Gen.listOf(bigIntGen)
} yield AccessListItem(address, storageKeys)

def transactionGen(): Gen[Transaction] =
Gen.oneOf(legacyTransactionGen(), typedTransactionGen())
def transactionGen: Gen[Transaction] =
Gen.oneOf(legacyTransactionGen, typedTransactionGen)

def legacyTransactionGen(): Gen[LegacyTransaction] = for {
def legacyTransactionGen: Gen[LegacyTransaction] = for {
nonce <- bigIntGen
gasPrice <- bigIntGen
gasLimit <- bigIntGen
Expand All @@ -106,15 +111,15 @@ trait ObjectGenerators {
payload
)

def typedTransactionGen(): Gen[TransactionWithAccessList] = for {
def typedTransactionGen: Gen[TransactionWithAccessList] = for {
chainId <- bigIntGen
nonce <- bigIntGen
gasPrice <- bigIntGen
gasLimit <- bigIntGen
receivingAddress <- addressGen
value <- bigIntGen
payload <- byteStringOfLengthNGen(256)
accessList <- Gen.listOf(accessListItemGen())
accessList <- Gen.listOf(accessListItemGen)
} yield TransactionWithAccessList(
chainId,
nonce,
Expand All @@ -126,7 +131,7 @@ trait ObjectGenerators {
accessList
)

def receiptsGen(n: Int): Gen[Seq[Seq[Receipt]]] = Gen.listOfN(n, Gen.listOf(receiptGen()))
def receiptsGen(n: Int): Gen[Seq[Seq[Receipt]]] = Gen.listOfN(n, Gen.listOf(receiptGen))

def branchNodeGen: Gen[BranchNode] = for {
children <- Gen
Expand Down Expand Up @@ -167,7 +172,7 @@ trait ObjectGenerators {

def signedTxSeqGen(length: Int, secureRandom: SecureRandom, chainId: Option[Byte]): Gen[Seq[SignedTransaction]] = {
val senderKeys = crypto.generateKeyPair(secureRandom)
val txsSeqGen = Gen.listOfN(length, transactionGen())
val txsSeqGen = Gen.listOfN(length, transactionGen)
txsSeqGen.map { txs =>
txs.map { tx =>
SignedTransaction.sign(tx, senderKeys, chainId)
Expand All @@ -178,7 +183,7 @@ trait ObjectGenerators {
def signedTxGen(secureRandom: SecureRandom, chainId: Option[Byte]): Gen[SignedTransaction] = {
val senderKeys = crypto.generateKeyPair(secureRandom)
for {
tx <- transactionGen()
tx <- transactionGen
} yield SignedTransaction.sign(tx, senderKeys, chainId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,20 @@ class SignedLegacyTransactionSpec
with ScalaCheckPropertyChecks
with SecureRandomBuilder {
"SignedTransaction" should "correctly set pointSign for chainId with chain specific signing schema" in {
forAll(Generators.transactionGen(), Arbitrary.arbitrary[Unit].map(_ => generateKeyPair(secureRandom))) {
(tx, key) =>
val chainId: Byte = 0x3d
val allowedPointSigns = Set((chainId * 2 + 35).toByte, (chainId * 2 + 36).toByte)
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
val address = Address(
crypto
.kec256(key.getPublic.asInstanceOf[ECPublicKeyParameters].getQ.getEncoded(false).tail)
.drop(FirstByteOfAddress)
)
val signedTransaction = SignedTransaction.sign(tx, key, Some(chainId))
val result = SignedTransactionWithSender(signedTransaction, Address(key))
forAll(Generators.transactionGen, Arbitrary.arbitrary[Unit].map(_ => generateKeyPair(secureRandom))) { (tx, key) =>
val chainId: Byte = 0x3d
val allowedPointSigns = Set((chainId * 2 + 35).toByte, (chainId * 2 + 36).toByte)
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
val address = Address(
crypto
.kec256(key.getPublic.asInstanceOf[ECPublicKeyParameters].getQ.getEncoded(false).tail)
.drop(FirstByteOfAddress)
)
val signedTransaction = SignedTransaction.sign(tx, key, Some(chainId))
val result = SignedTransactionWithSender(signedTransaction, Address(key))

allowedPointSigns should contain(result.tx.signature.v)
address shouldEqual result.senderAddress
allowedPointSigns should contain(result.tx.signature.v)
address shouldEqual result.senderAddress
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
Loading

0 comments on commit 2da90e4

Please sign in to comment.