diff --git a/relayer/gaspricer/gaspricer.go b/relayer/gaspricer/gaspricer.go new file mode 100644 index 00000000..291d772f --- /dev/null +++ b/relayer/gaspricer/gaspricer.go @@ -0,0 +1,8 @@ +package gaspricer + +import "context" + +type GasPricer interface { + Start(ctx context.Context) + GasPrice() uint64 +} diff --git a/relayer/gaspricer/network.go b/relayer/gaspricer/network.go new file mode 100644 index 00000000..ded3e54a --- /dev/null +++ b/relayer/gaspricer/network.go @@ -0,0 +1,74 @@ +package gaspricer + +import ( + "context" + "log" + "os" + "sync" + "time" + + "github.com/umbracle/ethgo/jsonrpc" +) + +func NewNetworkGasPricer(logger *log.Logger, client *jsonrpc.Eth, interval ...time.Duration) (GasPricer, error) { + if logger == nil { + logger = log.New(os.Stdout, "", log.LstdFlags) + } + n := &NetworkGasPricer{ + client: client, + logger: logger, + } + if len(interval) == 1 { + n.interval = interval[0] + } else { + n.interval = 15 * time.Second + } + + // try to fetch the gas price once + if err := n.updateGasPrice(); err != nil { + return nil, err + } + return n, nil +} + +type NetworkGasPricer struct { + logger *log.Logger + lock sync.Mutex + client *jsonrpc.Eth + interval time.Duration + gasPrice uint64 +} + +func (n *NetworkGasPricer) updateGasPrice() error { + n.lock.Lock() + defer n.lock.Unlock() + + gasPrice, err := n.client.GasPrice() + if err != nil { + return err + } + n.gasPrice = gasPrice + return nil +} + +func (n *NetworkGasPricer) Start(ctx context.Context) { + go func() { + for { + select { + case <-time.After(n.interval): + if err := n.updateGasPrice(); err != nil { + n.logger.Printf("[ERROR]: Failed to get gas price: %v", err) + } + case <-ctx.Done(): + return + } + } + }() +} + +func (n *NetworkGasPricer) GasPrice() uint64 { + n.lock.Lock() + defer n.lock.Unlock() + + return n.gasPrice +} diff --git a/relayer/gaspricer/network_test.go b/relayer/gaspricer/network_test.go new file mode 100644 index 00000000..7b539329 --- /dev/null +++ b/relayer/gaspricer/network_test.go @@ -0,0 +1,22 @@ +package gaspricer + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/umbracle/ethgo/testutil" +) + +func TestGasPricer_Network(t *testing.T) { + srv := testutil.NewTestServer(t, nil) + defer srv.Close() + + client, err := jsonrpc.NewClient(srv.HTTPAddr()) + assert.NoError(t, err) + + pricer, err := NewNetworkGasPricer(nil, client.Eth()) + assert.NoError(t, err) + + assert.Equal(t, pricer.GasPrice(), uint64(1)) +} diff --git a/relayer/relayer.go b/relayer/relayer.go new file mode 100644 index 00000000..c2f5ffc8 --- /dev/null +++ b/relayer/relayer.go @@ -0,0 +1,116 @@ +package relayer + +import ( + "fmt" + "log" + "os" + + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/contract" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/umbracle/ethgo/relayer/gaspricer" +) + +var _ contract.Provider = (*Relayer)(nil) + +type Config struct { + Logger *log.Logger + Endpoint string + GasPricer gaspricer.GasPricer +} + +type RelayerOption func(*Config) + +func WithGasPricer(pricer gaspricer.GasPricer) RelayerOption { + return func(c *Config) { + c.GasPricer = pricer + } +} + +func WithJSONRPCEndpoint(endpoint string) RelayerOption { + return func(c *Config) { + c.Endpoint = endpoint + } +} + +func WithLogger(logger *log.Logger) RelayerOption { + return func(c *Config) { + c.Logger = logger + } +} + +func DefaultConfig() *Config { + return &Config{ + Logger: log.New(os.Stdout, "", log.LstdFlags), + Endpoint: "http://localhost:8545", + } +} + +type Relayer struct { + config *Config + client *jsonrpc.Client + closeCh chan struct{} +} + +func NewRelayer(configOpts ...RelayerOption) (*Relayer, error) { + config := DefaultConfig() + for _, opts := range configOpts { + opts(config) + } + + client, err := jsonrpc.NewClient(config.Endpoint) + if err != nil { + return nil, err + } + + // if gas pricer is not set, use the network one + if config.GasPricer == nil { + pricer, err := gaspricer.NewNetworkGasPricer(config.Logger, client.Eth()) + if err != nil { + return nil, err + } + config.GasPricer = pricer + } + + r := &Relayer{ + config: config, + client: client, + closeCh: make(chan struct{}), + } + + go r.run() + + return r, nil +} + +type stateTxn struct { + To ethgo.Address + key ethgo.Key + input []byte + opts *contract.TxnOpts +} + +func (r *Relayer) Txn(to ethgo.Address, key ethgo.Key, input []byte, opts *contract.TxnOpts) (contract.Txn, error) { + txn := &stateTxn{} + fmt.Println(txn) + return nil, nil +} + +func (r *Relayer) SendTransaction(txn *ethgo.Transaction) (ethgo.Hash, error) { + return ethgo.Hash{}, nil +} + +func (r *Relayer) run() { + for { + // wait to see if anything is stucked + // after it is done move to the next pending txn + select { + case <-r.closeCh: + return + } + } +} + +func (r *Relayer) Call(to ethgo.Address, input []byte, opts *contract.CallOpts) ([]byte, error) { + panic("relayer does not make calls") +} diff --git a/relayer/relayer_test.go b/relayer/relayer_test.go new file mode 100644 index 00000000..f2e91370 --- /dev/null +++ b/relayer/relayer_test.go @@ -0,0 +1,7 @@ +package relayer + +import "testing" + +func TestRelayer(t *testing.T) { + +}