-
Notifications
You must be signed in to change notification settings - Fork 19
/
proposals.go
166 lines (139 loc) · 5.49 KB
/
proposals.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
156
157
158
159
160
161
162
163
164
165
166
package base
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/block-sdk/v2/block/proposals"
)
// DefaultProposalHandler returns a default implementation of the PrepareLaneHandler and
// ProcessLaneHandler.
type DefaultProposalHandler struct {
lane *BaseLane
}
// NewDefaultProposalHandler returns a new default proposal handler.
func NewDefaultProposalHandler(lane *BaseLane) *DefaultProposalHandler {
return &DefaultProposalHandler{
lane: lane,
}
}
// DefaultPrepareLaneHandler returns a default implementation of the PrepareLaneHandler. It
// selects all transactions in the mempool that are valid and not already in the partial
// proposal. It will continue to reap transactions until the maximum blockspace/gas for this
// lane has been reached. Additionally, any transactions that are invalid will be returned.
func (h *DefaultProposalHandler) PrepareLaneHandler() PrepareLaneHandler {
return func(ctx sdk.Context, proposal proposals.Proposal, limit proposals.LaneLimits) ([]sdk.Tx, []sdk.Tx, error) {
var (
totalSize int64
totalGas uint64
txsToInclude []sdk.Tx
txsToRemove []sdk.Tx
)
// Select all transactions in the mempool that are valid and not already in the
// partial proposal.
for iterator := h.lane.Select(ctx, nil); iterator != nil; iterator = iterator.Next() {
tx := iterator.Tx()
txInfo, err := h.lane.GetTxInfo(ctx, tx)
if err != nil {
h.lane.Logger().Info("failed to get hash of tx", "err", err)
txsToRemove = append(txsToRemove, tx)
continue
}
// Double check that the transaction belongs to this lane.
if !h.lane.Match(ctx, tx) {
h.lane.Logger().Info(
"failed to select tx for lane; tx does not belong to lane",
"tx_hash", txInfo.Hash,
"lane", h.lane.Name(),
)
txsToRemove = append(txsToRemove, tx)
continue
}
// if the transaction is already in the (partial) block proposal, we skip it.
if proposal.Contains(txInfo.Hash) {
h.lane.Logger().Info(
"failed to select tx for lane; tx is already in proposal",
"tx_hash", txInfo.Hash,
"lane", h.lane.Name(),
)
continue
}
// If the transaction is too large, we break and do not attempt to include more txs.
if updatedSize := totalSize + txInfo.Size; updatedSize > limit.MaxTxBytes {
h.lane.Logger().Info(
"failed to select tx for lane; tx bytes above the maximum allowed",
"lane", h.lane.Name(),
"tx_size", txInfo.Size,
"total_size", totalSize,
"max_tx_bytes", limit.MaxTxBytes,
"tx_hash", txInfo.Hash,
)
// TODO: Determine if there is any trade off with breaking or continuing here.
continue
}
// If the gas limit of the transaction is too large, we break and do not attempt to include more txs.
if updatedGas := totalGas + txInfo.GasLimit; updatedGas > limit.MaxGasLimit {
h.lane.Logger().Info(
"failed to select tx for lane; gas limit above the maximum allowed",
"lane", h.lane.Name(),
"tx_gas", txInfo.GasLimit,
"total_gas", totalGas,
"max_gas", limit.MaxGasLimit,
"tx_hash", txInfo.Hash,
)
// TODO: Determine if there is any trade off with breaking or continuing here.
continue
}
// Verify the transaction.
if err = h.lane.VerifyTx(ctx, tx, false); err != nil {
h.lane.Logger().Info(
"failed to verify tx",
"tx_hash", txInfo.Hash,
"err", err,
)
txsToRemove = append(txsToRemove, tx)
continue
}
totalSize += txInfo.Size
totalGas += txInfo.GasLimit
txsToInclude = append(txsToInclude, tx)
}
return txsToInclude, txsToRemove, nil
}
}
// DefaultProcessLaneHandler returns a default implementation of the ProcessLaneHandler. It verifies
// the following invariants:
// 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 (h *DefaultProposalHandler) ProcessLaneHandler() ProcessLaneHandler {
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 !h.lane.Match(ctx, tx) {
// 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 := h.lane.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 := h.lane.Compare(ctx, partialProposal[index-1], tx); v == -1 || err != nil {
return nil, nil, fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
}
}
if err := h.lane.VerifyTx(ctx, tx, false); err != nil {
return nil, nil, fmt.Errorf("failed to verify tx: %w", err)
}
}
// 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
}
}