@@ -13,13 +13,14 @@ import (
1313
1414 "github.com/avast/retry-go"
1515 "github.com/ethereum/go-ethereum"
16+ "github.com/ethereum/go-ethereum/ethclient"
17+ "github.com/ethereum/go-ethereum/ethclient/simulated"
1618 "github.com/ethereum/go-ethereum/rpc"
1719
1820 "github.com/ethereum/go-ethereum/accounts/abi"
1921 "github.com/ethereum/go-ethereum/accounts/abi/bind"
2022 "github.com/ethereum/go-ethereum/common"
2123 "github.com/ethereum/go-ethereum/core/types"
22- "github.com/ethereum/go-ethereum/ethclient"
2324 "github.com/pkg/errors"
2425 "github.com/rs/zerolog"
2526 "golang.org/x/sync/errgroup"
6465// Client is a vanilla go-ethereum client with enhanced debug logging
6566type Client struct {
6667 Cfg * Config
67- Client * ethclient .Client
68+ Client simulated .Client
6869 Addresses []common.Address
6970 PrivateKeys []* ecdsa.PrivateKey
7071 ChainID int64
@@ -139,23 +140,26 @@ func NewClientWithConfig(cfg *Config) (*Client, error) {
139140 }
140141
141142 abiFinder := NewABIFinder (contractAddressToNameMap , cs )
142- if len (cfg .Network .URLs ) == 0 {
143- return nil , fmt .Errorf ("at least one url should be present in config in 'secret_urls = []'" )
144- }
145- tr , err := NewTracer (cs , & abiFinder , cfg , contractAddressToNameMap , addrs )
146- if err != nil {
147- return nil , errors .Wrap (err , ErrCreateTracer )
143+
144+ var opts []ClientOpt
145+
146+ // even if the ethclient that was passed supports tracing, we still need the RPC URL, because we cannot get from
147+ // the instance of ethclient, since it doesn't expose any such method
148+ if (cfg .ethclient != nil && shouldIntialiseTracer (cfg .ethclient , cfg ) && len (cfg .Network .URLs ) > 0 ) || cfg .ethclient == nil {
149+ tr , err := NewTracer (cs , & abiFinder , cfg , contractAddressToNameMap , addrs )
150+ if err != nil {
151+ return nil , errors .Wrap (err , ErrCreateTracer )
152+ }
153+ opts = append (opts , WithTracer (tr ))
148154 }
149155
156+ opts = append (opts , WithContractStore (cs ), WithNonceManager (nm ), WithContractMap (contractAddressToNameMap ), WithABIFinder (& abiFinder ))
157+
150158 return NewClientRaw (
151159 cfg ,
152160 addrs ,
153161 pkeys ,
154- WithContractStore (cs ),
155- WithNonceManager (nm ),
156- WithTracer (tr ),
157- WithContractMap (contractAddressToNameMap ),
158- WithABIFinder (& abiFinder ),
162+ opts ... ,
159163 )
160164}
161165
@@ -175,53 +179,70 @@ func NewClientRaw(
175179 pkeys []* ecdsa.PrivateKey ,
176180 opts ... ClientOpt ,
177181) (* Client , error ) {
178- if len (cfg .Network .URLs ) == 0 {
179- return nil , errors .New ("no RPC URL provided" )
180- }
181- if len (cfg .Network .URLs ) > 1 {
182- L .Warn ().Msg ("Multiple RPC URLs provided, only the first one will be used" )
183- }
184-
185182 if cfg .ReadOnly && (len (addrs ) > 0 || len (pkeys ) > 0 ) {
186183 return nil , errors .New (ErrReadOnlyWithPrivateKeys )
187184 }
188185
189- ctx , cancel := context .WithTimeout (context .Background (), cfg .Network .DialTimeout .Duration ())
190- defer cancel ()
191- rpcClient , err := rpc .DialOptions (ctx ,
192- cfg .FirstNetworkURL (),
193- rpc .WithHeaders (cfg .RPCHeaders ),
194- rpc .WithHTTPClient (& http.Client {
195- Transport : NewLoggingTransport (),
196- }),
197- )
198- if err != nil {
199- return nil , fmt .Errorf ("failed to connect RPC client to '%s' due to: %w" , cfg .FirstNetworkURL (), err )
200- }
201- client := ethclient .NewClient (rpcClient )
186+ var firstUrl string
187+ var client simulated.Client
188+ if cfg .ethclient == nil {
189+ L .Info ().Msg ("Creating new ethereum client" )
190+ if len (cfg .Network .URLs ) == 0 {
191+ return nil , errors .New ("no RPC URL provided" )
192+ }
202193
203- if cfg .Network .ChainID == 0 {
204- chainId , err := client .ChainID (context .Background ())
194+ if len (cfg .Network .URLs ) > 1 {
195+ L .Warn ().Msg ("Multiple RPC URLs provided, only the first one will be used" )
196+ }
197+
198+ ctx , cancel := context .WithTimeout (context .Background (), cfg .Network .DialTimeout .Duration ())
199+ defer cancel ()
200+ rpcClient , err := rpc .DialOptions (ctx ,
201+ cfg .MustFirstNetworkURL (),
202+ rpc .WithHeaders (cfg .RPCHeaders ),
203+ rpc .WithHTTPClient (& http.Client {
204+ Transport : NewLoggingTransport (),
205+ }),
206+ )
205207 if err != nil {
206- return nil , errors . Wrap ( err , "failed to get chain ID" )
208+ return nil , fmt . Errorf ( "failed to connect RPC client to '%s' due to: %w" , cfg . MustFirstNetworkURL (), err )
207209 }
208- cfg .Network .ChainID = chainId .Uint64 ()
210+ client = ethclient .NewClient (rpcClient )
211+ firstUrl = cfg .MustFirstNetworkURL ()
212+ } else {
213+ L .Info ().
214+ Str ("Type" , reflect .TypeOf (cfg .ethclient ).String ()).
215+ Msg ("Using provided ethereum client" )
216+ client = cfg .ethclient
209217 }
218+
210219 ctx , cancelFunc := context .WithCancel (context .Background ())
211220 c := & Client {
212- Cfg : cfg ,
213221 Client : client ,
222+ Cfg : cfg ,
214223 Addresses : addrs ,
215224 PrivateKeys : pkeys ,
216- URL : cfg . FirstNetworkURL () ,
225+ URL : firstUrl ,
217226 ChainID : mustSafeInt64 (cfg .Network .ChainID ),
218227 Context : ctx ,
219228 CancelFunc : cancelFunc ,
220229 }
230+
221231 for _ , o := range opts {
222232 o (c )
223233 }
224234
235+ if cfg .Network .ChainID == 0 {
236+ chainId , err := c .Client .ChainID (context .Background ())
237+ if err != nil {
238+ return nil , errors .Wrap (err , "failed to get chain ID" )
239+ }
240+ cfg .Network .ChainID = chainId .Uint64 ()
241+ c .ChainID = mustSafeInt64 (cfg .Network .ChainID )
242+ }
243+
244+ var err error
245+
225246 if c .ContractAddressToNameMap .addressMap == nil {
226247 c .ContractAddressToNameMap = NewEmptyContractMap ()
227248 if ! cfg .IsSimulatedNetwork () {
@@ -279,7 +300,7 @@ func NewClientRaw(
279300 L .Info ().
280301 Str ("NetworkName" , cfg .Network .Name ).
281302 Interface ("Addresses" , addrs ).
282- Str ("RPC" , cfg . FirstNetworkURL () ).
303+ Str ("RPC" , firstUrl ).
283304 Uint64 ("ChainID" , cfg .Network .ChainID ).
284305 Int64 ("Ephemeral keys" , * cfg .EphemeralAddrs ).
285306 Msg ("Created new client" )
@@ -316,7 +337,9 @@ func NewClientRaw(
316337 }
317338 }
318339
319- if c .Cfg .TracingLevel != TracingLevel_None && c .Tracer == nil {
340+ // we cannot use the tracer with simulated backend, because it doesn't expose a method to get rpcClient (even though it has one)
341+ // and Tracer needs rpcClient to call debug_traceTransaction
342+ if shouldIntialiseTracer (c .Client , cfg ) && c .Cfg .TracingLevel != TracingLevel_None && c .Tracer == nil {
320343 if c .ContractStore == nil {
321344 cs , err := NewContractStore (filepath .Join (cfg .ConfigDir , cfg .ABIDir ), filepath .Join (cfg .ConfigDir , cfg .BINDir ), cfg .GethWrappersDirs )
322345 if err != nil {
@@ -407,7 +430,7 @@ func (m *Client) TransferETHFromKey(ctx context.Context, fromKeyNum int, to stri
407430 ctx , chainCancel := context .WithTimeout (ctx , m .Cfg .Network .TxnTimeout .Duration ())
408431 defer chainCancel ()
409432
410- chainID , err := m .Client .NetworkID (ctx )
433+ chainID , err := m .Client .ChainID (ctx )
411434 if err != nil {
412435 return errors .Wrap (err , "failed to get network ID" )
413436 }
@@ -1385,3 +1408,11 @@ func (m *Client) mergeLogMeta(pe *DecodedTransactionLog, l types.Log) {
13851408 pe .TXIndex = l .TxIndex
13861409 pe .Removed = l .Removed
13871410}
1411+
1412+ func shouldIntialiseTracer (client simulated.Client , cfg * Config ) bool {
1413+ return len (cfg .Network .URLs ) > 0 && supportsTracing (client )
1414+ }
1415+
1416+ func supportsTracing (client simulated.Client ) bool {
1417+ return strings .Contains (reflect .TypeOf (client ).String (), "ethclient.Client" )
1418+ }
0 commit comments