github.com/ethersphere/bee/v2@v2.2.0/pkg/node/devnode.go (about) 1 // Copyright 2021 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package node 6 7 import ( 8 "context" 9 "crypto/rand" 10 "errors" 11 "fmt" 12 "io" 13 stdlog "log" 14 "math/big" 15 "net" 16 "net/http" 17 "time" 18 19 "github.com/ethereum/go-ethereum/common" 20 "github.com/ethersphere/bee/v2/pkg/accesscontrol" 21 mockAccounting "github.com/ethersphere/bee/v2/pkg/accounting/mock" 22 "github.com/ethersphere/bee/v2/pkg/api" 23 "github.com/ethersphere/bee/v2/pkg/bzz" 24 "github.com/ethersphere/bee/v2/pkg/crypto" 25 "github.com/ethersphere/bee/v2/pkg/feeds/factory" 26 "github.com/ethersphere/bee/v2/pkg/log" 27 mockP2P "github.com/ethersphere/bee/v2/pkg/p2p/mock" 28 mockPingPong "github.com/ethersphere/bee/v2/pkg/pingpong/mock" 29 "github.com/ethersphere/bee/v2/pkg/postage" 30 "github.com/ethersphere/bee/v2/pkg/postage/batchstore" 31 mockPost "github.com/ethersphere/bee/v2/pkg/postage/mock" 32 "github.com/ethersphere/bee/v2/pkg/postage/postagecontract" 33 mockPostContract "github.com/ethersphere/bee/v2/pkg/postage/postagecontract/mock" 34 postagetesting "github.com/ethersphere/bee/v2/pkg/postage/testing" 35 "github.com/ethersphere/bee/v2/pkg/pss" 36 "github.com/ethersphere/bee/v2/pkg/pushsync" 37 mockPushsync "github.com/ethersphere/bee/v2/pkg/pushsync/mock" 38 resolverMock "github.com/ethersphere/bee/v2/pkg/resolver/mock" 39 "github.com/ethersphere/bee/v2/pkg/settlement/pseudosettle" 40 "github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook" 41 mockchequebook "github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook/mock" 42 erc20mock "github.com/ethersphere/bee/v2/pkg/settlement/swap/erc20/mock" 43 swapmock "github.com/ethersphere/bee/v2/pkg/settlement/swap/mock" 44 "github.com/ethersphere/bee/v2/pkg/statestore/leveldb" 45 mockSteward "github.com/ethersphere/bee/v2/pkg/steward/mock" 46 "github.com/ethersphere/bee/v2/pkg/storage/inmemstore" 47 "github.com/ethersphere/bee/v2/pkg/storageincentives/staking" 48 stakingContractMock "github.com/ethersphere/bee/v2/pkg/storageincentives/staking/mock" 49 "github.com/ethersphere/bee/v2/pkg/storer" 50 "github.com/ethersphere/bee/v2/pkg/swarm" 51 "github.com/ethersphere/bee/v2/pkg/topology/lightnode" 52 mockTopology "github.com/ethersphere/bee/v2/pkg/topology/mock" 53 "github.com/ethersphere/bee/v2/pkg/tracing" 54 "github.com/ethersphere/bee/v2/pkg/transaction" 55 "github.com/ethersphere/bee/v2/pkg/transaction/backendmock" 56 transactionmock "github.com/ethersphere/bee/v2/pkg/transaction/mock" 57 "github.com/ethersphere/bee/v2/pkg/util/ioutil" 58 "github.com/hashicorp/go-multierror" 59 "github.com/multiformats/go-multiaddr" 60 "golang.org/x/sync/errgroup" 61 ) 62 63 type DevBee struct { 64 tracerCloser io.Closer 65 stateStoreCloser io.Closer 66 localstoreCloser io.Closer 67 apiCloser io.Closer 68 pssCloser io.Closer 69 accesscontrolCloser io.Closer 70 errorLogWriter io.Writer 71 apiServer *http.Server 72 } 73 74 type DevOptions struct { 75 Logger log.Logger 76 APIAddr string 77 CORSAllowedOrigins []string 78 DBOpenFilesLimit uint64 79 ReserveCapacity uint64 80 DBWriteBufferSize uint64 81 DBBlockCacheCapacity uint64 82 DBDisableSeeksCompaction bool 83 } 84 85 // NewDevBee starts the bee instance in 'development' mode 86 // this implies starting an API and a Debug endpoints while mocking all their services. 87 func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { 88 tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{ 89 Enabled: false, 90 }) 91 if err != nil { 92 return nil, fmt.Errorf("tracer: %w", err) 93 } 94 95 sink := ioutil.WriterFunc(func(p []byte) (int, error) { 96 logger.Error(nil, string(p)) 97 return len(p), nil 98 }) 99 100 b = &DevBee{ 101 errorLogWriter: sink, 102 tracerCloser: tracerCloser, 103 } 104 105 stateStore, err := leveldb.NewInMemoryStateStore(logger) 106 if err != nil { 107 return nil, err 108 } 109 b.stateStoreCloser = stateStore 110 111 swarmAddress, err := randomAddress() 112 if err != nil { 113 return nil, err 114 } 115 116 batchStore, err := batchstore.New(stateStore, func(b []byte) error { return nil }, 1000000, logger) 117 if err != nil { 118 return nil, fmt.Errorf("batchstore: %w", err) 119 } 120 121 err = batchStore.PutChainState(&postage.ChainState{ 122 CurrentPrice: big.NewInt(1), 123 TotalAmount: big.NewInt(1), 124 }) 125 if err != nil { 126 return nil, fmt.Errorf("batchstore: %w", err) 127 } 128 129 mockKey, err := crypto.GenerateSecp256k1Key() 130 if err != nil { 131 return nil, err 132 } 133 signer := crypto.NewDefaultSigner(mockKey) 134 135 overlayEthAddress, err := signer.EthereumAddress() 136 if err != nil { 137 return nil, fmt.Errorf("blockchain address: %w", err) 138 } 139 140 var mockTransaction = transactionmock.New(transactionmock.WithPendingTransactionsFunc(func() ([]common.Hash, error) { 141 return []common.Hash{common.HexToHash("abcd")}, nil 142 }), transactionmock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error { 143 return nil 144 }), transactionmock.WithStoredTransactionFunc(func(txHash common.Hash) (*transaction.StoredTransaction, error) { 145 recipient := common.HexToAddress("dfff") 146 return &transaction.StoredTransaction{ 147 To: &recipient, 148 Created: 1, 149 Data: []byte{1, 2, 3, 4}, 150 GasPrice: big.NewInt(12), 151 GasTipBoost: 10, 152 GasFeeCap: big.NewInt(12), 153 GasTipCap: new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(10)+100), big.NewInt(12)), big.NewInt(100)), 154 GasLimit: 5345, 155 Value: big.NewInt(4), 156 Nonce: 3, 157 Description: "test", 158 }, nil 159 }), transactionmock.WithCancelTransactionFunc(func(ctx context.Context, originalTxHash common.Hash) (common.Hash, error) { 160 return common.Hash{}, nil 161 }), 162 ) 163 164 chainBackend := backendmock.New( 165 backendmock.WithBlockNumberFunc(func(ctx context.Context) (uint64, error) { 166 return 1, nil 167 }), 168 backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) { 169 return big.NewInt(0), nil 170 }), 171 ) 172 173 // Create api.Probe in healthy state and switch to ready state after all components have been constructed 174 probe := api.NewProbe() 175 probe.SetHealthy(api.ProbeStatusOK) 176 defer func(probe *api.Probe) { 177 if err != nil { 178 probe.SetHealthy(api.ProbeStatusNOK) 179 } else { 180 probe.SetReady(api.ProbeStatusOK) 181 } 182 }(probe) 183 184 localStore, err := storer.New(context.Background(), "", &storer.Options{ 185 Logger: logger, 186 CacheCapacity: 1_000_000, 187 }) 188 if err != nil { 189 return nil, fmt.Errorf("localstore: %w", err) 190 } 191 b.localstoreCloser = localStore 192 193 session := accesscontrol.NewDefaultSession(mockKey) 194 actLogic := accesscontrol.NewLogic(session) 195 accesscontrol := accesscontrol.NewController(actLogic) 196 b.accesscontrolCloser = accesscontrol 197 198 pssService := pss.New(mockKey, logger) 199 b.pssCloser = pssService 200 201 pssService.SetPushSyncer(mockPushsync.New(func(ctx context.Context, chunk swarm.Chunk) (*pushsync.Receipt, error) { 202 pssService.TryUnwrap(chunk) 203 return &pushsync.Receipt{}, nil 204 })) 205 206 post := mockPost.New() 207 postageContract := mockPostContract.New( 208 mockPostContract.WithCreateBatchFunc( 209 func(ctx context.Context, amount *big.Int, depth uint8, immutable bool, label string) (common.Hash, []byte, error) { 210 id := postagetesting.MustNewID() 211 batch := &postage.Batch{ 212 ID: id, 213 Owner: overlayEthAddress.Bytes(), 214 Value: big.NewInt(0).Mul(amount, big.NewInt(int64(1<<depth))), 215 Depth: depth, 216 Immutable: immutable, 217 } 218 219 err := batchStore.Save(batch) 220 if err != nil { 221 return common.Hash{}, nil, err 222 } 223 224 stampIssuer := postage.NewStampIssuer(label, string(overlayEthAddress.Bytes()), id, amount, batch.Depth, 0, 0, immutable) 225 _ = post.Add(stampIssuer) 226 227 return common.Hash{}, id, nil 228 }, 229 ), 230 mockPostContract.WithTopUpBatchFunc( 231 func(ctx context.Context, batchID []byte, topupAmount *big.Int) (common.Hash, error) { 232 return common.Hash{}, postagecontract.ErrNotImplemented 233 }, 234 ), 235 mockPostContract.WithDiluteBatchFunc( 236 func(ctx context.Context, batchID []byte, newDepth uint8) (common.Hash, error) { 237 return common.Hash{}, postagecontract.ErrNotImplemented 238 }, 239 ), 240 ) 241 242 var ( 243 lightNodes = lightnode.NewContainer(swarm.NewAddress(nil)) 244 pingPong = mockPingPong.New(pong) 245 p2ps = mockP2P.New( 246 mockP2P.WithConnectFunc(func(ctx context.Context, addr multiaddr.Multiaddr) (address *bzz.Address, err error) { 247 return &bzz.Address{}, nil 248 }), mockP2P.WithDisconnectFunc( 249 func(swarm.Address, string) error { 250 return nil 251 }, 252 ), mockP2P.WithAddressesFunc( 253 func() ([]multiaddr.Multiaddr, error) { 254 ma, _ := multiaddr.NewMultiaddr("mock") 255 return []multiaddr.Multiaddr{ma}, nil 256 }, 257 )) 258 acc = mockAccounting.NewAccounting() 259 kad = mockTopology.NewTopologyDriver() 260 pseudoset = pseudosettle.New(nil, logger, stateStore, nil, big.NewInt(10000), big.NewInt(10000), p2ps) 261 mockSwap = swapmock.New(swapmock.WithCashoutStatusFunc( 262 func(ctx context.Context, peer swarm.Address) (*chequebook.CashoutStatus, error) { 263 return &chequebook.CashoutStatus{ 264 Last: &chequebook.LastCashout{}, 265 UncashedAmount: big.NewInt(0), 266 }, nil 267 }, 268 ), swapmock.WithLastSentChequeFunc( 269 func(a swarm.Address) (*chequebook.SignedCheque, error) { 270 return &chequebook.SignedCheque{ 271 Cheque: chequebook.Cheque{ 272 Beneficiary: common.Address{}, 273 Chequebook: common.Address{}, 274 }, 275 }, nil 276 }, 277 ), swapmock.WithLastReceivedChequeFunc( 278 func(a swarm.Address) (*chequebook.SignedCheque, error) { 279 return &chequebook.SignedCheque{ 280 Cheque: chequebook.Cheque{ 281 Beneficiary: common.Address{}, 282 Chequebook: common.Address{}, 283 }, 284 }, nil 285 }, 286 )) 287 mockChequebook = mockchequebook.NewChequebook(mockchequebook.WithChequebookBalanceFunc( 288 func(context.Context) (ret *big.Int, err error) { 289 return big.NewInt(0), nil 290 }, 291 ), mockchequebook.WithChequebookAvailableBalanceFunc( 292 func(context.Context) (ret *big.Int, err error) { 293 return big.NewInt(0), nil 294 }, 295 ), mockchequebook.WithChequebookWithdrawFunc( 296 func(ctx context.Context, amount *big.Int) (hash common.Hash, err error) { 297 return common.Hash{}, nil 298 }, 299 ), mockchequebook.WithChequebookDepositFunc( 300 func(ctx context.Context, amount *big.Int) (hash common.Hash, err error) { 301 return common.Hash{}, nil 302 }, 303 )) 304 ) 305 306 var ( 307 // syncStatusFn mocks sync status because complete sync is required in order to curl certain apis e.g. /stamps. 308 // this allows accessing those apis by passing true to isDone in devNode. 309 syncStatusFn = func() (isDone bool, err error) { 310 return true, nil 311 } 312 ) 313 314 mockFeeds := factory.New(localStore.Download(true)) 315 mockResolver := resolverMock.NewResolver() 316 mockSteward := new(mockSteward.Steward) 317 318 mockStaking := stakingContractMock.New( 319 stakingContractMock.WithDepositStake(func(ctx context.Context, stakedAmount *big.Int) (common.Hash, error) { 320 return common.Hash{}, staking.ErrNotImplemented 321 }), 322 stakingContractMock.WithGetStake(func(ctx context.Context) (*big.Int, error) { 323 return nil, staking.ErrNotImplemented 324 }), 325 stakingContractMock.WithWithdrawStake(func(ctx context.Context) (common.Hash, error) { 326 return common.Hash{}, staking.ErrNotImplemented 327 }), 328 stakingContractMock.WithIsFrozen(func(ctx context.Context, block uint64) (bool, error) { 329 return false, staking.ErrNotImplemented 330 }), 331 ) 332 333 debugOpts := api.ExtraOptions{ 334 Pingpong: pingPong, 335 TopologyDriver: kad, 336 LightNodes: lightNodes, 337 Accounting: acc, 338 Pseudosettle: pseudoset, 339 Swap: mockSwap, 340 Chequebook: mockChequebook, 341 BlockTime: time.Second * 2, 342 Storer: localStore, 343 Resolver: mockResolver, 344 Pss: pssService, 345 FeedFactory: mockFeeds, 346 Post: post, 347 AccessControl: accesscontrol, 348 PostageContract: postageContract, 349 Staking: mockStaking, 350 Steward: mockSteward, 351 SyncStatus: syncStatusFn, 352 } 353 354 var erc20 = erc20mock.New( 355 erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) { 356 return big.NewInt(0), nil 357 }), 358 erc20mock.WithTransferFunc(func(ctx context.Context, address common.Address, value *big.Int) (common.Hash, error) { 359 return common.Hash{}, nil 360 }), 361 ) 362 363 apiService := api.New(mockKey.PublicKey, mockKey.PublicKey, overlayEthAddress, nil, logger, mockTransaction, batchStore, api.DevMode, true, true, chainBackend, o.CORSAllowedOrigins, inmemstore.New()) 364 365 apiService.Configure(signer, tracer, api.Options{ 366 CORSAllowedOrigins: o.CORSAllowedOrigins, 367 WsPingPeriod: 60 * time.Second, 368 }, debugOpts, 1, erc20) 369 apiService.MountTechnicalDebug() 370 apiService.MountDebug() 371 apiService.MountAPI() 372 373 apiService.SetProbe(probe) 374 apiService.SetP2P(p2ps) 375 apiService.SetSwarmAddress(&swarmAddress) 376 377 apiListener, err := net.Listen("tcp", o.APIAddr) 378 if err != nil { 379 return nil, fmt.Errorf("api listener: %w", err) 380 } 381 382 apiServer := &http.Server{ 383 IdleTimeout: 30 * time.Second, 384 ReadHeaderTimeout: 3 * time.Second, 385 Handler: apiService, 386 ErrorLog: stdlog.New(b.errorLogWriter, "", 0), 387 } 388 go func() { 389 logger.Info("starting api server", "address", apiListener.Addr()) 390 391 if err := apiServer.Serve(apiListener); err != nil && !errors.Is(err, http.ErrServerClosed) { 392 logger.Debug("api server failed to start", "error", err) 393 logger.Error(nil, "api server failed to start") 394 } 395 }() 396 397 b.apiServer = apiServer 398 b.apiCloser = apiService 399 400 return b, nil 401 } 402 403 func (b *DevBee) Shutdown() error { 404 var mErr error 405 406 tryClose := func(c io.Closer, errMsg string) { 407 if c == nil { 408 return 409 } 410 if err := c.Close(); err != nil { 411 mErr = multierror.Append(mErr, fmt.Errorf("%s: %w", errMsg, err)) 412 } 413 } 414 415 tryClose(b.apiCloser, "api") 416 417 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 418 defer cancel() 419 420 var eg errgroup.Group 421 if b.apiServer != nil { 422 eg.Go(func() error { 423 if err := b.apiServer.Shutdown(ctx); err != nil { 424 return fmt.Errorf("api server: %w", err) 425 } 426 return nil 427 }) 428 } 429 if err := eg.Wait(); err != nil { 430 mErr = multierror.Append(mErr, err) 431 } 432 433 tryClose(b.pssCloser, "pss") 434 tryClose(b.accesscontrolCloser, "accesscontrol") 435 tryClose(b.tracerCloser, "tracer") 436 tryClose(b.stateStoreCloser, "statestore") 437 tryClose(b.localstoreCloser, ioutil.DataPathLocalstore) 438 439 return mErr 440 } 441 442 func pong(_ context.Context, _ swarm.Address, _ ...string) (rtt time.Duration, err error) { 443 return time.Millisecond, nil 444 } 445 446 func randomAddress() (swarm.Address, error) { 447 448 b := make([]byte, 32) 449 450 _, err := rand.Read(b) 451 if err != nil { 452 return swarm.ZeroAddress, err 453 } 454 455 return swarm.NewAddress(b), nil 456 457 }