Skip to content

Commit

Permalink
Introduce nonce for consensus events on L1
Browse files Browse the repository at this point in the history
  • Loading branch information
mdehoog committed Dec 31, 2024
1 parent f3b9f0c commit 5486868
Show file tree
Hide file tree
Showing 50 changed files with 601 additions and 170 deletions.
3 changes: 1 addition & 2 deletions op-chain-ops/cmd/deposit-hash/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ func main() {

for _, ethLog := range l1Receipt.Logs {
if ethLog.Topics[0].String() == depositLogTopic.String() {

reconstructedDep, err := derive.UnmarshalDepositLogEvent(ethLog)
reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(ethLog)
if err != nil {
log.Crit("Failed to parse deposit event ", "err", err)
}
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/actions/helpers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ func (s *CrossLayerUser) CheckDepositTx(t Testing, l1TxHash common.Hash, index i
require.False(t, l2Success)
} else {
require.Less(t, index, len(depositReceipt.Logs), "must have enough logs in receipt")
reconstructedDep, err := derive.UnmarshalDepositLogEvent(depositReceipt.Logs[index])
reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(depositReceipt.Logs[index])
require.NoError(t, err, "Could not reconstruct L2 Deposit")
l2Tx := types.NewTx(reconstructedDep)
s.L2.CheckReceipt(t, l2Success, l2Tx.Hash())
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/system/bridge/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func TestERC20BridgeDeposits(t *testing.T) {
depositEvent, err := receipts.FindLog(depositReceipt.Logs, portal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")

depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash())
require.NoError(t, err)
Expand Down
6 changes: 3 additions & 3 deletions op-e2e/system/gastoken/gastoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func setCustomGasToken(t *testing.T, cfg e2esys.SystemConfig, sys *e2esys.System

depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")
depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)

require.NoError(t, err)
l2Client := sys.NodeClient("sequencer")
Expand Down Expand Up @@ -326,7 +326,7 @@ func checkDeposit(t *testing.T, gto gasTokenTestOpts, enabled bool) {
// compute the deposit transaction hash + poll for it
depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")
depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash())
require.NoError(t, err)
Expand Down Expand Up @@ -496,7 +496,7 @@ func checkFeeWithdrawal(t *testing.T, gto gasTokenTestOpts, enabled bool) {
// Compute the deposit transaction hash + poll for it
depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")
depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash())
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/system/helpers/tx_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func SendDepositTx(t *testing.T, cfg e2esys.SystemConfig, l1Client *ethclient.Cl
t.Logf("SendDepositTx: included on L1")

// Wait for transaction to be included on L2
reconstructedDep, err := derive.UnmarshalDepositLogEvent(l1Receipt.Logs[0])
reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(l1Receipt.Logs[0])
require.NoError(t, err, "Could not reconstruct L2 Deposit")
tx = types.NewTx(reconstructedDep)
l2Receipt, err := wait.ForReceipt(ctx, l2Client, tx.Hash(), l2Opts.ExpectedStatus)
Expand Down
19 changes: 8 additions & 11 deletions op-node/rollup/derive/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,38 +65,35 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
// case we need to fetch all transaction receipts from the L1 origin block so we can scan for
// user deposits.
if l2Parent.L1Origin.Number != epoch.Number {
info, receipts, err := ba.l1.FetchReceipts(ctx, epoch.Hash)
if err != nil {
var receipts types.Receipts
if l1Info, receipts, err = ba.l1.FetchReceipts(ctx, epoch.Hash); err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err))
}
if l2Parent.L1Origin.Hash != info.ParentHash() {
if l2Parent.L1Origin.Hash != l1Info.ParentHash() {
return nil, NewResetError(
fmt.Errorf("cannot create new block with L1 origin %s (parent %s) on top of L1 origin %s",
epoch, info.ParentHash(), l2Parent.L1Origin))
epoch, l1Info.ParentHash(), l2Parent.L1Origin))
}

deposits, err := DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress)
if err != nil {
if depositTxs, sysConfig.DepositNonce, err = DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress, sysConfig.DepositNonce); err != nil {
// deposits may never be ignored. Failing to process them is a critical error.
return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err))
}

// apply sysCfg changes
if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, info.Time()); err != nil {
if err = UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, l1Info.Time()); err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to apply derived L1 sysCfg updates: %w", err))
}

l1Info = info
depositTxs = deposits
seqNumber = 0
} else {
if l2Parent.L1Origin.Hash != epoch.Hash {
return nil, NewResetError(fmt.Errorf("cannot create new block with L1 origin %s in conflict with L1 origin %s", epoch, l2Parent.L1Origin))
}
info, err := ba.l1.InfoByHash(ctx, epoch.Hash)
l1Info, err = ba.l1.InfoByHash(ctx, epoch.Hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info: %w", err))
}
l1Info = info
depositTxs = nil
seqNumber = l2Parent.SequenceNumber + 1
}
Expand Down
72 changes: 58 additions & 14 deletions op-node/rollup/derive/deposit_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,58 @@ import (
var (
DepositEventABI = "TransactionDeposited(address,address,uint256,bytes)"
DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI))
DepositEventVersion0 = common.Hash{}
DepositEventVersion0 = uint64(0)
DepositEventVersion1 = uint64(1)
)

// UnmarshalDepositLogEventIgnoreNonce decodes an EVM log entry emitted by the deposit contract into typed deposit data.
// Same as UnmarshalDepositLogEvent, but it force-ensures that the deposit nonce is correct.
// This is useful for testing or non-consensus applications.
func UnmarshalDepositLogEventIgnoreNonce(ev *types.Log) (*types.DepositTx, error) {
previousNonce := uint64(0)
if len(ev.Topics) == 4 {
nonce, version := UnpackNonceAndVersion(ev.Topics[3])
switch version {
case DepositEventVersion1:
previousNonce = nonce - 1
}
}
dep, _, err := UnmarshalDepositLogEvent(ev, previousNonce)
return dep, err
}

// UnmarshalDepositLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data.
//
// parse log data for:
//
// event TransactionDeposited(
// address indexed from,
// address indexed to,
// uint256 indexed version,
// uint256 indexed nonceAndVersion,
// bytes opaqueData
// );
//
// Additionally, the event log-index and
func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
func UnmarshalDepositLogEvent(ev *types.Log, currentNonce uint64) (*types.DepositTx, uint64, error) {
if len(ev.Topics) != 4 {
return nil, fmt.Errorf("expected 4 event topics (event identity, indexed from, indexed to, indexed version), got %d", len(ev.Topics))
return nil, currentNonce, fmt.Errorf("expected 4 event topics (event identity, indexed from, indexed to, indexed version), got %d", len(ev.Topics))
}
if ev.Topics[0] != DepositEventABIHash {
return nil, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash)
return nil, currentNonce, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash)
}
if len(ev.Data) < 64 {
return nil, fmt.Errorf("incomplate opaqueData slice header (%d bytes): %x", len(ev.Data), ev.Data)
return nil, currentNonce, fmt.Errorf("incomplate opaqueData slice header (%d bytes): %x", len(ev.Data), ev.Data)
}
if len(ev.Data)%32 != 0 {
return nil, fmt.Errorf("expected log data to be multiple of 32 bytes: got %d bytes", len(ev.Data))
return nil, currentNonce, fmt.Errorf("expected log data to be multiple of 32 bytes: got %d bytes", len(ev.Data))
}

// indexed 0
from := common.BytesToAddress(ev.Topics[1][12:])
// indexed 1
to := common.BytesToAddress(ev.Topics[2][12:])
// indexed 2
version := ev.Topics[3]
nonceAndVersion := ev.Topics[3]
// unindexed data
// Solidity serializes the event's Data field as follows:
// abi.encode(abi.encodPacked(uint256 mint, uint256 value, uint64 gasLimit, uint8 isCreation, bytes data))
Expand All @@ -60,14 +77,14 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
var opaqueContentOffset uint256.Int
opaqueContentOffset.SetBytes(ev.Data[0:32])
if !opaqueContentOffset.IsUint64() || opaqueContentOffset.Uint64() != 32 {
return nil, fmt.Errorf("invalid opaqueData slice header offset: %d", opaqueContentOffset.Uint64())
return nil, currentNonce, fmt.Errorf("invalid opaqueData slice header offset: %d", opaqueContentOffset.Uint64())
}
// The next 32 bytes indicate the length of the opaqueData content.
var opaqueContentLength uint256.Int
opaqueContentLength.SetBytes(ev.Data[32:64])
// Make sure the length is an uint64, it's not larger than the remaining data, and the log is using minimal padding (i.e. can't add 32 bytes without exceeding data)
if !opaqueContentLength.IsUint64() || opaqueContentLength.Uint64() > uint64(len(ev.Data)-64) || opaqueContentLength.Uint64()+32 <= uint64(len(ev.Data)-64) {
return nil, fmt.Errorf("invalid opaqueData slice header length: %d", opaqueContentLength.Uint64())
return nil, currentNonce, fmt.Errorf("invalid opaqueData slice header length: %d", opaqueContentLength.Uint64())
}
// The remaining data is the opaqueData which is tightly packed
// and then padded to 32 bytes by the EVM.
Expand All @@ -83,17 +100,39 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
dep.From = from
dep.IsSystemTransaction = false

newNonce, version := UnpackNonceAndVersion(nonceAndVersion)

var err error
switch version {
case DepositEventVersion0:
err = unmarshalDepositVersion0(&dep, to, opaqueData)
case DepositEventVersion1:
if newNonce != currentNonce+1 {
return nil, currentNonce, fmt.Errorf("invalid deposit nonce, expected %d got %d", currentNonce+1, newNonce)
}
err = unmarshalDepositVersion1(&dep, to, opaqueData)
default:
return nil, fmt.Errorf("invalid deposit version, got %s", version)
return nil, currentNonce, fmt.Errorf("invalid deposit version, got %d", version)
}
if err != nil {
return nil, fmt.Errorf("failed to decode deposit (version %s): %w", version, err)
return nil, currentNonce, fmt.Errorf("failed to decode deposit (version %d): %w", version, err)
}
return &dep, nil
return &dep, newNonce, nil
}

func UnpackNonceAndVersion(nonceAndVersion common.Hash) (nonce uint64, version uint64) {
i := new(big.Int).SetBytes(nonceAndVersion[:])
mask64 := new(big.Int).SetBytes(common.Hex2Bytes("ffffffffffffffff"))
version = mask64.And(mask64, i).Uint64()
nonce = i.Lsh(i, 128).Uint64()
return
}

func PackNonceAndVersion(nonce uint64, version uint64) common.Hash {
i := new(big.Int).SetUint64(nonce)
i.Rsh(i, 128)
i.Or(i, new(big.Int).SetUint64(version))
return common.BytesToHash(i.Bytes())
}

func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueData []byte) error {
Expand Down Expand Up @@ -140,6 +179,11 @@ func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueDat
return nil
}

func unmarshalDepositVersion1(dep *types.DepositTx, to common.Address, opaqueData []byte) error {
// version 1 simply adds a nonce; the rest is the same
return unmarshalDepositVersion0(dep, to, opaqueData)
}

// MarshalDepositLogEvent returns an EVM log entry that encodes a TransactionDeposited event from the deposit contract.
// This is the reverse of the deposit transaction derivation.
func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.DepositTx) (*types.Log, error) {
Expand All @@ -151,7 +195,7 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D
DepositEventABIHash,
eth.AddressAsLeftPaddedHash(deposit.From),
toBytes,
DepositEventVersion0,
PackNonceAndVersion(0, DepositEventVersion0),
}

data := make([]byte, 64, 64+3*32)
Expand Down
4 changes: 2 additions & 2 deletions op-node/rollup/derive/deposit_log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestUnmarshalLogEvent(t *testing.T) {
log.TxIndex = uint(rng.Intn(10000))
log.Index = uint(source.LogIndex)
log.BlockHash = source.L1BlockHash
depOutput, err := UnmarshalDepositLogEvent(log)
depOutput, _, err := UnmarshalDepositLogEvent(log, 0)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -121,7 +121,7 @@ func TestDeriveUserDeposits(t *testing.T) {
if err != nil {
t.Fatal(err)
}
got, err := UserDeposits(receipts, MockDepositContractAddr)
got, _, err := UserDeposits(receipts, MockDepositContractAddr, 0)
require.NoError(t, err)
require.Equal(t, len(got), len(expectedDeposits))
for d, depTx := range got {
Expand Down
12 changes: 6 additions & 6 deletions op-node/rollup/derive/deposit_log_tob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func FuzzDeriveDepositsRoundTrip(f *testing.F) {
receipts, expectedDeposits := fuzzReceipts(typeProvider, blockHash, MockDepositContractAddr)

// Derive our user deposits from the transaction receipts
derivedDeposits, err := UserDeposits(receipts, MockDepositContractAddr)
derivedDeposits, _, err := UserDeposits(receipts, MockDepositContractAddr, 0)
require.NoError(t, err)

// Ensure all deposits we derived matched what we expected to receive.
Expand Down Expand Up @@ -188,21 +188,21 @@ func FuzzDeriveDepositsBadVersion(f *testing.F) {
// Generate any topic but the deposit event versions we support.
// TODO: As opposed to keeping this hardcoded, a method such as IsValidVersion(v) should be
// used here.
badTopic := DepositEventVersion0
for badTopic == DepositEventVersion0 {
typeProvider.Fuzz(&badTopic)
badVersion := DepositEventVersion0
for badVersion == DepositEventVersion0 {
typeProvider.Fuzz(&badVersion)
}

// Set our bad topic and update our state
log.Topics[3] = badTopic
log.Topics[3] = PackNonceAndVersion(0, badVersion)
hasBadDepositVersion = true
}
}
}
}

// Derive our user deposits from the transaction receipts
_, err := UserDeposits(receipts, MockDepositContractAddr)
_, _, err := UserDeposits(receipts, MockDepositContractAddr, 0)

// If we patched a bad deposit version this iteration, we should expect an error and not be able to proceed
// further
Expand Down
14 changes: 8 additions & 6 deletions op-node/rollup/derive/deposits.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// UserDeposits transforms the L2 block-height and L1 receipts into the transaction inputs for a full L2 block
func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]*types.DepositTx, error) {
func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address, currentNonce uint64) ([]*types.DepositTx, uint64, error) {
var out []*types.DepositTx
var result error
for i, rec := range receipts {
Expand All @@ -20,7 +20,8 @@ func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address)
}
for j, log := range rec.Logs {
if log.Address == depositContractAddr && len(log.Topics) > 0 && log.Topics[0] == DepositEventABIHash {
dep, err := UnmarshalDepositLogEvent(log)
dep, newNonce, err := UnmarshalDepositLogEvent(log, currentNonce)
currentNonce = newNonce
if err != nil {
result = multierror.Append(result, fmt.Errorf("malformatted L1 deposit log in receipt %d, log %d: %w", i, j, err))
} else {
Expand All @@ -29,12 +30,13 @@ func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address)
}
}
}
return out, result
return out, currentNonce, result
}

func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]hexutil.Bytes, error) {
func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address, currentNonce uint64) ([]hexutil.Bytes, uint64, error) {
var result error
userDeposits, err := UserDeposits(receipts, depositContractAddr)
userDeposits, newNonce, err := UserDeposits(receipts, depositContractAddr, currentNonce)
currentNonce = newNonce
if err != nil {
result = multierror.Append(result, err)
}
Expand All @@ -47,5 +49,5 @@ func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Addres
encodedTxs = append(encodedTxs, opaqueTx)
}
}
return encodedTxs, result
return encodedTxs, currentNonce, result
}
2 changes: 1 addition & 1 deletion op-node/rollup/derive/fuzz_parsers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) {
depositEvent.Raw = types.Log{} // Clear out the log

// Verify that is passes our custom unmarshalling logic
dep, err := UnmarshalDepositLogEvent(logs[0])
dep, _, err := UnmarshalDepositLogEvent(logs[0], 0)
if err != nil {
t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err)
}
Expand Down
Loading

0 comments on commit 5486868

Please sign in to comment.