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 }