Skip to content

Commit 332959a

Browse files
[ETCM-921] transaction with access list (#1094)
* [ETCM-911] Replace LegacyTX with trait Transaction in SignedTransaction * [ETCM-911] Drop unused 'chainId' parameter from constructor * [ETCM-911] Add rlp serialization basics * [ETCM-911] Add rlp tests showing compatibility with geth * [ETCM-921] rlp access list encoding + unit tests * [ETCM-921] formatting + code review changes * [ETCM-921] code review feedback * [ETCM-921] read chainId from rlp transaction instead of config * [ETCM-921] rlp encoding and decoding test for Seq[SignedTransaction] * [ETCM-921] Introduce PrefixedRLPEncodable to handle binary prefixed RLP items * [ETCM-921] properly decode SignedTransaction in BlockBody + scaladoc * [ETCM-921] Clean magic number and add unit test for PrefixedRLPEncodable * [ETCM-921] Code review feedback changes Co-authored-by: Dominik Zajkowski <[email protected]>
1 parent 9178c64 commit 332959a

File tree

24 files changed

+470
-112
lines changed

24 files changed

+470
-112
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ private[rlp] object RLP {
113113
val inputAsBytes = value.bytes
114114
if (inputAsBytes.length == 1 && (inputAsBytes(0) & 0xff) < 0x80) inputAsBytes
115115
else encodeLength(inputAsBytes.length, OffsetShortItem) ++ inputAsBytes
116+
case PrefixedRLPEncodable(prefix, prefixedRLPEncodeable) =>
117+
prefix +: encode(prefixedRLPEncodeable)
116118
}
117119

118120
/** This function transform a byte into byte array

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ package object rlp {
4444
override def toString: String = s"RLPValue(${Hex.toHexString(bytes)})"
4545
}
4646

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 >= 0, "prefix should be in the range [0; 0x7f]")
62+
}
63+
4764
trait RLPEncoder[T] {
4865
def encode(obj: T): RLPEncodeable
4966
}

src/benchmark/scala/io/iohk/ethereum/rlp/RLPSpeedSuite.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ class RLPSpeedSuite
8686
),
8787
pointSign = 28,
8888
signatureRandom = ByteString(Hex.decode("cfe3ad31d6612f8d787c45f115cc5b43fb22bcc210b62ae71dc7cbf0a6bea8df")),
89-
signature = ByteString(Hex.decode("57db8998114fae3c337e99dbd8573d4085691880f4576c6c1f6c5bbfe67d6cf0")),
90-
chainId = 0x3d.toByte
89+
signature = ByteString(Hex.decode("57db8998114fae3c337e99dbd8573d4085691880f4576c6c1f6c5bbfe67d6cf0"))
9190
)
9291

9392
lazy val blockGen: Gen[Block] = for {

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/domain/SignedTransaction.scala

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.SignedTransaction
2222
import io.iohk.ethereum.rlp.RLPImplicitConversions._
2323
import io.iohk.ethereum.rlp.RLPImplicits._
2424
import io.iohk.ethereum.rlp.{encode => rlpEncode, _}
25+
import io.iohk.ethereum.utils.Config
2526

2627
object SignedTransaction {
2728

@@ -50,22 +51,7 @@ object SignedTransaction {
5051
val valueForEmptyS = 0
5152

5253
def apply(
53-
tx: LegacyTransaction,
54-
pointSign: Byte,
55-
signatureRandom: ByteString,
56-
signature: ByteString,
57-
chainId: Byte
58-
): SignedTransaction = {
59-
val txSignature = ECDSASignature(
60-
r = new BigInteger(1, signatureRandom.toArray),
61-
s = new BigInteger(1, signature.toArray),
62-
v = pointSign
63-
)
64-
SignedTransaction(tx, txSignature)
65-
}
66-
67-
def apply(
68-
tx: LegacyTransaction,
54+
tx: Transaction,
6955
pointSign: Byte,
7056
signatureRandom: ByteString,
7157
signature: ByteString
@@ -79,7 +65,7 @@ object SignedTransaction {
7965
}
8066

8167
def sign(
82-
tx: LegacyTransaction,
68+
tx: Transaction,
8369
keyPair: AsymmetricCipherKeyPair,
8470
chainId: Option[Byte]
8571
): SignedTransaction = {
@@ -101,13 +87,14 @@ object SignedTransaction {
10187

10288
private def calculateSender(tx: SignedTransaction): Option[Address] = Try {
10389
val ECDSASignature(_, _, v) = tx.signature
104-
val bytesToSign: Array[Byte] = if (v == ECDSASignature.negativePointSign || v == ECDSASignature.positivePointSign) {
105-
generalTransactionBytes(tx.tx)
106-
} else {
107-
chainSpecificTransactionBytes(tx.tx, chainId)
90+
// chainId specific code that will be refactored with the Signer feature (ETCM-1096)
91+
val chainIdOpt = extractChainId(tx)
92+
val bytesToSign: Array[Byte] = chainIdOpt match {
93+
case None => generalTransactionBytes(tx.tx)
94+
case Some(chainId) => chainSpecificTransactionBytes(tx.tx, chainId)
10895
}
10996

110-
val recoveredPublicKey: Option[Array[Byte]] = tx.signature.publicKey(bytesToSign, Some(chainId))
97+
val recoveredPublicKey: Option[Array[Byte]] = tx.signature.publicKey(bytesToSign, chainIdOpt)
11198

11299
for {
113100
key <- recoveredPublicKey
@@ -157,6 +144,17 @@ object SignedTransaction {
157144
)
158145
}
159146

147+
private def extractChainId(stx: SignedTransaction): Option[Byte] = {
148+
val chainIdOpt: Option[BigInt] = stx.tx match {
149+
case _: LegacyTransaction
150+
if stx.signature.v == ECDSASignature.negativePointSign || stx.signature.v == ECDSASignature.positivePointSign =>
151+
None
152+
case _: LegacyTransaction => Some(Config.blockchains.blockchainConfig.chainId)
153+
case twal: TransactionWithAccessList => Some(twal.chainId)
154+
}
155+
chainIdOpt.map(_.toByte)
156+
}
157+
160158
val byteArraySerializable: ByteArraySerializable[SignedTransaction] = new ByteArraySerializable[SignedTransaction] {
161159

162160
override def fromBytes(bytes: Array[Byte]): SignedTransaction = bytes.toSignedTransaction
@@ -165,7 +163,7 @@ object SignedTransaction {
165163
}
166164
}
167165

168-
case class SignedTransaction(tx: LegacyTransaction, signature: ECDSASignature) {
166+
case class SignedTransaction(tx: Transaction, signature: ECDSASignature) {
169167

170168
def safeSenderIsEqualTo(address: Address): Boolean =
171169
SignedTransaction.getSender(this).contains(address)

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

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import akka.util.ByteString
44

55
import org.bouncycastle.util.encoders.Hex
66

7-
sealed trait Transaction {
7+
sealed trait Transaction extends Product with Serializable {
88
def nonce: BigInt
99
def gasPrice: BigInt
1010
def gasLimit: BigInt
@@ -22,15 +22,31 @@ sealed trait Transaction {
2222
}
2323

2424
object Transaction {
25-
val Type01: Int = 1
25+
val Type01: Byte = 1.toByte
26+
27+
val MinAllowedType: Byte = 0
28+
val MaxAllowedType: Byte = 0x7f
29+
2630
val LegacyThresholdLowerBound: Int = 0xc0
2731
val LegacyThresholdUpperBound: Int = 0xfe
32+
33+
def withGasLimit(gl: BigInt): Transaction => Transaction = {
34+
case tx: LegacyTransaction => tx.copy(gasLimit = gl)
35+
case tx: TransactionWithAccessList => tx.copy(gasLimit = gl)
36+
}
37+
38+
implicit class TransactionTypeValidator(val transactionType: Byte) extends AnyVal {
39+
def isValidTransactionType: Boolean = transactionType >= MinAllowedType && transactionType <= MaxAllowedType
40+
}
41+
42+
implicit class ByteArrayTransactionTypeValidator(val binaryData: Array[Byte]) extends AnyVal {
43+
def isValidTransactionType: Boolean = binaryData.length == 1 && binaryData.head.isValidTransactionType
44+
}
2845
}
2946

3047
sealed trait TypedTransaction extends Transaction
3148

3249
object LegacyTransaction {
33-
3450
val NonceLength = 32
3551
val GasLength = 32
3652
val ValueLength = 32
@@ -44,7 +60,6 @@ object LegacyTransaction {
4460
payload: ByteString
4561
): LegacyTransaction =
4662
LegacyTransaction(nonce, gasPrice, gasLimit, Some(receivingAddress), value, payload)
47-
4863
}
4964

5065
case class LegacyTransaction(
@@ -66,7 +81,22 @@ case class LegacyTransaction(
6681
s"}"
6782
}
6883

84+
object TransactionWithAccessList {
85+
def apply(
86+
chainId: BigInt,
87+
nonce: BigInt,
88+
gasPrice: BigInt,
89+
gasLimit: BigInt,
90+
receivingAddress: Address,
91+
value: BigInt,
92+
payload: ByteString,
93+
accessList: List[AccessListItem]
94+
): TransactionWithAccessList =
95+
TransactionWithAccessList(chainId, nonce, gasPrice, gasLimit, Some(receivingAddress), value, payload, accessList)
96+
}
97+
6998
case class TransactionWithAccessList(
99+
chainId: BigInt,
70100
nonce: BigInt,
71101
gasPrice: BigInt,
72102
gasLimit: BigInt,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ class BlockPreparator(
9898
* @param tx Target transaction
9999
* @return Upfront cost
100100
*/
101-
private[ledger] def calculateUpfrontGas(tx: LegacyTransaction): UInt256 = UInt256(tx.gasLimit * tx.gasPrice)
101+
private[ledger] def calculateUpfrontGas(tx: Transaction): UInt256 = UInt256(tx.gasLimit * tx.gasPrice)
102102

103103
/** v0 ≡ Tg (Tx gas limit) * Tp (Tx gas price) + Tv (Tx value). See YP equation number (65)
104104
*
105105
* @param tx Target transaction
106106
* @return Upfront cost
107107
*/
108-
private[ledger] def calculateUpfrontCost(tx: LegacyTransaction): UInt256 =
108+
private[ledger] def calculateUpfrontCost(tx: Transaction): UInt256 =
109109
UInt256(calculateUpfrontGas(tx) + tx.value)
110110

111111
/** Increments account nonce by 1 stated in YP equation (69) and

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import io.iohk.ethereum.domain.BlockHeader
88
import io.iohk.ethereum.domain.BlockchainImpl
99
import io.iohk.ethereum.domain.BlockchainReader
1010
import io.iohk.ethereum.domain.SignedTransactionWithSender
11+
import io.iohk.ethereum.domain.Transaction
1112
import io.iohk.ethereum.ledger.TxResult
1213
import io.iohk.ethereum.nodebuilder.BlockchainConfigBuilder
1314
import io.iohk.ethereum.vm.EvmConfig
@@ -68,7 +69,11 @@ class StxLedger(
6869
highLimit
6970
} else {
7071
StxLedger.binaryChop(lowLimit, highLimit) { gasLimit =>
71-
simulateTransaction(stx.copy(tx = tx.copy(tx = tx.tx.copy(gasLimit = gasLimit))), blockHeader, world).vmError
72+
simulateTransaction(
73+
stx.copy(tx = tx.copy(tx = Transaction.withGasLimit(gasLimit)(tx.tx))),
74+
blockHeader,
75+
world
76+
).vmError
7277
}
7378
}
7479
}

0 commit comments

Comments
 (0)