Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

L1 gas refunds for failing transactions. BaseFee not burned anymore. #1550

Merged
merged 8 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions go/enclave/components/batch_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ func (executor *batchExecutor) payL1Fees(stateDB *state.StateDB, context *BatchE
return transactions, freeTransactions
}

func (executor *batchExecutor) refundL1Fees(stateDB *state.StateDB, context *BatchExecutionContext, transactions []*common.L2Tx) {
block, _ := executor.storage.FetchBlock(context.BlockPtr)
for _, tx := range transactions {
cost, err := executor.gasOracle.EstimateL1StorageGasCost(tx, block)
if err != nil {
executor.logger.Warn("Unable to get gas cost for tx", log.TxKey, tx.Hash(), log.ErrKey, err)
continue
}

sender, err := core.GetAuthenticatedSender(context.ChainConfig.ChainID.Int64(), tx)
if err != nil {
// todo @siliev - is this critical? Potential desync spot
executor.logger.Warn("Unable to extract sender for tx", log.TxKey, tx.Hash())
continue
}

stateDB.AddBalance(*sender, cost)
stateDB.SubBalance(context.Creator, cost)
}
}

func (executor *batchExecutor) ComputeBatch(context *BatchExecutionContext) (*ComputedBatch, error) {
defer executor.logger.Info("Batch context processed", log.DurationKey, measure.NewStopwatch())

Expand Down Expand Up @@ -159,12 +180,14 @@ func (executor *batchExecutor) ComputeBatch(context *BatchExecutionContext) (*Co

crossChainTransactions = append(crossChainTransactions, freeTransactions...)

successfulTxs, txReceipts, err := executor.processTransactions(batch, 0, transactionsToProcess, stateDB, context.ChainConfig, false)
successfulTxs, excludedTxs, txReceipts, err := executor.processTransactions(batch, 0, transactionsToProcess, stateDB, context.ChainConfig, false)
if err != nil {
return nil, fmt.Errorf("could not process transactions. Cause: %w", err)
}

ccSuccessfulTxs, ccReceipts, err := executor.processTransactions(batch, len(successfulTxs), crossChainTransactions, stateDB, context.ChainConfig, true)
executor.refundL1Fees(stateDB, context, excludedTxs)

ccSuccessfulTxs, _, ccReceipts, err := executor.processTransactions(batch, len(successfulTxs), crossChainTransactions, stateDB, context.ChainConfig, true)
if err != nil {
return nil, err
}
Expand All @@ -176,7 +199,7 @@ func (executor *batchExecutor) ComputeBatch(context *BatchExecutionContext) (*Co
// we need to copy the batch to reset the internal hash cache
copyBatch := *batch
copyBatch.Header.Root = stateDB.IntermediateRoot(false)
copyBatch.Transactions = append(transactionsToProcess, freeTransactions...)
copyBatch.Transactions = append(successfulTxs, freeTransactions...)
copyBatch.ResetHash()

if err = executor.populateOutboundCrossChainData(&copyBatch, block, txReceipts); err != nil {
Expand Down Expand Up @@ -362,28 +385,37 @@ func (executor *batchExecutor) verifyInboundCrossChainTransactions(transactions
return nil
}

func (executor *batchExecutor) processTransactions(batch *core.Batch, tCount int, txs []*common.L2Tx, stateDB *state.StateDB, cc *params.ChainConfig, noBaseFee bool) ([]*common.L2Tx, []*types.Receipt, error) {
func (executor *batchExecutor) processTransactions(
batch *core.Batch,
tCount int,
txs []*common.L2Tx,
stateDB *state.StateDB,
cc *params.ChainConfig,
noBaseFee bool,
) ([]*common.L2Tx, []*common.L2Tx, []*types.Receipt, error) {
var executedTransactions []*common.L2Tx
var excludedTransactions []*common.L2Tx
var txReceipts []*types.Receipt

txResults := evm.ExecuteTransactions(txs, stateDB, batch.Header, executor.storage, cc, tCount, noBaseFee, executor.logger)
for _, tx := range txs {
result, f := txResults[tx.Hash()]
if !f {
return nil, nil, fmt.Errorf("there should be an entry for each transaction")
return nil, nil, nil, fmt.Errorf("there should be an entry for each transaction")
}
rec, foundReceipt := result.(*types.Receipt)
if foundReceipt {
executedTransactions = append(executedTransactions, tx)
txReceipts = append(txReceipts, rec)
} else {
// Exclude all errors
excludedTransactions = append(excludedTransactions, tx)
executor.logger.Info("Excluding transaction from batch", log.TxKey, tx.Hash(), log.BatchHashKey, batch.Hash(), "cause", result)
}
}
sort.Sort(sortByTxIndex(txReceipts))

return executedTransactions, txReceipts, nil
return executedTransactions, excludedTransactions, txReceipts, nil
}

func allReceipts(txReceipts []*types.Receipt, depositReceipts []*types.Receipt) types.Receipts {
Expand Down
6 changes: 6 additions & 0 deletions go/enclave/evm/evm_facade.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ func executeTransaction(
for _, l := range receipt.Logs {
l.BlockHash = batchHash
}

if header.Coinbase.Big().Cmp(gethcommon.Big0) != 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs a comment

gasUsed := big.NewInt(0).SetUint64(receipt.GasUsed)
executionGasCost := big.NewInt(0).Mul(gasUsed, header.BaseFee)
s.AddBalance(header.Coinbase, executionGasCost)
}
}

header.MixDigest = before
Expand Down
18 changes: 14 additions & 4 deletions integration/networktest/actions/native_fund_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
)

type SendNativeFunds struct {
FromUser int
ToUser int
Amount *big.Int
FromUser int
ToUser int
Amount *big.Int
GasLimit *big.Int
SkipVerify bool

user *userwallet.UserWallet
txHash *common.Hash
Expand All @@ -33,7 +35,11 @@ func (s *SendNativeFunds) Run(ctx context.Context, _ networktest.NetworkConnecto
if err != nil {
return ctx, err
}
txHash, err := user.SendFunds(ctx, target.Address(), s.Amount)
gas := uint64(1_000_000)
if s.GasLimit != nil {
gas = s.GasLimit.Uint64()
}
txHash, err := user.SendFunds(ctx, target.Address(), s.Amount, gas)
if err != nil {
return nil, err
}
Expand All @@ -43,6 +49,10 @@ func (s *SendNativeFunds) Run(ctx context.Context, _ networktest.NetworkConnecto
}

func (s *SendNativeFunds) Verify(ctx context.Context, _ networktest.NetworkConnector) error {
if s.SkipVerify {
return nil
}

receipt, err := s.user.AwaitReceipt(ctx, s.txHash)
if err != nil {
return fmt.Errorf("failed to fetch receipt - %w", err)
Expand Down
2 changes: 1 addition & 1 deletion integration/networktest/env/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (t *testnetConnector) GetValidatorNode(_ int) networktest.NodeOperator {
}

func (t *testnetConnector) AllocateFaucetFundsWithWallet(ctx context.Context, account gethcommon.Address) error {
txHash, err := t.faucetWallet.SendFunds(ctx, account, _defaultFaucetAmount)
txHash, err := t.faucetWallet.SendFunds(ctx, account, _defaultFaucetAmount, 1_000_000)
if err != nil {
return err
}
Expand Down
8 changes: 5 additions & 3 deletions integration/networktest/log.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package networktest

import (
"os"

"github.com/ethereum/go-ethereum/log"
"github.com/obscuronet/go-obscuro/integration/common/testlog"
)

// EnsureTestLogsSetUp calls Setup if it hasn't already been called (some tests run tests within themselves, we don't want
// the log folder flipping around for every subtest, so we assume this is called for the top level test that is running
// and ignore subsequent calls
func EnsureTestLogsSetUp(testName string) {
func EnsureTestLogsSetUp(testName string) *os.File {
logger := testlog.Logger()
if logger != nil {
return // already setup, do not reconfigure
return nil // already setup, do not reconfigure
}
testlog.Setup(&testlog.Cfg{
return testlog.Setup(&testlog.Cfg{
// todo (@matt) - walk up the dir tree to find /integration/.build or find best practice solution
// bit of a hack - tests need to be in a package nested within /tests to get logs in the right place
LogDir: "../../../.build/networktest/",
Expand Down
5 changes: 3 additions & 2 deletions integration/networktest/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import (
// networktest.Run(t, env.DevTestnet(), tests.smokeTest())
// networktest.Run(t, env.LocalDevNetwork(WithNumValidators(8)), traffic.RunnerTest(traffic.NativeFundsTransfers(), 30*time.Second)
func Run(testName string, t *testing.T, env Environment, action Action) {
EnsureTestLogsSetUp(testName)
logFile := EnsureTestLogsSetUp(testName)
network, envCleanup, err := env.Prepare()
if err != nil {
t.Fatal(err)
}
ctx, cancelCtx := context.WithCancel(context.Background())
initialCtx, cancelCtx := context.WithCancel(context.Background())
ctx := context.WithValue(initialCtx, "logFile", logFile)
defer func() {
envCleanup()
cancelCtx()
Expand Down
82 changes: 82 additions & 0 deletions integration/networktest/tests/gas/gas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package helpful

import (
"bufio"
"context"
"fmt"
"math/big"
"os"
"strings"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/obscuronet/go-obscuro/integration/networktest/actions"

"github.com/obscuronet/go-obscuro/integration/networktest"
"github.com/obscuronet/go-obscuro/integration/networktest/env"
)

// Smoke tests are useful for checking a network is live or checking basic functionality is not broken

var _transferAmount = big.NewInt(100_000_000)
var _testTimeSpan = 10 * time.Second

// Transaction with insufficient gas limit for the intrinsic cost. It should result in no difference
// to user balances, but network should remain operational.
// Used to automatically detect batch desync based on transaction inclusion.
// Sequencer and Validator will process different transactions, but state should be identical.
func TestExecuteNativeFundsTransferNoGas(t *testing.T) {
networktest.Run(
"gas-underlimit-test",
t,
env.LocalDevNetwork(),
actions.Series(
&actions.CreateTestUser{UserID: 0},
&actions.CreateTestUser{UserID: 1},
actions.SetContextValue(actions.KeyNumberOfTestUsers, 2),

&actions.AllocateFaucetFunds{UserID: 0},
actions.SnapshotUserBalances(actions.SnapAfterAllocation), // record user balances (we have no guarantee on how much the network faucet allocates)
&actions.SendNativeFunds{
FromUser: 0,
ToUser: 1,
Amount: _transferAmount,
GasLimit: big.NewInt(11_000),
SkipVerify: true,
},
&actions.VerifyBalanceAfterTest{
UserID: 1,
ExpectedBalance: common.Big0,
},
actions.VerifyOnlyAction(func(ctx context.Context, network networktest.NetworkConnector) error {
logFile, ok := (ctx.Value("logFile")).(*os.File)
if !ok {
return fmt.Errorf("Log file not provided in context!")
}
fmt.Println(logFile.Name())

f, err := os.Open(logFile.Name())
if err != nil {
return err
}

scanner := bufio.NewScanner(f)

// https://golang.org/pkg/bufio/#Scanner.Scan
for scanner.Scan() {
if strings.Contains(scanner.Text(), "Error validating batch") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit sad that this is the only way to find out that a validator has rejected batches haha, but nice that you found a way

return fmt.Errorf("Found bad batches in test logs.")
}
}

if err := scanner.Err(); err != nil {
// Handle the error
return err
}

return nil
}),
),
)
}
4 changes: 2 additions & 2 deletions integration/networktest/userwallet/userwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (s *UserWallet) ChainID() *big.Int {
return big.NewInt(integration.ObscuroChainID)
}

func (s *UserWallet) SendFunds(ctx context.Context, addr gethcommon.Address, value *big.Int) (*gethcommon.Hash, error) {
func (s *UserWallet) SendFunds(ctx context.Context, addr gethcommon.Address, value *big.Int, gas uint64) (*gethcommon.Hash, error) {
err := s.EnsureClientSetup(ctx)
if err != nil {
return nil, fmt.Errorf("unable to prepare client to send funds - %w", err)
Expand All @@ -94,7 +94,7 @@ func (s *UserWallet) SendFunds(ctx context.Context, addr gethcommon.Address, val
tx := &types.LegacyTx{
Nonce: s.nonce,
Value: value,
Gas: uint64(1_000_000),
Gas: gas,
GasPrice: gethcommon.Big1,
To: &addr,
}
Expand Down
2 changes: 1 addition & 1 deletion integration/simulation/devnetwork/dev_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (s *InMemDevNetwork) AllocateFaucetFunds(ctx context.Context, account gethc
s.faucetLock.Lock()
defer s.faucetLock.Unlock()

txHash, err := s.faucet.SendFunds(ctx, account, _defaultFaucetAmount)
txHash, err := s.faucet.SendFunds(ctx, account, _defaultFaucetAmount, 1_000_000)
if err != nil {
return err
}
Expand Down