github.com/onflow/flow-go@v0.33.17/engine/access/handle_irrecoverable_state_test.go (about)

     1  package access
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/antihax/optional"
    12  	accessproto "github.com/onflow/flow/protobuf/go/flow/access"
    13  	"github.com/rs/zerolog"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/suite"
    16  	"google.golang.org/grpc"
    17  	"google.golang.org/grpc/credentials"
    18  	"google.golang.org/grpc/credentials/insecure"
    19  
    20  	restclient "github.com/onflow/flow/openapi/go-client-generated"
    21  
    22  	"github.com/onflow/flow-go/crypto"
    23  	accessmock "github.com/onflow/flow-go/engine/access/mock"
    24  	"github.com/onflow/flow-go/engine/access/rest"
    25  	"github.com/onflow/flow-go/engine/access/rest/routes"
    26  	"github.com/onflow/flow-go/engine/access/rpc"
    27  	"github.com/onflow/flow-go/engine/access/rpc/backend"
    28  	statestreambackend "github.com/onflow/flow-go/engine/access/state_stream/backend"
    29  	"github.com/onflow/flow-go/model/flow"
    30  	"github.com/onflow/flow-go/module/grpcserver"
    31  	"github.com/onflow/flow-go/module/irrecoverable"
    32  	"github.com/onflow/flow-go/module/metrics"
    33  	module "github.com/onflow/flow-go/module/mock"
    34  	"github.com/onflow/flow-go/network/mocknetwork"
    35  	protocol "github.com/onflow/flow-go/state/protocol/mock"
    36  	storagemock "github.com/onflow/flow-go/storage/mock"
    37  	"github.com/onflow/flow-go/utils/grpcutils"
    38  	"github.com/onflow/flow-go/utils/unittest"
    39  )
    40  
    41  // IrrecoverableStateTestSuite tests that Access node indicate an inconsistent or corrupted node state
    42  type IrrecoverableStateTestSuite struct {
    43  	suite.Suite
    44  	state      *protocol.State
    45  	snapshot   *protocol.Snapshot
    46  	epochQuery *protocol.EpochQuery
    47  	log        zerolog.Logger
    48  	net        *mocknetwork.EngineRegistry
    49  	request    *module.Requester
    50  	collClient *accessmock.AccessAPIClient
    51  	execClient *accessmock.ExecutionAPIClient
    52  	me         *module.Local
    53  	chainID    flow.ChainID
    54  	metrics    *metrics.NoopCollector
    55  	rpcEng     *rpc.Engine
    56  	publicKey  crypto.PublicKey
    57  
    58  	// storage
    59  	blocks       *storagemock.Blocks
    60  	headers      *storagemock.Headers
    61  	collections  *storagemock.Collections
    62  	transactions *storagemock.Transactions
    63  	receipts     *storagemock.ExecutionReceipts
    64  
    65  	ctx irrecoverable.SignalerContext
    66  
    67  	// grpc servers
    68  	secureGrpcServer   *grpcserver.GrpcServer
    69  	unsecureGrpcServer *grpcserver.GrpcServer
    70  }
    71  
    72  func (suite *IrrecoverableStateTestSuite) SetupTest() {
    73  	suite.log = zerolog.New(os.Stdout)
    74  	suite.net = mocknetwork.NewEngineRegistry(suite.T())
    75  	suite.state = protocol.NewState(suite.T())
    76  	suite.snapshot = protocol.NewSnapshot(suite.T())
    77  
    78  	rootHeader := unittest.BlockHeaderFixture()
    79  	params := protocol.NewParams(suite.T())
    80  	params.On("SporkID").Return(unittest.IdentifierFixture(), nil)
    81  	params.On("ProtocolVersion").Return(uint(unittest.Uint64InRange(10, 30)), nil)
    82  	params.On("SporkRootBlockHeight").Return(rootHeader.Height, nil)
    83  	params.On("SealedRoot").Return(rootHeader, nil)
    84  
    85  	suite.epochQuery = protocol.NewEpochQuery(suite.T())
    86  	suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
    87  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
    88  	suite.state.On("Params").Return(params, nil).Maybe()
    89  	suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe()
    90  	suite.blocks = storagemock.NewBlocks(suite.T())
    91  	suite.headers = storagemock.NewHeaders(suite.T())
    92  	suite.transactions = storagemock.NewTransactions(suite.T())
    93  	suite.collections = storagemock.NewCollections(suite.T())
    94  	suite.receipts = storagemock.NewExecutionReceipts(suite.T())
    95  
    96  	suite.collClient = accessmock.NewAccessAPIClient(suite.T())
    97  	suite.execClient = accessmock.NewExecutionAPIClient(suite.T())
    98  
    99  	suite.request = module.NewRequester(suite.T())
   100  	suite.me = module.NewLocal(suite.T())
   101  
   102  	accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess))
   103  	suite.me.
   104  		On("NodeID").
   105  		Return(accessIdentity.NodeID).Maybe()
   106  
   107  	suite.chainID = flow.Testnet
   108  	suite.metrics = metrics.NewNoopCollector()
   109  
   110  	config := rpc.Config{
   111  		UnsecureGRPCListenAddr: unittest.DefaultAddress,
   112  		SecureGRPCListenAddr:   unittest.DefaultAddress,
   113  		HTTPListenAddr:         unittest.DefaultAddress,
   114  		RestConfig: rest.Config{
   115  			ListenAddress: unittest.DefaultAddress,
   116  		},
   117  	}
   118  
   119  	// generate a server certificate that will be served by the GRPC server
   120  	networkingKey := unittest.NetworkingPrivKeyFixture()
   121  	x509Certificate, err := grpcutils.X509Certificate(networkingKey)
   122  	assert.NoError(suite.T(), err)
   123  	tlsConfig := grpcutils.DefaultServerTLSConfig(x509Certificate)
   124  	// set the transport credentials for the server to use
   125  	config.TransportCredentials = credentials.NewTLS(tlsConfig)
   126  	// save the public key to use later in tests later
   127  	suite.publicKey = networkingKey.PublicKey()
   128  
   129  	suite.secureGrpcServer = grpcserver.NewGrpcServerBuilder(suite.log,
   130  		config.SecureGRPCListenAddr,
   131  		grpcutils.DefaultMaxMsgSize,
   132  		false,
   133  		nil,
   134  		nil,
   135  		grpcserver.WithTransportCredentials(config.TransportCredentials)).Build()
   136  
   137  	suite.unsecureGrpcServer = grpcserver.NewGrpcServerBuilder(suite.log,
   138  		config.UnsecureGRPCListenAddr,
   139  		grpcutils.DefaultMaxMsgSize,
   140  		false,
   141  		nil,
   142  		nil).Build()
   143  
   144  	blockHeader := unittest.BlockHeaderFixture()
   145  	suite.snapshot.On("Head").Return(blockHeader, nil).Once()
   146  
   147  	bnd, err := backend.New(backend.Params{
   148  		State:                suite.state,
   149  		CollectionRPC:        suite.collClient,
   150  		Blocks:               suite.blocks,
   151  		Headers:              suite.headers,
   152  		Collections:          suite.collections,
   153  		Transactions:         suite.transactions,
   154  		ChainID:              suite.chainID,
   155  		AccessMetrics:        suite.metrics,
   156  		MaxHeightRange:       0,
   157  		Log:                  suite.log,
   158  		SnapshotHistoryLimit: 0,
   159  		Communicator:         backend.NewNodeCommunicator(false),
   160  	})
   161  	suite.Require().NoError(err)
   162  
   163  	stateStreamConfig := statestreambackend.Config{}
   164  	rpcEngBuilder, err := rpc.NewBuilder(
   165  		suite.log,
   166  		suite.state,
   167  		config,
   168  		suite.chainID,
   169  		suite.metrics,
   170  		false,
   171  		suite.me,
   172  		bnd,
   173  		bnd,
   174  		suite.secureGrpcServer,
   175  		suite.unsecureGrpcServer,
   176  		nil,
   177  		stateStreamConfig,
   178  	)
   179  	assert.NoError(suite.T(), err)
   180  	suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build()
   181  	assert.NoError(suite.T(), err)
   182  
   183  	err = fmt.Errorf("inconsistent node's state")
   184  	signCtxErr := irrecoverable.NewExceptionf("failed to lookup sealed header: %w", err)
   185  	ctx := irrecoverable.NewMockSignalerContextExpectError(suite.T(), context.Background(), signCtxErr)
   186  
   187  	suite.rpcEng.Start(ctx)
   188  
   189  	suite.secureGrpcServer.Start(ctx)
   190  	suite.unsecureGrpcServer.Start(ctx)
   191  
   192  	// wait for the servers to startup
   193  	unittest.AssertClosesBefore(suite.T(), suite.secureGrpcServer.Ready(), 2*time.Second)
   194  	unittest.AssertClosesBefore(suite.T(), suite.unsecureGrpcServer.Ready(), 2*time.Second)
   195  
   196  	// wait for the engine to startup
   197  	unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Ready(), 2*time.Second)
   198  }
   199  
   200  func TestIrrecoverableState(t *testing.T) {
   201  	suite.Run(t, new(IrrecoverableStateTestSuite))
   202  }
   203  
   204  // TestGRPCInconsistentNodeState tests the behavior when gRPC encounters an inconsistent node state.
   205  func (suite *IrrecoverableStateTestSuite) TestGRPCInconsistentNodeState() {
   206  	err := fmt.Errorf("inconsistent node's state")
   207  	suite.snapshot.On("Head").Return(nil, err)
   208  
   209  	conn, err := grpc.Dial(
   210  		suite.unsecureGrpcServer.GRPCAddress().String(),
   211  		grpc.WithTransportCredentials(insecure.NewCredentials()))
   212  	assert.NoError(suite.T(), err)
   213  	defer io.Closer(conn).Close()
   214  
   215  	client := accessproto.NewAccessAPIClient(conn)
   216  
   217  	req := &accessproto.GetAccountAtLatestBlockRequest{
   218  		Address: unittest.AddressFixture().Bytes(),
   219  	}
   220  
   221  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   222  	defer cancel()
   223  
   224  	actual, err := client.GetAccountAtLatestBlock(ctx, req)
   225  	suite.Require().Error(err)
   226  	suite.Require().Nil(actual)
   227  }
   228  
   229  // TestRestInconsistentNodeState tests the behavior when the REST API encounters an inconsistent node state.
   230  func (suite *IrrecoverableStateTestSuite) TestRestInconsistentNodeState() {
   231  	collections := unittest.CollectionListFixture(1)
   232  	blockHeader := unittest.BlockWithGuaranteesFixture(
   233  		unittest.CollectionGuaranteesWithCollectionIDFixture(collections),
   234  	)
   235  	suite.blocks.On("ByID", blockHeader.ID()).Return(blockHeader, nil)
   236  
   237  	err := fmt.Errorf("inconsistent node's state")
   238  	suite.snapshot.On("Head").Return(nil, err)
   239  
   240  	config := restclient.NewConfiguration()
   241  	config.BasePath = fmt.Sprintf("http://%s/v1", suite.rpcEng.RestApiAddress().String())
   242  	client := restclient.NewAPIClient(config)
   243  
   244  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   245  	defer cancel()
   246  
   247  	actual, _, err := client.BlocksApi.BlocksIdGet(ctx, []string{blockHeader.ID().String()}, optionsForBlocksIdGetOpts())
   248  	suite.Require().Error(err)
   249  	suite.Require().Nil(actual)
   250  }
   251  
   252  // optionsForBlocksIdGetOpts returns options for the BlocksApi.BlocksIdGet function.
   253  func optionsForBlocksIdGetOpts() *restclient.BlocksApiBlocksIdGetOpts {
   254  	return &restclient.BlocksApiBlocksIdGetOpts{
   255  		Expand:  optional.NewInterface([]string{routes.ExpandableFieldPayload}),
   256  		Select_: optional.NewInterface([]string{"header.id"}),
   257  	}
   258  }