-
Notifications
You must be signed in to change notification settings - Fork 18
/
abci.go
155 lines (134 loc) · 5.83 KB
/
abci.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package abci
import (
"fmt"
"cosmossdk.io/log"
abci "github.com/cometbft/cometbft/abci/types"
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/block/utils"
)
type (
// ProposalHandler is a wrapper around the ABCI++ PrepareProposal and ProcessProposal
// handlers.
ProposalHandler struct {
logger log.Logger
txDecoder sdk.TxDecoder
txEncoder sdk.TxEncoder
prepareLanesHandler block.PrepareLanesHandler
mempool block.Mempool
}
)
// NewProposalHandler returns a new ABCI++ proposal handler. This proposal handler will
// iteratively call each of the lanes in the chain to prepare and process the proposal.
func NewProposalHandler(
logger log.Logger,
txDecoder sdk.TxDecoder,
txEncoder sdk.TxEncoder,
mempool block.Mempool,
) *ProposalHandler {
return &ProposalHandler{
logger: logger,
txDecoder: txDecoder,
txEncoder: txEncoder,
prepareLanesHandler: ChainPrepareLanes(mempool.Registry()),
mempool: mempool,
}
}
// PrepareProposalHandler prepares the proposal by selecting transactions from each lane
// according to each lane's selection logic. We select transactions in the order in which the
// lanes are configured on the chain. Note that each lane has an boundary on the number of
// bytes/gas that can be included in the proposal. By default, the default lane will not have
// a boundary on the number of bytes that can be included in the proposal and will include all
// valid transactions in the proposal (up to MaxBlockSize, MaxGasLimit).
func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) {
if req.Height <= 1 {
return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil
}
// In the case where there is a panic, we recover here and return an empty proposal.
defer func() {
if rec := recover(); rec != nil {
h.logger.Error("failed to prepare proposal", "err", err)
// TODO: Should we attempt to return a empty proposal here with empty proposal info?
resp = &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}
err = fmt.Errorf("failed to prepare proposal: %v", rec)
}
}()
h.logger.Info(
"mempool distribution before proposal creation",
"distribution", h.mempool.GetTxDistribution(),
"height", req.Height,
)
// Get the max gas limit and max block size for the proposal.
_, maxGasLimit := proposals.GetBlockLimits(ctx)
proposal := proposals.NewProposal(h.logger, req.MaxTxBytes, maxGasLimit)
// Fill the proposal with transactions from each lane.
finalProposal, err := h.prepareLanesHandler(ctx, proposal)
if err != nil {
h.logger.Error("failed to prepare proposal", "err", err)
return &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}, err
}
h.logger.Info(
"prepared proposal",
"num_txs", len(finalProposal.Txs),
"total_tx_bytes", finalProposal.Info.BlockSize,
"max_tx_bytes", finalProposal.Info.MaxBlockSize,
"total_gas_limit", finalProposal.Info.GasLimit,
"max_gas_limit", finalProposal.Info.MaxGasLimit,
"height", req.Height,
)
h.logger.Info(
"mempool distribution after proposal creation",
"distribution", h.mempool.GetTxDistribution(),
"height", req.Height,
)
return &abci.ResponsePrepareProposal{
Txs: finalProposal.Txs,
}, nil
}
}
// ProcessProposalHandler processes the proposal by verifying all transactions in the proposal
// according to each lane's verification logic. Proposals are verified similar to how they are
// constructed. After a proposal is processed, it should amount to the same proposal that was prepared.
// The proposal is verified in a greedy fashion, respecting the ordering of lanes. A lane will
// verify all transactions in the proposal that belong to the lane and pass any remaining transactions
// to the next lane in the chain.
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) {
if req.Height <= 1 {
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
}
// In the case where any of the lanes panic, we recover here and return a reject status.
defer func() {
if rec := recover(); rec != nil {
h.logger.Error("failed to process proposal", "recover_err", rec)
resp = &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
err = fmt.Errorf("failed to process proposal: %v", rec)
}
}()
// Decode the transactions in the proposal. These will be verified by each lane in a greedy fashion.
decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, req.Txs)
if err != nil {
h.logger.Error("failed to decode txs", "err", err)
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
}
// Build handler that will verify the partial proposals according to each lane's verification logic.
processLanesHandler := ChainProcessLanes(h.mempool.Registry())
finalProposal, err := processLanesHandler(ctx, proposals.NewProposalWithContext(ctx, h.logger), decodedTxs)
if err != nil {
h.logger.Error("failed to validate the proposal", "err", err)
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
}
h.logger.Info(
"processed proposal",
"num_txs", len(finalProposal.Txs),
"total_tx_bytes", finalProposal.Info.BlockSize,
"max_tx_bytes", finalProposal.Info.MaxBlockSize,
"total_gas_limit", finalProposal.Info.GasLimit,
"max_gas_limit", finalProposal.Info.MaxGasLimit,
"height", req.Height,
)
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
}
}