Skip to content

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

New issue

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

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

Already on GitHub? # to your account

Merged
merged 6 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 15 additions & 7 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,11 +78,14 @@ object SignedTransaction {
SignedTransaction(tx, txSignature)
}

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

private def bytesToSign(tx: Transaction, chainId: Option[Byte]): Array[Byte] =
Expand Down Expand Up @@ -157,7 +165,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 +192,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)
}
67 changes: 54 additions & 13 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,32 @@ 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

protected def receivingAddressString: String =
receivingAddress.map(_.toString).getOrElse("[Contract creation]")

protected def payloadString: String =
s"${if (isContractInit) "ContractInit: " else "TransactionData: "}${Hex.toHexString(payload.toArray[Byte])}"
}

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 +42,49 @@ 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
) {

def isContractInit: Boolean = receivingAddress.isEmpty

override def toString: String = {
val receivingAddressString =
receivingAddress.map(addr => Hex.toHexString(addr.toArray)).getOrElse("[Contract creation]")
) extends Transaction {
override def toString: String =
s"LegacyTransaction {" +
s"nonce: $nonce " +
s"gasPrice: $gasPrice " +
s"gasLimit: $gasLimit " +
s"receivingAddress: $receivingAddressString " +
s"value: $value wei " +
s"payload: $payloadString " +
s"}"
}

s"Transaction {" +
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 =
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"payload: $payloadString " +
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
6 changes: 3 additions & 3 deletions src/main/scala/io/iohk/ethereum/keystore/Wallet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import org.bouncycastle.crypto.AsymmetricCipherKeyPair

import io.iohk.ethereum.crypto._
import io.iohk.ethereum.domain.Address
import io.iohk.ethereum.domain.LegacyTransaction
import io.iohk.ethereum.domain.SignedTransaction
import io.iohk.ethereum.domain.SignedTransactionWithSender
import io.iohk.ethereum.domain.Transaction

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

def signTx(tx: Transaction, chainId: Option[Byte]): SignedTransactionWithSender =
SignedTransaction.sign(tx, keyPair, chainId)
def signTx(tx: LegacyTransaction, chainId: Option[Byte]): SignedTransactionWithSender =
SignedTransactionWithSender(SignedTransaction.sign(tx, keyPair, chainId), Address(keyPair))
}
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 @@ -12,8 +12,8 @@ import io.iohk.ethereum.domain.BlockHeader
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.LegacyTransaction
import io.iohk.ethereum.domain.SignedTransaction
import io.iohk.ethereum.domain.Transaction

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
4 changes: 2 additions & 2 deletions 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 Expand Up @@ -62,7 +62,7 @@ object BlockHelpers extends SecureRandomBuilder {
val tx = defaultTx.copy(payload = randomHash())
val stx = SignedTransaction.sign(tx, keyPair, None)

Block(header, BlockBody(List(stx.tx), List(ommer)))
Block(header, BlockBody(List(stx), List(ommer)))
}

def updateHeader(block: Block, updater: BlockHeader => BlockHeader): Block =
Expand Down
Loading