Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ETCM-921] Add optional access lists to transactions #1040

Merged
merged 6 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/benchmark/scala/io/iohk/ethereum/rlp/RLPSpeedSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import org.scalacheck.Gen
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.scalacheck.{ScalaCheckDrivenPropertyChecks, ScalaCheckPropertyChecks}

/**
* Tests based on
/** Tests based on
* - https://github.com/cryptape/ruby-rlp/blob/master/test/speed.rb
* - https://github.com/ethereum/pyrlp/blob/develop/tests/speed.py
*/
Expand Down Expand Up @@ -63,21 +62,21 @@ class RLPSpeedSuite
}

def doTestSerialize[T](toSerialize: T, encode: T => Array[Byte], rounds: Int): Array[Byte] = {
(1 until rounds).foreach(_ => {
(1 until rounds).foreach { _ =>
encode(toSerialize)
})
}
encode(toSerialize)
}

def doTestDeserialize[T](serialized: Array[Byte], decode: Array[Byte] => T, rounds: Int): T = {
(1 until rounds).foreach(_ => {
(1 until rounds).foreach { _ =>
decode(serialized)
})
}
decode(serialized)
}

val validTransaction = SignedTransaction(
Transaction(
LegacyTransaction(
nonce = 172320,
gasPrice = BigInt("50000000000"),
gasLimit = 90000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class StdSignedTransactionValidator(blockchainConfig: BlockchainConfig) extends
* @return Either the validated transaction or TransactionSyntaxError if an error was detected
*/
private def checkSyntacticValidity(stx: SignedTransaction): Either[SignedTransactionError, SignedTransactionValid] = {
import Transaction._
import LegacyTransaction._
import stx._
import stx.tx._

Expand Down
25 changes: 17 additions & 8 deletions src/main/scala/io/iohk/ethereum/domain/SignedTransaction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object SignedTransaction {
val valueForEmptyS = 0

def apply(
tx: Transaction,
tx: LegacyTransaction,
pointSign: Byte,
signatureRandom: ByteString,
signature: ByteString,
Expand All @@ -64,7 +64,12 @@ object SignedTransaction {
SignedTransaction(tx, txSignature)
}

def apply(tx: Transaction, pointSign: Byte, signatureRandom: ByteString, signature: ByteString): SignedTransaction = {
def apply(
tx: LegacyTransaction,
pointSign: Byte,
signatureRandom: ByteString,
signature: ByteString
): SignedTransaction = {
val txSignature = ECDSASignature(
r = new BigInteger(1, signatureRandom.toArray),
s = new BigInteger(1, signature.toArray),
Expand All @@ -73,14 +78,18 @@ object SignedTransaction {
SignedTransaction(tx, txSignature)
}

def sign(tx: Transaction, keyPair: AsymmetricCipherKeyPair, chainId: Option[Byte]): SignedTransactionWithSender = {
def sign(
tx: LegacyTransaction,
keyPair: AsymmetricCipherKeyPair,
chainId: Option[Byte]
): SignedTransactionWithSender = {
val bytes = bytesToSign(tx, chainId)
val sig = ECDSASignature.sign(bytes, keyPair, chainId)
val address = Address(keyPair)
SignedTransactionWithSender(tx, sig, address)
}

private def bytesToSign(tx: Transaction, chainId: Option[Byte]): Array[Byte] =
private def bytesToSign(tx: LegacyTransaction, chainId: Option[Byte]): Array[Byte] =
chainId match {
case Some(id) =>
chainSpecificTransactionBytes(tx, id)
Expand Down Expand Up @@ -125,12 +134,12 @@ object SignedTransaction {
private def calculateAndCacheSender(stx: SignedTransaction) =
calculateSender(stx).foreach(address => txSenders.put(stx.hash, address))

private def generalTransactionBytes(tx: Transaction): Array[Byte] = {
private def generalTransactionBytes(tx: LegacyTransaction): Array[Byte] = {
val receivingAddressAsArray: Array[Byte] = tx.receivingAddress.map(_.toArray).getOrElse(Array.emptyByteArray)
crypto.kec256(rlpEncode(RLPList(tx.nonce, tx.gasPrice, tx.gasLimit, receivingAddressAsArray, tx.value, tx.payload)))
}

private def chainSpecificTransactionBytes(tx: Transaction, chainId: Byte): Array[Byte] = {
private def chainSpecificTransactionBytes(tx: LegacyTransaction, chainId: Byte): Array[Byte] = {
val receivingAddressAsArray: Array[Byte] = tx.receivingAddress.map(_.toArray).getOrElse(Array.emptyByteArray)
crypto.kec256(
rlpEncode(
Expand All @@ -157,7 +166,7 @@ object SignedTransaction {
}
}

case class SignedTransaction(tx: Transaction, signature: ECDSASignature) {
case class SignedTransaction(tx: LegacyTransaction, signature: ECDSASignature) {

def safeSenderIsEqualTo(address: Address): Boolean =
SignedTransaction.getSender(this).contains(address)
Expand All @@ -184,6 +193,6 @@ object SignedTransactionWithSender {
sender.fold(acc)(addr => SignedTransactionWithSender(stx, addr) :: acc)
}

def apply(transaction: Transaction, signature: ECDSASignature, sender: Address): SignedTransactionWithSender =
def apply(transaction: LegacyTransaction, signature: ECDSASignature, sender: Address): SignedTransactionWithSender =
SignedTransactionWithSender(SignedTransaction(transaction, signature), sender)
}
55 changes: 49 additions & 6 deletions src/main/scala/io/iohk/ethereum/domain/Transaction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,26 @@ import akka.util.ByteString

import org.bouncycastle.util.encoders.Hex

sealed trait Transaction {
def nonce: BigInt
def gasPrice: BigInt
def gasLimit: BigInt
def receivingAddress: Option[Address]
def value: BigInt
def payload: ByteString

def isContractInit: Boolean = receivingAddress.isEmpty
}

object Transaction {
val Type01: Int = 1
val LegacyThresholdLowerBound: Int = 0xc0
val LegacyThresholdUpperBound: Int = 0xfe
}

sealed trait TypedTransaction extends Transaction

object LegacyTransaction {

val NonceLength = 32
val GasLength = 32
Expand All @@ -17,33 +36,57 @@ object Transaction {
receivingAddress: Address,
value: BigInt,
payload: ByteString
): Transaction =
Transaction(nonce, gasPrice, gasLimit, Some(receivingAddress), value, payload)
): LegacyTransaction =
LegacyTransaction(nonce, gasPrice, gasLimit, Some(receivingAddress), value, payload)

}

case class Transaction(
case class LegacyTransaction(
nonce: BigInt,
gasPrice: BigInt,
gasLimit: BigInt,
receivingAddress: Option[Address],
value: BigInt,
payload: ByteString
) {
) extends Transaction {
override def toString: String = {
val receivingAddressString =
receivingAddress.map(addr => Hex.toHexString(addr.toArray)).getOrElse("[Contract creation]")
dzajkowski marked this conversation as resolved.
Show resolved Hide resolved

def isContractInit: Boolean = receivingAddress.isEmpty
s"LegacyTransaction {" +
s"nonce: $nonce " +
s"gasPrice: $gasPrice " +
s"gasLimit: $gasLimit " +
s"receivingAddress: $receivingAddressString " +
s"value: $value wei " +
s"payload: ${if (isContractInit) "ContractInit: " else "TransactionData: "}${Hex.toHexString(payload.toArray[Byte])} " +
s"}"
}
}

case class TransactionWithAccessList(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this by any chance simply be a Transaction? :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would collide with trait Transaction. or do you have a good name for the base one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TransactionBase maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the deal with typed transactions is that there will be more of them. this one is just the first. I could name it TypedTransaction01.
02 has already been proposed and is being adapted afaik

nonce: BigInt,
gasPrice: BigInt,
gasLimit: BigInt,
receivingAddress: Option[Address],
value: BigInt,
payload: ByteString,
accessList: List[AccessListItem]
) extends TypedTransaction {
override def toString: String = {
val receivingAddressString =
receivingAddress.map(addr => Hex.toHexString(addr.toArray)).getOrElse("[Contract creation]")
dzajkowski marked this conversation as resolved.
Show resolved Hide resolved

s"Transaction {" +
s"TransactionWithAccessList {" +
s"nonce: $nonce " +
s"gasPrice: $gasPrice " +
s"gasLimit: $gasLimit " +
s"receivingAddress: $receivingAddressString " +
s"value: $value wei " +
s"payload: ${if (isContractInit) "ContractInit: " else "TransactionData: "}${Hex.toHexString(payload.toArray[Byte])} " +
s"accessList: $accessList" +
s"}"
}
}

case class AccessListItem(address: Address, storageKeys: List[BigInt]) // bytes32
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import cats.data.EitherT
import monix.eval.Task

import io.iohk.ethereum.domain.Address
import io.iohk.ethereum.domain.Transaction
import io.iohk.ethereum.domain.LegacyTransaction
import io.iohk.ethereum.faucet.FaucetConfig
import io.iohk.ethereum.jsonrpc.client.RpcClient.RpcError
import io.iohk.ethereum.keystore.KeyStore
Expand Down Expand Up @@ -36,7 +36,7 @@ class WalletService(walletRpcClient: WalletRpcClient, keyStore: KeyStore, config

private def prepareTx(wallet: Wallet, targetAddress: Address, nonce: BigInt): ByteString = {
val transaction =
Transaction(nonce, config.txGasPrice, config.txGasLimit, Some(targetAddress), config.txValue, ByteString())
LegacyTransaction(nonce, config.txGasPrice, config.txGasLimit, Some(targetAddress), config.txValue, ByteString())

val stx = wallet.signTx(transaction, None)
ByteString(rlp.encode(stx.tx.toRLPEncodable))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class EthInfoService(

val toAddress = req.tx.to.map(Address.apply)

val tx = Transaction(0, req.tx.gasPrice, gasLimit, toAddress, req.tx.value, req.tx.data)
val tx = LegacyTransaction(0, req.tx.gasPrice, gasLimit, toAddress, req.tx.value, req.tx.data)
val fakeSignature = ECDSASignature(0, 0, 0.toByte)
SignedTransactionWithSender(tx, fakeSignature, fromAddress)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package io.iohk.ethereum.jsonrpc
import akka.util.ByteString

import io.iohk.ethereum.domain.Address
import io.iohk.ethereum.domain.Transaction
import io.iohk.ethereum.domain.LegacyTransaction
import io.iohk.ethereum.utils.Config

case class TransactionRequest(
Expand All @@ -19,8 +19,8 @@ case class TransactionRequest(
private val defaultGasPrice: BigInt = 2 * BigInt(10).pow(10)
private val defaultGasLimit: BigInt = 90000

def toTransaction(defaultNonce: BigInt): Transaction =
Transaction(
def toTransaction(defaultNonce: BigInt): LegacyTransaction =
LegacyTransaction(
nonce = nonce.getOrElse(defaultNonce),
gasPrice = gasPrice.getOrElse(defaultGasPrice),
gasLimit = gasLimit.getOrElse(defaultGasLimit),
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/io/iohk/ethereum/keystore/Wallet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import io.iohk.ethereum.crypto._
import io.iohk.ethereum.domain.Address
import io.iohk.ethereum.domain.SignedTransaction
import io.iohk.ethereum.domain.SignedTransactionWithSender
import io.iohk.ethereum.domain.Transaction
import io.iohk.ethereum.domain.LegacyTransaction

case class Wallet(address: Address, prvKey: ByteString) {
lazy val keyPair: AsymmetricCipherKeyPair = keyPairFromPrvKey(prvKey.toArray)

def signTx(tx: Transaction, chainId: Option[Byte]): SignedTransactionWithSender =
def signTx(tx: LegacyTransaction, chainId: Option[Byte]): SignedTransactionWithSender =
SignedTransaction.sign(tx, keyPair, chainId)
}
4 changes: 2 additions & 2 deletions src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@ class BlockPreparator(
* @param tx Target transaction
* @return Upfront cost
*/
private[ledger] def calculateUpfrontGas(tx: Transaction): UInt256 = UInt256(tx.gasLimit * tx.gasPrice)
private[ledger] def calculateUpfrontGas(tx: LegacyTransaction): UInt256 = UInt256(tx.gasLimit * tx.gasPrice)

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

/** Increments account nonce by 1 stated in YP equation (69) and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ object BaseETH6XMessages {
) =>
val receivingAddressOpt = if (receivingAddress.bytes.isEmpty) None else Some(Address(receivingAddress.bytes))
SignedTransaction(
Transaction(nonce, gasPrice, gasLimit, receivingAddressOpt, value, payload),
LegacyTransaction(nonce, gasPrice, gasLimit, receivingAddressOpt, value, payload),
(pointSign: Int).toByte,
signatureRandom,
signature,
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/io/iohk/ethereum/utils/Picklers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields
import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields._
import io.iohk.ethereum.domain.Checkpoint
import io.iohk.ethereum.domain.SignedTransaction
import io.iohk.ethereum.domain.Transaction
import io.iohk.ethereum.domain.LegacyTransaction

object Picklers {
implicit val byteStringPickler: Pickler[ByteString] =
Expand All @@ -30,9 +30,9 @@ object Picklers {

implicit val addressPickler: Pickler[Address] =
transformPickler[Address, ByteString](bytes => Address(bytes))(address => address.bytes)
implicit val transactionPickler: Pickler[Transaction] = generatePickler[Transaction]
implicit val transactionPickler: Pickler[LegacyTransaction] = generatePickler[LegacyTransaction]
implicit val signedTransactionPickler: Pickler[SignedTransaction] =
transformPickler[SignedTransaction, (Transaction, ECDSASignature)] { case (tx, signature) =>
transformPickler[SignedTransaction, (LegacyTransaction, ECDSASignature)] { case (tx, signature) =>
new SignedTransaction(tx, signature)
}(stx => (stx.tx, stx.signature))

Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/io/iohk/ethereum/BlockHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object BlockHelpers extends SecureRandomBuilder {
unixTimestamp = 0
)

val defaultTx: Transaction = Transaction(
val defaultTx: LegacyTransaction = LegacyTransaction(
nonce = 42,
gasPrice = 1,
gasLimit = 90000,
Expand Down
Loading