feat: refactor: actor bundling system (#8838)
1. Include the builtin-actors in the lotus source tree.
2. Embed the bundle on build instead of downloading at runtime.
3. Avoid reading the bundle whenever possible by including bundle
metadata (the bundle CID, the actor CIDs, etc.).
4. Remove everything related to dependency injection.
1. We're no longer downloading the bundle, so doing anything ahead
of time doesn't really help.
2. We register the manifests on init because, unfortunately, they're
global.
3. We explicitly load the current actors bundle in the genesis
state-tree method.
4. For testing, we just change the in-use bundle with a bit of a
hack. It's not great, but using dependency injection doesn't make
any sense either because, again, the manifest information is
global.
5. Remove the bundle.toml file. Bundles may be overridden by
specifying an override path in the parameters file, or an
environment variable.
fixes #8701
This commit is contained in:
+58
-187
@@ -2,215 +2,86 @@ package bundle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ipld/go-car"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/filecoin-project/lotus/blockstore"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
var log = logging.Logger("bundle-fetcher")
|
||||
func LoadBundleFromFile(ctx context.Context, bs blockstore.Blockstore, path string) (cid.Cid, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("error opening bundle %q for builtin-actors: %w", path, err)
|
||||
}
|
||||
defer f.Close() //nolint
|
||||
|
||||
type BundleFetcher struct {
|
||||
path string
|
||||
return LoadBundle(ctx, bs, f)
|
||||
}
|
||||
|
||||
func NewBundleFetcher(basepath string) (*BundleFetcher, error) {
|
||||
path := filepath.Join(basepath, "builtin-actors")
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return nil, xerrors.Errorf("error making bundle directory %s: %w", path, err)
|
||||
func LoadBundle(ctx context.Context, bs blockstore.Blockstore, r io.Reader) (cid.Cid, error) {
|
||||
hdr, err := car.LoadCar(ctx, bs, r)
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("error loading builtin actors bundle: %w", err)
|
||||
}
|
||||
|
||||
return &BundleFetcher{path: path}, nil
|
||||
if len(hdr.Roots) != 1 {
|
||||
return cid.Undef, xerrors.Errorf("expected one root when loading actors bundle, got %d", len(hdr.Roots))
|
||||
}
|
||||
return hdr.Roots[0], nil
|
||||
}
|
||||
|
||||
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)
|
||||
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.checkRelease(bundleBasePath, bundleFile, bundleHash)
|
||||
if err == nil {
|
||||
return bundleFilePath, nil
|
||||
}
|
||||
|
||||
log.Warnf("invalid bundle %s: %s; refetching", bundleName, err)
|
||||
}
|
||||
|
||||
log.Infof("fetching bundle %s", bundleFile)
|
||||
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.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)
|
||||
}
|
||||
|
||||
return bundleFilePath, nil
|
||||
}
|
||||
|
||||
func (b *BundleFetcher) fetchURL(url, path string) error {
|
||||
log.Infof("fetching URL: %s", url)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
resp, err := http.Get(url) //nolint
|
||||
if err != nil {
|
||||
if isTemporary(err) {
|
||||
log.Warnf("temporary error fetching %s: %s; retrying in 1s", url, err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
return xerrors.Errorf("error fetching %s: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Warnf("unexpected response fetching %s: %s (%d); retrying in 1s", url, resp.Status, resp.StatusCode)
|
||||
time.Sleep(time.Second)
|
||||
// LoadBundles loads the bundles for the specified actor versions into the passed blockstore, if and
|
||||
// only if the bundle's manifest is not already present in the blockstore.
|
||||
func LoadBundles(ctx context.Context, bs blockstore.Blockstore, versions ...actors.Version) error {
|
||||
for _, av := range versions {
|
||||
// No bundles before version 8.
|
||||
if av < actors.Version8 {
|
||||
continue
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
manifestCid, ok := actors.GetManifest(av)
|
||||
if !ok {
|
||||
// All manifests are registered on start, so this must succeed.
|
||||
return xerrors.Errorf("unknown actor version v%d", av)
|
||||
}
|
||||
|
||||
if haveManifest, err := bs.Has(ctx, manifestCid); err != nil {
|
||||
return xerrors.Errorf("blockstore error when loading manifest %s: %w", manifestCid, err)
|
||||
} else if haveManifest {
|
||||
// We already have the manifest, and therefore everything under it.
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
root cid.Cid
|
||||
err error
|
||||
)
|
||||
if path, ok := build.BundleOverrides[av]; ok {
|
||||
root, err = LoadBundleFromFile(ctx, bs, path)
|
||||
} else if embedded, ok := build.GetEmbeddedBuiltinActorsBundle(av); ok {
|
||||
root, err = LoadBundle(ctx, bs, bytes.NewReader(embedded))
|
||||
} else {
|
||||
err = xerrors.Errorf("bundle for actors version v%d not found", av)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error opening %s for writing: %w", path, err)
|
||||
}
|
||||
defer f.Close() //nolint
|
||||
|
||||
if _, err := io.Copy(f, resp.Body); err != nil {
|
||||
return xerrors.Errorf("error writing %s: %w", path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
if root != manifestCid {
|
||||
return xerrors.Errorf("expected manifest for actors version %d does not match actual: %s != %s", av, manifestCid, root)
|
||||
}
|
||||
|
||||
return xerrors.Errorf("all attempts to fetch %s failed", url)
|
||||
}
|
||||
|
||||
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)
|
||||
if err := b.fetchURL(bundleHashUrl, bundleHashPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bundleFileUrl := fmt.Sprintf("https://github.com/filecoin-project/builtin-actors/releases/download/%s/%s",
|
||||
release, bundleFile)
|
||||
bundleFilePath := filepath.Join(bundleBasePath, bundleFile)
|
||||
if err := b.fetchURL(bundleFileUrl, bundleFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return xerrors.Errorf("error opening %s: %w", bundleHashPath, err)
|
||||
}
|
||||
defer f.Close() //nolint
|
||||
|
||||
bs, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error reading %s: %w", bundleHashPath, err)
|
||||
}
|
||||
|
||||
parts := strings.Split(string(bs), " ")
|
||||
hashHex := parts[0]
|
||||
|
||||
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", cksum, err)
|
||||
}
|
||||
|
||||
bundleFilePath := filepath.Join(bundleBasePath, bundleFile)
|
||||
f, err := os.Open(bundleFilePath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error opening %s: %w", bundleFilePath, err)
|
||||
}
|
||||
defer f.Close() //nolint
|
||||
|
||||
h256 := sha256.New()
|
||||
if _, err := io.Copy(h256, f); err != nil {
|
||||
return xerrors.Errorf("error computing digest for %s: %w", bundleFilePath, err)
|
||||
}
|
||||
digest := h256.Sum(nil)
|
||||
|
||||
if !bytes.Equal(digest, expectedDigest) {
|
||||
return xerrors.Errorf("hash mismatch")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isTemporary(err error) bool {
|
||||
if ne, ok := err.(net.Error); ok {
|
||||
return ne.Temporary()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ipld/go-car"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/blockstore"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
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.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)
|
||||
}
|
||||
|
||||
return LoadBundle(ctx, bs, path, av)
|
||||
}
|
||||
|
||||
func LoadBundle(ctx context.Context, bs blockstore.Blockstore, path string, av actors.Version) (cid.Cid, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("error opening bundle for builtin-actors version %d: %w", av, err)
|
||||
}
|
||||
defer f.Close() //nolint
|
||||
|
||||
hdr, err := car.LoadCar(ctx, bs, f)
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("error loading builtin actors v%d bundle: %w", av, err)
|
||||
}
|
||||
|
||||
// TODO: check that this only has one root?
|
||||
manifestCid := hdr.Roots[0]
|
||||
|
||||
if val, ok := build.ActorsCIDs[av]; ok {
|
||||
if val != manifestCid {
|
||||
return cid.Undef, xerrors.Errorf("actors V%d manifest CID %s did not match CID given in params file: %s", av, manifestCid, val)
|
||||
}
|
||||
}
|
||||
actors.AddManifest(av, manifestCid)
|
||||
|
||||
mfCid, ok := actors.GetManifest(av)
|
||||
if !ok {
|
||||
return cid.Undef, xerrors.Errorf("missing manifest CID for builtin-actors vrsion %d", av)
|
||||
}
|
||||
|
||||
return mfCid, nil
|
||||
}
|
||||
|
||||
// utility for blanket loading outside DI
|
||||
func FetchAndLoadBundles(ctx context.Context, bs blockstore.Blockstore, bar map[actors.Version]build.Bundle) error {
|
||||
netw := build.NetworkBundle
|
||||
|
||||
path := os.Getenv("LOTUS_PATH")
|
||||
if path == "" {
|
||||
var err error
|
||||
path, err = homedir.Expand("~/.lotus")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for av, bd := range bar {
|
||||
envvar := fmt.Sprintf("LOTUS_BUILTIN_ACTORS_V%d_BUNDLE", av)
|
||||
switch {
|
||||
case os.Getenv(envvar) != "":
|
||||
// this is a local bundle, specified by an env var to load from the filesystem
|
||||
path := os.Getenv(envvar)
|
||||
|
||||
if _, err := LoadBundle(ctx, bs, path, av); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case bd.Path[netw] != "":
|
||||
if _, err := LoadBundle(ctx, bs, bd.Path[netw], av); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case bd.URL[netw].URL != "":
|
||||
if _, err := FetchAndLoadBundleFromURL(ctx, path, bs, av, bd.Release, netw, bd.URL[netw].URL, bd.URL[netw].Checksum); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case bd.Release != "":
|
||||
if _, err := FetchAndLoadBundleFromRelease(ctx, path, bs, av, bd.Release, netw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cborStore := cbor.NewCborStore(bs)
|
||||
if err := actors.LoadManifests(ctx, cborStore); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user