Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

L1 events filtering for enclave submission #2174

Closed
wants to merge 26 commits into from
Closed
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
302 changes: 300 additions & 2 deletions contracts/generated/ManagementContract/ManagementContract.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 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 @@ -179,6 +181,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 Down Expand Up @@ -213,6 +216,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
187 changes: 187 additions & 0 deletions design/host/l1_block_processing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# 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

Loop through transactions to find events we care about in the guardian and submit these with the block to the enclave:

* Rollup
* Secret request
* Cross chain messages
* GrantSequencer events

```go
// L1EventType identifies different types of L1 transactions we care about
type L1EventType int

const (
RollupEvent L1EventType = iota
SecretRequestEvent
CrossChainMessageEvent
SequencerAddedEvent
)

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

// L1Event represents a processed L1 transaction that's relevant to us
type L1EventData struct {
Type L1EventType
Transaction *types.Transaction
Receipt *types.Receipt
BlockHeader *types.Header
Blobs []*kzg4844.Blob // Only populated for blob transactions
}
```
## Guardian
In the guardian we do all the transaction filtering 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?

```go
// This can be added to the guardian, or we include in one of the existing guardian services
type L1Processor struct {
mgmtContractLib mgmtcontractlib.MgmtContractLib
blobResolver BlobResolver
logger gethlog.Logger
}

func (p *L1Processor) ProcessBlock(br *common.BlockAndReceipts) (*common.ProcessedL1Data, error) {
processed := &common.ProcessedL1Data{
BlockHeader: br.BlockHeader,
Events: make(map[L1EventType][]*common.L1EventData),
}

// Extract blobs and hashes once
blobs, blobHashes, err := p.extractBlobsAndHashes(br)
if err != nil {
return nil, fmt.Errorf("failed to extract blobs: %w", err)
}

// Single pass through transactions
for _, tx := range *br.RelevantTransactions() {
decodedTx := p.mgmtContractLib.DecodeTx(tx)
if decodedTx == nil {
continue
}

// Find receipt for this transaction
receipt := br.GetReceipt(tx.Hash())

switch t := decodedTx.(type) {
case *ethadapter.L1RollupHashes:
// Verify blob hashes match for rollups
if err := verifyBlobHashes(t, blobHashes); err != nil {
p.logger.Warn("Invalid rollup blob hashes", "tx", tx.Hash(), "error", err)
continue
}

processed.Events[RollupEvent] = append(processed.Events[RollupEvent], &common.L1EventData{
TxHash: tx.Hash(),
Transaction: tx,
Receipt: receipt,
Blobs: blobs,
})

case *ethadapter.L1RequestSecretTx:
processed.Events[SecretRequestEvent] = append(processed.Events[SecretRequestEvent], &common.L1EventData{
TxHash: tx.Hash(),
Transaction: tx,
Receipt: receipt,
})

case *ethadapter.L1InitializeSecretTx:
processed.Events[SecretRequestEvent] = append(processed.Events[SecretRequestEvent], &common.L1EventData{
TxHash: tx.Hash(),
Transaction: tx,
Receipt: receipt,
})

// Add other event types...
}
}

return processed, nil
}
```

## Enclave side

On the enclave side we handle each of the `processedData.Events[L1EventType]` 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.

```go
func (e *enclaveAdminService) ingestL1Block(ctx context.Context, processedData *common.ProcessedL1Data) (*components.BlockIngestionType, error) {

// Process block first to ensure it's valid and get ingestion type
ingestion, err := e.l1BlockProcessor.Process(ctx, processedData.BlockHeader)
if err != nil {
if errors.Is(err, errutil.ErrBlockAncestorNotFound) || errors.Is(err, errutil.ErrBlockAlreadyProcessed) {
e.logger.Debug("Did not ingest block", log.ErrKey, err, log.BlockHashKey, processedData.BlockHeader.Hash())
} else {
e.logger.Warn("Failed ingesting block", log.ErrKey, err, log.BlockHashKey, processedData.BlockHeader.Hash())
}
return nil, err
}

// Process each event type in order
var secretResponses []*common.ProducedSecretResponse

// rollups
if rollupEvents, exists := processedData.Events[RollupEvent]; exists {
for _, event := range rollupEvents {
if err := e.rollupConsumer.ProcessRollup(ctx, event); err != nil {
if !errors.Is(err, components.ErrDuplicateRollup) {
e.logger.Error("Failed processing rollup", log.ErrKey, err)
// Continue processing other events even if one rollup fails
}
}
}
}

// secret requests
if secretEvents, exists := processedData.Events[SecretRequestEvent]; exists {
for _, event := range secretEvents {
resp, err := e.sharedSecretProcessor.ProcessSecretRequest(ctx, event)
if err != nil {
e.logger.Error("Failed to process secret request", "tx", event.TxHash, "error", err)
continue
}
if resp != nil {
secretResponses = append(secretResponses, resp)
}
}
}

// cross chain messages & add sequencer etc

// Handle any L1 reorg/fork
if ingestion.IsFork() {
e.registry.OnL1Reorg(ingestion)
if err := e.service.OnL1Fork(ctx, ingestion.ChainFork); err != nil {
return nil, err
}
}

// Add secret responses to ingestion result
ingestion.SecretResponses = secretResponses

return ingestion, nil
}
```
2 changes: 1 addition & 1 deletion go/common/enclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ type EnclaveAdmin interface {
// 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, blockHeader *types.Header, processedData *ProcessedL1Data) (*BlockSubmissionResponse, SystemError)

// SubmitBatch submits a batch received from the sequencer for processing.
SubmitBatch(ctx context.Context, batch *ExtBatch) SystemError
Expand Down
9 changes: 4 additions & 5 deletions go/common/host/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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
Expand Down Expand Up @@ -80,13 +79,15 @@ type P2PBatchRequestHandler interface {
type L1BlockRepository interface {
// Subscribe will register a block handler to receive new blocks as they arrive, returns unsubscribe func
Subscribe(handler L1BlockHandler) func()

// FetchBlockByHeight returns a block at a given height
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)
// ExtractTenTransactions returns the tx data and types of those relevant to Ten to be consumed by the enclave
ExtractTenTransactions(block *common.L1Block) (*common.ProcessedL1Data, error)
}

// L1BlockHandler is an interface for receiving new blocks from the repository as they arrive
Expand All @@ -101,10 +102,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(block *types.Block) []*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
Loading