diff --git a/build/README-bundle.md b/build/README-bundle.md index 2e33df0c8..2bbe652eb 100644 --- a/build/README-bundle.md +++ b/build/README-bundle.md @@ -6,7 +6,7 @@ The bundles are specified in build/bundles.toml using the following syntax: ```toml [[bundles]] version = X # actors version -release = tag # release gag +release = tag # release tag ``` This will add a bundle for version `X`, using the github release `tag` @@ -30,6 +30,18 @@ path = /path/to/builtin-actors.car development = true ``` +## Additional Options for Bundles + +- You can also specify a URL, together with a sha256 checksum to avoid downloading from + github. +- You can also specify an EnvVar, to provide the path dynamically at runtime. + +The precedence for bundle fetching/loading is as folllows: +- Check the EnvVar; use the bundle specified by it. +- Check the Path; use the bundle specified by it. +- Check the URL; use the bundle specified by it, and verify the checksum which must be present. +- Otherwise, use the release tag and download from github. + ## Local Storage Bundles downloaded from github will be stored in diff --git a/build/bundle.go b/build/bundle.go index ff2631bb0..e404abf09 100644 --- a/build/bundle.go +++ b/build/bundle.go @@ -20,8 +20,14 @@ type Bundle struct { Version actors.Version // Release is the release id Release string - // Path is the (optional) bundle path; uses the appropriate release bundle if unset + // EnvVar is the (optional) env var specifying the bundle path; takes precdence over path + EnvVar string + // Path is the (optional) bundle path; takes precedence over url Path string + // URL is the (optional) bundle URL; takes precdence over github release + URL string + // CHecksum is the bundle sha256 checksume in hex, when specifying a URL. + Checksum string // Devlopment indicates whether this is a development version; when set, in conjunction with path, // it will always load the bundle to the blockstore, without recording the manifest CID in the // datastore. diff --git a/node/bundle/bundle.go b/node/bundle/bundle.go index 99f214383..a3a14f468 100644 --- a/node/bundle/bundle.go +++ b/node/bundle/bundle.go @@ -33,7 +33,7 @@ func NewBundleFetcher(basepath string) (*BundleFetcher, error) { return &BundleFetcher{path: path}, nil } -func (b *BundleFetcher) Fetch(version int, release, netw string) (path string, err error) { +func (b *BundleFetcher) FetchFromRelease(version int, release, netw string) (path string, err error) { bundleName := fmt.Sprintf("builtin-actors-%s", netw) bundleFile := fmt.Sprintf("%s.car", bundleName) bundleHash := fmt.Sprintf("%s.sha256", bundleName) @@ -46,7 +46,7 @@ func (b *BundleFetcher) Fetch(version int, release, netw string) (path string, e // check if it exists; if it does, check the hash bundleFilePath := filepath.Join(bundleBasePath, bundleFile) if _, err := os.Stat(bundleFilePath); err == nil { - err := b.check(bundleBasePath, bundleFile, bundleHash) + err := b.checkRelease(bundleBasePath, bundleFile, bundleHash) if err == nil { return bundleFilePath, nil } @@ -55,12 +55,46 @@ func (b *BundleFetcher) Fetch(version int, release, netw string) (path string, e } log.Infof("fetching bundle %s", bundleFile) - if err := b.fetch(release, bundleBasePath, bundleFile, bundleHash); err != nil { + if err := b.fetchFromRelease(release, bundleBasePath, bundleFile, bundleHash); err != nil { log.Errorf("error fetching bundle %s: %s", bundleName, err) return "", xerrors.Errorf("error fetching bundle: %w", err) } - if err := b.check(bundleBasePath, bundleFile, bundleHash); err != nil { + if err := b.checkRelease(bundleBasePath, bundleFile, bundleHash); err != nil { + log.Errorf("error checking bundle %s: %s", bundleName, err) + return "", xerrors.Errorf("error checking bundle: %s", err) + } + + return bundleFilePath, nil +} + +func (b *BundleFetcher) FetchFromURL(version int, release, netw, url, cksum string) (path string, err error) { + bundleName := fmt.Sprintf("builtin-actors-%s", netw) + bundleFile := fmt.Sprintf("%s.car", bundleName) + bundleBasePath := filepath.Join(b.path, fmt.Sprintf("v%d", version), release) + + if err := os.MkdirAll(bundleBasePath, 0755); err != nil { + return "", xerrors.Errorf("error making bundle directory %s: %w", bundleBasePath, err) + } + + // check if it exists; if it does, check the hash + bundleFilePath := filepath.Join(bundleBasePath, bundleFile) + if _, err := os.Stat(bundleFilePath); err == nil { + err := b.checkHash(bundleBasePath, bundleFile, cksum) + if err == nil { + return bundleFilePath, nil + } + + log.Warnf("invalid bundle %s: %s; refetching", bundleName, err) + } + + log.Infof("fetching bundle %s", bundleFile) + if err := b.fetchFromURL(bundleBasePath, bundleFile, url); err != nil { + log.Errorf("error fetching bundle %s: %s", bundleName, err) + return "", xerrors.Errorf("error fetching bundle: %w", err) + } + + if err := b.checkHash(bundleBasePath, bundleFile, cksum); err != nil { log.Errorf("error checking bundle %s: %s", bundleName, err) return "", xerrors.Errorf("error checking bundle: %s", err) } @@ -105,7 +139,7 @@ func (b *BundleFetcher) fetchURL(url, path string) error { return xerrors.Errorf("all attempts to fetch %s failed", url) } -func (b *BundleFetcher) fetch(release, bundleBasePath, bundleFile, bundleHash string) error { +func (b *BundleFetcher) fetchFromRelease(release, bundleBasePath, bundleFile, bundleHash string) error { bundleHashUrl := fmt.Sprintf("https://github.com/filecoin-project/builtin-actors/releases/download/%s/%s", release, bundleHash) bundleHashPath := filepath.Join(bundleBasePath, bundleHash) @@ -123,7 +157,12 @@ func (b *BundleFetcher) fetch(release, bundleBasePath, bundleFile, bundleHash st return nil } -func (b *BundleFetcher) check(bundleBasePath, bundleFile, bundleHash string) error { +func (b *BundleFetcher) fetchFromURL(bundleBasePath, bundleFile, url string) error { + bundleFilePath := filepath.Join(bundleBasePath, bundleFile) + return b.fetchURL(url, bundleFilePath) +} + +func (b *BundleFetcher) checkRelease(bundleBasePath, bundleFile, bundleHash string) error { bundleHashPath := filepath.Join(bundleBasePath, bundleHash) f, err := os.Open(bundleHashPath) if err != nil { @@ -138,13 +177,18 @@ func (b *BundleFetcher) check(bundleBasePath, bundleFile, bundleHash string) err parts := strings.Split(string(bs), " ") hashHex := parts[0] - expectedDigest, err := hex.DecodeString(hashHex) + + return b.checkHash(bundleBasePath, bundleFile, hashHex) +} + +func (b *BundleFetcher) checkHash(bundleBasePath, bundleFile, cksum string) error { + expectedDigest, err := hex.DecodeString(cksum) if err != nil { - return xerrors.Errorf("error decoding digest from %s: %w", bundleHashPath, err) + return xerrors.Errorf("error decoding digest from %s: %w", cksum, err) } bundleFilePath := filepath.Join(bundleBasePath, bundleFile) - f, err = os.Open(bundleFilePath) + f, err := os.Open(bundleFilePath) if err != nil { return xerrors.Errorf("error opening %s: %w", bundleFilePath, err) } diff --git a/node/bundle/manifest.go b/node/bundle/manifest.go index 4bd55075e..0e4819ce2 100644 --- a/node/bundle/manifest.go +++ b/node/bundle/manifest.go @@ -17,13 +17,27 @@ import ( "github.com/mitchellh/go-homedir" ) -func FetchAndLoadBundle(ctx context.Context, basePath string, bs blockstore.Blockstore, av actors.Version, rel, netw string) (cid.Cid, error) { +func FetchAndLoadBundleFromRelease(ctx context.Context, basePath string, bs blockstore.Blockstore, av actors.Version, rel, netw string) (cid.Cid, error) { fetcher, err := NewBundleFetcher(basePath) if err != nil { return cid.Undef, xerrors.Errorf("error creating fetcher for builtin-actors version %d: %w", av, err) } - path, err := fetcher.Fetch(int(av), rel, netw) + path, err := fetcher.FetchFromRelease(int(av), rel, netw) + if err != nil { + return cid.Undef, xerrors.Errorf("error fetching bundle for builtin-actors version %d: %w", av, err) + } + + return LoadBundle(ctx, bs, path, av) +} + +func FetchAndLoadBundleFromURL(ctx context.Context, basePath string, bs blockstore.Blockstore, av actors.Version, rel, netw, url, cksum string) (cid.Cid, error) { + fetcher, err := NewBundleFetcher(basePath) + if err != nil { + return cid.Undef, xerrors.Errorf("error creating fetcher for builtin-actors version %d: %w", av, err) + } + + path, err := fetcher.FetchFromURL(int(av), rel, netw, url, cksum) if err != nil { return cid.Undef, xerrors.Errorf("error fetching bundle for builtin-actors version %d: %w", av, err) } @@ -72,12 +86,30 @@ func FetchAndLoadBundles(ctx context.Context, bs blockstore.Blockstore, bar map[ } for av, bd := range bar { - if bd.Path != "" { + switch { + case bd.EnvVar != "": + // this is a local bundle, specified by an env var to load from the filesystem + path := os.Getenv(bd.EnvVar) + if path == "" { + return xerrors.Errorf("bundle envvar is empty: %s", bd.EnvVar) + } + + if _, err := LoadBundle(ctx, bs, path, av); err != nil { + return err + } + + case bd.Path != "": if _, err := LoadBundle(ctx, bs, bd.Path, av); err != nil { return err } - } else { - if _, err := FetchAndLoadBundle(ctx, path, bs, av, bd.Release, netw); err != nil { + + case bd.URL != "": + if _, err := FetchAndLoadBundleFromURL(ctx, path, bs, av, bd.Release, netw, bd.URL, bd.Checksum); err != nil { + return err + } + + case bd.Release != "": + if _, err := FetchAndLoadBundleFromRelease(ctx, path, bs, av, bd.Release, netw); err != nil { return err } } diff --git a/node/modules/builtin_actors.go b/node/modules/builtin_actors.go index a21820585..c7cacaa56 100644 --- a/node/modules/builtin_actors.go +++ b/node/modules/builtin_actors.go @@ -2,6 +2,7 @@ package modules import ( "fmt" + "os" "path/filepath" "sync" @@ -69,6 +70,18 @@ func LoadBuiltinActors(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRe // we haven't recorded it in the datastore, so we need to load it var mfCid cid.Cid switch { + case bd.EnvVar != "": + // this is a local bundle, specified by an env var to load from the filesystem + path := os.Getenv(bd.EnvVar) + if path == "" { + return result, xerrors.Errorf("bundle envvar is empty: %s", bd.EnvVar) + } + + mfCid, err = bundle.LoadBundle(ctx, bs, path, av) + if err != nil { + return result, err + } + case bd.Path != "": // this is a local bundle, load it directly from the filessystem mfCid, err = bundle.LoadBundle(ctx, bs, bd.Path, av) @@ -76,9 +89,16 @@ func LoadBuiltinActors(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRe return result, err } + case bd.URL != "": + // fetch it from the specified URL + mfCid, err = bundle.FetchAndLoadBundleFromURL(ctx, r.Path(), bs, av, bd.Release, netw, bd.URL, bd.Checksum) + if err != nil { + return result, err + } + case bd.Release != "": // fetch it and add it to the blockstore - mfCid, err = bundle.FetchAndLoadBundle(ctx, r.Path(), bs, av, bd.Release, netw) + mfCid, err = bundle.FetchAndLoadBundleFromRelease(ctx, r.Path(), bs, av, bd.Release, netw) if err != nil { return result, err } @@ -135,12 +155,12 @@ func LoadBuiltinActorsTesting(lc fx.Lifecycle, mctx helpers.MetricsCtx, bs dtype case bd.Release != "": const basePath = "/tmp/lotus-testing" - if _, err := bundle.FetchAndLoadBundle(ctx, basePath, bs, av, bd.Release, netw); err != nil { + if _, err := bundle.FetchAndLoadBundleFromRelease(ctx, basePath, bs, av, bd.Release, netw); err != nil { return result, xerrors.Errorf("error loading testing bundle for builtin-actors version %d/%s: %w", av, netw, err) } default: - return result, xerrors.Errorf("no path or release specified for version %d bundle", av) + return result, xerrors.Errorf("no path or release specified for version %d testing bundle", av) } }