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 }