github.com/ethersphere/bee/v2@v2.2.0/pkg/node/bootstrap.go (about) 1 // Copyright 2022 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/ecdsa" 10 "encoding/hex" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "io" 15 "math/big" 16 "time" 17 18 "github.com/ethereum/go-ethereum/common" 19 "github.com/ethersphere/bee/v2/pkg/accounting" 20 "github.com/ethersphere/bee/v2/pkg/addressbook" 21 "github.com/ethersphere/bee/v2/pkg/crypto" 22 "github.com/ethersphere/bee/v2/pkg/feeds" 23 "github.com/ethersphere/bee/v2/pkg/feeds/factory" 24 "github.com/ethersphere/bee/v2/pkg/file" 25 "github.com/ethersphere/bee/v2/pkg/file/joiner" 26 "github.com/ethersphere/bee/v2/pkg/file/loadsave" 27 "github.com/ethersphere/bee/v2/pkg/hive" 28 "github.com/ethersphere/bee/v2/pkg/log" 29 "github.com/ethersphere/bee/v2/pkg/manifest" 30 "github.com/ethersphere/bee/v2/pkg/p2p/libp2p" 31 "github.com/ethersphere/bee/v2/pkg/postage" 32 "github.com/ethersphere/bee/v2/pkg/pricer" 33 "github.com/ethersphere/bee/v2/pkg/pricing" 34 "github.com/ethersphere/bee/v2/pkg/retrieval" 35 "github.com/ethersphere/bee/v2/pkg/settlement/pseudosettle" 36 "github.com/ethersphere/bee/v2/pkg/spinlock" 37 "github.com/ethersphere/bee/v2/pkg/storage" 38 storer "github.com/ethersphere/bee/v2/pkg/storer" 39 "github.com/ethersphere/bee/v2/pkg/swarm" 40 "github.com/ethersphere/bee/v2/pkg/topology" 41 "github.com/ethersphere/bee/v2/pkg/topology/kademlia" 42 "github.com/ethersphere/bee/v2/pkg/topology/lightnode" 43 "github.com/ethersphere/bee/v2/pkg/tracing" 44 "github.com/hashicorp/go-multierror" 45 ma "github.com/multiformats/go-multiaddr" 46 ) 47 48 var ( 49 // zeroed out while waiting to be replacement for the new snapshot feed address 50 // must be different to avoid stale reads on the old contract 51 snapshotFeed = swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000") 52 errDataMismatch = errors.New("data length mismatch") 53 ) 54 55 const ( 56 getSnapshotRetries = 3 57 retryWait = time.Second * 5 58 timeout = time.Minute * 2 59 ) 60 61 func bootstrapNode( 62 ctx context.Context, 63 addr string, 64 swarmAddress swarm.Address, 65 nonce []byte, 66 addressbook addressbook.Interface, 67 bootnodes []ma.Multiaddr, 68 lightNodes *lightnode.Container, 69 stateStore storage.StateStorer, 70 signer crypto.Signer, 71 networkID uint64, 72 logger log.Logger, 73 libp2pPrivateKey *ecdsa.PrivateKey, 74 o *Options, 75 ) (snapshot *postage.ChainSnapshot, retErr error) { 76 77 tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{ 78 Enabled: o.TracingEnabled, 79 Endpoint: o.TracingEndpoint, 80 ServiceName: o.TracingServiceName, 81 }) 82 if err != nil { 83 return nil, fmt.Errorf("tracer: %w", err) 84 } 85 86 p2pCtx, p2pCancel := context.WithCancel(ctx) 87 88 b := &Bee{ 89 ctxCancel: p2pCancel, 90 tracerCloser: tracerCloser, 91 } 92 93 defer func() { 94 retErr = multierror.Append(new(multierror.Error), retErr, b.Shutdown()).ErrorOrNil() 95 }() 96 97 p2ps, err := libp2p.New(p2pCtx, signer, networkID, swarmAddress, addr, addressbook, stateStore, lightNodes, logger, tracer, libp2p.Options{ 98 PrivateKey: libp2pPrivateKey, 99 NATAddr: o.NATAddr, 100 EnableWS: o.EnableWS, 101 WelcomeMessage: o.WelcomeMessage, 102 FullNode: false, 103 Nonce: nonce, 104 }) 105 if err != nil { 106 return nil, fmt.Errorf("p2p service: %w", err) 107 } 108 b.p2pService = p2ps 109 b.p2pHalter = p2ps 110 111 hive := hive.New(p2ps, addressbook, networkID, o.BootnodeMode, o.AllowPrivateCIDRs, logger) 112 113 if err = p2ps.AddProtocol(hive.Protocol()); err != nil { 114 return nil, fmt.Errorf("hive service: %w", err) 115 } 116 b.hiveCloser = hive 117 118 kad, err := kademlia.New(swarmAddress, addressbook, hive, p2ps, logger, 119 kademlia.Options{Bootnodes: bootnodes, BootnodeMode: o.BootnodeMode, StaticNodes: o.StaticNodes, DataDir: o.DataDir}) 120 if err != nil { 121 return nil, fmt.Errorf("unable to create kademlia: %w", err) 122 } 123 b.topologyCloser = kad 124 b.topologyHalter = kad 125 hive.SetAddPeersHandler(kad.AddPeers) 126 p2ps.SetPickyNotifier(kad) 127 128 paymentThreshold, _ := new(big.Int).SetString(o.PaymentThreshold, 10) 129 lightPaymentThreshold := new(big.Int).Div(paymentThreshold, big.NewInt(lightFactor)) 130 131 pricer := pricer.NewFixedPricer(swarmAddress, basePrice) 132 133 pricing := pricing.New(p2ps, logger, paymentThreshold, lightPaymentThreshold, big.NewInt(minPaymentThreshold)) 134 if err = p2ps.AddProtocol(pricing.Protocol()); err != nil { 135 return nil, fmt.Errorf("pricing service: %w", err) 136 } 137 138 acc, err := accounting.NewAccounting( 139 paymentThreshold, 140 o.PaymentTolerance, 141 o.PaymentEarly, 142 logger, 143 stateStore, 144 pricing, 145 big.NewInt(refreshRate), 146 lightFactor, 147 p2ps, 148 ) 149 if err != nil { 150 return nil, fmt.Errorf("accounting: %w", err) 151 } 152 b.accountingCloser = acc 153 154 // bootstrapper mode uses the light node refresh rate 155 enforcedRefreshRate := big.NewInt(lightRefreshRate) 156 157 pseudosettleService := pseudosettle.New(p2ps, logger, stateStore, acc, enforcedRefreshRate, enforcedRefreshRate, p2ps) 158 if err = p2ps.AddProtocol(pseudosettleService.Protocol()); err != nil { 159 return nil, fmt.Errorf("pseudosettle service: %w", err) 160 } 161 162 acc.SetRefreshFunc(pseudosettleService.Pay) 163 164 pricing.SetPaymentThresholdObserver(acc) 165 166 localStore, err := storer.New(ctx, "", &storer.Options{ 167 CacheCapacity: 1_000_000, 168 }) 169 if err != nil { 170 return nil, fmt.Errorf("local store creation: %w", err) 171 } 172 b.localstoreCloser = localStore 173 174 radiusF := func() (uint8, error) { return swarm.MaxBins, nil } 175 176 retrieve := retrieval.New(swarmAddress, radiusF, localStore, p2ps, kad, logger, acc, pricer, tracer, o.RetrievalCaching) 177 if err = p2ps.AddProtocol(retrieve.Protocol()); err != nil { 178 return nil, fmt.Errorf("retrieval service: %w", err) 179 } 180 b.retrievalCloser = retrieve 181 182 localStore.SetRetrievalService(retrieve) 183 184 if err := kad.Start(p2pCtx); err != nil { 185 return nil, err 186 } 187 188 if err := p2ps.Ready(); err != nil { 189 return nil, err 190 } 191 192 if err := waitPeers(kad); err != nil { 193 return nil, errors.New("timed out waiting for kademlia peers") 194 } 195 196 logger.Info("bootstrap: trying to fetch stamps snapshot") 197 198 var ( 199 snapshotReference swarm.Address 200 reader file.Joiner 201 l int64 202 eventsJSON []byte 203 ) 204 205 for i := 0; i < getSnapshotRetries; i++ { 206 if err != nil { 207 time.Sleep(retryWait) 208 } 209 210 ctx, cancel := context.WithTimeout(ctx, timeout) 211 defer cancel() 212 213 snapshotReference, err = getLatestSnapshot(ctx, localStore.Download(true), snapshotFeed) 214 if err != nil { 215 logger.Warning("bootstrap: fetching snapshot failed", "error", err) 216 continue 217 } 218 break 219 } 220 if err != nil { 221 return nil, err 222 } 223 224 for i := 0; i < getSnapshotRetries; i++ { 225 if err != nil { 226 time.Sleep(retryWait) 227 } 228 229 ctx, cancel := context.WithTimeout(ctx, timeout) 230 defer cancel() 231 232 reader, l, err = joiner.New(ctx, localStore.Download(true), localStore.Cache(), snapshotReference) 233 if err != nil { 234 logger.Warning("bootstrap: file joiner failed", "error", err) 235 continue 236 } 237 238 eventsJSON, err = io.ReadAll(reader) 239 if err != nil { 240 logger.Warning("bootstrap: reading failed", "error", err) 241 continue 242 } 243 244 if len(eventsJSON) != int(l) { 245 err = errDataMismatch 246 logger.Warning("bootstrap: count mismatch", "error", err) 247 continue 248 } 249 break 250 } 251 if err != nil { 252 return nil, err 253 } 254 255 events := postage.ChainSnapshot{} 256 err = json.Unmarshal(eventsJSON, &events) 257 if err != nil { 258 return nil, err 259 } 260 261 return &events, nil 262 } 263 264 // wait till some peers are connected. returns true if all is ok 265 func waitPeers(kad *kademlia.Kad) error { 266 const minPeersCount = 25 267 return spinlock.WaitWithInterval(time.Minute, time.Second, func() bool { 268 count := 0 269 _ = kad.EachConnectedPeer(func(_ swarm.Address, _ uint8) (bool, bool, error) { 270 count++ 271 return false, false, nil 272 }, topology.Select{}) 273 return count >= minPeersCount 274 }) 275 } 276 277 func getLatestSnapshot( 278 ctx context.Context, 279 st storage.Getter, 280 address swarm.Address, 281 ) (swarm.Address, error) { 282 ls := loadsave.NewReadonly(st) 283 feedFactory := factory.New(st) 284 285 m, err := manifest.NewDefaultManifestReference( 286 address, 287 ls, 288 ) 289 if err != nil { 290 return swarm.ZeroAddress, fmt.Errorf("not a manifest: %w", err) 291 } 292 293 e, err := m.Lookup(ctx, "/") 294 if err != nil { 295 return swarm.ZeroAddress, fmt.Errorf("node lookup: %w", err) 296 } 297 298 var ( 299 owner, topic []byte 300 t = new(feeds.Type) 301 ) 302 meta := e.Metadata() 303 if e := meta["swarm-feed-owner"]; e != "" { 304 owner, err = hex.DecodeString(e) 305 if err != nil { 306 return swarm.ZeroAddress, err 307 } 308 } 309 if e := meta["swarm-feed-topic"]; e != "" { 310 topic, err = hex.DecodeString(e) 311 if err != nil { 312 return swarm.ZeroAddress, err 313 } 314 } 315 if e := meta["swarm-feed-type"]; e != "" { 316 err := t.FromString(e) 317 if err != nil { 318 return swarm.ZeroAddress, err 319 } 320 } 321 if len(owner) == 0 || len(topic) == 0 { 322 return swarm.ZeroAddress, fmt.Errorf("node lookup: %s", "feed metadata absent") 323 } 324 f := feeds.New(topic, common.BytesToAddress(owner)) 325 326 l, err := feedFactory.NewLookup(*t, f) 327 if err != nil { 328 return swarm.ZeroAddress, fmt.Errorf("feed lookup failed: %w", err) 329 } 330 331 u, _, _, err := l.At(ctx, time.Now().Unix(), 0) 332 if err != nil { 333 return swarm.ZeroAddress, err 334 } 335 336 _, ref, err := feeds.FromChunk(u) 337 if err != nil { 338 return swarm.ZeroAddress, err 339 } 340 341 return swarm.NewAddress(ref), nil 342 } 343 344 func batchStoreExists(s storage.StateStorer) (bool, error) { 345 346 hasOne := false 347 err := s.Iterate("batchstore_", func(key, value []byte) (stop bool, err error) { 348 hasOne = true 349 return true, err 350 }) 351 352 return hasOne, err 353 }