Skip to content
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
96 changes: 2 additions & 94 deletions l2geth/rollup/sync_service.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package rollup

import (
"bytes"
"context"
"errors"
"fmt"
Expand All @@ -11,11 +10,11 @@ import (
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"

"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -707,7 +706,6 @@ func (s *SyncService) maybeApplyTransaction(tx *types.Transaction) error {
// Lower level API used to apply a transaction, must only be used with
// transactions that came from L1.
func (s *SyncService) applyTransaction(tx *types.Transaction) error {
tx = fixType(tx)
txs := types.Transactions{tx}
s.txFeed.Send(core.NewTxsEvent{Txs: txs})
return nil
Expand Down Expand Up @@ -747,7 +745,7 @@ func (s *SyncService) ApplyTransaction(tx *types.Transaction) error {
}

// Set the raw transaction data in the meta
Copy link
Contributor

Choose a reason for hiding this comment

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

Technically the better way to do this is use the raw RLP sent via RPC so that it doesn't need to be reserialized here. The txmeta is created in eth_sendRawTransaction handler and rawTransaction can be set there

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would you do this?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is the line that needs updating:

txMeta := types.NewTransactionMeta(nil, 0, nil, types.SighashEIP155, types.QueueOriginSequencer, nil, nil, nil)

The RLP encoded tx is in scope as a hexutil.Bytes

Copy link
Contributor

Choose a reason for hiding this comment

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

When the RollupClient fetches a tx, it sets the rawTransaction here:

txRaw, err := getRawTransaction(tx)
txRaw, err := rlp.EncodeToBytes(tx)
if err != nil {
return fmt.Errorf("invalid transaction: %w", err)
}
Expand All @@ -766,93 +764,3 @@ func (s *SyncService) ApplyTransaction(tx *types.Transaction) error {

return s.applyTransaction(tx)
}

func getRawTransaction(tx *types.Transaction) ([]byte, error) {
if tx == nil {
return nil, errors.New("Cannot process nil transaction")
}
v, r, s := tx.RawSignatureValues()

// V parameter here will include the chain ID, so we need to recover the original V. If the V
// does not equal zero or one, we have an invalid parameter and need to throw an error.
// This is technically a duplicate check because it happens inside of
// `tx.AsMessage` as well.
v = new(big.Int).SetUint64(v.Uint64() - 35 - 2*tx.ChainId().Uint64())
if v.Uint64() != 0 && v.Uint64() != 1 {
return nil, fmt.Errorf("invalid signature v parameter: %d", v.Uint64())
}

// Since we use a fixed encoding, we need to insert some placeholder address to represent that
// the user wants to create a contract (in this case, the zero address).
var target common.Address
if tx.To() == nil {
target = common.Address{}
} else {
target = *tx.To()
}

// Divide the gas price by one million to compress it
// before it is send to the sequencer entrypoint. This is to save
// space on calldata.
gasPrice := new(big.Int).Div(tx.GasPrice(), new(big.Int).SetUint64(1000000))

// Sequencer uses a custom encoding structure --
// We originally receive sequencer transactions encoded in this way, but we decode them before
// inserting into Geth so we can make transactions easily parseable. However, this means that
// we need to re-encode the transactions before executing them.
var data = new(bytes.Buffer)
data.WriteByte(getSignatureType(tx)) // 1 byte: 00 == EIP 155, 02 == ETH Sign Message
data.Write(fillBytes(r, 32)) // 32 bytes: Signature `r` parameter
data.Write(fillBytes(s, 32)) // 32 bytes: Signature `s` parameter
data.Write(fillBytes(v, 1)) // 1 byte: Signature `v` parameter
data.Write(fillBytes(new(big.Int).SetUint64(tx.Gas()), 3)) // 3 bytes: Gas limit
data.Write(fillBytes(gasPrice, 3)) // 3 bytes: Gas price
data.Write(fillBytes(new(big.Int).SetUint64(tx.Nonce()), 3)) // 3 bytes: Nonce
data.Write(target.Bytes()) // 20 bytes: Target address
data.Write(tx.Data())

return data.Bytes(), nil
}

func fillBytes(x *big.Int, size int) []byte {
b := x.Bytes()
switch {
case len(b) > size:
panic("math/big: value won't fit requested size")
case len(b) == size:
return b
default:
buf := make([]byte, size)
copy(buf[size-len(b):], b)
return buf
}
}

func getSignatureType(tx *types.Transaction) uint8 {
if tx.SignatureHashType() == 0 {
return 0
} else if tx.SignatureHashType() == 1 {
return 2
} else {
return 1
}
}

// This is a temporary fix to patch the enums being used in the raw data
func fixType(tx *types.Transaction) *types.Transaction {
meta := tx.GetMeta()
raw := meta.RawTransaction
if len(raw) == 0 {
log.Error("Transaction with no raw detected")
return tx
}
if raw[0] == 0x00 {
return tx
} else if raw[0] == 0x01 {
raw[0] = 0x02
}
queueOrigin := types.QueueOrigin(meta.QueueOrigin.Uint64())
fixed := types.NewTransactionMeta(meta.L1BlockNumber, meta.L1Timestamp, meta.L1MessageSender, meta.SignatureHashType, queueOrigin, meta.Index, meta.QueueIndex, raw)
tx.SetTransactionMeta(fixed)
return tx
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ pragma experimental ABIEncoderV2;
import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContractAccount.sol";

/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_ECDSAUtils } from "../../libraries/utils/Lib_ECDSAUtils.sol";
import { Lib_EIP155Tx } from "../../libraries/codec/Lib_EIP155Tx.sol";
import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol";

/* Contract Imports */
Expand All @@ -21,13 +20,20 @@ import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
* @title OVM_ECDSAContractAccount
* @dev The ECDSA Contract Account can be used as the implementation for a ProxyEOA deployed by the
* ovmCREATEEOA operation. It enables backwards compatibility with Ethereum's Layer 1, by
* providing eth_sign and EIP155 formatted transaction encodings.
* providing EIP155 formatted transaction encodings.
*
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {

/*************
* Libraries *
*************/

using Lib_EIP155Tx for Lib_EIP155Tx.EIP155Tx;


/*************
* Constants *
*************/
Expand All @@ -44,20 +50,12 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {

/**
* Executes a signed transaction.
* @param _transaction Signed EOA transaction.
* @param _signatureType Hashing scheme used for the transaction (e.g., ETH signed message).
* @param _v Signature `v` parameter.
* @param _r Signature `r` parameter.
* @param _s Signature `s` parameter.
* @param _encodedTransaction Signed EIP155 transaction.
* @return Whether or not the call returned (rather than reverted).
* @return Data returned by the call.
*/
function execute(
bytes memory _transaction,
Lib_OVMCodec.EOASignatureType _signatureType,
uint8 _v,
bytes32 _r,
bytes32 _s
bytes memory _encodedTransaction
)
override
public
Expand All @@ -66,66 +64,46 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
bytes memory
)
{
bool isEthSign = _signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE;
// Attempt to decode the transaction.
Lib_EIP155Tx.EIP155Tx memory transaction = Lib_EIP155Tx.decode(
_encodedTransaction,
Lib_ExecutionManagerWrapper.ovmCHAINID()
);

// Address of this contract within the ovm (ovmADDRESS) should be the same as the
// recovered address of the user who signed this message. This is how we manage to shim
// account abstraction even though the user isn't a contract.
// Need to make sure that the transaction nonce is right and bump it if so.
require(
Lib_ECDSAUtils.recover(
_transaction,
isEthSign,
_v,
_r,
_s
) == address(this),
transaction.sender() == address(this),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Notably the way this code path is currently done is that we re-encode the raw RLP within sender() to do recovery. We could instead recover the sig based on hash(msg.data[4:end]).

A quick aside: can you briefly talk about the reasoning for making the OVM_ECDSAContractAccount accept encoded bytes now as opposed to just a single Lib_EIP155Tx.EIP155Tx argument? Doesn't seem like it makes l2geth any different, and could add redundant encoding/decoding. Just curious to hear it laid out; makes total sense in the OVM_SequencerEntryPoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm yeah interesting, I hadn't thought of passing the EIP155Tx directly. I think that's a great idea. Will do this!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok looking at this now, this introduces enough of a diff that we may want to leave it for another PR + treat it as an optimization if this method is too expensive.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, it's a bit of a tradeoff. Ultimately the most gas-efficient version could just require extcodehash(msg.sender) == SEQUENCER_ENTRYPOINT_CODEHASH and trust that code path explicitly instead of redoing the work. Down to leave for future work.

"Signature provided for EOA transaction execution is invalid."
);

Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction(
_transaction,
isEthSign
);

// Grab the chain ID of the current network.
uint256 chainId;
assembly {
chainId := chainid()
}

// Need to make sure that the transaction chainId is correct.
require(
decodedTx.chainId == chainId,
"Transaction chainId does not match expected OVM chainId."
);

// Need to make sure that the transaction nonce is right.
require(
decodedTx.nonce == Lib_ExecutionManagerWrapper.ovmGETNONCE(),
transaction.nonce == Lib_ExecutionManagerWrapper.ovmGETNONCE(),
"Transaction nonce does not match the expected nonce."
);

// TEMPORARY: Disable gas checks for mainnet.
// // Need to make sure that the gas is sufficient to execute the transaction.
// require(
// gasleft() >= SafeMath.add(decodedTx.gasLimit, EXECUTION_VALIDATION_GAS_OVERHEAD),
// gasleft() >= SafeMath.add(transaction.gasLimit, EXECUTION_VALIDATION_GAS_OVERHEAD),
// "Gas is not sufficient to execute the transaction."
// );

// Transfer fee to relayer.
require(
ovmETH.transfer(
msg.sender,
SafeMath.mul(decodedTx.gasLimit, decodedTx.gasPrice)
SafeMath.mul(transaction.gasLimit, transaction.gasPrice)
),
"Fee was not transferred to relayer."
);

// Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.to == address(0)) {
if (transaction.isCreate) {
(address created, bytes memory revertdata) = Lib_ExecutionManagerWrapper.ovmCREATE(
decodedTx.data
transaction.data
);

// Return true if the contract creation succeeded, false w/ revertdata otherwise.
Expand All @@ -140,7 +118,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// cases, but since this is a contract we'd end up bumping the nonce twice.
Lib_ExecutionManagerWrapper.ovmINCREMENTNONCE();

return decodedTx.to.call(decodedTx.data);
return transaction.to.call(transaction.data);
}
}
}
Loading