feat: Greedy Algorithm for Lane Verification (#236)

* greedy approach to lane verification

* docs

* base lane testing

* mev lane testing nits

* abci top level testing done

* network spamming in E2E

* removing logs from testing

* nit
This commit is contained in:
David Terpay
2023-11-27 16:43:09 -05:00
committed by GitHub
parent f607439637
commit f7dfbda2b1
21 changed files with 1213 additions and 935 deletions
+18 -19
View File
@@ -76,42 +76,39 @@ func (l *BaseLane) PrepareLane(
func (l *BaseLane) ProcessLane(
ctx sdk.Context,
proposal proposals.Proposal,
txs [][]byte,
txs []sdk.Tx,
next block.ProcessLanesHandler,
) (proposals.Proposal, error) {
l.Logger().Info("processing lane", "lane", l.Name(), "num_txs_to_verify", len(txs))
l.Logger().Info(
"processing lane",
"lane", l.Name(),
"num_txs_to_verify", len(txs),
)
// Assume that this lane is processing sdk.Tx's and decode the transactions.
decodedTxs, err := utils.GetDecodedTxs(l.TxDecoder(), txs)
if err != nil {
l.Logger().Error(
"failed to decode transactions",
"lane", l.Name(),
"err", err,
)
return proposal, err
if len(txs) == 0 {
return next(ctx, proposal, txs)
}
// Verify the transactions that belong to this lane according to the verification logic of the lane.
if err := l.processLaneHandler(ctx, decodedTxs); err != nil {
// Verify the transactions that belong to the lane and return any transactions that must be
// validated by the next lane in the chain.
txsFromLane, remainingTxs, err := l.processLaneHandler(ctx, txs)
if err != nil {
l.Logger().Error(
"failed to process lane",
"lane", l.Name(),
"err", err,
"num_txs_to_verify", len(decodedTxs),
)
return proposal, err
}
// Optimistically update the proposal with the partial proposal.
if err := proposal.UpdateProposal(l, decodedTxs); err != nil {
if err := proposal.UpdateProposal(l, txsFromLane); err != nil {
l.Logger().Error(
"failed to update proposal",
"lane", l.Name(),
"num_txs_verified", len(txsFromLane),
"err", err,
"num_txs_to_verify", len(decodedTxs),
)
return proposal, err
@@ -120,10 +117,12 @@ func (l *BaseLane) ProcessLane(
l.Logger().Info(
"lane processed",
"lane", l.Name(),
"num_txs_verified", len(decodedTxs),
"num_txs_verified", len(txsFromLane),
"num_txs_remaining", len(remainingTxs),
)
return next(ctx, proposal)
// Validate the remaining transactions with the next lane in the chain.
return next(ctx, proposal, remainingTxs)
}
// VerifyTx verifies that the transaction is valid respecting the ante verification logic of
+34 -10
View File
@@ -111,35 +111,59 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
// DefaultProcessLaneHandler returns a default implementation of the ProcessLaneHandler. It verifies
// the following invariants:
// 1. All transactions belong to this lane.
// 2. All transactions respect the priority defined by the mempool.
// 3. All transactions are valid respecting the verification logic of the lane.
// 1. Transactions belonging to the lane must be contiguous from the beginning of the partial proposal.
// 2. Transactions that do not belong to the lane must be contiguous from the end of the partial proposal.
// 3. Transactions must be ordered respecting the priority defined by the lane (e.g. gas price).
// 4. Transactions must be valid according to the verification logic of the lane.
func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler {
return func(ctx sdk.Context, partialProposal []sdk.Tx) error {
// Process all transactions that match the lane's matcher.
return func(ctx sdk.Context, partialProposal []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) {
if len(partialProposal) == 0 {
return nil, nil, nil
}
for index, tx := range partialProposal {
if !l.Match(ctx, tx) {
return fmt.Errorf("the %s lane contains a transaction that belongs to another lane", l.Name())
// If the transaction does not belong to this lane, we return the remaining transactions
// iff there are no matches in the remaining transactions after this index.
if index+1 < len(partialProposal) {
if err := l.VerifyNoMatches(ctx, partialProposal[index+1:]); err != nil {
return nil, nil, fmt.Errorf("failed to verify no matches: %w", err)
}
}
return partialProposal[:index], partialProposal[index:], nil
}
// If the transactions do not respect the priority defined by the mempool, we consider the proposal
// to be invalid
if index > 0 {
if v, err := l.Compare(ctx, partialProposal[index-1], tx); v == -1 || err != nil {
return fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
return nil, nil, fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
}
}
if err := l.VerifyTx(ctx, tx, false); err != nil {
return fmt.Errorf("failed to verify tx: %w", err)
return nil, nil, fmt.Errorf("failed to verify tx: %w", err)
}
}
// This means we have processed all transactions in the partial proposal.
return nil
// This means we have processed all transactions in the partial proposal i.e.
// all of the transactions belong to this lane. There are no remaining transactions.
return partialProposal, nil, nil
}
}
// VerifyNoMatches returns an error if any of the transactions match the lane.
func (l *BaseLane) VerifyNoMatches(ctx sdk.Context, txs []sdk.Tx) error {
for _, tx := range txs {
if l.Match(ctx, tx) {
return fmt.Errorf("transaction belongs to lane when it should not")
}
}
return nil
}
// DefaultMatchHandler returns a default implementation of the MatchHandler. It matches all
// transactions.
func DefaultMatchHandler() MatchHandler {
+10 -6
View File
@@ -21,9 +21,13 @@ type (
) (txsToInclude []sdk.Tx, txsToRemove []sdk.Tx, err error)
// ProcessLaneHandler is responsible for processing transactions that are included in a block and
// belong to a given lane. This handler must return an error if the transactions are not correctly
// ordered, do not belong to this lane, or any other relevant error.
ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) error
// belong to a given lane. The handler must return the transactions that were successfully processed
// and the transactions that it cannot process because they belong to a different lane.
ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) (
txsFromLane []sdk.Tx,
remainingTxs []sdk.Tx,
err error,
)
)
// NoOpPrepareLaneHandler returns a no-op prepare lane handler.
@@ -45,15 +49,15 @@ func PanicPrepareLaneHandler() PrepareLaneHandler {
// NoOpProcessLaneHandler returns a no-op process lane handler.
// This should only be used for testing.
func NoOpProcessLaneHandler() ProcessLaneHandler {
return func(sdk.Context, []sdk.Tx) error {
return nil
return func(sdk.Context, []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) {
return nil, nil, nil
}
}
// PanicProcessLanesHandler returns a process lanes handler that panics.
// This should only be used for testing.
func PanicProcessLaneHandler() ProcessLaneHandler {
return func(sdk.Context, []sdk.Tx) error {
return func(sdk.Context, []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) {
panic("panic process lanes handler")
}
}