Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.

Commit 5740880

Browse files
dvushWazzymandias
andauthored
Check bundle atomicity (#96)
* Check bundle atomicity * track private txs from failed bundles * make sure that every tx in the block is from known source * benchmark * Small refactor * Update benchmarks to n=1000 * log --------- Co-authored-by: Wasif Iqbal <[email protected]>
1 parent 54c8b2e commit 5740880

File tree

3 files changed

+989
-22
lines changed

3 files changed

+989
-22
lines changed

miner/verify_bundles.go

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
package miner
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
"github.com/ethereum/go-ethereum/core/types"
8+
)
9+
10+
// ErrBundleTxNotFound is returned when a tx is not found in the resulting block
11+
type ErrBundleTxNotFound struct {
12+
BundleHash common.Hash
13+
TxHash common.Hash
14+
// Index of the tx in the bundle
15+
TxIndex int
16+
}
17+
18+
func NewErrBundleTxNotFound(bundleHash, txHash common.Hash, txIndex int) *ErrBundleTxNotFound {
19+
return &ErrBundleTxNotFound{
20+
BundleHash: bundleHash,
21+
TxHash: txHash,
22+
TxIndex: txIndex,
23+
}
24+
}
25+
26+
func (e *ErrBundleTxNotFound) Error() string {
27+
return fmt.Sprintf("tx from included bundle not found tx_hash=%s, bundle_hash=%s, tx_bundle_index=%d", e.TxHash.Hex(), e.BundleHash.Hex(), e.TxIndex)
28+
}
29+
30+
// ErrBundleTxReverted is returned when a tx is reverted in the resulting block, but it was not allowed to be reverted
31+
type ErrBundleTxReverted struct {
32+
BundleHash common.Hash
33+
TxHash common.Hash
34+
// Index of the tx in the bundle
35+
TxIndex int
36+
}
37+
38+
func NewErrBundleTxReverted(bundleHash, txHash common.Hash, txIndex int) *ErrBundleTxReverted {
39+
return &ErrBundleTxReverted{
40+
BundleHash: bundleHash,
41+
TxHash: txHash,
42+
TxIndex: txIndex,
43+
}
44+
}
45+
46+
func (e *ErrBundleTxReverted) Error() string {
47+
return fmt.Sprintf("tx from included bundle reverted tx_hash=%s, bundle_hash=%s, tx_bundle_index=%d", e.TxHash.Hex(), e.BundleHash.Hex(), e.TxIndex)
48+
}
49+
50+
// ErrBundleTxWrongPlace is returned when a tx is found in the resulting block, but it is not in the right place
51+
type ErrBundleTxWrongPlace struct {
52+
BundleHash common.Hash
53+
TxHash common.Hash
54+
// Index of the tx in the bundle
55+
TxIndex int
56+
// Index of the tx in the block
57+
BlockIndex int
58+
ExpectedBlockIndex int
59+
}
60+
61+
func NewErrBundleTxWrongPlace(bundleHash, txHash common.Hash, txIndex, blockIndex, expectedBlockIndex int) *ErrBundleTxWrongPlace {
62+
return &ErrBundleTxWrongPlace{
63+
BundleHash: bundleHash,
64+
TxHash: txHash,
65+
TxIndex: txIndex,
66+
BlockIndex: blockIndex,
67+
ExpectedBlockIndex: expectedBlockIndex,
68+
}
69+
}
70+
71+
func (e *ErrBundleTxWrongPlace) Error() string {
72+
return fmt.Sprintf("tx from included bundle is in wrong place tx_hash=%s, bundle_hash=%s, tx_bundle_index=%d, tx_block_index=%d, expected_block_index=%d", e.TxHash.Hex(), e.BundleHash.Hex(), e.TxIndex, e.BlockIndex, e.ExpectedBlockIndex)
73+
}
74+
75+
// ErrPrivateTxFromFailedBundle is returned when a private tx is included in the block, but the bundle it belongs to was not included
76+
type ErrPrivateTxFromFailedBundle struct {
77+
BundleHash common.Hash
78+
TxHash common.Hash
79+
// Index of the tx in the bundle
80+
TxIndex int
81+
}
82+
83+
func NewErrPrivateTxFromFailedBundle(bundleHash, txHash common.Hash, txIndex int) *ErrPrivateTxFromFailedBundle {
84+
return &ErrPrivateTxFromFailedBundle{
85+
BundleHash: bundleHash,
86+
TxHash: txHash,
87+
TxIndex: txIndex,
88+
}
89+
}
90+
91+
func (e *ErrPrivateTxFromFailedBundle) Error() string {
92+
return fmt.Sprintf("private tx from failed bundle included in the block tx_hash=%s, bundle_hash=%s, tx_bundle_index=%d", e.TxHash.Hex(), e.BundleHash.Hex(), e.TxIndex)
93+
}
94+
95+
// ErrUnexpectedTx is returned when a tx is included in the block, but it is not from the mempool or from the included bundles
96+
type ErrUnexpectedTx struct {
97+
TxHash common.Hash
98+
}
99+
100+
func NewErrUnexpectedTx(txHash common.Hash) *ErrUnexpectedTx {
101+
return &ErrUnexpectedTx{
102+
TxHash: txHash,
103+
}
104+
}
105+
106+
func (e *ErrUnexpectedTx) Error() string {
107+
return fmt.Sprintf("unexpected tx included in the block tx_hash=%s", e.TxHash.Hex())
108+
}
109+
110+
// VerifyBundlesAtomicity checks that all txs from the included bundles are included in the block correctly
111+
// 1. We check that all non-reverted txs from the bundle are included in the block and are not reverted
112+
// 2. Reverted txs are allowed to be not included in the block
113+
// 3. All txs from the bundle must be in the right order, gaps between txs are allowed
114+
// 4. All txs in the block are either from mempool or from the included bundles
115+
func VerifyBundlesAtomicity(env *environment, committedBundles, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle, mempoolTxHashes map[common.Hash]struct{}) error {
116+
// bundleHash -> tx
117+
includedBundles := make(bundleHashToTransactionDataMap).
118+
ExtractFromBundles(committedBundles).
119+
ExtractFromSbundles(usedSbundles, true)
120+
121+
includedTxDataByHash := extractIncludedTxDataFromEnv(env)
122+
123+
allUsedBundles := make(bundleHashToTransactionDataMap).
124+
ExtractFromBundles(allBundles).
125+
ExtractFromSbundles(usedSbundles, false)
126+
127+
privateTxDataFromFailedBundles := extractPrivateTxsFromFailedBundles(includedBundles, allUsedBundles, mempoolTxHashes)
128+
129+
return checkBundlesAtomicity(includedBundles, includedTxDataByHash, privateTxDataFromFailedBundles, mempoolTxHashes)
130+
}
131+
132+
type bundleTxData struct {
133+
hash common.Hash
134+
canRevert bool
135+
}
136+
137+
type includedTxData struct {
138+
hash common.Hash
139+
index int
140+
reverted bool
141+
}
142+
143+
type privateTxData struct {
144+
bundleHash common.Hash
145+
index int
146+
}
147+
148+
type bundleHashToTransactionDataMap map[common.Hash][]bundleTxData
149+
150+
func (btm bundleHashToTransactionDataMap) ExtractFromBundles(bundles []types.SimulatedBundle) bundleHashToTransactionDataMap {
151+
for _, b := range bundles {
152+
bundleData := make([]bundleTxData, len(b.OriginalBundle.Txs))
153+
for i, tx := range b.OriginalBundle.Txs {
154+
bundleData[i] = bundleTxData{
155+
hash: tx.Hash(),
156+
canRevert: b.OriginalBundle.RevertingHash(tx.Hash()),
157+
}
158+
}
159+
160+
btm[b.OriginalBundle.Hash] = bundleData
161+
}
162+
return btm
163+
}
164+
165+
func (btm bundleHashToTransactionDataMap) ExtractFromSbundles(sbundles []types.UsedSBundle, onlyIncluded bool) bundleHashToTransactionDataMap {
166+
for _, b := range sbundles {
167+
if onlyIncluded && !b.Success {
168+
continue
169+
}
170+
btm[b.Bundle.Hash()] = getShareBundleTxData(b.Bundle)
171+
}
172+
return btm
173+
}
174+
175+
// checkBundlesAtomicity checks that all txs from the included bundles are included in the block correctly
176+
func checkBundlesAtomicity(
177+
includedBundles map[common.Hash][]bundleTxData,
178+
includedTxDataByHash map[common.Hash]includedTxData,
179+
privateTxsFromFailedBundles map[common.Hash]privateTxData,
180+
mempoolTxHashes map[common.Hash]struct{},
181+
) error {
182+
txsFromSuccessfulBundles := make(map[common.Hash]struct{})
183+
184+
for bundleHash, b := range includedBundles {
185+
var (
186+
firstTxBlockIdx int
187+
firstTxBundleIdx int
188+
)
189+
// 1. locate the first included tx of the bundle
190+
for bundleIdx, tx := range b {
191+
txsFromSuccessfulBundles[tx.hash] = struct{}{}
192+
193+
txInclusion, ok := includedTxDataByHash[tx.hash]
194+
if !ok {
195+
// tx not found, maybe it was reverting
196+
if tx.canRevert {
197+
continue
198+
} else {
199+
return NewErrBundleTxNotFound(bundleHash, tx.hash, bundleIdx)
200+
}
201+
}
202+
203+
if txInclusion.reverted && !tx.canRevert {
204+
return NewErrBundleTxReverted(bundleHash, tx.hash, bundleIdx)
205+
}
206+
207+
firstTxBlockIdx = txInclusion.index
208+
firstTxBundleIdx = bundleIdx
209+
break
210+
}
211+
212+
currentBlockTx := firstTxBlockIdx + 1
213+
// locate other txs in the bundle
214+
for idx, tx := range b[firstTxBundleIdx+1:] {
215+
txsFromSuccessfulBundles[tx.hash] = struct{}{}
216+
217+
bundleIdx := firstTxBundleIdx + 1 + idx
218+
// see if tx is on its place
219+
txInclusion, ok := includedTxDataByHash[tx.hash]
220+
if !ok {
221+
// tx was not found, maybe its reverting
222+
if tx.canRevert {
223+
continue
224+
} else {
225+
return NewErrBundleTxNotFound(bundleHash, tx.hash, bundleIdx)
226+
}
227+
}
228+
229+
// we allow gaps between txs in the bundle,
230+
// but txs must be in the right order
231+
if txInclusion.index < currentBlockTx {
232+
return NewErrBundleTxWrongPlace(bundleHash, tx.hash, bundleIdx, txInclusion.index, currentBlockTx)
233+
}
234+
235+
if txInclusion.reverted && !tx.canRevert {
236+
return NewErrBundleTxReverted(bundleHash, tx.hash, bundleIdx)
237+
}
238+
239+
currentBlockTx = txInclusion.index + 1
240+
}
241+
}
242+
243+
for hash, priv := range privateTxsFromFailedBundles {
244+
if _, ok := txsFromSuccessfulBundles[hash]; ok {
245+
continue
246+
}
247+
if _, ok := includedTxDataByHash[hash]; ok {
248+
return NewErrPrivateTxFromFailedBundle(priv.bundleHash, hash, priv.index)
249+
}
250+
}
251+
252+
for hash := range includedTxDataByHash {
253+
if _, ok := txsFromSuccessfulBundles[hash]; ok {
254+
continue
255+
}
256+
if _, ok := mempoolTxHashes[hash]; ok {
257+
continue
258+
}
259+
return NewErrUnexpectedTx(hash)
260+
}
261+
262+
return nil
263+
}
264+
265+
func extractBundleTxDataFromBundles(bundles []types.SimulatedBundle, result map[common.Hash][]bundleTxData) {
266+
for _, b := range bundles {
267+
bundleData := make([]bundleTxData, len(b.OriginalBundle.Txs))
268+
for i, tx := range b.OriginalBundle.Txs {
269+
bundleData[i] = bundleTxData{
270+
hash: tx.Hash(),
271+
canRevert: b.OriginalBundle.RevertingHash(tx.Hash()),
272+
}
273+
}
274+
result[b.OriginalBundle.Hash] = bundleData
275+
}
276+
}
277+
278+
func getShareBundleTxData(bundle *types.SBundle) []bundleTxData {
279+
res := make([]bundleTxData, 0, len(bundle.Body))
280+
for _, el := range bundle.Body {
281+
if el.Tx != nil {
282+
res = append(res, bundleTxData{
283+
hash: el.Tx.Hash(),
284+
canRevert: el.CanRevert,
285+
})
286+
} else if el.Bundle != nil {
287+
res = append(res, getShareBundleTxData(el.Bundle)...)
288+
}
289+
}
290+
return res
291+
}
292+
293+
func extractBundleTxDataFromSbundles(bundles []types.UsedSBundle, result map[common.Hash][]bundleTxData, onlyIncluded bool) {
294+
for _, b := range bundles {
295+
if onlyIncluded && !b.Success {
296+
continue
297+
}
298+
result[b.Bundle.Hash()] = getShareBundleTxData(b.Bundle)
299+
}
300+
}
301+
302+
func extractIncludedTxDataFromEnv(env *environment) map[common.Hash]includedTxData {
303+
res := make(map[common.Hash]includedTxData)
304+
for i, tx := range env.txs {
305+
if tx != nil {
306+
res[tx.Hash()] = includedTxData{
307+
hash: tx.Hash(),
308+
index: i,
309+
reverted: env.receipts[i].Status == types.ReceiptStatusFailed,
310+
}
311+
}
312+
}
313+
return res
314+
}
315+
316+
func extractPrivateTxsFromFailedBundles(
317+
includedBundles, allBundles map[common.Hash][]bundleTxData, mempoolTxHashes map[common.Hash]struct{},
318+
) map[common.Hash]privateTxData {
319+
// we don't handle overlapping bundles here, they are handled in checkBundlesAtomicity
320+
res := make(map[common.Hash]privateTxData)
321+
322+
for bundleHash, b := range allBundles {
323+
if _, bundleIncluded := includedBundles[bundleHash]; bundleIncluded {
324+
continue
325+
}
326+
327+
for i, tx := range b {
328+
if _, mempool := mempoolTxHashes[tx.hash]; mempool {
329+
continue
330+
}
331+
res[tx.hash] = privateTxData{
332+
bundleHash: bundleHash,
333+
index: i,
334+
}
335+
}
336+
}
337+
return res
338+
}

0 commit comments

Comments
 (0)