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

Commit

Permalink
track private txs from failed bundles
Browse files Browse the repository at this point in the history
  • Loading branch information
dvush committed Aug 10, 2023
1 parent 9987814 commit 99d19b1
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 45 deletions.
88 changes: 81 additions & 7 deletions miner/verify_bundles.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,43 @@ func (e *ErrBundleTxWrongPlace) Error() string {
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)
}

// ErrPrivateTxFromFailedBundle is returned when a private tx is included in the block, but the bundle it belongs to was not included
type ErrPrivateTxFromFailedBundle struct {
BundleHash common.Hash
TxHash common.Hash
// Index of the tx in the bundle
TxIndex int
}

func NewErrPrivateTxFromFailedBundle(bundleHash, txHash common.Hash, txIndex int) *ErrPrivateTxFromFailedBundle {
return &ErrPrivateTxFromFailedBundle{
BundleHash: bundleHash,
TxHash: txHash,
TxIndex: txIndex,
}
}

func (e *ErrPrivateTxFromFailedBundle) Error() string {
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)
}

// VerifyBundlesAtomicity checks that all txs from the included bundles are included in the block correctly
// 1. We check that all non-reverted txs from the bundle are included in the block in correct order (with possible gaps) and are not reverted
// 2. Reverted txs are allowed to be not included in the block
// NOTE: we only verify bundles that were committed in the block but not all bundles that we tried to include
func VerifyBundlesAtomicity(env *environment, commitedBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle) error {
func VerifyBundlesAtomicity(env *environment, committedBundles, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle, mempoolTxHashes map[common.Hash]struct{}) error {
// bundleHash -> tx
includedBundles := make(map[common.Hash][]bundleTxData)
extractBundleTxDataFromBundles(commitedBundles, includedBundles)
extractBundleTxDataFromSbundles(usedSbundles, includedBundles)
extractBundleTxDataFromBundles(committedBundles, includedBundles)
extractBundleTxDataFromSbundles(usedSbundles, includedBundles, true)
includedTxDataByHash := extractIncludedTxDataFromEnv(env)

return checkBundlesAtomicity(includedBundles, includedTxDataByHash)
allUsedBundles := make(map[common.Hash][]bundleTxData)
extractBundleTxDataFromBundles(allBundles, allUsedBundles)
extractBundleTxDataFromSbundles(usedSbundles, allUsedBundles, false)
privateTxDataFromFailedBundles := extractPrivateTxsFromFailedBundles(includedBundles, allUsedBundles, mempoolTxHashes)

return checkBundlesAtomicity(includedBundles, includedTxDataByHash, privateTxDataFromFailedBundles)
}

type bundleTxData struct {
Expand All @@ -97,18 +122,31 @@ type includedTxData struct {
reverted bool
}

type privateTxData struct {
bundleHash common.Hash
index int
}

// checkBundlesAtomicity checks that all txs from the included bundles are included in the block correctly
// 1. We check that all non-reverted txs from the bundle are included in the block and are not reverted
// 2. Reverted txs are allowed to be not included in the block
// 3. All txs from the bundle must be in the right order, gaps between txs are allowed
func checkBundlesAtomicity(includedBundles map[common.Hash][]bundleTxData, includedTxDataByHash map[common.Hash]includedTxData) error {
func checkBundlesAtomicity(
includedBundles map[common.Hash][]bundleTxData,
includedTxDataByHash map[common.Hash]includedTxData,
privateTxsFromFailedBundles map[common.Hash]privateTxData,
) error {
txsFromSuccessfulBundles := make(map[common.Hash]struct{})

for bundleHash, b := range includedBundles {
var (
firstTxBlockIdx int
firstTxBundleIdx int
)
// 1. locate the first included tx of the bundle
for bundleIdx, tx := range b {
txsFromSuccessfulBundles[tx.hash] = struct{}{}

txInclusion, ok := includedTxDataByHash[tx.hash]
if !ok {
// tx not found, maybe it was reverting
Expand All @@ -131,6 +169,8 @@ func checkBundlesAtomicity(includedBundles map[common.Hash][]bundleTxData, inclu
currentBlockTx := firstTxBlockIdx + 1
// locate other txs in the bundle
for idx, tx := range b[firstTxBundleIdx+1:] {
txsFromSuccessfulBundles[tx.hash] = struct{}{}

bundleIdx := firstTxBundleIdx + 1 + idx
// see if tx is on its place
txInclusion, ok := includedTxDataByHash[tx.hash]
Expand All @@ -156,6 +196,16 @@ func checkBundlesAtomicity(includedBundles map[common.Hash][]bundleTxData, inclu
currentBlockTx = txInclusion.index + 1
}
}

for hash, priv := range privateTxsFromFailedBundles {
if _, ok := txsFromSuccessfulBundles[hash]; ok {
continue
}
if _, ok := includedTxDataByHash[hash]; ok {
return NewErrPrivateTxFromFailedBundle(priv.bundleHash, hash, priv.index)
}
}

return nil
}

Expand Down Expand Up @@ -187,9 +237,9 @@ func getShareBundleTxData(bundle *types.SBundle) []bundleTxData {
return res
}

func extractBundleTxDataFromSbundles(bundles []types.UsedSBundle, result map[common.Hash][]bundleTxData) {
func extractBundleTxDataFromSbundles(bundles []types.UsedSBundle, result map[common.Hash][]bundleTxData, onlyIncluded bool) {
for _, b := range bundles {
if !b.Success {
if onlyIncluded && !b.Success {
continue
}
result[b.Bundle.Hash()] = getShareBundleTxData(b.Bundle)
Expand All @@ -209,3 +259,27 @@ func extractIncludedTxDataFromEnv(env *environment) map[common.Hash]includedTxDa
}
return res
}

func extractPrivateTxsFromFailedBundles(
includedBundles, allBundles map[common.Hash][]bundleTxData, mempoolTxHashes map[common.Hash]struct{},
) map[common.Hash]privateTxData {
// we don't handle overlapping bundles here, they are handled in checkBundlesAtomicity
res := make(map[common.Hash]privateTxData)

for bundleHash, b := range allBundles {
if _, bundleIncluded := includedBundles[bundleHash]; bundleIncluded {
continue
}

for i, tx := range b {
if _, mempool := mempoolTxHashes[tx.hash]; mempool {
continue
}
res[tx.hash] = privateTxData{
bundleHash: bundleHash,
index: i,
}
}
}
return res
}
115 changes: 100 additions & 15 deletions miner/verify_bundles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
includedBundles map[common.Hash][]bundleTxData
// includedTxDataByHash is a map of tx hash to tx data that were included in the block
includedTxDataByHash map[common.Hash]includedTxData
// privateTxData is a map of tx hash to private tx data of private txs from failed bundles
privateTxData map[common.Hash]privateTxData
// expectedErr is the expected error returned by verifyBundles
expectedErr error
}{
Expand All @@ -35,7 +37,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: false},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false},
},
expectedErr: nil,
privateTxData: nil,
expectedErr: nil,
},
{
name: "Simple bundle included with gaps between txs",
Expand All @@ -56,7 +59,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xc5"): {hash: common.HexToHash("0xc5"), index: 6, reverted: false},
common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 7, reverted: false},
},
expectedErr: nil,
privateTxData: nil,
expectedErr: nil,
},
{
name: "Simple bundle included with revertible tx, tx included and reverted",
Expand All @@ -74,7 +78,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: true},
common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 4, reverted: false},
},
expectedErr: nil,
privateTxData: nil,
expectedErr: nil,
},
{
name: "Simple bundle included with revertible tx, tx not included",
Expand All @@ -89,7 +94,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 0, reverted: false},
common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 1, reverted: false},
},
expectedErr: nil,
privateTxData: nil,
expectedErr: nil,
},
{
name: "Simple bundle included with all revertible tx, last of them is included as success",
Expand All @@ -105,7 +111,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 1, reverted: false},
common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 2, reverted: true},
},
expectedErr: nil,
privateTxData: nil,
expectedErr: nil,
},
{
name: "Simple bundle included with all revertible tx, none of the txs are included",
Expand All @@ -120,7 +127,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 0, reverted: true},
common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 1, reverted: true},
},
expectedErr: nil,
privateTxData: nil,
expectedErr: nil,
},
{
name: "Two bundles included, both backrun one tx that is allowed to revert",
Expand All @@ -140,6 +148,27 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 2, reverted: true},
common.HexToHash("0xb22"): {hash: common.HexToHash("0xb22"), index: 3, reverted: false},
},
privateTxData: nil,
expectedErr: nil,
},
{
name: "Private tx from overlapping included bundle included",
includedBundles: map[common.Hash][]bundleTxData{
common.HexToHash("0xb1"): {
{hash: common.HexToHash("0xb11"), canRevert: true},
{hash: common.HexToHash("0xb12"), canRevert: false},
},
},
includedTxDataByHash: map[common.Hash]includedTxData{
common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 0, reverted: false},
common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 1, reverted: true},
common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 2, reverted: false},
common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: false},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false},
},
privateTxData: map[common.Hash]privateTxData{
common.HexToHash("0xb11"): {bundleHash: common.HexToHash("0xb2"), index: 2},
},
expectedErr: nil,
},
// Error cases
Expand All @@ -158,7 +187,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: false},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false},
},
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0),
privateTxData: nil,
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0),
},
{
name: "Simple bundle included but with reverted txs (second tx reverted)",
Expand All @@ -175,7 +205,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: true},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false},
},
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1),
privateTxData: nil,
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1),
},
{
name: "Simple bundle included with gaps between txs (second tx reverted)",
Expand All @@ -192,7 +223,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false},
common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 4, reverted: true},
},
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1),
privateTxData: nil,
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1),
},
{
name: "Simple bundle included but with incorrect order",
Expand All @@ -209,7 +241,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 3, reverted: false},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false},
},
expectedErr: NewErrBundleTxWrongPlace(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1, 2, 4),
privateTxData: nil,
expectedErr: NewErrBundleTxWrongPlace(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1, 2, 4),
},
{
name: "Simple bundle included but first tx missing",
Expand All @@ -225,7 +258,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 2, reverted: false},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false},
},
expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0),
privateTxData: nil,
expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0),
},
{
name: "Simple bundle included but second tx missing",
Expand All @@ -241,7 +275,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 2, reverted: false},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false},
},
expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1),
privateTxData: nil,
expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1),
},
{
name: "Bundle with multiple reverting txs in the front has failing tx",
Expand All @@ -259,13 +294,28 @@ func TestVerifyBundlesAtomicity(t *testing.T) {
common.HexToHash("0xb14"): {hash: common.HexToHash("0xb11"), index: 2, reverted: true},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false},
},
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb14"), 3),
privateTxData: nil,
expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb14"), 3),
},
{
name: "Private tx from failed bundles was included in a block",
includedBundles: nil,
includedTxDataByHash: map[common.Hash]includedTxData{
common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 0, reverted: false},
common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 1, reverted: true},
common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 2, reverted: false},
common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 2, reverted: false},
},
privateTxData: map[common.Hash]privateTxData{
common.HexToHash("0xb11"): {bundleHash: common.HexToHash("0xb1"), index: 2},
},
expectedErr: NewErrPrivateTxFromFailedBundle(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 2),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := checkBundlesAtomicity(test.includedBundles, test.includedTxDataByHash)
err := checkBundlesAtomicity(test.includedBundles, test.includedTxDataByHash, test.privateTxData)
if test.expectedErr == nil {
require.NoError(t, err)
} else {
Expand Down Expand Up @@ -319,7 +369,7 @@ func TestExtractBundleDataFromUsedBundles(t *testing.T) {

result := make(map[common.Hash][]bundleTxData)
extractBundleTxDataFromBundles([]types.SimulatedBundle{bundle}, result)
extractBundleTxDataFromSbundles([]types.UsedSBundle{{Bundle: sbundle, Success: true}}, result)
extractBundleTxDataFromSbundles([]types.UsedSBundle{{Bundle: sbundle, Success: true}}, result, true)

require.Equal(t, expectedResult, result)
}
Expand All @@ -346,3 +396,38 @@ func TestExtractIncludedTxDataFromEnv(t *testing.T) {
result := extractIncludedTxDataFromEnv(env)
require.Equal(t, expectedResult, result)
}

func TestExtractPrivateTxData(t *testing.T) {
includedBundles := map[common.Hash][]bundleTxData{
common.HexToHash("0xb1"): {
{hash: common.HexToHash("0xb11"), canRevert: true},
},
common.HexToHash("0xb2"): {
{hash: common.HexToHash("0xb21"), canRevert: true},
},
}
allUsedBundles := map[common.Hash][]bundleTxData{
common.HexToHash("0xb1"): {
{hash: common.HexToHash("0xb11"), canRevert: true},
},
common.HexToHash("0xb2"): {
{hash: common.HexToHash("0xb21"), canRevert: true},
},
common.HexToHash("0xb3"): {
{hash: common.HexToHash("0xb31"), canRevert: true},
{hash: common.HexToHash("0xb32"), canRevert: true},
},
}
mempoolTxHashes := map[common.Hash]struct{}{
common.HexToHash("0xb11"): {},
common.HexToHash("0xb31"): {},
}

expectedResult := map[common.Hash]privateTxData{
common.HexToHash("0xb32"): {bundleHash: common.HexToHash("0xb3"), index: 1},
}

result := extractPrivateTxsFromFailedBundles(includedBundles, allUsedBundles, mempoolTxHashes)

require.Equal(t, expectedResult, result)
}
Loading

0 comments on commit 99d19b1

Please sign in to comment.