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

feat: eip 1559 dynamic transactions #3504

Merged
merged 9 commits into from Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/crypto/signer.go
Expand Up @@ -90,7 +90,7 @@ func (d *defaultSigner) Sign(data []byte) (signature []byte, err error) {

// SignTx signs an ethereum transaction.
func (d *defaultSigner) SignTx(transaction *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
txSigner := types.NewEIP155Signer(chainID)
txSigner := types.NewLondonSigner(chainID)
hash := txSigner.Hash(transaction).Bytes()
// isCompressedKey is false here so we get the expected v value (27 or 28)
signature, err := d.sign(hash, false)
Expand Down
3 changes: 3 additions & 0 deletions pkg/node/chain.go
Expand Up @@ -399,6 +399,9 @@ func (m noOpChainBackend) PendingNonceAt(context.Context, common.Address) (uint6
func (m noOpChainBackend) SuggestGasPrice(context.Context) (*big.Int, error) {
panic("chain no op: SuggestGasPrice")
}
func (m noOpChainBackend) SuggestGasTipCap(context.Context) (*big.Int, error) {
panic("chain no op: SuggestGasPrice")
}
func (m noOpChainBackend) EstimateGas(context.Context, ethereum.CallMsg) (uint64, error) {
panic("chain no op: EstimateGas")
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/postage/postagecontract/contract.go
Expand Up @@ -96,7 +96,7 @@ func (c *postageContract) sendApproveTransaction(ctx context.Context, amount *bi
GasLimit: 65000,
Value: big.NewInt(0),
Description: approveDescription,
}, 0)
}, transaction.DefaultTipBoostPercent)
if err != nil {
return nil, err
}
Expand All @@ -123,7 +123,7 @@ func (c *postageContract) sendTransaction(ctx context.Context, callData []byte,
Description: desc,
}

txHash, err := c.transactionService.Send(ctx, request, 0)
txHash, err := c.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/settlement/swap/chequebook/cashout.go
Expand Up @@ -149,7 +149,7 @@ func (s *cashoutService) CashCheque(ctx context.Context, chequebook, recipient c
Description: "cheque cashout",
}

txHash, err := s.transactionService.Send(ctx, request, 0)
txHash, err := s.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent)
if err != nil {
return common.Hash{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/settlement/swap/chequebook/chequebook.go
Expand Up @@ -334,7 +334,7 @@ func (s *service) Withdraw(ctx context.Context, amount *big.Int) (hash common.Ha
Description: fmt.Sprintf("chequebook withdrawal of %d BZZ", amount),
}

txHash, err := s.transactionService.Send(ctx, request, 0)
txHash, err := s.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent)
if err != nil {
return common.Hash{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/settlement/swap/chequebook/factory.go
Expand Up @@ -87,7 +87,7 @@ func (c *factory) Deploy(ctx context.Context, issuer common.Address, defaultHard
Description: "chequebook deployment",
}

txHash, err := c.transactionService.Send(ctx, request, 0)
txHash, err := c.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent)
if err != nil {
return common.Hash{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/settlement/swap/erc20/erc20.go
Expand Up @@ -83,7 +83,7 @@ func (c *erc20Service) Transfer(ctx context.Context, address common.Address, val
Description: "token transfer",
}

txHash, err := c.transactionService.Send(ctx, request, 0)
txHash, err := c.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent)
if err != nil {
return common.Hash{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/storageincentives/staking/contract.go
Expand Up @@ -106,7 +106,7 @@ func (s *contract) sendTransaction(ctx context.Context, callData []byte, desc st
Description: desc,
}

txHash, err := s.transactionService.Send(ctx, request, 0)
txHash, err := s.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/transaction/backend.go
Expand Up @@ -26,6 +26,7 @@ type Backend interface {
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
SuggestGasPrice(ctx context.Context) (*big.Int, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
SendTransaction(ctx context.Context, tx *types.Transaction) error
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
Expand Down
10 changes: 10 additions & 0 deletions pkg/transaction/backendmock/backend.go
Expand Up @@ -19,6 +19,7 @@ type backendMock struct {
codeAt func(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
sendTransaction func(ctx context.Context, tx *types.Transaction) error
suggestGasPrice func(ctx context.Context) (*big.Int, error)
suggestGasTipCap func(ctx context.Context) (*big.Int, error)
estimateGas func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
transactionReceipt func(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
pendingNonceAt func(ctx context.Context, account common.Address) (uint64, error)
Expand Down Expand Up @@ -130,6 +131,9 @@ func (m *backendMock) NonceAt(ctx context.Context, account common.Address, block
}

func (m *backendMock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
if m.suggestGasPrice != nil {
return m.suggestGasTipCap(ctx)
}
return nil, errors.New("not implemented")
}

Expand Down Expand Up @@ -181,6 +185,12 @@ func WithSuggestGasPriceFunc(f func(ctx context.Context) (*big.Int, error)) Opti
})
}

func WithSuggestGasTipCapFunc(f func(ctx context.Context) (*big.Int, error)) Option {
return optionFunc(func(s *backendMock) {
s.suggestGasTipCap = f
})
}

func WithEstimateGasFunc(f func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)) Option {
return optionFunc(func(s *backendMock) {
s.estimateGas = f
Expand Down
82 changes: 52 additions & 30 deletions pkg/transaction/transaction.go
Expand Up @@ -43,7 +43,7 @@ var (

// minGasPrice determines the minimum gas price
// threshold (in wei) for the creation of a transaction.
var minGasPrice = big.NewInt(1000)
const DefaultTipBoostPercent = 20

// TxRequest describes a request for a transaction that can be executed.
type TxRequest struct {
Expand Down Expand Up @@ -71,7 +71,7 @@ type StoredTransaction struct {
type Service interface {
io.Closer
// Send creates a transaction based on the request (with gasprice increased by provided percentage) and sends it.
Send(ctx context.Context, request *TxRequest, priceBoostPercent int) (txHash common.Hash, err error)
Send(ctx context.Context, request *TxRequest, tipCapBoostPercent int) (txHash common.Hash, err error)
// Call simulate a transaction based on the request.
Call(ctx context.Context, request *TxRequest) (result []byte, err error)
// WaitForReceipt waits until either the transaction with the given hash has been mined or the context is cancelled.
Expand Down Expand Up @@ -140,7 +140,7 @@ func NewService(logger log.Logger, backend Backend, signer crypto.Signer, store
}

// Send creates and signs a transaction based on the request and sends it.
func (t *transactionService) Send(ctx context.Context, request *TxRequest, priceBoostPercent int) (txHash common.Hash, err error) {
func (t *transactionService) Send(ctx context.Context, request *TxRequest, tipCapBoostPercent int) (txHash common.Hash, err error) {
loggerV1 := t.logger.V(1).Register()

t.lock.Lock()
Expand All @@ -151,7 +151,7 @@ func (t *transactionService) Send(ctx context.Context, request *TxRequest, price
return common.Hash{}, err
}

tx, err := t.prepareTransaction(ctx, request, nonce, priceBoostPercent)
tx, err := t.prepareTransaction(ctx, request, nonce, tipCapBoostPercent)
if err != nil {
return common.Hash{}, err
}
Expand Down Expand Up @@ -254,7 +254,7 @@ func (t *transactionService) StoredTransaction(txHash common.Hash) (*StoredTrans
}

// prepareTransaction creates a signable transaction based on a request.
func (t *transactionService) prepareTransaction(ctx context.Context, request *TxRequest, nonce uint64, boostPercent int) (tx *types.Transaction, err error) {
func (t *transactionService) prepareTransaction(ctx context.Context, request *TxRequest, nonce uint64, tipPercent int) (tx *types.Transaction, err error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would this be better as tipBoostPercent?

var gasLimit uint64
if request.GasLimit == 0 {
gasLimit, err = t.backend.EstimateGas(ctx, ethereum.CallMsg{
Expand All @@ -272,25 +272,43 @@ func (t *transactionService) prepareTransaction(ctx context.Context, request *Tx
gasLimit = request.GasLimit
}

/*
Transactions are EIP 1559 dynamic transactions where there are three fee related fields:
1. base fee is the price that will be burned as part of the transaction.
2. max fee is the max price we are willing to spend as gas price.
3. max priority fee is max price want to give to the miner to prioritize the transaction.
as an example:
if base fee is 15, max fee is 20, and max priority is 3, gas price will be 15 + 3 = 18
if base is 15, max fee is 20, and max priority fee is 10,
gas price will be 15 + 10 = 25, but since 25 > 20, gas price is 20.
notice that gas price does not exceed 20 as defined by max fee.
*/

gasPrice := request.GasPrice
if gasPrice == nil {
gasPriceSuggested, err := t.backend.SuggestGasPrice(ctx)
gasPrice = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(boostPercent)+100), gasPriceSuggested), big.NewInt(100))
gasPrice, err = t.backend.SuggestGasPrice(ctx)
if err != nil {
return nil, err
}
}
if gasPrice.Cmp(minGasPrice) < 0 {
return nil, ErrGasPriceTooLow

gasTipCap, err := t.backend.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}

return types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: request.To,
Value: request.Value,
Gas: gasLimit,
GasPrice: gasPrice,
Data: request.Data,
gasTipCap = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(tipPercent)+100), gasTipCap), big.NewInt(100))
gasFeeCap := new(big.Int).Add(gasTipCap, gasPrice)

return types.NewTx(&types.DynamicFeeTx{
Nonce: nonce,
ChainID: t.chainID,
To: request.To,
Value: request.Value,
Gas: gasLimit,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: request.Data,
}), nil
}

Expand Down Expand Up @@ -385,13 +403,15 @@ func (t *transactionService) ResendTransaction(ctx context.Context, txHash commo
return err
}

tx := types.NewTx(&types.LegacyTx{
Nonce: storedTransaction.Nonce,
To: storedTransaction.To,
Value: storedTransaction.Value,
Gas: storedTransaction.GasLimit,
GasPrice: storedTransaction.GasPrice,
Data: storedTransaction.Data,
tx := types.NewTx(&types.DynamicFeeTx{
Nonce: storedTransaction.Nonce,
ChainID: t.chainID,
To: storedTransaction.To,
Value: storedTransaction.Value,
Gas: storedTransaction.GasLimit,
GasTipCap: storedTransaction.GasPrice,
GasFeeCap: storedTransaction.GasPrice,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we be recalling the individual GasTipCap and GasFeeCap that were originally specified for the transaction? Or haven't we modified the transaction store for the new fields yet?

Copy link
Member Author

@istae istae Nov 7, 2022

Choose a reason for hiding this comment

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

it's ok to put the gas price of the transaction as the gas fee cap and tip because the gas price was originally calculated using the gas tip cap and gas fee cap.

here is an example:
https://goerli.etherscan.io/tx/0x621afbab180977bc77230ec1d5066bda0c617bd8a7126f4896c384ffb8b94dc7

where the gas price is base fee + gas tip, with an upper bound of gas fee cap.

ideally, what needs to be done is get a fresh suggested gas price and tip cap and resend the trx with that because the base price of the blockchain might have changed in the meanwhile.

Data: storedTransaction.Data,
})

signedTx, err := t.signer.SignTx(tx, t.chainID)
Expand Down Expand Up @@ -425,13 +445,15 @@ func (t *transactionService) CancelTransaction(ctx context.Context, originalTxHa
return common.Hash{}, ErrGasPriceTooLow
}

signedTx, err := t.signer.SignTx(types.NewTx(&types.LegacyTx{
Nonce: storedTransaction.Nonce,
To: &t.sender,
Value: big.NewInt(0),
Gas: 21000,
GasPrice: gasPrice,
Data: []byte{},
signedTx, err := t.signer.SignTx(types.NewTx(&types.DynamicFeeTx{
Nonce: storedTransaction.Nonce,
ChainID: t.chainID,
To: &t.sender,
Value: big.NewInt(0),
Gas: 21000,
GasTipCap: gasPrice,
GasFeeCap: gasPrice,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto here on the difference between GasTipCap and GasFeeCap?

Copy link
Member Author

Choose a reason for hiding this comment

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

we have a test case called "sendWithBoost" which defines a different tip and fee, this is sufficient unit testing for now.

Data: []byte{},
}), t.chainID)
if err != nil {
return common.Hash{}, err
Expand Down