forked from ethereum-optimism/optimism
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
op-supervisor: Include executing message info when storing logs. (eth…
…ereum-optimism#11369) * Rebase: op-supervisor: Include executing message info when storing logs. Takes from aj/parse-exec-msg and makes the following updates: - uses upstream ABI definitions for identifier hash - removes the core recording functionality for the moment - fixes up inconsistent typing and merge conflicts due to rearranged packages * Incorporate new ABI format * remove trailing newline in contract --------- Co-authored-by: Adrian Sutton <[email protected]>
- Loading branch information
1 parent
d098cf8
commit 773e476
Showing
6 changed files
with
341 additions
and
30 deletions.
There are no files selected for viewing
134 changes: 134 additions & 0 deletions
134
op-supervisor/supervisor/backend/source/contracts/l2inbox.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package contracts | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math/big" | ||
|
||
"github.com/ethereum-optimism/optimism/op-service/predeploys" | ||
"github.com/ethereum-optimism/optimism/op-service/solabi" | ||
"github.com/ethereum-optimism/optimism/op-service/sources/batching" | ||
backendTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/types" | ||
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" | ||
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" | ||
"github.com/ethereum/go-ethereum/common" | ||
ethTypes "github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
) | ||
|
||
const ( | ||
eventExecutingMessage = "ExecutingMessage" | ||
) | ||
|
||
var ( | ||
ErrEventNotFound = errors.New("event not found") | ||
) | ||
|
||
type contractIdentifier struct { | ||
// Origin represents the address that initiated the message | ||
// it is used in combination with the MsgHash to uniquely identify a message | ||
// and is hashed into the log hash, not stored directly. | ||
Origin common.Address | ||
LogIndex *big.Int | ||
BlockNumber *big.Int | ||
ChainId *big.Int | ||
Timestamp *big.Int | ||
} | ||
|
||
type CrossL2Inbox struct { | ||
contract *batching.BoundContract | ||
} | ||
|
||
func NewCrossL2Inbox() *CrossL2Inbox { | ||
abi := snapshots.LoadCrossL2InboxABI() | ||
return &CrossL2Inbox{ | ||
contract: batching.NewBoundContract(abi, predeploys.CrossL2InboxAddr), | ||
} | ||
} | ||
|
||
func (i *CrossL2Inbox) DecodeExecutingMessageLog(l *ethTypes.Log) (backendTypes.ExecutingMessage, error) { | ||
if l.Address != i.contract.Addr() { | ||
return backendTypes.ExecutingMessage{}, fmt.Errorf("%w: log not from CrossL2Inbox", ErrEventNotFound) | ||
} | ||
// use DecodeEvent to check the name of the event | ||
// but the actual decoding is done manually to extract the contract identifier | ||
name, _, err := i.contract.DecodeEvent(l) | ||
if errors.Is(err, batching.ErrUnknownEvent) { | ||
return backendTypes.ExecutingMessage{}, fmt.Errorf("%w: %v", ErrEventNotFound, err.Error()) | ||
} else if err != nil { | ||
return backendTypes.ExecutingMessage{}, fmt.Errorf("failed to decode event: %w", err) | ||
} | ||
if name != eventExecutingMessage { | ||
return backendTypes.ExecutingMessage{}, fmt.Errorf("%w: event %v not an ExecutingMessage event", ErrEventNotFound, name) | ||
} | ||
// the second topic is the hash of the payload (the first is the event ID) | ||
msgHash := l.Topics[1] | ||
// the first 32 bytes of the data are the msgHash, so we skip them | ||
identifierBytes := bytes.NewReader(l.Data[32:]) | ||
identifier, err := identifierFromBytes(identifierBytes) | ||
if err != nil { | ||
return backendTypes.ExecutingMessage{}, fmt.Errorf("failed to read contract identifier: %w", err) | ||
} | ||
chainID, err := types.ChainIDFromBig(identifier.ChainId).ToUInt32() | ||
if err != nil { | ||
return backendTypes.ExecutingMessage{}, fmt.Errorf("failed to convert chain ID %v to uint32: %w", identifier.ChainId, err) | ||
} | ||
hash := payloadHashToLogHash(msgHash, identifier.Origin) | ||
return backendTypes.ExecutingMessage{ | ||
Chain: chainID, | ||
Hash: hash, | ||
BlockNum: identifier.BlockNumber.Uint64(), | ||
LogIdx: uint32(identifier.LogIndex.Uint64()), | ||
Timestamp: identifier.Timestamp.Uint64(), | ||
}, nil | ||
} | ||
|
||
// identifierFromBytes reads a contract identifier from a byte stream. | ||
// it follows the spec and matches the CrossL2Inbox.json definition, | ||
// rather than relying on reflection, as that can be error-prone regarding struct ordering | ||
func identifierFromBytes(identifierBytes io.Reader) (contractIdentifier, error) { | ||
origin, err := solabi.ReadAddress(identifierBytes) | ||
if err != nil { | ||
return contractIdentifier{}, fmt.Errorf("failed to read origin address: %w", err) | ||
} | ||
originAddr := common.BytesToAddress(origin[:]) | ||
blockNumber, err := solabi.ReadUint256(identifierBytes) | ||
if err != nil { | ||
return contractIdentifier{}, fmt.Errorf("failed to read block number: %w", err) | ||
} | ||
logIndex, err := solabi.ReadUint256(identifierBytes) | ||
if err != nil { | ||
return contractIdentifier{}, fmt.Errorf("failed to read log index: %w", err) | ||
} | ||
timestamp, err := solabi.ReadUint256(identifierBytes) | ||
if err != nil { | ||
return contractIdentifier{}, fmt.Errorf("failed to read timestamp: %w", err) | ||
} | ||
chainID, err := solabi.ReadUint256(identifierBytes) | ||
if err != nil { | ||
return contractIdentifier{}, fmt.Errorf("failed to read chain ID: %w", err) | ||
} | ||
return contractIdentifier{ | ||
Origin: originAddr, | ||
BlockNumber: blockNumber, | ||
LogIndex: logIndex, | ||
Timestamp: timestamp, | ||
ChainId: chainID, | ||
}, nil | ||
} | ||
|
||
// payloadHashToLogHash converts the payload hash to the log hash | ||
// it is the concatenation of the log's address and the hash of the log's payload, | ||
// which is then hashed again. This is the hash that is stored in the log storage. | ||
// The logHash can then be used to traverse from the executing message | ||
// to the log the referenced initiating message. | ||
// TODO: this function is duplicated between contracts and backend/source/log_processor.go | ||
// to avoid a circular dependency. It should be reorganized to avoid this duplication. | ||
func payloadHashToLogHash(payloadHash common.Hash, addr common.Address) backendTypes.TruncatedHash { | ||
msg := make([]byte, 0, 2*common.HashLength) | ||
msg = append(msg, addr.Bytes()...) | ||
msg = append(msg, payloadHash.Bytes()...) | ||
return backendTypes.TruncateHash(crypto.Keccak256Hash(msg)) | ||
} |
75 changes: 75 additions & 0 deletions
75
op-supervisor/supervisor/backend/source/contracts/l2inbox_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package contracts | ||
|
||
import ( | ||
"bytes" | ||
"math/big" | ||
"testing" | ||
|
||
"github.com/ethereum-optimism/optimism/op-service/predeploys" | ||
"github.com/ethereum-optimism/optimism/op-service/sources/batching" | ||
backendTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/types" | ||
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" | ||
"github.com/ethereum/go-ethereum/common" | ||
ethTypes "github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestDecodeExecutingMessageEvent(t *testing.T) { | ||
inbox := NewCrossL2Inbox() | ||
payload := bytes.Repeat([]byte{0xaa, 0xbb}, 50) | ||
payloadHash := crypto.Keccak256Hash(payload) | ||
expected := backendTypes.ExecutingMessage{ | ||
Chain: 42424, | ||
BlockNum: 12345, | ||
LogIdx: 98, | ||
Timestamp: 9578295, | ||
} | ||
contractIdent := contractIdentifier{ | ||
Origin: common.Address{0xbb, 0xcc}, | ||
ChainId: new(big.Int).SetUint64(uint64(expected.Chain)), | ||
BlockNumber: new(big.Int).SetUint64(expected.BlockNum), | ||
Timestamp: new(big.Int).SetUint64(expected.Timestamp), | ||
LogIndex: new(big.Int).SetUint64(uint64(expected.LogIdx)), | ||
} | ||
expected.Hash = payloadHashToLogHash(payloadHash, contractIdent.Origin) | ||
abi := snapshots.LoadCrossL2InboxABI() | ||
validData, err := abi.Events[eventExecutingMessage].Inputs.Pack(payloadHash, contractIdent) | ||
require.NoError(t, err) | ||
createValidLog := func() *ethTypes.Log { | ||
//protoHack := bytes.Repeat([]byte{0x00}, 32*5) | ||
return ðTypes.Log{ | ||
Address: predeploys.CrossL2InboxAddr, | ||
Topics: []common.Hash{abi.Events[eventExecutingMessage].ID, payloadHash}, | ||
Data: validData, | ||
} | ||
} | ||
|
||
t.Run("ParseValid", func(t *testing.T) { | ||
l := createValidLog() | ||
result, err := inbox.DecodeExecutingMessageLog(l) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, result) | ||
}) | ||
|
||
t.Run("IgnoreIncorrectContract", func(t *testing.T) { | ||
l := createValidLog() | ||
l.Address = common.Address{0xff} | ||
_, err := inbox.DecodeExecutingMessageLog(l) | ||
require.ErrorIs(t, err, ErrEventNotFound) | ||
}) | ||
|
||
t.Run("IgnoreWrongEvent", func(t *testing.T) { | ||
l := createValidLog() | ||
l.Topics[0] = common.Hash{0xbb} | ||
_, err := inbox.DecodeExecutingMessageLog(l) | ||
require.ErrorIs(t, err, ErrEventNotFound) | ||
}) | ||
|
||
t.Run("ErrorOnInvalidEvent", func(t *testing.T) { | ||
l := createValidLog() | ||
l.Data = []byte{0xbb, 0xcc} | ||
_, err := inbox.DecodeExecutingMessageLog(l) | ||
require.ErrorIs(t, err, batching.ErrInvalidEvent) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.