Skip to content

Commit

Permalink
L1 events and block processing (#2209)
Browse files Browse the repository at this point in the history
* homogenise l1 events into singular block processing
  • Loading branch information
badgersrus authored Dec 20, 2024
1 parent a12a05d commit 514417e
Show file tree
Hide file tree
Showing 48 changed files with 1,832 additions and 1,255 deletions.
302 changes: 300 additions & 2 deletions contracts/generated/ManagementContract/ManagementContract.go

Large diffs are not rendered by default.

22 changes: 13 additions & 9 deletions contracts/src/management/ManagementContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ contract ManagementContract is Initializable, OwnableUpgradeable {
event SequencerEnclaveGranted(address enclaveID);
event SequencerEnclaveRevoked(address enclaveID);
event RollupAdded(bytes32 rollupHash);
event NetworkSecretRequested(address indexed requester, string requestReport);
event NetworkSecretResponded(address indexed attester, address indexed requester);

// mapping of enclaveID to whether it is attested
mapping(address => bool) private attested;
Expand Down Expand Up @@ -113,13 +115,13 @@ contract ManagementContract is Initializable, OwnableUpgradeable {
}

function addCrossChainMessagesRoot(bytes32 _lastBatchHash, bytes32 blockHash, uint256 blockNum, bytes[] memory crossChainHashes, bytes calldata signature, uint256 rollupNumber, bytes32 forkID) external {
/* if (block.number > blockNum + 255) {
revert("Block binding too old");
}
/* if (block.number > blockNum + 255) {
revert("Block binding too old");
}
if ((blockhash(blockNum) != blockHash)) {
revert(string(abi.encodePacked("Invalid block binding:", Strings.toString(block.number),":", Strings.toString(uint256(blockHash)), ":", Strings.toString(uint256(blockhash(blockNum))))));
} */
if ((blockhash(blockNum) != blockHash)) {
revert(string(abi.encodePacked("Invalid block binding:", Strings.toString(block.number),":", Strings.toString(uint256(blockHash)), ":", Strings.toString(uint256(blockhash(blockNum))))));
} */

if (rollups.toUniqueForkID[rollupNumber] != forkID) {
revert("Invalid forkID");
Expand Down Expand Up @@ -178,7 +180,7 @@ contract ManagementContract is Initializable, OwnableUpgradeable {

// Enclaves can request the Network Secret given an attestation request report
function RequestNetworkSecret(string calldata requestReport) public {
// currently this is a no-op, nodes will monitor for these transactions and respond to them
emit NetworkSecretRequested(msg.sender, requestReport);
}

function ExtractNativeValue(MessageStructs.Structs.ValueTransferMessage calldata _msg, bytes32[] calldata proof, bytes32 root) external {
Expand All @@ -200,7 +202,7 @@ contract ManagementContract is Initializable, OwnableUpgradeable {
require(isEnclAttested, "responding attester is not attested");

if (verifyAttester) {

// the data must be signed with by the correct private key
// signature = f(PubKey, PrivateKey, message)
// address = f(signature, message)
Expand All @@ -213,6 +215,8 @@ contract ManagementContract is Initializable, OwnableUpgradeable {

// mark the requesterID enclave as an attested enclave and store its host address
attested[requesterID] = true;

emit NetworkSecretResponded(attesterID, requesterID);
}


Expand Down Expand Up @@ -263,4 +267,4 @@ contract ManagementContract is Initializable, OwnableUpgradeable {
function GetImportantContractKeys() public view returns(string[] memory) {
return importantContractKeys;
}
}
}
75 changes: 75 additions & 0 deletions design/host/l1_block_processing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Standardizing L1 Data Processing

## Requirements

1. Standardise the way in which we process l1 blocks to find the relevant data needed for processing on the L2
2. Reduce the processing load on the enclave

## Current Problems
* Multiple redundant loops through L1 block data
* Inconsistent processing patterns
* Unnecessary load on the enclave
* Scattered responsibility for L1 data extraction

## Proposed Solution

Filter logs from blocks using the Management Contract and MessageBus Contract address. Build a map of emitted event types
against the transactions that created them. The events we care about:

* Initialize secret
* Request Secret
* Secret Response
* Rollup
* Cross chain messages
* Value transfers
* Enclave granted sequencer
* Enclave sequencer revoked

```go
const (
RollupTx L1TxType = iota
InitialiseSecretTx
SecretRequestTx
SecretResponseTx
CrossChainMessageTx
CrossChainValueTranserTx
SequencerAddedTx
SequencerRevokedTx
SetImportantContractsTx
)

// ProcessedL1Data is submitted to the enclave by the guardian
type ProcessedL1Data struct {
BlockHeader *types.Header
Events []L1Event
}

// L1Event represents a single event type and its associated transactions
type L1Event struct {
Type uint8
Txs []*L1TxData
}

// L1TxData represents an L1 transaction that are relevant to us
type L1TxData struct {
Transaction *types.Transaction
Receipt *types.Receipt
Blobs []*kzg4844.Blob // Only populated for blob transactions
SequencerEnclaveID gethcommon.Address // Only non-zero when a new enclave is added as a sequencer
CrossChainMessages CrossChainMessages // Only populated for xchain messages
ValueTransfers ValueTransferEvents // Only populated for xchain transfers
Proof []byte // Some merkle proof TBC
}
```
## Guardian
In the guardian we do all the transaction extraction to look for the event types we care about and then submit a
`ProcessedL1Data` object to the enclave via gRPC in the `SubmitL1Block` function.

`TODO` what proof to submit?

## Enclave side

On the enclave side we handle each of the `processedData.GetEvents[L1TxType]` individually and don't have duplicate loops
through the transactions.

Correct ordering of these event processing is going to be the biggest pain point I suspect.
4 changes: 2 additions & 2 deletions go/common/enclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ type EnclaveAdmin interface {
// MakeActive - backup sequencer enclave can become active at the command of the host
MakeActive() SystemError

// SubmitL1Block - Used for the host to submit L1 blocks to the enclave, these may be:
// SubmitL1Block - Used for the host to submit L1 pre-processed blocks to the enclave, these may be:
// a. historic block - if the enclave is behind and in the process of catching up with the L1 state
// b. the latest block published by the L1, to which the enclave should respond with a rollup
// It is the responsibility of the host to gossip the returned rollup
// For good functioning the caller should always submit blocks ordered by height
// submitting a block before receiving ancestors of it, will result in it being ignored
SubmitL1Block(ctx context.Context, blockHeader *types.Header, receipts []*TxAndReceiptAndBlobs) (*BlockSubmissionResponse, SystemError)
SubmitL1Block(ctx context.Context, processed *ProcessedL1Data) (*BlockSubmissionResponse, SystemError)

// SubmitBatch submits a batch received from the sequencer for processing.
SubmitBatch(ctx context.Context, batch *ExtBatch) SystemError
Expand Down
2 changes: 1 addition & 1 deletion go/common/host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ type P2PHostService interface {

type L1RepoService interface {
Service
L1BlockRepository
L1DataService
}
15 changes: 6 additions & 9 deletions go/common/host/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import (
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ten-protocol/go-ten/go/common"
"github.com/ten-protocol/go-ten/go/ethadapter"
)

// service names - these are the keys used to register known services with the host
const (
P2PName = "p2p"
L1BlockRepositoryName = "l1-block-repo"
L1DataServiceName = "l1-data-service"
L1PublisherName = "l1-publisher"
L2BatchRepositoryName = "l2-batch-repo"
EnclaveServiceName = "enclaves"
Expand Down Expand Up @@ -76,17 +75,17 @@ type P2PBatchRequestHandler interface {
HandleBatchRequest(requestID string, fromSeqNo *big.Int)
}

// L1BlockRepository provides an interface for the host to request L1 block data (live-streaming and historical)
type L1BlockRepository interface {
// L1DataService provides an interface for the host to request L1 block data (live-streaming and historical)
type L1DataService interface {
// Subscribe will register a block handler to receive new blocks as they arrive, returns unsubscribe func
Subscribe(handler L1BlockHandler) func()

FetchBlockByHeight(height *big.Int) (*types.Block, error)
// FetchNextBlock returns the next canonical block after a given block hash
// It returns the new block, a bool which is true if the block is the current L1 head and a bool if the block is on a different fork to prevBlock
FetchNextBlock(prevBlock gethcommon.Hash) (*types.Block, bool, error)
// FetchObscuroReceipts returns the receipts for a given L1 block
FetchObscuroReceipts(block *common.L1Block) (types.Receipts, error)
// GetTenRelevantTransactions returns the events and transactions relevant to Ten
GetTenRelevantTransactions(block *common.L1Block) (*common.ProcessedL1Data, error)
}

// L1BlockHandler is an interface for receiving new blocks from the repository as they arrive
Expand All @@ -101,10 +100,8 @@ type L1Publisher interface {
InitializeSecret(attestation *common.AttestationReport, encSecret common.EncryptedSharedEnclaveSecret) error
// RequestSecret will send a management contract transaction to request a secret from the enclave, returning the L1 head at time of sending
RequestSecret(report *common.AttestationReport) (gethcommon.Hash, error)
// ExtractRelevantTenTransactions will return all TEN relevant tx from an L1 block
ExtractRelevantTenTransactions(block *types.Block, receipts types.Receipts) ([]*common.TxAndReceiptAndBlobs, []*ethadapter.L1RollupTx, []*ethadapter.L1SetImportantContractsTx)
// FindSecretResponseTx will return the secret response tx from an L1 block
FindSecretResponseTx(block *types.Block) []*ethadapter.L1RespondSecretTx
FindSecretResponseTx(responseTxs []*common.L1TxData) []*common.L1RespondSecretTx
// PublishRollup will create and publish a rollup tx to the management contract - fire and forget we don't wait for receipt
// todo (#1624) - With a single sequencer, it is problematic if rollup publication fails; handle this case better
PublishRollup(producedRollup *common.ExtRollup)
Expand Down
139 changes: 139 additions & 0 deletions go/common/l1_transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package common

import (
"math/big"

gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
)

// L1TenTransaction is an abstraction that transforms an Ethereum transaction into a format that can be consumed more
// easily by TEN.
type L1TenTransaction interface{}

type L1RollupTx struct {
Rollup EncodedRollup
}

type L1RollupHashes struct {
BlobHashes []gethcommon.Hash
}

type L1DepositTx struct {
Amount *big.Int // Amount to be deposited
To *gethcommon.Address // Address the ERC20 Transfer was made to (always be the Management Contract Addr)
Sender *gethcommon.Address // Address that issued the ERC20, the token holder or tx.origin
TokenContract *gethcommon.Address // Address of the ERC20 Contract address that was executed
}

type L1RespondSecretTx struct {
Secret []byte
RequesterID gethcommon.Address
AttesterID gethcommon.Address
AttesterSig []byte
}

type L1SetImportantContractsTx struct {
Key string
NewAddress gethcommon.Address
}

type L1RequestSecretTx struct {
Attestation EncodedAttestationReport
}

type L1InitializeSecretTx struct {
EnclaveID *gethcommon.Address
InitialSecret []byte
Attestation EncodedAttestationReport
}

// The following types and structs are used for processing the l1 blocks and categorising the transactions to be processed
// by the enclave.

// L1TenEventType represents different types of L1 transactions we monitor for
type L1TenEventType uint8 // Change to uint8 for RLP serialization

const (
RollupTx L1TenEventType = iota
InitialiseSecretTx
SecretRequestTx
SecretResponseTx
CrossChainMessageTx
CrossChainValueTranserTx
SequencerAddedTx
SequencerRevokedTx
SetImportantContractsTx
)

// ProcessedL1Data is submitted to the enclave by the guardian
type ProcessedL1Data struct {
BlockHeader *types.Header
Events []L1Event
}

// L1Event represents a single event type and its associated transactions
type L1Event struct {
Type uint8
Txs []*L1TxData
}

// L1TxData represents an L1 transaction that are relevant to us
type L1TxData struct {
Transaction *types.Transaction
Receipt *types.Receipt
Blobs []*kzg4844.Blob // Only populated for blob transactions
SequencerEnclaveID gethcommon.Address // Only non-zero when a new enclave is added as a sequencer
CrossChainMessages CrossChainMessages // Only populated for xchain messages
ValueTransfers ValueTransferEvents // Only populated for xchain transfers
Proof []byte // Some merkle proof TBC
}

// HasSequencerEnclaveID helper method to check if SequencerEnclaveID is set to avoid custom RLP when we send over grpc
func (tx *L1TxData) HasSequencerEnclaveID() bool {
return tx.SequencerEnclaveID != (gethcommon.Address{})
}

func (p *ProcessedL1Data) AddEvent(tenEventType L1TenEventType, tx *L1TxData) {
eventType := uint8(tenEventType)

for i := range p.Events {
if p.Events[i].Type != eventType {
continue
}

txHash := tx.Transaction.Hash()

// check for duplicate transaction
for _, existingTx := range p.Events[i].Txs {
if existingTx.Transaction.Hash() == txHash {
return // Skip duplicate transaction
}
}

p.Events[i].Txs = append(p.Events[i].Txs, tx)
return
}

p.Events = append(p.Events, L1Event{
Type: eventType,
Txs: []*L1TxData{tx},
})
}

func (p *ProcessedL1Data) GetEvents(txType L1TenEventType) []*L1TxData {
if p == nil || len(p.Events) == 0 {
return nil
}

for _, event := range p.Events {
if event.Type == uint8(txType) {
if event.Txs == nil {
return nil
}
return event.Txs
}
}
return nil
}
Loading

0 comments on commit 514417e

Please sign in to comment.