github.com/filecoin-project/specs-actors/v4@v4.0.2/support/agent/sim.go (about)

     1  package agent
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/filecoin-project/go-address"
    11  	"github.com/filecoin-project/go-bitfield"
    12  	"github.com/filecoin-project/go-state-types/abi"
    13  	"github.com/filecoin-project/go-state-types/big"
    14  	"github.com/filecoin-project/go-state-types/cbor"
    15  	"github.com/filecoin-project/go-state-types/dline"
    16  	"github.com/filecoin-project/go-state-types/exitcode"
    17  	"github.com/filecoin-project/go-state-types/rt"
    18  	power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power"
    19  	reward2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/reward"
    20  	cid "github.com/ipfs/go-cid"
    21  	ipldcbor "github.com/ipfs/go-ipld-cbor"
    22  	"github.com/pkg/errors"
    23  	"golang.org/x/xerrors"
    24  
    25  	adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt"
    26  	vm2 "github.com/filecoin-project/specs-actors/v2/support/vm"
    27  	"github.com/filecoin-project/specs-actors/v4/actors/builtin"
    28  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/market"
    29  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/power"
    30  	power3 "github.com/filecoin-project/specs-actors/v4/actors/builtin/power"
    31  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/reward"
    32  	"github.com/filecoin-project/specs-actors/v4/actors/states"
    33  	"github.com/filecoin-project/specs-actors/v4/actors/util/adt"
    34  	"github.com/filecoin-project/specs-actors/v4/support/ipld"
    35  	vm "github.com/filecoin-project/specs-actors/v4/support/vm"
    36  )
    37  
    38  // Sim is a simulation framework to exercise actor code in a network-like environment.
    39  // It's goal is to simulate realistic call sequences and interactions to perform invariant analysis
    40  // and test performance assumptions prior to shipping actor code out to implementations.
    41  // The model is that the simulation will "Tick" once per epoch. Within this tick:
    42  // * It will first compute winning tickets from previous state for miners to simulate block mining.
    43  // * It will create any agents it is configured to create and generate messages to create their associated actors.
    44  // * It will call tick on all it agents. This call will return messages that will get added to the simulated "tipset".
    45  // * Messages will be shuffled to simulate network entropy.
    46  // * Messages will be applied and an new VM will be created from the resulting state tree for the next tick.
    47  type Sim struct {
    48  	Config                SimConfig
    49  	Agents                []Agent
    50  	DealProviders         []DealProvider
    51  	WinCount              uint64
    52  	MessageCount          uint64
    53  	ComputePowerTable     func(SimVM, []Agent) (PowerTable, error)
    54  	CreateMinerParamsFunc func(address.Address, address.Address, abi.RegisteredSealProof) (interface{}, error)
    55  
    56  	v                 SimVM
    57  	vmFactory         VMFactoryFunc
    58  	minerStateFactory func(context.Context, cid.Cid) (SimMinerState, error)
    59  	rnd               *rand.Rand
    60  	statsByMethod     map[vm.MethodKey]*vm.CallStats
    61  	blkStore          ipldcbor.IpldBlockstore
    62  	blkStoreFactory   func() ipldcbor.IpldBlockstore
    63  	ctx               context.Context
    64  	t                 testing.TB
    65  }
    66  
    67  type VMFactoryFunc func(context.Context, vm2.ActorImplLookup, adt.Store, cid.Cid, abi.ChainEpoch) (SimVM, error)
    68  
    69  func NewSim(ctx context.Context, t testing.TB, blockstoreFactory func() ipldcbor.IpldBlockstore, config SimConfig) *Sim {
    70  	blkStore := blockstoreFactory()
    71  	metrics := ipld.NewMetricsBlockStore(blkStore)
    72  	v := vm.NewVMWithSingletons(ctx, t, metrics)
    73  	vmFactory := func(ctx context.Context, impl vm2.ActorImplLookup, store adt.Store, stateRoot cid.Cid, epoch abi.ChainEpoch) (SimVM, error) {
    74  		return vm.NewVMAtEpoch(ctx, vm.ActorImplLookup(impl), store, stateRoot, epoch)
    75  	}
    76  	v.SetStatsSource(metrics)
    77  	minerStateFactory := func(ctx context.Context, root cid.Cid) (SimMinerState, error) {
    78  		return &MinerStateV3{
    79  			Ctx:  ctx,
    80  			Root: root,
    81  		}, nil
    82  	}
    83  	return &Sim{
    84  		Config:                config,
    85  		Agents:                []Agent{},
    86  		DealProviders:         []DealProvider{},
    87  		ComputePowerTable:     ComputePowerTableV3,
    88  		CreateMinerParamsFunc: CreateMinerParamsV3,
    89  		v:                     v,
    90  		vmFactory:             vmFactory,
    91  		minerStateFactory:     minerStateFactory,
    92  		rnd:                   rand.New(rand.NewSource(config.Seed)),
    93  		blkStore:              blkStore,
    94  		blkStoreFactory:       blockstoreFactory,
    95  		ctx:                   ctx,
    96  		t:                     t,
    97  	}
    98  }
    99  
   100  func NewSimWithVM(ctx context.Context, t testing.TB, v SimVM, vmFactory VMFactoryFunc,
   101  	computePowerTable func(SimVM, []Agent) (PowerTable, error), blkStore ipldcbor.IpldBlockstore,
   102  	blockstoreFactory func() ipldcbor.IpldBlockstore, minerStateFactory func(context.Context, cid.Cid) (SimMinerState, error),
   103  	config SimConfig, createMinerParams func(address.Address, address.Address, abi.RegisteredSealProof) (interface{}, error),
   104  ) *Sim {
   105  	metrics := ipld.NewMetricsBlockStore(blkStore)
   106  	v.SetStatsSource(metrics)
   107  
   108  	return &Sim{
   109  		Config:                config,
   110  		Agents:                []Agent{},
   111  		DealProviders:         []DealProvider{},
   112  		ComputePowerTable:     computePowerTable,
   113  		CreateMinerParamsFunc: createMinerParams,
   114  		v:                     v,
   115  		vmFactory:             vmFactory,
   116  		minerStateFactory:     minerStateFactory,
   117  		rnd:                   rand.New(rand.NewSource(config.Seed)),
   118  		blkStore:              blkStore,
   119  		blkStoreFactory:       blockstoreFactory,
   120  		ctx:                   ctx,
   121  		t:                     t,
   122  	}
   123  }
   124  
   125  func (s *Sim) SwapVM(v SimVM, vmFactory VMFactoryFunc, minerStateFactory func(context.Context, cid.Cid) (SimMinerState, error),
   126  	computePowerTable func(SimVM, []Agent) (PowerTable, error), createMinerParams func(address.Address, address.Address, abi.RegisteredSealProof) (interface{}, error),
   127  ) {
   128  	s.v = v
   129  	s.vmFactory = vmFactory
   130  	s.minerStateFactory = minerStateFactory
   131  	s.ComputePowerTable = computePowerTable
   132  	s.CreateMinerParamsFunc = createMinerParams
   133  }
   134  
   135  //////////////////////////////////////////
   136  //
   137  //  Sim execution
   138  //
   139  //////////////////////////////////////////
   140  
   141  func (s *Sim) Tick() error {
   142  	var err error
   143  	var blockMessages []message
   144  	// compute power table before state transition to create block rewards at the end
   145  	powerTable, err := s.ComputePowerTable(s.v, s.Agents)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	if err := computeCircSupply(s.v); err != nil {
   151  		return err
   152  	}
   153  
   154  	// add all agent messages
   155  	for _, agent := range s.Agents {
   156  		msgs, err := agent.Tick(s)
   157  		if err != nil {
   158  			return err
   159  		}
   160  
   161  		blockMessages = append(blockMessages, msgs...)
   162  	}
   163  
   164  	// shuffle messages
   165  	s.rnd.Shuffle(len(blockMessages), func(i, j int) {
   166  		blockMessages[i], blockMessages[j] = blockMessages[j], blockMessages[i]
   167  	})
   168  
   169  	// run messages
   170  	for _, msg := range blockMessages {
   171  		ret, code := s.v.ApplyMessage(msg.From, msg.To, msg.Value, msg.Method, msg.Params)
   172  
   173  		// for now, assume everything should work
   174  		if code != exitcode.Ok {
   175  			return errors.Errorf("exitcode %d: message failed: %v\n%s\n", code, msg, strings.Join(s.v.GetLogs(), "\n"))
   176  		}
   177  
   178  		if msg.ReturnHandler != nil {
   179  			if err := msg.ReturnHandler(s, msg, ret); err != nil {
   180  				return err
   181  			}
   182  		}
   183  	}
   184  	s.MessageCount += uint64(len(blockMessages))
   185  	// Apply block rewards
   186  	// Note that this differs from the specification in that it applies all reward messages at the end, whereas
   187  	// a real implementation would apply a reward messages at the end of each block in the tipset (thereby
   188  	// interleaving them with the rest of the messages).
   189  	for _, miner := range powerTable.minerPower {
   190  		if powerTable.totalQAPower.GreaterThan(big.Zero()) {
   191  			wins := WinCount(miner.qaPower, powerTable.totalQAPower, s.rnd.Float64())
   192  			s.WinCount += wins
   193  			err := s.rewardMiner(miner.addr, wins)
   194  			if err != nil {
   195  				return err
   196  			}
   197  		}
   198  	}
   199  
   200  	// run cron
   201  	_, code := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil)
   202  	if code != exitcode.Ok {
   203  		return errors.Errorf("exitcode %d: cron message failed:\n%s\n", code, strings.Join(s.v.GetLogs(), "\n"))
   204  	}
   205  
   206  	// store last stats
   207  	s.statsByMethod = s.v.GetCallStats()
   208  
   209  	// dump logs if we have them
   210  	if len(s.v.GetLogs()) > 0 {
   211  		fmt.Printf("%s\n", strings.Join(s.v.GetLogs(), "\n"))
   212  	}
   213  
   214  	// create next vm
   215  	nextEpoch := s.v.GetEpoch() + 1
   216  	if s.Config.CheckpointEpochs > 0 && uint64(nextEpoch)%s.Config.CheckpointEpochs == 0 {
   217  		nextStore := s.blkStoreFactory()
   218  		blks, size, err := BlockstoreCopy(s.blkStore, nextStore, s.v.StateRoot())
   219  		if err != nil {
   220  			return err
   221  		}
   222  		fmt.Printf("CHECKPOINT: state blocks: %d, state data size %d\n", blks, size)
   223  
   224  		s.blkStore = nextStore
   225  		metrics := ipld.NewMetricsBlockStore(nextStore)
   226  		s.v, err = s.vmFactory(s.ctx, s.v.GetActorImpls(), adt.WrapBlockStore(s.ctx, metrics), s.v.StateRoot(), nextEpoch)
   227  		if err != nil {
   228  			return err
   229  		}
   230  		s.v.SetStatsSource(metrics)
   231  
   232  	} else {
   233  		statsSource := s.v.GetStatsSource()
   234  		s.v, err = s.vmFactory(s.ctx, s.v.GetActorImpls(), s.v.Store(), s.v.StateRoot(), nextEpoch)
   235  		if err != nil {
   236  			return err
   237  		}
   238  		s.v.SetStatsSource(statsSource)
   239  	}
   240  
   241  	return err
   242  }
   243  
   244  //////////////////////////////////////////////////
   245  //
   246  //  SimState Methods and other accessors
   247  //
   248  //////////////////////////////////////////////////
   249  
   250  func (s *Sim) GetEpoch() abi.ChainEpoch {
   251  	return s.v.GetEpoch()
   252  }
   253  
   254  func (s *Sim) GetState(addr address.Address, out cbor.Unmarshaler) error {
   255  	return s.v.GetState(addr, out)
   256  }
   257  
   258  func (s *Sim) Store() adt.Store {
   259  	return s.v.Store()
   260  }
   261  
   262  func (s *Sim) MinerState(addr address.Address) (SimMinerState, error) {
   263  	act, found, err := s.v.GetActor(addr)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	if !found {
   268  		return nil, xerrors.Errorf("miner %s not found", addr)
   269  	}
   270  	return s.minerStateFactory(s.ctx, act.Head)
   271  }
   272  
   273  func (s *Sim) AddAgent(a Agent) {
   274  	s.Agents = append(s.Agents, a)
   275  }
   276  
   277  func (s *Sim) AddDealProvider(d DealProvider) {
   278  	s.DealProviders = append(s.DealProviders, d)
   279  }
   280  
   281  func (s *Sim) GetVM() SimVM {
   282  	return s.v
   283  }
   284  
   285  func (s *Sim) GetCallStats() map[vm.MethodKey]*vm.CallStats {
   286  	return s.statsByMethod
   287  }
   288  
   289  func (s *Sim) ChooseDealProvider() DealProvider {
   290  	if len(s.DealProviders) == 0 {
   291  		return nil
   292  	}
   293  	return s.DealProviders[s.rnd.Int63n(int64(len(s.DealProviders)))]
   294  }
   295  
   296  func (s *Sim) NetworkCirculatingSupply() abi.TokenAmount {
   297  	return s.v.GetCirculatingSupply()
   298  }
   299  
   300  func (s *Sim) CreateMinerParams(worker, owner address.Address, sealProof abi.RegisteredSealProof) (interface{}, error) {
   301  	return s.CreateMinerParamsFunc(worker, owner, sealProof)
   302  }
   303  
   304  //////////////////////////////////////////////////
   305  //
   306  //  Misc Methods
   307  //
   308  //////////////////////////////////////////////////
   309  
   310  func (s *Sim) rewardMiner(addr address.Address, wins uint64) error {
   311  	if wins < 1 {
   312  		return nil
   313  	}
   314  
   315  	rewardParams := reward.AwardBlockRewardParams{
   316  		Miner:     addr,
   317  		Penalty:   big.Zero(),
   318  		GasReward: big.Zero(),
   319  		WinCount:  int64(wins),
   320  	}
   321  	_, code := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.RewardActorAddr, big.Zero(), builtin.MethodsReward.AwardBlockReward, &rewardParams)
   322  	if code != exitcode.Ok {
   323  		return errors.Errorf("exitcode %d: reward message failed:\n%s\n", code, strings.Join(s.v.GetLogs(), "\n"))
   324  	}
   325  	return nil
   326  }
   327  
   328  func ComputePowerTableV3(v SimVM, agents []Agent) (PowerTable, error) {
   329  	pt := PowerTable{}
   330  
   331  	var rwst reward.State
   332  	if err := v.GetState(builtin.RewardActorAddr, &rwst); err != nil {
   333  		return PowerTable{}, err
   334  	}
   335  	pt.blockReward = rwst.ThisEpochReward
   336  
   337  	var st power.State
   338  	if err := v.GetState(builtin.StoragePowerActorAddr, &st); err != nil {
   339  		return PowerTable{}, err
   340  	}
   341  	pt.totalQAPower = st.TotalQualityAdjPower
   342  
   343  	for _, agent := range agents {
   344  		if miner, ok := agent.(*MinerAgent); ok {
   345  			if claim, found, err := st.GetClaim(v.Store(), miner.IDAddress); err != nil {
   346  				return pt, err
   347  			} else if found {
   348  				if sufficient, err := st.MinerNominalPowerMeetsConsensusMinimum(v.Store(), miner.IDAddress); err != nil {
   349  					return pt, err
   350  				} else if sufficient {
   351  					pt.minerPower = append(pt.minerPower, minerPowerTable{miner.IDAddress, claim.QualityAdjPower})
   352  				}
   353  			}
   354  		}
   355  	}
   356  	return pt, nil
   357  }
   358  
   359  func ComputePowerTableV2(v SimVM, agents []Agent) (PowerTable, error) {
   360  	pt := PowerTable{}
   361  
   362  	var rwst reward2.State
   363  	if err := v.GetState(builtin.RewardActorAddr, &rwst); err != nil {
   364  		return PowerTable{}, err
   365  	}
   366  	pt.blockReward = rwst.ThisEpochReward
   367  
   368  	var st power2.State
   369  	if err := v.GetState(builtin.StoragePowerActorAddr, &st); err != nil {
   370  		return PowerTable{}, err
   371  	}
   372  	pt.totalQAPower = st.TotalQualityAdjPower
   373  
   374  	for _, agent := range agents {
   375  		if miner, ok := agent.(*MinerAgent); ok {
   376  			if claim, found, err := st.GetClaim(v.Store(), miner.IDAddress); err != nil {
   377  				return pt, err
   378  			} else if found {
   379  				if sufficient, err := st.MinerNominalPowerMeetsConsensusMinimum(v.Store(), miner.IDAddress); err != nil {
   380  					return pt, err
   381  				} else if sufficient {
   382  					pt.minerPower = append(pt.minerPower, minerPowerTable{miner.IDAddress, claim.QualityAdjPower})
   383  				}
   384  			}
   385  		}
   386  	}
   387  	return pt, nil
   388  }
   389  
   390  func CreateMinerParamsV2(worker, owner address.Address, sealProof abi.RegisteredSealProof) (interface{}, error) {
   391  	return &power2.CreateMinerParams{
   392  		Owner:         owner,
   393  		Worker:        worker,
   394  		SealProofType: sealProof,
   395  	}, nil
   396  }
   397  
   398  func CreateMinerParamsV3(worker, owner address.Address, sealProof abi.RegisteredSealProof) (interface{}, error) {
   399  	wPoStProof, err := sealProof.RegisteredWindowPoStProof()
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  
   404  	return &power3.CreateMinerParams{
   405  		Owner:               owner,
   406  		Worker:              worker,
   407  		WindowPoStProofType: wPoStProof,
   408  	}, nil
   409  }
   410  
   411  func computeCircSupply(v SimVM) error {
   412  	// disbursed + reward.State.TotalStoragePowerReward - burnt.Balance - power.State.TotalPledgeCollateral
   413  	var rewardSt reward.State
   414  	if err := v.GetState(builtin.RewardActorAddr, &rewardSt); err != nil {
   415  		return err
   416  	}
   417  
   418  	var powerSt power.State
   419  	if err := v.GetState(builtin.StoragePowerActorAddr, &powerSt); err != nil {
   420  		return err
   421  	}
   422  
   423  	burnt, found, err := v.GetActor(builtin.BurntFundsActorAddr)
   424  	if err != nil {
   425  		return err
   426  	} else if !found {
   427  		return errors.Errorf("burnt actor not found at %v", builtin.BurntFundsActorAddr)
   428  	}
   429  
   430  	v.SetCirculatingSupply(big.Sum(DisbursedAmount, rewardSt.TotalStoragePowerReward,
   431  		powerSt.TotalPledgeCollateral.Neg(), burnt.Balance.Neg()))
   432  	return nil
   433  }
   434  
   435  //////////////////////////////////////////////
   436  //
   437  //  Internal Types
   438  //
   439  //////////////////////////////////////////////
   440  
   441  type SimState interface {
   442  	GetEpoch() abi.ChainEpoch
   443  	GetState(addr address.Address, out cbor.Unmarshaler) error
   444  	Store() adt.Store
   445  	AddAgent(a Agent)
   446  	AddDealProvider(d DealProvider)
   447  	NetworkCirculatingSupply() abi.TokenAmount
   448  	MinerState(addr address.Address) (SimMinerState, error)
   449  	CreateMinerParams(worker, owner address.Address, sealProof abi.RegisteredSealProof) (interface{}, error)
   450  
   451  	// randomly select an agent capable of making deals.
   452  	// Returns nil if no providers exist.
   453  	ChooseDealProvider() DealProvider
   454  }
   455  
   456  type Agent interface {
   457  	Tick(v SimState) ([]message, error)
   458  }
   459  
   460  type DealProvider interface {
   461  	Address() address.Address
   462  	DealRange(currentEpoch abi.ChainEpoch) (start abi.ChainEpoch, end abi.ChainEpoch)
   463  	CreateDeal(proposal market.ClientDealProposal)
   464  	AvailableCollateral() abi.TokenAmount
   465  }
   466  
   467  type SimConfig struct {
   468  	AccountCount           int
   469  	AccountInitialBalance  abi.TokenAmount
   470  	Seed                   int64
   471  	CreateMinerProbability float32
   472  	CheckpointEpochs       uint64
   473  }
   474  
   475  type returnHandler func(v SimState, msg message, ret cbor.Marshaler) error
   476  
   477  type message struct {
   478  	From          address.Address
   479  	To            address.Address
   480  	Value         abi.TokenAmount
   481  	Method        abi.MethodNum
   482  	Params        interface{}
   483  	ReturnHandler returnHandler
   484  }
   485  
   486  type minerPowerTable struct {
   487  	addr    address.Address
   488  	qaPower abi.StoragePower
   489  }
   490  
   491  type PowerTable struct {
   492  	blockReward  abi.TokenAmount
   493  	totalQAPower abi.StoragePower
   494  	minerPower   []minerPowerTable
   495  }
   496  
   497  // VM interface allowing a simulation to operate over multiple VM versions
   498  type SimVM interface {
   499  	ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) (cbor.Marshaler, exitcode.ExitCode)
   500  	GetCirculatingSupply() abi.TokenAmount
   501  	GetLogs() []string
   502  	GetState(addr address.Address, out cbor.Unmarshaler) error
   503  	SetStatsSource(stats vm2.StatsSource)
   504  	GetCallStats() map[vm2.MethodKey]*vm2.CallStats
   505  	GetEpoch() abi.ChainEpoch
   506  	Store() adt2.Store
   507  	GetActor(addr address.Address) (*states.Actor, bool, error)
   508  	SetCirculatingSupply(supply big.Int)
   509  	GetActorImpls() map[cid.Cid]rt.VMActor
   510  	StateRoot() cid.Cid
   511  	GetStatsSource() vm2.StatsSource
   512  	GetTotalActorBalance() (abi.TokenAmount, error)
   513  }
   514  
   515  var _ SimVM = (*vm.VM)(nil)
   516  var _ SimVM = (*vm2.VM)(nil)
   517  
   518  type SimMinerState interface {
   519  	HasSectorNo(adt.Store, abi.SectorNumber) (bool, error)
   520  	FindSector(adt.Store, abi.SectorNumber) (uint64, uint64, error)
   521  	ProvingPeriodStart(adt.Store) (abi.ChainEpoch, error)
   522  	LoadSectorInfo(adt.Store, uint64) (SimSectorInfo, error)
   523  	DeadlineInfo(adt.Store, abi.ChainEpoch) (*dline.Info, error)
   524  	FeeDebt(adt.Store) (abi.TokenAmount, error)
   525  	LoadDeadlineState(adt.Store, uint64) (SimDeadlineState, error)
   526  }
   527  
   528  type SimSectorInfo interface {
   529  	Expiration() abi.ChainEpoch
   530  }
   531  
   532  type SimDeadlineState interface {
   533  	LoadPartition(adt.Store, uint64) (SimPartitionState, error)
   534  }
   535  
   536  type SimPartitionState interface {
   537  	Terminated() bitfield.BitField
   538  }