88 "encoding/json"
99 "errors"
1010 "hash"
11+ "math/big"
1112 "sort"
1213
1314 "github.com/ethereum/go-ethereum/common"
@@ -28,13 +29,16 @@ const (
2829 BundleTxLimit = 100
2930 MevBundleTxLimit = 50
3031 MevBundleMaxDepth = 1
32+ BundleVersionV1 = "v1"
33+ BundleVersionV2 = "v2"
3134)
3235
3336var (
34- ErrBundleNoTxs = errors .New ("bundle with no txs" )
35- ErrBundleTooManyTxs = errors .New ("too many txs in bundle" )
36- ErrMevBundleUnmatchedTx = errors .New ("mev bundle with unmatched tx" )
37- ErrMevBundleTooDeep = errors .New ("mev bundle too deep" )
37+ ErrBundleNoTxs = errors .New ("bundle with no txs" )
38+ ErrBundleTooManyTxs = errors .New ("too many txs in bundle" )
39+ ErrMevBundleUnmatchedTx = errors .New ("mev bundle with unmatched tx" )
40+ ErrMevBundleTooDeep = errors .New ("mev bundle too deep" )
41+ ErrUnsupportedBundleVersion = errors .New ("unsupported bundle version" )
3842)
3943
4044type EthSendBundleArgs struct {
@@ -44,6 +48,7 @@ type EthSendBundleArgs struct {
4448 MaxTimestamp * uint64 `json:"maxTimestamp,omitempty"`
4549 RevertingTxHashes []common.Hash `json:"revertingTxHashes,omitempty"`
4650 ReplacementUUID * string `json:"replacementUuid,omitempty"`
51+ Version * string `json:"version,omitempty"`
4752
4853 // fields available only when receiving from the Flashbots or other builders, not users
4954 ReplacementNonce * uint64 `json:"replacementNonce,omitempty"`
@@ -186,6 +191,23 @@ func (b *EthSendBundleArgs) UniqueKey() uuid.UUID {
186191 _ = binary .Write (hash , binary .LittleEndian , * b .ReplacementNonce )
187192 }
188193
194+ sort .Slice (b .DroppingTxHashes , func (i , j int ) bool {
195+ return bytes .Compare (b .DroppingTxHashes [i ][:], b .DroppingTxHashes [j ][:]) <= 0
196+ })
197+ for _ , txHash := range b .DroppingTxHashes {
198+ _ , _ = hash .Write (txHash .Bytes ())
199+ }
200+ if b .RefundPercent != nil {
201+ _ = binary .Write (hash , binary .LittleEndian , * b .RefundPercent )
202+ }
203+
204+ if b .RefundRecipient != nil {
205+ _ , _ = hash .Write (b .RefundRecipient .Bytes ())
206+ }
207+ for _ , txHash := range b .RefundTxHashes {
208+ _ , _ = hash .Write ([]byte (txHash ))
209+ }
210+
189211 if b .SigningAddress != nil {
190212 _ , _ = hash .Write (b .SigningAddress .Bytes ())
191213 }
@@ -211,19 +233,136 @@ func (b *EthSendBundleArgs) Validate() (common.Hash, uuid.UUID, error) {
211233 }
212234 hashBytes := hasher .Sum (nil )
213235
214- // then compute the uuid
215- var buf []byte
216- buf = binary .AppendVarint (buf , int64 (blockNumber ))
217- buf = append (buf , hashBytes ... )
218- sort .Slice (b .RevertingTxHashes , func (i , j int ) bool {
219- return bytes .Compare (b .RevertingTxHashes [i ][:], b .RevertingTxHashes [j ][:]) <= 0
220- })
221- for _ , txHash := range b .RevertingTxHashes {
222- buf = append (buf , txHash [:]... )
236+ if b .Version == nil || * b .Version == BundleVersionV1 {
237+ // then compute the uuid
238+ var buf []byte
239+ buf = binary .AppendVarint (buf , int64 (blockNumber ))
240+ buf = append (buf , hashBytes ... )
241+ sort .Slice (b .RevertingTxHashes , func (i , j int ) bool {
242+ return bytes .Compare (b .RevertingTxHashes [i ][:], b .RevertingTxHashes [j ][:]) <= 0
243+ })
244+ for _ , txHash := range b .RevertingTxHashes {
245+ buf = append (buf , txHash [:]... )
246+ }
247+ return common .BytesToHash (hashBytes ),
248+ uuid .NewHash (sha256 .New (), uuid .Nil , buf , 5 ),
249+ nil
223250 }
224- return common .BytesToHash (hashBytes ),
225- uuid .NewHash (sha256 .New (), uuid .Nil , buf , 5 ),
226- nil
251+
252+ if * b .Version == BundleVersionV2 {
253+ // blockNumber, default 0
254+ blockNumber := uint64 (0 )
255+ if b .BlockNumber != nil {
256+ blockNumber = uint64 (* b .BlockNumber )
257+ }
258+
259+ // minTimestamp, default 0
260+ minTimestamp := uint64 (0 )
261+ if b .MinTimestamp != nil {
262+ minTimestamp = * b .MinTimestamp
263+ }
264+
265+ // maxTimestamp, default ^uint64(0) (i.e. 0xFFFFFFFFFFFFFFFF in Rust)
266+ maxTimestamp := ^ uint64 (0 )
267+ if b .MaxTimestamp != nil {
268+ maxTimestamp = * b .MaxTimestamp
269+ }
270+
271+ // Build up our buffer using variable-length encoding of the block
272+ // number, minTimestamp, maxTimestamp, #revertingTxHashes, #droppingTxHashes.
273+ var buf []byte
274+ buf = binary .AppendUvarint (buf , blockNumber )
275+ buf = binary .AppendUvarint (buf , minTimestamp )
276+ buf = binary .AppendUvarint (buf , maxTimestamp )
277+ buf = binary .AppendUvarint (buf , uint64 (len (b .RevertingTxHashes )))
278+ buf = binary .AppendUvarint (buf , uint64 (len (b .DroppingTxHashes )))
279+
280+ // Append the main txs keccak hash (already computed in hashBytes).
281+ buf = append (buf , hashBytes ... )
282+
283+ // Sort revertingTxHashes and append them.
284+ sort .Slice (b .RevertingTxHashes , func (i , j int ) bool {
285+ return bytes .Compare (b .RevertingTxHashes [i ][:], b .RevertingTxHashes [j ][:]) < 0
286+ })
287+ for _ , h := range b .RevertingTxHashes {
288+ buf = append (buf , h [:]... )
289+ }
290+
291+ // Sort droppingTxHashes and append them.
292+ sort .Slice (b .DroppingTxHashes , func (i , j int ) bool {
293+ return bytes .Compare (b .DroppingTxHashes [i ][:], b .DroppingTxHashes [j ][:]) < 0
294+ })
295+ for _ , h := range b .DroppingTxHashes {
296+ buf = append (buf , h [:]... )
297+ }
298+
299+ // If a "refund" is present (analogous to the Rust code), we push:
300+ // refundPercent (1 byte)
301+ // refundRecipient (20 bytes, if an Ethereum address)
302+ // #refundTxHashes (varint)
303+ // each refundTxHash (32 bytes)
304+ // NOTE: The Rust code uses a single byte for `refund.percent`,
305+ // so we do the same here
306+ if b .RefundPercent != nil && * b .RefundPercent != 0 {
307+ if len (b .Txs ) == 0 {
308+ // Bundle with not txs can't be refund-recipient
309+ return common.Hash {}, uuid .Nil , ErrBundleNoTxs
310+ }
311+
312+ // We only keep the low 8 bits of RefundPercent (mimicking Rust's `buff.push(u8)`).
313+ buf = append (buf , byte (* b .RefundPercent ))
314+
315+ refundRecipient := b .RefundRecipient
316+ if refundRecipient == nil {
317+ var tx types.Transaction
318+ if err := tx .UnmarshalBinary (b .Txs [0 ]); err != nil {
319+ return common.Hash {}, uuid .Nil , err
320+ }
321+ from , err := types .Sender (types .LatestSignerForChainID (big .NewInt (1 )), & tx )
322+ if err != nil {
323+ return common.Hash {}, uuid .Nil , err
324+ }
325+ refundRecipient = & from
326+ }
327+ bts := [20 ]byte (* refundRecipient )
328+ // RefundRecipient is a common.Address, which is 20 bytes in geth.
329+ buf = append (buf , bts [:]... )
330+
331+ var refundTxHashes []common.Hash
332+ for _ , rth := range b .RefundTxHashes {
333+ // decode from hex
334+ refundTxHashes = append (refundTxHashes , common .HexToHash (rth ))
335+ }
336+
337+ if len (refundTxHashes ) == 0 {
338+ var lastTx types.Transaction
339+ if err := lastTx .UnmarshalBinary (b .Txs [len (b .Txs )- 1 ]); err != nil {
340+ return common.Hash {}, uuid .Nil , err
341+ }
342+ refundTxHashes = []common.Hash {lastTx .Hash ()}
343+ }
344+
345+ // #refundTxHashes
346+ buf = binary .AppendUvarint (buf , uint64 (len (refundTxHashes )))
347+
348+ sort .Slice (refundTxHashes , func (i , j int ) bool {
349+ return bytes .Compare (refundTxHashes [i ][:], refundTxHashes [j ][:]) < 0
350+ })
351+ for _ , h := range refundTxHashes {
352+ buf = append (buf , h [:]... )
353+ }
354+ }
355+
356+ // Now produce a UUID from `buf` using SHA-256 in the same way the Rust code calls
357+ // `Self::uuid_from_buffer(buff)` (which is effectively a UUIDv5 but with SHA-256).
358+ finalUUID := uuid .NewHash (sha256 .New (), uuid .Nil , buf , 5 )
359+
360+ // Return the main txs keccak hash as well as the computed UUID
361+ return common .BytesToHash (hashBytes ), finalUUID , nil
362+ }
363+
364+ return common.Hash {}, uuid .Nil , ErrUnsupportedBundleVersion
365+
227366}
228367
229368func (b * MevSendBundleArgs ) UniqueKey () uuid.UUID {
0 commit comments