89 lines
3.7 KiB
Go
89 lines
3.7 KiB
Go
package abci
|
|
|
|
import (
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/skip-mev/block-sdk/v2/block"
|
|
"github.com/skip-mev/block-sdk/v2/block/proposals"
|
|
"github.com/skip-mev/block-sdk/v2/lanes/terminator"
|
|
)
|
|
|
|
// ChainPrepareLanes chains together the proposal preparation logic from each lane into a
|
|
// single function. The first lane in the chain is the first lane to be prepared and the
|
|
// last lane in the chain is the last lane to be prepared. In the case where any of the lanes
|
|
// fail to prepare the partial proposal, the lane that failed will be skipped and the next
|
|
// lane in the chain will be called to prepare the proposal.
|
|
func ChainPrepareLanes(chain []block.Lane) block.PrepareLanesHandler {
|
|
if len(chain) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Handle non-terminated decorators chain
|
|
if (chain[len(chain)-1] != terminator.Terminator{}) {
|
|
chain = append(chain, terminator.Terminator{})
|
|
}
|
|
|
|
return func(ctx sdk.Context, partialProposal proposals.Proposal) (finalProposal proposals.Proposal, err error) {
|
|
lane := chain[0]
|
|
|
|
// Cache the context in the case where any of the lanes fail to prepare the proposal.
|
|
cacheCtx, write := ctx.CacheContext()
|
|
|
|
// We utilize a recover to handle any panics or errors that occur during the preparation
|
|
// of a lane's transactions. This defer will first check if there was a panic or error
|
|
// thrown from the lane's preparation logic. If there was, we log the error, skip the lane,
|
|
// and call the next lane in the chain to the prepare the proposal.
|
|
defer func() {
|
|
if rec := recover(); rec != nil || err != nil {
|
|
if len(chain) <= 2 {
|
|
// If there are only two lanes remaining, then the first lane in the chain
|
|
// is the lane that failed to prepare the partial proposal and the second lane in the
|
|
// chain is the terminator lane. We return the proposal as is.
|
|
finalProposal, err = partialProposal, nil
|
|
} else {
|
|
// If there are more than two lanes remaining, then the first lane in the chain
|
|
// is the lane that failed to prepare the proposal but the second lane in the
|
|
// chain is not the terminator lane so there could potentially be more transactions
|
|
// added to the proposal
|
|
finalProposal, err = ChainPrepareLanes(chain[1:])(ctx, partialProposal)
|
|
}
|
|
} else {
|
|
// Write the cache to the context since we know that the lane successfully prepared
|
|
// the partial proposal. State is written to in a backwards, cascading fashion. This means
|
|
// that the final context will only be updated after all other lanes have successfully
|
|
// prepared the partial proposal.
|
|
write()
|
|
}
|
|
}()
|
|
|
|
return lane.PrepareLane(
|
|
cacheCtx,
|
|
partialProposal,
|
|
ChainPrepareLanes(chain[1:]),
|
|
)
|
|
}
|
|
}
|
|
|
|
// ChainProcessLanes chains together the proposal verification logic from each lane
|
|
// into a single function. The first lane in the chain is the first lane to be verified and
|
|
// the last lane in the chain is the last lane to be verified. Each lane will validate
|
|
// the transactions that belong to the lane and pass any remaining transactions to the next
|
|
// lane in the chain. If any of the lanes fail to verify the transactions, the proposal will
|
|
// be rejected. If there are any remaining transactions after all lanes have been processed,
|
|
// the proposal will be rejected.
|
|
func ChainProcessLanes(chain []block.Lane) block.ProcessLanesHandler {
|
|
if len(chain) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Handle non-terminated decorators chain
|
|
if (chain[len(chain)-1] != terminator.Terminator{}) {
|
|
chain = append(chain, terminator.Terminator{})
|
|
}
|
|
|
|
return func(ctx sdk.Context, proposal proposals.Proposal, txs []sdk.Tx) (proposals.Proposal, error) {
|
|
lane := chain[0]
|
|
return lane.ProcessLane(ctx, proposal, txs, ChainProcessLanes(chain[1:]))
|
|
}
|
|
}
|