Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bindings/rocketpool/ec-interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rocketpool
import (
"context"
"math/big"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -102,4 +103,11 @@ type ExecutionClient interface {
// SyncProgress retrieves the current progress of the sync algorithm. If there's
// no sync currently running, it returns nil.
SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error)

/// =================
/// Utility functions
/// =================

// LatestBlockTime returns the timestamp of the latest block
LatestBlockTime(ctx context.Context) (time.Time, error)
}
13 changes: 1 addition & 12 deletions rocketpool-cli/node/withdraw-rpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/rocket-pool/smartnode/bindings/utils/eth"
"github.com/urfave/cli"

"github.com/rocket-pool/smartnode/shared/services"
"github.com/rocket-pool/smartnode/shared/services/gas"
"github.com/rocket-pool/smartnode/shared/services/rocketpool"
cliutils "github.com/rocket-pool/smartnode/shared/utils/cli"
Expand All @@ -27,10 +26,6 @@ func nodeWithdrawRpl(c *cli.Context) error {
return err
}
defer rp.Close()
ec, err := services.GetEthClient(c)
if err != nil {
return err
}

// Get node status
status, err := rp.NodeStatus()
Expand All @@ -41,12 +36,6 @@ func nodeWithdrawRpl(c *cli.Context) error {
var unstakingPeriodEnd time.Time

if status.IsSaturnDeployed {
// Get the latest block time
latestBlockTimeUnix, err := services.GetEthClientLatestBlockTimestamp(ec)
if err != nil {
return err
}
latestBlockTime := time.Unix(int64(latestBlockTimeUnix), 0)
fmt.Print("The RPL withdrawal process has changed in Saturn. It is now a 2-step process:")
fmt.Println()
fmt.Print("1. Request to unstake a certain RPL amount;")
Expand All @@ -69,7 +58,7 @@ func nodeWithdrawRpl(c *cli.Context) error {
fmt.Printf("")
if status.UnstakingRPL.Cmp(big.NewInt(0)) > 0 {

if unstakingPeriodEnd.After(latestBlockTime) {
if unstakingPeriodEnd.After(status.LatestBlockTime) {
fmt.Printf("You have %.6f RPL currently unstaking until %s.\n", status.UnstakingRPL, unstakingPeriodEnd.Format(TimeFormat))
} else {
if !c.Bool("yes") || prompt.Confirm(fmt.Sprintf("You have %.6f RPL already unstaked. Would you like to withdraw it now?", eth.WeiToEth(status.UnstakingRPL))) {
Expand Down
4 changes: 1 addition & 3 deletions rocketpool/api/node/smoothing-pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package node
import (
"context"
"fmt"
"time"

"github.com/rocket-pool/smartnode/bindings/node"
"github.com/rocket-pool/smartnode/bindings/rewards"
Expand Down Expand Up @@ -66,11 +65,10 @@ func getSmoothingPoolRegistrationStatus(c *cli.Context) (*api.GetSmoothingPoolRe
}

// Get the time the user can next change their opt-in status
latestBlockTimeUnix, err := services.GetEthClientLatestBlockTimestamp(ec)
latestBlockTime, err := ec.LatestBlockTime(context.Background())
if err != nil {
return nil, err
}
latestBlockTime := time.Unix(int64(latestBlockTimeUnix), 0)
changeAvailableTime := regChangeTime.Add(intervalTime)
response.TimeLeftUntilChangeable = changeAvailableTime.Sub(latestBlockTime)

Expand Down
9 changes: 9 additions & 0 deletions rocketpool/api/node/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) {

// Sync
var wg errgroup.Group
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

if saturnDeployed {
wg.Go(func() error {
Expand Down Expand Up @@ -189,6 +191,11 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) {
})

if saturnDeployed {
wg.Go(func() error {
var err error
response.LatestBlockTime, err = rp.Client.LatestBlockTime(ctx)
return err
})
// Get the node's locked RPL
wg.Go(func() error {
var err error
Expand Down Expand Up @@ -417,6 +424,8 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) {

// Wait for data
if err := wg.Wait(); err != nil {
// Cancel in-flight requests.
cancel()
return nil, err
}

Expand Down
3 changes: 1 addition & 2 deletions rocketpool/watchtower/generate-rewards-tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/rocket-pool/smartnode/bindings/rewards"
"github.com/rocket-pool/smartnode/bindings/rocketpool"
"github.com/rocket-pool/smartnode/shared/services"
Expand Down Expand Up @@ -183,7 +182,7 @@ func (t *generateRewardsTree) generateRewardsTree(index uint64) {
archiveEcUrl := t.cfg.Smartnode.ArchiveECUrl.Value.(string)
if archiveEcUrl != "" {
t.log.Printlnf("%s Primary EC cannot retrieve state for historical block %d, using archive EC [%s]", generationPrefix, elBlockHeader.Number.Uint64(), archiveEcUrl)
ec, err := ethclient.Dial(archiveEcUrl)
ec, err := services.NewEthClient(archiveEcUrl)
if err != nil {
t.handleError(fmt.Errorf("Error connecting to archive EC: %w", err))
return
Expand Down
58 changes: 34 additions & 24 deletions shared/services/ec-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ import (
type ExecutionClientManager struct {
primaryEcUrl string
fallbackEcUrl string
primaryEc *ethclient.Client
fallbackEc *ethclient.Client
primaryEc *ethClient
fallbackEc *ethClient
logger log.ColorLogger
primaryReady bool
fallbackReady bool
ignoreSyncCheck bool
}

// This is a signature for a wrapped ethclient.Client function
type ecFunction func(*ethclient.Client) (interface{}, error)
type ecFunction func(*ethClient) (interface{}, error)

// Creates a new ExecutionClientManager instance based on the Rocket Pool config
func NewExecutionClientManager(cfg *config.RocketPoolConfig) (*ExecutionClientManager, error) {
Expand Down Expand Up @@ -80,8 +80,8 @@ func NewExecutionClientManager(cfg *config.RocketPoolConfig) (*ExecutionClientMa
return &ExecutionClientManager{
primaryEcUrl: primaryEcUrl,
fallbackEcUrl: fallbackEcUrl,
primaryEc: primaryEc,
fallbackEc: fallbackEc,
primaryEc: &ethClient{primaryEc},
fallbackEc: &ethClient{fallbackEc},
logger: log.NewColorLogger(color.FgYellow),
primaryReady: true,
fallbackReady: fallbackEc != nil,
Expand All @@ -96,7 +96,7 @@ func NewExecutionClientManager(cfg *config.RocketPoolConfig) (*ExecutionClientMa
// CodeAt returns the code of the given account. This is needed to differentiate
// between contract internal errors and the local chain being out of sync.
func (p *ExecutionClientManager) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.CodeAt(ctx, contract, blockNumber)
})
if err != nil {
Expand All @@ -108,7 +108,7 @@ func (p *ExecutionClientManager) CodeAt(ctx context.Context, contract common.Add
// CallContract executes an Ethereum contract call with the specified data as the
// input.
func (p *ExecutionClientManager) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.CallContract(ctx, call, blockNumber)
})
if err != nil {
Expand All @@ -123,7 +123,7 @@ func (p *ExecutionClientManager) CallContract(ctx context.Context, call ethereum

// HeaderByHash returns the block header with the given hash.
func (p *ExecutionClientManager) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.HeaderByHash(ctx, hash)
})
if err != nil {
Expand All @@ -135,7 +135,7 @@ func (p *ExecutionClientManager) HeaderByHash(ctx context.Context, hash common.H
// HeaderByNumber returns a block header from the current canonical chain. If number is
// nil, the latest known header is returned.
func (p *ExecutionClientManager) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.HeaderByNumber(ctx, number)
})
if err != nil {
Expand All @@ -146,7 +146,7 @@ func (p *ExecutionClientManager) HeaderByNumber(ctx context.Context, number *big

// PendingCodeAt returns the code of the given account in the pending state.
func (p *ExecutionClientManager) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.PendingCodeAt(ctx, account)
})
if err != nil {
Expand All @@ -157,7 +157,7 @@ func (p *ExecutionClientManager) PendingCodeAt(ctx context.Context, account comm

// PendingNonceAt retrieves the current pending nonce associated with an account.
func (p *ExecutionClientManager) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.PendingNonceAt(ctx, account)
})
if err != nil {
Expand All @@ -169,7 +169,7 @@ func (p *ExecutionClientManager) PendingNonceAt(ctx context.Context, account com
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
func (p *ExecutionClientManager) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.SuggestGasPrice(ctx)
})
if err != nil {
Expand All @@ -181,7 +181,7 @@ func (p *ExecutionClientManager) SuggestGasPrice(ctx context.Context) (*big.Int,
// SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
// a timely execution of a transaction.
func (p *ExecutionClientManager) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.SuggestGasTipCap(ctx)
})
if err != nil {
Expand All @@ -196,7 +196,7 @@ func (p *ExecutionClientManager) SuggestGasTipCap(ctx context.Context) (*big.Int
// transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default.
func (p *ExecutionClientManager) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.EstimateGas(ctx, call)
})
if err != nil {
Expand All @@ -207,7 +207,7 @@ func (p *ExecutionClientManager) EstimateGas(ctx context.Context, call ethereum.

// SendTransaction injects the transaction into the pending pool for execution.
func (p *ExecutionClientManager) SendTransaction(ctx context.Context, tx *types.Transaction) error {
_, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
_, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return nil, client.SendTransaction(ctx, tx)
})
return err
Expand All @@ -222,7 +222,7 @@ func (p *ExecutionClientManager) SendTransaction(ctx context.Context, tx *types.
//
// TODO(karalabe): Deprecate when the subscription one can return past data too.
func (p *ExecutionClientManager) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.FilterLogs(ctx, query)
})
if err != nil {
Expand All @@ -234,7 +234,7 @@ func (p *ExecutionClientManager) FilterLogs(ctx context.Context, query ethereum.
// SubscribeFilterLogs creates a background log filtering operation, returning
// a subscription immediately, which can be used to stream the found events.
func (p *ExecutionClientManager) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.SubscribeFilterLogs(ctx, query, ch)
})
if err != nil {
Expand All @@ -250,7 +250,7 @@ func (p *ExecutionClientManager) SubscribeFilterLogs(ctx context.Context, query
// TransactionReceipt returns the receipt of a transaction by transaction hash.
// Note that the receipt is not available for pending transactions.
func (p *ExecutionClientManager) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.TransactionReceipt(ctx, txHash)
})
if err != nil {
Expand All @@ -265,7 +265,7 @@ func (p *ExecutionClientManager) TransactionReceipt(ctx context.Context, txHash

// BlockNumber returns the most recent block number
func (p *ExecutionClientManager) BlockNumber(ctx context.Context) (uint64, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.BlockNumber(ctx)
})
if err != nil {
Expand All @@ -277,7 +277,7 @@ func (p *ExecutionClientManager) BlockNumber(ctx context.Context) (uint64, error
// BalanceAt returns the wei balance of the given account.
// The block number can be nil, in which case the balance is taken from the latest known block.
func (p *ExecutionClientManager) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.BalanceAt(ctx, account, blockNumber)
})
if err != nil {
Expand All @@ -288,7 +288,7 @@ func (p *ExecutionClientManager) BalanceAt(ctx context.Context, account common.A

// TransactionByHash returns the transaction with the given hash.
func (p *ExecutionClientManager) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
tx, isPending, err := client.TransactionByHash(ctx, hash)
result := []interface{}{tx, isPending}
return result, err
Expand All @@ -307,7 +307,7 @@ func (p *ExecutionClientManager) TransactionByHash(ctx context.Context, hash com
// NonceAt returns the account nonce of the given account.
// The block number can be nil, in which case the nonce is taken from the latest known block.
func (p *ExecutionClientManager) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.NonceAt(ctx, account, blockNumber)
})
if err != nil {
Expand All @@ -319,7 +319,7 @@ func (p *ExecutionClientManager) NonceAt(ctx context.Context, account common.Add
// SyncProgress retrieves the current progress of the sync algorithm. If there's
// no sync currently running, it returns nil.
func (p *ExecutionClientManager) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) {
result, err := p.runFunction(func(client *ethclient.Client) (interface{}, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.SyncProgress(ctx)
})
if err != nil {
Expand All @@ -328,6 +328,16 @@ func (p *ExecutionClientManager) SyncProgress(ctx context.Context) (*ethereum.Sy
return result.(*ethereum.SyncProgress), err
}

func (p *ExecutionClientManager) LatestBlockTime(ctx context.Context) (time.Time, error) {
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
return client.LatestBlockTime(ctx)
})
if err != nil {
return time.Time{}, err
}
return result.(time.Time), err
}

/// ==================
/// Internal functions
/// ==================
Expand Down Expand Up @@ -387,7 +397,7 @@ func getNetworkNameFromId(networkId uint) string {
}

// Check the client status
func checkEcStatus(client *ethclient.Client) api.ClientStatus {
func checkEcStatus(client *ethClient) api.ClientStatus {

status := api.ClientStatus{}

Expand Down
18 changes: 0 additions & 18 deletions shared/services/eth1.go

This file was deleted.

29 changes: 29 additions & 0 deletions shared/services/ethclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package services

import (
"context"
"time"

"github.com/ethereum/go-ethereum/ethclient"
)

type ethClient struct {
*ethclient.Client
}

func NewEthClient(url string) (*ethClient, error) {
ec, err := ethclient.Dial(url)
if err != nil {
return nil, err
}
return &ethClient{ec}, nil
}

func (c *ethClient) LatestBlockTime(ctx context.Context) (time.Time, error) {
header, err := c.HeaderByNumber(ctx, nil)
if err != nil {
return time.Time{}, err
}

return time.Unix(int64(header.Time), 0), nil
}
Loading
Loading