Commit 5f1c1478 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into refcell/first/stylestack

parents bddc53d6 38003570
......@@ -32,9 +32,8 @@ And as it executes each step, it can optionally produce the witness data for the
The Cannon CLI is used to load a program into an initial state,
transition it N steps quickly without witness generation, and 1 step while producing a witness.
`mipsevm` is backed by the Unicorn emulator, but instrumented for proof generation,
and handles delay-slots by isolating each individual instruction and tracking `nextPC`
to emulate the delayed `PC` changes after delay-slot execution.
`mipsevm` is instrumented for proof generation and handles delay-slots by isolating each individual instruction
and tracking `nextPC` to emulate the delayed `PC` changes after delay-slot execution.
## Witness Data
......
......@@ -108,7 +108,7 @@ func TestEVM(t *testing.T) {
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(),
"unicorn produced different state than EVM")
"mipsevm produced different state than EVM")
}
require.Equal(t, uint32(endAddr), state.PC, "must reach end")
// inspect test result
......@@ -175,7 +175,7 @@ func TestHelloEVM(t *testing.T) {
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(),
"unicorn produced different state than EVM")
"mipsevm produced different state than EVM")
}
end := time.Now()
delta := end.Sub(start)
......
......@@ -11,8 +11,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)
// Note: 2**12 = 4 KiB, the minimum page-size in Unicorn for mmap
// as well as the Go runtime min phys page size.
// Note: 2**12 = 4 KiB, the min phys page size in the Go runtime.
const (
PageAddrSize = 12
PageKeySize = 32 - PageAddrSize
......
......@@ -56,15 +56,6 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = a0
//fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz)
}
// Go does this thing where it first gets memory with PROT_NONE,
// and then mmaps with a hint with prot=3 (PROT_READ|WRITE).
// We can ignore the NONE case, to avoid duplicate/overlapping mmap calls to unicorn.
//prot := a2
//if prot != 0 {
// if err := mu.MemMap(uint64(v0), uint64(sz)); err != nil {
// log.Fatalf("mmap fail: %v", err)
// }
//}
case 4045: // brk
v0 = 0x40000000
case 4120: // clone (not supported)
......
......@@ -8,6 +8,38 @@ import (
"github.com/stretchr/testify/require"
)
// TestAlphabetProvider_Get_ClaimsByTraceIndex tests the [fault.AlphabetProvider] Get function.
func TestAlphabetProvider_Get_ClaimsByTraceIndex(t *testing.T) {
// Create a new alphabet provider.
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(3))
// Build a list of traces.
traces := []struct {
traceIndex uint64
expectedHash common.Hash
}{
{
7,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
},
{
3,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
},
{
5,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
},
}
// Execute each trace and check the alphabet provider returns the expected hash.
for _, trace := range traces {
expectedHash, err := canonicalProvider.Get(trace.traceIndex)
require.NoError(t, err)
require.Equal(t, trace.expectedHash, expectedHash)
}
}
// FuzzIndexToBytes tests the IndexToBytes function.
func FuzzIndexToBytes(f *testing.F) {
f.Fuzz(func(t *testing.T, index uint64) {
......
package examples
import (
"github.com/ethereum-optimism/optimism/op-challenger/fault"
)
func PositionExampleOne() {
// Example 1
// abcdefgh
// abcdexyz
// go left to d, then right to f, then left to e
p := fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
p.Attack()
p.Print(3)
}
func PositionExampleTwo() {
// Example 2
// abcdefgh
// abqrstuv
// go left r, then left to b, then right to q
p := fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
}
package examples
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-challenger/fault"
)
// SolverExampleOne uses the [fault.Solver] with a [fault.AlphabetProvider]
// to print out fault game traces for the "abcdexyz" counter-state.
func SolverExampleOne() {
fmt.Println()
fmt.Println("Solver: Example 1")
fmt.Println()
// Construct the fault position.
canonical := "abcdefgh"
disputed := "abcdexyz"
maxDepth := 3
parent := fault.Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: fault.NewPosition(0, 0),
}
canonicalProvider := fault.NewAlphabetProvider(canonical, uint64(maxDepth))
disputedProvider := fault.NewAlphabetProvider(disputed, uint64(maxDepth))
// Create a solver with the canonical provider.
solver := fault.NewSolver(maxDepth, canonicalProvider)
// Print the initial state.
fmt.Println("Canonical state: ", canonical)
fmt.Println("Disputed state: ", disputed)
fmt.Println()
fmt.Println("Proceeding with the following moves:")
fmt.Println("go left to d, then right to f, then left to e")
fmt.Println()
// Get the claim from the disputed provider.
claim, err := disputedProvider.Get(3)
if err != nil {
fmt.Printf("error getting claim from disputed provider: %v", err)
}
firstDisputedClaim := fault.Claim{
Value: claim,
Position: fault.NewPosition(1, 0),
}
res, err := solver.NextMove(firstDisputedClaim, parent)
if err != nil {
fmt.Printf("error getting next move: %v", err)
}
fmt.Printf("Disputed claim: %s\n", claim)
fmt.Printf("Expected claim: %s\n", parent.Value)
fmt.Printf("Response: [Attack: %v, Value: %s]\n", res.Attack, res.Value)
fmt.Println()
// Get the next claim from the disputed provider.
claim, err = disputedProvider.Get(5)
if err != nil {
fmt.Printf("error getting claim from disputed provider: %v", err)
}
firstDisputedClaim = fault.Claim{
Value: claim,
Position: fault.NewPosition(2, 2),
}
res, err = solver.NextMove(firstDisputedClaim, parent)
if err != nil {
fmt.Printf("error getting next move: %v", err)
}
fmt.Printf("Disputed claim: %s\n", claim)
fmt.Printf("Expected claim: %s\n", parent.Value)
fmt.Printf("Response: [Attack: %v, Value: %s]\n", res.Attack, res.Value)
fmt.Println()
// This marks the end of the game!
if res.Attack {
fmt.Println("Game successfully completed!")
} else {
fmt.Println("Game failed!")
}
fmt.Println()
}
package main
import (
"github.com/ethereum-optimism/optimism/op-challenger/fault"
"github.com/ethereum-optimism/optimism/op-challenger/fault/cmd/examples"
)
func main() {
// Example 1
// abcdefgh
// abcdexyz
// go left to d, then right to f, then left to e
p := fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
p.Attack()
p.Print(3)
examples.SolverExampleOne()
// GIN: 1 Trace Position is 0 Trace Depth is: 0 Trace Index is: 8
// GIN: 10 Trace Position is 0 Trace Depth is: 1 Trace Index is: 4
// GIN: 110 Trace Position is 10 Trace Depth is: 2 Trace Index is: 6
// GIN: 1100 Trace Position is 100 Trace Depth is: 3 Trace Index is: 5
// Example 2
// abcdefgh
// abqrstuv
// go left r, then left to b, then right to q
p = fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
// Trace Position is 0000 Trace Depth is: 0 Trace Index is: 8
// Trace Position is 0000 Trace Depth is: 1 Trace Index is: 4
// Trace Position is 0000 Trace Depth is: 2 Trace Index is: 2
// Trace Position is 0010 Trace Depth is: 3 Trace Index is: 3
// examples.PositionExampleOne()
// examples.PositionExampleTwo()
}
package fault
// Game is an interface that represents the state of a dispute game.
type Game interface {
// Put adds a claim into the game state and returns its parent claim.
Put(claim Claim) (Claim, error)
// ClaimPairs returns a list of claim pairs.
ClaimPairs() []struct {
claim Claim
parent Claim
}
}
package fault
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
// Solver uses a [TraceProvider] to determine the moves to make in a dispute game.
type Solver struct {
TraceProvider
gameDepth int
}
// NewSolver creates a new [Solver] using the provided [TraceProvider].
func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver {
return &Solver{
traceProvider,
gameDepth,
}
}
// NextMove returns the next move to make given the current state of the game.
func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) {
parentCorrect, err := s.agreeWithClaim(parent)
if err != nil {
return nil, err
}
claimCorrect, err := s.agreeWithClaim(claim)
if err != nil {
return nil, err
}
if parentCorrect && claimCorrect {
// We agree with the parent, but the claim is disagreeing with it.
// Since we agree with the claim, the difference must be to the right of the claim
return s.defend(claim)
} else if parentCorrect && !claimCorrect {
// We agree with the parent, but the claim disagrees with it.
// Since we disagree with the claim, the difference must be to the left of the claim
return s.attack(claim)
} else if !parentCorrect && claimCorrect {
// Do nothing, we disagree with the parent, but this claim has correctly countered it
return s.doNothing()
} else if !parentCorrect && !claimCorrect {
// We disagree with the parent so want to counter it (which the claim is doing)
// but we also disagree with the claim so there must be a difference to the left of claim
// Note that we will create the correct counter-claim for parent when it is evaluated, no need to do it here
return s.attack(claim)
}
// This should not be reached
return nil, errors.New("no next move")
}
func (s *Solver) doNothing() (*Response, error) {
return nil, nil
}
// attack returns a response that attacks the claim.
func (s *Solver) attack(claim Claim) (*Response, error) {
claim.Position.Attack()
value, err := s.traceAtPosition(&claim.Position)
if err != nil {
return nil, err
}
return &Response{Attack: true, Value: value}, nil
}
// defend returns a response that defends the claim.
func (s *Solver) defend(claim Claim) (*Response, error) {
claim.Position.Defend()
value, err := s.traceAtPosition(&claim.Position)
if err != nil {
return nil, err
}
return &Response{Attack: false, Value: value}, nil
}
// agreeWithClaim returns true if the [Claim] is correct according to the internal [TraceProvider].
func (s *Solver) agreeWithClaim(claim Claim) (bool, error) {
ourValue, err := s.traceAtPosition(&claim.Position)
return ourValue == claim.Value, err
}
// traceAtPosition returns the [common.Hash] from internal [TraceProvider] at the given [Position].
func (s *Solver) traceAtPosition(p *Position) (common.Hash, error) {
// todo: update TraceIndex to return a uint64 to keep types consistent
index := p.TraceIndex(s.gameDepth)
hash, err := s.Get(uint64(index))
return hash, err
}
package fault
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
// TestSolver_NextMove_Opponent tests the [Solver] NextMove function
// with an [fault.AlphabetProvider] as the [TraceProvider].
func TestSolver_NextMove_Opponent(t *testing.T) {
// Construct the solver.
maxDepth := 3
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth))
solver := NewSolver(maxDepth, canonicalProvider)
// The following claims are created using the state: "abcdexyz".
// The responses are the responses we expect from the solver.
indices := []struct {
traceIndex int
claim Claim
parent Claim
response *Response
}{
{
3,
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
Position: NewPosition(1, 0),
},
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(0, 0),
},
&Response{
Attack: false,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
},
},
{
5,
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"),
Position: NewPosition(2, 2),
},
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(1, 1),
},
&Response{
Attack: true,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"),
},
},
}
for _, test := range indices {
res, err := solver.NextMove(test.claim, test.parent)
require.NoError(t, err)
require.Equal(t, test.response, res)
}
}
package fault
import (
"context"
"errors"
"github.com/ethereum/go-ethereum/common"
......@@ -26,4 +27,11 @@ type Claim struct {
type Response struct {
Attack bool // note: can we flip this to true == going right / defending??
Value common.Hash
Parent Claim
}
// Responder takes a response action & executes.
// For full op-challenger this means executing the transaction on chain.
type Responder interface {
Respond(ctx context.Context, response Response) error
}
......@@ -30,51 +30,52 @@ func (tc *priceBumpTest) run(t *testing.T) {
}
func TestUpdateFees(t *testing.T) {
require.Equal(t, int64(10), priceBump, "test must be updated if priceBump is adjusted")
tests := []priceBumpTest{
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 90, newBasefee: 900,
expectedTip: 100, expectedFC: 2100,
expectedTip: 110, expectedFC: 2310,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 101, newBasefee: 1000,
expectedTip: 115, expectedFC: 2415,
expectedTip: 110, expectedFC: 2310,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 100, newBasefee: 1001,
expectedTip: 115, expectedFC: 2415,
expectedTip: 110, expectedFC: 2310,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 101, newBasefee: 900,
expectedTip: 115, expectedFC: 2415,
expectedTip: 110, expectedFC: 2310,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 90, newBasefee: 1010,
expectedTip: 115, expectedFC: 2415,
expectedTip: 110, expectedFC: 2310,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 101, newBasefee: 2000,
expectedTip: 115, expectedFC: 4115,
expectedTip: 110, expectedFC: 4110,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 120, newBasefee: 900,
expectedTip: 120, expectedFC: 2415,
expectedTip: 120, expectedFC: 2310,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 120, newBasefee: 1100,
expectedTip: 120, expectedFC: 2415,
expectedTip: 120, expectedFC: 2320,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 120, newBasefee: 1140,
expectedTip: 120, expectedFC: 2415,
expectedTip: 120, expectedFC: 2400,
},
{
prevGasTip: 100, prevBasefee: 1000,
......
......@@ -20,9 +20,13 @@ import (
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
)
// Geth defaults the priceBump to 10
// Set it to 15% to be more aggressive about including transactions
const priceBump int64 = 15
const (
// Geth requires a minimum fee bump of 10% for tx resubmission
priceBump int64 = 10
// The multiplier applied to fee suggestions to put a hard limit on fee increases
feeLimitMultiplier = 5
)
// new = old * (100 + priceBump) / 100
var priceBumpPercent = big.NewInt(100 + priceBump)
......@@ -280,7 +284,15 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t
return nil, errors.New("aborted transaction sending")
}
// Increase the gas price & submit the new transaction
tx = m.increaseGasPrice(ctx, tx)
newTx, err := m.increaseGasPrice(ctx, tx)
if err != nil || sendState.IsWaitingForConfirmation() {
// there is a chance the previous tx goes into "waiting for confirmation" state
// during the increaseGasPrice call. In some (but not all) cases increaseGasPrice
// will error out during gas estimation. In either case we should continue waiting
// rather than resubmit the tx.
continue
}
tx = newTx
wg.Add(1)
bumpCounter += 1
go sendTxAsync(tx)
......@@ -418,46 +430,67 @@ func (m *SimpleTxManager) queryReceipt(ctx context.Context, txHash common.Hash,
return nil
}
// increaseGasPrice takes the previous transaction & potentially clones then signs it with a higher tip.
// If the tip + basefee suggested by the network are not greater than the previous values, the same transaction
// will be returned. If they are greater, this function will ensure that they are at least greater by 15% than
// the previous transaction's value to ensure that the price bump is large enough.
//
// We do not re-estimate the amount of gas used because for some stateful transactions (like output proposals) the
// act of including the transaction renders the repeat of the transaction invalid.
//
// If it encounters an error with creating the new transaction, it will return the old transaction.
func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transaction) *types.Transaction {
// increaseGasPrice takes the previous transaction, clones it, and returns it with fee values that
// are at least `priceBump` percent higher than the previous ones to satisfy Geth's replacement
// rules, and no lower than the values returned by the fee suggestion algorithm to ensure it
// doesn't linger in the mempool. Finally to avoid runaway price increases, fees are capped at a
// `feeLimitMultiplier` multiple of the suggested values.
func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
m.l.Info("bumping gas price for tx", "hash", tx.Hash(), "tip", tx.GasTipCap(), "fee", tx.GasFeeCap(), "gaslimit", tx.Gas())
tip, basefee, err := m.suggestGasPriceCaps(ctx)
if err != nil {
m.l.Warn("failed to get suggested gas tip and basefee", "err", err)
return tx
return nil, err
}
gasTipCap, gasFeeCap := updateFees(tx.GasTipCap(), tx.GasFeeCap(), tip, basefee, m.l)
bumpedTip, bumpedFee := updateFees(tx.GasTipCap(), tx.GasFeeCap(), tip, basefee, m.l)
if tx.GasTipCapIntCmp(gasTipCap) == 0 && tx.GasFeeCapIntCmp(gasFeeCap) == 0 {
return tx
// Make sure increase is at most 5x the suggested values
maxTip := new(big.Int).Mul(tip, big.NewInt(feeLimitMultiplier))
if bumpedTip.Cmp(maxTip) > 0 {
m.l.Warn(fmt.Sprintf("bumped tip getting capped at %dx multiple of the suggested value", feeLimitMultiplier), "bumped", bumpedTip, "suggestion", tip)
bumpedTip.Set(maxTip)
}
maxFee := calcGasFeeCap(new(big.Int).Mul(basefee, big.NewInt(feeLimitMultiplier)), maxTip)
if bumpedFee.Cmp(maxFee) > 0 {
m.l.Warn("bumped fee getting capped at multiple of the implied suggested value", "bumped", bumpedFee, "suggestion", maxFee)
bumpedFee.Set(maxFee)
}
rawTx := &types.DynamicFeeTx{
ChainID: tx.ChainId(),
Nonce: tx.Nonce(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: tx.Gas(),
GasTipCap: bumpedTip,
GasFeeCap: bumpedFee,
To: tx.To(),
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
}
// Re-estimate gaslimit in case things have changed or a previous gaslimit estimate was wrong
gas, err := m.backend.EstimateGas(ctx, ethereum.CallMsg{
From: m.cfg.From,
To: rawTx.To,
GasFeeCap: bumpedTip,
GasTipCap: bumpedFee,
Data: rawTx.Data,
})
if err != nil {
m.l.Warn("failed to re-estimate gas", "err", err, "gaslimit", tx.Gas())
return nil, err
}
if tx.Gas() != gas {
m.l.Info("re-estimated gas differs", "oldgas", tx.Gas(), "newgas", gas)
}
rawTx.Gas = gas
ctx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
newTx, err := m.cfg.Signer(ctx, m.cfg.From, types.NewTx(rawTx))
if err != nil {
m.l.Warn("failed to sign new transaction", "err", err)
return tx
return tx, nil
}
return newTx
return newTx, nil
}
// suggestGasPriceCaps suggests what the new tip & new basefee should be based on the current L1 conditions
......@@ -490,18 +523,15 @@ func calcThresholdValue(x *big.Int) *big.Int {
return threshold
}
// updateFees takes the old tip/basefee & the new tip/basefee and then suggests
// a gasTipCap and gasFeeCap that satisfies geth's required fee bumps
// Geth: FC and Tip must be bumped if any increase
// updateFees takes an old transaction's tip & fee cap plus a new tip & basefee, and returns
// a suggested tip and fee cap such that:
//
// (a) each satisfies geth's required tx-replacement fee bumps (we use a 10% increase), and
// (b) gasTipCap is no less than new tip, and
// (c) gasFeeCap is no less than calcGasFee(newBaseFee, newTip)
func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, lgr log.Logger) (*big.Int, *big.Int) {
newFeeCap := calcGasFeeCap(newBaseFee, newTip)
lgr = lgr.New("old_tip", oldTip, "old_feecap", oldFeeCap, "new_tip", newTip, "new_feecap", newFeeCap)
// If the new prices are less than the old price, reuse the old prices
if oldTip.Cmp(newTip) >= 0 && oldFeeCap.Cmp(newFeeCap) >= 0 {
lgr.Debug("Reusing old tip and feecap")
return oldTip, oldFeeCap
}
// Determine if we need to increase the suggested values
thresholdTip := calcThresholdValue(oldTip)
thresholdFeeCap := calcThresholdValue(oldFeeCap)
if newTip.Cmp(thresholdTip) >= 0 && newFeeCap.Cmp(thresholdFeeCap) >= 0 {
......
......@@ -734,12 +734,14 @@ func doGasPriceIncrease(t *testing.T, txTipCap, txFeeCap, newTip, newBaseFee int
GasTipCap: big.NewInt(txTipCap),
GasFeeCap: big.NewInt(txFeeCap),
})
newTx := mgr.increaseGasPrice(context.Background(), tx)
newTx, err := mgr.increaseGasPrice(context.Background(), tx)
require.NoError(t, err)
return tx, newTx
}
func TestIncreaseGasPrice(t *testing.T) {
// t.Parallel()
require.Equal(t, int64(10), priceBump, "test must be updated if priceBump is adjusted")
tests := []struct {
name string
run func(t *testing.T)
......@@ -781,22 +783,16 @@ func TestIncreaseGasPrice(t *testing.T) {
run: func(t *testing.T) {
_, newTx := doGasPriceIncrease(t, 100, 2200, 120, 1050)
require.True(t, newTx.GasTipCap().Cmp(big.NewInt(120)) == 0, "new tx tip must be equal L1")
require.True(t, newTx.GasFeeCap().Cmp(big.NewInt(2530)) == 0, "new tx fee cap must be equal to the threshold value")
require.True(t, newTx.GasFeeCap().Cmp(big.NewInt(2420)) == 0, "new tx fee cap must be equal to the threshold value")
},
},
{
name: "uses L1 FC when larger and threshold tip",
run: func(t *testing.T) {
_, newTx := doGasPriceIncrease(t, 100, 2200, 100, 2000)
require.True(t, newTx.GasTipCap().Cmp(big.NewInt(115)) == 0, "new tx tip must be equal the threshold value")
require.True(t, newTx.GasFeeCap().Cmp(big.NewInt(4115)) == 0, "new tx fee cap must be equal L1")
},
},
{
name: "reuses tx when no bump",
run: func(t *testing.T) {
tx, newTx := doGasPriceIncrease(t, 10, 100, 10, 45)
require.Equal(t, tx.Hash(), newTx.Hash(), "tx hash must be the same")
require.True(t, newTx.GasTipCap().Cmp(big.NewInt(110)) == 0, "new tx tip must be equal the threshold value")
t.Log("Vals:", newTx.GasFeeCap())
require.True(t, newTx.GasFeeCap().Cmp(big.NewInt(4110)) == 0, "new tx fee cap must be equal L1")
},
},
}
......@@ -811,11 +807,12 @@ func TestIncreaseGasPrice(t *testing.T) {
func TestIncreaseGasPriceNotExponential(t *testing.T) {
t.Parallel()
borkedTip := int64(10)
borkedFee := int64(45)
borkedBackend := failingBackend{
gasTip: big.NewInt(10),
baseFee: big.NewInt(45),
gasTip: big.NewInt(borkedTip),
baseFee: big.NewInt(borkedFee),
}
feeCap := calcGasFeeCap(borkedBackend.baseFee, borkedBackend.gasTip)
mgr := &SimpleTxManager{
cfg: Config{
......@@ -839,14 +836,23 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) {
})
// Run IncreaseGasPrice a bunch of times in a row to simulate a very fast resubmit loop.
for i := 0; i < 20; i++ {
var err error
for i := 0; i < 30; i++ {
ctx := context.Background()
newTx := mgr.increaseGasPrice(ctx, tx)
require.True(t, newTx.GasFeeCap().Cmp(feeCap) == 0, "new tx fee cap must be equal L1")
require.True(t, newTx.GasTipCap().Cmp(borkedBackend.gasTip) == 0, "new tx tip must be equal L1")
tx = newTx
tx, err = mgr.increaseGasPrice(ctx, tx)
require.NoError(t, err)
}
lastTip, lastFee := tx.GasTipCap(), tx.GasFeeCap()
require.Equal(t, lastTip.Int64(), feeLimitMultiplier*borkedTip)
require.Equal(t, lastFee.Int64(), feeLimitMultiplier*(borkedTip+2*borkedFee))
// Confirm that fees stop rising
for i := 0; i < 5; i++ {
ctx := context.Background()
tx, err := mgr.increaseGasPrice(ctx, tx)
require.NoError(t, err)
require.True(t, tx.GasTipCap().Cmp(lastTip) == 0, "suggested tx tip must stop increasing")
require.True(t, tx.GasFeeCap().Cmp(lastFee) == 0, "suggested tx fee must stop increasing")
}
}
func TestErrStringMatch(t *testing.T) {
......
Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9413)
Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1430)
Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17240)
Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20826)
Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20804)
Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 78882)
Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 3992)
Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 823)
......
......@@ -70,59 +70,6 @@ contract Bytes_slice_Test is Test {
assertEq(Bytes.slice(input, 31, 34), expected);
}
/**
* @notice Tests that, when given an input bytes array of length `n`, the `slice` function will
* always revert if `_start + _length > n`.
*/
function testFuzz_slice_outOfBounds_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// We want a valid start index and a length that will not overflow.
vm.assume(_start < _input.length && _length < type(uint256).max - 31);
// But, we want an invalid slice length.
vm.assume(_start + _length > _input.length);
vm.expectRevert("slice_outOfBounds");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a length `n` that is greater than `type(uint256).max - 31`,
* the `slice` function reverts.
*/
function testFuzz_slice_lengthOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that the `_length` will overflow if a number >= 31 is added to it.
vm.assume(_length > type(uint256).max - 31);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a start index `n` that is greater than
* `type(uint256).max - n`, the `slice` function reverts.
*/
function testFuzz_slice_rangeOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that `_length` is a realistic length of a slice. This is to make sure
// we revert on the correct require statement.
vm.assume(_length < _input.length);
// Ensure that `_start` will overflow if `_length` is added to it.
vm.assume(_start > type(uint256).max - _length);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that the `slice` function correctly updates the free memory pointer depending
* on the length of the slice.
......@@ -181,6 +128,61 @@ contract Bytes_slice_Test is Test {
}
}
contract Bytes_slice_TestFail is Test {
/**
* @notice Tests that, when given an input bytes array of length `n`, the `slice` function will
* always revert if `_start + _length > n`.
*/
function testFuzz_slice_outOfBounds_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// We want a valid start index and a length that will not overflow.
vm.assume(_start < _input.length && _length < type(uint256).max - 31);
// But, we want an invalid slice length.
vm.assume(_start + _length > _input.length);
vm.expectRevert("slice_outOfBounds");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a length `n` that is greater than `type(uint256).max - 31`,
* the `slice` function reverts.
*/
function testFuzz_slice_lengthOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that the `_length` will overflow if a number >= 31 is added to it.
vm.assume(_length > type(uint256).max - 31);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a start index `n` that is greater than
* `type(uint256).max - n`, the `slice` function reverts.
*/
function testFuzz_slice_rangeOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that `_length` is a realistic length of a slice. This is to make sure
// we revert on the correct require statement.
vm.assume(_length < _input.length);
// Ensure that `_start` will overflow if `_length` is added to it.
vm.assume(_start > type(uint256).max - _length);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
}
contract Bytes_toNibbles_Test is Test {
/**
* @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of
......
......@@ -16,11 +16,9 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/go-redis/redis/v8"
"github.com/gorilla/mux"
......@@ -580,16 +578,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
}
ctx := context.WithValue(r.Context(), ContextKeyXForwardedFor, xff) // nolint:staticcheck
if len(s.authenticatedPaths) == 0 {
// handle the edge case where auth is disabled
// but someone sends in an auth key anyway
if authorization != "" {
log.Info("blocked authenticated request against unauthenticated proxy")
httpResponseCodesTotal.WithLabelValues("404").Inc()
w.WriteHeader(404)
return nil
}
} else {
if len(s.authenticatedPaths) > 0 {
if authorization == "" || s.authenticatedPaths[authorization] == "" {
log.Info("blocked unauthorized request", "authorization", authorization)
httpResponseCodesTotal.WithLabelValues("401").Inc()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment