package gen import ( "bytes" "context" "fmt" "sync/atomic" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-car" offline "github.com/ipfs/go-ipfs-exchange-offline" "github.com/ipfs/go-merkledag" peer "github.com/libp2p/go-libp2p-peer" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/node/repo" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" logging "github.com/ipfs/go-log" ) var log = logging.Logger("gen") const msgsPerBlock = 20 type ChainGen struct { accounts []address.Address msgsPerBlock int bs blockstore.Blockstore cs *store.ChainStore sm *stmgr.StateManager genesis *types.BlockHeader CurTipset *store.FullTipSet Timestamper func(*types.TipSet, int) uint64 w *wallet.Wallet Miners []address.Address mworkers []address.Address receivers []address.Address banker address.Address bankerNonce uint64 r repo.Repo lr repo.LockedRepo } type mybs struct { blockstore.Blockstore } func (m mybs) Get(c cid.Cid) (block.Block, error) { b, err := m.Blockstore.Get(c) if err != nil { return nil, err } return b, nil } func NewGenerator() (*ChainGen, error) { mr := repo.NewMemory(nil) lr, err := mr.Lock(repo.StorageMiner) if err != nil { return nil, xerrors.Errorf("taking mem-repo lock failed: %w", err) } ds, err := lr.Datastore("/metadata") if err != nil { return nil, xerrors.Errorf("failed to get metadata datastore: %w", err) } bds, err := lr.Datastore("/blocks") if err != nil { return nil, xerrors.Errorf("failed to get blocks datastore: %w", err) } bs := mybs{blockstore.NewIdStore(blockstore.NewBlockstore(bds))} ks, err := lr.KeyStore() if err != nil { return nil, xerrors.Errorf("getting repo keystore failed: %w", err) } w, err := wallet.NewWallet(ks) if err != nil { return nil, xerrors.Errorf("creating memrepo wallet failed: %w", err) } worker1, err := w.GenerateKey(types.KTBLS) if err != nil { return nil, xerrors.Errorf("failed to generate worker key: %w", err) } worker2, err := w.GenerateKey(types.KTBLS) if err != nil { return nil, xerrors.Errorf("failed to generate worker key: %w", err) } banker, err := w.GenerateKey(types.KTSecp256k1) if err != nil { return nil, xerrors.Errorf("failed to generate banker key: %w", err) } receievers := make([]address.Address, msgsPerBlock) for r := range receievers { receievers[r], err = w.GenerateKey(types.KTBLS) if err != nil { return nil, xerrors.Errorf("failed to generate receiver key: %w", err) } } minercfg := &GenMinerCfg{ Workers: []address.Address{worker1, worker2}, Owners: []address.Address{worker1, worker2}, PeerIDs: []peer.ID{"peerID1", "peerID2"}, } genb, err := MakeGenesisBlock(bs, map[address.Address]types.BigInt{ worker1: types.FromFil(40000), worker2: types.FromFil(40000), banker: types.FromFil(50000), }, minercfg, 100000) if err != nil { return nil, xerrors.Errorf("make genesis block failed: %w", err) } cs := store.NewChainStore(bs, ds) genfb := &types.FullBlock{Header: genb.Genesis} gents := store.NewFullTipSet([]*types.FullBlock{genfb}) if err := cs.SetGenesis(genb.Genesis); err != nil { return nil, xerrors.Errorf("set genesis failed: %w", err) } if len(minercfg.MinerAddrs) == 0 { return nil, xerrors.Errorf("MakeGenesisBlock failed to set miner address") } sm := stmgr.NewStateManager(cs) gen := &ChainGen{ bs: bs, cs: cs, sm: sm, msgsPerBlock: msgsPerBlock, genesis: genb.Genesis, w: w, Miners: minercfg.MinerAddrs, mworkers: minercfg.Workers, banker: banker, receivers: receievers, CurTipset: gents, r: mr, lr: lr, } return gen, nil } func (cg *ChainGen) Genesis() *types.BlockHeader { return cg.genesis } func (cg *ChainGen) GenesisCar() ([]byte, error) { offl := offline.Exchange(cg.bs) blkserv := blockservice.New(cg.bs, offl) dserv := merkledag.NewDAGService(blkserv) out := new(bytes.Buffer) if err := car.WriteCar(context.TODO(), dserv, []cid.Cid{cg.Genesis().Cid()}, out); err != nil { return nil, err } return out.Bytes(), nil } func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m address.Address, ticks []*types.Ticket) (types.ElectionProof, *types.Ticket, error) { var lastTicket *types.Ticket if len(ticks) == 0 { lastTicket = pts.MinTicket() } else { lastTicket = ticks[len(ticks)-1] } st := pts.ParentState() worker, err := stmgr.GetMinerWorkerRaw(ctx, cg.sm, st, m) if err != nil { return nil, nil, xerrors.Errorf("get miner worker: %w", err) } vrfout, err := ComputeVRF(ctx, cg.w.Sign, worker, lastTicket.VRFProof) if err != nil { return nil, nil, xerrors.Errorf("compute VRF: %w", err) } tick := &types.Ticket{ VRFProof: vrfout, } win, eproof, err := IsRoundWinner(ctx, pts, append(ticks, tick), m, &mca{w: cg.w, sm: cg.sm}) if err != nil { return nil, nil, xerrors.Errorf("checking round winner failed: %w", err) } if !win { return nil, tick, nil } return eproof, tick, nil } type MinedTipSet struct { TipSet *store.FullTipSet Messages []*types.SignedMessage } func (cg *ChainGen) NextTipSet() (*MinedTipSet, error) { mts, err := cg.NextTipSetFromMiners(cg.CurTipset.TipSet(), cg.Miners) if err != nil { return nil, err } cg.CurTipset = mts.TipSet return mts, nil } func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address) (*MinedTipSet, error) { var blks []*types.FullBlock ticketSets := make([][]*types.Ticket, len(miners)) msgs, err := cg.getRandomMessages() if err != nil { return nil, xerrors.Errorf("get random messages: %w", err) } for len(blks) == 0 { for i, m := range miners { proof, t, err := cg.nextBlockProof(context.TODO(), base, m, ticketSets[i]) if err != nil { return nil, xerrors.Errorf("next block proof: %w", err) } ticketSets[i] = append(ticketSets[i], t) if proof != nil { fblk, err := cg.makeBlock(base, m, proof, ticketSets[i], msgs) if err != nil { return nil, xerrors.Errorf("making a block for next tipset failed: %w", err) } if err := cg.cs.PersistBlockHeader(fblk.Header); err != nil { return nil, xerrors.Errorf("chainstore AddBlock: %w", err) } blks = append(blks, fblk) } } } fts := store.NewFullTipSet(blks) return &MinedTipSet{ TipSet: fts, Messages: msgs, }, nil } func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, eproof types.ElectionProof, tickets []*types.Ticket, msgs []*types.SignedMessage) (*types.FullBlock, error) { var ts uint64 if cg.Timestamper != nil { ts = cg.Timestamper(parents, len(tickets)) } else { ts = parents.MinTimestamp() + (uint64(len(tickets)) * build.BlockDelay) } fblk, err := MinerCreateBlock(context.TODO(), cg.sm, cg.w, m, parents, tickets, eproof, msgs, ts) if err != nil { return nil, err } return fblk, err } // This function is awkward. It's used to deal with messages made when // simulating forks func (cg *ChainGen) ResyncBankerNonce(ts *types.TipSet) error { act, err := cg.sm.GetActor(cg.banker, ts) if err != nil { return err } cg.bankerNonce = act.Nonce return nil } func (cg *ChainGen) getRandomMessages() ([]*types.SignedMessage, error) { msgs := make([]*types.SignedMessage, cg.msgsPerBlock) for m := range msgs { msg := types.Message{ To: cg.receivers[m%len(cg.receivers)], From: cg.banker, Nonce: atomic.AddUint64(&cg.bankerNonce, 1) - 1, Value: types.NewInt(uint64(m + 1)), Method: 0, GasLimit: types.NewInt(10000), GasPrice: types.NewInt(0), } sig, err := cg.w.Sign(context.TODO(), cg.banker, msg.Cid().Bytes()) if err != nil { return nil, err } msgs[m] = &types.SignedMessage{ Message: msg, Signature: *sig, } } return msgs, nil } func (cg *ChainGen) YieldRepo() (repo.Repo, error) { if err := cg.lr.Close(); err != nil { return nil, err } return cg.r, nil } type MiningCheckAPI interface { ChainGetRandomness(context.Context, *types.TipSet, []*types.Ticket, int) ([]byte, error) StateMinerPower(context.Context, address.Address, *types.TipSet) (api.MinerPower, error) StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error) WalletSign(context.Context, address.Address, []byte) (*types.Signature, error) } type mca struct { w *wallet.Wallet sm *stmgr.StateManager } func (mca mca) ChainGetRandomness(ctx context.Context, pts *types.TipSet, ticks []*types.Ticket, lb int) ([]byte, error) { return mca.sm.ChainStore().GetRandomness(ctx, pts.Cids(), ticks, int64(lb)) } func (mca mca) StateMinerPower(ctx context.Context, maddr address.Address, ts *types.TipSet) (api.MinerPower, error) { mpow, tpow, err := stmgr.GetPower(ctx, mca.sm, ts, maddr) if err != nil { return api.MinerPower{}, err } return api.MinerPower{ MinerPower: mpow, TotalPower: tpow, }, err } func (mca mca) StateMinerWorker(ctx context.Context, maddr address.Address, ts *types.TipSet) (address.Address, error) { return stmgr.GetMinerWorkerRaw(ctx, mca.sm, ts.ParentState(), maddr) } func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*types.Signature, error) { return mca.w.Sign(ctx, a, v) } func IsRoundWinner(ctx context.Context, ts *types.TipSet, ticks []*types.Ticket, miner address.Address, a MiningCheckAPI) (bool, types.ElectionProof, error) { r, err := a.ChainGetRandomness(ctx, ts, ticks, build.EcRandomnessLookback) if err != nil { return false, nil, xerrors.Errorf("chain get randomness: %w", err) } mworker, err := a.StateMinerWorker(ctx, miner, ts) if err != nil { return false, nil, xerrors.Errorf("failed to get miner worker: %w", err) } vrfout, err := ComputeVRF(ctx, a.WalletSign, mworker, r) if err != nil { return false, nil, xerrors.Errorf("failed to compute VRF: %w", err) } pow, err := a.StateMinerPower(ctx, miner, ts) if err != nil { return false, nil, xerrors.Errorf("failed to check power: %w", err) } return types.PowerCmp(vrfout, pow.MinerPower, pow.TotalPower), vrfout, nil } type SignFunc func(context.Context, address.Address, []byte) (*types.Signature, error) func ComputeVRF(ctx context.Context, sign SignFunc, w address.Address, input []byte) ([]byte, error) { sig, err := sign(ctx, w, input) if err != nil { return nil, err } if sig.Type != types.KTBLS { return nil, fmt.Errorf("miner worker address was not a BLS key") } return sig.Data, nil }