Skip to content

Commit

Permalink
TXMv2 alpha version
Browse files Browse the repository at this point in the history
  • Loading branch information
dimriou committed Oct 11, 2024
1 parent 64eb3cf commit 9c86afc
Show file tree
Hide file tree
Showing 12 changed files with 2,667 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ packages:
BalanceMonitor:
config:
dir: "{{ .InterfaceDir }}/../mocks"
github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm:
interfaces:
Client:
Storage:
AttemptBuilder:
github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr:
interfaces:
ChainConfig:
Expand Down
144 changes: 144 additions & 0 deletions core/chains/evm/txm/attempt_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package txm

import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
evmtypes "github.com/ethereum/go-ethereum/core/types"

"github.com/smartcontractkit/chainlink-common/pkg/logger"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types"
)

type Keystore interface {
SignTx(ctx context.Context, fromAddress common.Address, tx *evmtypes.Transaction, chainID *big.Int) (*evmtypes.Transaction, error)
}

type attemptBuilder struct {
chainID *big.Int
priceMax *assets.Wei // TODO: PriceMax per key level
estimator gas.EvmFeeEstimator
keystore Keystore
}

func NewAttemptBuilder(chainID *big.Int, priceMax *assets.Wei, estimator gas.EvmFeeEstimator, keystore Keystore) *attemptBuilder {
return &attemptBuilder{
chainID: chainID,
estimator: estimator,
keystore: keystore,
}
}

func (a *attemptBuilder) NewAttempt(ctx context.Context, lggr logger.Logger, tx *types.Transaction, dynamic bool) (*types.Attempt, error) {
fee, estimatedGasLimit, err := a.estimator.GetFee(ctx, tx.Data, tx.SpecifiedGasLimit, a.priceMax, &tx.FromAddress, &tx.ToAddress)
if err != nil {
return nil, err
}
txType := evmtypes.LegacyTxType
if dynamic {
txType = evmtypes.DynamicFeeTxType
}
return a.newCustomAttempt(ctx, tx, fee, estimatedGasLimit, byte(txType), lggr)
}

func (a *attemptBuilder) NewBumpAttempt(ctx context.Context, lggr logger.Logger, tx *types.Transaction, previousAttempt types.Attempt) (*types.Attempt, error) {
bumpedFee, bumpedFeeLimit, err := a.estimator.BumpFee(ctx, previousAttempt.Fee, tx.SpecifiedGasLimit, a.priceMax, nil)
if err != nil {
return nil, err
}
return a.newCustomAttempt(ctx, tx, bumpedFee, bumpedFeeLimit, previousAttempt.Type, lggr)
}

func (a *attemptBuilder) newCustomAttempt(
ctx context.Context,
tx *types.Transaction,
fee gas.EvmFee,
estimatedGasLimit uint64,
txType byte,
lggr logger.Logger,
) (attempt *types.Attempt, err error) {
switch txType {
case 0x0:
if fee.GasPrice == nil {
err = fmt.Errorf("attemptID: %v of txID: %v, is a type 0 transaction but estimator did not return legacy fee", tx.ID, attempt.ID)
logger.Sugared(lggr).AssumptionViolation(err.Error())
return
}
return a.newLegacyAttempt(ctx, tx, fee.GasPrice, estimatedGasLimit)
case 0x2:
if !fee.ValidDynamic() {
err = fmt.Errorf("attemptID %v of txID: %v, is a type 2 transaction but estimator did not return dynamic fee", tx.ID, attempt.ID)
logger.Sugared(lggr).AssumptionViolation(err.Error())
return
}
return a.newDynamicFeeAttempt(ctx, tx, fee.DynamicFee, estimatedGasLimit)
default:
return nil, fmt.Errorf("cannot build attempt, unrecognized transaction type: %v", txType)
}
}

func (a *attemptBuilder) newLegacyAttempt(ctx context.Context, tx *types.Transaction, gasPrice *assets.Wei, estimatedGasLimit uint64) (*types.Attempt, error) {
var data []byte
if !tx.IsPurgeable {
data = tx.Data
}
legacyTx := evmtypes.LegacyTx{
Nonce: tx.Nonce,
To: &tx.ToAddress,
Value: tx.Value,
Gas: estimatedGasLimit,
GasPrice: gasPrice.ToInt(),
Data: data,
}

signedTx, err := a.keystore.SignTx(ctx, tx.FromAddress, evmtypes.NewTx(&legacyTx), a.chainID)
if err != nil {
return nil, fmt.Errorf("failed to sign attempt for txID: %v, err: %w", tx.ID, err)
}

attempt := &types.Attempt{
TxID: tx.ID,
Fee: gas.EvmFee{GasPrice: gasPrice},
Hash: signedTx.Hash(),
GasLimit: estimatedGasLimit,
SignedTransaction: signedTx,
}

return attempt, nil
}

func (a *attemptBuilder) newDynamicFeeAttempt(ctx context.Context, tx *types.Transaction, dynamicFee gas.DynamicFee, estimatedGasLimit uint64) (*types.Attempt, error) {
var data []byte
if !tx.IsPurgeable {
data = tx.Data
}
dynamicTx := evmtypes.DynamicFeeTx{
Nonce: tx.Nonce,
To: &tx.ToAddress,
Value: tx.Value,
Gas: estimatedGasLimit,
GasFeeCap: dynamicFee.GasFeeCap.ToInt(),
GasTipCap: dynamicFee.GasTipCap.ToInt(),
Data: data,
}

signedTx, err := a.keystore.SignTx(ctx, tx.FromAddress, evmtypes.NewTx(&dynamicTx), a.chainID)
if err != nil {
return nil, fmt.Errorf("failed to sign attempt for txID: %v, err: %w", tx.ID, err)
}

attempt := &types.Attempt{
TxID: tx.ID,
Fee: gas.EvmFee{DynamicFee: gas.DynamicFee{GasFeeCap: dynamicFee.GasFeeCap, GasTipCap: dynamicFee.GasTipCap}},
Hash: signedTx.Hash(),
GasLimit: estimatedGasLimit,
SignedTransaction: signedTx,
}

return attempt, nil
}
32 changes: 32 additions & 0 deletions core/chains/evm/txm/dummy_keystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package txm

import (
"context"
"crypto/ecdsa"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)

type DummyKeystore struct {
privateKey *ecdsa.PrivateKey
}

func NewKeystore(privateKeyString string) *DummyKeystore {
return &DummyKeystore{}
}

func (k *DummyKeystore) Add(privateKeyString string) error {
privateKey, err := crypto.HexToECDSA(privateKeyString)
if err != nil {
return err
}
k.privateKey = privateKey
return nil
}

func (k *DummyKeystore) SignTx(_ context.Context, fromAddress common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
return types.SignTx(tx, types.NewEIP155Signer(chainID), k.privateKey)
}
Loading

0 comments on commit 9c86afc

Please sign in to comment.