Skip to content

Commit 54bad8f

Browse files
authored
Merge pull request #413 from bane-labs/antimev-envelope-price
antimev: add envelope fee policy and check
2 parents cae241b + 3a2f7f5 commit 54bad8f

File tree

17 files changed

+171
-23
lines changed

17 files changed

+171
-23
lines changed

antimev/envelope.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,24 @@ var (
4040
// IsEnvelope checks whether a transaction is an Envelope transaction. The criteria
4141
// include receiver's address, data prefix and data length check.
4242
func IsEnvelope(tx *types.Transaction) bool {
43-
if tx.To() == nil || *(tx.To()) != systemcontracts.GovernanceRewardProxyHash {
43+
return IsEnvelopeToAddress(tx.To()) && IsEnvelopeData(tx.Data())
44+
}
45+
46+
// IsEnvelopeToAddress checks whether an address pointer has the expected value for
47+
// Envelope To address.
48+
func IsEnvelopeToAddress(addr *common.Address) bool {
49+
if addr == nil || *addr != systemcontracts.GovernanceRewardProxyHash {
4450
return false
4551
}
52+
return true
53+
}
4654

47-
data := tx.Data()
55+
// IsEnvelopeData checks whether the input data bytes has the expected prefix for
56+
// Envelope specification.
57+
func IsEnvelopeData(data []byte) bool {
4858
if len(data) < minEncryptedDataSize || !bytes.HasPrefix(data, EncryptedDataPrefix) {
4959
return false
5060
}
51-
5261
return true
5362
}
5463

contracts/solidity/Policy.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
1717
uint256 public minGasTipCap;
1818
uint256 public baseFee;
1919
uint256 internal candidateLimit;
20+
uint256 public envelopeFee;
2021

2122
// Only for precompiled uups implementation in genesis file, need to be removed when upgrading the contract.
2223
// This override is added because "immutable __self" in UUPSUpgradeable is not avaliable in precompiled contract.
@@ -130,4 +131,21 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
130131
if (limit > 0) return limit;
131132
else return DEFAULT_CANDIDATE_LIMIT;
132133
}
134+
135+
function setEnvelopeFee(
136+
uint256 _fee
137+
)
138+
external
139+
needVote(
140+
bytes32(
141+
// keccak256("setEnvelopeFee")
142+
0xab26bca1cb5d7b0a97ee434cabffdf1efbc3073c1645a8ed4d1732335c51df49
143+
),
144+
keccak256(abi.encode(_fee))
145+
)
146+
{
147+
if (_fee <= 0) revert Errors.InvalidEnvelopeFee();
148+
envelopeFee = _fee;
149+
emit SetEnvelopeFee(_fee);
150+
}
133151
}

contracts/solidity/interfaces/IPolicy.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface IPolicy {
77
event SetMinGasTipCap(uint256 gasTipCap);
88
event SetBaseFee(uint256 baseFee);
99
event SetCandidateLimit(uint256 candidateLimit);
10+
event SetEnvelopeFee(uint256 envelopeFee);
1011

1112
// add an address to blacklist policy
1213
function addBlackList(address _addr) external;

contracts/solidity/libraries/Errors.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ library Errors {
2020
error InvalidMinGasTipCap();
2121
error InvalidBaseFee();
2222
error InvalidCandidateLimit();
23+
error InvalidEnvelopeFee();
2324

2425
// Governance Errors
2526
error SideCallNotAllowed();

core/state_transition.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"math"
2222
"math/big"
2323

24+
"github.com/ethereum/go-ethereum/antimev"
2425
"github.com/ethereum/go-ethereum/common"
2526
cmath "github.com/ethereum/go-ethereum/common/math"
2627
"github.com/ethereum/go-ethereum/core/systemcontracts"
@@ -328,9 +329,13 @@ func (st *StateTransition) preCheck() error {
328329
// For LegacyTx, GasFeeCap and GasPrice are equal, so checking GasTipCap and GasFeeCap is enough.
329330
if st.evm.ChainConfig().DBFT != nil {
330331
var minGasTipCap = st.state.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetMinGasTipCapStateHash()).Big()
332+
if antimev.IsEnvelopeToAddress(msg.To) && antimev.IsEnvelopeData(msg.Data) {
333+
var envelopeFee = st.state.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetEnvelopeFeeStateHash()).Big()
334+
minGasTipCap.Add(minGasTipCap, envelopeFee)
335+
}
331336
if cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)).Cmp(minGasTipCap) < 0 {
332-
return fmt.Errorf("%w: address %v, gasTipCap %v, gasFeeCap %v, policy minGasTipCap %v, baseFee %v ", ErrUnderpriced,
333-
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap, minGasTipCap, st.evm.Context.BaseFee)
337+
return fmt.Errorf("%w: address %v, gasTipCap %v, gasFeeCap %v, policy minGasTipCap (including Envelope fee for Envelopes) %v, baseFee %v ",
338+
ErrUnderpriced, msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap, minGasTipCap, st.evm.Context.BaseFee)
334339
}
335340
}
336341
}

core/systemcontracts/contracts.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const (
2424
const blackListSlotIndex = 1
2525
const minGasTipCapSlotIndex = 2
2626
const baseFeeSlotIndex = 3
27+
const envelopeFeeSlotIndex = 5
2728

2829
// A set of genesis contract hashes.
2930
var (
@@ -95,3 +96,9 @@ func GetBaseFeeStateHash() common.Hash {
9596
func GetBlackListStateHash(addr common.Address) common.Hash {
9697
return crypto.Keccak256Hash(common.LeftPadBytes(addr.Bytes(), 32), common.LeftPadBytes([]byte{blackListSlotIndex}, 32))
9798
}
99+
100+
// GetEnvelopeFeeStateHash computes and returns the storage key of envelopeFee
101+
// in policy contract, for reading corresponding values from statedb.
102+
func GetEnvelopeFeeStateHash() common.Hash {
103+
return common.BytesToHash([]byte{envelopeFeeSlotIndex})
104+
}

core/txpool/validation.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"math/big"
2323

24+
"github.com/ethereum/go-ethereum/antimev"
2425
"github.com/ethereum/go-ethereum/common"
2526
"github.com/ethereum/go-ethereum/common/math"
2627
"github.com/ethereum/go-ethereum/core"
@@ -215,8 +216,13 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
215216
// For LegacyTx, GasFeeCap and GasPrice are equal, so checking GasTipCap and GasFeeCap is enough
216217
var minGasTipCap = opts.State.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetMinGasTipCapStateHash()).Big()
217218
var baseFee = opts.State.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetBaseFeeStateHash()).Big()
219+
// Apply policy envelope fee check
220+
if antimev.IsEnvelope(tx) {
221+
var envelopeFee = opts.State.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetEnvelopeFeeStateHash()).Big()
222+
minGasTipCap.Add(minGasTipCap, envelopeFee)
223+
}
218224
if math.BigMin(tx.GasTipCap(), new(big.Int).Sub(tx.GasFeeCap(), baseFee)).Cmp(minGasTipCap) < 0 {
219-
return fmt.Errorf("%w: policy minGasTipCap needed %v, baseFee needed %v, gasTipCap %v, gasFeeCap %v ",
225+
return fmt.Errorf("%w: policy minGasTipCap (including Envelope fee for Envelopes) needed %v, baseFee needed %v, gasTipCap %v, gasFeeCap %v ",
220226
ErrUnderpriced, minGasTipCap, baseFee, tx.GasTipCap(), tx.GasFeeCap())
221227
}
222228
// Apply policy blacklist

eth/api_backend.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,10 @@ func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
377377
return suggestTipCap, nil
378378
}
379379

380+
func (b *EthAPIBackend) EnvelopeFee(ctx context.Context) (*big.Int, error) {
381+
return b.gpo.EnvelopeFee(ctx)
382+
}
383+
380384
func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
381385
return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
382386
}

eth/gasprice/gasprice.go

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type Oracle struct {
7272
lastPrice *big.Int
7373
lastBaseFee *big.Int // lastBaseFee contains next BaseFee value calculated based on the lastHead block.
7474
lastMinGasTipCap *big.Int // lastMinGasTipCap contains next MinGasTipCap value calculated based on the lastHead block.
75+
lastEnvelopeFee *big.Int // lastEnvelopeFee contains next EnvelopeFee value calculated based on the lastHead block.
7576
maxPrice *big.Int
7677
ignorePrice *big.Int
7778
cacheLock sync.RWMutex
@@ -161,6 +162,40 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, *big.Int, er
161162
return price, minGasTipCap, err
162163
}
163164

165+
// EnvelopeFee returns an extra tip cap so that newly created envelope transactions
166+
// can have a very high chance to be included in the following blocks.
167+
func (oracle *Oracle) EnvelopeFee(ctx context.Context) (*big.Int, error) {
168+
head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
169+
headHash := head.Hash()
170+
171+
// If the latest envelope fee is still available, return it.
172+
oracle.cacheLock.RLock()
173+
lastHead, lastEnvelopeFee := oracle.lastHead, oracle.lastEnvelopeFee
174+
oracle.cacheLock.RUnlock()
175+
if headHash == lastHead {
176+
return new(big.Int).Set(lastEnvelopeFee), nil
177+
}
178+
oracle.fetchLock.Lock()
179+
defer oracle.fetchLock.Unlock()
180+
181+
// Try checking the cache again, maybe the last fetch fetched what we need.
182+
oracle.cacheLock.RLock()
183+
lastHead, lastPrice, lastEnvelopeFee := oracle.lastHead, oracle.lastPrice, oracle.lastEnvelopeFee
184+
oracle.cacheLock.RUnlock()
185+
if headHash == lastHead {
186+
return new(big.Int).Set(lastEnvelopeFee), nil
187+
}
188+
189+
// It's a new head, then update.
190+
err := oracle.updateCache(ctx, head, lastPrice)
191+
if err != nil {
192+
return new(big.Int).Set(lastEnvelopeFee), err
193+
}
194+
oracle.cacheLock.RLock()
195+
defer oracle.cacheLock.RUnlock()
196+
return new(big.Int).Set(oracle.lastEnvelopeFee), nil
197+
}
198+
164199
// suggestTipCapInternal return GAS price, BaseFee and minGasTipCap for the specified block.
165200
// It updates the cache for the specified block height if needed. Zero BaseFee is returned if
166201
// London is not active yet.
@@ -184,12 +219,35 @@ func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Hea
184219
if headHash == lastHead {
185220
return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), nil
186221
}
222+
223+
// It's a new head, then update.
224+
err := oracle.updateCache(ctx, head, lastPrice)
225+
if err != nil {
226+
return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), err
227+
}
228+
oracle.cacheLock.RLock()
229+
defer oracle.cacheLock.RUnlock()
230+
return new(big.Int).Set(oracle.lastPrice), new(big.Int).Set(oracle.lastBaseFee), new(big.Int).Set(oracle.lastMinGasTipCap), nil
231+
}
232+
233+
type results struct {
234+
values []*big.Int
235+
err error
236+
}
237+
238+
// updateCache updates the cache for the specified block height.
239+
func (oracle *Oracle) updateCache(ctx context.Context, head *types.Header, lastPrice *big.Int) error {
240+
headHash := head.Hash()
187241
var (
188-
sent, exp int
189-
number = head.Number.Uint64()
190-
result = make(chan results, oracle.checkBlocks)
191-
quit = make(chan struct{})
192-
results []*big.Int
242+
state *state.StateDB
243+
sent, exp int
244+
number = head.Number.Uint64()
245+
result = make(chan results, oracle.checkBlocks)
246+
quit = make(chan struct{})
247+
results []*big.Int
248+
lastBaseFee *big.Int
249+
lastMinGasTipCap *big.Int
250+
lastEnvelopeFee *big.Int
193251
)
194252
for sent < oracle.checkBlocks && number > 0 {
195253
go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit)
@@ -201,7 +259,7 @@ func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Hea
201259
res := <-result
202260
if res.err != nil {
203261
close(quit)
204-
return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), res.err
262+
return res.err
205263
}
206264
exp--
207265
// Nothing returned. There are two special cases here:
@@ -231,9 +289,10 @@ func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Hea
231289
price = new(big.Int).Set(oracle.maxPrice)
232290
}
233291
if cfg := oracle.backend.ChainConfig(); cfg.IsLondon(head.Number) {
234-
state, _, err := oracle.backend.StateAndHeaderByNumber(ctx, rpc.BlockNumber(head.Number.Uint64()))
292+
var err error
293+
state, _, err = oracle.backend.StateAndHeaderByNumber(ctx, rpc.BlockNumber(head.Number.Uint64()))
235294
if err != nil {
236-
return nil, nil, nil, fmt.Errorf("failed to get state at %d to calculate base fee: %w", head.Number.Uint64(), err)
295+
return fmt.Errorf("failed to get state at %d to calculate base fee: %w", head.Number.Uint64(), err)
237296
}
238297
lastBaseFee = eip1559.CalcBaseFeeDBFT(cfg, head, state)
239298
if cfg.DBFT != nil {
@@ -245,19 +304,31 @@ func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Hea
245304
lastBaseFee = new(big.Int)
246305
lastMinGasTipCap = new(big.Int)
247306
}
307+
if cfg := oracle.backend.ChainConfig(); cfg.IsNeoXAMEV(head.Number) {
308+
if cfg.DBFT != nil {
309+
if state == nil {
310+
var err error
311+
state, _, err = oracle.backend.StateAndHeaderByNumber(ctx, rpc.BlockNumber(head.Number.Uint64()))
312+
if err != nil {
313+
return fmt.Errorf("failed to get state at %d to get envelope fee: %w", head.Number.Uint64(), err)
314+
}
315+
}
316+
lastEnvelopeFee = state.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetEnvelopeFeeStateHash()).Big()
317+
} else {
318+
lastEnvelopeFee = new(big.Int)
319+
}
320+
} else {
321+
lastEnvelopeFee = new(big.Int)
322+
}
248323
oracle.cacheLock.Lock()
249324
oracle.lastHead = headHash
250325
oracle.lastPrice = price
251326
oracle.lastBaseFee = lastBaseFee
252327
oracle.lastMinGasTipCap = lastMinGasTipCap
328+
oracle.lastEnvelopeFee = lastEnvelopeFee
253329
oracle.cacheLock.Unlock()
254330

255-
return new(big.Int).Set(price), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), nil
256-
}
257-
258-
type results struct {
259-
values []*big.Int
260-
err error
331+
return nil
261332
}
262333

263334
// getBlockValues calculates the lowest transaction gas price in a given block

ethclient/ethclient.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,16 @@ func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
567567
return (*big.Int)(&hex), nil
568568
}
569569

570+
// EnvelopeFee retrieves the currently envelope fee after NeoXAMEV to allow a
571+
// timely execution of an antimev envelope.
572+
func (ec *Client) EnvelopeFee(ctx context.Context) (*big.Int, error) {
573+
var hex hexutil.Big
574+
if err := ec.c.CallContext(ctx, &hex, "eth_envelopeFee"); err != nil {
575+
return nil, err
576+
}
577+
return (*big.Int)(&hex), nil
578+
}
579+
570580
type feeHistoryResultMarshaling struct {
571581
OldestBlock *hexutil.Big `json:"oldestBlock"`
572582
Reward [][]*hexutil.Big `json:"reward,omitempty"`

0 commit comments

Comments
 (0)