dynamic loading of builtin actor bundles

This commit is contained in:
vyzo 2022-05-11 22:30:07 +03:00
parent f0867a9615
commit 74f300290e
17 changed files with 310 additions and 279 deletions

View File

@ -57,14 +57,6 @@ build/.update-modules:
# end git modules
# builtin actor bundles
builtin-actor-bundles:
./build/builtin-actors/fetch-bundles.sh
BUILD_DEPS+=builtin-actor-bundles
.PHONY: builtin-actor-bundles
## MAIN BINARIES
CLEAN+=build/.update-modules

View File

@ -1,3 +0,0 @@
This directory contains the builtin actors v8 bundle, to be emdedded in the binary.
To change your actor bundle, prior to fixing for nv16 upgrade, generate a bundle using the actor
bundler and place it in this directory, in a file named builtin-actors-v8.car

View File

@ -1,2 +0,0 @@
actors7_release=""
actors8_release=c718ae4f957b1806

View File

@ -1,85 +0,0 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
. bundles.env
die() {
echo "$1"
exit 1
}
fetch() {
ver=$1
rel=$2
if [ ! -e $ver ]; then
mkdir $ver
fi
if [ -e $ver/release ]; then
cur=$(cat $ver/release)
if [ $cur == $rel ]; then
return 0
fi
fi
for net in mainnet caterpillarnet butterflynet calibrationnet devnet testing testing-fake-proofs; do
fetch_bundle $ver $rel $net
done
# remember the current release so that we don't have to hit github unless we have modified it
echo $rel > $ver/release
}
fetch_bundle() {
ver=$1
rel=$2
net=$3
target=builtin-actors-$net.car
hash=builtin-actors-$net.sha256
pushd $ver
# fetch the hash first and check if it matches what we (may) already have
curl -L --retry 3 https://github.com/filecoin-project/builtin-actors/releases/download/$rel/$hash -o $hash || die "error fetching hash for $ver/$net"
if [ -e $target ]; then
if (shasum -a 256 --check $hash); then
popd
return 0
fi
fi
# we don't have the (correct) bundle, fetch it
curl -L --retry 3 https://github.com/filecoin-project/builtin-actors/releases/download/$rel/$target -o $target || die "error fetching bundle for $ver/$net"
# verify
shasum -a 256 --check $hash || die "hash mismatch"
# all good
popd
}
touch_bundles() {
ver=$1
if [ ! -e $ver ]; then
mkdir $ver
fi
for net in mainnet caterpillarnet butterflynet calibrationnet devnet testing testing-fake-proofs; do
touch $ver/builtin-actors-$net.car
done
}
if [ -n "$actors7_release" ]; then
fetch v7 "$actors7_release"
else
touch_bundles v7
fi
if [ -n "$actors8_release" ]; then
fetch v8 "$actors8_release"
else
touch_bundles v8
fi

14
build/builtin_actors.go Normal file
View File

@ -0,0 +1,14 @@
package build
import (
"github.com/filecoin-project/lotus/chain/actors"
)
// TODO a nicer interface would be to embed (and parse) a toml file containing the releases
var BuiltinActorReleases map[actors.Version]string
func init() {
BuiltinActorReleases = map[actors.Version]string{
actors.Version8: "b71c2ec785aec23d",
}
}

View File

@ -1,22 +0,0 @@
//go:build debug || 2k || testground
// +build debug 2k testground
package build
import (
_ "embed"
)
//go:embed builtin-actors/v8/builtin-actors-devnet.car
var actorsv8 []byte
func BuiltinActorsV8Bundle() []byte {
return actorsv8
}
//go:embed builtin-actors/v7/builtin-actors-devnet.car
var actorsv7 []byte
func BuiltinActorsV7Bundle() []byte {
return actorsv7
}

View File

@ -1,22 +0,0 @@
//go:build butterflynet
// +build butterflynet
package build
import (
_ "embed"
)
//go:embed builtin-actors/v8/builtin-actors-butterflynet.car
var actorsv8 []byte
func BuiltinActorsV8Bundle() []byte {
return actorsv8
}
//go:embed builtin-actors/v7/builtin-actors-butterflynet.car
var actorsv7 []byte
func BuiltinActorsV7Bundle() []byte {
return actorsv7
}

View File

@ -1,22 +0,0 @@
//go:build calibnet
// +build calibnet
package build
import (
_ "embed"
)
//go:embed builtin-actors/v8/builtin-actors-calibrationnet.car
var actorsv8 []byte
func BuiltinActorsV8Bundle() []byte {
return actorsv8
}
//go:embed builtin-actors/v7/builtin-actors-calibrationnet.car
var actorsv7 []byte
func BuiltinActorsV7Bundle() []byte {
return actorsv7
}

View File

@ -1,22 +0,0 @@
//go:build interopnet
// +build interopnet
package build
import (
_ "embed"
)
//go:embed builtin-actors/v8/builtin-actors-caterpillarnet.car
var actorsv8 []byte
func BuiltinActorsV8Bundle() []byte {
return actorsv8
}
//go:embed builtin-actors/v7/builtin-actors-caterpillarnet.car
var actorsv7 []byte
func BuiltinActorsV7Bundle() []byte {
return actorsv7
}

View File

@ -1,22 +0,0 @@
//go:build !debug && !2k && !testground && !calibnet && !nerpanet && !butterflynet && !interopnet
// +build !debug,!2k,!testground,!calibnet,!nerpanet,!butterflynet,!interopnet
package build
import (
_ "embed"
)
//go:embed builtin-actors/v8/builtin-actors-mainnet.car
var actorsv8 []byte
func BuiltinActorsV8Bundle() []byte {
return actorsv8
}
//go:embed builtin-actors/v7/builtin-actors-mainnet.car
var actorsv7 []byte
func BuiltinActorsV7Bundle() []byte {
return actorsv7
}

151
chain/actors/bundle.go Normal file
View File

@ -0,0 +1,151 @@
package actors
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"golang.org/x/xerrors"
logging "github.com/ipfs/go-log/v2"
)
var logb = logging.Logger("bundle-fetcher")
type BundleFetcher struct {
path string
}
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: err")
}
return &BundleFetcher{path: path}, nil
}
func (b *BundleFetcher) Fetch(version Version, 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: 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.check(bundleBasePath, bundleFile, bundleHash)
if err == nil {
return bundleFilePath, nil
}
logb.Warnf("invalid bundle %s: %s; refetching", bundleName, err)
}
logb.Infof("fetching bundle %s", bundleFile)
if err := b.fetch(release, bundleBasePath, bundleFile, bundleHash); err != nil {
logb.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 {
logb.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 {
logb.Infof("fetching URL: %s", url)
resp, err := http.Get(url)
if err != nil {
return xerrors.Errorf("error fetching %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return xerrors.Errorf("error fetching %s: http response status is %d", url, resp.StatusCode)
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return xerrors.Errorf("error opening %s for writing: %w", path, err)
}
defer f.Close()
if _, err := io.Copy(f, resp.Body); err != nil {
return xerrors.Errorf("error writing %s: %w", path, err)
}
return nil
}
func (b *BundleFetcher) fetch(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) check(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()
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]
expectedDigest, err := hex.DecodeString(hashHex)
if err != nil {
return xerrors.Errorf("error decoding digest from %s: %w", bundleHashPath, 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()
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
}

View File

@ -3,6 +3,8 @@ package actors
import (
"bytes"
"context"
"io"
"os"
"strings"
"sync"
@ -15,6 +17,8 @@ import (
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/chain/actors/adt"
"github.com/filecoin-project/specs-actors/v8/actors/builtin/manifest"
"github.com/mitchellh/go-homedir"
)
var manifestCids map[Version]cid.Cid = map[Version]cid.Cid{
@ -117,6 +121,40 @@ func CanonicalName(name string) string {
return name
}
func FetchAndLoadBundle(ctx context.Context, basePath string, bs blockstore.Blockstore, av 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(av, rel, netw)
if err != nil {
return cid.Undef, xerrors.Errorf("error fetching bundle for builtin-actors version %d: %w", av, err)
}
f, err := os.Open(path)
if err != nil {
return cid.Undef, xerrors.Errorf("error opening bundle for builtin-actors vresion %d: %w", av, err)
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return cid.Undef, xerrors.Errorf("error reading bundle for builtin-actors vresion %d: %w", av, err)
}
if err := LoadBundle(ctx, bs, av, data); err != nil {
return cid.Undef, xerrors.Errorf("error loading bundle for builtin-actors vresion %d: %w", av, err)
}
mfCid, ok := GetManifest(av)
if !ok {
return cid.Undef, xerrors.Errorf("missing manifest CID for builtin-actors vrsion %d", av)
}
return mfCid, nil
}
func LoadBundle(ctx context.Context, bs blockstore.Blockstore, av Version, data []byte) error {
blobr := bytes.NewReader(data)
@ -131,11 +169,34 @@ func LoadBundle(ctx context.Context, bs blockstore.Blockstore, av Version, data
return nil
}
func LoadManifestFromBundle(ctx context.Context, bs blockstore.Blockstore, av Version, data []byte) error {
if err := LoadBundle(ctx, bs, av, data); err != nil {
// utility for blanket loading outside DI
func FetchAndLoadBundles(ctx context.Context, bs blockstore.Blockstore, bar map[Version]string) error {
// TODO: how to get the network name properly?
netw := "mainnet"
if v := os.Getenv("LOTUS_FIL_NETWORK"); v != "" {
netw = v
}
// TODO: how to get the repo properly?
path, err := homedir.Expand("~/.lotus")
if err != nil {
return err
}
cborStore := cbor.NewCborStore(bs)
return LoadManifests(ctx, cborStore)
if p := os.Getenv("LOTUS_PATH"); p != "" {
path = p
}
for av, rel := range bar {
if _, err := FetchAndLoadBundle(ctx, path, bs, av, rel, netw); err != nil {
return err
}
}
cborStore := cbor.NewCborStore(bs)
if err := LoadManifests(ctx, cborStore); err != nil {
return err
}
return nil
}

View File

@ -1,22 +0,0 @@
package cli
import (
"context"
"fmt"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
)
func init() {
// preload manifest so that we have the correct code CID inventory for cli since that doesn't
// go through CI
if len(build.BuiltinActorsV8Bundle()) > 0 {
bs := blockstore.NewMemory()
if err := actors.LoadManifestFromBundle(context.TODO(), bs, actors.Version8, build.BuiltinActorsV8Bundle()); err != nil {
panic(fmt.Errorf("error loading actor manifest: %w", err))
}
}
}

View File

@ -21,6 +21,9 @@ import (
"github.com/filecoin-project/lotus/api/client"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/api/v1api"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/node/repo"
)
@ -28,6 +31,14 @@ const (
metadataTraceContext = "traceContext"
)
func loadBundles(ctx context.Context) error {
// preload manifest so that we have the correct code CID inventory for cli since that doesn't
// go through CI
bs := blockstore.NewMemory()
return actors.FetchAndLoadBundles(ctx, bs, build.BuiltinActorReleases)
}
// GetAPIInfo returns the API endpoint to use for the specified kind of repo.
//
// The order of precedence is as follows:
@ -37,6 +48,11 @@ const (
// 3. deprecated *_API_INFO environment variables
// 4. *-repo command line flags.
func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) {
// do this first
if err := loadBundles(ctx.Context); err != nil {
return APIInfo{}, fmt.Errorf("error loading builtin-actor bundles: %w", err)
}
// Check if there was a flag passed with the listen address of the API
// server (only used by the tests)
for _, f := range t.APIFlags() {

View File

@ -216,12 +216,11 @@ var initCmd = &cli.Command{
return err
}
if len(build.BuiltinActorsV8Bundle()) > 0 {
// load bundles
bs := blockstore.NewMemory()
if err := actors.LoadManifestFromBundle(context.TODO(), bs, actors.Version8, build.BuiltinActorsV8Bundle()); err != nil {
return xerrors.Errorf("error loading actor manifest: %w", err)
}
if err := actors.FetchAndLoadBundles(ctx, bs, build.BuiltinActorReleases); err != nil {
return err
}
var localPaths []stores.LocalPath

View File

@ -1,7 +1,6 @@
package main
import (
"context"
"encoding/csv"
"encoding/json"
"fmt"
@ -580,11 +579,12 @@ var genesisCarCmd = &cli.Command{
jrnl := journal.NilJournal()
bstor := blockstore.WrapIDStore(blockstore.NewMemorySync())
sbldr := vm.Syscalls(ffiwrapper.ProofVerifier)
if len(build.BuiltinActorsV8Bundle()) > 0 {
if err := actors.LoadManifestFromBundle(context.TODO(), bstor, actors.Version8, build.BuiltinActorsV8Bundle()); err != nil {
return xerrors.Errorf("error loading actor manifest: %w", err)
}
// load appropriate bundles
if err := actors.FetchAndLoadBundles(c.Context, bstor, build.BuiltinActorReleases); err != nil {
return err
}
_, err := testing.MakeGenesis(ofile, c.Args().First())(bstor, sbldr, jrnl, dtypes.BuiltinActorsLoaded{})()
return err
},

View File

@ -2,8 +2,8 @@ package modules
import (
"fmt"
"io"
"os"
"sync"
"go.uber.org/fx"
"golang.org/x/xerrors"
@ -12,28 +12,60 @@ import (
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/node/modules/dtypes"
"github.com/filecoin-project/lotus/node/modules/helpers"
"github.com/filecoin-project/lotus/node/repo"
cid "github.com/ipfs/go-cid"
dstore "github.com/ipfs/go-datastore"
cbor "github.com/ipfs/go-ipld-cbor"
)
func LoadBultinActors(lc fx.Lifecycle, mctx helpers.MetricsCtx, bs dtypes.UniversalBlockstore) (result dtypes.BuiltinActorsLoaded, err error) {
func LoadBultinActors(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRepo, bs dtypes.UniversalBlockstore, ds dtypes.MetadataDS) (result dtypes.BuiltinActorsLoaded, err error) {
ctx := helpers.LifecycleCtx(mctx, lc)
// TODO eventually we want this to start with bundle/manifest CIDs and fetch them from IPFS if
// not already loaded.
// For now, we just embed the v8 bundle and adjust the manifest CIDs for the migration/actor
// metadata.
if len(build.BuiltinActorsV8Bundle()) > 0 {
if err := actors.LoadBundle(ctx, bs, actors.Version8, build.BuiltinActorsV8Bundle()); err != nil {
return result, err
}
// TODO how to properly get the network name?
// putting it as a dep in inputs causes a stack overflow in DI from circular dependency
// sigh...
netw := "mainnet"
if v := os.Getenv("LOTUS_FIL_NETWORK"); v != "" {
netw = v
}
// for testing -- need to also set LOTUS_USE_FVM_CUSTOM_BUNDLE=1 to force the fvm to use it.
if len(build.BuiltinActorsV7Bundle()) > 0 {
if err := actors.LoadBundle(ctx, bs, actors.Version7, build.BuiltinActorsV7Bundle()); err != nil {
for av, rel := range build.BuiltinActorReleases {
key := dstore.NewKey(fmt.Sprintf("/builtin-actors/v%d/%s", av, rel))
data, err := ds.Get(ctx, key)
switch err {
case nil:
mfCid, err := cid.Cast(data)
if err != nil {
return result, xerrors.Errorf("error parsing cid for %s: %w", key, err)
}
has, err := bs.Has(ctx, mfCid)
if err != nil {
return result, xerrors.Errorf("error checking blockstore for manifest cid %s: %w", mfCid, err)
}
if has {
actors.AddManifest(av, mfCid)
continue
}
case dstore.ErrNotFound:
default:
return result, xerrors.Errorf("error loading %s from datastore: %w", key, err)
}
// ok, we don't have it -- fetch it and add it to the blockstore
mfCid, err := actors.FetchAndLoadBundle(ctx, r.Path(), bs, av, rel, string(netw))
if err != nil {
return result, err
}
if err := ds.Put(ctx, key, mfCid.Bytes()); err != nil {
return result, xerrors.Errorf("error storing manifest CID for builtin-actors vrsion %d to the datastore: %w", av, err)
}
}
cborStore := cbor.NewCborStore(bs)
@ -45,38 +77,26 @@ func LoadBultinActors(lc fx.Lifecycle, mctx helpers.MetricsCtx, bs dtypes.Univer
}
// for itests
var testingBundleMx sync.Mutex
func LoadBuiltinActorsTesting(lc fx.Lifecycle, mctx helpers.MetricsCtx, bs dtypes.UniversalBlockstore) (result dtypes.BuiltinActorsLoaded, err error) {
ctx := helpers.LifecycleCtx(mctx, lc)
base := os.Getenv("LOTUS_SRC_DIR")
if base == "" {
base = "."
}
var template string
var netw string
if build.InsecurePoStValidation {
template = "%s/build/builtin-actors/v%d/builtin-actors-testing-fake-proofs.car"
netw = "testing-fake-proofs"
} else {
template = "%s/build/builtin-actors/v%d/builtin-actors-testing.car"
netw = "testing"
}
for _, ver := range []actors.Version{actors.Version8} {
path := fmt.Sprintf(template, base, ver)
testingBundleMx.Lock()
defer testingBundleMx.Unlock()
log.Infof("loading testing bundle: %s", path)
for av, rel := range build.BuiltinActorReleases {
const basePath = "/tmp/lotus-testing"
file, err := os.Open(path)
if err != nil {
return result, xerrors.Errorf("error opening v%d bundle: %w", ver, err)
}
bundle, err := io.ReadAll(file)
if err != nil {
return result, xerrors.Errorf("error reading v%d bundle: %w", ver, err)
}
if err := actors.LoadBundle(ctx, bs, actors.Version8, bundle); err != nil {
return result, xerrors.Errorf("error loading v%d bundle: %w", ver, err)
if _, err := actors.FetchAndLoadBundle(ctx, basePath, bs, av, rel, netw); err != nil {
return result, xerrors.Errorf("error loading bundle for builtin-actors vresion %d: %w", av, err)
}
}