|
| 1 | +package provider |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "strconv" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/ethereum/go-ethereum/accounts/abi/bind" |
| 10 | + "github.com/ethereum/go-ethereum/common" |
| 11 | + "github.com/ethereum/go-ethereum/core/types" |
| 12 | + |
| 13 | + chain_selectors "github.com/smartcontractkit/chain-selectors" |
| 14 | + |
| 15 | + "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" |
| 16 | + "github.com/smartcontractkit/chainlink-deployments-framework/deployment" |
| 17 | +) |
| 18 | + |
| 19 | +// ConfirmFunctor is an interface for creating a confirmation function for transactions on the |
| 20 | +// EVM chain. |
| 21 | +type ConfirmFunctor interface { |
| 22 | + // Generate returns a function that confirms transactions on the EVM chain. |
| 23 | + Generate( |
| 24 | + ctx context.Context, selector uint64, client evm.OnchainClient, from common.Address, |
| 25 | + ) (evm.ConfirmFunc, error) |
| 26 | +} |
| 27 | + |
| 28 | +// ConfirmFuncGeth returns a ConfirmFunctor that uses the Geth client to confirm transactions. |
| 29 | +func ConfirmFuncGeth(waitMinedTimeout time.Duration) ConfirmFunctor { |
| 30 | + return &confirmFuncGeth{ |
| 31 | + waitMinedTimeout: waitMinedTimeout, |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +// confirmFuncGeth implements the ConfirmFunctor interface which generates a confirmation function |
| 36 | +// for transactions using the Geth client. |
| 37 | +type confirmFuncGeth struct { |
| 38 | + waitMinedTimeout time.Duration |
| 39 | +} |
| 40 | + |
| 41 | +// Generate returns a function that confirms transactions using the Geth client. |
| 42 | +func (g *confirmFuncGeth) Generate( |
| 43 | + ctx context.Context, selector uint64, client evm.OnchainClient, from common.Address, |
| 44 | +) (evm.ConfirmFunc, error) { |
| 45 | + return func(tx *types.Transaction) (uint64, error) { |
| 46 | + var blockNum uint64 |
| 47 | + if tx == nil { |
| 48 | + return 0, fmt.Errorf("tx was nil, nothing to confirm for selector: %d", selector) |
| 49 | + } |
| 50 | + |
| 51 | + ctxTimeout, cancel := context.WithTimeout(ctx, g.waitMinedTimeout) |
| 52 | + defer cancel() |
| 53 | + |
| 54 | + receipt, err := bind.WaitMined(ctxTimeout, client, tx) |
| 55 | + if err != nil { |
| 56 | + return 0, fmt.Errorf("tx %s failed to confirm for selector %d: %w", |
| 57 | + tx.Hash().Hex(), selector, err, |
| 58 | + ) |
| 59 | + } |
| 60 | + if receipt == nil { |
| 61 | + return blockNum, fmt.Errorf("receipt was nil for tx %s for selector %d", |
| 62 | + tx.Hash().Hex(), selector, |
| 63 | + ) |
| 64 | + } |
| 65 | + |
| 66 | + blockNum = receipt.BlockNumber.Uint64() |
| 67 | + |
| 68 | + if receipt.Status == 0 { |
| 69 | + reason, err := getErrorReasonFromTx(ctxTimeout, client, from, tx, receipt) |
| 70 | + if err == nil && reason != "" { |
| 71 | + return 0, fmt.Errorf("tx %s reverted for selector %d: %s", |
| 72 | + tx.Hash().Hex(), selector, reason, |
| 73 | + ) |
| 74 | + } |
| 75 | + |
| 76 | + return blockNum, fmt.Errorf("tx %s reverted, could not decode error reason for selector %d", |
| 77 | + tx.Hash().Hex(), selector, |
| 78 | + ) |
| 79 | + } |
| 80 | + |
| 81 | + return blockNum, nil |
| 82 | + }, nil |
| 83 | +} |
| 84 | + |
| 85 | +// ConfirmFuncSeth returns a ConfirmFunctor that uses the Seth client to confirm transactions. |
| 86 | +// It requires the RPC URL, a list of directories where Geth wrappers are located, and an optional |
| 87 | +// configuration file path for the Seth client. If you do not have a configuration file, you can |
| 88 | +// pass an empty string. |
| 89 | +func ConfirmFuncSeth( |
| 90 | + rpcURL string, waitMinedTimeout time.Duration, gethWrapperDirs []string, configFilePath string, |
| 91 | +) ConfirmFunctor { |
| 92 | + return &confirmFuncSeth{ |
| 93 | + rpcURL: rpcURL, |
| 94 | + waitMinedTimeout: waitMinedTimeout, |
| 95 | + gethWrappersDirs: gethWrapperDirs, |
| 96 | + configFilePath: configFilePath, |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +// confirmFuncSeth implements the ConfirmFunctor interface which generates a confirmation function |
| 101 | +// for transactions using the Seth client. |
| 102 | +type confirmFuncSeth struct { |
| 103 | + rpcURL string |
| 104 | + waitMinedTimeout time.Duration |
| 105 | + gethWrappersDirs []string |
| 106 | + configFilePath string |
| 107 | +} |
| 108 | + |
| 109 | +// Generate returns a function that confirms transactions using the Seth client. The provided |
| 110 | +// client must be a MultiClient. |
| 111 | +func (g *confirmFuncSeth) Generate( |
| 112 | + ctx context.Context, selector uint64, client evm.OnchainClient, from common.Address, |
| 113 | +) (evm.ConfirmFunc, error) { |
| 114 | + // Convert the client to a MultiClient because we need to use the multi-client's WaitMined |
| 115 | + // method. |
| 116 | + multiClient, ok := client.(*deployment.MultiClient) |
| 117 | + if !ok { |
| 118 | + return nil, fmt.Errorf("expected client to be of type *deployment.MultiClient, got %T", client) |
| 119 | + } |
| 120 | + |
| 121 | + // Get the ChainID from the selector |
| 122 | + chainIDStr, err := chain_selectors.GetChainIDFromSelector(selector) |
| 123 | + if err != nil { |
| 124 | + return nil, fmt.Errorf("failed to get chain ID from selector %d: %w", selector, err) |
| 125 | + } |
| 126 | + |
| 127 | + chainID, err := strconv.ParseUint(chainIDStr, 10, 64) |
| 128 | + if err != nil { |
| 129 | + return nil, fmt.Errorf("failed to parse chain ID %s: %w", chainIDStr, err) |
| 130 | + } |
| 131 | + |
| 132 | + // Setup the seth client |
| 133 | + sethClient, err := newSethClient( |
| 134 | + g.rpcURL, chainID, g.gethWrappersDirs, g.configFilePath, |
| 135 | + ) |
| 136 | + if err != nil { |
| 137 | + return nil, fmt.Errorf("failed to setup seth client: %w", err) |
| 138 | + } |
| 139 | + |
| 140 | + return func(tx *types.Transaction) (uint64, error) { |
| 141 | + ctxTimeout, cancel := context.WithTimeout(ctx, g.waitMinedTimeout) |
| 142 | + defer cancel() |
| 143 | + |
| 144 | + if _, err := multiClient.WaitMined(ctxTimeout, tx); err != nil { |
| 145 | + return 0, err |
| 146 | + } |
| 147 | + |
| 148 | + decoded, err := sethClient.DecodeTx(tx) |
| 149 | + if err != nil { |
| 150 | + return 0, err |
| 151 | + } |
| 152 | + |
| 153 | + if decoded.Receipt == nil { |
| 154 | + return 0, fmt.Errorf("no receipt found for transaction %s even though it wasn't reverted. This should not happen", tx.Hash().String()) |
| 155 | + } |
| 156 | + |
| 157 | + return decoded.Receipt.BlockNumber.Uint64(), nil |
| 158 | + }, nil |
| 159 | +} |
0 commit comments