github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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  	"github.com/onflow/crypto"
    21  	restclient "github.com/onflow/flow/openapi/go-client-generated"
    22  
    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  		BlockTracker:         nil,
   161  	})
   162  	suite.Require().NoError(err)
   163  
   164  	stateStreamConfig := statestreambackend.Config{}
   165  	rpcEngBuilder, err := rpc.NewBuilder(
   166  		suite.log,
   167  		suite.state,
   168  		config,
   169  		suite.chainID,
   170  		suite.metrics,
   171  		false,
   172  		suite.me,
   173  		bnd,
   174  		bnd,
   175  		suite.secureGrpcServer,
   176  		suite.unsecureGrpcServer,
   177  		nil,
   178  		stateStreamConfig,
   179  	)
   180  	assert.NoError(suite.T(), err)
   181  	suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build()
   182  	assert.NoError(suite.T(), err)
   183  
   184  	err = fmt.Errorf("inconsistent node's state")
   185  	signCtxErr := irrecoverable.NewExceptionf("failed to lookup sealed header: %w", err)
   186  	ctx := irrecoverable.NewMockSignalerContextExpectError(suite.T(), context.Background(), signCtxErr)
   187  
   188  	suite.rpcEng.Start(ctx)
   189  
   190  	suite.secureGrpcServer.Start(ctx)
   191  	suite.unsecureGrpcServer.Start(ctx)
   192  
   193  	// wait for the servers to startup
   194  	unittest.AssertClosesBefore(suite.T(), suite.secureGrpcServer.Ready(), 2*time.Second)
   195  	unittest.AssertClosesBefore(suite.T(), suite.unsecureGrpcServer.Ready(), 2*time.Second)
   196  
   197  	// wait for the engine to startup
   198  	unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Ready(), 2*time.Second)
   199  }
   200  
   201  func TestIrrecoverableState(t *testing.T) {
   202  	suite.Run(t, new(IrrecoverableStateTestSuite))
   203  }
   204  
   205  // TestGRPCInconsistentNodeState tests the behavior when gRPC encounters an inconsistent node state.
   206  func (suite *IrrecoverableStateTestSuite) TestGRPCInconsistentNodeState() {
   207  	err := fmt.Errorf("inconsistent node's state")
   208  	suite.snapshot.On("Head").Return(nil, err)
   209  
   210  	conn, err := grpc.Dial(
   211  		suite.unsecureGrpcServer.GRPCAddress().String(),
   212  		grpc.WithTransportCredentials(insecure.NewCredentials()))
   213  	assert.NoError(suite.T(), err)
   214  	defer io.Closer(conn).Close()
   215  
   216  	client := accessproto.NewAccessAPIClient(conn)
   217  
   218  	req := &accessproto.GetAccountAtLatestBlockRequest{
   219  		Address: unittest.AddressFixture().Bytes(),
   220  	}
   221  
   222  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   223  	defer cancel()
   224  
   225  	actual, err := client.GetAccountAtLatestBlock(ctx, req)
   226  	suite.Require().Error(err)
   227  	suite.Require().Nil(actual)
   228  }
   229  
   230  // TestRestInconsistentNodeState tests the behavior when the REST API encounters an inconsistent node state.
   231  func (suite *IrrecoverableStateTestSuite) TestRestInconsistentNodeState() {
   232  	collections := unittest.CollectionListFixture(1)
   233  	blockHeader := unittest.BlockWithGuaranteesFixture(
   234  		unittest.CollectionGuaranteesWithCollectionIDFixture(collections),
   235  	)
   236  	suite.blocks.On("ByID", blockHeader.ID()).Return(blockHeader, nil)
   237  
   238  	err := fmt.Errorf("inconsistent node's state")
   239  	suite.snapshot.On("Head").Return(nil, err)
   240  
   241  	config := restclient.NewConfiguration()
   242  	config.BasePath = fmt.Sprintf("http://%s/v1", suite.rpcEng.RestApiAddress().String())
   243  	client := restclient.NewAPIClient(config)
   244  
   245  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   246  	defer cancel()
   247  
   248  	actual, _, err := client.BlocksApi.BlocksIdGet(ctx, []string{blockHeader.ID().String()}, optionsForBlocksIdGetOpts())
   249  	suite.Require().Error(err)
   250  	suite.Require().Nil(actual)
   251  }
   252  
   253  // optionsForBlocksIdGetOpts returns options for the BlocksApi.BlocksIdGet function.
   254  func optionsForBlocksIdGetOpts() *restclient.BlocksApiBlocksIdGetOpts {
   255  	return &restclient.BlocksApiBlocksIdGetOpts{
   256  		Expand:  optional.NewInterface([]string{routes.ExpandableFieldPayload}),
   257  		Select_: optional.NewInterface([]string{"header.id"}),
   258  	}
   259  }