package testkit import ( "bytes" "context" "fmt" mbig "math/big" "time" "github.com/google/uuid" "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/genesis" "github.com/filecoin-project/lotus/node" "github.com/filecoin-project/lotus/node/modules" modtest "github.com/filecoin-project/lotus/node/modules/testing" "github.com/filecoin-project/lotus/node/repo" ) // Bootstrapper is a special kind of process that produces a genesis block with // the initial wallet balances and preseals for all enlisted miners and clients. type Bootstrapper struct { *LotusNode t *TestEnvironment } func PrepareBootstrapper(t *TestEnvironment) (*Bootstrapper, error) { var ( clients = t.IntParam("clients") miners = t.IntParam("miners") nodes = clients + miners ) ctx, cancel := context.WithTimeout(context.Background(), PrepareNodeTimeout) defer cancel() pubsubTracerMaddr, err := GetPubsubTracerMaddr(ctx, t) if err != nil { return nil, err } randomBeaconOpt, err := GetRandomBeaconOpts(ctx, t) if err != nil { return nil, err } // the first duty of the boostrapper is to construct the genesis block // first collect all client and miner balances to assign initial funds balances, err := WaitForBalances(t, ctx, nodes) if err != nil { return nil, err } totalBalance := big.Zero() for _, b := range balances { totalBalance = big.Add(filToAttoFil(b.Balance), totalBalance) } totalBalanceFil := attoFilToFil(totalBalance) t.RecordMessage("TOTAL BALANCE: %s AttoFIL (%s FIL)", totalBalance, totalBalanceFil) if max := types.TotalFilecoinInt; totalBalanceFil.GreaterThanEqual(max) { panic(fmt.Sprintf("total sum of balances is greater than max Filecoin ever; sum=%s, max=%s", totalBalance, max)) } // then collect all preseals from miners preseals, err := CollectPreseals(t, ctx, miners) if err != nil { return nil, err } // now construct the genesis block var genesisActors []genesis.Actor var genesisMiners []genesis.Miner for _, bm := range balances { balance := filToAttoFil(bm.Balance) t.RecordMessage("balance assigned to actor %s: %s AttoFIL", bm.Addr, balance) genesisActors = append(genesisActors, genesis.Actor{ Type: genesis.TAccount, Balance: balance, Meta: (&genesis.AccountMeta{Owner: bm.Addr}).ActorMeta(), }) } for _, pm := range preseals { genesisMiners = append(genesisMiners, pm.Miner) } genesisTemplate := genesis.Template{ Accounts: genesisActors, Miners: genesisMiners, Timestamp: uint64(time.Now().Unix()) - uint64(t.IntParam("genesis_timestamp_offset")), VerifregRootKey: gen.DefaultVerifregRootkeyActor, RemainderAccount: gen.DefaultRemainderAccountActor, NetworkName: "testground-local-" + uuid.New().String(), } // dump the genesis block // var jsonBuf bytes.Buffer // jsonEnc := json.NewEncoder(&jsonBuf) // err := jsonEnc.Encode(genesisTemplate) // if err != nil { // panic(err) // } // runenv.RecordMessage(fmt.Sprintf("Genesis template: %s", string(jsonBuf.Bytes()))) // this is horrendously disgusting, we use this contraption to side effect the construction // of the genesis block in the buffer -- yes, a side effect of dependency injection. // I remember when software was straightforward... var genesisBuffer bytes.Buffer bootstrapperIP := t.NetClient.MustGetDataNetworkIP().String() n := &LotusNode{} r := repo.NewMemory(nil) stop, err := node.New(context.Background(), node.FullAPI(&n.FullApi), node.Base(), node.Repo(r), node.Override(new(modules.Genesis), modtest.MakeGenesisMem(&genesisBuffer, genesisTemplate)), withApiEndpoint(fmt.Sprintf("/ip4/0.0.0.0/tcp/%s", t.PortNumber("node_rpc", "0"))), withListenAddress(bootstrapperIP), withBootstrapper(nil), withPubsubConfig(true, pubsubTracerMaddr), randomBeaconOpt, ) if err != nil { return nil, err } n.StopFn = stop var bootstrapperAddr ma.Multiaddr bootstrapperAddrs, err := n.FullApi.NetAddrsListen(ctx) if err != nil { stop(context.TODO()) return nil, err } for _, a := range bootstrapperAddrs.Addrs { ip, err := a.ValueForProtocol(ma.P_IP4) if err != nil { continue } if ip != bootstrapperIP { continue } addrs, err := peer.AddrInfoToP2pAddrs(&peer.AddrInfo{ ID: bootstrapperAddrs.ID, Addrs: []ma.Multiaddr{a}, }) if err != nil { panic(err) } bootstrapperAddr = addrs[0] break } if bootstrapperAddr == nil { panic("failed to determine bootstrapper address") } genesisMsg := &GenesisMsg{ Genesis: genesisBuffer.Bytes(), Bootstrapper: bootstrapperAddr.Bytes(), } t.SyncClient.MustPublish(ctx, GenesisTopic, genesisMsg) t.RecordMessage("waiting for all nodes to be ready") t.SyncClient.MustSignalAndWait(ctx, StateReady, t.TestInstanceCount) return &Bootstrapper{n, t}, nil } // RunDefault runs a default bootstrapper. func (b *Bootstrapper) RunDefault() error { b.t.RecordMessage("running bootstrapper") ctx := context.Background() b.t.SyncClient.MustSignalAndWait(ctx, StateDone, b.t.TestInstanceCount) return nil } // filToAttoFil converts a fractional filecoin value into AttoFIL, rounding if necessary func filToAttoFil(f float64) big.Int { a := mbig.NewFloat(f) a.Mul(a, mbig.NewFloat(float64(build.FilecoinPrecision))) i, _ := a.Int(nil) return big.Int{Int: i} } func attoFilToFil(atto big.Int) big.Int { i := big.NewInt(0) i.Add(i.Int, atto.Int) i.Div(i.Int, big.NewIntUnsigned(build.FilecoinPrecision).Int) return i }