Remove Geth Bindings From Prysm (#11586)

* check in changes

* gaz

* preston's review

* comment

* fix up

* remove test

* gaz

* preston's review

* fix it
This commit is contained in:
Nishant Das
2022-11-17 17:16:19 +08:00
committed by GitHub
parent f02ad8a68b
commit 08d63a0cd0
16 changed files with 373 additions and 204 deletions

View File

@@ -129,7 +129,6 @@ go_test(
"@com_github_ethereum_go_ethereum//core/beacon:go_default_library",
"@com_github_ethereum_go_ethereum//core/types:go_default_library",
"@com_github_ethereum_go_ethereum//rpc:go_default_library",
"@com_github_ethereum_go_ethereum//trie:go_default_library",
"@com_github_holiman_uint256//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",

View File

@@ -6,7 +6,6 @@ import (
"sync"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/types"
@@ -133,14 +132,11 @@ func (c *headerCache) HeaderInfoByHeight(height *big.Int) (bool, *types.HeaderIn
// size limit. This method should be called in sequential header number order if
// the desired behavior is that the blocks with the highest header number should
// be present in the cache.
func (c *headerCache) AddHeader(hdr *gethTypes.Header) error {
func (c *headerCache) AddHeader(hdr *types.HeaderInfo) error {
c.lock.Lock()
defer c.lock.Unlock()
hInfo, err := types.HeaderToHeaderInfo(hdr)
if err != nil {
return err
}
hInfo := hdr.Copy()
if err := c.hashCache.AddIfNotPresent(hInfo); err != nil {
return err

View File

@@ -5,8 +5,8 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/types"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
)
@@ -44,34 +44,30 @@ func TestHeightKeyFn_InvalidObj(t *testing.T) {
func TestBlockCache_byHash(t *testing.T) {
cache := newHeaderCache()
header := &gethTypes.Header{
ParentHash: common.HexToHash("0x12345"),
Number: big.NewInt(55),
header := &types.HeaderInfo{
Number: big.NewInt(55),
}
exists, _, err := cache.HeaderInfoByHash(header.Hash())
exists, _, err := cache.HeaderInfoByHash(header.Hash)
require.NoError(t, err)
assert.Equal(t, false, exists, "Expected block info not to exist in empty cache")
err = cache.AddHeader(header)
require.NoError(t, err)
exists, fetchedInfo, err := cache.HeaderInfoByHash(header.Hash())
exists, fetchedInfo, err := cache.HeaderInfoByHash(header.Hash)
require.NoError(t, err)
assert.Equal(t, true, exists, "Expected headerInfo to exist")
assert.Equal(t, 0, fetchedInfo.Number.Cmp(header.Number), "Expected fetched info number to be equal")
assert.Equal(t, header.Hash(), fetchedInfo.Hash, "Expected hash to be equal")
assert.Equal(t, header.Hash, fetchedInfo.Hash, "Expected hash to be equal")
}
func TestBlockCache_byHeight(t *testing.T) {
cache := newHeaderCache()
header := &gethTypes.Header{
ParentHash: common.HexToHash("0x12345"),
Number: big.NewInt(55),
header := &types.HeaderInfo{
Number: big.NewInt(55),
}
exists, _, err := cache.HeaderInfoByHeight(header.Number)
require.NoError(t, err)
assert.Equal(t, false, exists, "Expected block info not to exist in empty cache")
@@ -84,7 +80,7 @@ func TestBlockCache_byHeight(t *testing.T) {
assert.Equal(t, true, exists, "Expected headerInfo to exist")
assert.Equal(t, 0, fetchedInfo.Number.Cmp(header.Number), "Expected fetched info number to be equal")
assert.Equal(t, header.Hash(), fetchedInfo.Hash, "Expected hash to be equal")
assert.Equal(t, header.Hash, fetchedInfo.Hash, "Expected hash to be equal")
}
@@ -92,8 +88,9 @@ func TestBlockCache_maxSize(t *testing.T) {
cache := newHeaderCache()
for i := int64(0); i < int64(maxCacheSize+10); i++ {
header := &gethTypes.Header{
header := &types.HeaderInfo{
Number: big.NewInt(i),
Hash: common.Hash(bytesutil.ToBytes32(bytesutil.Bytes32(uint64(i)))),
}
err := cache.AddHeader(header)
require.NoError(t, err)

View File

@@ -35,7 +35,7 @@ func (s *Service) BlockExists(ctx context.Context, hash common.Hash) (bool, *big
return true, hdrInfo.Number, nil
}
span.AddAttributes(trace.BoolAttribute("blockCacheHit", false))
header, err := s.eth1DataFetcher.HeaderByHash(ctx, hash)
header, err := s.HeaderByHash(ctx, hash)
if err != nil {
return false, big.NewInt(0), errors.Wrap(err, "could not query block with given hash")
}
@@ -61,33 +61,33 @@ func (s *Service) BlockHashByHeight(ctx context.Context, height *big.Int) (commo
}
span.AddAttributes(trace.BoolAttribute("headerCacheHit", false))
if s.eth1DataFetcher == nil {
err := errors.New("nil eth1DataFetcher")
if s.rpcClient == nil {
err := errors.New("nil rpc client")
tracing.AnnotateError(span, err)
return [32]byte{}, err
}
header, err := s.eth1DataFetcher.HeaderByNumber(ctx, height)
header, err := s.HeaderByNumber(ctx, height)
if err != nil {
return [32]byte{}, errors.Wrap(err, fmt.Sprintf("could not query header with height %d", height.Uint64()))
}
if err := s.headerCache.AddHeader(header); err != nil {
return [32]byte{}, err
}
return header.Hash(), nil
return header.Hash, nil
}
// BlockTimeByHeight fetches an eth1 block timestamp by its height.
func (s *Service) BlockTimeByHeight(ctx context.Context, height *big.Int) (uint64, error) {
ctx, span := trace.StartSpan(ctx, "powchain.BlockTimeByHeight")
defer span.End()
if s.eth1DataFetcher == nil {
err := errors.New("nil eth1DataFetcher")
if s.rpcClient == nil {
err := errors.New("nil rpc client")
tracing.AnnotateError(span, err)
return 0, err
}
header, err := s.eth1DataFetcher.HeaderByNumber(ctx, height)
header, err := s.HeaderByNumber(ctx, height)
if err != nil {
return 0, errors.Wrap(err, fmt.Sprintf("could not query block with height %d", height.Uint64()))
}
@@ -207,20 +207,17 @@ func (s *Service) retrieveHeaderInfo(ctx context.Context, bNum uint64) (*types.H
return nil, err
}
if !exists {
blk, err := s.eth1DataFetcher.HeaderByNumber(ctx, bn)
hdr, err := s.HeaderByNumber(ctx, bn)
if err != nil {
return nil, err
}
if blk == nil {
if hdr == nil {
return nil, errors.Errorf("header with the number %d does not exist", bNum)
}
if err := s.headerCache.AddHeader(blk); err != nil {
return nil, err
}
info, err = types.HeaderToHeaderInfo(blk)
if err != nil {
if err := s.headerCache.AddHeader(hdr); err != nil {
return nil, err
}
info = hdr
}
return info, nil
}

View File

@@ -9,9 +9,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"
dbutil "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing"
mockExecution "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/types"
contracts "github.com/prysmaticlabs/prysm/v3/contracts/deposit"
"github.com/prysmaticlabs/prysm/v3/contracts/deposit/mock"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
@@ -19,7 +19,6 @@ import (
)
func setDefaultMocks(service *Service) *Service {
service.eth1DataFetcher = &goodFetcher{}
service.httpLogger = &goodLogger{}
service.cfg.stateNotifier = &goodNotifier{}
return service
@@ -44,7 +43,6 @@ func TestLatestMainchainInfo_OK(t *testing.T) {
web3Service = setDefaultMocks(web3Service)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.depositContractCaller, err = contracts.NewDepositContractCaller(testAcc.ContractAddr, testAcc.Backend)
require.NoError(t, err)
@@ -57,7 +55,7 @@ func TestLatestMainchainInfo_OK(t *testing.T) {
<-exitRoutine
}()
header, err := web3Service.eth1DataFetcher.HeaderByNumber(web3Service.ctx, nil)
header, err := web3Service.HeaderByNumber(web3Service.ctx, nil)
require.NoError(t, err)
tickerChan := make(chan time.Time)
@@ -67,7 +65,7 @@ func TestLatestMainchainInfo_OK(t *testing.T) {
exitRoutine <- true
assert.Equal(t, web3Service.latestEth1Data.BlockHeight, header.Number.Uint64())
assert.Equal(t, hexutil.Encode(web3Service.latestEth1Data.BlockHash), header.Hash().Hex())
assert.Equal(t, hexutil.Encode(web3Service.latestEth1Data.BlockHash), header.Hash.Hex())
assert.Equal(t, web3Service.latestEth1Data.BlockTime, header.Time)
}
@@ -85,6 +83,7 @@ func TestBlockHashByHeight_ReturnsHash(t *testing.T) {
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
web3Service = setDefaultMocks(web3Service)
web3Service.rpcClient = &mockExecution.RPCClient{}
ctx := context.Background()
header := &gethTypes.Header{
@@ -117,15 +116,17 @@ func TestBlockHashByHeight_ReturnsError_WhenNoEth1Client(t *testing.T) {
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
web3Service = setDefaultMocks(web3Service)
web3Service.eth1DataFetcher = nil
web3Service.rpcClient = nil
ctx := context.Background()
_, err = web3Service.BlockHashByHeight(ctx, big.NewInt(0))
require.ErrorContains(t, "nil eth1DataFetcher", err)
require.ErrorContains(t, "nil rpc client", err)
}
func TestBlockExists_ValidHash(t *testing.T) {
beaconDB := dbutil.SetupDB(t)
testAcc, err := mock.Setup()
require.NoError(t, err, "Unable to set up simulated backend")
server, endpoint, err := mockExecution.SetupRPCServer()
require.NoError(t, err)
t.Cleanup(func() {
@@ -138,16 +139,10 @@ func TestBlockExists_ValidHash(t *testing.T) {
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
web3Service = setDefaultMocks(web3Service)
block := gethTypes.NewBlock(
&gethTypes.Header{
Number: big.NewInt(0),
},
[]*gethTypes.Transaction{},
[]*gethTypes.Header{},
[]*gethTypes.Receipt{},
new(trie.Trie),
)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
testAcc.Backend.Commit()
block, err := testAcc.Backend.BlockByNumber(context.Background(), big.NewInt(0))
assert.NoError(t, err)
exists, height, err := web3Service.BlockExists(context.Background(), block.Hash())
require.NoError(t, err, "Could not get block hash with given height")
@@ -191,17 +186,15 @@ func TestBlockExists_UsesCachedBlockInfo(t *testing.T) {
WithDatabase(beaconDB),
)
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
// nil eth1DataFetcher would panic if cached value not used
web3Service.eth1DataFetcher = nil
header := &gethTypes.Header{
header := &types.HeaderInfo{
Number: big.NewInt(0),
}
err = web3Service.headerCache.AddHeader(header)
require.NoError(t, err)
exists, height, err := web3Service.BlockExists(context.Background(), header.Hash())
exists, height, err := web3Service.BlockExists(context.Background(), header.Hash)
require.NoError(t, err, "Could not get block hash with given height")
require.Equal(t, true, exists)
require.Equal(t, 0, height.Cmp(header.Number))
@@ -222,7 +215,7 @@ func TestService_BlockNumberByTimestamp(t *testing.T) {
)
require.NoError(t, err)
web3Service = setDefaultMocks(web3Service)
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
for i := 0; i < 200; i++ {
testAcc.Backend.Commit()
@@ -254,7 +247,7 @@ func TestService_BlockNumberByTimestampLessTargetTime(t *testing.T) {
)
require.NoError(t, err)
web3Service = setDefaultMocks(web3Service)
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
for i := 0; i < 200; i++ {
testAcc.Backend.Commit()
@@ -292,7 +285,7 @@ func TestService_BlockNumberByTimestampMoreTargetTime(t *testing.T) {
)
require.NoError(t, err)
web3Service = setDefaultMocks(web3Service)
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
for i := 0; i < 200; i++ {
testAcc.Backend.Commit()
@@ -329,9 +322,9 @@ func TestService_BlockTimeByHeight_ReturnsError_WhenNoEth1Client(t *testing.T) {
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
web3Service = setDefaultMocks(web3Service)
web3Service.eth1DataFetcher = nil
web3Service.rpcClient = nil
ctx := context.Background()
_, err = web3Service.BlockTimeByHeight(ctx, big.NewInt(0))
require.ErrorContains(t, "nil eth1DataFetcher", err)
require.ErrorContains(t, "nil rpc client", err)
}

View File

@@ -8,11 +8,13 @@ import (
"strings"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethRPC "github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/types"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/consensus-types/blocks"
@@ -354,6 +356,26 @@ func (s *Service) ExecutionBlocksByHashes(ctx context.Context, hashes []common.H
return execBlks, nil
}
// HeaderByHash returns the relevant header details for the provided block hash.
func (s *Service) HeaderByHash(ctx context.Context, hash common.Hash) (*types.HeaderInfo, error) {
var hdr *types.HeaderInfo
err := s.rpcClient.CallContext(ctx, &hdr, ExecutionBlockByHashMethod, hash, false /* no transactions */)
if err == nil && hdr == nil {
err = ethereum.NotFound
}
return hdr, err
}
// HeaderByNumber returns the relevant header details for the provided block number.
func (s *Service) HeaderByNumber(ctx context.Context, number *big.Int) (*types.HeaderInfo, error) {
var hdr *types.HeaderInfo
err := s.rpcClient.CallContext(ctx, &hdr, ExecutionBlockByNumberMethod, toBlockNumArg(number), false /* no transactions */)
if err == nil && hdr == nil {
err = ethereum.NotFound
}
return hdr, err
}
// ReconstructFullBellatrixBlock takes in a blinded beacon block and reconstructs
// a beacon block with a full execution payload via the engine API.
func (s *Service) ReconstructFullBellatrixBlock(
@@ -610,3 +632,22 @@ func buildEmptyExecutionPayload() *pb.ExecutionPayload {
ExtraData: make([]byte, 0),
}
}
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
pending := big.NewInt(-1)
if number.Cmp(pending) == 0 {
return "pending"
}
finalized := big.NewInt(int64(gethRPC.FinalizedBlockNumber))
if number.Cmp(finalized) == 0 {
return "finalized"
}
safe := big.NewInt(int64(gethRPC.SafeBlockNumber))
if number.Cmp(safe) == 0 {
return "safe"
}
return hexutil.EncodeBig(number)
}

View File

@@ -11,10 +11,12 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
gethRPC "github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
"github.com/pkg/errors"
mocks "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing"
@@ -24,6 +26,7 @@ import (
"github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
pb "github.com/prysmaticlabs/prysm/v3/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/testing/util"
"google.golang.org/protobuf/proto"
@@ -36,6 +39,18 @@ var (
_ = EngineCaller(&mocks.EngineClient{})
)
type RPCClientBad struct {
}
func (RPCClientBad) Close() {}
func (RPCClientBad) BatchCall([]gethRPC.BatchElem) error {
return errors.New("rpc client is not initialized")
}
func (RPCClientBad) CallContext(context.Context, interface{}, string, ...interface{}) error {
return ethereum.NotFound
}
func TestClient_IPC(t *testing.T) {
server := newTestIPCServer(t)
defer server.Stop()
@@ -1215,6 +1230,78 @@ func Test_fullPayloadFromExecutionBlock(t *testing.T) {
}
}
func TestHeaderByHash_NotFound(t *testing.T) {
srv := &Service{}
srv.rpcClient = RPCClientBad{}
_, err := srv.HeaderByHash(context.Background(), common.Hash([32]byte{}))
assert.Equal(t, ethereum.NotFound, err)
}
func TestHeaderByNumber_NotFound(t *testing.T) {
srv := &Service{}
srv.rpcClient = RPCClientBad{}
_, err := srv.HeaderByNumber(context.Background(), big.NewInt(100))
assert.Equal(t, ethereum.NotFound, err)
}
func TestToBlockNumArg(t *testing.T) {
tests := []struct {
name string
number *big.Int
want string
}{
{
name: "genesis",
number: big.NewInt(0),
want: "0x0",
},
{
name: "near genesis block",
number: big.NewInt(300),
want: "0x12c",
},
{
name: "current block",
number: big.NewInt(15838075),
want: "0xf1ab7b",
},
{
name: "far off block",
number: big.NewInt(12032894823020),
want: "0xaf1a06bea6c",
},
{
name: "latest block",
number: nil,
want: "latest",
},
{
name: "pending block",
number: big.NewInt(-1),
want: "pending",
},
{
name: "finalized block",
number: big.NewInt(-3),
want: "finalized",
},
{
name: "safe block",
number: big.NewInt(-4),
want: "safe",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := toBlockNumArg(tt.number); got != tt.want {
t.Errorf("toBlockNumArg() = %v, want %v", got, tt.want)
}
})
}
}
type testEngineService struct{}
func (*testEngineService) NoArgsRets() {}

View File

@@ -17,6 +17,7 @@ import (
statefeed "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers"
coreState "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/types"
statenative "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v3/config/params"
contracts "github.com/prysmaticlabs/prysm/v3/contracts/deposit"
@@ -284,7 +285,7 @@ func (s *Service) processPastLogs(ctx context.Context) error {
currentBlockNum = deploymentBlock
}
// To store all blocks.
headersMap := make(map[uint64]*gethtypes.Header)
headersMap := make(map[uint64]*types.HeaderInfo)
rawLogCount, err := s.depositContractCaller.GetDepositCount(&bind.CallOpts{})
if err != nil {
return err
@@ -341,7 +342,7 @@ func (s *Service) processPastLogs(ctx context.Context) error {
return nil
}
func (s *Service) processBlockInBatch(ctx context.Context, currentBlockNum uint64, latestFollowHeight uint64, batchSize uint64, additiveFactor uint64, logCount uint64, headersMap map[uint64]*gethtypes.Header) (uint64, uint64, error) {
func (s *Service) processBlockInBatch(ctx context.Context, currentBlockNum uint64, latestFollowHeight uint64, batchSize uint64, additiveFactor uint64, logCount uint64, headersMap map[uint64]*types.HeaderInfo) (uint64, uint64, error) {
// Batch request the desired headers and store them in a
// map for quick access.
requestHeaders := func(startBlk uint64, endBlk uint64) error {
@@ -499,11 +500,11 @@ func (s *Service) processChainStartFromBlockNum(ctx context.Context, blkNum *big
return nil
}
func (s *Service) processChainStartFromHeader(ctx context.Context, header *gethtypes.Header) {
s.processChainStartIfReady(ctx, header.Hash(), header.Number, header.Time)
func (s *Service) processChainStartFromHeader(ctx context.Context, header *types.HeaderInfo) {
s.processChainStartIfReady(ctx, header.Hash, header.Number, header.Time)
}
func (s *Service) checkHeaderRange(ctx context.Context, start, end uint64, headersMap map[uint64]*gethtypes.Header,
func (s *Service) checkHeaderRange(ctx context.Context, start, end uint64, headersMap map[uint64]*types.HeaderInfo,
requestHeaders func(uint64, uint64) error) error {
for i := start; i <= end; i++ {
if !s.chainStartData.Chainstarted {

View File

@@ -308,6 +308,7 @@ func TestProcessETH2GenesisLog(t *testing.T) {
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
web3Service = setDefaultMocks(web3Service)
web3Service.depositContractCaller, err = contracts.NewDepositContractCaller(testAcc.ContractAddr, testAcc.Backend)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
require.NoError(t, err)
params.SetupTestConfigCleanup(t)
bConfig := params.MinimalSpecConfig().Copy()
@@ -403,7 +404,6 @@ func TestProcessETH2GenesisLog_CorrectNumOfDeposits(t *testing.T) {
require.NoError(t, err)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
web3Service.httpLogger = testAcc.Backend
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.latestEth1Data.LastRequestedBlock = 0
web3Service.latestEth1Data.BlockHeight = testAcc.Backend.Blockchain().CurrentBlock().NumberU64()
web3Service.latestEth1Data.BlockTime = testAcc.Backend.Blockchain().CurrentBlock().Time()
@@ -501,7 +501,6 @@ func TestProcessETH2GenesisLog_LargePeriodOfNoLogs(t *testing.T) {
require.NoError(t, err)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
web3Service.httpLogger = testAcc.Backend
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.latestEth1Data.LastRequestedBlock = 0
web3Service.latestEth1Data.BlockHeight = testAcc.Backend.Blockchain().CurrentBlock().NumberU64()
web3Service.latestEth1Data.BlockTime = testAcc.Backend.Blockchain().CurrentBlock().Time()
@@ -613,7 +612,6 @@ func newPowchainService(t *testing.T, eth1Backend *mock.TestAccount, beaconDB db
require.NoError(t, err)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: eth1Backend.Backend}
web3Service.eth1DataFetcher = &goodFetcher{backend: eth1Backend.Backend}
web3Service.httpLogger = &goodLogger{backend: eth1Backend.Backend}
params.SetupTestConfigCleanup(t)
bConfig := params.MinimalSpecConfig().Copy()

View File

@@ -26,7 +26,6 @@ func (s *Service) setupExecutionClientConnections(ctx context.Context, currEndpo
fetcher := ethclient.NewClient(client)
s.rpcClient = client
s.httpLogger = fetcher
s.eth1DataFetcher = fetcher
depositContractCaller, err := contracts.NewDepositContractCaller(s.cfg.depositContractAddr, fetcher)
if err != nil {

View File

@@ -16,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethTypes "github.com/ethereum/go-ethereum/core/types"
gethRPC "github.com/ethereum/go-ethereum/rpc"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
@@ -97,14 +96,6 @@ type Chain interface {
POWBlockFetcher
}
// RPCDataFetcher defines a subset of methods conformed to by ETH1.0 RPC clients for
// fetching eth1 data from the clients.
type RPCDataFetcher interface {
Close()
HeaderByNumber(ctx context.Context, number *big.Int) (*gethTypes.Header, error)
HeaderByHash(ctx context.Context, hash common.Hash) (*gethTypes.Header, error)
}
// RPCClient defines the rpc methods required to interact with the eth1 node.
type RPCClient interface {
Close()
@@ -154,7 +145,6 @@ type Service struct {
cancel context.CancelFunc
eth1HeadTicker *time.Ticker
httpLogger bind.ContractFilterer
eth1DataFetcher RPCDataFetcher
rpcClient RPCClient
headerCache *headerCache // cache to store block hash/block height.
latestEth1Data *ethpb.LatestETH1Data
@@ -264,9 +254,6 @@ func (s *Service) Stop() error {
if s.rpcClient != nil {
s.rpcClient.Close()
}
if s.eth1DataFetcher != nil {
s.eth1DataFetcher.Close()
}
return nil
}
@@ -398,36 +385,35 @@ func (s *Service) initDepositCaches(ctx context.Context, ctrs []*ethpb.DepositCo
// processBlockHeader adds a newly observed eth1 block to the block cache and
// updates the latest blockHeight, blockHash, and blockTime properties of the service.
func (s *Service) processBlockHeader(header *gethTypes.Header) {
func (s *Service) processBlockHeader(header *types.HeaderInfo) {
defer safelyHandlePanic()
blockNumberGauge.Set(float64(header.Number.Int64()))
s.latestEth1DataLock.Lock()
s.latestEth1Data.BlockHeight = header.Number.Uint64()
s.latestEth1Data.BlockHash = header.Hash().Bytes()
s.latestEth1Data.BlockHash = header.Hash.Bytes()
s.latestEth1Data.BlockTime = header.Time
s.latestEth1DataLock.Unlock()
log.WithFields(logrus.Fields{
"blockNumber": s.latestEth1Data.BlockHeight,
"blockHash": hexutil.Encode(s.latestEth1Data.BlockHash),
"difficulty": header.Difficulty.String(),
}).Debug("Latest eth1 chain event")
}
// batchRequestHeaders requests the block range specified in the arguments. Instead of requesting
// each block in one call, it batches all requests into a single rpc call.
func (s *Service) batchRequestHeaders(startBlock, endBlock uint64) ([]*gethTypes.Header, error) {
func (s *Service) batchRequestHeaders(startBlock, endBlock uint64) ([]*types.HeaderInfo, error) {
if startBlock > endBlock {
return nil, fmt.Errorf("start block height %d cannot be > end block height %d", startBlock, endBlock)
}
requestRange := (endBlock - startBlock) + 1
elems := make([]gethRPC.BatchElem, 0, requestRange)
headers := make([]*gethTypes.Header, 0, requestRange)
headers := make([]*types.HeaderInfo, 0, requestRange)
errs := make([]error, 0, requestRange)
if requestRange == 0 {
return headers, nil
}
for i := startBlock; i <= endBlock; i++ {
header := &gethTypes.Header{}
header := &types.HeaderInfo{}
err := error(nil)
elems = append(elems, gethRPC.BatchElem{
Method: "eth_getBlockByNumber",
@@ -523,7 +509,7 @@ func (s *Service) initPOWService() {
return
default:
ctx := s.ctx
header, err := s.eth1DataFetcher.HeaderByNumber(ctx, nil)
header, err := s.HeaderByNumber(ctx, nil)
if err != nil {
s.retryExecutionClientConnection(ctx, err)
errorLogger(err, "Unable to retrieve latest execution client header")
@@ -532,7 +518,7 @@ func (s *Service) initPOWService() {
s.latestEth1DataLock.Lock()
s.latestEth1Data.BlockHeight = header.Number.Uint64()
s.latestEth1Data.BlockHash = header.Hash().Bytes()
s.latestEth1Data.BlockHash = header.Hash.Bytes()
s.latestEth1Data.BlockTime = header.Time
s.latestEth1DataLock.Unlock()
@@ -562,7 +548,7 @@ func (s *Service) initPOWService() {
// In the event our provided chainstart data references a non-existent block hash,
// we assume the genesis block to be 0.
if genHash != [32]byte{} {
genHeader, err := s.eth1DataFetcher.HeaderByHash(ctx, genHash)
genHeader, err := s.HeaderByHash(ctx, genHash)
if err != nil {
s.retryExecutionClientConnection(ctx, err)
errorLogger(err, "Unable to retrieve proof-of-stake genesis block data")
@@ -601,7 +587,7 @@ func (s *Service) run(done <-chan struct{}) {
log.Debug("Context closed, exiting goroutine")
return
case <-s.eth1HeadTicker.C:
head, err := s.eth1DataFetcher.HeaderByNumber(s.ctx, nil)
head, err := s.HeaderByNumber(s.ctx, nil)
if err != nil {
s.pollConnectionStatus(s.ctx)
log.WithError(err).Debug("Could not fetch latest eth1 header")

View File

@@ -1,9 +1,7 @@
package execution
import (
"bytes"
"context"
"fmt"
"math/big"
"strings"
"testing"
@@ -20,6 +18,7 @@ import (
"github.com/prysmaticlabs/prysm/v3/beacon-chain/cache/depositcache"
dbutil "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing"
mockExecution "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/types"
doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v3/config/params"
@@ -80,56 +79,6 @@ func (g *goodNotifier) StateFeed() *event.Feed {
return g.MockStateFeed
}
type goodFetcher struct {
backend *backends.SimulatedBackend
blockNumMap map[uint64]*gethTypes.Header
}
func (_ *goodFetcher) Close() {}
func (g *goodFetcher) HeaderByHash(_ context.Context, hash common.Hash) (*gethTypes.Header, error) {
if bytes.Equal(hash.Bytes(), common.BytesToHash([]byte{0}).Bytes()) {
return nil, fmt.Errorf("expected block hash to be nonzero %v", hash)
}
if g.backend == nil {
return &gethTypes.Header{
Number: big.NewInt(0),
}, nil
}
header := g.backend.Blockchain().GetHeaderByHash(hash)
if header == nil {
return nil, errors.New("nil header returned")
}
return header, nil
}
func (g *goodFetcher) HeaderByNumber(_ context.Context, number *big.Int) (*gethTypes.Header, error) {
if g.backend == nil && g.blockNumMap == nil {
return &gethTypes.Header{
Number: big.NewInt(15),
Time: 150,
}, nil
}
if g.blockNumMap != nil {
return g.blockNumMap[number.Uint64()], nil
}
var header *gethTypes.Header
if number == nil {
header = g.backend.Blockchain().CurrentHeader()
} else {
header = g.backend.Blockchain().GetHeaderByNumber(number.Uint64())
}
if header == nil {
return nil, errors.New("nil header returned")
}
return header, nil
}
func (_ *goodFetcher) SyncProgress(_ context.Context) (*ethereum.SyncProgress, error) {
return nil, nil
}
var depositsReqForChainStart = 64
func TestStart_OK(t *testing.T) {
@@ -229,7 +178,6 @@ func TestService_Eth1Synced(t *testing.T) {
web3Service = setDefaultMocks(web3Service)
web3Service.depositContractCaller, err = contracts.NewDepositContractCaller(testAcc.ContractAddr, testAcc.Backend)
require.NoError(t, err)
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
currTime := testAcc.Backend.Blockchain().CurrentHeader().Time
now := time.Now()
@@ -261,7 +209,7 @@ func TestFollowBlock_OK(t *testing.T) {
params.OverrideBeaconConfig(conf)
web3Service = setDefaultMocks(web3Service)
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
baseHeight := testAcc.Backend.Blockchain().CurrentBlock().NumberU64()
// process follow_distance blocks
for i := 0; i < int(params.BeaconConfig().Eth1FollowDistance); i++ {
@@ -330,7 +278,7 @@ func TestHandlePanic_OK(t *testing.T) {
)
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
// nil eth1DataFetcher would panic if cached value not used
web3Service.eth1DataFetcher = nil
web3Service.rpcClient = nil
web3Service.processBlockHeader(nil)
require.LogsContain(t, hook, "Panicked when handling data from ETH 1.0 Chain!")
}
@@ -371,7 +319,6 @@ func TestLogTillGenesis_OK(t *testing.T) {
require.NoError(t, err)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
web3Service.httpLogger = testAcc.Backend
for i := 0; i < 30; i++ {
testAcc.Backend.Commit()
@@ -506,7 +453,6 @@ func TestNewService_EarliestVotingBlock(t *testing.T) {
WithDatabase(beaconDB),
)
require.NoError(t, err, "unable to setup web3 ETH1.0 chain service")
web3Service.eth1DataFetcher = &goodFetcher{backend: testAcc.Backend}
// simulated backend sets eth1 block
// time as 10 seconds
params.SetupTestConfigCleanup(t)
@@ -800,18 +746,23 @@ func TestService_CacheBlockHeaders(t *testing.T) {
func TestService_FollowBlock(t *testing.T) {
followTime := params.BeaconConfig().Eth1FollowDistance * params.BeaconConfig().SecondsPerETH1Block
followTime += 10000
bMap := make(map[uint64]*gethTypes.Header)
bMap := make(map[uint64]*types.HeaderInfo)
for i := uint64(3000); i > 0; i-- {
bMap[i] = &gethTypes.Header{
h := &gethTypes.Header{
Number: big.NewInt(int64(i)),
Time: followTime + (i * 40),
}
bMap[i] = &types.HeaderInfo{
Number: h.Number,
Hash: h.Hash(),
Time: h.Time,
}
}
s := &Service{
cfg: &config{eth1HeaderReqLimit: 1000},
eth1DataFetcher: &goodFetcher{blockNumMap: bMap},
headerCache: newHeaderCache(),
latestEth1Data: &ethpb.LatestETH1Data{BlockTime: (3000 * 40) + followTime, BlockHeight: 3000},
cfg: &config{eth1HeaderReqLimit: 1000},
rpcClient: &mockExecution.RPCClient{BlockNumMap: bMap},
headerCache: newHeaderCache(),
latestEth1Data: &ethpb.LatestETH1Data{BlockTime: (3000 * 40) + followTime, BlockHeight: 3000},
}
h, err := s.followedBlockHeight(context.Background())
assert.NoError(t, err)
@@ -839,7 +790,7 @@ func (s *slowRPCClient) BatchCall(b []rpc.BatchElem) error {
return err
}
h := &gethTypes.Header{Number: num}
*e.Result.(*gethTypes.Header) = *h
*e.Result.(*types.HeaderInfo) = types.HeaderInfo{Number: h.Number, Hash: h.Hash()}
}
return nil
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/async/event"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/types"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
@@ -140,12 +141,92 @@ func (m *Chain) ETH1ConnectionErrors() []error {
// RPCClient defines the mock rpc client.
type RPCClient struct {
Backend *backends.SimulatedBackend
Backend *backends.SimulatedBackend
BlockNumMap map[uint64]*types.HeaderInfo
}
func (*RPCClient) Close() {}
func (*RPCClient) CallContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
func (r *RPCClient) CallContext(ctx context.Context, obj interface{}, methodName string, args ...interface{}) error {
if r.BlockNumMap != nil && methodName == "eth_getBlockByNumber" {
val, ok := args[0].(string)
if !ok {
return errors.Errorf("wrong argument type provided: %T", args[0])
}
num, err := hexutil.DecodeBig(val)
if err != nil {
return err
}
b := r.BlockNumMap[num.Uint64()]
assertedObj, ok := obj.(**types.HeaderInfo)
if !ok {
return errors.Errorf("wrong argument type provided: %T", obj)
}
*assertedObj = b
return nil
}
if r.Backend == nil && methodName == "eth_getBlockByNumber" {
h := &gethTypes.Header{
Number: big.NewInt(15),
Time: 150,
}
assertedObj, ok := obj.(**types.HeaderInfo)
if !ok {
return errors.Errorf("wrong argument type provided: %T", obj)
}
*assertedObj = &types.HeaderInfo{
Hash: h.Hash(),
Number: h.Number,
Time: h.Time,
}
return nil
}
switch methodName {
case "eth_getBlockByNumber":
val, ok := args[0].(string)
if !ok {
return errors.Errorf("wrong argument type provided: %T", args[0])
}
var num *big.Int
var err error
if val != "latest" {
num, err = hexutil.DecodeBig(val)
if err != nil {
return err
}
}
h, err := r.Backend.HeaderByNumber(ctx, num)
if err != nil {
return err
}
assertedObj, ok := obj.(**types.HeaderInfo)
if !ok {
return errors.Errorf("wrong argument type provided: %T", obj)
}
*assertedObj = &types.HeaderInfo{
Hash: h.Hash(),
Number: h.Number,
Time: h.Time,
}
case "eth_getBlockByHash":
val, ok := args[0].(common.Hash)
if !ok {
return errors.Errorf("wrong argument type provided: %T", args[0])
}
h, err := r.Backend.HeaderByHash(ctx, val)
if err != nil {
return err
}
assertedObj, ok := obj.(**types.HeaderInfo)
if !ok {
return errors.Errorf("wrong argument type provided: %T", obj)
}
*assertedObj = &types.HeaderInfo{
Hash: h.Hash(),
Number: h.Number,
Time: h.Time,
}
}
return nil
}
@@ -164,7 +245,7 @@ func (r *RPCClient) BatchCall(b []rpc.BatchElem) error {
if err != nil {
return err
}
*e.Result.(*gethTypes.Header) = *h
*e.Result.(*types.HeaderInfo) = types.HeaderInfo{Number: h.Number, Time: h.Time, Hash: h.Hash()}
}
return nil

View File

@@ -8,7 +8,7 @@ go_library(
deps = [
"//encoding/bytesutil:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//core/types:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
],
)
@@ -17,7 +17,7 @@ go_test(
srcs = ["eth1_types_test.go"],
embed = [":go_default_library"],
deps = [
"//testing/assert:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//core/types:go_default_library",
],
)

View File

@@ -1,33 +1,20 @@
package types
import (
"encoding/json"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
)
// HeaderInfo specifies the block header information in the ETH 1.0 chain.
type HeaderInfo struct {
Number *big.Int
Hash common.Hash
Time uint64
}
// HeaderToHeaderInfo converts an eth1 header to a header metadata type.
func HeaderToHeaderInfo(hdr *gethTypes.Header) (*HeaderInfo, error) {
if hdr.Number == nil {
// A nil number will panic when calling *big.Int.Set(...)
return nil, errors.New("cannot convert block header with nil block number")
}
return &HeaderInfo{
Hash: hdr.Hash(),
Number: new(big.Int).Set(hdr.Number),
Time: hdr.Time,
}, nil
Number *big.Int `json:"number"`
Hash common.Hash `json:"hash"`
Time uint64 `json:"timestamp"`
}
// Copy sends out a copy of the current header info.
@@ -38,3 +25,44 @@ func (h *HeaderInfo) Copy() *HeaderInfo {
Time: h.Time,
}
}
// MarshalJSON marshals as JSON.
func (h *HeaderInfo) MarshalJSON() ([]byte, error) {
type HeaderInfoJson struct {
Number *hexutil.Big `json:"number"`
Hash common.Hash `json:"hash"`
Time hexutil.Uint64 `json:"timestamp"`
}
var enc HeaderInfoJson
enc.Number = (*hexutil.Big)(h.Number)
enc.Hash = h.Hash
enc.Time = hexutil.Uint64(h.Time)
return json.Marshal(enc)
}
// UnmarshalJSON unmarshals from JSON.
func (h *HeaderInfo) UnmarshalJSON(data []byte) error {
type HeaderInfoJson struct {
Number *hexutil.Big `json:"number"`
Hash *common.Hash `json:"hash"`
Time *hexutil.Uint64 `json:"timestamp"`
}
var dec HeaderInfoJson
if err := json.Unmarshal(data, &dec); err != nil {
return err
}
if dec.Time == nil {
return errors.New("missing required field 'timestamp'")
}
h.Time = uint64(*dec.Time)
if dec.Number == nil {
return errors.New("missing required field 'number'")
}
h.Number = (*big.Int)(dec.Number)
if dec.Hash == nil {
return errors.New("missing required field 'hash'")
}
h.Hash = *dec.Hash
return nil
}

View File

@@ -1,53 +1,68 @@
package types
import (
"math"
"math/big"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
)
func Test_headerToHeaderInfo(t *testing.T) {
type args struct {
hdr *gethTypes.Header
}
func TestRoundtrip_HeaderInfo(t *testing.T) {
tests := []struct {
name string
args args
want *HeaderInfo
hInfo HeaderInfo
wantErr bool
}{
{
name: "OK",
args: args{hdr: &gethTypes.Header{
Number: big.NewInt(500),
Time: 2345,
}},
want: &HeaderInfo{
Number: big.NewInt(500),
name: "normal header object",
hInfo: HeaderInfo{
Number: big.NewInt(1000),
Hash: common.Hash{239, 10, 13, 71, 156, 192, 23, 93, 73, 154, 255, 209, 163, 204, 129, 12, 179, 183, 65, 70, 205, 200, 57, 12, 17, 211, 209, 4, 104, 133, 73, 86},
Time: 2345,
Time: 1000,
},
wantErr: false,
},
{
name: "nil number",
args: args{hdr: &gethTypes.Header{
Time: 2345,
}},
name: "large header object",
hInfo: HeaderInfo{
Number: big.NewInt(10023982389238920),
Hash: common.Hash{192, 19, 18, 71, 156, 239, 23, 93, 73, 17, 255, 209, 163, 204, 129, 12, 179, 129, 65, 70, 209, 200, 57, 12, 17, 211, 209, 4, 104, 57, 73, 86},
Time: math.MaxUint64,
},
wantErr: false,
},
{
name: "missing number",
hInfo: HeaderInfo{
Hash: common.Hash{192, 19, 18, 71, 156, 239, 23, 93, 73, 17, 255, 209, 163, 204, 129, 12, 179, 129, 65, 70, 209, 200, 57, 12, 17, 211, 209, 4, 104, 57, 73, 86},
Time: math.MaxUint64,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := HeaderToHeaderInfo(tt.args.hdr)
h := &HeaderInfo{
Number: tt.hInfo.Number,
Hash: tt.hInfo.Hash,
Time: tt.hInfo.Time,
}
recv, err := h.MarshalJSON()
assert.NoError(t, err)
newH := &HeaderInfo{}
err = newH.UnmarshalJSON(recv)
if (err != nil) != tt.wantErr {
t.Errorf("headerToHeaderInfo() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("headerToHeaderInfo() got = %v, want %v", got, tt.want)
if tt.wantErr {
return
}
if !reflect.DeepEqual(*newH, tt.hInfo) {
t.Errorf("MarshalJSON() got = %v, want %v", newH, tt.hInfo)
}
})
}