243 lines
8.0 KiB
Go
243 lines
8.0 KiB
Go
package chain
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
healthManager "github.com/status-im/status-go/healthmanager"
|
|
"github.com/status-im/status-go/healthmanager/rpcstatus"
|
|
"github.com/status-im/status-go/rpc/chain/ethclient"
|
|
"github.com/status-im/status-go/rpc/chain/rpclimiter"
|
|
|
|
mockEthclient "github.com/status-im/status-go/rpc/chain/ethclient/mock/client/ethclient"
|
|
)
|
|
|
|
type ClientWithFallbackSuite struct {
|
|
suite.Suite
|
|
client *ClientWithFallback
|
|
mockEthClients []*mockEthclient.MockRPSLimitedEthClientInterface
|
|
providersHealthManager *healthManager.ProvidersHealthManager
|
|
mockCtrl *gomock.Controller
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) SetupTest() {
|
|
s.mockCtrl = gomock.NewController(s.T())
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TearDownTest() {
|
|
s.mockCtrl.Finish()
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) setupClients(numClients int) {
|
|
s.mockEthClients = make([]*mockEthclient.MockRPSLimitedEthClientInterface, 0)
|
|
ethClients := make([]ethclient.RPSLimitedEthClientInterface, 0)
|
|
|
|
for i := 0; i < numClients; i++ {
|
|
ethClient := mockEthclient.NewMockRPSLimitedEthClientInterface(s.mockCtrl)
|
|
ethClient.EXPECT().GetName().AnyTimes().Return("test" + strconv.Itoa(i))
|
|
ethClient.EXPECT().GetLimiter().AnyTimes().Return(nil)
|
|
|
|
s.mockEthClients = append(s.mockEthClients, ethClient)
|
|
ethClients = append(ethClients, ethClient)
|
|
}
|
|
var chainID uint64 = 0
|
|
s.providersHealthManager = healthManager.NewProvidersHealthManager(chainID)
|
|
s.client = NewClient(ethClients, chainID, s.providersHealthManager)
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestSingleClientSuccess() {
|
|
s.setupClients(1)
|
|
ctx := context.Background()
|
|
hash := common.HexToHash("0x1234")
|
|
block := &types.Block{}
|
|
|
|
// GIVEN
|
|
s.mockEthClients[0].EXPECT().BlockByHash(ctx, hash).Return(block, nil).Times(1)
|
|
|
|
// WHEN
|
|
result, err := s.client.BlockByHash(ctx, hash)
|
|
require.NoError(s.T(), err)
|
|
require.Equal(s.T(), block, result)
|
|
|
|
// THEN
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusUp, chainStatus.Status)
|
|
providerStatuses := s.providersHealthManager.GetStatuses()
|
|
require.Len(s.T(), providerStatuses, 1)
|
|
require.Equal(s.T(), providerStatuses["test0"].Status, rpcstatus.StatusUp)
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestSingleClientConnectionError() {
|
|
s.setupClients(1)
|
|
ctx := context.Background()
|
|
hash := common.HexToHash("0x1234")
|
|
|
|
// GIVEN
|
|
s.mockEthClients[0].EXPECT().BlockByHash(ctx, hash).Return(nil, errors.New("connection error")).Times(1)
|
|
|
|
// WHEN
|
|
_, err := s.client.BlockByHash(ctx, hash)
|
|
require.Error(s.T(), err)
|
|
|
|
// THEN
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusDown, chainStatus.Status)
|
|
providerStatuses := s.providersHealthManager.GetStatuses()
|
|
require.Len(s.T(), providerStatuses, 1)
|
|
require.Equal(s.T(), providerStatuses["test0"].Status, rpcstatus.StatusDown)
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestRPSLimitErrorDoesNotMarkChainDown() {
|
|
s.setupClients(1)
|
|
|
|
ctx := context.Background()
|
|
hash := common.HexToHash("0x1234")
|
|
|
|
// WHEN
|
|
s.mockEthClients[0].EXPECT().BlockByHash(ctx, hash).Return(nil, rpclimiter.ErrRequestsOverLimit).Times(1)
|
|
|
|
_, err := s.client.BlockByHash(ctx, hash)
|
|
require.Error(s.T(), err)
|
|
|
|
// THEN
|
|
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusUp, chainStatus.Status)
|
|
providerStatuses := s.providersHealthManager.GetStatuses()
|
|
require.Len(s.T(), providerStatuses, 1)
|
|
require.Equal(s.T(), providerStatuses["test0"].Status, rpcstatus.StatusUp)
|
|
|
|
status := providerStatuses["test0"]
|
|
require.Equal(s.T(), status.Status, rpcstatus.StatusUp, "provider shouldn't be DOWN on RPS limit")
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestContextCanceledDoesNotMarkChainDown() {
|
|
s.setupClients(1)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
hash := common.HexToHash("0x1234")
|
|
|
|
// WHEN
|
|
s.mockEthClients[0].EXPECT().BlockByHash(ctx, hash).Return(nil, context.Canceled).Times(1)
|
|
|
|
_, err := s.client.BlockByHash(ctx, hash)
|
|
require.Error(s.T(), err)
|
|
require.True(s.T(), errors.Is(err, context.Canceled))
|
|
|
|
// THEN
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusUp, chainStatus.Status)
|
|
providerStatuses := s.providersHealthManager.GetStatuses()
|
|
require.Len(s.T(), providerStatuses, 1)
|
|
require.Equal(s.T(), providerStatuses["test0"].Status, rpcstatus.StatusUp)
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestVMErrorDoesNotMarkChainDown() {
|
|
s.setupClients(1)
|
|
ctx := context.Background()
|
|
hash := common.HexToHash("0x1234")
|
|
vmError := vm.ErrOutOfGas
|
|
|
|
// GIVEN
|
|
s.mockEthClients[0].EXPECT().BlockByHash(ctx, hash).Return(nil, vmError).Times(1)
|
|
|
|
// WHEN
|
|
_, err := s.client.BlockByHash(ctx, hash)
|
|
require.Error(s.T(), err)
|
|
require.True(s.T(), errors.Is(err, vm.ErrOutOfGas))
|
|
|
|
// THEN
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusUp, chainStatus.Status)
|
|
providerStatuses := s.providersHealthManager.GetStatuses()
|
|
require.Len(s.T(), providerStatuses, 1)
|
|
require.Equal(s.T(), providerStatuses["test0"].Status, rpcstatus.StatusUp)
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestNoClientsChainDown() {
|
|
s.setupClients(0)
|
|
|
|
ctx := context.Background()
|
|
hash := common.HexToHash("0x1234")
|
|
|
|
// WHEN
|
|
_, err := s.client.BlockByHash(ctx, hash)
|
|
require.Error(s.T(), err)
|
|
|
|
// THEN
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusDown, chainStatus.Status)
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestAllClientsDifferentErrors() {
|
|
s.setupClients(3)
|
|
ctx := context.Background()
|
|
hash := common.HexToHash("0x1234")
|
|
|
|
// GIVEN
|
|
s.mockEthClients[0].EXPECT().BlockByHash(ctx, hash).Return(nil, errors.New("no such host")).Times(1)
|
|
s.mockEthClients[1].EXPECT().BlockByHash(ctx, hash).Return(nil, rpclimiter.ErrRequestsOverLimit).Times(1)
|
|
s.mockEthClients[2].EXPECT().BlockByHash(ctx, hash).Return(nil, vm.ErrOutOfGas).Times(1)
|
|
|
|
// WHEN
|
|
_, err := s.client.BlockByHash(ctx, hash)
|
|
require.Error(s.T(), err)
|
|
|
|
// THEN
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusUp, chainStatus.Status)
|
|
|
|
providerStatuses := s.providersHealthManager.GetStatuses()
|
|
require.Len(s.T(), providerStatuses, 3)
|
|
|
|
require.Equal(s.T(), providerStatuses["test0"].Status, rpcstatus.StatusDown, "provider test0 should be DOWN due to a connection error")
|
|
require.Equal(s.T(), providerStatuses["test1"].Status, rpcstatus.StatusUp, "provider test1 should not be marked DOWN due to RPS limit error")
|
|
require.Equal(s.T(), providerStatuses["test2"].Status, rpcstatus.StatusUp, "provider test2 should not be labelled DOWN due to a VM error")
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestAllClientsNetworkErrors() {
|
|
s.setupClients(3)
|
|
ctx := context.Background()
|
|
hash := common.HexToHash("0x1234")
|
|
|
|
// GIVEN
|
|
s.mockEthClients[0].EXPECT().BlockByHash(ctx, hash).Return(nil, errors.New("no such host")).Times(1)
|
|
s.mockEthClients[1].EXPECT().BlockByHash(ctx, hash).Return(nil, errors.New("no such host")).Times(1)
|
|
s.mockEthClients[2].EXPECT().BlockByHash(ctx, hash).Return(nil, errors.New("no such host")).Times(1)
|
|
|
|
// WHEN
|
|
_, err := s.client.BlockByHash(ctx, hash)
|
|
require.Error(s.T(), err)
|
|
|
|
// THEN
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusDown, chainStatus.Status)
|
|
|
|
providerStatuses := s.providersHealthManager.GetStatuses()
|
|
require.Len(s.T(), providerStatuses, 3)
|
|
require.Equal(s.T(), providerStatuses["test0"].Status, rpcstatus.StatusDown)
|
|
require.Equal(s.T(), providerStatuses["test1"].Status, rpcstatus.StatusDown)
|
|
require.Equal(s.T(), providerStatuses["test2"].Status, rpcstatus.StatusDown)
|
|
}
|
|
|
|
func (s *ClientWithFallbackSuite) TestChainStatusDownWhenInitial() {
|
|
s.setupClients(2)
|
|
|
|
chainStatus := s.providersHealthManager.Status()
|
|
require.Equal(s.T(), rpcstatus.StatusDown, chainStatus.Status)
|
|
}
|
|
|
|
func TestClientWithFallbackSuite(t *testing.T) {
|
|
suite.Run(t, new(ClientWithFallbackSuite))
|
|
}
|