diff --git a/.gitmodules b/.gitmodules index 5bc6224029f..10c58256206 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,9 @@ [submodule "libmdbx"] path = libmdbx url = https://github.com/erthink/libmdbx +[submodule "turbo/snapshotsync/snapshothashes/erigon-snapshots"] + path = turbo/snapshotsync/snapshothashes/erigon-snapshots + url = https://github.com/ledgerwatch/erigon-snapshot.git +[submodule "cmd/downloader/trackers/trackerslist"] + path = cmd/downloader/trackers/trackerslist + url = https://github.com/ngosang/trackerslist.git diff --git a/cmd/downloader/downloader/downloader.go b/cmd/downloader/downloader/downloader.go new file mode 100644 index 00000000000..604db33e48f --- /dev/null +++ b/cmd/downloader/downloader/downloader.go @@ -0,0 +1,291 @@ +package downloader + +import ( + "context" + "fmt" + "path/filepath" + "sync" + "time" + + lg "github.com/anacrolix/log" + "github.com/anacrolix/torrent" + "github.com/anacrolix/torrent/metainfo" + "github.com/anacrolix/torrent/storage" + "github.com/dustin/go-humanize" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" + "github.com/ledgerwatch/log/v3" +) + +type Client struct { + Cli *torrent.Client + pieceCompletionStore storage.PieceCompletion +} + +func New(snapshotsDir string, seeding bool, peerID string) (*Client, error) { + torrentConfig := DefaultTorrentConfig() + torrentConfig.Seed = seeding + torrentConfig.DataDir = snapshotsDir + torrentConfig.UpnpID = torrentConfig.UpnpID + "leecher" + torrentConfig.PeerID = peerID + + progressStore, err := storage.NewBoltPieceCompletion(snapshotsDir) + if err != nil { + panic(err) + } + torrentConfig.DefaultStorage = storage.NewMMapWithCompletion(snapshotsDir, progressStore) + + torrentClient, err := torrent.NewClient(torrentConfig) + if err != nil { + log.Error("Fail to start torrnet client", "err", err) + return nil, fmt.Errorf("fail to start: %w", err) + } + + log.Info(fmt.Sprintf("Seeding: %t, my peerID: %x", seeding, torrentClient.PeerID())) + + return &Client{ + Cli: torrentClient, + pieceCompletionStore: progressStore, + }, nil +} + +func DefaultTorrentConfig() *torrent.ClientConfig { + torrentConfig := torrent.NewDefaultClientConfig() + torrentConfig.ListenPort = 0 + // debug + torrentConfig.Debug = false + torrentConfig.Logger = NewAdapterLogger() + torrentConfig.Logger = torrentConfig.Logger.FilterLevel(lg.Debug) + + // enable dht + torrentConfig.NoDHT = true + torrentConfig.DisableTrackers = false + //torrentConfig.DisableWebtorrent = true + //torrentConfig.DisableWebseeds = true + + // Increase default timeouts, because we often run on commodity networks + torrentConfig.MinDialTimeout = 6 * time.Second // default: 3sec + torrentConfig.NominalDialTimeout = 20 * time.Second // default: 20sec + torrentConfig.HandshakesTimeout = 8 * time.Second // default: 4sec + + //torrentConfig.MinPeerExtensions.SetBit(peer_protocol.ExtensionBitFast, true) + return torrentConfig +} + +func (cli *Client) SavePeerID(db kv.Putter) error { + return db.Put(kv.BittorrentInfo, []byte(kv.BittorrentPeerID), cli.PeerID()) +} + +func (cli *Client) Close() { + cli.pieceCompletionStore.Close() + cli.Cli.Close() +} + +func (cli *Client) PeerID() []byte { + peerID := cli.Cli.PeerID() + return peerID[:] +} + +func MainLoop(ctx context.Context, torrentClient *torrent.Client) { + interval := time.Second * 5 + logEvery := time.NewTicker(interval) + defer logEvery.Stop() + for { + var prevBytesReadUsefulData, aggByteRate int64 + select { + case <-ctx.Done(): + return + case <-logEvery.C: + torrents := torrentClient.Torrents() + allComplete := true + gotInfo := 0 + for _, t := range torrents { + select { + case <-t.GotInfo(): // all good + gotInfo++ + default: + t.AllowDataUpload() + t.AllowDataDownload() + } + allComplete = allComplete && t.Complete.Bool() + } + if gotInfo < len(torrents) { + log.Info(fmt.Sprintf("[torrent] Waiting for torrents metadata: %d/%d", gotInfo, len(torrents))) + continue + } + if allComplete { + peers := map[torrent.PeerID]struct{}{} + for _, t := range torrentClient.Torrents() { + for _, peer := range t.PeerConns() { + peers[peer.PeerID] = struct{}{} + } + } + log.Info("[torrent] Seeding", "peers", len(peers), "torrents", len(torrents)) + continue + } + + var aggBytesCompleted, aggLen int64 + //var aggCompletedPieces, aggNumPieces, aggPartialPieces int + peers := map[torrent.PeerID]*torrent.PeerConn{} + + for _, t := range torrents { + stats := t.Stats() + /* + var completedPieces, partialPieces int + psrs := t.PieceStateRuns() + for _, r := range psrs { + if r.Complete { + completedPieces += r.Length + } + if r.Partial { + partialPieces += r.Length + } + } + aggCompletedPieces += completedPieces + aggPartialPieces += partialPieces + aggNumPieces = t.NumPieces() + */ + + byteRate := int64(time.Second) + bytesReadUsefulData := stats.BytesReadUsefulData.Int64() + byteRate *= stats.BytesReadUsefulData.Int64() - prevBytesReadUsefulData + byteRate /= int64(interval) + aggByteRate += byteRate + + prevBytesReadUsefulData = bytesReadUsefulData + aggBytesCompleted += t.BytesCompleted() + aggLen += t.Length() + + for _, peer := range t.PeerConns() { + peers[peer.PeerID] = peer + } + + } + + line := fmt.Sprintf( + "[torrent] Downloading: %d%%, %v/s, peers: %d", + int(100*(float64(aggBytesCompleted)/float64(aggLen))), + //humanize.Bytes(uint64(aggBytesCompleted)), + // humanize.Bytes(uint64(aggLen)), + humanize.Bytes(uint64(aggByteRate)), + len(peers), + ) + log.Info(line) + } + } +} + +func (cli *Client) StopSeeding(hash metainfo.Hash) error { + t, ok := cli.Cli.Torrent(hash) + if !ok { + return nil + } + ch := t.Closed() + t.Drop() + <-ch + return nil +} + +// AddTorrentFiles - adding .torrent files to torrentClient (and checking their hashes), if .torrent file +// added first time - pieces verification process will start (disk IO heavy) - progress +// kept in `piece completion storage` (surviving reboot). Once it done - no disk IO needed again. +// Don't need call torrent.VerifyData manually +func AddTorrentFiles(ctx context.Context, snapshotsDir string, torrentClient *torrent.Client, preverifiedHashes snapshothashes.Preverified) error { + if err := ForEachTorrentFile(snapshotsDir, func(torrentFilePath string) error { + mi, err := metainfo.LoadFromFile(torrentFilePath) + if err != nil { + return err + } + mi.AnnounceList = Trackers + + // skip non-preverified files + _, torrentFileName := filepath.Split(torrentFilePath) + segmentFileName := segmentFileNameFromTorrentFileName(torrentFileName) + hashString, ok := preverifiedHashes[segmentFileName] + if !ok { + return nil + } + expect := metainfo.NewHashFromHex(hashString) + if mi.HashInfoBytes() != expect { + return fmt.Errorf("file %s has unexpected hash %x, expected %x", torrentFileName, mi.HashInfoBytes(), expect) + } + + if _, err = torrentClient.AddTorrent(mi); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + waitForChecksumVerify(ctx, torrentClient) + return nil +} + +// ResolveAbsentTorrents - add hard-coded hashes (if client doesn't have) as magnet links and download everything +func ResolveAbsentTorrents(ctx context.Context, torrentClient *torrent.Client, preverifiedHashes []metainfo.Hash, snapshotDir string) error { + mi := &metainfo.MetaInfo{AnnounceList: Trackers} + wg := &sync.WaitGroup{} + for _, infoHash := range preverifiedHashes { + if _, ok := torrentClient.Torrent(infoHash); ok { + continue + } + magnet := mi.Magnet(&infoHash, nil) + t, err := torrentClient.AddMagnet(magnet.String()) + if err != nil { + return err + } + t.AllowDataDownload() + t.AllowDataUpload() + + wg.Add(1) + go func(t *torrent.Torrent, infoHash metainfo.Hash) { + defer wg.Done() + + select { + case <-ctx.Done(): + t.Drop() + return + case <-t.GotInfo(): + mi := t.Metainfo() + _ = CreateTorrentFileIfNotExists(snapshotDir, t.Info(), &mi) + } + }(t, infoHash) + } + + wg.Wait() + + return nil +} + +func waitForChecksumVerify(ctx context.Context, torrentClient *torrent.Client) { + //TODO: tr.VerifyData() - find when to call it + ctx, cancel := context.WithCancel(ctx) + defer cancel() + go func() { + interval := time.Second * 5 + logEvery := time.NewTicker(interval) + defer logEvery.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-logEvery.C: + var aggBytesCompleted, aggLen int64 + for _, t := range torrentClient.Torrents() { + aggBytesCompleted += t.BytesCompleted() + aggLen += t.Length() + } + + line := fmt.Sprintf( + "[torrent] verifying snapshots: %s/%s", + humanize.Bytes(uint64(aggBytesCompleted)), + humanize.Bytes(uint64(aggLen)), + ) + log.Info(line) + } + } + }() + torrentClient.WaitAll() // wait for checksum verify +} diff --git a/cmd/downloader/downloader/logger.go b/cmd/downloader/downloader/logger.go new file mode 100644 index 00000000000..b8a6d204f5a --- /dev/null +++ b/cmd/downloader/downloader/logger.go @@ -0,0 +1,74 @@ +package downloader + +import ( + stdlog "log" + "strings" + + utp "github.com/anacrolix/go-libutp" + lg "github.com/anacrolix/log" + "github.com/ledgerwatch/log/v3" +) + +func init() { + lg.Default = NewAdapterLogger() + utp.Logger = stdlog.New(NullWriter(1), "", stdlog.LstdFlags) +} + +func NewAdapterLogger() lg.Logger { + return lg.Logger{ + LoggerImpl: lg.LoggerImpl(adapterLogger{}), + } +} + +type adapterLogger struct{} + +func (b adapterLogger) Log(msg lg.Msg) { + lvl, ok := msg.GetLevel() + if !ok { + lvl = lg.Info + } + + switch lvl { + case lg.Debug: + log.Debug(msg.String()) + case lg.Info: + str := msg.String() + if strings.Contains(str, "EOF") { // suppress useless errors + break + } + + log.Info(str) + case lg.Warning: + str := msg.String() + if strings.Contains(str, "could not find offer for id") { // suppress useless errors + break + } + + log.Warn(str) + case lg.Error: + str := msg.String() + if strings.Contains(str, "EOF") { // suppress useless errors + break + } + + log.Error(str) + case lg.Critical: + str := msg.String() + if strings.Contains(str, "EOF") { // suppress useless errors + break + } + if strings.Contains(str, "don't want conns") { // suppress useless errors + break + } + + log.Error(str) + default: + log.Warn("unknown log type", "msg", msg.String()) + } +} + +// NullWriter implements the io.Write interface but doesn't do anything. +type NullWriter int + +// Write implements the io.Write interface but is a noop. +func (NullWriter) Write([]byte) (int, error) { return 0, nil } diff --git a/cmd/downloader/downloader/server.go b/cmd/downloader/downloader/server.go new file mode 100644 index 00000000000..7b78c2d3179 --- /dev/null +++ b/cmd/downloader/downloader/server.go @@ -0,0 +1,121 @@ +package downloader + +import ( + "context" + "errors" + "time" + + "github.com/anacrolix/torrent" + "github.com/anacrolix/torrent/metainfo" + "github.com/ledgerwatch/erigon-lib/gointerfaces" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" + prototypes "github.com/ledgerwatch/erigon-lib/gointerfaces/types" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" + "google.golang.org/protobuf/types/known/emptypb" +) + +var ( + ErrNotSupportedNetworkID = errors.New("not supported network id") + ErrNotSupportedSnapshot = errors.New("not supported snapshot for this network id") +) +var ( + _ proto_downloader.DownloaderServer = &SNDownloaderServer{} +) + +func NewServer(db kv.RwDB, client *Client, snapshotDir string) (*SNDownloaderServer, error) { + sn := &SNDownloaderServer{ + db: db, + t: client, + snapshotDir: snapshotDir, + } + return sn, nil +} + +func Stop(torrentClient *torrent.Client) { + for _, t := range torrentClient.Torrents() { + t.DisallowDataDownload() + t.DisallowDataUpload() + } +} + +func Start(ctx context.Context, snapshotDir string, torrentClient *torrent.Client, config *snapshothashes.Config) error { + if err := BuildTorrentFilesIfNeed(ctx, snapshotDir); err != nil { + return err + } + if err := AddTorrentFiles(ctx, snapshotDir, torrentClient, config.Preverified); err != nil { + return err + } + for _, t := range torrentClient.Torrents() { + t.AllowDataDownload() + t.AllowDataUpload() + } + + return nil +} + +type SNDownloaderServer struct { + proto_downloader.UnimplementedDownloaderServer + t *Client + db kv.RwDB + snapshotDir string +} + +func (s *SNDownloaderServer) Download(ctx context.Context, request *proto_downloader.DownloadRequest) (*emptypb.Empty, error) { + infoHashes := make([]metainfo.Hash, len(request.Items)) + for i, it := range request.Items { + //TODO: if hash is empty - create .torrent file from path file (if it exists) + infoHashes[i] = gointerfaces.ConvertH160toAddress(it.TorrentHash) + } + ctx, cancel := context.WithTimeout(ctx, time.Minute*10) + defer cancel() + if err := ResolveAbsentTorrents(ctx, s.t.Cli, infoHashes, s.snapshotDir); err != nil { + return nil, err + } + for _, t := range s.t.Cli.Torrents() { + t.AllowDataDownload() + t.AllowDataUpload() + t.DownloadAll() + } + return &emptypb.Empty{}, nil +} + +func (s *SNDownloaderServer) Stats(ctx context.Context, request *proto_downloader.StatsRequest) (*proto_downloader.StatsReply, error) { + torrents := s.t.Cli.Torrents() + reply := &proto_downloader.StatsReply{Completed: true, Torrents: int32(len(torrents))} + + peers := map[torrent.PeerID]struct{}{} + + for _, t := range torrents { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-t.GotInfo(): + reply.BytesCompleted += uint64(t.BytesCompleted()) + reply.BytesTotal += uint64(t.Info().TotalLength()) + reply.Completed = reply.Completed && t.Complete.Bool() + for _, peer := range t.PeerConns() { + peers[peer.PeerID] = struct{}{} + } + default: + reply.Completed = false + } + } + + reply.Peers = int32(len(peers)) + reply.Progress = int32(100 * (float64(reply.BytesCompleted) / float64(reply.BytesTotal))) + if reply.Progress == 100 && !reply.Completed { + reply.Progress = 99 + } + return reply, nil +} + +func Proto2InfoHashes(in []*prototypes.H160) []metainfo.Hash { + infoHashes := make([]metainfo.Hash, len(in)) + i := 0 + for _, h := range in { + infoHashes[i] = gointerfaces.ConvertH160toAddress(h) + i++ + } + return infoHashes +} diff --git a/cmd/downloader/downloader/util.go b/cmd/downloader/downloader/util.go new file mode 100644 index 00000000000..fae51be87c8 --- /dev/null +++ b/cmd/downloader/downloader/util.go @@ -0,0 +1,177 @@ +package downloader + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "time" + + "github.com/anacrolix/torrent/bencode" + "github.com/anacrolix/torrent/metainfo" + "github.com/ledgerwatch/erigon/cmd/downloader/trackers" + "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/log/v3" +) + +// DefaultPieceSize - Erigon serves many big files, bigger pieces will reduce +// amount of network announcements, but can't go over 2Mb +// see https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure +const DefaultPieceSize = 2 * 1024 * 1024 + +// Trackers - break down by priority tier +var Trackers = [][]string{ + trackers.Best, trackers.Ws, // trackers.Udp, trackers.Https, trackers.Http, +} + +func allTorrentFiles(dir string) ([]string, error) { + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + var res []string + for _, f := range files { + if !snapshotsync.IsCorrectFileName(f.Name()) { + continue + } + if f.Size() == 0 { + continue + } + if filepath.Ext(f.Name()) != ".torrent" { // filter out only compressed files + continue + } + res = append(res, f.Name()) + } + return res, nil +} +func allSegmentFiles(dir string) ([]string, error) { + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + var res []string + for _, f := range files { + if !snapshotsync.IsCorrectFileName(f.Name()) { + continue + } + if f.Size() == 0 { + continue + } + if filepath.Ext(f.Name()) != ".seg" { // filter out only compressed files + continue + } + res = append(res, f.Name()) + } + return res, nil +} + +func ForEachTorrentFile(root string, walker func(torrentFileName string) error) error { + files, err := allTorrentFiles(root) + if err != nil { + return err + } + for _, f := range files { + torrentFileName := filepath.Join(root, f) + if _, err := os.Stat(torrentFileName); err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + return err + } + if err := walker(torrentFileName); err != nil { + return err + } + } + return nil +} + +// BuildTorrentFilesIfNeed - create .torrent files from .seg files (big IO) - if .seg files were added manually +func BuildTorrentFilesIfNeed(ctx context.Context, root string) error { + logEvery := time.NewTicker(20 * time.Second) + defer logEvery.Stop() + + files, err := allSegmentFiles(root) + if err != nil { + return err + } + for i, f := range files { + torrentFileName := path.Join(root, f+".torrent") + if _, err := os.Stat(torrentFileName); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + info, err := BuildInfoBytesForFile(root, f) + if err != nil { + return err + } + if err := CreateTorrentFile(root, info, nil); err != nil { + return err + } + } + + select { + default: + case <-ctx.Done(): + return ctx.Err() + case <-logEvery.C: + log.Info("[torrent] Create .torrent files", "progress", fmt.Sprintf("%d/%d", i, len(files))) + } + } + return nil +} + +func BuildInfoBytesForFile(root string, fileName string) (*metainfo.Info, error) { + info := &metainfo.Info{PieceLength: DefaultPieceSize} + if err := info.BuildFromFilePath(filepath.Join(root, fileName)); err != nil { + return nil, err + } + return info, nil +} + +func CreateTorrentFileIfNotExists(root string, info *metainfo.Info, mi *metainfo.MetaInfo) error { + torrentFileName := filepath.Join(root, info.Name+".torrent") + if _, err := os.Stat(torrentFileName); err != nil { + if errors.Is(err, os.ErrNotExist) { + return CreateTorrentFile(root, info, mi) + } + return err + } + return nil +} + +func CreateTorrentFile(root string, info *metainfo.Info, mi *metainfo.MetaInfo) error { + if mi == nil { + infoBytes, err := bencode.Marshal(info) + if err != nil { + return err + } + mi = &metainfo.MetaInfo{ + CreationDate: time.Now().Unix(), + CreatedBy: "erigon", + InfoBytes: infoBytes, + AnnounceList: Trackers, + } + } else { + mi.AnnounceList = Trackers + } + torrentFileName := filepath.Join(root, info.Name+".torrent") + + file, err := os.Create(torrentFileName) + if err != nil { + return err + } + defer file.Sync() + defer file.Close() + if err := mi.Write(file); err != nil { + return err + } + return nil +} + +func segmentFileNameFromTorrentFileName(in string) string { + ext := filepath.Ext(in) + return in[0 : len(in)-len(ext)] +} diff --git a/cmd/downloader/downloadergrpc/client.go b/cmd/downloader/downloadergrpc/client.go new file mode 100644 index 00000000000..47ae9492f21 --- /dev/null +++ b/cmd/downloader/downloadergrpc/client.go @@ -0,0 +1,64 @@ +package downloadergrpc + +import ( + "context" + "fmt" + "time" + + "github.com/anacrolix/torrent/metainfo" + "github.com/c2h5oh/datasize" + "github.com/ledgerwatch/erigon-lib/gointerfaces" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" + prototypes "github.com/ledgerwatch/erigon-lib/gointerfaces/types" + "github.com/ledgerwatch/erigon/common" + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/keepalive" +) + +func NewClient(ctx context.Context, downloaderAddr string) (proto_downloader.DownloaderClient, error) { + // creating grpc client connection + var dialOpts []grpc.DialOption + + backoffCfg := backoff.DefaultConfig + backoffCfg.BaseDelay = 500 * time.Millisecond + backoffCfg.MaxDelay = 10 * time.Second + dialOpts = []grpc.DialOption{ + grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoffCfg, MinConnectTimeout: 10 * time.Minute}), + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(16 * datasize.MB))), + grpc.WithKeepaliveParams(keepalive.ClientParameters{}), + } + + dialOpts = append(dialOpts, grpc.WithInsecure()) + conn, err := grpc.DialContext(ctx, downloaderAddr, dialOpts...) + if err != nil { + return nil, fmt.Errorf("creating client connection to sentry P2P: %w", err) + } + return proto_downloader.NewDownloaderClient(conn), nil +} + +func InfoHashes2Proto(in []metainfo.Hash) []*prototypes.H160 { + infoHashes := make([]*prototypes.H160, len(in)) + i := 0 + for _, h := range in { + infoHashes[i] = gointerfaces.ConvertAddressToH160(h) + i++ + } + return infoHashes +} + +func Strings2Proto(in []string) []*prototypes.H160 { + infoHashes := make([]*prototypes.H160, len(in)) + i := 0 + for _, h := range in { + infoHashes[i] = String2Proto(h) + i++ + } + return infoHashes +} + +func String2Proto(in string) *prototypes.H160 { + var infoHash [20]byte + copy(infoHash[:], common.FromHex(in)) + return gointerfaces.ConvertAddressToH160(infoHash) +} diff --git a/cmd/downloader/generator/commands/metainfo_hash.go b/cmd/downloader/generator/commands/metainfo_hash.go deleted file mode 100644 index 81efd1b2f9c..00000000000 --- a/cmd/downloader/generator/commands/metainfo_hash.go +++ /dev/null @@ -1,46 +0,0 @@ -package commands - -import ( - "errors" - "fmt" - "time" - - "github.com/anacrolix/torrent/bencode" - "github.com/anacrolix/torrent/metainfo" - "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/turbo/snapshotsync" - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(snapshotMetainfoCmd) -} - -func PrintMetaInfoHash(path string) error { - t := time.Now() - mi := metainfo.MetaInfo{} - info, err := snapshotsync.BuildInfoBytesForSnapshot(path, snapshotsync.MdbxFilename) - if err != nil { - return err - } - mi.InfoBytes, err = bencode.Marshal(info) - if err != nil { - return err - } - - fmt.Println("infohash:", mi.HashInfoBytes().String()) - fmt.Println("infobytes:", common.Bytes2Hex(mi.InfoBytes)) - fmt.Println("It took", time.Since(t)) - return nil -} - -var snapshotMetainfoCmd = &cobra.Command{ - Use: "snapshotMetainfo", - Short: "Calculate snapshot metainfo", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("empty path") - } - return PrintMetaInfoHash(args[0]) - }, -} diff --git a/cmd/downloader/root.go b/cmd/downloader/main.go similarity index 50% rename from cmd/downloader/root.go rename to cmd/downloader/main.go index d9c76c972e9..80a9207dff9 100644 --- a/cmd/downloader/root.go +++ b/cmd/downloader/main.go @@ -2,20 +2,26 @@ package main import ( "context" + "encoding/json" "fmt" "net" "os" "path" "time" + "github.com/anacrolix/torrent/metainfo" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" - proto_snap "github.com/ledgerwatch/erigon-lib/gointerfaces/snapshotsync" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" + "github.com/ledgerwatch/erigon/cmd/downloader/downloader" + "github.com/ledgerwatch/erigon/cmd/hack/tool" "github.com/ledgerwatch/erigon/cmd/utils" "github.com/ledgerwatch/erigon/common/paths" "github.com/ledgerwatch/erigon/internal/debug" "github.com/ledgerwatch/erigon/params" - "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/ledgerwatch/log/v3" "github.com/spf13/cobra" "google.golang.org/grpc" @@ -30,21 +36,26 @@ var ( datadir string seeding bool downloaderApiAddr string - healthCheck bool ) func init() { flags := append(debug.Flags, utils.MetricFlags...) utils.CobraFlags(rootCmd, flags) - rootCmd.Flags().StringVar(&datadir, utils.DataDirFlag.Name, paths.DefaultDataDir(), utils.DataDirFlag.Usage) - if err := rootCmd.MarkFlagDirname(utils.DataDirFlag.Name); err != nil { - panic(err) - } + withDatadir(rootCmd) rootCmd.PersistentFlags().BoolVar(&seeding, "seeding", true, "Seed snapshots") rootCmd.Flags().StringVar(&downloaderApiAddr, "downloader.api.addr", "127.0.0.1:9093", "external downloader api network address, for example: 127.0.0.1:9093 serves remote downloader interface") - rootCmd.Flags().BoolVar(&healthCheck, "healthcheck", false, "Enable grpc health check") + + withDatadir(printInfoHashes) + rootCmd.AddCommand(printInfoHashes) +} + +func withDatadir(cmd *cobra.Command) { + cmd.Flags().StringVar(&datadir, utils.DataDirFlag.Name, paths.DefaultDataDir(), utils.DataDirFlag.Usage) + if err := cmd.MarkFlagDirname(utils.DataDirFlag.Name); err != nil { + panic(err) + } } func main() { @@ -70,64 +81,111 @@ var rootCmd = &cobra.Command{ debug.Exit() }, RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - snapshotsDir := path.Join(datadir, "snapshots") - log.Info("Run snapshot downloader", "addr", downloaderApiAddr, "datadir", datadir, "seeding", seeding) + if err := Downloader(cmd.Context(), cmd); err != nil { + log.Error("Downloader", "err", err) + return nil + } + return nil + }, +} - bittorrentServer, err := snapshotsync.NewServer(snapshotsDir, seeding) +func Downloader(ctx context.Context, cmd *cobra.Command) error { + var cc *params.ChainConfig + { + chaindataDir := path.Join(datadir, "chaindata") + if err := os.MkdirAll(chaindataDir, 0755); err != nil { + return err + } + chaindata, err := mdbx.Open(chaindataDir, log.New(), true) + if err != nil { + return fmt.Errorf("%w, path: %s", err, chaindataDir) + } + cc = tool.ChainConfigFromDB(chaindata) + chaindata.Close() + } + + snapshotsDir := path.Join(datadir, "snapshots") + log.Info("Run snapshot downloader", "addr", downloaderApiAddr, "datadir", datadir, "seeding", seeding) + if err := os.MkdirAll(snapshotsDir, 0755); err != nil { + return err + } + + db := mdbx.MustOpen(snapshotsDir + "/db") + var t *downloader.Client + if err := db.Update(context.Background(), func(tx kv.RwTx) error { + peerID, err := tx.GetOne(kv.BittorrentInfo, []byte(kv.BittorrentPeerID)) if err != nil { - return fmt.Errorf("new server: %w", err) + return fmt.Errorf("get peer id: %w", err) } - log.Info("Load") - err = bittorrentServer.Load() + t, err = downloader.New(snapshotsDir, seeding, string(peerID)) if err != nil { - return fmt.Errorf("load: %w", err) + return err + } + if len(peerID) == 0 { + err = t.SavePeerID(tx) + if err != nil { + return fmt.Errorf("save peer id: %w", err) + } } + return nil + }); err != nil { + return err + } + defer t.Close() + + bittorrentServer, err := downloader.NewServer(db, t, snapshotsDir) + if err != nil { + return fmt.Errorf("new server: %w", err) + } + + snapshotsCfg := snapshothashes.KnownConfig(cc.ChainName) + err = downloader.Start(ctx, snapshotsDir, t.Cli, snapshotsCfg) + if err != nil { + return fmt.Errorf("start: %w", err) + } + + go downloader.MainLoop(ctx, t.Cli) - go func() { - _, err := bittorrentServer.Download(ctx, &proto_snap.DownloadSnapshotRequest{ - NetworkId: params.MainnetChainConfig.ChainID.Uint64(), - Type: snapshotsync.GetAvailableSnapshotTypes(params.MainnetChainConfig.ChainID.Uint64()), - }) + grpcServer, err := StartGrpc(bittorrentServer, downloaderApiAddr, nil) + if err != nil { + return err + } + <-cmd.Context().Done() + grpcServer.GracefulStop() + return nil +} + +var printInfoHashes = &cobra.Command{ + Use: "print_info_hashes", + Example: "go run ./cmd/downloader print_info_hashes --datadir ", + RunE: func(cmd *cobra.Command, args []string) error { + snapshotsDir := path.Join(datadir, "snapshots") + + res := map[string]string{} + if err := downloader.ForEachTorrentFile(snapshotsDir, func(torrentFilePath string) error { + mi, err := metainfo.LoadFromFile(torrentFilePath) if err != nil { - log.Error("Download failed", "err", err, "networkID", params.MainnetChainConfig.ChainID.Uint64()) + return err } - }() - go func() { - for { - select { - case <-cmd.Context().Done(): - return - default: - } - - snapshots, err := bittorrentServer.Snapshots(ctx, &proto_snap.SnapshotsRequest{ - NetworkId: params.MainnetChainConfig.ChainID.Uint64(), - }) - if err != nil { - log.Error("get snapshots", "err", err) - time.Sleep(time.Minute) - continue - } - stats := bittorrentServer.Stats(context.Background()) - for _, v := range snapshots.Info { - log.Info("Snapshot "+v.Type.String(), "%", v.Readiness, "peers", stats[v.Type.String()].ConnectedSeeders) - } - time.Sleep(time.Minute) + info, err := mi.UnmarshalInfo() + if err != nil { + return err } - }() - grpcServer, err := StartGrpc(bittorrentServer, downloaderApiAddr, nil, healthCheck) + res[info.Name] = mi.HashInfoBytes().String() + return nil + }); err != nil { + return err + } + b, err := json.Marshal(res) if err != nil { return err } - <-cmd.Context().Done() - grpcServer.GracefulStop() - + fmt.Printf("%s\n", b) return nil }, } -func StartGrpc(snServer *snapshotsync.SNDownloaderServer, addr string, creds *credentials.TransportCredentials, healthCheck bool) (*grpc.Server, error) { +func StartGrpc(snServer *downloader.SNDownloaderServer, addr string, creds *credentials.TransportCredentials) (*grpc.Server, error) { lis, err := net.Listen("tcp", addr) if err != nil { return nil, fmt.Errorf("could not create listener: %w, addr=%s", err, addr) @@ -162,22 +220,18 @@ func StartGrpc(snServer *snapshotsync.SNDownloaderServer, addr string, creds *cr grpcServer := grpc.NewServer(opts...) reflection.Register(grpcServer) // Register reflection service on gRPC server. if snServer != nil { - proto_snap.RegisterDownloaderServer(grpcServer, snServer) - } - var healthServer *health.Server - if healthCheck { - healthServer = health.NewServer() - grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) + proto_downloader.RegisterDownloaderServer(grpcServer, snServer) } //if metrics.Enabled { // grpc_prometheus.Register(grpcServer) //} + healthServer := health.NewServer() + grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) + go func() { - if healthCheck { - defer healthServer.Shutdown() - } + defer healthServer.Shutdown() if err := grpcServer.Serve(lis); err != nil { log.Error("gRPC server stop", "err", err) } diff --git a/cmd/downloader/seeder/main.go b/cmd/downloader/seeder/main.go deleted file mode 100644 index e2c0f3adb70..00000000000 --- a/cmd/downloader/seeder/main.go +++ /dev/null @@ -1,150 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "path/filepath" - "time" - - lg "github.com/anacrolix/log" - "github.com/anacrolix/torrent" - "github.com/anacrolix/torrent/bencode" - "github.com/anacrolix/torrent/metainfo" - libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/cmd/utils" - "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/internal/debug" - trnt "github.com/ledgerwatch/erigon/turbo/snapshotsync" - "github.com/ledgerwatch/log/v3" - "github.com/spf13/cobra" -) - -func init() { - utils.CobraFlags(rootCmd, append(debug.Flags, utils.MetricFlags...)) -} - -func main() { - ctx, cancel := utils.RootContext() - defer cancel() - - if err := rootCmd.ExecuteContext(ctx); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -var rootCmd = &cobra.Command{ - Use: "seed", - Short: "seed snapshot", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - if err := debug.SetupCobra(cmd); err != nil { - panic(err) - } - }, - PersistentPostRun: func(cmd *cobra.Command, args []string) { - debug.Exit() - }, - Args: cobra.ExactArgs(1), - ArgAliases: []string{"snapshots dir"}, - RunE: func(cmd *cobra.Command, args []string) error { - return Seed(cmd.Context(), args[0]) - }, -} - -func Seed(ctx context.Context, datadir string) error { - defer func() { - //hack origin lib don't have proper close handling - time.Sleep(time.Second * 5) - }() - datadir = filepath.Dir(datadir) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - cfg := trnt.DefaultTorrentConfig() - cfg.NoDHT = false - cfg.DisableTrackers = false - cfg.Seed = true - cfg.Debug = false - cfg.Logger = cfg.Logger.FilterLevel(lg.Info) - cfg.DataDir = datadir - - pathes := []string{ - cfg.DataDir + "/headers", - cfg.DataDir + "/bodies", - cfg.DataDir + "/state", - } - - cl, err := torrent.NewClient(cfg) - if err != nil { - return err - } - defer cl.Close() - - torrents := make([]*torrent.Torrent, len(pathes)) - for i, v := range pathes { - i := i - mi := &metainfo.MetaInfo{ - CreationDate: time.Now().Unix(), - CreatedBy: "erigon", - AnnounceList: trnt.Trackers, - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - fmt.Println(err) - continue - } else if err != nil { - return err - } - tt := time.Now() - if common.IsCanceled(ctx) { - return libcommon.ErrStopped - } - info, err := trnt.BuildInfoBytesForSnapshot(v, trnt.MdbxFilename) - if err != nil { - return err - } - - mi.InfoBytes, err = bencode.Marshal(info) - if err != nil { - return err - } - - torrents[i], _, err = cl.AddTorrentSpec(&torrent.TorrentSpec{ - Trackers: trnt.Trackers, - InfoHash: mi.HashInfoBytes(), - InfoBytes: mi.InfoBytes, - ChunkSize: trnt.DefaultChunkSize, - }) - if err != nil { - return err - } - - log.Info("Torrent added", "name", torrents[i].Info().Name, "path", v, "t", time.Since(tt)) - - if !torrents[i].Seeding() { - log.Warn(torrents[i].Name() + " not seeding") - } - - if common.IsCanceled(ctx) { - return libcommon.ErrStopped - } - } - - go func() { - ticker := time.NewTicker(10 * time.Second) - for range ticker.C { - for _, t := range cl.Torrents() { - log.Info("Snapshot stats", "snapshot", t.Name(), "active peers", t.Stats().ActivePeers, "seeding", t.Seeding(), "hash", t.Metainfo().HashInfoBytes().String()) - } - - if common.IsCanceled(ctx) { - ticker.Stop() - return - } - } - }() - - <-ctx.Done() - return nil -} diff --git a/cmd/downloader/trackers/embed.go b/cmd/downloader/trackers/embed.go new file mode 100644 index 00000000000..a0a0f7162dd --- /dev/null +++ b/cmd/downloader/trackers/embed.go @@ -0,0 +1,39 @@ +package trackers + +import ( + _ "embed" + "strings" +) + +//go:embed trackerslist/trackers_best.txt +var best string +var Best = strings.Split(best, "\n\n") + +//go:embed trackerslist/trackers_all_https.txt +var https string +var Https = withoutBest(strings.Split(https, "\n\n")) + +//go:embed trackerslist/trackers_all_http.txt +var http string +var Http = withoutBest(strings.Split(http, "\n\n")) + +//go:embed trackerslist/trackers_all_udp.txt +var udp string +var Udp = withoutBest(strings.Split(udp, "\n\n")) + +//go:embed trackerslist/trackers_all_ws.txt +var ws string +var Ws = withoutBest(strings.Split(ws, "\n\n")) + +func withoutBest(in []string) (res []string) { +Loop: + for _, tracker := range in { + for _, bestItem := range Best { + if tracker == bestItem { + continue Loop + } + } + res = append(res, tracker) + } + return res +} diff --git a/cmd/downloader/trackers/trackerslist b/cmd/downloader/trackers/trackerslist new file mode 160000 index 00000000000..40110ecd394 --- /dev/null +++ b/cmd/downloader/trackers/trackerslist @@ -0,0 +1 @@ +Subproject commit 40110ecd394cd66c749ca5fe278e36cb444bdbf8 diff --git a/cmd/hack/hack.go b/cmd/hack/hack.go index f9dfa41c9e5..618a379f111 100644 --- a/cmd/hack/hack.go +++ b/cmd/hack/hack.go @@ -60,6 +60,7 @@ import ( "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/ledgerwatch/erigon/turbo/trie" "github.com/ledgerwatch/log/v3" "github.com/wcharczuk/go-chart/v2" @@ -2654,7 +2655,7 @@ func checkBlockSnapshot(chaindata string) error { chainID, _ := uint256.FromBig(chainConfig.ChainID) _ = chainID - snapshots := snapshotsync.NewAllSnapshots(path.Join(dataDir, "snapshots"), params.KnownSnapshots(chainConfig.ChainName)) + snapshots := snapshotsync.NewAllSnapshots(path.Join(dataDir, "snapshots"), snapshothashes.KnownConfig(chainConfig.ChainName)) snapshots.ReopenSegments() snapshots.ReopenIndices() //if err := snapshots.BuildIndices(context.Background(), *chainID); err != nil { diff --git a/cmd/integration/commands/stages.go b/cmd/integration/commands/stages.go index 8c5a9ffd6cb..0127c313d97 100644 --- a/cmd/integration/commands/stages.go +++ b/cmd/integration/commands/stages.go @@ -14,7 +14,7 @@ import ( "github.com/ledgerwatch/erigon-lib/etl" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces" - "github.com/ledgerwatch/erigon/cmd/sentry/download" + "github.com/ledgerwatch/erigon/cmd/sentry/sentry" "github.com/ledgerwatch/erigon/cmd/utils" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/dbutils" @@ -33,7 +33,9 @@ import ( "github.com/ledgerwatch/erigon/migrations" "github.com/ledgerwatch/erigon/p2p" "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/params/networkname" "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" stages2 "github.com/ledgerwatch/erigon/turbo/stages" "github.com/ledgerwatch/log/v3" "github.com/ledgerwatch/secp256k1" @@ -996,25 +998,25 @@ func byChain() (*core.Genesis, *params.ChainConfig) { var genesis *core.Genesis switch chain { - case "", params.MainnetChainName: + case "", networkname.MainnetChainName: chainConfig = params.MainnetChainConfig genesis = core.DefaultGenesisBlock() - case params.RopstenChainName: + case networkname.RopstenChainName: chainConfig = params.RopstenChainConfig genesis = core.DefaultRopstenGenesisBlock() - case params.GoerliChainName: + case networkname.GoerliChainName: chainConfig = params.GoerliChainConfig genesis = core.DefaultGoerliGenesisBlock() - case params.RinkebyChainName: + case networkname.RinkebyChainName: chainConfig = params.RinkebyChainConfig genesis = core.DefaultRinkebyGenesisBlock() - case params.SokolChainName: + case networkname.SokolChainName: chainConfig = params.SokolChainConfig genesis = core.DefaultSokolGenesisBlock() - case params.KovanChainName: + case networkname.KovanChainName: chainConfig = params.KovanChainConfig genesis = core.DefaultKovanGenesisBlock() - case params.FermionChainName: + case networkname.FermionChainName: chainConfig = params.FermionChainConfig genesis = core.DefaultFermionGenesisBlock() } @@ -1031,11 +1033,11 @@ func allSnapshots(cc *params.ChainConfig) *snapshotsync.AllSnapshots { Enabled: true, Dir: path.Join(datadir, "snapshots"), } - _allSnapshotsSingleton = snapshotsync.NewAllSnapshots(snapshotCfg.Dir, params.KnownSnapshots(cc.ChainName)) + _allSnapshotsSingleton = snapshotsync.NewAllSnapshots(snapshotCfg.Dir, snapshothashes.KnownConfig(cc.ChainName)) if err := _allSnapshotsSingleton.ReopenSegments(); err != nil { panic(err) } - if err := _allSnapshotsSingleton.ReopenIndices(); err != nil { + if err := _allSnapshotsSingleton.ReopenSomeIndices(snapshotsync.AllSnapshotTypes...); err != nil { panic(err) } } @@ -1098,7 +1100,7 @@ func newSync(ctx context.Context, db kv.RwDB, miningConfig *params.MiningConfig) must(batchSize.UnmarshalText([]byte(batchSizeStr))) blockDownloaderWindow := 65536 - downloadServer, err := download.NewControlServer(db, "", chainConfig, genesisBlock.Hash(), engine, 1, nil, blockDownloaderWindow) + sentryControlServer, err := sentry.NewControlServer(db, "", chainConfig, genesisBlock.Hash(), engine, 1, nil, blockDownloaderWindow) if err != nil { panic(err) } @@ -1111,7 +1113,11 @@ func newSync(ctx context.Context, db kv.RwDB, miningConfig *params.MiningConfig) cfg.Miner = *miningConfig } - sync, err := stages2.NewStagedSync(context.Background(), logger, db, p2p.Config{}, cfg, chainConfig.TerminalTotalDifficulty, downloadServer, tmpdir, nil, nil, nil, nil) + sync, err := stages2.NewStagedSync(context.Background(), logger, db, p2p.Config{}, cfg, + chainConfig.TerminalTotalDifficulty, sentryControlServer, tmpdir, + nil, nil, nil, nil, + nil, + ) if err != nil { panic(err) } diff --git a/cmd/rpcdaemon/cli/config.go b/cmd/rpcdaemon/cli/config.go index 8a881c09d9a..cb57e51b800 100644 --- a/cmd/rpcdaemon/cli/config.go +++ b/cmd/rpcdaemon/cli/config.go @@ -31,6 +31,7 @@ import ( "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/ledgerwatch/log/v3" "github.com/spf13/cobra" "google.golang.org/grpc" @@ -277,7 +278,7 @@ func RemoteServices(ctx context.Context, cfg Flags, logger log.Logger, rootCance return nil, nil, nil, nil, nil, nil, fmt.Errorf("chain config not found in db. Need start erigon at least once on this db") } - allSnapshots := snapshotsync.NewAllSnapshots(cfg.Snapshot.Dir, params.KnownSnapshots(cc.ChainName)) + allSnapshots := snapshotsync.NewAllSnapshots(cfg.Snapshot.Dir, snapshothashes.KnownConfig(cc.ChainName)) if err != nil { return nil, nil, nil, nil, nil, nil, err } diff --git a/cmd/rpcdaemon/interfaces/interfaces.go b/cmd/rpcdaemon/interfaces/interfaces.go index 7b3346f8b49..41431335630 100644 --- a/cmd/rpcdaemon/interfaces/interfaces.go +++ b/cmd/rpcdaemon/interfaces/interfaces.go @@ -6,6 +6,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/rlp" ) type BlockReader interface { @@ -17,7 +18,13 @@ type HeaderReader interface { HeaderByNumber(ctx context.Context, tx kv.Getter, blockHeight uint64) (*types.Header, error) } +type BodyReader interface { + Body(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (body *types.Body, err error) + BodyRlp(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (bodyRlp rlp.RawValue, err error) +} + type FullBlockReader interface { BlockReader + BodyReader HeaderReader } diff --git a/cmd/sentry/commands/sentry.go b/cmd/sentry/commands/sentry.go deleted file mode 100644 index c9a1670883c..00000000000 --- a/cmd/sentry/commands/sentry.go +++ /dev/null @@ -1,93 +0,0 @@ -package commands - -import ( - "fmt" - "os" - "path" - - "github.com/ledgerwatch/erigon/cmd/sentry/download" - "github.com/ledgerwatch/erigon/cmd/utils" - "github.com/ledgerwatch/erigon/common/paths" - "github.com/ledgerwatch/erigon/eth/protocols/eth" - "github.com/ledgerwatch/erigon/internal/debug" - node2 "github.com/ledgerwatch/erigon/turbo/node" - "github.com/spf13/cobra" -) - -var ( - sentryAddr string // Address of the sentry : - chaindata string // Path to chaindata - datadir string // Path to td working dir - - natSetting string // NAT setting - port int // Listening port - staticPeers []string // static peers - trustedPeers []string // trusted peers - discoveryDNS []string - nodiscover bool // disable sentry's discovery mechanism - protocol string - netRestrict string // CIDR to restrict peering to - healthCheck bool -) - -func init() { - utils.CobraFlags(rootCmd, append(debug.Flags, utils.MetricFlags...)) - - rootCmd.Flags().StringVar(&natSetting, "nat", "", `NAT port mapping mechanism (any|none|upnp|pmp|extip:) - "" or "none" default - do not nat - "extip:77.12.33.4" will assume the local machine is reachable on the given IP - "any" uses the first auto-detected mechanism - "upnp" uses the Universal Plug and Play protocol - "pmp" uses NAT-PMP with an auto-detected gateway address - "pmp:192.168.0.1" uses NAT-PMP with the given gateway address -`) - rootCmd.Flags().IntVar(&port, "port", 30303, "p2p port number") - rootCmd.Flags().StringVar(&sentryAddr, "sentry.api.addr", "localhost:9091", "grpc addresses") - rootCmd.Flags().StringVar(&protocol, "p2p.protocol", "eth66", "eth66") - rootCmd.Flags().StringSliceVar(&staticPeers, "staticpeers", []string{}, "static peer list [enode]") - rootCmd.Flags().StringSliceVar(&trustedPeers, "trustedpeers", []string{}, "trusted peer list [enode]") - rootCmd.Flags().StringSliceVar(&discoveryDNS, utils.DNSDiscoveryFlag.Name, []string{}, utils.DNSDiscoveryFlag.Usage) - rootCmd.Flags().BoolVar(&nodiscover, utils.NoDiscoverFlag.Name, false, utils.NoDiscoverFlag.Usage) - rootCmd.Flags().StringVar(&netRestrict, "netrestrict", "", "CIDR range to accept peers from ") - rootCmd.Flags().StringVar(&datadir, utils.DataDirFlag.Name, paths.DefaultDataDir(), utils.DataDirFlag.Usage) - rootCmd.Flags().BoolVar(&healthCheck, utils.HealthCheckFlag.Name, false, utils.HealthCheckFlag.Usage) - if err := rootCmd.MarkFlagDirname(utils.DataDirFlag.Name); err != nil { - panic(err) - } - -} - -var rootCmd = &cobra.Command{ - Use: "sentry", - Short: "Run p2p sentry", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - if err := debug.SetupCobra(cmd); err != nil { - panic(err) - } - if chaindata == "" { - chaindata = path.Join(datadir, "chaindata") - } - }, - PersistentPostRun: func(cmd *cobra.Command, args []string) { - debug.Exit() - }, - RunE: func(cmd *cobra.Command, args []string) error { - p := eth.ETH66 - - nodeConfig := node2.NewNodeConfig() - p2pConfig, err := utils.NewP2PConfig(nodiscover, datadir, netRestrict, natSetting, nodeConfig.NodeName(), staticPeers, trustedPeers, uint(port), uint(p)) - if err != nil { - return err - } - return download.Sentry(datadir, sentryAddr, discoveryDNS, p2pConfig, uint(p), healthCheck) - }, -} - -func Execute() { - ctx, cancel := utils.RootContext() - defer cancel() - if err := rootCmd.ExecuteContext(ctx); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/cmd/sentry/main.go b/cmd/sentry/main.go index b212bb0493f..45f42c69d60 100644 --- a/cmd/sentry/main.go +++ b/cmd/sentry/main.go @@ -1,11 +1,95 @@ package main import ( - "github.com/ledgerwatch/erigon/cmd/sentry/commands" + "fmt" + "os" + "path" + + "github.com/ledgerwatch/erigon/cmd/sentry/sentry" + "github.com/ledgerwatch/erigon/cmd/utils" + "github.com/ledgerwatch/erigon/common/paths" + "github.com/ledgerwatch/erigon/eth/protocols/eth" + "github.com/ledgerwatch/erigon/internal/debug" + node2 "github.com/ledgerwatch/erigon/turbo/node" + "github.com/spf13/cobra" ) // generate the messages +var ( + sentryAddr string // Address of the sentry : + chaindata string // Path to chaindata + datadir string // Path to td working dir + + natSetting string // NAT setting + port int // Listening port + staticPeers []string // static peers + trustedPeers []string // trusted peers + discoveryDNS []string + nodiscover bool // disable sentry's discovery mechanism + protocol string + netRestrict string // CIDR to restrict peering to + healthCheck bool +) + +func init() { + utils.CobraFlags(rootCmd, append(debug.Flags, utils.MetricFlags...)) + + rootCmd.Flags().StringVar(&natSetting, "nat", "", `NAT port mapping mechanism (any|none|upnp|pmp|extip:) + "" or "none" default - do not nat + "extip:77.12.33.4" will assume the local machine is reachable on the given IP + "any" uses the first auto-detected mechanism + "upnp" uses the Universal Plug and Play protocol + "pmp" uses NAT-PMP with an auto-detected gateway address + "pmp:192.168.0.1" uses NAT-PMP with the given gateway address +`) + rootCmd.Flags().IntVar(&port, "port", 30303, "p2p port number") + rootCmd.Flags().StringVar(&sentryAddr, "sentry.api.addr", "localhost:9091", "grpc addresses") + rootCmd.Flags().StringVar(&protocol, "p2p.protocol", "eth66", "eth66") + rootCmd.Flags().StringSliceVar(&staticPeers, "staticpeers", []string{}, "static peer list [enode]") + rootCmd.Flags().StringSliceVar(&trustedPeers, "trustedpeers", []string{}, "trusted peer list [enode]") + rootCmd.Flags().StringSliceVar(&discoveryDNS, utils.DNSDiscoveryFlag.Name, []string{}, utils.DNSDiscoveryFlag.Usage) + rootCmd.Flags().BoolVar(&nodiscover, utils.NoDiscoverFlag.Name, false, utils.NoDiscoverFlag.Usage) + rootCmd.Flags().StringVar(&netRestrict, "netrestrict", "", "CIDR range to accept peers from ") + rootCmd.Flags().StringVar(&datadir, utils.DataDirFlag.Name, paths.DefaultDataDir(), utils.DataDirFlag.Usage) + rootCmd.Flags().BoolVar(&healthCheck, utils.HealthCheckFlag.Name, false, utils.HealthCheckFlag.Usage) + if err := rootCmd.MarkFlagDirname(utils.DataDirFlag.Name); err != nil { + panic(err) + } + +} + +var rootCmd = &cobra.Command{ + Use: "sentry", + Short: "Run p2p sentry", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if err := debug.SetupCobra(cmd); err != nil { + panic(err) + } + if chaindata == "" { + chaindata = path.Join(datadir, "chaindata") + } + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + debug.Exit() + }, + RunE: func(cmd *cobra.Command, args []string) error { + p := eth.ETH66 + + nodeConfig := node2.NewNodeConfig() + p2pConfig, err := utils.NewP2PConfig(nodiscover, datadir, netRestrict, natSetting, nodeConfig.NodeName(), staticPeers, trustedPeers, uint(port), uint(p)) + if err != nil { + return err + } + return sentry.Sentry(datadir, sentryAddr, discoveryDNS, p2pConfig, uint(p), healthCheck) + }, +} + func main() { - commands.Execute() + ctx, cancel := utils.RootContext() + defer cancel() + if err := rootCmd.ExecuteContext(ctx); err != nil { + fmt.Println(err) + os.Exit(1) + } } diff --git a/cmd/sentry/download/broadcast.go b/cmd/sentry/sentry/broadcast.go similarity index 99% rename from cmd/sentry/download/broadcast.go rename to cmd/sentry/sentry/broadcast.go index fce68ee7b9a..09d60d4ab84 100644 --- a/cmd/sentry/download/broadcast.go +++ b/cmd/sentry/sentry/broadcast.go @@ -1,4 +1,4 @@ -package download +package sentry import ( "context" diff --git a/cmd/sentry/download/downloader.go b/cmd/sentry/sentry/downloader.go similarity index 99% rename from cmd/sentry/download/downloader.go rename to cmd/sentry/sentry/downloader.go index 6d487551744..9d1411079bd 100644 --- a/cmd/sentry/download/downloader.go +++ b/cmd/sentry/sentry/downloader.go @@ -1,4 +1,4 @@ -package download +package sentry import ( "bytes" @@ -753,9 +753,7 @@ func makeStatusData(s *ControlServerImpl) *proto_sentry.StatusData { } } -// Methods of Core called by sentry - -func GrpcSentryClient(ctx context.Context, sentryAddr string) (*direct.SentryClientRemote, error) { +func GrpcClient(ctx context.Context, sentryAddr string) (*direct.SentryClientRemote, error) { // creating grpc client connection var dialOpts []grpc.DialOption diff --git a/cmd/sentry/download/sentry.go b/cmd/sentry/sentry/sentry.go similarity index 99% rename from cmd/sentry/download/sentry.go rename to cmd/sentry/sentry/sentry.go index a3a56cb112c..f70a839d4f1 100644 --- a/cmd/sentry/download/sentry.go +++ b/cmd/sentry/sentry/sentry.go @@ -1,4 +1,4 @@ -package download +package sentry import ( "bytes" diff --git a/cmd/sentry/download/sentry_api.go b/cmd/sentry/sentry/sentry_api.go similarity index 99% rename from cmd/sentry/download/sentry_api.go rename to cmd/sentry/sentry/sentry_api.go index 6cefab0d18a..ae12dc8c690 100644 --- a/cmd/sentry/download/sentry_api.go +++ b/cmd/sentry/sentry/sentry_api.go @@ -1,4 +1,4 @@ -package download +package sentry import ( "context" diff --git a/cmd/sentry/download/sentry_test.go b/cmd/sentry/sentry/sentry_test.go similarity index 99% rename from cmd/sentry/download/sentry_test.go rename to cmd/sentry/sentry/sentry_test.go index 95a989e54cc..d8316129e97 100644 --- a/cmd/sentry/download/sentry_test.go +++ b/cmd/sentry/sentry/sentry_test.go @@ -1,4 +1,4 @@ -package download +package sentry import ( "context" diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 70535d10557..60ed95aecf2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -33,6 +33,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/txpool" "github.com/ledgerwatch/erigon/eth/protocols/eth" + "github.com/ledgerwatch/erigon/params/networkname" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/urfave/cli" @@ -122,7 +123,7 @@ var ( ChainFlag = cli.StringFlag{ Name: "chain", Usage: "Name of the testnet to join", - Value: params.MainnetChainName, + Value: networkname.MainnetChainName, } IdentityFlag = cli.StringFlag{ Name: "identity", @@ -412,6 +413,11 @@ var ( Name: "sentry.api.addr", Usage: "comma separated sentry addresses ':,:'", } + DownloaderAddrFlag = cli.StringFlag{ + Name: "downloader.api.addr", + Value: "127.0.0.1:9093", + Usage: "downloader address ':'", + } BootnodesFlag = cli.StringFlag{ Name: "bootnodes", Usage: "Comma separated enode URLs for P2P discovery bootstrap", @@ -601,19 +607,19 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { } else { chain := ctx.GlobalString(ChainFlag.Name) switch chain { - case params.RopstenChainName: + case networkname.RopstenChainName: urls = params.RopstenBootnodes - case params.RinkebyChainName: + case networkname.RinkebyChainName: urls = params.RinkebyBootnodes - case params.GoerliChainName: + case networkname.GoerliChainName: urls = params.GoerliBootnodes - case params.ErigonMineName: + case networkname.ErigonMineName: urls = params.ErigonBootnodes - case params.SokolChainName: + case networkname.SokolChainName: urls = params.SokolBootnodes - case params.KovanChainName: + case networkname.KovanChainName: urls = params.KovanBootnodes - case params.FermionChainName: + case networkname.FermionChainName: urls = params.FermionBootnodes default: if cfg.BootstrapNodes != nil { @@ -635,19 +641,19 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { chain := ctx.GlobalString(ChainFlag.Name) switch chain { - case params.RopstenChainName: + case networkname.RopstenChainName: urls = params.RopstenBootnodes - case params.RinkebyChainName: + case networkname.RinkebyChainName: urls = params.RinkebyBootnodes - case params.GoerliChainName: + case networkname.GoerliChainName: urls = params.GoerliBootnodes - case params.ErigonMineName: + case networkname.ErigonMineName: urls = params.ErigonBootnodes - case params.SokolChainName: + case networkname.SokolChainName: urls = params.SokolBootnodes - case params.KovanChainName: + case networkname.KovanChainName: urls = params.KovanBootnodes - case params.FermionChainName: + case networkname.FermionChainName: urls = params.FermionBootnodes default: if cfg.BootstrapNodesV5 != nil { @@ -821,7 +827,7 @@ func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) { } } - if ctx.GlobalString(ChainFlag.Name) == params.DevChainName { + if ctx.GlobalString(ChainFlag.Name) == networkname.DevChainName { if etherbase == "" { cfg.Miner.SigKey = core.DevnetSignPrivateKey cfg.Miner.Etherbase = core.DevnetEtherbase @@ -829,9 +835,9 @@ func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) { setSigKey(ctx, cfg) } - if ctx.GlobalString(ChainFlag.Name) == params.FermionChainName { + if ctx.GlobalString(ChainFlag.Name) == networkname.FermionChainName { if ctx.GlobalIsSet(MiningEnabledFlag.Name) && !ctx.GlobalIsSet(MinerSigningKeyFileFlag.Name) { - panic(fmt.Sprintf("Flag --%s is required in %s chain with --%s flag", MinerSigningKeyFileFlag.Name, params.FermionChainName, MiningEnabledFlag.Name)) + panic(fmt.Sprintf("Flag --%s is required in %s chain with --%s flag", MinerSigningKeyFileFlag.Name, networkname.FermionChainName, MiningEnabledFlag.Name)) } setSigKey(ctx, cfg) if cfg.Miner.SigKey != nil { @@ -876,7 +882,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config, nodeName, dataDir string) { cfg.NetRestrict = list } - if ctx.GlobalString(ChainFlag.Name) == params.DevChainName { + if ctx.GlobalString(ChainFlag.Name) == networkname.DevChainName { // --dev mode can't use p2p networking. // cfg.MaxPeers = 0 // It can have peers otherwise local sync is not possible cfg.ListenAddr = ":0" @@ -890,6 +896,8 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { setDataDir(ctx, cfg) setNodeUserIdent(ctx, cfg) SetP2PConfig(ctx, &cfg.P2P, cfg.NodeName(), cfg.DataDir) + + cfg.DownloaderAddr = strings.TrimSpace(ctx.GlobalString(DownloaderAddrFlag.Name)) } func SetNodeConfigCobra(cmd *cobra.Command, cfg *node.Config) { @@ -905,17 +913,17 @@ func DataDirForNetwork(datadir string, network string) string { } switch network { - case params.DevChainName: + case networkname.DevChainName: return "" // unless explicitly requested, use memory databases - case params.RinkebyChainName: + case networkname.RinkebyChainName: return filepath.Join(datadir, "rinkeby") - case params.GoerliChainName: + case networkname.GoerliChainName: filepath.Join(datadir, "goerli") - case params.SokolChainName: + case networkname.SokolChainName: return filepath.Join(datadir, "sokol") - case params.KovanChainName: + case networkname.KovanChainName: return filepath.Join(datadir, "kovan") - case params.FermionChainName: + case networkname.FermionChainName: return filepath.Join(datadir, "fermion") default: return datadir @@ -1264,51 +1272,51 @@ func SetEthConfig(ctx *cli.Context, nodeConfig *node.Config, cfg *ethconfig.Conf if cfg.NetworkID == 1 { SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) } - case params.MainnetChainName: + case networkname.MainnetChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 1 } cfg.Genesis = core.DefaultGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) - case params.RopstenChainName: + case networkname.RopstenChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 3 } cfg.Genesis = core.DefaultRopstenGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash) - case params.RinkebyChainName: + case networkname.RinkebyChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 4 } cfg.Genesis = core.DefaultRinkebyGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash) - case params.GoerliChainName: + case networkname.GoerliChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 5 } cfg.Genesis = core.DefaultGoerliGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) - case params.ErigonMineName: + case networkname.ErigonMineName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = new(big.Int).SetBytes([]byte("erigon-mine")).Uint64() // erigon-mine } cfg.Genesis = core.DefaultErigonGenesisBlock() - case params.SokolChainName: + case networkname.SokolChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 77 } cfg.Genesis = core.DefaultSokolGenesisBlock() - case params.KovanChainName: + case networkname.KovanChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 42 } cfg.Genesis = core.DefaultKovanGenesisBlock() - case params.FermionChainName: + case networkname.FermionChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 1212120 } cfg.Genesis = core.DefaultFermionGenesisBlock() - case params.DevChainName: + case networkname.DevChainName: if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkID = 1337 } @@ -1372,21 +1380,21 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis chain := ctx.GlobalString(ChainFlag.Name) switch chain { - case params.RopstenChainName: + case networkname.RopstenChainName: genesis = core.DefaultRopstenGenesisBlock() - case params.RinkebyChainName: + case networkname.RinkebyChainName: genesis = core.DefaultRinkebyGenesisBlock() - case params.GoerliChainName: + case networkname.GoerliChainName: genesis = core.DefaultGoerliGenesisBlock() - case params.ErigonMineName: + case networkname.ErigonMineName: genesis = core.DefaultErigonGenesisBlock() - case params.SokolChainName: + case networkname.SokolChainName: genesis = core.DefaultSokolGenesisBlock() - case params.KovanChainName: + case networkname.KovanChainName: genesis = core.DefaultKovanGenesisBlock() - case params.FermionChainName: + case networkname.FermionChainName: genesis = core.DefaultFermionGenesisBlock() - case params.DevChainName: + case networkname.DevChainName: Fatalf("Developer chains are ephemeral") } return genesis diff --git a/consensus/aura/consensusconfig/embed.go b/consensus/aura/consensusconfig/embed.go index 883c4c3d04e..48deb45aa21 100644 --- a/consensus/aura/consensusconfig/embed.go +++ b/consensus/aura/consensusconfig/embed.go @@ -2,7 +2,8 @@ package consensusconfig import ( _ "embed" - "github.com/ledgerwatch/erigon/params" + + "github.com/ledgerwatch/erigon/params/networkname" ) //go:embed poasokol.json @@ -13,9 +14,9 @@ var Kovan []byte func GetConfigByChain(chainName string) []byte { switch chainName { - case params.SokolChainName: + case networkname.SokolChainName: return Sokol - case params.KovanChainName: + case networkname.KovanChainName: return Kovan default: return Sokol diff --git a/eth/backend.go b/eth/backend.go index a6ca8aa7a64..9a64a6a864b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -34,9 +34,10 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/direct" "github.com/ledgerwatch/erigon-lib/etl" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" "github.com/ledgerwatch/erigon-lib/gointerfaces/grpcutil" "github.com/ledgerwatch/erigon-lib/gointerfaces/remote" - "github.com/ledgerwatch/erigon-lib/gointerfaces/sentry" + proto_sentry "github.com/ledgerwatch/erigon-lib/gointerfaces/sentry" txpool_proto "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool" prototypes "github.com/ledgerwatch/erigon-lib/gointerfaces/types" "github.com/ledgerwatch/erigon-lib/kv" @@ -44,8 +45,9 @@ import ( "github.com/ledgerwatch/erigon-lib/kv/remotedbserver" txpool2 "github.com/ledgerwatch/erigon-lib/txpool" "github.com/ledgerwatch/erigon-lib/txpool/txpooluitl" + "github.com/ledgerwatch/erigon/cmd/downloader/downloadergrpc" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces" - "github.com/ledgerwatch/erigon/cmd/sentry/download" + "github.com/ledgerwatch/erigon/cmd/sentry/sentry" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/debug" "github.com/ledgerwatch/erigon/consensus" @@ -68,6 +70,7 @@ import ( "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/shards" "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" stages2 "github.com/ledgerwatch/erigon/turbo/stages" "github.com/ledgerwatch/log/v3" "google.golang.org/grpc" @@ -103,12 +106,15 @@ type Ethereum struct { minedBlocks chan *types.Block // downloader fields - downloadCtx context.Context - downloadCancel context.CancelFunc - downloadServer *download.ControlServerImpl - sentryServers []*download.SentryServerImpl - sentries []direct.SentryClient - stagedSync *stagedsync.Sync + sentryCtx context.Context + sentryCancel context.CancelFunc + sentryControlServer *sentry.ControlServerImpl + sentryServers []*sentry.SentryServerImpl + sentries []direct.SentryClient + + stagedSync *stagedsync.Sync + + downloaderClient proto_downloader.DownloaderClient notifications *stagedsync.Notifications @@ -172,8 +178,8 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere ctx, ctxCancel := context.WithCancel(context.Background()) kvRPC := remotedbserver.NewKvServer(ctx, chainKv) backend := &Ethereum{ - downloadCtx: ctx, - downloadCancel: ctxCancel, + sentryCtx: ctx, + sentryCancel: ctxCancel, config: config, logger: logger, chainDB: chainKv, @@ -243,7 +249,7 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere if len(stack.Config().P2P.SentryAddr) > 0 { for _, addr := range stack.Config().P2P.SentryAddr { - sentryClient, err := download.GrpcSentryClient(backend.downloadCtx, addr) + sentryClient, err := sentry.GrpcClient(backend.sentryCtx, addr) if err != nil { return nil, err } @@ -267,7 +273,7 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere cfg66 := stack.Config().P2P cfg66.NodeDatabase = path.Join(stack.Config().DataDir, "nodes", "eth66") - server66 := download.NewSentryServer(backend.downloadCtx, d66, readNodeInfo, &cfg66, eth.ETH66) + server66 := sentry.NewSentryServer(backend.sentryCtx, d66, readNodeInfo, &cfg66, eth.ETH66) backend.sentryServers = append(backend.sentryServers, server66) backend.sentries = []direct.SentryClient{direct.NewSentryClientDirect(eth.ETH66, server66)} @@ -279,7 +285,7 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere for { select { - case <-backend.downloadCtx.Done(): + case <-backend.sentryCtx.Done(): return case <-logEvery.C: logItems = logItems[:0] @@ -291,7 +297,7 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere } }() } - backend.downloadServer, err = download.NewControlServer(chainKv, stack.Config().NodeName(), chainConfig, genesis.Hash(), backend.engine, backend.config.NetworkID, backend.sentries, config.BlockDownloaderWindow) + backend.sentryControlServer, err = sentry.NewControlServer(chainKv, stack.Config().NodeName(), chainConfig, genesis.Hash(), backend.engine, backend.config.NetworkID, backend.sentries, config.BlockDownloaderWindow) if err != nil { return nil, err } @@ -340,17 +346,23 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere var blockReader interfaces.FullBlockReader if config.Snapshot.Enabled { - allSnapshots := snapshotsync.NewAllSnapshots(config.Snapshot.Dir, params.KnownSnapshots(chainConfig.ChainName)) + allSnapshots := snapshotsync.NewAllSnapshots(config.Snapshot.Dir, snapshothashes.KnownConfig(chainConfig.ChainName)) if err != nil { return nil, err } blockReader = snapshotsync.NewBlockReaderWithSnapshots(allSnapshots) + + // connect to Downloader + backend.downloaderClient, err = downloadergrpc.NewClient(ctx, stack.Config().DownloaderAddr) + if err != nil { + return nil, err + } } else { blockReader = snapshotsync.NewBlockReader() } mining := stagedsync.New( - stagedsync.MiningStages(backend.downloadCtx, + stagedsync.MiningStages(backend.sentryCtx, stagedsync.StageMiningCreateBlockCfg(backend.chainDB, miner, *backend.chainConfig, backend.engine, backend.txPool2, backend.txPool2DB, tmpdir), stagedsync.StageMiningExecCfg(backend.chainDB, miner, backend.notifications.Events, *backend.chainConfig, backend.engine, &vm.Config{}, tmpdir), stagedsync.StageHashStateCfg(backend.chainDB, tmpdir), @@ -391,7 +403,7 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere if !config.TxPool.Disable { backend.txPool2Fetch.ConnectCore() backend.txPool2Fetch.ConnectSentries() - go txpool2.MainLoop(backend.downloadCtx, + go txpool2.MainLoop(backend.sentryCtx, backend.txPool2DB, backend.chainDB, backend.txPool2, backend.newTxs2, backend.txPool2Send, backend.txPool2GrpcServer.NewSlotsStreams, func() { @@ -407,15 +419,15 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere select { case b := <-backend.minedBlocks: //p2p - //backend.downloadServer.BroadcastNewBlock(context.Background(), b, b.Difficulty()) + //backend.sentryControlServer.BroadcastNewBlock(context.Background(), b, b.Difficulty()) //rpcdaemon if err := miningRPC.(*privateapi.MiningServer).BroadcastMinedBlock(b); err != nil { log.Error("txpool rpc mined block broadcast", "err", err) } - if err := backend.downloadServer.Hd.AddMinedHeader(b.Header()); err != nil { + if err := backend.sentryControlServer.Hd.AddMinedHeader(b.Header()); err != nil { log.Error("add mined block to header downloader", "err", err) } - if err := backend.downloadServer.Bd.AddMinedBlock(b); err != nil { + if err := backend.sentryControlServer.Bd.AddMinedBlock(b); err != nil { log.Error("add mined block to body downloader", "err", err) } @@ -433,7 +445,11 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere return nil, err } - backend.stagedSync, err = stages2.NewStagedSync(backend.downloadCtx, backend.logger, backend.chainDB, stack.Config().P2P, *config, chainConfig.TerminalTotalDifficulty, backend.downloadServer, tmpdir, backend.notifications.Accumulator, backend.reverseDownloadCh, backend.statusCh, &backend.waitingForPOSHeaders) + backend.stagedSync, err = stages2.NewStagedSync(backend.sentryCtx, backend.logger, backend.chainDB, + stack.Config().P2P, *config, chainConfig.TerminalTotalDifficulty, + backend.sentryControlServer, tmpdir, backend.notifications.Accumulator, + backend.reverseDownloadCh, backend.statusCh, &backend.waitingForPOSHeaders, + backend.downloaderClient) if err != nil { return nil, err } @@ -552,7 +568,7 @@ func (s *Ethereum) StartMining(ctx context.Context, db kv.RwDB, mining *stagedsy defer skipCycleEvery.Stop() for range skipCycleEvery.C { select { - case s.downloadServer.Hd.SkipCycleHack <- struct{}{}: + case s.sentryControlServer.Hd.SkipCycleHack <- struct{}{}: default: } } @@ -610,7 +626,7 @@ func (s *Ethereum) NetPeerCount() (uint64, error) { log.Trace("sentry", "peer count", sentryPc) for _, sc := range s.sentries { ctx := context.Background() - reply, err := sc.PeerCount(ctx, &sentry.PeerCountRequest{}) + reply, err := sc.PeerCount(ctx, &proto_sentry.PeerCountRequest{}) if err != nil { log.Warn("sentry", "err", err) return 0, nil @@ -659,17 +675,17 @@ func (s *Ethereum) Protocols() []p2p.Protocol { func (s *Ethereum) Start() error { for i := range s.sentries { go func(i int) { - download.RecvMessageLoop(s.downloadCtx, s.sentries[i], s.downloadServer, nil) + sentry.RecvMessageLoop(s.sentryCtx, s.sentries[i], s.sentryControlServer, nil) }(i) go func(i int) { - download.RecvUploadMessageLoop(s.downloadCtx, s.sentries[i], s.downloadServer, nil) + sentry.RecvUploadMessageLoop(s.sentryCtx, s.sentries[i], s.sentryControlServer, nil) }(i) go func(i int) { - download.RecvUploadHeadersMessageLoop(s.downloadCtx, s.sentries[i], s.downloadServer, nil) + sentry.RecvUploadHeadersMessageLoop(s.sentryCtx, s.sentries[i], s.sentryControlServer, nil) }(i) } - go stages2.StageLoop(s.downloadCtx, s.chainDB, s.stagedSync, s.downloadServer.Hd, s.notifications, s.downloadServer.UpdateHead, s.waitForStageLoopStop, s.config.SyncLoopThrottle) + go stages2.StageLoop(s.sentryCtx, s.chainDB, s.stagedSync, s.sentryControlServer.Hd, s.notifications, s.sentryControlServer.UpdateHead, s.waitForStageLoopStop, s.config.SyncLoopThrottle) return nil } @@ -678,7 +694,7 @@ func (s *Ethereum) Start() error { // Ethereum protocol. func (s *Ethereum) Stop() error { // Stop all the peer-related stuff first. - s.downloadCancel() + s.sentryCancel() if s.privateAPI != nil { shutdownDone := make(chan bool) go func() { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 02aaa207cae..3e6df2d4f61 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -31,6 +31,7 @@ import ( "github.com/ledgerwatch/erigon/consensus/aura/consensusconfig" "github.com/ledgerwatch/erigon/consensus/serenity" "github.com/ledgerwatch/erigon/ethdb/prune" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/consensus" @@ -119,7 +120,7 @@ func init() { type Snapshot struct { Enabled bool Dir string - ChainSnapshotConfig *params.SnapshotsConfig + ChainSnapshotConfig *snapshothashes.Config } // Config contains configuration options for ETH protocol. diff --git a/eth/stagedsync/stage_headers.go b/eth/stagedsync/stage_headers.go index 7adcf17c74c..5f25739dbd6 100644 --- a/eth/stagedsync/stage_headers.go +++ b/eth/stagedsync/stage_headers.go @@ -12,7 +12,9 @@ import ( "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/etl" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon/cmd/downloader/downloadergrpc" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/dbutils" @@ -24,8 +26,8 @@ import ( "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/ledgerwatch/erigon/turbo/stages/headerdownload" - "github.com/ledgerwatch/log/v3" ) @@ -42,8 +44,10 @@ type HeadersCfg struct { tmpdir string reverseDownloadCh chan types.Header waitingPosHeaders *bool - snapshots *snapshotsync.AllSnapshots - blockReader interfaces.FullBlockReader + + snapshots *snapshotsync.AllSnapshots + snapshotDownloader proto_downloader.DownloaderClient + blockReader interfaces.FullBlockReader } func StageHeadersCfg( @@ -59,23 +63,25 @@ func StageHeadersCfg( reverseDownloadCh chan types.Header, waitingPosHeaders *bool, snapshots *snapshotsync.AllSnapshots, + snapshotDownloader proto_downloader.DownloaderClient, blockReader interfaces.FullBlockReader, tmpdir string, ) HeadersCfg { return HeadersCfg{ - db: db, - hd: headerDownload, - statusCh: statusCh, - chainConfig: chainConfig, - headerReqSend: headerReqSend, - announceNewHashes: announceNewHashes, - penalize: penalize, - batchSize: batchSize, - noP2PDiscovery: noP2PDiscovery, - reverseDownloadCh: reverseDownloadCh, - waitingPosHeaders: waitingPosHeaders, - snapshots: snapshots, - blockReader: blockReader, + db: db, + hd: headerDownload, + statusCh: statusCh, + chainConfig: chainConfig, + headerReqSend: headerReqSend, + announceNewHashes: announceNewHashes, + penalize: penalize, + batchSize: batchSize, + noP2PDiscovery: noP2PDiscovery, + reverseDownloadCh: reverseDownloadCh, + waitingPosHeaders: waitingPosHeaders, + snapshots: snapshots, + snapshotDownloader: snapshotDownloader, + blockReader: blockReader, } } @@ -291,123 +297,11 @@ func HeadersPOW( test bool, // Set to true in tests, allows the stage to fail rather than wait indefinitely useExternalTx bool, ) error { - var headerProgress uint64 - - if cfg.snapshots != nil { - if !cfg.snapshots.AllSegmentsAvailable() { - // wait for Downloader service to download all expected snapshots - logEvery := time.NewTicker(logInterval) - defer logEvery.Stop() - for { - headers, bodies, txs, err := cfg.snapshots.SegmentsAvailability() - if err != nil { - return err - } - expect := cfg.snapshots.ChainSnapshotConfig().ExpectBlocks - if headers >= expect && bodies >= expect && txs >= expect { - if err := cfg.snapshots.ReopenSegments(); err != nil { - return err - } - if expect > cfg.snapshots.BlocksAvailable() { - return fmt.Errorf("not enough snapshots available: %d > %d", expect, cfg.snapshots.BlocksAvailable()) - } - cfg.snapshots.SetAllSegmentsAvailable(true) - - break - } - log.Info(fmt.Sprintf("[%s] Waiting for snapshots up to block %d...", s.LogPrefix(), expect), "headers", headers, "bodies", bodies, "txs", txs) - time.Sleep(10 * time.Second) - - select { - case <-ctx.Done(): - return ctx.Err() - case <-logEvery.C: - log.Info(fmt.Sprintf("[%s] Waiting for snapshots up to block %d...", s.LogPrefix(), expect), "headers", headers, "bodies", bodies, "txs", txs) - default: - } - } - } - - if !cfg.snapshots.AllIdxAvailable() { - if !cfg.snapshots.AllSegmentsAvailable() { - return fmt.Errorf("not all snapshot segments are available") - } - - // wait for Downloader service to download all expected snapshots - logEvery := time.NewTicker(logInterval) - defer logEvery.Stop() - headers, bodies, txs, err := cfg.snapshots.IdxAvailability() - if err != nil { - return err - } - expect := cfg.snapshots.ChainSnapshotConfig().ExpectBlocks - if headers < expect || bodies < expect || txs < expect { - chainID, _ := uint256.FromBig(cfg.chainConfig.ChainID) - if err := cfg.snapshots.BuildIndices(ctx, *chainID); err != nil { - return err - } - } - - if err := cfg.snapshots.ReopenIndices(); err != nil { - return err - } - if expect > cfg.snapshots.IndicesAvailable() { - return fmt.Errorf("not enough snapshots available: %d > %d", expect, cfg.snapshots.BlocksAvailable()) - } - cfg.snapshots.SetAllIdxAvailable(true) - } - - c, _ := tx.Cursor(kv.HeaderTD) - count, _ := c.Count() - if count == 0 || count == 1 { // genesis does write 1 record - logEvery := time.NewTicker(logInterval) - defer logEvery.Stop() - - tx.ClearBucket(kv.HeaderTD) - var lastHeader *types.Header - //total difficulty write - td := big.NewInt(0) - if err := snapshotsync.ForEachHeader(cfg.snapshots, func(header *types.Header) error { - td.Add(td, header.Difficulty) - /* - if header.Eip3675 { - return nil - } - if td.Cmp(cfg.terminalTotalDifficulty) > 0 { - return rawdb.MarkTransition(tx, blockNum) - } - */ - // TODO: append - rawdb.WriteTd(tx, header.Hash(), header.Number.Uint64(), td) - lastHeader = header - select { - case <-ctx.Done(): - return ctx.Err() - case <-logEvery.C: - log.Info(fmt.Sprintf("[%s] Writing total difficulty index for snapshots", s.LogPrefix()), "block_num", header.Number.Uint64()) - default: - } - return nil - }); err != nil { - return err - } - tx.ClearBucket(kv.HeaderCanonical) - if err := fixCanonicalChain(s.LogPrefix(), logEvery, lastHeader.Number.Uint64(), lastHeader.Hash(), tx, cfg.blockReader); err != nil { - return err - } - } - - if s.BlockNumber < cfg.snapshots.BlocksAvailable() { - if err := cfg.hd.AddHeaderFromSnapshot(cfg.snapshots.BlocksAvailable(), cfg.blockReader); err != nil { - return err - } - if err := s.Update(tx, cfg.snapshots.BlocksAvailable()); err != nil { - return err - } - s.BlockNumber = cfg.snapshots.BlocksAvailable() - } + if err := DownloadAndIndexSnapshotsIfNeed(s, ctx, tx, cfg); err != nil { + return err } + var headerProgress uint64 var err error if !useExternalTx { @@ -452,6 +346,9 @@ func HeadersPOW( if err != nil { return err } + if localTd == nil { + return fmt.Errorf("localTD is nil: %d, %x", headerProgress, hash) + } headerInserter := headerdownload.NewHeaderInserter(logPrefix, localTd, headerProgress) cfg.hd.SetHeaderReader(&chainReader{config: &cfg.chainConfig, tx: tx, blockReader: cfg.blockReader}) @@ -812,3 +709,201 @@ func HeadersPrune(p *PruneState, tx kv.RwTx, cfg HeadersCfg, ctx context.Context } return nil } + +func DownloadAndIndexSnapshotsIfNeed(s *StageState, ctx context.Context, tx kv.RwTx, cfg HeadersCfg) error { + if cfg.snapshots == nil { + return nil + } + + // TODO: save AllSegmentsAvailable flag to DB? (to allow Erigon start without Downloader) + if !cfg.snapshots.AllSegmentsAvailable() { + if err := WaitForDownloader(ctx, tx, cfg); err != nil { + return err + } + + logEvery := time.NewTicker(logInterval) + defer logEvery.Stop() + + // Open segments + for { + headers, bodies, txs, err := cfg.snapshots.SegmentsAvailability() + if err != nil { + return err + } + expect := cfg.snapshots.ChainSnapshotConfig().ExpectBlocks + if headers >= expect && bodies >= expect && txs >= expect { + if err := cfg.snapshots.ReopenSegments(); err != nil { + return err + } + if expect > cfg.snapshots.BlocksAvailable() { + return fmt.Errorf("not enough snapshots available: %d > %d", expect, cfg.snapshots.BlocksAvailable()) + } + cfg.snapshots.SetAllSegmentsAvailable(true) + + break + } + log.Info(fmt.Sprintf("[%s] Waiting for snapshots up to block %d...", s.LogPrefix(), expect), "headers", headers, "bodies", bodies, "txs", txs) + time.Sleep(10 * time.Second) + + select { + case <-ctx.Done(): + return ctx.Err() + case <-logEvery.C: + log.Info(fmt.Sprintf("[%s] Waiting for snapshots up to block %d...", s.LogPrefix(), expect), "headers", headers, "bodies", bodies, "txs", txs) + default: + } + } + } + + // Create .idx files + if !cfg.snapshots.AllIdxAvailable() { + if !cfg.snapshots.AllSegmentsAvailable() { + return fmt.Errorf("not all snapshot segments are available") + } + + // wait for Downloader service to download all expected snapshots + logEvery := time.NewTicker(logInterval) + defer logEvery.Stop() + headers, bodies, txs, err := cfg.snapshots.IdxAvailability() + if err != nil { + return err + } + expect := cfg.snapshots.ChainSnapshotConfig().ExpectBlocks + if headers < expect || bodies < expect || txs < expect { + chainID, _ := uint256.FromBig(cfg.chainConfig.ChainID) + if err := cfg.snapshots.BuildIndices(ctx, *chainID); err != nil { + return err + } + } + + if err := cfg.snapshots.ReopenIndices(); err != nil { + return err + } + if expect > cfg.snapshots.IndicesAvailable() { + return fmt.Errorf("not enough snapshots available: %d > %d", expect, cfg.snapshots.BlocksAvailable()) + } + cfg.snapshots.SetAllIdxAvailable(true) + } + + // Fill kv.HeaderTD table from snapshots + c, _ := tx.Cursor(kv.HeaderTD) + count, _ := c.Count() + if count == 0 || count == 1 { // genesis does write 1 record + logEvery := time.NewTicker(logInterval) + defer logEvery.Stop() + + tx.ClearBucket(kv.HeaderTD) + var lastHeader *types.Header + //total difficulty write + td := big.NewInt(0) + if err := snapshotsync.ForEachHeader(cfg.snapshots, func(header *types.Header) error { + td.Add(td, header.Difficulty) + /* + if header.Eip3675 { + return nil + } + + if td.Cmp(cfg.terminalTotalDifficulty) > 0 { + return rawdb.MarkTransition(tx, blockNum) + } + */ + // TODO: append + rawdb.WriteTd(tx, header.Hash(), header.Number.Uint64(), td) + lastHeader = header + select { + case <-ctx.Done(): + return ctx.Err() + case <-logEvery.C: + log.Info(fmt.Sprintf("[%s] Writing total difficulty index for snapshots", s.LogPrefix()), "block_num", header.Number.Uint64()) + default: + } + return nil + }); err != nil { + return err + } + + // Fill kv.HeaderCanonical table from snapshots + tx.ClearBucket(kv.HeaderCanonical) + if err := fixCanonicalChain(s.LogPrefix(), logEvery, lastHeader.Number.Uint64(), lastHeader.Hash(), tx, cfg.blockReader); err != nil { + return err + } + + sn, ok := cfg.snapshots.Blocks(cfg.snapshots.BlocksAvailable()) + if !ok { + return fmt.Errorf("snapshot not found for block: %d", cfg.snapshots.BlocksAvailable()) + } + + // ResetSequence - allow set arbitrary value to sequence (for example to decrement it to exact value) + lastTxnID := sn.Transactions.Idx.BaseDataID() + uint64(sn.Transactions.Segment.Count()) + if err := rawdb.ResetSequence(tx, kv.EthTx, lastTxnID+1); err != nil { + return err + } + } + + // Add last headers from snapshots to HeaderDownloader (as persistent links) + if s.BlockNumber < cfg.snapshots.BlocksAvailable() { + if err := cfg.hd.AddHeaderFromSnapshot(cfg.snapshots.BlocksAvailable(), cfg.blockReader); err != nil { + return err + } + if err := s.Update(tx, cfg.snapshots.BlocksAvailable()); err != nil { + return err + } + s.BlockNumber = cfg.snapshots.BlocksAvailable() + } + + return nil +} + +// WaitForDownloader - wait for Downloader service to download all expected snapshots +// for MVP we sync with Downloader only once, in future will send new snapshots also +func WaitForDownloader(ctx context.Context, tx kv.RwTx, cfg HeadersCfg) error { + const readyKey = "snapshots_ready" + v, err := tx.GetOne(kv.DatabaseInfo, []byte(readyKey)) + if err != nil { + return err + } + if len(v) == 1 && v[0] == 1 { + return nil + } + snapshotsCfg := snapshothashes.KnownConfig(cfg.chainConfig.ChainName) + + // send all hashes to the Downloader service + preverified := snapshotsCfg.Preverified + req := &proto_downloader.DownloadRequest{Items: make([]*proto_downloader.DownloadItem, len(preverified))} + i := 0 + for filePath, infoHashStr := range preverified { + req.Items[i] = &proto_downloader.DownloadItem{ + TorrentHash: downloadergrpc.String2Proto(infoHashStr), + Path: filePath, + } + i++ + } + for { + if _, err := cfg.snapshotDownloader.Download(ctx, req); err != nil { + log.Error("[Snapshots] Can't call downloader", "err", err) + time.Sleep(10 * time.Second) + continue + } + break + } + + // Print download progress until all segments are available + for { + if reply, err := cfg.snapshotDownloader.Stats(ctx, &proto_downloader.StatsRequest{}); err != nil { + log.Warn("Error while waiting for snapshots progress", "err", err) + } else if int(reply.Torrents) < len(snapshotsCfg.Preverified) { + log.Warn("Downloader has not enough snapshots (yet)") + } else if reply.Completed { + break + } else { + readiness := int32(100 * (float64(reply.BytesCompleted) / float64(reply.BytesTotal))) + log.Info("[Snapshots] download", "progress", fmt.Sprintf("%d%%", readiness)) + } + time.Sleep(10 * time.Second) + } + + if err := tx.Put(kv.DatabaseInfo, []byte(readyKey), []byte{1}); err != nil { + return err + } + return nil +} diff --git a/go.mod b/go.mod index 6ce78993fc6..9dbbdb79c67 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/RoaringBitmap/roaring v0.9.4 github.com/VictoriaMetrics/fastcache v1.7.0 github.com/VictoriaMetrics/metrics v1.18.1 + github.com/anacrolix/go-libutp v1.0.4 github.com/anacrolix/log v0.10.0 github.com/anacrolix/torrent v1.38.0 github.com/btcsuite/btcd v0.21.0-beta @@ -15,6 +16,7 @@ require ( github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 + github.com/dustin/go-humanize v1.0.0 github.com/edsrzf/mmap-go v1.0.0 github.com/emicklei/dot v0.16.0 github.com/fatih/color v1.12.0 @@ -35,7 +37,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/kevinburke/go-bindata v3.21.0+incompatible - github.com/ledgerwatch/erigon-lib v0.0.0-20211206140018-b06f3cec6bfb + github.com/ledgerwatch/erigon-lib v0.0.0-20211213041304-7fb44e022d47 github.com/ledgerwatch/log/v3 v3.4.0 github.com/ledgerwatch/secp256k1 v1.0.0 github.com/logrusorgru/aurora/v3 v3.0.0 diff --git a/go.sum b/go.sum index 8307befc2b1..22d599ed2ef 100644 --- a/go.sum +++ b/go.sum @@ -617,8 +617,8 @@ github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758 h1:0D5M2HQSGD3P github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/ledgerwatch/erigon-lib v0.0.0-20211206140018-b06f3cec6bfb h1:bN+YFj+BSV2sMFl+TR3tPS82D7aq+2OHYRk44wwwtYk= -github.com/ledgerwatch/erigon-lib v0.0.0-20211206140018-b06f3cec6bfb/go.mod h1:lyGP3i0x4CeabdKZ4beycD5xZfHWZwJsAX+70OfGj4Y= +github.com/ledgerwatch/erigon-lib v0.0.0-20211213041304-7fb44e022d47 h1:Y4OJ9Z1RNNJ9GceKcFC8JVruvgrAFg+6NJr9O1qoRnU= +github.com/ledgerwatch/erigon-lib v0.0.0-20211213041304-7fb44e022d47/go.mod h1:lyGP3i0x4CeabdKZ4beycD5xZfHWZwJsAX+70OfGj4Y= github.com/ledgerwatch/log/v3 v3.4.0 h1:SEIOcv5a2zkG3PmoT5jeTU9m/0nEUv0BJS5bzsjwKCI= github.com/ledgerwatch/log/v3 v3.4.0/go.mod h1:VXcz6Ssn6XEeU92dCMc39/g1F0OYAjw1Mt+dGP5DjXY= github.com/ledgerwatch/secp256k1 v1.0.0 h1:Usvz87YoTG0uePIV8woOof5cQnLXGYa162rFf3YnwaQ= diff --git a/node/config.go b/node/config.go index d1dd53d37e3..42f29409a74 100644 --- a/node/config.go +++ b/node/config.go @@ -69,6 +69,8 @@ type Config struct { // Configuration of peer-to-peer networking. P2P p2p.Config + DownloaderAddr string + // IPCPath is the requested location to place the IPC endpoint. If the path is // a simple file name, it is placed inside the data directory (or on the root // pipe path on Windows), whereas if it's a resolvable path name (absolute or diff --git a/params/config.go b/params/config.go index 5c9dff72b40..e24152259f0 100644 --- a/params/config.go +++ b/params/config.go @@ -23,18 +23,7 @@ import ( "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/paths" -) - -const ( - MainnetChainName = "mainnet" - RopstenChainName = "ropsten" - RinkebyChainName = "rinkeby" - GoerliChainName = "goerli" - DevChainName = "dev" - ErigonMineName = "erigonmine" - SokolChainName = "sokol" - KovanChainName = "kovan" - FermionChainName = "fermion" + "github.com/ledgerwatch/erigon/params/networkname" ) type ConsensusType string @@ -70,7 +59,7 @@ var ( var ( // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ - ChainName: MainnetChainName, + ChainName: networkname.MainnetChainName, ChainID: big.NewInt(1), Consensus: EtHashConsensus, HomesteadBlock: big.NewInt(1_150_000), @@ -94,7 +83,7 @@ var ( // RopstenChainConfig contains the chain parameters to run a node on the Ropsten test network. RopstenChainConfig = &ChainConfig{ - ChainName: RopstenChainName, + ChainName: networkname.RopstenChainName, ChainID: big.NewInt(3), Consensus: EtHashConsensus, HomesteadBlock: big.NewInt(0), @@ -117,7 +106,7 @@ var ( // RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network. RinkebyChainConfig = &ChainConfig{ - ChainName: RinkebyChainName, + ChainName: networkname.RinkebyChainName, ChainID: big.NewInt(4), Consensus: CliqueConsensus, HomesteadBlock: big.NewInt(1), @@ -143,7 +132,7 @@ var ( // GoerliChainConfig contains the chain parameters to run a node on the Görli test network. GoerliChainConfig = &ChainConfig{ - ChainName: GoerliChainName, + ChainName: networkname.GoerliChainName, ChainID: big.NewInt(5), Consensus: CliqueConsensus, HomesteadBlock: big.NewInt(0), @@ -167,7 +156,7 @@ var ( // MainnetChainConfig is the chain parameters to run a PoW dev net to test Erigon mining ErigonChainConfig = &ChainConfig{ - ChainName: ErigonMineName, + ChainName: networkname.ErigonMineName, ChainID: new(big.Int).SetBytes([]byte("erigon-mine")), Consensus: EtHashConsensus, HomesteadBlock: big.NewInt(0), @@ -188,7 +177,7 @@ var ( } SokolChainConfig = &ChainConfig{ - ChainName: SokolChainName, + ChainName: networkname.SokolChainName, ChainID: big.NewInt(77), Consensus: AuRaConsensus, HomesteadBlock: big.NewInt(0), @@ -252,7 +241,7 @@ var ( } KovanChainConfig = &ChainConfig{ - ChainName: KovanChainName, + ChainName: networkname.KovanChainName, ChainID: big.NewInt(42), Consensus: AuRaConsensus, HomesteadBlock: big.NewInt(0), @@ -273,7 +262,7 @@ var ( } FermionChainConfig = &ChainConfig{ - ChainName: FermionChainName, + ChainName: networkname.FermionChainName, ChainID: big.NewInt(1212120), Consensus: CliqueConsensus, HomesteadBlock: big.NewInt(0), diff --git a/params/networkname/network_name.go b/params/networkname/network_name.go new file mode 100644 index 00000000000..9766c6add21 --- /dev/null +++ b/params/networkname/network_name.go @@ -0,0 +1,13 @@ +package networkname + +const ( + MainnetChainName = "mainnet" + RopstenChainName = "ropsten" + RinkebyChainName = "rinkeby" + GoerliChainName = "goerli" + DevChainName = "dev" + ErigonMineName = "erigonmine" + SokolChainName = "sokol" + KovanChainName = "kovan" + FermionChainName = "fermion" +) diff --git a/params/snapshots.go b/params/snapshots.go deleted file mode 100644 index 3edca1a112e..00000000000 --- a/params/snapshots.go +++ /dev/null @@ -1,23 +0,0 @@ -package params - -var ( - MainnetChainSnapshotConfig = &SnapshotsConfig{} - GoerliChainSnapshotConfig = &SnapshotsConfig{ - ExpectBlocks: 5_900_000 - 1, - } -) - -type SnapshotsConfig struct { - ExpectBlocks uint64 -} - -func KnownSnapshots(networkName string) *SnapshotsConfig { - switch networkName { - case MainnetChainName: - return MainnetChainSnapshotConfig - case GoerliChainName: - return GoerliChainSnapshotConfig - default: - return nil - } -} diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go index da2a041a45c..2bf010d8f04 100644 --- a/turbo/cli/default_flags.go +++ b/turbo/cli/default_flags.go @@ -84,5 +84,6 @@ var DefaultFlags = []cli.Flag{ utils.MinerNoVerfiyFlag, utils.MinerSigningKeyFileFlag, utils.SentryAddrFlag, + utils.DownloaderAddrFlag, HealthCheckFlag, } diff --git a/turbo/node/node.go b/turbo/node/node.go index 58ea3eefe95..084a0f05bb9 100644 --- a/turbo/node/node.go +++ b/turbo/node/node.go @@ -8,6 +8,7 @@ import ( "github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/node" "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/params/networkname" erigoncli "github.com/ledgerwatch/erigon/turbo/cli" "github.com/ledgerwatch/log/v3" @@ -52,19 +53,19 @@ func NewNodConfigUrfave(ctx *cli.Context) *node.Config { // If we're running a known preset, log it for convenience. chain := ctx.GlobalString(utils.ChainFlag.Name) switch chain { - case params.RopstenChainName: + case networkname.RopstenChainName: log.Info("Starting Erigon on Ropsten testnet...") - case params.RinkebyChainName: + case networkname.RinkebyChainName: log.Info("Starting Erigon on Rinkeby testnet...") - case params.GoerliChainName: + case networkname.GoerliChainName: log.Info("Starting Erigon on Görli testnet...") - case params.DevChainName: + case networkname.DevChainName: log.Info("Starting Erigon in ephemeral dev mode...") - case "", params.MainnetChainName: + case "", networkname.MainnetChainName: if !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) { log.Info("Starting Erigon on Ethereum mainnet...") } diff --git a/turbo/snapshotsync/block_reader.go b/turbo/snapshotsync/block_reader.go index 8efe49d15ce..5ad1676eb89 100644 --- a/turbo/snapshotsync/block_reader.go +++ b/turbo/snapshotsync/block_reader.go @@ -28,6 +28,23 @@ func (back *BlockReader) Header(ctx context.Context, tx kv.Getter, hash common.H return h, nil } +func (back *BlockReader) Body(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (body *types.Body, err error) { + body, _, _ = rawdb.ReadBody(tx, hash, blockHeight) + return body, nil +} + +func (back *BlockReader) BodyRlp(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (bodyRlp rlp.RawValue, err error) { + body, err := back.Body(ctx, tx, hash, blockHeight) + if err != nil { + return nil, err + } + bodyRlp, err = rlp.EncodeToBytes(body) + if err != nil { + return nil, err + } + return bodyRlp, nil +} + func (back *BlockReader) HeaderByNumber(ctx context.Context, tx kv.Getter, blockHeight uint64) (*types.Header, error) { h := rawdb.ReadHeaderByNumber(tx, blockHeight) return h, nil @@ -88,6 +105,28 @@ func (back *RemoteBlockReader) Header(ctx context.Context, tx kv.Tx, hash common } return block.Header(), nil } +func (back *RemoteBlockReader) Body(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (body *types.Body, err error) { + block, _, err := back.BlockWithSenders(ctx, tx, hash, blockHeight) + if err != nil { + return nil, err + } + if block == nil { + return nil, nil + } + return block.Body(), nil +} + +func (back *RemoteBlockReader) BodyRlp(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (bodyRlp rlp.RawValue, err error) { + body, err := back.Body(ctx, tx, hash, blockHeight) + if err != nil { + return nil, err + } + bodyRlp, err = rlp.EncodeToBytes(body) + if err != nil { + return nil, err + } + return bodyRlp, nil +} // BlockReaderWithSnapshots can read blocks from db and snapshots type BlockReaderWithSnapshots struct { @@ -106,6 +145,7 @@ func (back *BlockReaderWithSnapshots) HeaderByNumber(ctx context.Context, tx kv. } return back.headerFromSnapshot(blockHeight, sn) } + func (back *BlockReaderWithSnapshots) Header(ctx context.Context, tx kv.Getter, hash common.Hash, blockHeight uint64) (*types.Header, error) { sn, ok := back.sn.Blocks(blockHeight) if !ok { @@ -126,6 +166,50 @@ func (back *BlockReaderWithSnapshots) ReadHeaderByNumber(ctx context.Context, tx return back.headerFromSnapshot(blockHeight, sn) } +func (back *BlockReaderWithSnapshots) Body(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (body *types.Body, err error) { + sn, ok := back.sn.Blocks(blockHeight) + if !ok { + canonicalHash, err := rawdb.ReadCanonicalHash(tx, blockHeight) + if err != nil { + return nil, fmt.Errorf("requested non-canonical hash %x. canonical=%x", hash, canonicalHash) + } + body, baseTxID, txsAmount := rawdb.ReadBody(tx, hash, blockHeight) + if body == nil { + return nil, fmt.Errorf("body not found for block %d,%x", blockHeight, hash) + } + if canonicalHash == hash { + body.Transactions, err = rawdb.CanonicalTransactions(tx, baseTxID, txsAmount) + if err != nil { + return nil, err + } + return body, nil + } + body.Transactions, err = rawdb.NonCanonicalTransactions(tx, baseTxID, txsAmount) + if err != nil { + return nil, err + } + return body, nil + } + + body, _, _, _, err = back.bodyFromSnapshot(blockHeight, sn) + if err != nil { + return nil, err + } + return body, nil +} + +func (back *BlockReaderWithSnapshots) BodyRlp(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (bodyRlp rlp.RawValue, err error) { + body, err := back.Body(ctx, tx, hash, blockHeight) + if err != nil { + return nil, err + } + bodyRlp, err = rlp.EncodeToBytes(body) + if err != nil { + return nil, err + } + return bodyRlp, nil +} + func (back *BlockReaderWithSnapshots) BlockWithSenders(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (block *types.Block, senders []common.Address, err error) { sn, ok := back.sn.Blocks(blockHeight) if !ok { @@ -213,3 +297,47 @@ func (back *BlockReaderWithSnapshots) headerFromSnapshot(blockHeight uint64, sn } return h, nil } + +func (back *BlockReaderWithSnapshots) bodyFromSnapshot(blockHeight uint64, sn *BlocksSnapshot) (*types.Body, []common.Address, uint64, uint32, error) { + buf := make([]byte, 16) + + bodyOffset := sn.Bodies.Idx.Lookup2(blockHeight - sn.Bodies.Idx.BaseDataID()) + + gg := sn.Bodies.Segment.MakeGetter() + gg.Reset(bodyOffset) + buf, _ = gg.Next(buf[:0]) + b := &types.BodyForStorage{} + reader := bytes.NewReader(buf) + if err := rlp.Decode(reader, b); err != nil { + return nil, nil, 0, 0, err + } + + if b.BaseTxId < sn.Transactions.Idx.BaseDataID() { + return nil, nil, 0, 0, fmt.Errorf(".idx file has wrong baseDataID? %d<%d, %s", b.BaseTxId, sn.Transactions.Idx.BaseDataID(), sn.Transactions.File) + } + + txs := make([]types.Transaction, b.TxAmount) + senders := make([]common.Address, b.TxAmount) + if b.TxAmount > 0 { + txnOffset := sn.Transactions.Idx.Lookup2(b.BaseTxId - sn.Transactions.Idx.BaseDataID()) // need subtract baseID of indexFile + gg = sn.Transactions.Segment.MakeGetter() + gg.Reset(txnOffset) + stream := rlp.NewStream(reader, 0) + for i := uint32(0); i < b.TxAmount; i++ { + buf, _ = gg.Next(buf[:0]) + senders[i].SetBytes(buf[1 : 1+20]) + txRlp := buf[1+20:] + reader.Reset(txRlp) + stream.Reset(reader, 0) + var err error + txs[i], err = types.DecodeTransaction(stream) + if err != nil { + return nil, nil, 0, 0, err + } + } + } + + body := new(types.Body) + body.Uncles = b.Uncles + return body, senders, b.BaseTxId, b.TxAmount, nil +} diff --git a/turbo/snapshotsync/block_snapshots.go b/turbo/snapshotsync/block_snapshots.go index 4f7c9411105..eb4a18217b2 100644 --- a/turbo/snapshotsync/block_snapshots.go +++ b/turbo/snapshotsync/block_snapshots.go @@ -30,8 +30,8 @@ import ( "github.com/ledgerwatch/erigon/common/dbutils" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rlp" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/ledgerwatch/log/v3" ) @@ -43,6 +43,8 @@ const ( Transactions SnapshotType = "transactions" ) +var AllSnapshotTypes = []SnapshotType{Headers, Bodies, Transactions} + var ( ErrInvalidCompressedFileName = fmt.Errorf("invalid compressed file name") ) @@ -90,7 +92,7 @@ type AllSnapshots struct { segmentsAvailable uint64 idxAvailable uint64 blocks []*BlocksSnapshot - cfg *params.SnapshotsConfig + cfg *snapshothashes.Config } // NewAllSnapshots - opens all snapshots. But to simplify everything: @@ -98,20 +100,20 @@ type AllSnapshots struct { // - all snapshots of given blocks range must exist - to make this blocks range available // - gaps are not allowed // - segment have [from:to) semantic -func NewAllSnapshots(dir string, cfg *params.SnapshotsConfig) *AllSnapshots { +func NewAllSnapshots(dir string, cfg *snapshothashes.Config) *AllSnapshots { if err := os.MkdirAll(dir, 0755); err != nil { panic(err) } return &AllSnapshots{dir: dir, cfg: cfg} } -func (s *AllSnapshots) ChainSnapshotConfig() *params.SnapshotsConfig { return s.cfg } -func (s *AllSnapshots) AllSegmentsAvailable() bool { return s.allSegmentsAvailable } -func (s *AllSnapshots) SetAllSegmentsAvailable(v bool) { s.allSegmentsAvailable = v } -func (s *AllSnapshots) BlocksAvailable() uint64 { return s.segmentsAvailable } -func (s *AllSnapshots) AllIdxAvailable() bool { return s.allIdxAvailable } -func (s *AllSnapshots) SetAllIdxAvailable(v bool) { s.allIdxAvailable = v } -func (s *AllSnapshots) IndicesAvailable() uint64 { return s.idxAvailable } +func (s *AllSnapshots) ChainSnapshotConfig() *snapshothashes.Config { return s.cfg } +func (s *AllSnapshots) AllSegmentsAvailable() bool { return s.allSegmentsAvailable } +func (s *AllSnapshots) SetAllSegmentsAvailable(v bool) { s.allSegmentsAvailable = v } +func (s *AllSnapshots) BlocksAvailable() uint64 { return s.segmentsAvailable } +func (s *AllSnapshots) AllIdxAvailable() bool { return s.allIdxAvailable } +func (s *AllSnapshots) SetAllIdxAvailable(v bool) { s.allIdxAvailable = v } +func (s *AllSnapshots) IndicesAvailable() uint64 { return s.idxAvailable } func (s *AllSnapshots) SegmentsAvailability() (headers, bodies, txs uint64, err error) { if headers, err = latestSegment(s.dir, Headers); err != nil { @@ -137,37 +139,51 @@ func (s *AllSnapshots) IdxAvailability() (headers, bodies, txs uint64, err error } return } + func (s *AllSnapshots) ReopenIndices() error { + return s.ReopenSomeIndices(AllSnapshotTypes...) +} + +func (s *AllSnapshots) ReopenSomeIndices(types ...SnapshotType) error { for _, bs := range s.blocks { - if bs.Headers.Idx != nil { - bs.Headers.Idx.Close() - bs.Headers.Idx = nil - } - idx, err := recsplit.OpenIndex(path.Join(s.dir, IdxFileName(bs.Headers.From, bs.Headers.To, Headers))) - if err != nil { - return err - } - bs.Headers.Idx = idx + for _, snapshotType := range types { + switch snapshotType { + case Headers: + if bs.Headers.Idx != nil { + bs.Headers.Idx.Close() + bs.Headers.Idx = nil + } + idx, err := recsplit.OpenIndex(path.Join(s.dir, IdxFileName(bs.Headers.From, bs.Headers.To, Headers))) + if err != nil { + return err + } + bs.Headers.Idx = idx + case Bodies: + if bs.Bodies.Idx != nil { + bs.Bodies.Idx.Close() + bs.Bodies.Idx = nil + } + idx, err := recsplit.OpenIndex(path.Join(s.dir, IdxFileName(bs.Bodies.From, bs.Bodies.To, Bodies))) + if err != nil { + return err + } + bs.Bodies.Idx = idx - if bs.Bodies.Idx != nil { - bs.Bodies.Idx.Close() - bs.Bodies.Idx = nil - } - idx, err = recsplit.OpenIndex(path.Join(s.dir, IdxFileName(bs.Bodies.From, bs.Bodies.To, Bodies))) - if err != nil { - return err + case Transactions: + if bs.Transactions.Idx != nil { + bs.Transactions.Idx.Close() + bs.Transactions.Idx = nil + } + idx, err := recsplit.OpenIndex(path.Join(s.dir, IdxFileName(bs.Transactions.From, bs.Transactions.To, Transactions))) + if err != nil { + return err + } + bs.Transactions.Idx = idx + default: + panic(fmt.Sprintf("unknown snapshot type: %s", snapshotType)) + } } - bs.Bodies.Idx = idx - if bs.Transactions.Idx != nil { - bs.Transactions.Idx.Close() - bs.Transactions.Idx = nil - } - idx, err = recsplit.OpenIndex(path.Join(s.dir, IdxFileName(bs.Transactions.From, bs.Transactions.To, Transactions))) - if err != nil { - return err - } - bs.Transactions.Idx = idx s.idxAvailable = bs.Transactions.To - 1 } return nil @@ -274,7 +290,6 @@ func (s *AllSnapshots) Blocks(blockNumber uint64) (snapshot *BlocksSnapshot, fou } func (s *AllSnapshots) BuildIndices(ctx context.Context, chainID uint256.Int) error { - fmt.Printf("build!\n") for _, sn := range s.blocks { f := path.Join(s.dir, SegmentFileName(sn.Headers.From, sn.Headers.To, Headers)) if err := HeadersHashIdx(f, sn.Headers.From); err != nil { @@ -288,31 +303,33 @@ func (s *AllSnapshots) BuildIndices(ctx context.Context, chainID uint256.Int) er } // hack to read first block body - to get baseTxId from there - _ = s.ReopenIndices() + if err := s.ReopenSomeIndices(Headers, Bodies); err != nil { + return err + } + for _, sn := range s.blocks { gg := sn.Bodies.Segment.MakeGetter() buf, _ := gg.Next(nil) - b := &types.BodyForStorage{} - if err := rlp.DecodeBytes(buf, b); err != nil { + firstBody := &types.BodyForStorage{} + if err := rlp.DecodeBytes(buf, firstBody); err != nil { return err } var expectedTxsAmount uint64 { - off := sn.Bodies.Idx.Lookup2(sn.To - 1) + off := sn.Bodies.Idx.Lookup2(sn.To - 1 - sn.From) gg.Reset(off) - buf, _ = gg.Next(nil) - bodyForStorage := new(types.BodyForStorage) - err := rlp.DecodeBytes(buf, bodyForStorage) + buf, _ = gg.Next(buf[:0]) + lastBody := new(types.BodyForStorage) + err := rlp.DecodeBytes(buf, lastBody) if err != nil { - panic(err) + return err } - expectedTxsAmount = bodyForStorage.BaseTxId + uint64(bodyForStorage.TxAmount) - b.BaseTxId + expectedTxsAmount = lastBody.BaseTxId + uint64(lastBody.TxAmount) - firstBody.BaseTxId } f := path.Join(s.dir, SegmentFileName(sn.Transactions.From, sn.Transactions.To, Transactions)) - fmt.Printf("create: %s\n", f) - if err := TransactionsHashIdx(chainID, b.BaseTxId, f, expectedTxsAmount); err != nil { + if err := TransactionsHashIdx(chainID, firstBody.BaseTxId, f, expectedTxsAmount); err != nil { return err } } diff --git a/turbo/snapshotsync/block_snapshots_test.go b/turbo/snapshotsync/block_snapshots_test.go index 84f8e4632b4..778fe2b958e 100644 --- a/turbo/snapshotsync/block_snapshots_test.go +++ b/turbo/snapshotsync/block_snapshots_test.go @@ -6,13 +6,14 @@ import ( "github.com/ledgerwatch/erigon-lib/compress" "github.com/ledgerwatch/erigon-lib/recsplit" - "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/params/networkname" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/stretchr/testify/require" ) func TestOpenAllSnapshot(t *testing.T) { dir, require := t.TempDir(), require.New(t) - cfg := params.KnownSnapshots(params.MainnetChainName) + cfg := snapshothashes.KnownConfig(networkname.MainnetChainName) createFile := func(from, to uint64, name SnapshotType) { c, err := compress.NewCompressor("test", path.Join(dir, SegmentFileName(from, to, name)), dir, 100) require.NoError(err) diff --git a/turbo/snapshotsync/bodies_snapshot.go b/turbo/snapshotsync/bodies_snapshot.go deleted file mode 100644 index adcd09ff16e..00000000000 --- a/turbo/snapshotsync/bodies_snapshot.go +++ /dev/null @@ -1 +0,0 @@ -package snapshotsync diff --git a/turbo/snapshotsync/build_infobytes.go b/turbo/snapshotsync/build_infobytes.go deleted file mode 100644 index 839970f34ba..00000000000 --- a/turbo/snapshotsync/build_infobytes.go +++ /dev/null @@ -1,46 +0,0 @@ -package snapshotsync - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/anacrolix/torrent/metainfo" -) - -func BuildInfoBytesForSnapshot(root string, fileName string) (metainfo.Info, error) { - - path := filepath.Join(root, fileName) - fi, err := os.Stat(path) - if err != nil { - return metainfo.Info{}, err - } - relPath, err := filepath.Rel(root, path) - if err != nil { - return metainfo.Info{}, fmt.Errorf("error getting relative path: %s", err) - } - - info := metainfo.Info{ - Name: filepath.Base(root), - PieceLength: DefaultChunkSize, - Length: fi.Size(), - Files: []metainfo.FileInfo{ - { - Length: fi.Size(), - Path: []string{relPath}, - PathUTF8: nil, - }, - }, - } - - err = info.GeneratePieces(func(fi metainfo.FileInfo) (io.ReadCloser, error) { - return os.Open(filepath.Join(root, strings.Join(fi.Path, string(filepath.Separator)))) - }) - if err != nil { - err = fmt.Errorf("error generating pieces: %s", err) - return metainfo.Info{}, err - } - return info, nil -} diff --git a/turbo/snapshotsync/client.go b/turbo/snapshotsync/client.go index eb3e4a7d889..707640cfb1b 100644 --- a/turbo/snapshotsync/client.go +++ b/turbo/snapshotsync/client.go @@ -1,14 +1,14 @@ package snapshotsync import ( - "github.com/ledgerwatch/erigon-lib/gointerfaces/snapshotsync" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" "google.golang.org/grpc" ) //go:generate ls ./../../interfaces/snapshot_downloader //go:generate protoc --go_out=. --go-grpc_out=. --proto_path=./../../interfaces/snapshot_downloader "external_downloader.proto" -I=. -I=./../../build/include/google -func NewClient(addr string) (snapshotsync.DownloaderClient, func() error, error) { +func NewClient(addr string) (proto_downloader.DownloaderClient, func() error, error) { opts := []grpc.DialOption{ grpc.WithInsecure(), } @@ -18,5 +18,5 @@ func NewClient(addr string) (snapshotsync.DownloaderClient, func() error, error) return nil, nil, err } - return snapshotsync.NewDownloaderClient(conn), conn.Close, nil + return proto_downloader.NewDownloaderClient(conn), conn.Close, nil } diff --git a/turbo/snapshotsync/const.go b/turbo/snapshotsync/const.go index 699f44c34f8..10566537903 100644 --- a/turbo/snapshotsync/const.go +++ b/turbo/snapshotsync/const.go @@ -2,367 +2,8 @@ package snapshotsync import ( "errors" - - "github.com/anacrolix/torrent/metainfo" - "github.com/ledgerwatch/erigon-lib/gointerfaces/snapshotsync" - "github.com/ledgerwatch/erigon/params" -) - -const ( - DefaultChunkSize = 1024 * 1024 - MdbxFilename = "mdbx.dat" - EpochSize = 500_000 - - //todo It'll be changed after enabling new snapshot generation mechanism - HeadersSnapshotHash = "0000000000000000000000000000000000000000" - BlocksSnapshotHash = "0000000000000000000000000000000000000000" - StateSnapshotHash = "0000000000000000000000000000000000000000" - - SnapshotInfoHashPrefix = "ih" - SnapshotInfoBytesPrefix = "ib" ) var ( - TorrentHashes = map[uint64]map[snapshotsync.SnapshotType]metainfo.Hash{ - params.MainnetChainConfig.ChainID.Uint64(): { - snapshotsync.SnapshotType_headers: metainfo.NewHashFromHex(HeadersSnapshotHash), - snapshotsync.SnapshotType_bodies: metainfo.NewHashFromHex(BlocksSnapshotHash), - snapshotsync.SnapshotType_state: metainfo.NewHashFromHex(StateSnapshotHash), - }, - } ErrInvalidSnapshot = errors.New("this snapshot for this chainID not supported ") ) - -func GetAvailableSnapshotTypes(chainID uint64) []snapshotsync.SnapshotType { - v := TorrentHashes[chainID] - res := make([]snapshotsync.SnapshotType, 0, len(v)) - for i := range v { - res = append(res, i) - } - return res -} - -var Trackers = [][]string{{ - "http://35.189.110.210:80/announce", -}, { - "udp://tracker.openbittorrent.com:80", - "udp://tracker.openbittorrent.com:80", - "udp://tracker.publicbt.com:80", - "udp://coppersurfer.tk:6969/announce", - "udp://open.demonii.com:1337", - "http://bttracker.crunchbanglinux.org:6969/announce", - "udp://wambo.club:1337/announce", - "udp://tracker.dutchtracking.com:6969/announce", - "udp://tc.animereactor.ru:8082/announce", - "udp://tracker.justseed.it:1337/announce", - "udp://tracker.leechers-paradise.org:6969/announce", - "udp://tracker.opentrackr.org:1337/announce", - "https://open.kickasstracker.com:443/announce", - "udp://tracker.coppersurfer.tk:6969/announce", - "udp://open.stealth.si:80/announce", - "http://87.253.152.137/announce", - "http://91.216.110.47/announce", - "http://91.217.91.21:3218/announce", - "http://91.218.230.81:6969/announce", - "http://93.92.64.5/announce", - "http://atrack.pow7.com/announce", - "http://bt.henbt.com:2710/announce", - "http://bt.pusacg.org:8080/announce", - "https://tracker.bt-hash.com:443/announce", - "udp://tracker.leechers-paradise.org:6969", - "https://182.176.139.129:6969/announce", - "udp://zephir.monocul.us:6969/announce", - "https://tracker.dutchtracking.com:80/announce", - "https://grifon.info:80/announce", - "udp://tracker.kicks-ass.net:80/announce", - "udp://p4p.arenabg.com:1337/announce", - "udp://tracker.aletorrenty.pl:2710/announce", - "udp://tracker.sktorrent.net:6969/announce", - "udp://tracker.internetwarriors.net:1337/announce", - "https://tracker.parrotsec.org:443/announce", - "https://tracker.moxing.party:6969/announce", - "https://tracker.ipv6tracker.ru:80/announce", - "https://tracker.fastdownload.xyz:443/announce", - "udp://open.stealth.si:80/announce", - "https://gwp2-v19.rinet.ru:80/announce", - "https://tr.kxmp.cf:80/announce", - "https://explodie.org:6969/announce", -}, { - "udp://zephir.monocul.us:6969/announce", - "udp://tracker.torrent.eu.org:451/announce", - "udp://tracker.uw0.xyz:6969/announce", - "udp://tracker.cyberia.is:6969/announce", - "http://tracker.files.fm:6969/announce", - "udp://tracker.zum.bi:6969/announce", - "http://tracker.nyap2p.com:8080/announce", - "udp://opentracker.i2p.rocks:6969/announce", - "udp://tracker.zerobytes.xyz:1337/announce", - "https://tracker.tamersunion.org:443/announce", - "https://w.wwwww.wtf:443/announce", - "https://tracker.imgoingto.icu:443/announce", - "udp://blokas.io:6969/announce", - "udp://api.bitumconference.ru:6969/announce", - "udp://discord.heihachi.pw:6969/announce", - "udp://cutiegirl.ru:6969/announce", - "udp://fe.dealclub.de:6969/announce", - "udp://ln.mtahost.co:6969/announce", - "udp://vibe.community:6969/announce", - "http://vpn.flying-datacenter.de:6969/announce", - "udp://eliastre100.fr:6969/announce", - "udp://wassermann.online:6969/announce", - "udp://retracker.local.msn-net.ru:6969/announce", - "udp://chanchan.uchuu.co.uk:6969/announce", - "udp://kanal-4.de:6969/announce", - "udp://handrew.me:6969/announce", - "udp://mail.realliferpg.de:6969/announce", - "udp://bubu.mapfactor.com:6969/announce", - "udp://mts.tvbit.co:6969/announce", - "udp://6ahddutb1ucc3cp.ru:6969/announce", - "udp://adminion.n-blade.ru:6969/announce", - "udp://contra.sf.ca.us:6969/announce", - "udp://61626c.net:6969/announce", - "udp://benouworldtrip.fr:6969/announce", - "udp://sd-161673.dedibox.fr:6969/announce", - "udp://cdn-1.gamecoast.org:6969/announce", - "udp://cdn-2.gamecoast.org:6969/announce", - "udp://daveking.com:6969/announce", - "udp://bms-hosxp.com:6969/announce", - "udp://teamspeak.value-wolf.org:6969/announce", - "udp://edu.uifr.ru:6969/announce", - "udp://adm.category5.tv:6969/announce", - "udp://code2chicken.nl:6969/announce", - "udp://t1.leech.ie:1337/announce", - "udp://forever-tracker.zooki.xyz:6969/announce", - "udp://free-tracker.zooki.xyz:6969/announce", - "udp://public.publictracker.xyz:6969/announce", - "udp://public-tracker.zooki.xyz:6969/announce", - "udp://vps2.avc.cx:7171/announce", - "udp://tracker.fileparadise.in:1337/announce", - "udp://tracker.skynetcloud.site:6969/announce", - "udp://z.mercax.com:53/announce", - "https://publictracker.pp.ua:443/announce", - "udp://us-tracker.publictracker.xyz:6969/announce", - "udp://open.stealth.si:80/announce", - "http://tracker1.itzmx.com:8080/announce", - "http://vps02.net.orel.ru:80/announce", - "http://tracker.gbitt.info:80/announce", - "http://tracker.bt4g.com:2095/announce", - "https://tracker.nitrix.me:443/announce", - "udp://aaa.army:8866/announce", - "udp://tracker.vulnix.sh:6969/announce", - "udp://engplus.ru:6969/announce", - "udp://movies.zsw.ca:6969/announce", - "udp://storage.groupees.com:6969/announce", - "udp://nagios.tks.sumy.ua:80/announce", - "udp://tracker.v6speed.org:6969/announce", - "udp://47.ip-51-68-199.eu:6969/announce", - "udp://aruacfilmes.com.br:6969/announce", - "https://trakx.herokuapp.com:443/announce", - "udp://inferno.demonoid.is:3391/announce", - "udp://publictracker.xyz:6969/announce", - "http://tracker2.itzmx.com:6961/announce", - "http://tracker3.itzmx.com:6961/announce", - "udp://retracker.akado-ural.ru:80/announce", - "udp://tracker-udp.gbitt.info:80/announce", - "http://h4.trakx.nibba.trade:80/announce", - "udp://tracker.army:6969/announce", - "http://tracker.anonwebz.xyz:8080/announce", - "udp://tracker.shkinev.me:6969/announce", - "http://0205.uptm.ch:6969/announce", - "udp://tracker.zooki.xyz:6969/announce", - "udp://forever.publictracker.xyz:6969/announce", - "udp://tracker.moeking.me:6969/announce", - "udp://ultra.zt.ua:6969/announce", - "udp://tracker.publictracker.xyz:6969/announce", - "udp://ipv4.tracker.harry.lu:80/announce", - "udp://u.wwwww.wtf:1/announce", - "udp://line-net.ru:6969/announce", - "udp://dpiui.reedlan.com:6969/announce", - "udp://tracker.zemoj.com:6969/announce", - "udp://t3.leech.ie:1337/announce", - "http://t.nyaatracker.com:80/announce", - "udp://exodus.desync.com:6969/announce", - "udp://valakas.rollo.dnsabr.com:2710/announce", - "udp://tracker.ds.is:6969/announce", - "udp://tracker.opentrackr.org:1337/announce", - "udp://tracker0.ufibox.com:6969/announce", - "https://tracker.hama3.net:443/announce", - "udp://opentor.org:2710/announce", - "udp://t2.leech.ie:1337/announce", - "https://1337.abcvg.info:443/announce", - "udp://git.vulnix.sh:6969/announce", - "udp://retracker.lanta-net.ru:2710/announce", - "udp://tracker.lelux.fi:6969/announce", - "udp://bt1.archive.org:6969/announce", - "udp://admin.videoenpoche.info:6969/announce", - "udp://drumkitx.com:6969/announce", - "udp://tracker.dler.org:6969/announce", - "udp://koli.services:6969/announce", - "udp://tracker.dyne.org:6969/announce", - "http://torrenttracker.nwc.acsalaska.net:6969/announce", - "udp://rutorrent.frontline-mod.com:6969/announce", - "http://rt.tace.ru:80/announce", - "udp://explodie.org:6969/announce", -}, { - "udp://public.popcorn-tracker.org:6969/announce", - "http://104.28.1.30:8080/announce", - "http://104.28.16.69/announce", - "http://107.150.14.110:6969/announce", - "http://109.121.134.121:1337/announce", - "http://114.55.113.60:6969/announce", - "http://125.227.35.196:6969/announce", - "http://128.199.70.66:5944/announce", - "http://157.7.202.64:8080/announce", - "http://158.69.146.212:7777/announce", - "http://173.254.204.71:1096/announce", - "http://178.175.143.27/announce", - "http://178.33.73.26:2710/announce", - "http://182.176.139.129:6969/announce", - "http://185.5.97.139:8089/announce", - "http://188.165.253.109:1337/announce", - "http://194.106.216.222/announce", - "http://195.123.209.37:1337/announce", - "http://210.244.71.25:6969/announce", - "http://210.244.71.26:6969/announce", - "http://213.159.215.198:6970/announce", - "http://213.163.67.56:1337/announce", - "http://37.19.5.139:6969/announce", - "http://37.19.5.155:6881/announce", - "http://46.4.109.148:6969/announce", - "http://5.79.249.77:6969/announce", - "http://5.79.83.193:2710/announce", - "http://51.254.244.161:6969/announce", - "http://59.36.96.77:6969/announce", - "http://74.82.52.209:6969/announce", - "http://80.246.243.18:6969/announce", - "http://81.200.2.231/announce", - "http://85.17.19.180/announce", - "http://87.248.186.252:8080/announce", - "http://87.253.152.137/announce", - "http://91.216.110.47/announce", - "http://91.217.91.21:3218/announce", - "http://91.218.230.81:6969/announce", - "http://93.92.64.5/announce", - "http://atrack.pow7.com/announce", - "http://bt.henbt.com:2710/announce", - "http://bt.pusacg.org:8080/announce", - "http://bt2.careland.com.cn:6969/announce", - "http://explodie.org:6969/announce", - "http://mgtracker.org:2710/announce", - "http://mgtracker.org:6969/announce", - "http://open.acgtracker.com:1096/announce", - "http://open.lolicon.eu:7777/announce", - "http://open.touki.ru/announce.php", - "http://p4p.arenabg.ch:1337/announce", - "http://p4p.arenabg.com:1337/announce", - "http://pow7.com:80/announce", - "http://retracker.gorcomnet.ru/announce", - "http://retracker.krs-ix.ru/announce", - "http://retracker.krs-ix.ru:80/announce", - "http://secure.pow7.com/announce", - "http://t1.pow7.com/announce", - "http://t2.pow7.com/announce", - "http://thetracker.org:80/announce", - "http://torrent.gresille.org/announce", - "http://torrentsmd.com:8080/announce", - "http://tracker.aletorrenty.pl:2710/announce", - "http://tracker.baravik.org:6970/announce", - "http://tracker.bittor.pw:1337/announce", - "http://tracker.bittorrent.am/announce", - "http://tracker.calculate.ru:6969/announce", - "http://tracker.dler.org:6969/announce", - "http://tracker.dutchtracking.com/announce", - "http://tracker.dutchtracking.com:80/announce", - "http://tracker.dutchtracking.nl/announce", - "http://tracker.dutchtracking.nl:80/announce", - "http://tracker.edoardocolombo.eu:6969/announce", - "http://tracker.ex.ua/announce", - "http://tracker.ex.ua:80/announce", - "http://tracker.filetracker.pl:8089/announce", - "http://tracker.flashtorrents.org:6969/announce", - "http://tracker.grepler.com:6969/announce", - "http://tracker.internetwarriors.net:1337/announce", - "http://tracker.kicks-ass.net/announce", - "http://tracker.kicks-ass.net:80/announce", - "http://tracker.kuroy.me:5944/announce", - "http://tracker.mg64.net:6881/announce", - "http://tracker.opentrackr.org:1337/announce", - "http://tracker.skyts.net:6969/announce", - "http://tracker.tfile.me/announce", - "http://tracker.tiny-vps.com:6969/announce", - "http://tracker.tvunderground.org.ru:3218/announce", - "http://tracker.yoshi210.com:6969/announc", - "http://tracker1.wasabii.com.tw:6969/announce", - "http://tracker2.itzmx.com:6961/announce", - "http://tracker2.wasabii.com.tw:6969/announce", - "http://www.wareztorrent.com/announce", - "http://www.wareztorrent.com:80/announce", - "https://104.28.17.69/announce", - "https://www.wareztorrent.com/announce", - "udp://107.150.14.110:6969/announce", - "udp://109.121.134.121:1337/announce", - "udp://114.55.113.60:6969/announce", - "udp://128.199.70.66:5944/announce", - "udp://151.80.120.114:2710/announce", - "udp://168.235.67.63:6969/announce", - "udp://178.33.73.26:2710/announce", - "udp://182.176.139.129:6969/announce", - "udp://185.5.97.139:8089/announce", - "udp://185.86.149.205:1337/announce", - "udp://188.165.253.109:1337/announce", - "udp://191.101.229.236:1337/announce", - "udp://194.106.216.222:80/announce", - "udp://195.123.209.37:1337/announce", - "udp://195.123.209.40:80/announce", - "udp://208.67.16.113:8000/announce", - "udp://213.163.67.56:1337/announce", - "udp://37.19.5.155:2710/announce", - "udp://46.4.109.148:6969/announce", - "udp://5.79.249.77:6969/announce", - "udp://5.79.83.193:6969/announce", - "udp://51.254.244.161:6969/announce", - "udp://62.138.0.158:6969/announce", - "udp://62.212.85.66:2710/announce", - "udp://74.82.52.209:6969/announce", - "udp://85.17.19.180:80/announce", - "udp://89.234.156.205:80/announce", - "udp://9.rarbg.com:2710/announce", - "udp://9.rarbg.me:2780/announce", - "udp://9.rarbg.to:2730/announce", - "udp://91.218.230.81:6969/announce", - "udp://94.23.183.33:6969/announce", - "udp://bt.xxx-tracker.com:2710/announce", - "udp://eddie4.nl:6969/announce", - "udp://explodie.org:6969/announce", - "udp://mgtracker.org:2710/announce", - "udp://open.stealth.si:80/announce", - "udp://p4p.arenabg.com:1337/announce", - "udp://shadowshq.eddie4.nl:6969/announce", - "udp://shadowshq.yi.org:6969/announce", - "udp://torrent.gresille.org:80/announce", - "udp://tracker.aletorrenty.pl:2710/announce", - "udp://tracker.bittor.pw:1337/announce", - "udp://tracker.coppersurfer.tk:6969/announce", - "udp://tracker.eddie4.nl:6969/announce", - "udp://tracker.ex.ua:80/announce", - "udp://tracker.filetracker.pl:8089/announce", - "udp://tracker.flashtorrents.org:6969/announce", - "udp://tracker.grepler.com:6969/announce", - "udp://tracker.ilibr.org:80/announce", - "udp://tracker.internetwarriors.net:1337/announce", - "udp://tracker.kicks-ass.net:80/announce", - "udp://tracker.kuroy.me:5944/announce", - "udp://tracker.leechers-paradise.org:6969/announce", - "udp://tracker.mg64.net:2710/announce", - "udp://tracker.mg64.net:6969/announce", - "udp://tracker.opentrackr.org:1337/announce", - "udp://tracker.piratepublic.com:1337/announce", - "udp://tracker.sktorrent.net:6969/announce", - "udp://tracker.skyts.net:6969/announce", - "udp://tracker.tiny-vps.com:6969/announce", - "udp://tracker.yoshi210.com:6969/announce", - "udp://tracker2.indowebster.com:6969/announce", - "udp://tracker4.piratux.com:6969/announce", - "udp://zer0day.ch:1337/announce", - "udp://zer0day.to:1337/announce", -}} diff --git a/turbo/snapshotsync/downloader.go b/turbo/snapshotsync/downloader.go deleted file mode 100644 index fecfe84c631..00000000000 --- a/turbo/snapshotsync/downloader.go +++ /dev/null @@ -1,382 +0,0 @@ -package snapshotsync - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "path/filepath" - "time" - - lg "github.com/anacrolix/log" - "github.com/anacrolix/torrent/bencode" - "github.com/ledgerwatch/erigon-lib/gointerfaces/snapshotsync" - "github.com/ledgerwatch/erigon-lib/kv" - "github.com/ledgerwatch/log/v3" - - "github.com/anacrolix/torrent" - "github.com/anacrolix/torrent/metainfo" - "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/common/debug" - "github.com/ledgerwatch/erigon/ethdb" -) - -type Client struct { - Cli *torrent.Client - snapshotsDir string - trackers [][]string -} - -func New(snapshotsDir string, seeding bool, peerID string) (*Client, error) { - torrentConfig := DefaultTorrentConfig() - torrentConfig.Seed = seeding - torrentConfig.DataDir = snapshotsDir - torrentConfig.UpnpID = torrentConfig.UpnpID + "leecher" - torrentConfig.PeerID = peerID - - torrentClient, err := torrent.NewClient(torrentConfig) - if err != nil { - log.Error("Fail to start torrnet client", "err", err) - return nil, fmt.Errorf("fail to start: %w", err) - } - - return &Client{ - Cli: torrentClient, - snapshotsDir: snapshotsDir, - trackers: Trackers, - }, nil -} - -func DefaultTorrentConfig() *torrent.ClientConfig { - torrentConfig := torrent.NewDefaultClientConfig() - torrentConfig.ListenPort = 0 - torrentConfig.NoDHT = true - torrentConfig.DisableTrackers = false - torrentConfig.Debug = false - torrentConfig.Logger = NewAdapterLogger() - torrentConfig.Logger = torrentConfig.Logger.FilterLevel(lg.Info) - return torrentConfig -} - -func (cli *Client) Torrents() []metainfo.Hash { - t := cli.Cli.Torrents() - hashes := make([]metainfo.Hash, 0, len(t)) - for _, v := range t { - hashes = append(hashes, v.InfoHash()) - } - return hashes -} -func (cli *Client) Load(tx kv.Tx) error { - log.Info("Load added torrents") - return tx.ForEach(kv.SnapshotInfo, []byte{}, func(k, infoHashBytes []byte) error { - if !bytes.HasPrefix(k[8:], []byte(SnapshotInfoHashPrefix)) { - return nil - } - networkID, snapshotName := ParseInfoHashKey(k) - infoHash := metainfo.Hash{} - copy(infoHash[:], infoHashBytes) - infoBytes, err := tx.GetOne(kv.SnapshotInfo, MakeInfoBytesKey(snapshotName, networkID)) - if err != nil { - return err - } - - log.Info("Add torrent", "snapshot", snapshotName, "hash", infoHash.String(), "infobytes", len(infoBytes) > 0) - _, err = cli.AddTorrentSpec(snapshotName, infoHash, infoBytes) - if err != nil { - return err - } - - return nil - }) -} - -func (cli *Client) SavePeerID(db kv.Putter) error { - return db.Put(kv.BittorrentInfo, []byte(kv.BittorrentPeerID), cli.PeerID()) -} - -func (cli *Client) Close() { - cli.Cli.Close() -} - -func (cli *Client) PeerID() []byte { - peerID := cli.Cli.PeerID() - return peerID[:] -} -func (cli *Client) AddTorrentSpec(snapshotName string, snapshotHash metainfo.Hash, infoBytes []byte) (*torrent.Torrent, error) { - t, ok := cli.Cli.Torrent(snapshotHash) - if ok { - return t, nil - } - t, _, err := cli.Cli.AddTorrentSpec(&torrent.TorrentSpec{ - Trackers: Trackers, - InfoHash: snapshotHash, - DisplayName: snapshotName, - InfoBytes: infoBytes, - }) - return t, err -} - -type torrentSpecFromDb struct { - exists bool - snapshotType snapshotsync.SnapshotType - networkID uint64 - infoHash torrent.InfoHash - infoBytes []byte -} - -func (cli *Client) AddTorrent(ctx context.Context, spec *torrentSpecFromDb) (*torrentSpecFromDb, error) { //nolint: interfacer - newTorrent := false - if !spec.exists { - log.Info("Init new torrent", "snapshot", spec.snapshotType.String()) - newTorrent = true - var ok bool - spec.infoHash, ok = TorrentHashes[spec.networkID][spec.snapshotType] - if !ok { - return nil, fmt.Errorf("%w type %v, networkID %v", ErrInvalidSnapshot, spec.snapshotType, spec.networkID) - } - } - log.Info("Added torrent spec", "snapshot", spec.snapshotType.String(), "hash", spec.infoHash.String()) - t, err := cli.AddTorrentSpec(spec.snapshotType.String(), spec.infoHash, spec.infoBytes) - if err != nil { - return nil, fmt.Errorf("error on add snapshot: %w", err) - } - log.Info("Getting infobytes", "snapshot", spec.snapshotType.String()) - spec.infoBytes, err = cli.GetInfoBytes(context.Background(), spec.infoHash) - if err != nil { - log.Warn("Init failure", "snapshot", spec.snapshotType.String(), "err", ctx.Err()) - return nil, fmt.Errorf("error on get info bytes: %w", err) - } - t.AllowDataDownload() - t.DownloadAll() - log.Info("Got infobytes", "snapshot", spec.snapshotType.String(), "file", t.Files()[0].Path()) - - if newTorrent { - return spec, nil - } - return nil, nil -} - -func (cli *Client) GetInfoBytes(ctx context.Context, snapshotHash metainfo.Hash) ([]byte, error) { - t, ok := cli.Cli.Torrent(snapshotHash) - if !ok { - return nil, errors.New("torrent not added") - } - for { - select { - case <-ctx.Done(): - return nil, fmt.Errorf("add torrent timeout: %w", ctx.Err()) - case <-t.GotInfo(): - return common.CopyBytes(t.Metainfo().InfoBytes), nil - default: - log.Info("Searching infobytes", "seeders", t.Stats().ConnectedSeeders, "active peers", t.Stats().ActivePeers) - time.Sleep(time.Second * 60) - } - } -} - -func (cli *Client) Download() { - log.Info("Start snapshot downloading") - torrents := cli.Cli.Torrents() - for i := range torrents { - t := torrents[i] - go func(t *torrent.Torrent) { - defer debug.LogPanic() - t.AllowDataDownload() - t.DownloadAll() - - tt := time.Now() - prev := t.BytesCompleted() - dwn: - for { - if t.Info().TotalLength()-t.BytesCompleted() == 0 { - log.Info("Dowloaded", "snapshot", t.Name(), "t", time.Since(tt)) - break dwn - } else { - stats := t.Stats() - log.Info("Downloading snapshot", - "snapshot", t.Name(), - "%", int(100*(float64(t.BytesCompleted())/float64(t.Info().TotalLength()))), - "mb", t.BytesCompleted()/1024/1024, - "diff(kb)", (t.BytesCompleted()-prev)/1024, - "seeders", stats.ConnectedSeeders, - "active", stats.ActivePeers, - "total", stats.TotalPeers) - prev = t.BytesCompleted() - time.Sleep(time.Second * 10) - - } - - } - }(t) - } - cli.Cli.WaitAll() - - for _, t := range cli.Cli.Torrents() { - log.Info("Snapshot seeding", "name", t.Name(), "seeding", t.Seeding()) - } -} - -func (cli *Client) GetSnapshots(tx kv.Tx, networkID uint64) (map[snapshotsync.SnapshotType]*snapshotsync.SnapshotsInfo, error) { - mp := make(map[snapshotsync.SnapshotType]*snapshotsync.SnapshotsInfo) - networkIDBytes := make([]byte, 8) - binary.BigEndian.PutUint64(networkIDBytes, networkID) - err := tx.ForPrefix(kv.SnapshotInfo, append(networkIDBytes, []byte(SnapshotInfoHashPrefix)...), func(k, v []byte) error { - var hash metainfo.Hash - if len(v) != metainfo.HashSize { - return nil - } - copy(hash[:], v) - t, ok := cli.Cli.Torrent(hash) - if !ok { - return nil - } - - var gotInfo bool - readiness := int32(0) - select { - case <-t.GotInfo(): - gotInfo = true - readiness = int32(100 * (float64(t.BytesCompleted()) / float64(t.Info().TotalLength()))) - default: - } - - _, tpStr := ParseInfoHashKey(k) - tp, ok := snapshotsync.SnapshotType_value[tpStr] - if !ok { - return fmt.Errorf("incorrect type: %v", tpStr) - } - - val := &snapshotsync.SnapshotsInfo{ - Type: snapshotsync.SnapshotType(tp), - GotInfoByte: gotInfo, - Readiness: readiness, - SnapshotBlock: SnapshotBlock, - Dbpath: filepath.Join(cli.snapshotsDir, t.Files()[0].Path()), - } - mp[snapshotsync.SnapshotType(tp)] = val - return nil - }) - if err != nil { - return nil, err - } - - return mp, nil -} - -func (cli *Client) SeedSnapshot(name string, path string) (metainfo.Hash, error) { - info, err := BuildInfoBytesForSnapshot(path, MdbxFilename) - if err != nil { - return [20]byte{}, err - } - - infoBytes, err := bencode.Marshal(info) - if err != nil { - return [20]byte{}, err - } - - t, err := cli.AddTorrentSpec(name, metainfo.HashBytes(infoBytes), infoBytes) - if err != nil { - return [20]byte{}, err - } - return t.InfoHash(), nil -} -func (cli *Client) StopSeeding(hash metainfo.Hash) error { - t, ok := cli.Cli.Torrent(hash) - if !ok { - return nil - } - ch := t.Closed() - t.Drop() - <-ch - return nil -} - -func getTorrentSpec(db kv.Tx, snapshotName snapshotsync.SnapshotType, networkID uint64) (*torrentSpecFromDb, error) { - snapshotNameS := snapshotName.String() - var infoHashBytes, infobytes []byte - var err error - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, networkID) - infoHashBytes, err = db.GetOne(kv.SnapshotInfo, MakeInfoHashKey(snapshotNameS, networkID)) - if err != nil { - return nil, err - } - var infoHash metainfo.Hash - if infoHashBytes != nil { - copy(infoHash[:], infoHashBytes[:metainfo.HashSize]) - } - - infobytes, err = db.GetOne(kv.SnapshotInfo, MakeInfoBytesKey(snapshotNameS, networkID)) - if err != nil { - return nil, err - } - - return &torrentSpecFromDb{ - exists: infoHashBytes != nil, - snapshotType: snapshotName, - networkID: networkID, - infoHash: infoHash, - infoBytes: infobytes, - }, nil -} -func saveTorrentSpec(db kv.Putter, spec *torrentSpecFromDb) error { - snapshotNameS := spec.snapshotType.String() - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, spec.networkID) - err := db.Put(kv.SnapshotInfo, MakeInfoHashKey(snapshotNameS, spec.networkID), spec.infoHash.Bytes()) - if err != nil { - return err - } - return db.Put(kv.SnapshotInfo, MakeInfoBytesKey(snapshotNameS, spec.networkID), spec.infoBytes) -} - -func MakeInfoHashKey(snapshotName string, networkID uint64) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, networkID) - return append(b, []byte(SnapshotInfoHashPrefix+snapshotName)...) -} - -func MakeInfoBytesKey(snapshotName string, networkID uint64) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, networkID) - return append(b, []byte(SnapshotInfoBytesPrefix+snapshotName)...) -} - -// ParseInfoHashKey returns networkID and snapshot name -func ParseInfoHashKey(k []byte) (uint64, string) { - return binary.BigEndian.Uint64(k), string(bytes.TrimPrefix(k[8:], []byte(SnapshotInfoHashPrefix))) -} - -func GetInfo() { - -} - -func SnapshotSeeding(chainDB kv.RwDB, cli *Client, name string, snapshotsDir string) error { - var snapshotBlock uint64 - var hasSnapshotBlock bool - if err := chainDB.View(context.Background(), func(tx kv.Tx) error { - v, err := tx.GetOne(kv.BittorrentInfo, kv.CurrentHeadersSnapshotBlock) - if err != nil && !errors.Is(err, ethdb.ErrKeyNotFound) { - return err - } - hasSnapshotBlock = len(v) == 8 - if hasSnapshotBlock { - snapshotBlock = binary.BigEndian.Uint64(v) - } else { - log.Warn("Snapshot block unknown", "snapshot", name, "v", common.Bytes2Hex(v)) - } - return nil - }); err != nil { - return err - } - - if hasSnapshotBlock { - hash, err := cli.SeedSnapshot(name, SnapshotName(snapshotsDir, name, snapshotBlock)) - if err != nil { - return err - } - log.Info("Start seeding", "snapshot", name, "hash", hash.String()) - } - return nil -} diff --git a/turbo/snapshotsync/logger.go b/turbo/snapshotsync/logger.go deleted file mode 100644 index 793d60efc94..00000000000 --- a/turbo/snapshotsync/logger.go +++ /dev/null @@ -1,39 +0,0 @@ -package snapshotsync - -import ( - lg "github.com/anacrolix/log" - "github.com/ledgerwatch/log/v3" -) - -func init() { - lg.Default = NewAdapterLogger() -} -func NewAdapterLogger() lg.Logger { - return lg.Logger{ - LoggerImpl: lg.LoggerImpl(adapterLogger{}), - } -} - -type adapterLogger struct{} - -func (b adapterLogger) Log(msg lg.Msg) { - lvl, ok := msg.GetLevel() - if !ok { - lvl = lg.Info - } - - switch lvl { - case lg.Debug: - log.Info(msg.String()) - case lg.Info: - log.Info(msg.String()) - case lg.Warning: - log.Warn(msg.String()) - case lg.Error: - log.Error(msg.String()) - case lg.Critical: - log.Error(msg.String()) - default: - log.Warn("unknown log type", "msg", msg.String()) - } -} diff --git a/turbo/snapshotsync/postprocessing.go b/turbo/snapshotsync/postprocessing.go deleted file mode 100644 index 5f2fb6c26d9..00000000000 --- a/turbo/snapshotsync/postprocessing.go +++ /dev/null @@ -1,336 +0,0 @@ -package snapshotsync - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "math/big" - "os" - - "github.com/ledgerwatch/erigon-lib/etl" - "github.com/ledgerwatch/erigon-lib/gointerfaces/snapshotsync" - "github.com/ledgerwatch/erigon-lib/kv" - "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/common/dbutils" - "github.com/ledgerwatch/erigon/core/rawdb" - "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/eth/stagedsync/stages" - "github.com/ledgerwatch/erigon/ethdb" - "github.com/ledgerwatch/erigon/rlp" - "github.com/ledgerwatch/log/v3" -) - -const ( - SnapshotBlock = 11_500_000 - HeaderHash11kk = "0x4d5e647b2d8d3a3c8c1561ebb88734bc5fc3c2941016f810cf218738c0ecd99e" - Header11kk = "f90211a01cb6a590440a9ed02e8762ac35faa04ec30cdbcaff0b276fa1ab5e2339033a6aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944bb96091ee9d802ed039c4d1a5f6216f90f81b01a08b2258fc3693f6ed1102f3142839a174b27f215841d2f542b586682898981c6da07d63a1ceded7864e95f09fe65b6bd17fb3f02a3644b1340bb0ab8a7267251e62a04cb5cf79c8a58a4787ec1ed0af51bcce19e6ad701dd40a45086244b933104cf2b901002438522b194b05881a7d976aa8c45ff47193ba8adc6fe2cc85eb68c66503558fa0ba43cebbd2327cfa297a87228511374ed3a2f66f3999426dced224c464840303de108b8604dcafce84d678b589cbe8a74aa2c540668a9a9acfa1eb94c6569918d819063600c000f3c060d649129f8327cad2c7ba1f9495531224b34a1ad8ca0810ab2d2d43a18877484dc33d220c0531024f1dc7448f8a6c016340ae143efd87c5e681d40a34e6be5803ea696038d3ad090048cb267a2ae72e7290da6b385f9874c002302c85e96005aa08031e30ac2a8a9a021bdc2a7a39a1089a08586cefcb937700ff03e4acaa37448c00f4ad02116216437bc52846ebd205869231e574870bf465887ac96883a7d8c083bdfd7483bde3a8845f7befc090505059452d657468706f6f6c2d757331a07a1a8c57afdf3be769e0f6a54e92900374cc207c7cf01b9da6ccca80a8b4006c88d495a5d800490fad" - Body11kk = "f99a34f99a30f8ac8214268541314cf0008301d4c0946b0359f95796327475ad4f12ae4e1047c3a67fa380b844a9059cbb000000000000000000000000fa1dfa4588664928951778b355b3de1f703ade4a00000000000000000000000000000000000000000000000000000000001e0b8725a0b3855a81e5f5ab38eae3b2c1bcd9ce72e143803ef4e47d9845c177aa156137f1a05993fea2c5b3e4995ca84aac5a11dd87ea1dd9a70f6cfc52f88a4b932edec56df8ac8214278541314cf0008301d4c0946b0359f95796327475ad4f12ae4e1047c3a67fa380b844a9059cbb000000000000000000000000def1b9aaedeeb0dbeba57773fad640aa7f30e735000000000000000000000000000000000000000000000000000000000057f29b26a0dca35e61c625a15c23579b346e032598a2ae33086ef1146330ae59335b28b90aa0162e3d461d1ecd5a57ef92ec582aab7daa6d7a402bb162963f37e1af9b0a8eb7f8ad830109f9853a35294400830138809412d79c345cac7b050a5ff0797b5a607e254c73f580b844a9059cbb000000000000000000000000e7e7a94e68be242a0db0c525dc3acefb755ae271000000000000000000000000000000000000000000000000000000000041696725a06f44e89a311d35682d93f81599bbb23c7a3477682c0ee66436688eca14ead9a5a00465af62a83af5135904c7953fcbb4821cc977a912b9e9055afc978224014a30f86d5785131794b40083021fdb9456710f7e591fa0559003831e041dd67789d2af4a881bc16d674ec800008025a08286e05045a922f5cb264de212d0fcd9bae59dfdc98a9a9d30b8cfc90baaa273a05945cb96da53d2ac56063d5a7273892e946f74ab371771ba75a725c223626820f88b825259851298879d29830249f0940000000071e801062eb0544403f66176bba42dc080a400000019000000000000000000000000000000000000000000000000000000000000079d25a0d704c1f7f9480b5f8f7e74c9dc5cf77391f4eb8abd901e5d5331af8272f4820ba026c7b701f8a24c6ce84d86c02fcdfd116b9a590bccb4848e012b6baefded1e99f8678211cc851229298c0083030d4094e5fd3dc48656dfed32601468426fe57595d8938080801ba0d1e94509c4ac498235452be50481fc49fb4037f37d2686a13c52280fcec15815a064fa69a8fb3bc49599be90dcd293f72a2575da8e991285c2a4923aa6042c1c45f86d82f06285120b5c270082627094ad978dc888e1a2590244f8bf9e716eb18b3b68d787354a6ba7a180008026a0ed667359fcecb1d62418dfd5535fa5ecc0abf7dcf314b1a59222f8e87a1af8a0a00755cf05e3dd4e9f95d9f9240cf5e66d8320fe3e64f7ed2eccf879c2db546ca7f8708302e5a28510c388d000830186a094dfda58c58d9d2e862d386706efdf6153e147ae8b8821db0ba827b770008026a07c7bbca5548e94b3da64473eb35b287d802a00433b5e67815da4765803b076a0a00c4c1bd0ba655f8c82cc4880bd4a1821de03bba379644685e6d8ceb3252d22def8708302e5a38510c388d000830186a0941afd9659259df405dd3f507ef77887aa0cb751b98843966d18b91b80008025a0c345c9fc5217f79b2e9375916020162f10f0505298ce5c074b15ad14495c8e81a00292440b70d3a6455902013ba251caa8578a22b76d59b14a668d839e2947f608f8ab818f851087ee06008301d4c0946bc1f3a1ae56231dbb64d3e82e070857eae8604580b844a9059cbb00000000000000000000000024434ebf296c2f9cd59b14412aae5c4ca1d5aad2000000000000000000000000000000000000000000000a9e566513a4910d400025a0db1575b0bb6f9ff489eceda148be18c2914680dd61d93e94b272d25919536feba0539db0b71bfd18077a63b7cec6624459bfb2cc1b5ec157bd540d7e323b833bf7f8aa1d851087ee06008301d4c0946bc1f3a1ae56231dbb64d3e82e070857eae8604580b844a9059cbb00000000000000000000000024434ebf296c2f9cd59b14412aae5c4ca1d5aad2000000000000000000000000000000000000000000002a4c2b736cdf768d400026a06abb41e737220b9f918e351d4e1106c7f4ab1c76e9f71dcf1313e34056c855e4a02e5737341566322cf7049561d9c62bebbb2d9fe681a9672a98b76d27b3525cc4f86c44851087ee060082520894efb2e870b14d7e555a31b392541acf002dae6ae98808a9fd000b5d10008026a0a3a8ce710f12d38d704b748aac579095fa76a2d4335fddc507c2b320ddadb341a048b3889aeb79855224b84c89aca69ef15716a965b1a3d4a416a2b932e53617d3f8aa3d851087ee06008301d4c0946bc1f3a1ae56231dbb64d3e82e070857eae8604580b844a9059cbb00000000000000000000000024434ebf296c2f9cd59b14412aae5c4ca1d5aad200000000000000000000000000000000000000000000386992ac084d52b9a40026a0a30a1900def92ba2d630ce8851016f200a6f0ab7cbb3726e856b80c8c7c2cdb9a046b2ebc2062888b195266b27c24b832d16c38a681097661e7c85dc918c659f74f8aa61851087ee06008301d4c0946bc1f3a1ae56231dbb64d3e82e070857eae8604580b844a9059cbb00000000000000000000000024434ebf296c2f9cd59b14412aae5c4ca1d5aad200000000000000000000000000000000000000000000d3998a945d035620e20025a00aef9d9d942121f0ec78f855f5b41a8f5012e9ad245adf3b19bd25a5e60d40cca07119f902bc2bf5ef3a30788ee545e4dbedbc52054c970409fc6a742281802831f8aa37851087ee06008301d4c0946bc1f3a1ae56231dbb64d3e82e070857eae8604580b844a9059cbb00000000000000000000000024434ebf296c2f9cd59b14412aae5c4ca1d5aad200000000000000000000000000000000000000000000135d111acd67564fac0026a0f1d8a696da6547e491e1fb89c642493f3ae1b80c1f2b7f48857940117b10e83da040b4b8557111d4fbfa45e8711e1856dacf6d5aacdfd83975d91f8507f6d2491af88a82238285104fe73d7882ea60940000000071e801062eb0544403f66176bba42dc080a400000039000000000000000000000000000000000000000000000000000000000000fdeb25a08fb55bfd8459411b7bb69bc388a2f1fb4ccf2e28a52739ba6e7246b8dfdd282ca06924a9f7387c9405b0ebe379a69a3fab0e5606254213b67646470d0d9ae36fdbf8ac82b747851010b872008302bf209410086399dd8c1e3de736724af52587a2044c9fa280b844a9059cbb000000000000000000000000444705e7ccdc6bf014f3e956e5c4eda78b59f79b0000000000000000000000000000000000000000000013ad784a9942cb78980026a0b2074cb4a3d2954203fb6dfdd75dbe2b4e581452c8111691b2a8903d64c741baa014edfd2a96f05c667482fb098da4df0d323ceaa03c1120fa8c3b11b1e6752cd9f86c03850ee6b28000825208946cbdaad85cf5f084c25d349972178e12e0f3811b8801cd3fc4be2751008026a08cce1042c48eac631c16b870576577bc6837ea9fc8aa8be4fe6085115fc1072fa01e5e0c39bb23d8a844d3fbb15a2b803db00d19f16db962c51aca9efac4735be2f8aa11850ee6b280008301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000f2b293e9c1cbef2316ba8a4eccfecc8ba7af123c0000000000000000000000000000000000000000000000000000000013fae61025a0fbdaa8990cf1562bc687fba0919bee234fc4943ba536959b5d53199b08e645eda03610f59eb4a9f1b264a41da1f4b75e6ddcfdddc25e51d319e8aeae9f1866e2c6f86f820109850ee6b28000830111709429bd07c50a2a2a843b309264361c9a91950ec650880a47e9a2ff200c008025a098d7a79510ec8088e40332b6c17eb825c85b0f7f5169b38f32b4176804ea798ba02103aa7c8c18724bd2266f56f6eb2982e59ce66fc7e815b96258f1733000680df8aa1d850ee6b280008301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000f2b293e9c1cbef2316ba8a4eccfecc8ba7af123c000000000000000000000000000000000000000000000000000000001141445026a0304b4214474adb7c012af51f66a557bd5e4dd5ca6384181b75718b695cc0ca58a028e921c3e0bde2b373d3327575493f56f2c38e938db5a3af79d9cf73ee5bf41df8aa07850ee6b280008301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000f2b293e9c1cbef2316ba8a4eccfecc8ba7af123c0000000000000000000000000000000000000000000000000000000015bb0ca026a05817ff739574af40bb6a2a1e3521c81d9feeac9e1581ea1862e1d1310e992f25a04062cbda0d6c9cfd1ec650ab2f566ad443a32ea185460ba7b848e0c707222bd5f8aa04850ee6b280008301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000f2b293e9c1cbef2316ba8a4eccfecc8ba7af123c000000000000000000000000000000000000000000000000000000002b181ac025a01db94ba187623de31c790f335151b554f6edc8c3d3ae31c4bd1ff87f76b494c1a02f2d366e315e0243f2c00127f5bb00e3306e6c56fd93d415588381e2636912d3f8ab820ee7850ee6b2800082a0f994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000030c6ce4559c9e40f79a21be12d8ac6a7cab5fbf400000000000000000000000000000000000000000000000000000000115987401ca0760fc54c11cf12f3a7cccb5340b4a625a926dbde65d482400de8eb9c2b21eb26a0725adba5a62c12842b6167cff941048910ac3bb0aa3502924d713b5648819d15f86c09850ed81dd19882520894f47b8ff418b81d929b1e2ab079ca96f9bae00ada888ef0f36da28600008025a0f3c3e987f3474dd3b8d298f9b127ac5dcd0d7728ce040da6cf148170b61b9008a06cd26c1e56d27009833ecebee4dcc7b69791476a64cddb9f660b39e9a79bf19bf9015103850e6f7cec008302a5e6947a250d5630b4cf539739df2c5dacb4c659f2488d87943afad18a21f4b8e4fb3bdb4100000000000000000000000000000000000000000000000038e62046fb1a00000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000c279a165c11340ff6262bd74ce7b328ca7d738d5000000000000000000000000000000000000000000000000000000005f7bf4210000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000bd301be09eb78df47019aa833d29edc5d815d83825a02f07ebc6959fac30dcd3d89d9a102f919086a2641afb6be871b021a956fc9cb7a0195f7d7e0b59d32190c2fd10c7a448d9a2ea40b7c72762a99c4613b690089073f901ac818c850e6f7cec0083048968947a250d5630b4cf539739df2c5dacb4c659f2488d80b901448803dbee0000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000000000000000001d690b82191f53800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000006795e7f4219a48e083157db6b52cf70002eced5f000000000000000000000000000000000000000000000000000000005f7bf45c00000000000000000000000000000000000000000000000000000000000000040000000000000000000000005befbb272290dd5b8521d4a938f6c4757742c430000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001712aad2c773ee04bdc9114b32163c058321cd8526a064c6e05031df9eb138b78b0e4d6578d17d2b17fda9f05d8ac921ed707390fa5ca06a826a830e4e740cca349d89f1afce04cd48cdf527780401ce28d235497734c0f8ab820c6b850e6f7cec0082715e94a1d0e215a23d7030842fc67ce582a6afa3ccab8380b844095ea7b30000000000000000000000009748d39b0b0949c0d5214094045fb7e2222a26ca000000000000000000000000000000000000000000000000000000000000000025a038e6bd86c77f10475337e7cd32c88043465d9cdfde5d1fdce4e778ae55383b3ea0767b31439035cc8499be4c407d7c1db7f38755b7cc8cbbc09065b689de0716bdf9016b34850e6f7cec00830290bc947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe500000000000000000000000000000000000000000000000007492cb7eb148000000000000000000000000000000000000000000000000000075567ee0995c7e400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ddaaef5d33f009bef641ee9a8054d0fa7c1e8d0e000000000000000000000000000000000000000000000000000000005f7bf45c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000028cb7e841ee97947a86b06fa4090c8451f64c0be000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc226a02daaf93f114bbe4209f1c082ead858afdc91ed29f2d5fa7866457e14a114d99ca022e100caeb5c78e445a014875c10b9bdbeb2828e1d7f2b35b5f0558f3d230981f9016d820274850e6f7cec008302878c947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe50000000000000000000000000000000000000000000006d3c17ae6ef8aafbcc90000000000000000000000000000000000000000000000000e718f3360c35d6800000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e53699169e2ce5c49dbb85d86de834ad4a4a9d6a000000000000000000000000000000000000000000000000000000005f7bf45c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000087c00817abe35ed4c093e59043fae488238d2f74000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc225a0747216c39ceee5fcfcf7981317eba8108952819d6d0cf1fc4301e2882d3d54d7a068555b7b379ae6a66f31f64616860db5be895ee5fb5e6e1045d6217769a80d00f9026e830efef8850e6f7cec0083017c4094baf075545c3a56ecbaf219e4a1b69bc2b94b0b7580b9020464887334000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000a7d8bd0000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000331005a675e1d000118e351dc07f3000000000000000000000000000000000000526b143b1737372ff3262608cdf5000000000000000000000000000000000000e37003f2f90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d600e2aeaa0800fc011eb527f90f000000000000000000000000000000000000bfa0eaf4ebd0c9d80c09da01490d00000000000000000000000000000000000017a1fd0b0900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021ba0e8b0c6d27b12a2cd8506f694a874547161c2ef5fb3134fec6415010124624e32a018c3b32b8c7fbd563c33025b712f4b311cfa10ae2b7d4e6a8df76721b0087b97f8cc822bf2850e6f7cec00830249f0940000000071e801062eb0544403f66176bba42dc080b86400000022000086fef14c27c78deaeb4349fd959caa11fc5b5d75000000000000000000000000000000000025948db5a78975a28a00000000000000000000000000000001000000000000000000000000fca59cd816ab1ead66534d82bc21e7515ce441cf25a049cbfb1f56c0a1555daf81f30121a791d149d1f4b5c9bfb278a070255d5bad65a0669c9a99b922a620ef5295e6f0b88bdc2619eb3ea9043d9ba784967e7c796721f9015482025b850e6f7cec008302aa06947a250d5630b4cf539739df2c5dacb4c659f2488d88011c640541017538b8e4fb3bdb41000000000000000000000000000000000000000000000001158e460913d0000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000003c8e49a61e91e6fdf7a56bfc4d6e347dd863ac27000000000000000000000000000000000000000000000000000000005f7bf45b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002a7f709ee001069771ceb6d42e85035f7d18e73626a02068b8d08937830226e99f3751ac9d21c381177b46f3cd7db65402655d4d8a0fa01a67a20e628a491743bee70f0dd3a573fca70c754d4b5b117052df173160b32df9015278850e6f7cec008302ad2f947a250d5630b4cf539739df2c5dacb4c659f2488d8801859bf17ded6942b8e4fb3bdb410000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000934831793baa281838c1e09e665a4f1406bc7560000000000000000000000000000000000000000000000000000000005f7bf45c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000de201daec04ba73166d9917fdf08e1728e270f0625a0b9eee4c06f560fa62718be16a004561f8822346db521eff8b2454e8768bc0f07a07e4161e5898bab99feb09b44cf6737fb7c5f1e3f48138d599a22fc00ed867c0ff8ac820758850e6f7cec00830102cc94990f341946a3fdb507ae7e52d17851b87168017c80b844095ea7b30000000000000000000000002c6a9c15e7f13de8ae55302bd29608c9eed78f080000000000000000000000000000000000000000000000008ac7230489e8000026a06feeea2d9505a20d7b2d10e1936d5bf9117d4b88e632c786322c3217dd916ae5a02e618e1517a44cb023837b64f1e902f0ba4a9a6a7d2d9806175a83f5d9ec826cf8ab81a6850e6f7cec00830381f9944d23c0537d7ee782cfaf9bbecdd60840e3d0ffe880b844441a3e7000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000045be7ad5bf65491d325a0a2b643fdec2ee570d216e2fce4f6030e7f7d162157808fc0e07c22da253e96b1a0752e21f128a8a64f7b79beadb3c65714505e9df52cf99068c7acf83a21e2d928f86b02850e6f7cec00825208947264c9f085858c607733daac1eef2625237fb333870b4fcc9ecd62008026a053b3489fc1005069296c19ee693c6e0ad78367ba818b930558b006beed0fa479a003adaa5da3158347c5141ca120dc4f2623e0fb0a90fab1129093fa665893e11ff8aa04850e6f7cec0083013d8c943fef5a74bcd2b8d2d44885b144df7f9d149b4b4580b844a9059cbb00000000000000000000000044fa1f6a271c6eb84227304e92813142758e133300000000000000000000000000000000000000000000001226788055013fc00026a0e2e6d5433b941dd559ff46677069c3689ac3dd5e28d12ee0287cb6ff3cb48bc5a010ae409467d096bc0c07fb1c4b2a3d9afca1e69d4842f329ccf987966aaceccef86c18850e6f7cec0082520894dfc105318661fc8241eb4d50275ad49bfd99903d88014a6701dc1c80008025a0c6301d39576a4f65741d63644701dcf63fb0dd8b8faaf5dbec353f3f64197d7ba0011557cc13411b8664590e2bc18517cbec0683bf4da982cad2f85cf3eed68fa4f8ab8201b4850e6f7cec0082914d9432ce7e48debdccbfe0cd037cc89526e4382cb81b80b844095ea7b300000000000000000000000073282a63f0e3d7e9604575420f777361eca3c86a0000000000000000000000000000000000000000000000001e3c5e6d667f9c2e25a09d6afbad3bdf0216cba142d92e752fd8c22956ba61fa642d2665e507e7a1192fa047403cec0c3d6bf90fa1c933907d80ec4668a605f2a18dfbc87255c8dda92ea8f8d240850e6f7cec00830454ff9473282a63f0e3d7e9604575420f777361eca3c86a880de0b6b3a7640000b864aed351470000000000000000000000000000000000000000000000000000000000000cfc0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000025a042d718cb1b80c7615c6ed21a5cf00b8b174f70eb1004005e0dfffc4fb2a4a090a07ceb1139767232185f78e822b78649d0d4123861205f8236b8e6268750048180f8aa80850e6f7cec008301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000040c266a51ced55caf390cb6e00b5b38c5736a79200000000000000000000000000000000000000000000000000000000b06d6f9026a0493eb28fb2ad6ed89ddcf5f2cc30f6432d503c69dd91d5ff0c990c9f4f188886a045bc2d33815b1765150e63ef106e5b4c5ad877b25c1419a122304b089572f15ff86d820229850e6f7cec0082520894bd7de91133e32ced22820aeb44a864b2ec25c7188711c37937e080008025a07d0a9d5cdb3f59f1bfb3df400acd0aa3112ab5948018e49024571d3254f7aee9a02f0a90d0eb973cf22f86fc9c4d15a1e6862f2e9cf2817d63a12380866938f93cf9013201850e6f7cec008302fbca947a250d5630b4cf539739df2c5dacb4c659f2488d888684fd111d7566bbb8c4f305d71900000000000000000000000080fb784b7ed66730e8b1dbd9820afd29931aab0300000000000000000000000000000000000000000000016cabe0ead394bc000000000000000000000000000000000000000000000000016ad91964884878800000000000000000000000000000000000000000000000000085d8cdb8b5a19d41000000000000000000000000e62d82f95e7fb1848b1a01a5972d4f7a0507a55a000000000000000000000000000000000000000000000000000000005f7bf43925a0305fb0ab4a1a1b73a4dbfb4ad525ca66b9fa64e737361343cf0881dfdf489024a0749ceb292f716bbf44ad3e8c5100da848a456086e45fb273d77accf232b8fd74f8a977850e6f7cec0082ad46946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff25a096609f8ab69bbb36e2862e2d6cef3fd7cf38535243737431aac84dcb7ee0507ba00dc3a059327e9248c684828dbce2b482843dd2b066058ba81cb3bb4a4fd5af16f8a93e850e6f7cec0082beb29487047986e8e4961c11d2edcd94285e3a1331d97b80b844095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff25a0d2e07ce83d0c82fa351da9765e8a9ba3cb6c48694e40f39cfd5adaf10a114aa9a03281be0d89b856c3b5615af37ea71e8a8860b9b626d71d7bccf082b99fc2861bf9016c819c850e6f7cec008301f961947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010438ed173900000000000000000000000000000000000000000000001080bca471a87251120000000000000000000000000000000000000000000000000fd6f74af9fc2ee000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000f13f7bf69a5e57ea3367222c65dd3380096d3fbf000000000000000000000000000000000000000000000000000000005f7bf45800000000000000000000000000000000000000000000000000000000000000020000000000000000000000002a7f709ee001069771ceb6d42e85035f7d18e736000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc225a0e901cfdeae3ac58d894863e2c810c32c3dd9d3c93c2a5c205a1dd1f860d51d8aa07454fb37616bb4897a4ff1aae0690c678b4039fc88cbb24b942767444027c3cff86c09850e6f7cec00825208947fd97df8b1bd5b29e97ccf09197fa86196b6a79e880de0b6b3a76400008025a09f2c471e5f9372d9d1d67ff2ad43a2a290cef47624e71c0d41e28a6e4b7ce7aaa05601a15d106d6d9ebbfcaa8d755c88d7ab4144f689d394b712a30ddeb4794d78f8cc82525a850e6f7cebff830249f0940000000071e801062eb0544403f66176bba42dc080b86400000022000086fef14c27c78deaeb4349fd959caa11fc5b5d75000000000000000000000000000000000025948db5a78975a28a00000000000000000000000000000001000000000000000000000000fca59cd816ab1ead66534d82bc21e7515ce441cf26a0dae920ba1886fd25c3749ebe20b67e39de3fce33227c1205a9fbd389652f8674a069100b5700683f1f58729cf795837c93a1ffaf8095368a82c477e287b8530b8cf87083032eb4850df847580083015f90940ea6bf3bf79c21703f34d021c2b7dcf76c44fa888804820fbd9230bc008026a0128468f13235769729c71c3929fb11127d400455ff314782c1508c3c6c273163a025cdb5a37aa93a72b1848940336010df7e27d89396a3e6d56dfd44dd373d4538f8ac824268850dd4841200830119ed94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000008d8478f898907e6251d98601f32a0c1a94d315580000000000000000000000000000000000000000000000000000000001a35a541ba092cf05068392bebbe1e7fb04c7afc1e8781b5dfa4aa8d09bce894e5d9353406ba00dd9f708bd64cebc57b26596c553d05620084aebab5c219bbf5db14ee071a0ddf901323f850dbcac94198302ea14947a250d5630b4cf539739df2c5dacb4c659f2488d882f24f966aa302b2eb8c4f305d719000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000047820d4100000000000000000000000000000000000000000000000000000000472685810000000000000000000000000000000000000000000000002ee8a11d3104686700000000000000000000000015fdaab0c0e130c7f0e72d7efab5e96f103f143c000000000000000000000000000000000000000000000000000000005f7bf02025a01199f616db8a6af136eff302e4dea17713ab7a1f82f4ca3967dcdd7124626098a01f83463019dadf0b2f4d6d60808be65f59a02169c0a267adf8adcb2eb644bed6f86f83010d82850dbcac8e00830186a0945dee759699e83cbc165fa0bf12f80a9ad11c6a0087470de4df820000801ba072ca22aa17cf7a155c6be34f2591ff378832d8db9331009272d5edbfb3b38453a010fd777ad7ccfee732efc0b9309f917495eabf8c59d6425c5719af0cdd42654cf9018c8192850d92f367e98302d223947a250d5630b4cf539739df2c5dacb4c659f2488d80b901248803dbee0000000000000000000000000000000000000000000000d8d726b7177a800000000000000000000000000000000000000000000000000000000000004186672900000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000834d399905c86b993354777ab858e08f6d5b996c000000000000000000000000000000000000000000000000000000005f7bf45c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000ff6ffcfda92c53f615a4a75d982f399c989366b26a0b0f7ae019099233e1a7a4fee1bcc0dc0e21cbe61e49f110cf582169f2808dfeda0663acd0f4b34b6d4471748bc1f1bd3bc39d615d725592816d5a57866b6170a07f8ad8314be13850d4576fa00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000009a6cc6e8a82f6f4e10972300d0b1094072007dc000000000000000000000000000000000000000000000000000000002625a00026a02f21d1dfe66cacbd2d5558809ec77b7c2fa772bf02012f823bfe860f112fa672a056a28ff19d10debc6eba7b0ec39a5c61e88deb0e0258380421bb48b1f270c3baf86d81a0850d4576fa0082520894104ff25f04d2ebcc602495fc5b4f4d422eb7975088016345785d8a00008025a08c8ec39beb6ff5136625b1d07d364bbcdb8f408ffbc65f01951a7b6491601508a0425709fcd2cdd064dd1196044283e0b8667635dd72bd606015301f95c3baf0c0f8ad83147438850d4576fa00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000eb845a45e0dc707164b55f04e6a15e967705ff2500000000000000000000000000000000000000000000000000000000743b3c6526a01f72ff693b4ccf22e1e091643d9b1434e90297e8164560c6047844e0e18f1c0ca02886161bdc412246a540b082900dbddfb7448f1227405b9b52f9b67a858f1e01f8ad83147439850d4576fa00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000003e8d29f9ef0b3b4bdc997c7e644d1e44eac5a185000000000000000000000000000000000000000000000000000000003d648d8026a00748de85240f9a86b2584f9c905f7239a9cb6e136a7e495f19f3c009a62912d9a04cc16a5a7497b42d45368e01d0d6348edad9f8dfc62b8f80c6d23a30fa96115df870830115f1850d4576fa0083015f909433f543df44521a4777f9b65756922b4a51b189598801bea209329540008025a09500efe32941004db60b82da2478379add6ed9cd9592e48ebd63dac7f68dd42ba05977faec04d591e3bb8c6be36e7cffad810e8fdc614f9d0c0a877ee4fec8822cf870830115f2850d4576fa0083015f9094b44e83a8d9aaddc4864f5105822dd5ff234741f7880b4d876ab52f80008025a037d89eb746412bb23c7bcb9090736bc1ec8494c4094227676065f48089d1f4f1a005d6a3a8def1ae1c671ab50f172cf0ea4c744a8a40a8d926a693578e16044949f8ad83128a50850d4576fa00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000000556c4e74997b8c2aa9fab24bcfa7671714124ac000000000000000000000000000000000000000000000000000000001e0a6e0026a039497e16a9268eae2309470719234e0d0585d8c98c61e4ba97a2a10f5a586e1ca070f0a295cda26024665e80966761bcf4c9d489ca8a8211c83763edbd6abfd615f8ac821801850c92a69c00830141da94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000097870b2951805078c826dec929a0a4e39c1e3ec40000000000000000000000000000000000000000000000000000000005b8d80025a0e40cbd82ebe816bd45396bc46035bbef4062358f71a6f808b6a13c775644b0a1a028b30761ba0479db9a2dcc965b9d6868eff9975f24dd9129aa6bc4f5b480e00df86e820e41850c92a69c00830186a0949469805c2450c797e94db7b684437883af5b26df87d529ae9e8600008025a0766d5f402b9cb8b770574d56a0a18e31a2858d953c02634035540e67f816e67ea0660cf0f8727caa2fff314e153021f34f08940f71af0300051eb9be94334cdeb6f8ac82013c850bdfd63e008301075094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000ea68673a2aa2d7845ebda48f68fece2233a3b90e00000000000000000000000000000000000000000000000000000000042c1d8026a03ddea161a13234d724e4d468935d97c898c1ac1d25498e32ccb37173ec12a9baa022f1e426f03dd02b67cea7068830628ea16df0a853e6a33a772d1b1a5b5215adf8b9829ae2850bdfd63e008304f58894000000000000006f6502b7f2bbac8c30a3f67e9a80b8510000d9c58600140051000000000a68000000000000c12d099be31567add4e4e4d0d45691c3f58f566337134075f5b5a0a94ac891c7b5ec5db5cfcf392cc04744ab87a4c37afd91680ef280b96ee21a026e26a091f853150fbed3d6ef89aa0eefa97ce96374ce267ce44e48b8e355aaae52450ea06d2ac99c554c8d922523541dbe7e961c87cd58621f90bc6d95b72f3bf103044af9029182122a850bdfd63e00830bc315947ee8ab2a8d890c000acc87bf6e22e2ad383e23ce80b90228865a6b4f00000000000000000000000063faee0a2cbcedc0102575c6ff17da38b1565c2200000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001847ecc63970000000000000000000000000000000000000000000000001c9eb028e55c1af0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c12d099be31567add4e4e4d0d45691c3f58f5663000000000000000000000000164a1229f4826c9dd70ee3d9f4f3d7b68a172153000000000000000000000000c04744ab87a4c37afd91680ef280b96ee21a026e000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000078d65bce7dd600000000000000000000000000c12d099be31567add4e4e4d0d45691c3f58f5663000000000000000000000000c04744ab87a4c37afd91680ef280b96ee21a026e000000000000000000000000000000000000000000003b3558c3d643af5e8324000000000000000000000000000000000000000000000000000000000000000126a0aa58ab93b2fc330ed5fd4d5e9e5165877fc517bdc944e03f9b107de1d558fd9aa010272681e6e57c3aa74f719f526873c81e01d01222157448e913f62ab9dcd06df8f6827823850bdfd63e00830a4cb894000000000000006f6502b7f2bbac8c30a3f67e9a80b88e0000bb6bee0051008e0000000014d1000000000000c12d099be31567add4e4e4d0d45691c3f58f566300000000000000000000000000000000000000000000000000000000000000000000000000000000003b0116363e435d9e4ef24eca6282a21b7cc662df164a1229f4826c9dd70ee3d9f4f3d7b68a172153c04744ab87a4c37afd91680ef280b96ee21a026e26a0c4e7826ef9776170a23747941d8e2031e1d632cf4bd0860d00c5ecf14f0fac4aa03a4ab912aa4b0bafeb1251e62a686c05d01856725c2bc9fcb9d514b16e8c3efcf86c01850bdfd63e00825208947a3b3a48da48dfd7171064c43efca3eb88dd83bd880a804988f69b5c008025a07ab7ce5668879c2a8b2271e9899006d1f955755bcca0e84c7aacc87f7384d8f5a001c09a314305066a7a99b0757c3fd34306fd740c3fe676ce9b779989027002def9021f83017e4a850bdfd63e008304f58894e33c8e3a0d14a81f0dd7e174830089e82f65fc8580b901b5000b060d0713abadabced27fd1eaeaeaaf0fd72adb0120000000000000000000007fc95945eaa14e7a2954052a4c9bfbaa79d170ae000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c12d099be31567add4e4e4d0d45691c3f58f56630000000000000000000000000000000000000000000003643f0f256d9376e9c40000000000000000000000000000000000000000000003520346458fd696cc0e00000000000000000000000000000000000000000000000018a893fdda355e00012000000000000000000000c04744ab87a4c37afd91680ef280b96ee21a026e000000000000000000000000c12d099be31567add4e4e4d0d45691c3f58f5663000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e33c8e3a0d14a81f0dd7e174830089e82f65fc850000000000000000000000000000000000000000000000000000f539c0ca6be80000000000000000000000000000000000000000000000000000e92936feea3900000000000000000000000000000000000000000000017cd98b0be009d000001ca05422af299139186ec365f14c427205bc76e7b41d7d6eaf14183a282d41fe90b2a00cc85b86c76ce5d6f9cdcad085624d82476baf34d6c38afb99c8db0ee02a906ef8d48260ee850bdfd63e0083061a8094000000000000006f6502b7f2bbac8c30a3f67e9a80b86c00000bb276002f006c000000001bc1000000000000c12d099be31567add4e4e4d0d45691c3f58f5663017fc95945eaa14e7a2954052a4c9bfbaa79d170ae00071afd498d0000008ac7230489e80000022b1c8c1227a00000c04744ab87a4c37afd91680ef280b96ee21a026e25a00e84a9a46e23d619837c594d3192cea4d9fa388066a4d5b100f79a17a9b45928a06f82d747d441daa4de0c573baf56c442c53c615021b5e9e23d25a3f985d5cc10f9015233850bdfd63e0083026b7d947a250d5630b4cf539739df2c5dacb4c659f2488d88243ea5ce72ec5d47b8e4fb3bdb4100000000000000000000000000000000000000000000021e19e0c9bab24000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000c281e970225cf3bce07d9024b4423b1fd3df0e29000000000000000000000000000000000000000000000000000000005f7bf4390000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c12d099be31567add4e4e4d0d45691c3f58f566325a053005935343bd6b8355c3559ffcd855b30936a0386fbaa36b04aecb6b6fd51c8a0670215bcb30cb573ab8b4c424323039b00fccd611437dc99708c4a93769590f0f8f8821662850bdfd63e00831050879478a55b9b3bbeffb36a43d9905f654d2769dc55e880b890c89e4361bdb09816eaff92a339048a9119bfdb7f0b848b42628642fe0abf20dcc2c82f28fe4f3e406928c22938f493207e57277a8b483b3b122dcdc4000000005016c281e970225cf3bce07d9024b4423b1fd3df0e29470002b67d020000000c427fc95945eaa14e7a2954052a4c9bfbaa79d170ae000000c12d099be31567add4e4e4d0d45691c3f58f5663550000011ba074a4ce83493116995c8eb5746c1068cdd6422514ad83ef16f9e113c69bdb2b93a03002e2ca1e6e7ad945af6eb93d7a4b1d66c8e24efcee9923a565a4619f5640e6f8ab821a47850bcdf49b0082c7bc94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000948045ebbc5fe5ded30f2ace84f99ce1b9fc30a900000000000000000000000000000000000000000000000000000000001bd4e325a083ebfa18687b06c006e7db126cc80855081a2b9954aa2ef84614c467276a1e08a01be36d428ed259ef55ef0cf93515fba796518b62c2a101bb3dcda2fbef6e20a6f901ed820182850ba43b740183061a8094a57bd00134b2850b2a1c55860c9e9ea100fdd6cf80b901841cff79cd000000000000000000000000728258295b1ccbc61d72c4e86c5b44345ac790ce000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001042fdc7315000000000000000000000000c2adda861f89bbb333c90c492cb837741916a2250000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a200000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000228a17fe49a0620000000000000000000000000000000000000000000000000f69c471cb506f64bd8000000000000000000000000000000000000000000000000015c808740b32a90f000000000000000000000000000000000000000000000000000000005f7bf0340000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000001ca05df7b3acaeb55dec2fb125f4261814983a0f4fb24696bb3c02ecd4bdb8664ab2a0634cd17f4bd2eebd956dcd1c60c882483dd145f36738802d1540d6ca0bd18e6ef9015482016c850ba43b740083026981947a250d5630b4cf539739df2c5dacb4c659f2488d8801028b9db6d1e430b8e4fb3bdb410000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000277d46fcbe71144c6489d182a8aa00da61b57b07000000000000000000000000000000000000000000000000000000005f7c43e90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000014c4d8e57751e083bcd1edcca5b435720166f5e925a00985497b640cb6f70739b068292050a427b6e0c908a9d29422582a9214a6003ea0415cb1f10d3b6f2a201ef340dbc8ca8c67171e7422cd315bb9fb574d36111317f8a901850ba43b740082ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000eba638932d82ebc1161eb9a992572ecafc197f7e00000000000000000000000000000000000000000000000000000001b9df78601ca0f0f487cfbb3896e83daab39082d361b5df9c6b7af1b24c227268f23c886c6ff7a00db21e461dea4897cb626c4aa60df3979aba5ab4e89971b962ecc59729403e1cf9015258850b2d05e00083037033947a250d5630b4cf539739df2c5dacb4c659f2488d88027f7d0bdb920000b8e47ff36ab5000000000000000000000000000000000000000000000229a587e3e68685925200000000000000000000000000000000000000000000000000000000000000800000000000000000000000007562be2022d31a75f9887b7b932256c704f0c8e7000000000000000000000000000000000000000000000000000000005f7bf45b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009b06d48e0529ecf05905ff52dd426ebec0ea301125a057e78766ae798e30b06e2ffea600bb64950ee8498790fae4be4232a58a7dab76a00bcf0307bd910dc4eb8a0f59c08c38941c951ca5c176e394c20e2802f0ec0569f8a928850b2d05e00082f17594dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000005f053acac0d67f0e6d1cc7a5284727ddc108a56400000000000000000000000000000000000000000000000000000000389fd98026a0af5ebed76724a1a2ec0480f7a928c758f105039ece0c9dd358fd36ff89148d96a058f4978b9b948fc259616bbc48bff2d42b201af89d3920f730c9bda89bd95ad9f8aa3c850b2d05e000830320b394de4ee8057785a7e8e800db58f9784845a5c2cbd680b844a9059cbb00000000000000000000000038e3c92a543ed6c87eaed33bcba04b9d302912200000000000000000000000000000000000000000000004d12beac3ee39f0000026a0df7786f7994c5546becc6741ef476cb6d1a4b07cdfc95ce9d4c54973b86460baa07a32b0a60ca94b0902b6a2640ca23c65daecc540d4c60662f8e1488373659117f8ab819c850b2d05e0008301494794dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000002ebd488b3b362228c503afc7cdf1698f37722fc200000000000000000000000000000000000000000000000000000000000f424026a0ac0838acef8e84034d475c2e2ab9b194ca0a29ad1550a69aabc497e857373d96a03ba2143fd0ab21a2ca741b874ccd2db3b6dcdf36756b7b7e678b90157e068f4ef86e8319ac74850b2d05e000825208941c6cc4f378266a1ea89edf1f92fa529869571a18870e90eda39440008026a0f1885607a6a384eeca3f2a221cbae0a7d1be32189a26e3f6024f1013b6458a66a02eea3bde50f2a790547d02e53f21a721131cc0d6823a7a0f104a1d2e027bcb9bf86e8319ac75850b2d05e0008252089498aae5c4097f803a0616b537a4f62bb25ad837ec870e90eda39440008026a08da329c004df7420e2065f1344a77dc6e2819b5fc5db5812c362af58596989e5a054e3f72cfedd01491d81ac512663d1bc6915757f32d12ac65385214dfa6b8390f86e8319ac76850b2d05e000825208945b879b10efcc5400c4b5931be5e577c8c97593d1870e90eda39440008026a02260ca75dc848b82e2e24e00de3dd0bea9c664adfde6805a791c77c16dca03f5a0225743a8e187c5b54148cb099920874a99a9e761b2870e46a3f9bf4d60fe9e4af86e8319ac77850b2d05e00082520894ec0ae94c1c03959118a66fbb43fc39bc2c1bb63b870e90eda39440008025a08b750dcdc29374b2cb70f517ca646f5725b820a0acfe10b9e3a216a652b2f54ea0765bc5c251ee433c85ab35bd130bba145df8511fccce743adafc46722d4da752f86e8319ac78850b2d05e00082520894558d7cdf026486652f3d94b2c860c9fdb9b05fae870e90eda39440008026a0bad87f9413c9d85e5fd708e81d62f68d8c34addcfb8e269594573c6f9a9427a2a0672011774c1598a0099b20bb3426768a96ef8962de389ecb94d6eb7e0d5f0f81f904b375850b09429fca83053d2b94dc6c91b569c98f9f6f74d90f9beff99fdaf4248b88745c7039d837c000b90444288b8133b1d717c9fedc8e8f40fb977a4a887e7f3b9721a403109441c45dac7dbd42d537000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000032464a3bc150000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000745c7039d837c00000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000d0e6cbd9da5d6214818a3ca2c4be074174faea8100000000000000000000000041f8d14c9475444f30a80431c68cf24dc9a8369a000000000000000000000000b9e29984fe50602e7a619662ebed4f90d93824c7000000000000000000000000dc6c91b569c98f9f6f74d90f9beff99fdaf4248b00000000000000000000000000000000000000000000000000000000b0de47ff000000000000000000000000000000000000000000000000745c7039d837c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f7bf6caa989bdc492903b542331fb370f3307fd1b2b2bbabf59c552db34fd50dcf47be5000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000581c0d6b99cda91d02c52cfe70cdee39e3908e10733ea0c7bbea96e4bd0cf1eb3c9a179df6e905e92a575c6d947671bfe84aa761eca6ffec506e310223bd236d6854da8d98e2a1a211130e940c71f2004885e8c7810800160400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000561c4cce9f6578c835fb47c7b5d6fb1b4d4f5f815dda45bd98d920c70577d1f063fb3a80eacb0dddc9730cef6735e23abe58b2747d3173eb0f75ce1ca86d3472bb01da8d98e2a1a211130e940c71f2004885e8c78108040000000000000000000025a014fad5808e4a9ae55ab8dd2c1807fa7ec2c1d4c15bea056922b2e791b6264941a047f50bb736001dec7314f4ea345412053d147442a4204236d5f27d3d69ccae6cf8ac820244850af16b1bb383030d4094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000ba6f0cb6688748e1dd48d1bd5924f930bd164ed2000000000000000000000000000000000000000000000000000000000cc6438025a0b5374ec5198978e5e9e362dbe3dce2a7271bbb466ae3aeeb4fbc7fe845089986a02110277cc3dcaf906ef487fe923f0c36347415a31425449d0edc92c9777d5782f86c1d850ab5d04c0082520894c0631a2e7e8b96f7a22e117560b39c9e908a6da98806f05b59d3b200008025a0196275ae3606075a8f23e705b9c8438c0fe9aba2cdac79ca0fbc52b456826ae5a003071f97a20eb4e372adfe0261bc123f31dbe1fdf431749281a8e4626e6f9ddef90d8b04850ab5d04c00830357e8943b5d2b254224954547a33cbf753bcaa5eb4b27bd80b90d249ec9b36b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001c5d932241d20c7a72a42bc6a023437347bb80940c45e7fc8c4c5e6f1cc02c591b1c7b04f95c1be906d5354ad4d3458e97a04a9ba7a45b1303756728eaa0d45b7f000000000000000000000000000000000000000000000000000000000000003200000000000000000000000007bae6e785f8c9f5aee6ba49cf72d19d5e412707000000000000000000000000000000000000000000000000ac7b5695e7e7f530000000000000000000000000c31bf9a4969a3ad0f2b9823317b329125bd3e923000000000000000000000000000000000000000000000000abc29874867d4bf8000000000000000000000000d1e6fc718660618d73d458a77bd0339f6c2f4212000000000000000000000000000000000000000000000000a98966b8c60716b000000000000000000000000066b064b5fac00eeb32cd6bce64bbf5fd98581b08000000000000000000000000000000000000000000000000a8fc4c2e8afd20f000000000000000000000000061f1d048323de4aa996290f63a1e2a29636a3d06000000000000000000000000000000000000000000000000a8d7cb23466da910000000000000000000000000ad1b4d6d80aea57c966d9751a5fe2c60a0469f60000000000000000000000000000000000000000000000000a7a6751221a3ac90000000000000000000000000bdc7ac031671bd67190b09a8c3cb1e7872b6c6a1000000000000000000000000000000000000000000000000a7883a2f27b3c020000000000000000000000000088b99442b893a861db16e5c8c6a01a66d0c3ada000000000000000000000000000000000000000000000000a6e441d4b6183960000000000000000000000000dcf54138acc74e9b99570af10328e89d97d5512f000000000000000000000000000000000000000000000000a6610325fe2ec410000000000000000000000000d65e87f756d0f21faceb8d32eacc509c1284f25d000000000000000000000000000000000000000000000000a62aa70b1f6f1f6000000000000000000000000037a33fcd214405e74ed93addfc7f0787326c4f40000000000000000000000000000000000000000000000000a445a47aeb3c1b1800000000000000000000000068799b81c7bfca8c661197645755ac33da8ef4ff000000000000000000000000000000000000000000000000a3fa48f31df4cd6800000000000000000000000090496a8e1925ad4a1931ca2be0e50480abc24e8e000000000000000000000000000000000000000000000000a2f5c981c11bb8e000000000000000000000000097cf2bcd41c323940022bf8906da9011b8271ab5000000000000000000000000000000000000000000000000a289933188fd0790000000000000000000000000945fe27749f564173237fddf47749b676a14111f000000000000000000000000000000000000000000000000a1088c0a7dd222c0000000000000000000000000b21763ce87472dc76fc3b485fca9d57df5d47bfd000000000000000000000000000000000000000000000000a0b781dfa364764000000000000000000000000067513103973cf85cb20f2f8ebe61cfc1990d3d3f000000000000000000000000000000000000000000000000a0820178a791d2700000000000000000000000003b2e5507929f9b794f39acba3f0311cc636390140000000000000000000000000000000000000000000000009f93541fdff84f4000000000000000000000000080c5cae1fc0b6a548ea16fe40171d01555f4f57e0000000000000000000000000000000000000000000000009f8a6ff0dc0416b00000000000000000000000003d16451a4b73e778bfec126025ba79716a17e32d0000000000000000000000000000000000000000000000009ed512db2aea289c00000000000000000000000033debb5ee65549ffa71116957da6db17a9d8fe570000000000000000000000000000000000000000000000009e9a91a4b0b0683000000000000000000000000024d0db7e182fcd29ddea14c04bcb182c89cbb0c00000000000000000000000000000000000000000000000009dcaa8e3be581688000000000000000000000000952ab11461fc27f65175b0469eee9f0f5af3a9ef0000000000000000000000000000000000000000000000009d3748d8c22dc31000000000000000000000000061af6c7925cb106ea04fb3148affba6220bda5c20000000000000000000000000000000000000000000000009c6cf18620f1e0180000000000000000000000002db3c0f42022fdc8dfe70036fee85e48a24b88af0000000000000000000000000000000000000000000000009bd0fa53e03389700000000000000000000000002fe50c88f228dacfc24100de0c5167aa7a539dc600000000000000000000000000000000000000000000000099d85913e41be54000000000000000000000000033d66941465ac776c38096cb1bc496c673ae739000000000000000000000000000000000000000000000000098e8c47b582093f00000000000000000000000009e75d5cf5d6a1970e47bc3d37f3c21a878c321e900000000000000000000000000000000000000000000000097c535c877dce088000000000000000000000000852bab46dd2dd7a585d23a79a8600c2a65fcf2e000000000000000000000000000000000000000000000000097811829ba6749d0000000000000000000000000bb9297f03e92ecefe1a45bda0e585fc6f9fab41600000000000000000000000000000000000000000000000097811829ba6749d0000000000000000000000000dabc521955476811ed6d39d7d54224c37bd8acb000000000000000000000000000000000000000000000000097811829ba6749d0000000000000000000000000eb5831daff60bc27a3af7ded164ce159dcac035100000000000000000000000000000000000000000000000097811829ba6749d0000000000000000000000000f4317d52acdaf924329c8a15c37a9273e2a3797b00000000000000000000000000000000000000000000000097811829ba6749d00000000000000000000000006c31b772cc57cac99c85d2d0b0df8e54b07a7a5500000000000000000000000000000000000000000000000096b154951fcf8310000000000000000000000000ea94c9a3bcc2bf5a3c08e3de1ccc64c6b604971000000000000000000000000000000000000000000000000095585cb6c4282a10000000000000000000000000e5ae7e62ee71f675f2fe0ff5cc83c215a44e76d100000000000000000000000000000000000000000000000094ca53acb0c381500000000000000000000000000db5e7924d87d2ec27876882e7af911a58050dd1000000000000000000000000000000000000000000000000932042d59dfabc70000000000000000000000000c2d3ad9240a85cbdbf2e77223185542996f192eb00000000000000000000000000000000000000000000000091ad5c281f8ac6d00000000000000000000000001fd252771bc2dfd38ec774efeeb3c6ab7e8e535b000000000000000000000000000000000000000000000000915a77a49303dab80000000000000000000000000b946efae53975b97a0d1d02f75fabf55d0d6a960000000000000000000000000000000000000000000000009082d22b97189770000000000000000000000000e07e487d5a5e1098bbb4d259dac5ef83ae273f4e0000000000000000000000000000000000000000000000009074a6a89f402e980000000000000000000000001fc022c33332bb9e45f3ff8129a51f03347f842f0000000000000000000000000000000000000000000000008fb33fc665fa01a00000000000000000000000006726b6dc15eaef7a530cd9e27a5027fff66a46420000000000000000000000000000000000000000000000008ec37e27521c85500000000000000000000000002fec5d8835b39f314187dc1da933b273f32f18c30000000000000000000000000000000000000000000000008e2577f5cf5c0b700000000000000000000000001f4d088464a0175c8ddb90bc7a510b1d5a0da1a60000000000000000000000000000000000000000000000008c7de56bd4e5a0580000000000000000000000000c780749e6d0be3c64c130450b20c40b843fbec40000000000000000000000000000000000000000000000008c14be8f739d98a8000000000000000000000000291502048a3396486cded59288df83fb9cc9d1cf0000000000000000000000000000000000000000000000008c0d91180c2789f00000000000000000000000009e199d8a3a39c9892b1c3ae348a382662dcbaa120000000000000000000000000000000000000000000000008bd9a02684ae1cc0000000000000000000000000c13ec844eb19d6a72ddd5f2779484ba35279a8170000000000000000000000000000000000000000000000008bd9a02684ae1cc0000000000000000000000000970c7ab1ca32547eaacdfcc89f6ed924600da8670000000000000000000000000000000000000000000000008bd9a02684ae1cc025a0d7be5b752c62db2acc078d5ec60fdf525d8d1866399827772c7b97c8b2996b7ea0390cf16c61b37128ad6e8e56a36190e11de890f326ba7014b82fe655f08cb37ef86e8305985c850a567242d6825208941d86c8a33c7b3b6c7f2fc580e7a2b076c49cac75870118a797e62f6e8026a029ce1c2f5f6e95826e3484b579a32b14088966c802120a7f64051d64188683f8a044a9a1c7f2d05971808a1699b2d21e1240dd5e99ddb716cd34be9ebc23e17ff3f86e8305985d850a567242d68252089499ab81b3e74800ac05958cf8e8ebb9d3a3f994648702c8cc350d8c048026a0f06dc5296371c0503dfd1a8c7c1372f23bce4ded60a992e348df49116241b12aa0659b3d14f8a3a9a26bbf8571e9520c8f319977a368c665ced3708daa18074703f901ab43850a3e9ab8008304fba5947a250d5630b4cf539739df2c5dacb4c659f2488d80b90144ded9382a000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000001e4bc4bcbad7000000000000000000000000000000000000000000000000000000002ddd72f10000000000000000000000000000000000000000000000001e3cf46b06880d66000000000000000000000000ce156d5d62a8f82326da8d808d0f3f76360036d0000000000000000000000000000000000000000000000000000000005f7bf4390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bfd21d58146585fa30ccb92362c65c6e09d6b9ee2bdc77e87f02dae12152a13f72e3c0ba57b03f89e25d5ba8bbf4f945ac5b8f1693b7739f3d802f640876b682326a062147cdc091cd1f38684508540739bcd6fe25c07e3bcec27f88edae81e0c4753a0569a6a8f75bbac18ad6a97ee205aebafa30f8c31d53d0f582d6630c0eee3babbf9012c820539850a02ffee00830493e09451b4b27a7bd296fd34ca7c469f49d5bcd7fe513780b8c4942d468b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000007c5e7fe5fbef2a35b02e1bce0d39e0076ec389a30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000c9c653445750800025a013e64eaee87f711f381bb4a6c240ebe85489843050872b076872b512f7c809eda02864404d277f8dd792b0871b2873e086725661906c5cb606e93a1a4291261328f86b82040c850a02ffee00830493e0943388a0117f0c5c7f493afdf46ea03fe7445dab6980843d18b91225a0125032c84dd6e8d83c39daaeb652d36590cc3fbc36924b2cdedd080a70f5d819a03cb2f9762d738d5140831726d20d039e83a0b12ed93e28d0f5563737d1a043b0f8a901850a02ffee0082e74994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000006c8d7c88371e673807372d7d3eb84101905be6cf000000000000000000000000000000000000000000000000000000004510cee21ba03e58bb7a78dcfa7ed656e565fe79ab2ff5787e6790d18abcd8db3151f4bdff32a024087d8b67f5a2c7f4e82d73444b7fe59d29128fa3e69a407da7ded484f1633af8d2278509fd0a0d00830124bc94006a7fab93971acdcfd5e465c29d714ea0d530b5880de0b6b3a7640000b864ad65d76d00000000000000000000000000000000000000000000000081c4d9b0b73ea666000000000000000000000000000000000000000000000000000000005f7bf45c0000000000000000000000001764dd87455540b164f45edd8d1efabeba26d52425a03359ecd73c9462378f22b31f6d3cc9cc0f4689d2748baf03a15bce87d66f826ba00ce062c7e1e797ecdee2f97625959230607aad8f182d44c367e855e26647fdfff86c028509fd0a0d00825208949187a1b5022ad5e801c474589c3d683e067467c188058d15e1762800008025a0151e70852cd730f3a3069bc8088f0caa6dec4c16defb7cf78776b0a55477641fa0709edc84332329640ba64a75b28e36c2bd9472cf251f10ae679da3cb0a8665b9f86b038509fd0a0d008252089469ae0b74d23a741a25a6e997de6418f374a0cf4d870c8304402804008026a02d86bfefc7eea31f1899d12e71d8ad9399c4a74ccca7fe43218a485858dc6bf3a00c93da0de2cfeda1909b651cafc831f28ab568b18a195bc5c4538ed09094f372f901548202848509fd0a0d008302a6b4947a250d5630b4cf539739df2c5dacb4c659f2488d8820b00eea5820b8beb8e4fb3bdb410000000000000000000000000000000000000000000000001bc16d674ec800000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000373d0ed6a697846661c3b0a0b9b94577c564c79b000000000000000000000000000000000000000000000000000000005f7bf4690000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c34ef872a751a10e2a80243ef826ec0942fe3f1426a0b9c2f746418b791458f6a3437cd5c73c486373a6917c2e6def8d938539aaa492a03e9771211cee9602321727096da87c4b78b086a5ea0d08fe6b5164217333eac4f8ab821b878509fd0a0d0082ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000003c707feb77ee94b42ea0544c6982865b651ac9660000000000000000000000000000000000000000000000000000000005f5e1001ca0d26fff2eaa7936817c0d64c73dd405a2671e6db3f9d5d38adf58312f035cf734a0418044c1fe119a29b5b280dc352c96a70d1ebc1a31e67dfbd4d9039f063f10c3f901527c8509fd0a0d00830243a1947a250d5630b4cf539739df2c5dacb4c659f2488d880d2f13f7789f0000b8e47ff36ab5000000000000000000000000000000000000000000000000073b44eb80b69e320000000000000000000000000000000000000000000000000000000000000080000000000000000000000000579c90f77ae0c44ea7ba329c41a980d36348d8a7000000000000000000000000000000000000000000000000000000005f7bf45c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000005befbb272290dd5b8521d4a938f6c4757742c43026a0c9e13f207c1877b89b510db359d494bc9a46cbb54ecd7227eb28c8f101decfe2a05ada94e4ecce058759c82d417fb1135c93d878f3e2143f73e88a2741feb1a853f8aa018509fd0a0d00830132b294c05d14442a510de4d3d71a3d316585aa0ce32b5080b844a9059cbb0000000000000000000000006a43c606609fd2f10b5e04bd199c311f2381a26a00000000000000000000000000000000000000000000007062f534e5517b000026a0f41544dcc58b2f6c501f4d339e8019c1c05cdd8a95eea59a11a67564e06c16f0a070ab365c8e8138326e309989eba25093c4731748d7449f1903a403118f2ff62bf9016b558509c765240083029c35947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe5000000000000000000000000000000000000000000000000660fb7013f9c7b1a0000000000000000000000000000000000000000000000000cd39e0af9d7081f00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000f68b6c658087cc45a32afdb8c38509f6eaa84b0c000000000000000000000000000000000000000000000000000000005f7bf45c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000038acefad338b870373fb8c810fe705569e1c7225000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc225a05479d4365dc72b124109ddf48047e17ed0bd6b111dd72343dc57f3f0b23df5fda0544dd5bde89240cee4ecadcaaf71ece54053a68646fe98203b812cb5d9e6ca87f9018c81968509c765240083030526947a250d5630b4cf539739df2c5dacb4c659f2488d80b901248803dbee000000000000000000000000000000000000000000000091fce1efdfdef40000000000000000000000000000000000000000000000000000000000003185259f00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000b15dc08b5d8a0c596c1f7970732ba7e5074c9ebb000000000000000000000000000000000000000000000000000000005f7bf0b50000000000000000000000000000000000000000000000000000000000000003000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009e78b8274e1d6a76a0dbbf90418894df27cbceb526a02fbcf47980497d578369380634d9107bce7e5e00e3db4335a46513a054878971a04abc8b37dec7aa082b6fcaa84577838fb08fccbceaff700be48102b5d6c17153f9015482019b8509c7652400830283bf947a250d5630b4cf539739df2c5dacb4c659f2488d880de0b6b3a7640000b8e47ff36ab500000000000000000000000000000000000000000000000274287b3e75d1707c0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000f3c31bb059036a9cb0fe4f516d88aca7a8c0ae0f000000000000000000000000000000000000000000000000000000005f7bf0240000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000fdc5313333533cc0c00c22792bff7383d3055f226a0c263437091b1b6470cefea0b3e958b1df35b51bb14211dfb217a0ab42cd8686aa02c97da3139e224a50a66d0d0256a463e6fbe5c3e0eda7d5c4f1a156b07b02bfbf86c148509c765240082520894cecea3a4cc75bdb5681e0c1e9b9ef09a8793bba7880780d514a93f0000801ba0ddd15b761f33f327617511150ef88572812500f1b1720d124b118e39b8ffe000a051cec8a670a822bd6faabae5cdad84d770f97dce67a654bcfd5b65caf3aa167cf86b028509c7652400825208945cfe24f2912ebb0fbbde9d70e6c495e69f58a58f8725ec578c01e8a18025a0e755e96f5d27937fbe5a171c2601df09076a1cdd21f1717a1042bf8196b19af5a03d784c0743047be9f39af71cb3d0eddb9c1e89d4082cba5eb39cd6ed78a0a4daf86b0c8509c7652400825208940adca8d84b20b30a8a7d4ed207ccad825c0cd39f870aa87bee5380008026a09129654f51f4fa187ebf9b7af4b95bfa25a1135f51e1fd247ef9c0dd3046b527a0464e5c2b13f0e3108a67a3201676a00115770a915b2cd8a42e887cbce681b3e5f90131038509c7652400830308d3947a250d5630b4cf539739df2c5dacb4c659f2488d87071afd498d0000b8c4f305d719000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000ac6eb00000000000000000000000000000000000000000000000000000000000ab91f000000000000000000000000000000000000000000000000000711e4fb1a60000000000000000000000000006c015175b6fa02f93ce0fe99e13554fa6a104c59000000000000000000000000000000000000000000000000000000005f7bf45c26a0654606132d677aa2b799c7ff664581d1fd7e10534c7138fbcc871772e275ae46a0250615fdc003986fcda12488d162770f98973e583ab86b8c07132cd0fed93272f9016b028509c7652400830290c1947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe5000000000000000000000000000000000000000000000000013fbe85edc900000000000000000000000000000000000000000000000000000180b9cdf65f766d00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e9bc563691fa5adca0dab7ed8e7c1341277ed4c2000000000000000000000000000000000000000000000000000000005f7bf45c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000009dfc4b433d359024eb3e810d77d60fbe8b0d9b82000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc226a05f5fc078448ccc652abf2e867870bacd9c4f78b10fb16dcc5597170d9d04f7a0a07ec8ad3754705ea33324ff8991dcaa36b8ea34722f506dabc7ed6733f3847da0f86c238509c765240082ae3494f22953567a8807848c36ee02576694649a37f645880bcbce7f1b1500008025a099c7734ad747ff41d40e3ca37f28c1e7c1cbf11bf495fcfc7db4161f76901aeda02c08079a11a875b06bd38b9db97266613f83b2c173462185e91295500b8c86d1f8aa3685098bca5a0083015f9094514910771af9ca656af840dff83e8264ecf986ca80b844a9059cbb0000000000000000000000005c985e89dde482efe97ea9f1950ad149eb73829b00000000000000000000000000000000000000000000004f78d640cc92a8000026a036462ef4f7dc79093050f26be02535f20064fdedcc456c27c45b4e4bcd3b186da03ac78299efb26baf2e54276886e2d726c6459b7d67e614f63f549c9cc6b29cc4f8aa0585098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000fdb16996831753d5331ff813c29a93c76834a0ad000000000000000000000000000000000000000000000000000000009fa9def025a02576ec6be227f2ccebe0fe03d1c89e0c6e85a4da440c13006662ef3b79f246cda01b207e62214fedca6621c32f8f9b50034bb76b73a9a196dde34c737c7591976af8aa0385098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000adb2b42f6bd96f5c65920b9ac88619dce4166f94000000000000000000000000000000000000000000000000000000007733b2c026a07c2de322142a5037fe2b0e849903883af9f7801b64b12049f2eb31fc6b4fc679a04287a9e2e1bf0238aac81b0bca1c6f2e147cd43bf9ef219f4eddd424504e3ed0f8aa0385098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000fdb16996831753d5331ff813c29a93c76834a0ad0000000000000000000000000000000000000000000000000000000077326f5025a0bc3b627348c07ceaecde8c20ae369ed4044435eadb98fd730d39327b03110ac0a078cff03080223c0fc2d61652c62d4b77fd4e2662338e1fd6df25738d482d45c8f8aa2185098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000fdb16996831753d5331ff813c29a93c76834a0ad000000000000000000000000000000000000000000000000000000007731273025a009d0269469b831785c4daa4f705547f3379099988f0409427a6b7aebfefc8b07a06004458520a81a83c5603c0ec2717660dfafa2f58c8411d237f44fe1312d2628f8aa1585098bca5a0083015f909432d74896f05204d1b6ae7b0a3cebd7fc0cd8f9c780b844a9059cbb000000000000000000000000e93381fb4c4f14bda253907b18fad305d799241a00000000000000000000000000000000000000000000014fb68f5d03c265400026a01855cad304a3c245f7635998e739ae70924444b10337677c554369b25ee76e95a00ec7988b478d3e0aa4e7a434da8928e413efc073108988091fc817d624d9a2bff8aa6985098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000000a98fb70939162725ae66e626fe4b52cff62c2e5000000000000000000000000000000000000000000000000000000007f60084026a03a371eca9ea58a82b38af8032e73870382e18009cf39d8eb290ed342b0825bfca03f3fa1cce7e07ff0909a1914d6747c33123634dd6a5940f2a3093e020ce9f8d2f8aa1585098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000046705dfff24256421a05d056c29e81bdc09723b800000000000000000000000000000000000000000000000000000000773021a026a063a1a1a30aded50d8619b08e0fbf2b9defc2e521a0731f2953518b65cb413907a02ddaa13c9793edf51f570429f09f714f2a79c1a9a1ccc343899be81911bcdfd6f8aa2e85098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000adb2b42f6bd96f5c65920b9ac88619dce4166f94000000000000000000000000000000000000000000000000000000007733232c25a0eea5d54d8a2988876099e73b7a7b458a370f94f4b7343119da720bbe130624aca048ae11d7219a72fc9f20e1144624db5f1ec7d2b220b5ef59d6cc22c4feda515df8aa1185098bca5a00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000006748f50f686bfbca6fe8ad62b22228b87f31ff2b000000000000000000000000000000000000000000000000000000014b48598026a09bd76874b6033a5cab13a2215a7b3a78a571c4411a8225d4855de261a1ece36ea054edbd5d59ed79cdad561ea97df9fc17977495ded7ce4ea4955725a10562e7f1f8a911850979e8bd4482a0f994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000006b71dcaa3fb9a4901491b748074a314dad9e980b000000000000000000000000000000000000000000000000000000001091f18026a0737ea17db2080698c2af3d7a9f9ca14bfcdd0e21fc0e592271fe050d4209d1f4a035ae008889f7d5d85b8ec48bc3929f681b09305f36eb65a6760918e9b9b04677f9018d8214c1850979e8bd4483010e2a94d141a26d786e9432d345774721fc141c13ec10f380b9012452bf1d92000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000003fdc8e278f5bc4576a7fd13d1a01dc081ae0a2b00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000da57b127b937185816683503de31cdb1fa203001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000af90e3681ba01773e74e7b8be1b0a6cdea3dd634dcb724613891e99b06e690a05addee7d8cfaa04d966d9097c1e3b0f2d2dd7a2eac5deea28d8fa328643d0b065ea86aea472fedf8aa07850979e8bd448301d4c094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c3462da2baac16770caf09c6349241945c0714f700000000000000000000000000000000000000000000000000000000810351d426a0176f3b44e6e9c0c12c89132a64089ba2b9d66aaf7aee5df92747c5a9bd847fcda01d9d2f4f002f006817cbe5da7375dfd5949e682d24bf347c61fe3350c9098764f8ab821b88850979e8bd4482ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000531052427a3fdb4e1eebc8765cdf6e98972b31d4000000000000000000000000000000000000000000000000000000001e8480001ba019040c51840ce978756fb8d0b98a47b05d36e8e78228ecaf6d223f93fae8f3caa01ae2bdf972eae310ef838938165d47379563cdd265a7004ff42836917da8a522f8a906850979e8bd4482a0f994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000006b71dcaa3fb9a4901491b748074a314dad9e980b0000000000000000000000000000000000000000000000000000000024d5ee8025a06fe92d92cc309c4150846ea5ed9413490306579b6c7ce664478443e6fbb74ac3a046448faf47445d8b533c6423aba3ab72322b3e24ecdea9a0051c6c7983927715f9018b51850973f2d6008303b352947a250d5630b4cf539739df2c5dacb4c659f2488d80b9012438ed17390000000000000000000000000000000000000000000000002636c37f92d4f0c0000000000000000000000000000000000000000000000000054e31d02918ea9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000bcc414838d46bb02a5651a8714cd647a0c1fca27000000000000000000000000000000000000000000000000000000005f7bf4640000000000000000000000000000000000000000000000000000000000000003000000000000000000000000dfcb3fe1c69bf561a920f623fd152e8630674d0d000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000009464a922c053c519ac0237a9650e303d532a4d225a05d067e854341536417fcf031529d2ab65232d4d7964a8c7dac9907cf6fb738e8a055549b345c8b0a10e9aba2d721142995d9e4a74393107d46ca6f80a186b13dd9f9015267850973f2d6008302a2ad947a250d5630b4cf539739df2c5dacb4c659f2488d8805734bbb01b06a28b8e4fb3bdb410000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000001ce0c96cf5aad45a8824c22152ec5055f83ddaa1000000000000000000000000000000000000000000000000000000005f7bf45d0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000c7be37b00c1d3127c09da66fadf3d6eb600dece26a07033f3a1433bd9455f9808c6ea10268aa3f0399b351c9fa96487fba14ab00e6da028578d7fcfa99a356d83787d38cec328f981bd2f3a50a5141f22298f8704edc0f8ca028509502f95b383019f539427b4bc90fbe56f02ef50f2e2f79d7813aa8941a780b86423b872dd000000000000000000000000946f2bf0f01580f3063b94115d48221a92916cf7000000000000000000000000f629527029499db6a6154a1cb20eb210735da33d000000000000000000000000000000000000000000000000000000000000927225a04852d05e9c6c106918bc5312720148113e6a48ae4403a6fd436b08ead64d41e8a05c1cec000313c180f5d29294c27b0d98b3969b7644800366f23ca91b73d31d78f8ca268509502f90008306b5e094bd277e47d0ecdd5db6c57eda717dd8f5a329edec80b864fc1d230e000000000000000000000000000000000000000000000000000000000000898a0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000025a0542d85a2796e173a90b813bce70913b51600f7d180a64195ef04d7e2c7b8f90aa0123ed49099ae6c7a368fba99cdb6b6fdd1622e66b9ad8e663ca7d9065e51bcf8f8aa808509502f9000830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000adb2b42f6bd96f5c65920b9ac88619dce4166f9400000000000000000000000000000000000000000000000000000000e10f4ba026a05574388857f5e60d3ec72ceb629d81b98c336f0907ef37f4359a46dc8213dc16a02e749cdc7f6b0a7b240235be95128f0a603abf4e7ad95e1c3df043eef640ef6ef8aa168509502f9000830186a094df574c24545e5ffecb9a659c229253d4111d87e180b844a9059cbb0000000000000000000000001062a747393198f70f71ec65a582423dba7e5ab3000000000000000000000000000000000000000000000000000000743058279026a063f627fcf6ac293cac6f84b5f8807aef92682368e99b244878d162998e5c250fa07a20fbb570af828a69f7e62d06cfe913fa3739f42cb43c2cc16e036efbaa0709f86c808509502f900082520894f85a34c8c414dc13ea15590cd383198ef0c5c63d880e397b561dbfa0008025a0228e8f924ee18c31d102873dada7c8564021e1bf7b3f717cfa230f59ddc42132a045bb47ed08ed4df51d1325f8670ffb91ee4c8891036886caf72b474afa6d94e1f8a90685094312a10082ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000030950d97d1854f587b73f8346c2a808ad73b73ac0000000000000000000000000000000000000000000000000000000051a88a801ba0a3bb6f585af27c6fd0053936542418d3583b5f7d2887a1ba353160f4edb008d2a01ccc663badc590398149dc6757d2351d8276cbd71c8771549cfd902ceda762adf8aa80850938580c008303d09094dac17f958d2ee523a2206206994597c13d831ec780b844095ea7b300000000000000000000000041f8d14c9475444f30a80431c68cf24dc9a8369affffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff26a02a8ef1097bb4bc7036343ad59ce010d590ba8c3ad659b160e50287bc817b6d89a06d4e37d3f219cb4f9cf06328259d09a5441e0bf2aec1eacd999ae3142fca4a25f8a90c850938580c0082a0f994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000006b71dcaa3fb9a4901491b748074a314dad9e980b0000000000000000000000000000000000000000000000000000000005208fa026a04b59e7f4ce7a662e3d133a26fb7beea4c8dcd2df71f162dcfade0a0b28119d4ba06da4670c1575a071084fcebdfb6c73dffbb7d6bde8364d73c1c798aa8b0090f3f86b4b85091494cbb3825208948ac4bf2465dbeec007b7f9809979f22aedeb7806870a71ea17a3c0008026a05a5a5f216393d82283357f4bc9a9b569b467cebdd08da509cc1880301bbefd01a060d0c4d61ad1455b6847965f5f521e62affa0fc86f97eb6395344bb746549344f8890385091494c60083018808941f9840a85d5af5bf1d1762f925bdaddc4201f98480a45c19a95c000000000000000000000000050ed33cc70e3d7952967acb55901b4b44d3cee226a0def74c07de86a0fe72d478fb412e3430b28a2658fe6b10eac18311f6f5a3c4b4a06246e2c183d3dc928cf860bf2c82dc557788a4f8866c179297bfbe1d6479f06df8691985091494c6008311935f9401285d34fe29262a61788775df716acc7ddf897c8084d1058e5926a0ef28dfb04a55901b9a67b00c794502813cfb85b019c9804a004fec560b3c17c3a056254016ed435f806ba9b92be8b3a290a9617b79bac068f223b6abac5220af69f8698085091494c600830878a6949765fea9752505a685c1bce137ae5b2efe8ddf6280844e71d92d25a084331b1cd673c4634c16fa25c6d381b36fcca12b8fdfa34ceeee2f4e7fc68d29a05c0c6ffae21c5c348d0b03c82c6f0cfcb75a10c0b130809aa791977ce26bf17af8ca0185091494c6008301a3869427b4bc90fbe56f02ef50f2e2f79d7813aa8941a780b86442842e0e000000000000000000000000068323fa0171364b9fbb928dcfc22dfe965785f800000000000000000000000003bbdbc26a0aaf9a8991a28b1cc429c8e0ea2897000000000000000000000000000000000000000000000000000000000000927b25a032507f49426c3b56e42db20d0cea50235528728ab5b98097fc562461373161f8a065b9f9df23f54faf91e8a298f1b40da3f00de262492a62f0f9b85d89cbfb2085f8a90185091494c60082fde8940435316b3ab4b999856085c98c3b1ab21d85cd4d80b844a9059cbb0000000000000000000000007b00bcf548a069906f654627c3e04795357d82990000000000000000000000000000000000000000000001f29fe46ae84af800001ca0a7a4d1458365c4beb07b05a6e7dfa0a9bdf73807c508dbd4cd65aa254070147ba035333336910d89b785f3c33feb9c46a11230002e14e32f90533c348499ee6a35f8ac824b2b85091494c60083011170949dffe202df7f82ba57a7f8d571628805eff7fed980b844a9059cbb00000000000000000000000017849384ca9173290f540e566e369df84e924b9800000000000000000000000000000000000000000000006c6b935b8bbd40000025a069acf936392176ffb46c35851218efbe318218f8250261db49a8c9fd411aef94a049509eb3ddd2865547739a9be6c45958552f9d77f9abbbfea2c5047984e378f9f8aa8085091494c6008301259494725440512cb7b78bf56b334e50e31707418231cb80b844a9059cbb0000000000000000000000007b00bcf548a069906f654627c3e04795357d82990000000000000000000000000000000000000000000821a69b498f0c61f800001ca08b42ff2c6746c6663ac1ce97183287bfe79e1e36906ba0e3a349707bdf378442a04f8af6df29b3a0bbc4d5b2b7b1934388c096c9c5eb90172fb297d9361cdf80d4f8890285091494c6008301cdbe94a39a6e35d98e531058b52c28af4ffdeeafb1749d80a4a694fc3a00000000000000000000000000000000000000000000001d776c9997241a800026a0acc704c9c151bb6988ff16cc1f9c3605525c7e2dfebb1251ae4a21733e0a5447a00fc1abe875a992fa1368766c31d1a301467cf565387d9b3772dcd50b96dbad02f86d0185091494c60082520894d43ffc3367ea4fee1a32342c8b92ddc558e5e826890108721d3368fdf0008026a0efd4fbbadc6d2714947ce815eeb99f711459db9573d66a14b4837e1a994442cba02da5a5d74fbf3d4b5a8a17acff8d4e8d0055ed488ef13e56c738e95ef8c85efdf8cc82034585091494c6008305d22794b86021cba87337dea87bc055666146a263c9e0cd80b864fc1d230e000000000000000000000000000000000000000000000000000000000000922f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000025a0734009bcae96f41f77d600c562a70c1606abcc0751268407b7a4829b92eb8cf7a00fb0a91b8b350cc898d3fbf4ea9249344ee677e334d353f228ebc1ec40bcffe5f8698085091494c600830878a6949765fea9752505a685c1bce137ae5b2efe8ddf6280844e71d92d25a0ee1e0d0c01d19fa2881d2db84d4c54c8719ecee4fe8ada44ab6b51bd1892480ba065d9eed77a7f76565d10552537b29753f8f7182403f0334b0a823bc70fa1da08f8897e85091494c6008302b4c59475ce0e501e2e6776fcaaa514f394a88a772a897080a451c6590a0000000000000000000000000000000000000000000000056bc75e2d6310000025a0b56d9e64fd7baac51c8a1464af551978ce94037ba93cd23499a01a42c7950a5fa06d04bf6de15e8f2ffb9359ce517960422106a3bd9c5a4baa3e865ee3e021e2a6f8cd830226b185091494c60083014c0894bb97e381f1d1e94ffa2a5844f6875e614698100980b86423b872dd0000000000000000000000006dd0ff909dc42abd3b6b52f6e7643d215715e1e7000000000000000000000000c1ab0c708aa638ad492d99383217c033f0eeab950000000000000000000000000000000000000000000000ce661595e03f85f8d925a01831096c49030fd369e15776f7d4bac38ef0f8e5572e3447841e66c5ccd5807da02fd5e62915e24339d30a472a565330b8808dbeb06009e6bed9715893c8b36d2df9018d82017585089d5f37b383035ff3947a250d5630b4cf539739df2c5dacb4c659f2488d80b9012438ed173900000000000000000000000000000000000000000000001650c2523dbb933d49000000000000000000000000000000000000000000000000000000000674219400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000082cd3c7035104d1fbfb55acc62667907791fe423000000000000000000000000000000000000000000000000000000005f7bf4390000000000000000000000000000000000000000000000000000000000000003000000000000000000000000b1f66997a5760428d3a87d68b90bfe0ae64121cc000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec725a0f6147595b4fba2f27e1d595a05c11fab5953335862c6edea856f9e9a77814529a056473fcf8e676c26ba05a1767ae9a514901c8fe6faa1ec473d47879afe7762f3f8ab82015785089d5f37b382ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000089191c0f10eebde841ea5d12b1f1098ef73859de000000000000000000000000000000000000000000000000000000174876e80025a0be3553ba3c9a205e4d76afbc0a6dd6f0d8a84f7efc8d5076eb98fc43a76808baa055e94b68d8a1da425f4febc9e0272cb65e1da38e6d58c06489d3b54e79b52b4ff8ab8218ee85089d5f37b3828fe094de7f89c3d1261f07589fa52fea19bc1b14dfc91980b844a9059cbb0000000000000000000000008700e5430a4b57f24a7f84abe38864e51af7a4b8000000000000000000000000000000000000000000000000000000909af338a526a02cfb25a32d955a1d8c6b51f339c8cb0acf6cf3e27d45fbb0a2ecfc12b85602f0a024d77aa56ace50dfe58c7413aa08a5006728e293bb26397e7be096277903938ff901522a85089d5f37b383028908947a250d5630b4cf539739df2c5dacb4c659f2488d880b263a94504c2e58b8e47ff36ab500000000000000000000000000000000000000000000000ae0b305bb98f9a7ad000000000000000000000000000000000000000000000000000000000000008000000000000000000000000071a85ed3f949a63dc13c127fc2f2751c13eed024000000000000000000000000000000000000000000000000000000005f7befe80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002a7f709ee001069771ceb6d42e85035f7d18e73626a0e3ed1ca94b4285e9ae128d9ffc907da874634e6784ac29db70570e5a610aacc7a016da61376d706d3c8bbedfad33e229d2f80a0015e40e5345e7415aaf9fab79d9f9016b0a85089d5f37b38302935b947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe5000000000000000000000000000000000000000000000006551c67c8504ff9b600000000000000000000000000000000000000000000000003bc3298899feb5700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e42bf6c707ff70adbcb5d3c41a18af9c7b59078d000000000000000000000000000000000000000000000000000000005f7bf436000000000000000000000000000000000000000000000000000000000000000200000000000000000000000005d3606d5c81eb9b7b18530995ec9b29da05faba000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc225a038bae485ad7254f45da6ee9ccfaa7b61e09335ed4d63145c171a6db4b2c58b43a06aaacde283c87486dfaeed8b7c65da2a4968b0da29e5891a74633d1142b9eb7bf8ab81be85089d5f37b383033e3794577af3dce5aaa89510135d7f6e095e33c06b8b1f80b844e2bbb158000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000026a05591dc7d86736fec625daea5eb6e71cf2d41f29b77d6d9e59f030ef0352aba5aa04459cd24f3cbba8eb1f5640ad1f1f36a8402cf438bcccd48b9feb280d80c1e10f901731685089d5f37b38303bbc3947a250d5630b4cf539739df2c5dacb4c659f2488d8802b3a3cb06ced308b901047ff36ab500000000000000000000000000000000000000000000022dbeb06eb8dc19a65e000000000000000000000000000000000000000000000000000000000000008000000000000000000000000032769651c655db12154b17910dd477592c54576d000000000000000000000000000000000000000000000000000000005f7bf4390000000000000000000000000000000000000000000000000000000000000003000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000009b06d48e0529ecf05905ff52dd426ebec0ea301125a0a0d302734d13499be466feeda44570a2bdbf364b31e34be62ee16b4dc4b5699ba0687177566a9a2e6bf7d3f2d68cf60e93482447dd3b472b67c17b8f1e00981b78f9016b7885089d5f37b3830295f8947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe500000000000000000000000000000000000000000000000f23c3cdb2637622cb0000000000000000000000000000000000000000000000000f2ec381bef4f22100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000003904ea4b55a09e7c6e4c213cce0db7a9f599a4c3000000000000000000000000000000000000000000000000000000005f7bf43900000000000000000000000000000000000000000000000000000000000000020000000000000000000000002a7f709ee001069771ceb6d42e85035f7d18e736000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc225a0f34f01bed0f338683c5207c1587d1a5ba229779db4c1de9d5482c4a905a2ad3ea068e89a5b8047924cb71cbc8e123e8b05a8c8719a049afb02a3c6972febcd0085f8a90985089d5f37b382ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000d3d0278216491edb86bc06f8cfb11d51a9dd808b0000000000000000000000000000000000000000000000000000000005f5e10025a06c7279aba31527aae21db6b895612f99ba1244bc9aef8264bc9fa6a15cb3efaaa073c383316de97f4c2b5b9b51ea7d74d17be0b4dd9529c6ab4e59e93f5fb57346f8aa81dd85089d5f37b382bf2c940ff6ffcfda92c53f615a4a75d982f399c989366b80b844095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff26a0e4ce469a5e40f47e838933ebf2a5af9cc0cb39b1c87dcc90db9ea90207060960a04210af1974ec3fbade5ffdf79e92a4287b68290a52c8301aa7a96f325b29b089f8a97285089d5f37b382ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000002562dac3ecc0df840568597f5fe5db96d7fad37f0000000000000000000000000000000000000000000000000000000011e1a30025a05d6b5a2fb581a0c76a0b6ad86d9f14f9461a924bfa23fd3e6dc29469aecee960a047cb4d29954ef81da7a65933c5e398a3d012b106ad1f49dd78ede22414a59ff0f8ab82011f85089d5f37b382ea6094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000006742a5be977a95e6bd413ad7dd67e750d25acf77000000000000000000000000000000000000000000000000000000000081b32026a09d70ff51194da072becc6095e121853a1e9500ee6285a37fb3fff0b7b37d8d46a03054b0e28af6cac98ca911dcfe7e47eef7e9546d1c4743d72b5761839c133f24f8ab82010585089d5f37b382ea60945913d0f34615923552ee913dbe809f9f348e706e80b844a9059cbb0000000000000000000000009bcae07a4f32391c337081db539f18c8a41f0b17000000000000000000000000000000000000000000000000016345785d8a000026a0d8364bccfe1cd13452d32320bbbe7007d2a49eb4c44e4192adcf13c5319dba12a0556f74086c4f19473dcd0e1651ed2f1ad41a6e4955ff4489e580637b76cf559bf8cc82027b85089d5f37b3830449df94398ec7346dcd622edc5ae82352f02be94c62d11980b864d2d0e0660000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000002f51218000000000000000000000000000000000000000000000000000000000000000026a05f066b907360fdd977a7be7b87f8abb63c026bd6a9b79961b2df31f4b05197b6a03ee77c3bb73623eaf754e19a3de2587917d5bc9fe9fd6d18d01263994a8ae482f9016b2d85089d5f37b383040b67947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe5000000000000000000000000000000000000000000000005fe80d4a5da71c8000000000000000000000000000000000000000000000000000f4d4114fc1b990700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000d5ef38a6bfe29e37fde0e05c3b5d053d35cde31c000000000000000000000000000000000000000000000000000000005f7bf45c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000960b236a07cf122663c4303350609a66a7b288c0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc226a077384993315e895c506addb765b6ead9aa40643cfe0e47fa9a7cbe7b68b5c26da023e3d59adf9659ebc24ac7b1c61a8d34870c1473482062dee58001e433dacffbf8ab8202e885089d5f37b38270d094c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280b844095ea7b3000000000000000000000000e2466deb9536a69bf8131ecd0c267ee41dd1cda0000000000000000000000000000000000000000000000000000000000000000026a0122be7ab3dd42a95a6f9f7afd4b53cdd96a54533355678de8e6a079c1f3a507da0184286f4fb49dc18285b01b7b881ef7219783a94e4adba214d5f846cd05bf080f8ab8202e985089d5f37b38270d094c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280b844095ea7b30000000000000000000000006f400810b62df8e13fded51be75ff5393eaa841f000000000000000000000000000000000000000000000000000000000000000026a09cad61d8896acfc385722a5bbb7cc933cf424848b3c1e5ca77d65bf05c6d6fcda074d8318b68ede0035abf2b7be4ff24b8434a14e9265539d0a7a667da017671a1f8ab8202ea85089d5f37b38270d094c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280b844095ea7b30000000000000000000000003e66b66fd1d0b02fda6c811da9e0547970db2f21000000000000000000000000000000000000000000000000000000000000000026a0e6503c732760d18c807a75da58051281d667f8c4198c45ed51ca087614d5785aa01e224cb49e6837da617107107dc0ea4dfad191de3797142648bc1dde0df987faf9016d82018f85089d5f37b3830295f8947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010418cbafe500000000000000000000000000000000000000000000000f068603508f9456ad0000000000000000000000000000000000000000000000000e494da22dc7c0b800000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffcee9c64c3cdab271f5c3c7c184b153f1344b2a000000000000000000000000000000000000000000000000000000005f7bf03d00000000000000000000000000000000000000000000000000000000000000020000000000000000000000002a7f709ee001069771ceb6d42e85035f7d18e736000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc226a0197b3da148fcee4ccbf62eb74082ef5b79384afdaf3abd2175173b404d2c6c3ba02fa9b883c18f17f6327cc8de85764d2b59b3dc3e8b5359a225585e719067c0fdf8ab82069a850826299e0082a95994b4e16d0168e52d35cacd2c6185b44281ec28c9dc80b844095ea7b3000000000000000000000000fab1eab539d2cf50162245e59ddea174a301d85d000000000000000000000000000000000000000000000000000000000000000026a04919640cc96cbb20a3fb12871737fbd812a1f8c812f852db7abfbec889c04a20a04c6f5c296e8c2628537c559d76ad3b750cf068b689e6021dc17691bb846d6d5bf86b1b8507ea8ed9b38252089409f72bc14a0901c3d8127dc3b64971187900a66987470de4df8200008025a0ef0162ddd727191632140402fcd1d3d3e05cc0386eb5700c71b12d85a1732176a02958d5b3f7e4c9a555a172c6b4921cc568708ea8138c26bd1445036fbbaef2eff86b078507ea8ed9b38252089437434a338fdcff98e8ae3c07e3ce6bfae701a47d87ce0eb154f900008026a0d38057dbe925e7517a9321248cdce4a91aef0caac2a28b275909ba1e9b365312a01b1f4727a4a644c733b7fc975dd3791d4931d1e210ad8423415a8d3a4ec85b41f86c038507b4e9eb00825208941bb95bd09a9463a9fb0874cc9305744eed1965368815957b01e7cdcf5b801ba06a8e9d014dd495c9d5c98b8e7d71e3d170e2fe4b83752d219f8bf794c8c9f2eda023dd8b8cb5491984b639e0855817d71dca426e1ac25053e2156d5f95042b86f4c0" -) - -/* -0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 -0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 -0x4d5e647b2d8d3a3c8c1561ebb88734bc5fc3c2941016f810cf218738c0ecd99e - ,BranchPageN,LeafPageN,OverflowN,Entries -b,4085,326718,0,11635055 -eth_tx,212580,47829931,6132023,969755142 -*/ - -var ( - HeadersPostProcessingStage = stages.SyncStage("post processing") - Snapshot11kkTD = []byte{138, 3, 199, 118, 5, 203, 95, 162, 81, 64, 161} -) - -func PostProcessing(db kv.RwDB, downloadedSnapshots map[snapshotsync.SnapshotType]*snapshotsync.SnapshotsInfo) error { - if _, ok := downloadedSnapshots[snapshotsync.SnapshotType_headers]; ok { - if err := db.Update(context.Background(), func(tx kv.RwTx) error { - return GenerateHeaderIndexes(context.Background(), tx) - }); err != nil { - return err - } - } - if _, ok := downloadedSnapshots[snapshotsync.SnapshotType_state]; ok { - if err := db.Update(context.Background(), func(tx kv.RwTx) error { - return PostProcessState(tx, downloadedSnapshots[snapshotsync.SnapshotType_state]) - }); err != nil { - return err - } - } - - if _, ok := downloadedSnapshots[snapshotsync.SnapshotType_bodies]; ok { - if err := db.Update(context.Background(), func(tx kv.RwTx) error { - return PostProcessBodies(tx) - }); err != nil { - return err - } - } - - return nil -} - -func PostProcessBodies(tx kv.RwTx) error { - v, err := stages.GetStageProgress(tx, stages.Bodies) - if err != nil { - return err - } - - if v > 0 { - return nil - } - err = tx.ClearBucket(kv.TxLookup) - if err != nil { - return err - } - - ethTxC, err := tx.Cursor(kv.EthTx) - if err != nil { - return err - } - k, _, err := ethTxC.Last() - if err != nil { - return err - } - if len(k) != 8 { - return errors.New("incorrect transaction id in body snapshot") - } - secKey := make([]byte, 8) - binary.BigEndian.PutUint64(secKey, binary.BigEndian.Uint64(k)+1) - err = tx.Put(kv.Sequence, []byte(kv.EthTx), secKey) - if err != nil { - return err - } - - bodyC, err := tx.Cursor(kv.BlockBody) - if err != nil { - return err - } - k, body, err := bodyC.Last() - if err != nil { - return err - } - - if body == nil { - return fmt.Errorf("empty body for key %s", common.Bytes2Hex(k)) - } - - number := binary.BigEndian.Uint64(k[:8]) - err = stages.SaveStageProgress(tx, stages.Bodies, number) - if err != nil { - return err - } - return tx.Commit() -} - -func PostProcessState(db kv.RwTx, info *snapshotsync.SnapshotsInfo) error { - v, err := stages.GetStageProgress(db, stages.Execution) - if err != nil { - return err - } - - if v > 0 { - return nil - } - // clear genesis state - if err = db.ClearBucket(kv.PlainState); err != nil { - return err - } - if err = db.ClearBucket(kv.EthTx); err != nil { - return err - } - err = stages.SaveStageProgress(db, stages.Execution, info.SnapshotBlock) - if err != nil { - return err - } - err = stages.SaveStageProgress(db, stages.Senders, info.SnapshotBlock) - if err != nil { - return err - } - return nil -} - -//It'll be enabled later -func PostProcessNoBlocksSync(db ethdb.Database, blockNum uint64, blockHash common.Hash, blockHeaderBytes, blockBodyBytes []byte) error { - v, err := stages.GetStageProgress(db, stages.Execution) - if err != nil { - return err - } - - if v > 0 { - return nil - } - log.Info("PostProcessNoBlocksSync", "blocknum", blockNum, "hash", blockHash.String()) - - tx, err := db.(ethdb.HasRwKV).RwKV().BeginRw(context.Background()) - if err != nil { - return err - } - defer tx.Rollback() - - //add header - err = tx.Put(kv.Headers, dbutils.HeaderKey(SnapshotBlock, blockHash), blockHeaderBytes) - if err != nil { - return err - } - //add canonical - err = tx.Put(kv.HeaderCanonical, dbutils.EncodeBlockNumber(SnapshotBlock), blockHash.Bytes()) - if err != nil { - return err - } - body := new(types.Body) - err = rlp.DecodeBytes(blockBodyBytes, body) - if err != nil { - return err - } - err = rawdb.WriteBody(tx, blockHash, SnapshotBlock, body) - if err != nil { - return err - } - - err = tx.Put(kv.HeaderNumber, blockHash.Bytes(), dbutils.EncodeBlockNumber(SnapshotBlock)) - if err != nil { - return err - } - b, err := rlp.EncodeToBytes(big.NewInt(0).SetBytes(Snapshot11kkTD)) - if err != nil { - return err - } - err = tx.Put(kv.HeaderTD, dbutils.HeaderKey(SnapshotBlock, blockHash), b) - if err != nil { - return err - } - - err = tx.Put(kv.HeadHeaderKey, []byte(kv.HeadHeaderKey), blockHash.Bytes()) - if err != nil { - return err - } - - err = tx.Put(kv.HeadBlockKey, []byte(kv.HeadBlockKey), blockHash.Bytes()) - if err != nil { - return err - } - - err = stages.SaveStageProgress(tx, stages.Headers, blockNum) - if err != nil { - return err - } - err = stages.SaveStageProgress(tx, stages.Bodies, blockNum) - if err != nil { - return err - } - err = stages.SaveStageProgress(tx, stages.BlockHashes, blockNum) - if err != nil { - return err - } - err = stages.SaveStageProgress(tx, stages.Senders, blockNum) - if err != nil { - return err - } - err = stages.SaveStageProgress(tx, stages.Execution, blockNum) - if err != nil { - return err - } - return tx.Commit() -} - -func generateHeaderHashToNumberIndex(ctx context.Context, tx kv.RwTx) error { - c, err := tx.Cursor(kv.Headers) - if err != nil { - return err - } - log.Info("Generate headers hash to number index") - lastHeader, _, innerErr := c.Last() - if innerErr != nil { - return innerErr - } - c.Close() - - headNumberBytes := lastHeader[:8] - headHashBytes := lastHeader[8:] - - headNumber := big.NewInt(0).SetBytes(headNumberBytes).Uint64() - headHash := common.BytesToHash(headHashBytes) - - return etl.Transform("Torrent post-processing 1", tx, kv.Headers, kv.HeaderNumber, os.TempDir(), func(k []byte, v []byte, next etl.ExtractNextFunc) error { - return next(k, common.CopyBytes(k[8:]), common.CopyBytes(k[:8])) - }, etl.IdentityLoadFunc, etl.TransformArgs{ - Quit: ctx.Done(), - ExtractEndKey: dbutils.HeaderKey(headNumber, headHash), - }) -} - -func generateHeaderTDAndCanonicalIndexes(ctx context.Context, tx kv.RwTx) error { - var hash common.Hash - var number uint64 - var err error - - h := rawdb.ReadHeaderByNumber(tx, 0) - td := h.Difficulty - - log.Info("Generate TD index & canonical") - err = etl.Transform("Torrent post-processing 2", tx, kv.Headers, kv.HeaderTD, os.TempDir(), func(k []byte, v []byte, next etl.ExtractNextFunc) error { - header := &types.Header{} - innerErr := rlp.DecodeBytes(v, header) - if innerErr != nil { - return innerErr - } - number = header.Number.Uint64() - hash = header.Hash() - td = td.Add(td, header.Difficulty) - tdBytes, innerErr := rlp.EncodeToBytes(td) - if innerErr != nil { - return innerErr - } - - return next(k, dbutils.HeaderKey(header.Number.Uint64(), header.Hash()), tdBytes) - }, etl.IdentityLoadFunc, etl.TransformArgs{ - Quit: ctx.Done(), - }) - if err != nil { - return err - } - log.Info("Generate TD index & canonical") - err = etl.Transform("Torrent post-processing 2", tx, kv.Headers, kv.HeaderCanonical, os.TempDir(), func(k []byte, v []byte, next etl.ExtractNextFunc) error { - return next(k, common.CopyBytes(k[:8]), common.CopyBytes(k[8:])) - }, etl.IdentityLoadFunc, etl.TransformArgs{ - Quit: ctx.Done(), - }) - if err != nil { - return err - } - rawdb.WriteHeadHeaderHash(tx, hash) - rawdb.WriteHeaderNumber(tx, hash, number) - err = stages.SaveStageProgress(tx, stages.Headers, number) - if err != nil { - return err - } - err = stages.SaveStageProgress(tx, stages.BlockHashes, number) - if err != nil { - return err - } - rawdb.WriteHeadBlockHash(tx, hash) - log.Info("Last processed block", "num", number, "hash", hash.String()) - return nil -} - -func GenerateHeaderIndexes(ctx context.Context, tx kv.RwTx) error { - v, err1 := stages.GetStageProgress(tx, HeadersPostProcessingStage) - if err1 != nil { - return err1 - } - - if v == 0 { - if err := generateHeaderHashToNumberIndex(ctx, tx); err != nil { - return err - } - if err := generateHeaderTDAndCanonicalIndexes(ctx, tx); err != nil { - return err - } - if err := stages.SaveStageProgress(tx, HeadersPostProcessingStage, 1); err != nil { - return err1 - } - - return nil - } - return nil -} diff --git a/turbo/snapshotsync/postprocessing_test.go b/turbo/snapshotsync/postprocessing_test.go deleted file mode 100644 index 2227323030d..00000000000 --- a/turbo/snapshotsync/postprocessing_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package snapshotsync - -import ( - "context" - "math/big" - "os" - "testing" - - "github.com/ledgerwatch/erigon-lib/kv" - mdbx2 "github.com/ledgerwatch/erigon-lib/kv/mdbx" - "github.com/ledgerwatch/erigon/common/dbutils" - "github.com/ledgerwatch/erigon/core/rawdb" - "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/ethdb/snapshotdb" - "github.com/ledgerwatch/erigon/rlp" - "github.com/ledgerwatch/log/v3" - "github.com/stretchr/testify/require" - "github.com/torquem-ch/mdbx-go/mdbx" -) - -func TestHeadersGenerateIndex(t *testing.T) { - snPath := t.TempDir() - snKV := mdbx2.NewMDBX(log.New()).Path(snPath).MustOpen() - defer os.RemoveAll(snPath) - headers := generateHeaders(10) - err := snKV.Update(context.Background(), func(tx kv.RwTx) error { - for _, header := range headers { - headerBytes, innerErr := rlp.EncodeToBytes(header) - if innerErr != nil { - panic(innerErr) - } - innerErr = tx.Put(kv.Headers, dbutils.HeaderKey(header.Number.Uint64(), header.Hash()), headerBytes) - if innerErr != nil { - panic(innerErr) - } - } - return nil - }) - if err != nil { - t.Fatal(err) - } - snKV.Close() - - db := mdbx2.NewMDBX(log.New()).InMem().WithTablessCfg(mdbx2.WithChaindataTables).MustOpen() - defer db.Close() - //we need genesis - if err := db.Update(context.Background(), func(tx kv.RwTx) error { - return rawdb.WriteCanonicalHash(tx, headers[0].Hash(), headers[0].Number.Uint64()) - - }); err != nil { - t.Fatal(err) - } - - snKV = mdbx2.NewMDBX(log.New()).Path(snPath).Flags(func(flags uint) uint { return flags | mdbx.Readonly }).WithTablessCfg(mdbx2.WithChaindataTables).MustOpen() - defer snKV.Close() - - snKV = snapshotdb.NewSnapshotKV().HeadersSnapshot(snKV).DB(db).Open() - snTx, err := snKV.BeginRw(context.Background()) - require.NoError(t, err) - defer snTx.Rollback() - - err = GenerateHeaderIndexes(context.Background(), snTx) - if err != nil { - t.Fatal(err) - } - td := big.NewInt(0) - for i, header := range headers { - td = td.Add(td, header.Difficulty) - canonical, err1 := rawdb.ReadCanonicalHash(snTx, header.Number.Uint64()) - if err1 != nil { - t.Errorf("reading canonical hash for block %d: %v", header.Number.Uint64(), err1) - } - if canonical != header.Hash() { - t.Error(i, "canonical not correct", canonical) - } - - hasHeader := rawdb.HasHeader(snTx, header.Hash(), header.Number.Uint64()) - if !hasHeader { - t.Error(i, header.Hash(), header.Number.Uint64(), "not exists") - } - headerNumber := rawdb.ReadHeaderNumber(snTx, header.Hash()) - if headerNumber == nil { - t.Error(i, "empty header number") - } else if *headerNumber != header.Number.Uint64() { - t.Error(i, header.Hash(), header.Number.Uint64(), "header number incorrect") - } - if td == nil { - t.Error(i, "empty td") - } else { - td, err := rawdb.ReadTd(snTx, header.Hash(), header.Number.Uint64()) - if err != nil { - panic(err) - } - if td.Cmp(td) != 0 { - t.Error(i, header.Hash(), header.Number.Uint64(), "td incorrect") - } - } - } -} - -func generateHeaders(n int) []types.Header { - headers := make([]types.Header, n) - for i := uint64(0); i < uint64(n); i++ { - headers[i] = types.Header{Difficulty: new(big.Int).SetUint64(i), Number: new(big.Int).SetUint64(i)} - } - return headers -} diff --git a/turbo/snapshotsync/server.go b/turbo/snapshotsync/server.go deleted file mode 100644 index 857249ed7bc..00000000000 --- a/turbo/snapshotsync/server.go +++ /dev/null @@ -1,220 +0,0 @@ -package snapshotsync - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/anacrolix/torrent" - "github.com/ledgerwatch/erigon-lib/gointerfaces/snapshotsync" - "github.com/ledgerwatch/erigon-lib/kv" - "github.com/ledgerwatch/erigon-lib/kv/mdbx" - "github.com/ledgerwatch/log/v3" - "golang.org/x/sync/errgroup" - "google.golang.org/protobuf/types/known/emptypb" -) - -var ( - ErrNotSupportedNetworkID = errors.New("not supported network id") - ErrNotSupportedSnapshot = errors.New("not supported snapshot for this network id") -) -var ( - _ snapshotsync.DownloaderServer = &SNDownloaderServer{} -) - -func NewServer(dir string, seeding bool) (*SNDownloaderServer, error) { - db := mdbx.MustOpen(dir + "/db") - sn := &SNDownloaderServer{ - db: db, - } - if err := db.Update(context.Background(), func(tx kv.RwTx) error { - peerID, err := tx.GetOne(kv.BittorrentInfo, []byte(kv.BittorrentPeerID)) - if err != nil { - return fmt.Errorf("get peer id: %w", err) - } - sn.t, err = New(dir, seeding, string(peerID)) - if err != nil { - return err - } - if len(peerID) == 0 { - err = sn.t.SavePeerID(tx) - if err != nil { - return fmt.Errorf("save peer id: %w", err) - } - } - return nil - }); err != nil { - return nil, err - } - - return sn, nil -} - -type SNDownloaderServer struct { - snapshotsync.UnimplementedDownloaderServer - t *Client - db kv.RwDB -} - -func (s *SNDownloaderServer) Download(ctx context.Context, request *snapshotsync.DownloadSnapshotRequest) (*emptypb.Empty, error) { - ctx, cancel := context.WithTimeout(ctx, time.Minute*10) - defer cancel() - eg := errgroup.Group{} - - networkId := request.NetworkId - mode := FromSnapshotTypes(request.Type) - - var headerSpec, bodySpec, stateSpec, receiptSpec *torrentSpecFromDb - - if err := s.db.View(ctx, func(tx kv.Tx) error { - var err error - headerSpec, err = getTorrentSpec(tx, snapshotsync.SnapshotType_headers, networkId) - if err != nil { - return err - } - bodySpec, err = getTorrentSpec(tx, snapshotsync.SnapshotType_bodies, networkId) - if err != nil { - return err - } - stateSpec, err = getTorrentSpec(tx, snapshotsync.SnapshotType_state, networkId) - if err != nil { - return err - } - receiptSpec, err = getTorrentSpec(tx, snapshotsync.SnapshotType_receipts, networkId) - if err != nil { - return err - } - return nil - }); err != nil { - return nil, err - } - - if mode.Headers { - eg.Go(func() error { - var newSpec *torrentSpecFromDb - var err error - newSpec, err = s.t.AddTorrent(ctx, headerSpec) - if err != nil { - return fmt.Errorf("add torrent: %w", err) - } - if newSpec != nil { - log.Info("Save spec", "snapshot", newSpec.snapshotType.String()) - if err = s.db.Update(ctx, func(tx kv.RwTx) error { - return saveTorrentSpec(tx, newSpec) - }); err != nil { - return err - } - if err != nil { - return err - } - } - return nil - }) - } - - if mode.Bodies { - eg.Go(func() error { - var newSpec *torrentSpecFromDb - var err error - newSpec, err = s.t.AddTorrent(ctx, bodySpec) - if err != nil { - return fmt.Errorf("add torrent: %w", err) - } - if newSpec != nil { - log.Info("Save spec", "snapshot", newSpec.snapshotType.String()) - if err = s.db.Update(ctx, func(tx kv.RwTx) error { - return saveTorrentSpec(tx, newSpec) - }); err != nil { - return err - } - if err != nil { - return err - } - } - return nil - }) - } - - if mode.State { - eg.Go(func() error { - var newSpec *torrentSpecFromDb - var err error - newSpec, err = s.t.AddTorrent(ctx, stateSpec) - if err != nil { - return fmt.Errorf("add torrent: %w", err) - } - if newSpec != nil { - log.Info("Save spec", "snapshot", newSpec.snapshotType.String()) - if err = s.db.Update(ctx, func(tx kv.RwTx) error { - return saveTorrentSpec(tx, newSpec) - }); err != nil { - return err - } - if err != nil { - return err - } - } - return nil - }) - } - - if mode.Receipts { - eg.Go(func() error { - var newSpec *torrentSpecFromDb - var err error - newSpec, err = s.t.AddTorrent(ctx, receiptSpec) - if err != nil { - return fmt.Errorf("add torrent: %w", err) - } - if newSpec != nil { - log.Info("Save spec", "snapshot", newSpec.snapshotType.String()) - if err = s.db.Update(ctx, func(tx kv.RwTx) error { - return saveTorrentSpec(tx, newSpec) - }); err != nil { - return err - } - if err != nil { - return err - } - } - return nil - }) - } - err := eg.Wait() - if err != nil { - return nil, err - } - return &emptypb.Empty{}, nil -} -func (s *SNDownloaderServer) Load() error { - return s.db.View(context.Background(), func(tx kv.Tx) error { - return s.t.Load(tx) - }) -} - -func (s *SNDownloaderServer) Snapshots(ctx context.Context, request *snapshotsync.SnapshotsRequest) (*snapshotsync.SnapshotsInfoReply, error) { - reply := snapshotsync.SnapshotsInfoReply{} - tx, err := s.db.BeginRo(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - resp, err := s.t.GetSnapshots(tx, request.NetworkId) - if err != nil { - return nil, err - } - for i := range resp { - reply.Info = append(reply.Info, resp[i]) - } - return &reply, nil -} - -func (s *SNDownloaderServer) Stats(ctx context.Context) map[string]torrent.TorrentStats { - stats := map[string]torrent.TorrentStats{} - torrents := s.t.Cli.Torrents() - for _, t := range torrents { - stats[t.Name()] = t.Stats() - } - return stats -} diff --git a/turbo/snapshotsync/snapshot_mode.go b/turbo/snapshotsync/snapshot_mode.go index 592b56c38fe..f217c87a3c2 100644 --- a/turbo/snapshotsync/snapshot_mode.go +++ b/turbo/snapshotsync/snapshot_mode.go @@ -1,5 +1,6 @@ package snapshotsync +/* import ( "fmt" @@ -83,3 +84,4 @@ func SnapshotModeFromString(flags string) (SnapshotMode, error) { } return mode, nil } +*/ diff --git a/turbo/snapshotsync/snapshot_mode_test.go b/turbo/snapshotsync/snapshot_mode_test.go index 810d4494f41..84d24418ebb 100644 --- a/turbo/snapshotsync/snapshot_mode_test.go +++ b/turbo/snapshotsync/snapshot_mode_test.go @@ -1,5 +1,6 @@ package snapshotsync +/* import ( "reflect" "testing" @@ -39,3 +40,4 @@ func TestSnapshotModeFromString(t *testing.T) { t.Fatal(sm) } } +*/ diff --git a/turbo/snapshotsync/snapshothashes/embed.go b/turbo/snapshotsync/snapshothashes/embed.go new file mode 100644 index 00000000000..bb0f139f0f6 --- /dev/null +++ b/turbo/snapshotsync/snapshothashes/embed.go @@ -0,0 +1,49 @@ +package snapshothashes + +import ( + _ "embed" + "encoding/json" + + "github.com/ledgerwatch/erigon/params/networkname" +) + +//go:embed erigon-snapshots/mainnet.json +var mainnet []byte +var Mainnet = fromJson(mainnet) + +//go:embed erigon-snapshots/goerli.json +var goerli []byte +var Goerli = fromJson(goerli) + +type Preverified map[string]string + +func fromJson(in []byte) (out Preverified) { + if err := json.Unmarshal(in, &out); err != nil { + panic(err) + } + return out +} + +var ( + MainnetChainSnapshotConfig = &Config{} + GoerliChainSnapshotConfig = &Config{ + ExpectBlocks: 5_900_000 - 1, + Preverified: Goerli, + } +) + +type Config struct { + ExpectBlocks uint64 + Preverified Preverified +} + +func KnownConfig(networkName string) *Config { + switch networkName { + case networkname.MainnetChainName: + return MainnetChainSnapshotConfig + case networkname.GoerliChainName: + return GoerliChainSnapshotConfig + default: + return nil + } +} diff --git a/turbo/snapshotsync/snapshothashes/erigon-snapshots b/turbo/snapshotsync/snapshothashes/erigon-snapshots new file mode 160000 index 00000000000..40713656a9f --- /dev/null +++ b/turbo/snapshotsync/snapshothashes/erigon-snapshots @@ -0,0 +1 @@ +Subproject commit 40713656a9f5c60dcb0125cb382d41014d601434 diff --git a/turbo/snapshotsync/wrapdb.go b/turbo/snapshotsync/wrapdb.go index 5d7b74f5e66..e13442ab0cc 100644 --- a/turbo/snapshotsync/wrapdb.go +++ b/turbo/snapshotsync/wrapdb.go @@ -1,5 +1,6 @@ package snapshotsync +/* import ( "github.com/ledgerwatch/erigon-lib/gointerfaces/snapshotsync" "github.com/ledgerwatch/erigon-lib/kv" @@ -56,3 +57,4 @@ func WrapBySnapshotsFromDownloader(db kv.RwDB, snapshots map[snapshotsync.Snapsh return snKV.Open(), nil } +*/ diff --git a/turbo/stages/headerdownload/header_algos.go b/turbo/stages/headerdownload/header_algos.go index 87ec09d99ab..23558b9bd02 100644 --- a/turbo/stages/headerdownload/header_algos.go +++ b/turbo/stages/headerdownload/header_algos.go @@ -26,7 +26,7 @@ import ( "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/eth/stagedsync/stages" "github.com/ledgerwatch/erigon/p2p/enode" - "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/params/networkname" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/log/v3" ) @@ -439,10 +439,10 @@ func InitPreverifiedHashes(chain string) (map[common.Hash]struct{}, uint64) { var encodings []string var height uint64 switch chain { - case params.MainnetChainName: + case networkname.MainnetChainName: encodings = mainnetPreverifiedHashes height = mainnetPreverifiedHeight - case params.RopstenChainName: + case networkname.RopstenChainName: encodings = ropstenPreverifiedHashes height = ropstenPreverifiedHeight default: diff --git a/turbo/stages/mock_sentry.go b/turbo/stages/mock_sentry.go index d2bad024704..780825536d3 100644 --- a/turbo/stages/mock_sentry.go +++ b/turbo/stages/mock_sentry.go @@ -13,6 +13,7 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/direct" "github.com/ledgerwatch/erigon-lib/gointerfaces" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" proto_sentry "github.com/ledgerwatch/erigon-lib/gointerfaces/sentry" ptypes "github.com/ledgerwatch/erigon-lib/gointerfaces/types" "github.com/ledgerwatch/erigon-lib/kv" @@ -20,7 +21,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv/memdb" "github.com/ledgerwatch/erigon-lib/kv/remotedbserver" "github.com/ledgerwatch/erigon-lib/txpool" - "github.com/ledgerwatch/erigon/cmd/sentry/download" + "github.com/ledgerwatch/erigon/cmd/sentry/sentry" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/consensus/ethash" @@ -60,7 +61,7 @@ type MockSentry struct { MiningSync *stagedsync.Sync PendingBlocks chan *types.Block MinedBlocks chan *types.Block - downloader *download.ControlServerImpl + downloader *sentry.ControlServerImpl Key *ecdsa.PrivateKey Genesis *types.Block SentryClient direct.SentryClient @@ -260,7 +261,7 @@ func MockWithEverything(t *testing.T, gspec *core.Genesis, key *ecdsa.PrivateKey blockDownloaderWindow := 65536 networkID := uint64(1) - mock.downloader, err = download.NewControlServer(mock.DB, "mock", mock.ChainConfig, mock.Genesis.Hash(), mock.Engine, networkID, sentries, blockDownloaderWindow) + mock.downloader, err = sentry.NewControlServer(mock.DB, "mock", mock.ChainConfig, mock.Genesis.Hash(), mock.Engine, networkID, sentries, blockDownloaderWindow) if err != nil { if t != nil { t.Fatal(err) @@ -271,6 +272,7 @@ func MockWithEverything(t *testing.T, gspec *core.Genesis, key *ecdsa.PrivateKey blockReader := snapshotsync.NewBlockReader() var allSnapshots *snapshotsync.AllSnapshots + var snapshotsDownloader proto_downloader.DownloaderClient mock.Sync = stagedsync.New( stagedsync.DefaultStages(mock.Ctx, prune, stagedsync.StageHeadersCfg( mock.DB, @@ -285,6 +287,7 @@ func MockWithEverything(t *testing.T, gspec *core.Genesis, key *ecdsa.PrivateKey nil, nil, allSnapshots, + snapshotsDownloader, blockReader, mock.tmpdir, ), stagedsync.StageBlockHashesCfg(mock.DB, mock.tmpdir, mock.ChainConfig), stagedsync.StageBodiesCfg( @@ -344,13 +347,13 @@ func MockWithEverything(t *testing.T, gspec *core.Genesis, key *ecdsa.PrivateKey ) mock.StreamWg.Add(1) - go download.RecvMessageLoop(mock.Ctx, mock.SentryClient, mock.downloader, &mock.ReceiveWg) + go sentry.RecvMessageLoop(mock.Ctx, mock.SentryClient, mock.downloader, &mock.ReceiveWg) mock.StreamWg.Wait() mock.StreamWg.Add(1) - go download.RecvUploadMessageLoop(mock.Ctx, mock.SentryClient, mock.downloader, &mock.ReceiveWg) + go sentry.RecvUploadMessageLoop(mock.Ctx, mock.SentryClient, mock.downloader, &mock.ReceiveWg) mock.StreamWg.Wait() mock.StreamWg.Add(1) - go download.RecvUploadHeadersMessageLoop(mock.Ctx, mock.SentryClient, mock.downloader, &mock.ReceiveWg) + go sentry.RecvUploadHeadersMessageLoop(mock.Ctx, mock.SentryClient, mock.downloader, &mock.ReceiveWg) mock.StreamWg.Wait() return mock diff --git a/turbo/stages/stageloop.go b/turbo/stages/stageloop.go index 47122e46b92..45e78f2ea4c 100644 --- a/turbo/stages/stageloop.go +++ b/turbo/stages/stageloop.go @@ -10,9 +10,10 @@ import ( "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/dbg" + proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces" - "github.com/ledgerwatch/erigon/cmd/sentry/download" + "github.com/ledgerwatch/erigon/cmd/sentry/sentry" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core/rawdb" @@ -23,9 +24,9 @@ import ( "github.com/ledgerwatch/erigon/eth/stagedsync/stages" "github.com/ledgerwatch/erigon/ethdb/privateapi" "github.com/ledgerwatch/erigon/p2p" - "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/turbo/shards" "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/snapshotsync/snapshothashes" "github.com/ledgerwatch/erigon/turbo/stages/headerdownload" "github.com/ledgerwatch/log/v3" ) @@ -224,17 +225,18 @@ func NewStagedSync( p2pCfg p2p.Config, cfg ethconfig.Config, terminalTotalDifficulty *big.Int, - controlServer *download.ControlServerImpl, + controlServer *sentry.ControlServerImpl, tmpdir string, accumulator *shards.Accumulator, reverseDownloadCh chan types.Header, statusCh chan privateapi.ExecutionStatus, waitingForPOSHeaders *bool, + snapshotDownloader proto_downloader.DownloaderClient, ) (*stagedsync.Sync, error) { var blockReader interfaces.FullBlockReader var allSnapshots *snapshotsync.AllSnapshots if cfg.Snapshot.Enabled { - allSnapshots = snapshotsync.NewAllSnapshots(cfg.Snapshot.Dir, params.KnownSnapshots(controlServer.ChainConfig.ChainName)) + allSnapshots = snapshotsync.NewAllSnapshots(cfg.Snapshot.Dir, snapshothashes.KnownConfig(controlServer.ChainConfig.ChainName)) blockReader = snapshotsync.NewBlockReaderWithSnapshots(allSnapshots) } else { blockReader = snapshotsync.NewBlockReader() @@ -254,6 +256,7 @@ func NewStagedSync( reverseDownloadCh, waitingForPOSHeaders, allSnapshots, + snapshotDownloader, blockReader, tmpdir, ), stagedsync.StageBlockHashesCfg(db, tmpdir, controlServer.ChainConfig), stagedsync.StageBodiesCfg(