feat: implement lotus-sim
This commit is contained in:
parent
715176698f
commit
e2f5c494b0
@ -97,9 +97,13 @@ type State interface {
|
||||
FindSector(abi.SectorNumber) (*SectorLocation, error)
|
||||
GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error)
|
||||
GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error)
|
||||
ForEachPrecommittedSector(func(SectorPreCommitOnChainInfo) error) error
|
||||
LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error)
|
||||
NumLiveSectors() (uint64, error)
|
||||
IsAllocated(abi.SectorNumber) (bool, error)
|
||||
// UnallocatedSectorNumbers returns up to count unallocated sector numbers (or less than
|
||||
// count if there aren't enough).
|
||||
UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error)
|
||||
|
||||
// Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors
|
||||
GetProvingPeriodStart() (abi.ChainEpoch, error)
|
||||
|
@ -156,9 +156,13 @@ type State interface {
|
||||
FindSector(abi.SectorNumber) (*SectorLocation, error)
|
||||
GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error)
|
||||
GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error)
|
||||
ForEachPrecommittedSector(func(SectorPreCommitOnChainInfo) error) error
|
||||
LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error)
|
||||
NumLiveSectors() (uint64, error)
|
||||
IsAllocated(abi.SectorNumber) (bool, error)
|
||||
// UnallocatedSectorNumbers returns up to count unallocated sector numbers (or less than
|
||||
// count if there aren't enough).
|
||||
UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error)
|
||||
|
||||
// Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors
|
||||
GetProvingPeriodStart() (abi.ChainEpoch, error)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
{{end}}
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
rle "github.com/filecoin-project/go-bitfield/rle"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
"github.com/ipfs/go-cid"
|
||||
@ -209,6 +210,26 @@ func (s *state{{.v}}) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCom
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *state{{.v}}) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error {
|
||||
{{if (ge .v 3) -}}
|
||||
precommitted, err := adt{{.v}}.AsMap(s.store, s.State.PreCommittedSectors, builtin{{.v}}.DefaultHamtBitwidth)
|
||||
{{- else -}}
|
||||
precommitted, err := adt{{.v}}.AsMap(s.store, s.State.PreCommittedSectors)
|
||||
{{- end}}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info miner{{.v}}.SectorPreCommitOnChainInfo
|
||||
if err := precommitted.ForEach(&info, func(_ string) error {
|
||||
return cb(fromV{{.v}}SectorPreCommitOnChainInfo(info))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state{{.v}}) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) {
|
||||
sectors, err := miner{{.v}}.LoadSectors(s.store, s.State.Sectors)
|
||||
if err != nil {
|
||||
@ -242,9 +263,15 @@ func (s *state{{.v}}) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (s *state{{.v}}) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
func (s *state{{.v}}) loadAllocatedSectorNumbers() (bitfield.BitField, error) {
|
||||
var allocatedSectors bitfield.BitField
|
||||
if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil {
|
||||
err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors)
|
||||
return allocatedSectors, err
|
||||
}
|
||||
|
||||
func (s *state{{.v}}) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -255,6 +282,42 @@ func (s *state{{.v}}) GetProvingPeriodStart() (abi.ChainEpoch, error) {
|
||||
return s.State.ProvingPeriodStart, nil
|
||||
}
|
||||
|
||||
func (s *state{{.v}}) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allocatedRuns, err := allocatedSectors.RunIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unallocatedRuns, err := rle.Subtract(
|
||||
&rle.RunSliceIterator{Runs: []rle.Run{ {Val: true, Len: abi.MaxSectorNumber} }},
|
||||
allocatedRuns,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iter, err := rle.BitsFromRuns(unallocatedRuns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sectors := make([]abi.SectorNumber, 0, count)
|
||||
for iter.HasNext() && len(sectors) < count {
|
||||
nextNo, err := iter.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectors = append(sectors, abi.SectorNumber(nextNo))
|
||||
}
|
||||
|
||||
return sectors, nil
|
||||
}
|
||||
|
||||
func (s *state{{.v}}) LoadDeadline(idx uint64) (Deadline, error) {
|
||||
dls, err := s.State.LoadDeadlines(s.store)
|
||||
if err != nil {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
rle "github.com/filecoin-project/go-bitfield/rle"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
"github.com/ipfs/go-cid"
|
||||
@ -206,6 +207,22 @@ func (s *state0) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *state0) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error {
|
||||
precommitted, err := adt0.AsMap(s.store, s.State.PreCommittedSectors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info miner0.SectorPreCommitOnChainInfo
|
||||
if err := precommitted.ForEach(&info, func(_ string) error {
|
||||
return cb(fromV0SectorPreCommitOnChainInfo(info))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state0) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) {
|
||||
sectors, err := miner0.LoadSectors(s.store, s.State.Sectors)
|
||||
if err != nil {
|
||||
@ -239,9 +256,15 @@ func (s *state0) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (s *state0) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
func (s *state0) loadAllocatedSectorNumbers() (bitfield.BitField, error) {
|
||||
var allocatedSectors bitfield.BitField
|
||||
if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil {
|
||||
err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors)
|
||||
return allocatedSectors, err
|
||||
}
|
||||
|
||||
func (s *state0) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -252,6 +275,42 @@ func (s *state0) GetProvingPeriodStart() (abi.ChainEpoch, error) {
|
||||
return s.State.ProvingPeriodStart, nil
|
||||
}
|
||||
|
||||
func (s *state0) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allocatedRuns, err := allocatedSectors.RunIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unallocatedRuns, err := rle.Subtract(
|
||||
&rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}},
|
||||
allocatedRuns,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iter, err := rle.BitsFromRuns(unallocatedRuns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sectors := make([]abi.SectorNumber, 0, count)
|
||||
for iter.HasNext() && len(sectors) < count {
|
||||
nextNo, err := iter.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectors = append(sectors, abi.SectorNumber(nextNo))
|
||||
}
|
||||
|
||||
return sectors, nil
|
||||
}
|
||||
|
||||
func (s *state0) LoadDeadline(idx uint64) (Deadline, error) {
|
||||
dls, err := s.State.LoadDeadlines(s.store)
|
||||
if err != nil {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
rle "github.com/filecoin-project/go-bitfield/rle"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
"github.com/ipfs/go-cid"
|
||||
@ -204,6 +205,22 @@ func (s *state2) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *state2) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error {
|
||||
precommitted, err := adt2.AsMap(s.store, s.State.PreCommittedSectors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info miner2.SectorPreCommitOnChainInfo
|
||||
if err := precommitted.ForEach(&info, func(_ string) error {
|
||||
return cb(fromV2SectorPreCommitOnChainInfo(info))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state2) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) {
|
||||
sectors, err := miner2.LoadSectors(s.store, s.State.Sectors)
|
||||
if err != nil {
|
||||
@ -237,9 +254,15 @@ func (s *state2) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (s *state2) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
func (s *state2) loadAllocatedSectorNumbers() (bitfield.BitField, error) {
|
||||
var allocatedSectors bitfield.BitField
|
||||
if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil {
|
||||
err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors)
|
||||
return allocatedSectors, err
|
||||
}
|
||||
|
||||
func (s *state2) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -250,6 +273,42 @@ func (s *state2) GetProvingPeriodStart() (abi.ChainEpoch, error) {
|
||||
return s.State.ProvingPeriodStart, nil
|
||||
}
|
||||
|
||||
func (s *state2) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allocatedRuns, err := allocatedSectors.RunIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unallocatedRuns, err := rle.Subtract(
|
||||
&rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}},
|
||||
allocatedRuns,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iter, err := rle.BitsFromRuns(unallocatedRuns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sectors := make([]abi.SectorNumber, 0, count)
|
||||
for iter.HasNext() && len(sectors) < count {
|
||||
nextNo, err := iter.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectors = append(sectors, abi.SectorNumber(nextNo))
|
||||
}
|
||||
|
||||
return sectors, nil
|
||||
}
|
||||
|
||||
func (s *state2) LoadDeadline(idx uint64) (Deadline, error) {
|
||||
dls, err := s.State.LoadDeadlines(s.store)
|
||||
if err != nil {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
rle "github.com/filecoin-project/go-bitfield/rle"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
"github.com/ipfs/go-cid"
|
||||
@ -206,6 +207,22 @@ func (s *state3) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *state3) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error {
|
||||
precommitted, err := adt3.AsMap(s.store, s.State.PreCommittedSectors, builtin3.DefaultHamtBitwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info miner3.SectorPreCommitOnChainInfo
|
||||
if err := precommitted.ForEach(&info, func(_ string) error {
|
||||
return cb(fromV3SectorPreCommitOnChainInfo(info))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) {
|
||||
sectors, err := miner3.LoadSectors(s.store, s.State.Sectors)
|
||||
if err != nil {
|
||||
@ -239,9 +256,15 @@ func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (s *state3) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
func (s *state3) loadAllocatedSectorNumbers() (bitfield.BitField, error) {
|
||||
var allocatedSectors bitfield.BitField
|
||||
if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil {
|
||||
err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors)
|
||||
return allocatedSectors, err
|
||||
}
|
||||
|
||||
func (s *state3) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -252,6 +275,42 @@ func (s *state3) GetProvingPeriodStart() (abi.ChainEpoch, error) {
|
||||
return s.State.ProvingPeriodStart, nil
|
||||
}
|
||||
|
||||
func (s *state3) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allocatedRuns, err := allocatedSectors.RunIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unallocatedRuns, err := rle.Subtract(
|
||||
&rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}},
|
||||
allocatedRuns,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iter, err := rle.BitsFromRuns(unallocatedRuns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sectors := make([]abi.SectorNumber, 0, count)
|
||||
for iter.HasNext() && len(sectors) < count {
|
||||
nextNo, err := iter.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectors = append(sectors, abi.SectorNumber(nextNo))
|
||||
}
|
||||
|
||||
return sectors, nil
|
||||
}
|
||||
|
||||
func (s *state3) LoadDeadline(idx uint64) (Deadline, error) {
|
||||
dls, err := s.State.LoadDeadlines(s.store)
|
||||
if err != nil {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
rle "github.com/filecoin-project/go-bitfield/rle"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
"github.com/ipfs/go-cid"
|
||||
@ -206,6 +207,22 @@ func (s *state4) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *state4) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error {
|
||||
precommitted, err := adt4.AsMap(s.store, s.State.PreCommittedSectors, builtin4.DefaultHamtBitwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info miner4.SectorPreCommitOnChainInfo
|
||||
if err := precommitted.ForEach(&info, func(_ string) error {
|
||||
return cb(fromV4SectorPreCommitOnChainInfo(info))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) {
|
||||
sectors, err := miner4.LoadSectors(s.store, s.State.Sectors)
|
||||
if err != nil {
|
||||
@ -239,9 +256,15 @@ func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (s *state4) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
func (s *state4) loadAllocatedSectorNumbers() (bitfield.BitField, error) {
|
||||
var allocatedSectors bitfield.BitField
|
||||
if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil {
|
||||
err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors)
|
||||
return allocatedSectors, err
|
||||
}
|
||||
|
||||
func (s *state4) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -252,6 +275,42 @@ func (s *state4) GetProvingPeriodStart() (abi.ChainEpoch, error) {
|
||||
return s.State.ProvingPeriodStart, nil
|
||||
}
|
||||
|
||||
func (s *state4) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allocatedRuns, err := allocatedSectors.RunIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unallocatedRuns, err := rle.Subtract(
|
||||
&rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}},
|
||||
allocatedRuns,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iter, err := rle.BitsFromRuns(unallocatedRuns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sectors := make([]abi.SectorNumber, 0, count)
|
||||
for iter.HasNext() && len(sectors) < count {
|
||||
nextNo, err := iter.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectors = append(sectors, abi.SectorNumber(nextNo))
|
||||
}
|
||||
|
||||
return sectors, nil
|
||||
}
|
||||
|
||||
func (s *state4) LoadDeadline(idx uint64) (Deadline, error) {
|
||||
dls, err := s.State.LoadDeadlines(s.store)
|
||||
if err != nil {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
rle "github.com/filecoin-project/go-bitfield/rle"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
"github.com/ipfs/go-cid"
|
||||
@ -206,6 +207,22 @@ func (s *state5) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *state5) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error {
|
||||
precommitted, err := adt5.AsMap(s.store, s.State.PreCommittedSectors, builtin5.DefaultHamtBitwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info miner5.SectorPreCommitOnChainInfo
|
||||
if err := precommitted.ForEach(&info, func(_ string) error {
|
||||
return cb(fromV5SectorPreCommitOnChainInfo(info))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state5) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) {
|
||||
sectors, err := miner5.LoadSectors(s.store, s.State.Sectors)
|
||||
if err != nil {
|
||||
@ -239,9 +256,15 @@ func (s *state5) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (s *state5) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
func (s *state5) loadAllocatedSectorNumbers() (bitfield.BitField, error) {
|
||||
var allocatedSectors bitfield.BitField
|
||||
if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil {
|
||||
err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors)
|
||||
return allocatedSectors, err
|
||||
}
|
||||
|
||||
func (s *state5) IsAllocated(num abi.SectorNumber) (bool, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -252,6 +275,42 @@ func (s *state5) GetProvingPeriodStart() (abi.ChainEpoch, error) {
|
||||
return s.State.ProvingPeriodStart, nil
|
||||
}
|
||||
|
||||
func (s *state5) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) {
|
||||
allocatedSectors, err := s.loadAllocatedSectorNumbers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allocatedRuns, err := allocatedSectors.RunIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unallocatedRuns, err := rle.Subtract(
|
||||
&rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}},
|
||||
allocatedRuns,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iter, err := rle.BitsFromRuns(unallocatedRuns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sectors := make([]abi.SectorNumber, 0, count)
|
||||
for iter.HasNext() && len(sectors) < count {
|
||||
nextNo, err := iter.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectors = append(sectors, abi.SectorNumber(nextNo))
|
||||
}
|
||||
|
||||
return sectors, nil
|
||||
}
|
||||
|
||||
func (s *state5) LoadDeadline(idx uint64) (Deadline, error) {
|
||||
dls, err := s.State.LoadDeadlines(s.store)
|
||||
if err != nil {
|
||||
|
@ -669,6 +669,12 @@ func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) {
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// Get the buffered blockstore associated with the VM. This includes any temporary blocks produced
|
||||
// during thsi VM's execution.
|
||||
func (vm *VM) ActorStore(ctx context.Context) adt.Store {
|
||||
return adt.WrapStore(ctx, vm.cst)
|
||||
}
|
||||
|
||||
func linksForObj(blk block.Block, cb func(cid.Cid)) error {
|
||||
switch blk.Cid().Prefix().Codec {
|
||||
case cid.DagCBOR:
|
||||
|
44
cmd/lotus-sim/create.go
Normal file
44
cmd/lotus-sim/create.go
Normal file
@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var createSimCommand = &cli.Command{
|
||||
Name: "create",
|
||||
ArgsUsage: "[tipset]",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
node, err := open(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer node.Close()
|
||||
|
||||
var ts *types.TipSet
|
||||
switch cctx.NArg() {
|
||||
case 0:
|
||||
if err := node.Chainstore.Load(); err != nil {
|
||||
return err
|
||||
}
|
||||
ts = node.Chainstore.GetHeaviestTipSet()
|
||||
case 1:
|
||||
cids, err := lcli.ParseTipSetString(cctx.Args().Get(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tsk := types.NewTipSetKey(cids...)
|
||||
ts, err = node.Chainstore.LoadTipSet(tsk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("expected 0 or 1 arguments")
|
||||
}
|
||||
_, err = node.CreateSim(cctx.Context, cctx.String("simulation"), ts)
|
||||
return err
|
||||
},
|
||||
}
|
18
cmd/lotus-sim/delete.go
Normal file
18
cmd/lotus-sim/delete.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var deleteSimCommand = &cli.Command{
|
||||
Name: "delete",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
node, err := open(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer node.Close()
|
||||
|
||||
return node.DeleteSim(cctx.Context, cctx.String("simulation"))
|
||||
},
|
||||
}
|
35
cmd/lotus-sim/list.go
Normal file
35
cmd/lotus-sim/list.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var listSimCommand = &cli.Command{
|
||||
Name: "list",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
node, err := open(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer node.Close()
|
||||
|
||||
list, err := node.ListSims(cctx.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0)
|
||||
for _, name := range list {
|
||||
sim, err := node.LoadSim(cctx.Context, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
head := sim.GetHead()
|
||||
fmt.Fprintf(tw, "%s\t%s\t%s\n", name, head.Height(), head.Key())
|
||||
sim.Close()
|
||||
}
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
87
cmd/lotus-sim/main.go
Normal file
87
cmd/lotus-sim/main.go
Normal file
@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
|
||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||
)
|
||||
|
||||
var root []*cli.Command = []*cli.Command{
|
||||
createSimCommand,
|
||||
deleteSimCommand,
|
||||
listSimCommand,
|
||||
stepSimCommand,
|
||||
setUpgradeCommand,
|
||||
}
|
||||
|
||||
func main() {
|
||||
if _, set := os.LookupEnv("GOLOG_LOG_LEVEL"); !set {
|
||||
_ = logging.SetLogLevel("simulation", "DEBUG")
|
||||
}
|
||||
app := &cli.App{
|
||||
Name: "lotus-sim",
|
||||
Usage: "A tool to simulate a network.",
|
||||
Commands: root,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "repo",
|
||||
EnvVars: []string{"LOTUS_PATH"},
|
||||
Hidden: true,
|
||||
Value: "~/.lotus",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "simulation",
|
||||
Aliases: []string{"sim"},
|
||||
EnvVars: []string{"LOTUS_SIMULATION"},
|
||||
Value: "default",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func run(cctx *cli.Context) error {
|
||||
ctx := cctx.Context
|
||||
|
||||
node, err := open(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer node.Close()
|
||||
|
||||
if err := node.Chainstore.Load(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ts := node.Chainstore.GetHeaviestTipSet()
|
||||
|
||||
st, err := stmgr.NewStateManagerWithUpgradeSchedule(node.Chainstore, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
powerTableActor, err := st.LoadActor(ctx, power.Address, ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
powerTable, err := power.Load(node.Chainstore.ActorStore(ctx), powerTableActor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allMiners, err := powerTable.ListAllMiners()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("miner count: %d\n", len(allMiners))
|
||||
return nil
|
||||
}
|
187
cmd/lotus-sim/simulation/commit_queue.go
Normal file
187
cmd/lotus-sim/simulation/commit_queue.go
Normal file
@ -0,0 +1,187 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
||||
"github.com/filecoin-project/lotus/chain/actors/policy"
|
||||
)
|
||||
|
||||
type pendingCommitTracker map[address.Address]minerPendingCommits
|
||||
type minerPendingCommits map[abi.RegisteredSealProof][]abi.SectorNumber
|
||||
|
||||
func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) {
|
||||
snos := m[proof]
|
||||
if len(snos) < count {
|
||||
panic("not enough sector numbers to finish")
|
||||
} else if len(snos) == count {
|
||||
delete(m, proof)
|
||||
} else {
|
||||
m[proof] = snos[count:]
|
||||
}
|
||||
}
|
||||
|
||||
func (m minerPendingCommits) empty() bool {
|
||||
return len(m) == 0
|
||||
}
|
||||
|
||||
func (m minerPendingCommits) count() int {
|
||||
count := 0
|
||||
for _, snos := range m {
|
||||
count += len(snos)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
type commitQueue struct {
|
||||
minerQueue []address.Address
|
||||
queue []pendingCommitTracker
|
||||
offset abi.ChainEpoch
|
||||
}
|
||||
|
||||
func (q *commitQueue) ready() int {
|
||||
if len(q.queue) == 0 {
|
||||
return 0
|
||||
}
|
||||
count := 0
|
||||
for _, pending := range q.queue[0] {
|
||||
count += pending.count()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (q *commitQueue) nextMiner() (address.Address, minerPendingCommits, bool) {
|
||||
if len(q.queue) == 0 {
|
||||
return address.Undef, nil, false
|
||||
}
|
||||
next := q.queue[0]
|
||||
|
||||
// Go through the queue and find the first non-empty batch.
|
||||
for len(q.minerQueue) > 0 {
|
||||
addr := q.minerQueue[0]
|
||||
q.minerQueue = q.minerQueue[1:]
|
||||
pending := next[addr]
|
||||
if !pending.empty() {
|
||||
return addr, pending, true
|
||||
}
|
||||
delete(next, addr)
|
||||
}
|
||||
|
||||
return address.Undef, nil, false
|
||||
}
|
||||
|
||||
func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) {
|
||||
if epoch < q.offset {
|
||||
panic("cannot roll epoch backwards")
|
||||
}
|
||||
// Now we "roll forwards", merging each epoch we advance over with the next.
|
||||
for len(q.queue) > 1 && q.offset < epoch {
|
||||
curr := q.queue[0]
|
||||
q.queue[0] = nil
|
||||
q.queue = q.queue[1:]
|
||||
q.offset++
|
||||
|
||||
next := q.queue[0]
|
||||
|
||||
// Cleanup empty entries.
|
||||
for addr, pending := range curr {
|
||||
if pending.empty() {
|
||||
delete(curr, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// If the entire level is actually empty, just skip to the next one.
|
||||
if len(curr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, merge the next into the current.
|
||||
for addr, nextPending := range next {
|
||||
currPending := curr[addr]
|
||||
if currPending.empty() {
|
||||
curr[addr] = nextPending
|
||||
continue
|
||||
}
|
||||
for ty, nextSnos := range nextPending {
|
||||
currSnos := currPending[ty]
|
||||
if len(currSnos) == 0 {
|
||||
currPending[ty] = nextSnos
|
||||
continue
|
||||
}
|
||||
currPending[ty] = append(currSnos, nextSnos...)
|
||||
}
|
||||
}
|
||||
}
|
||||
q.offset = epoch
|
||||
if len(q.queue) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
next := q.queue[0]
|
||||
seenMiners := make(map[address.Address]struct{}, len(q.minerQueue))
|
||||
for _, addr := range q.minerQueue {
|
||||
seenMiners[addr] = struct{}{}
|
||||
}
|
||||
|
||||
// Find the new miners not already in the queue.
|
||||
offset := len(q.minerQueue)
|
||||
for addr, pending := range next {
|
||||
if pending.empty() {
|
||||
delete(next, addr)
|
||||
continue
|
||||
}
|
||||
if _, ok := seenMiners[addr]; ok {
|
||||
continue
|
||||
}
|
||||
q.minerQueue = append(q.minerQueue, addr)
|
||||
}
|
||||
|
||||
// Sort the new miners only.
|
||||
newMiners := q.minerQueue[offset:]
|
||||
sort.Slice(newMiners, func(i, j int) bool {
|
||||
// eh, escape analysis should be fine here...
|
||||
return string(newMiners[i].Bytes()) < string(newMiners[j].Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func (q *commitQueue) enqueueProveCommit(addr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo) error {
|
||||
// Compute the epoch at which we can start trying to commit.
|
||||
preCommitDelay := policy.GetPreCommitChallengeDelay()
|
||||
minCommitEpoch := preCommitEpoch + preCommitDelay + 1
|
||||
|
||||
// Figure out the offset in the queue.
|
||||
i := int(minCommitEpoch - q.offset)
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
|
||||
// Expand capacity and insert.
|
||||
if cap(q.queue) <= i {
|
||||
pc := make([]pendingCommitTracker, i+1, preCommitDelay*2)
|
||||
copy(pc, q.queue)
|
||||
q.queue = pc
|
||||
} else if len(q.queue) <= i {
|
||||
q.queue = q.queue[:i+1]
|
||||
}
|
||||
tracker := q.queue[i]
|
||||
if tracker == nil {
|
||||
tracker = make(pendingCommitTracker)
|
||||
q.queue[i] = tracker
|
||||
}
|
||||
minerPending := tracker[addr]
|
||||
if minerPending == nil {
|
||||
minerPending = make(minerPendingCommits)
|
||||
tracker[addr] = minerPending
|
||||
}
|
||||
minerPending[info.SealProof] = append(minerPending[info.SealProof], info.SectorNumber)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *commitQueue) head() pendingCommitTracker {
|
||||
if len(q.queue) > 0 {
|
||||
return q.queue[0]
|
||||
}
|
||||
return nil
|
||||
}
|
81
cmd/lotus-sim/simulation/messages.go
Normal file
81
cmd/lotus-sim/simulation/messages.go
Normal file
@ -0,0 +1,81 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
blockadt "github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
"github.com/ipfs/go-cid"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) {
|
||||
arr := blockadt.MakeEmptyArray(store)
|
||||
for i, c := range cids {
|
||||
oc := cbg.CborCid(c)
|
||||
if err := arr.Set(uint64(i), &oc); err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
}
|
||||
return arr.Root()
|
||||
}
|
||||
|
||||
func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) {
|
||||
var blsMessages, sekpMessages []cid.Cid
|
||||
fakeSig := make([]byte, 32)
|
||||
for _, msg := range messages {
|
||||
protocol := msg.From.Protocol()
|
||||
|
||||
// It's just a very convenient way to fill up accounts.
|
||||
if msg.From == builtin.BurntFundsActorAddr {
|
||||
protocol = address.SECP256K1
|
||||
}
|
||||
switch protocol {
|
||||
case address.SECP256K1:
|
||||
chainMsg := &types.SignedMessage{
|
||||
Message: *msg,
|
||||
Signature: crypto.Signature{
|
||||
Type: crypto.SigTypeSecp256k1,
|
||||
Data: fakeSig,
|
||||
},
|
||||
}
|
||||
c, err := nd.Chainstore.PutMessage(chainMsg)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
sekpMessages = append(sekpMessages, c)
|
||||
case address.BLS:
|
||||
c, err := nd.Chainstore.PutMessage(msg)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
blsMessages = append(blsMessages, c)
|
||||
default:
|
||||
return cid.Undef, xerrors.Errorf("unexpected from address %q of type %d", msg.From, msg.From.Protocol())
|
||||
}
|
||||
}
|
||||
adtStore := nd.Chainstore.ActorStore(ctx)
|
||||
blsMsgArr, err := toArray(adtStore, blsMessages)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
sekpMsgArr, err := toArray(adtStore, sekpMessages)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
msgsCid, err := adtStore.Put(adtStore.Context(), &types.MsgMeta{
|
||||
BlsMessages: blsMsgArr,
|
||||
SecpkMessages: sekpMsgArr,
|
||||
})
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
return msgsCid, nil
|
||||
}
|
136
cmd/lotus-sim/simulation/mock.go
Normal file
136
cmd/lotus-sim/simulation/mock.go
Normal file
@ -0,0 +1,136 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof"
|
||||
)
|
||||
|
||||
// Ideally, we'd use extern/sector-storage/mock. Unfortunately, those mocks are a bit _too_ accurate
|
||||
// and would force us to load sector info for window post proofs.
|
||||
|
||||
const (
|
||||
mockSealProofPrefix = "valid seal proof:"
|
||||
mockAggregateSealProofPrefix = "valid aggregate seal proof:"
|
||||
mockPoStProofPrefix = "valid post proof:"
|
||||
)
|
||||
|
||||
func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) {
|
||||
plen, err := proofType.ProofSize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proof := make([]byte, plen)
|
||||
i := copy(proof, mockSealProofPrefix)
|
||||
binary.BigEndian.PutUint64(proof[i:], uint64(proofType))
|
||||
i += 8
|
||||
i += copy(proof[i:], minerAddr.Bytes())
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) {
|
||||
proof := make([]byte, aggProofLen(count))
|
||||
i := copy(proof, mockAggregateSealProofPrefix)
|
||||
binary.BigEndian.PutUint64(proof[i:], uint64(proofType))
|
||||
i += 8
|
||||
binary.BigEndian.PutUint64(proof[i:], uint64(count))
|
||||
i += 8
|
||||
i += copy(proof[i:], minerAddr.Bytes())
|
||||
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) {
|
||||
plen, err := proofType.ProofSize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proof := make([]byte, plen)
|
||||
i := copy(proof, mockPoStProofPrefix)
|
||||
i += copy(proof[i:], minerAddr.Bytes())
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
// TODO: dedup
|
||||
func aggProofLen(nproofs int) int {
|
||||
switch {
|
||||
case nproofs <= 8:
|
||||
return 11220
|
||||
case nproofs <= 16:
|
||||
return 14196
|
||||
case nproofs <= 32:
|
||||
return 17172
|
||||
case nproofs <= 64:
|
||||
return 20148
|
||||
case nproofs <= 128:
|
||||
return 23124
|
||||
case nproofs <= 256:
|
||||
return 26100
|
||||
case nproofs <= 512:
|
||||
return 29076
|
||||
case nproofs <= 1024:
|
||||
return 32052
|
||||
case nproofs <= 2048:
|
||||
return 35028
|
||||
case nproofs <= 4096:
|
||||
return 38004
|
||||
case nproofs <= 8192:
|
||||
return 40980
|
||||
default:
|
||||
panic("too many proofs")
|
||||
}
|
||||
}
|
||||
|
||||
type mockVerifier struct{}
|
||||
|
||||
func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) {
|
||||
addr, err := address.NewIDAddress(uint64(proof.Miner))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
mockProof, err := mockSealProof(proof.SealProof, addr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Equal(proof.Proof, mockProof), nil
|
||||
}
|
||||
|
||||
func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) {
|
||||
addr, err := address.NewIDAddress(uint64(aggregate.Miner))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
mockProof, err := mockAggregateSealProof(aggregate.SealProof, addr, len(aggregate.Infos))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Equal(aggregate.Proof, mockProof), nil
|
||||
}
|
||||
func (mockVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) {
|
||||
if len(info.Proofs) != 1 {
|
||||
return false, fmt.Errorf("expected exactly one proof")
|
||||
}
|
||||
proof := info.Proofs[0]
|
||||
addr, err := address.NewIDAddress(uint64(info.Prover))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
mockProof, err := mockWpostProof(proof.PoStProof, addr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Equal(proof.ProofBytes, mockProof), nil
|
||||
}
|
||||
|
||||
func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) {
|
||||
panic("should not be called")
|
||||
}
|
167
cmd/lotus-sim/simulation/node.go
Normal file
167
cmd/lotus-sim/simulation/node.go
Normal file
@ -0,0 +1,167 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/query"
|
||||
|
||||
"github.com/filecoin-project/lotus/blockstore"
|
||||
"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/vm"
|
||||
"github.com/filecoin-project/lotus/node/repo"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Repo repo.LockedRepo
|
||||
Blockstore blockstore.Blockstore
|
||||
MetadataDS datastore.Batching
|
||||
Chainstore *store.ChainStore
|
||||
}
|
||||
|
||||
func OpenNode(ctx context.Context, path string) (*Node, error) {
|
||||
var node Node
|
||||
r, err := repo.NewFS(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node.Repo, err = r.Lock(repo.FullNode)
|
||||
if err != nil {
|
||||
node.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node.Blockstore, err = node.Repo.Blockstore(ctx, repo.UniversalBlockstore)
|
||||
if err != nil {
|
||||
node.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node.MetadataDS, err = node.Repo.Datastore(ctx, "/metadata")
|
||||
if err != nil {
|
||||
node.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mockVerifier{}), nil)
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
func (nd *Node) Close() error {
|
||||
var err error
|
||||
if closer, ok := nd.Blockstore.(io.Closer); ok && closer != nil {
|
||||
err = multierr.Append(err, closer.Close())
|
||||
}
|
||||
if nd.MetadataDS != nil {
|
||||
err = multierr.Append(err, nd.MetadataDS.Close())
|
||||
}
|
||||
if nd.Repo != nil {
|
||||
err = multierr.Append(err, nd.Repo.Close())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) {
|
||||
sim := &Simulation{
|
||||
Node: nd,
|
||||
name: name,
|
||||
}
|
||||
tskBytes, err := nd.MetadataDS.Get(sim.key("head"))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load simulation %s: %w", name, err)
|
||||
}
|
||||
tsk, err := types.TipSetKeyFromBytes(tskBytes)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to parse simulation %s's tipset %v: %w", name, tskBytes, err)
|
||||
}
|
||||
sim.head, err = nd.Chainstore.LoadTipSet(tsk)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load simulation tipset %s: %w", tsk, err)
|
||||
}
|
||||
|
||||
err = sim.loadConfig()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load config for simulation %s: %w", name, err)
|
||||
}
|
||||
|
||||
us, err := sim.config.upgradeSchedule()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to create upgrade schedule for simulation %s: %w", name, err)
|
||||
}
|
||||
sim.sm, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err)
|
||||
}
|
||||
return sim, nil
|
||||
}
|
||||
|
||||
func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) (*Simulation, error) {
|
||||
if strings.Contains(name, "/") {
|
||||
return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name)
|
||||
}
|
||||
sim := &Simulation{
|
||||
name: name,
|
||||
Node: nd,
|
||||
sm: stmgr.NewStateManager(nd.Chainstore),
|
||||
}
|
||||
if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil {
|
||||
return nil, err
|
||||
} else if has {
|
||||
return nil, xerrors.Errorf("simulation named %s already exists", name)
|
||||
}
|
||||
|
||||
if err := sim.SetHead(head); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sim, nil
|
||||
}
|
||||
|
||||
func (nd *Node) ListSims(ctx context.Context) ([]string, error) {
|
||||
prefix := simulationPrefix.ChildString("head").String()
|
||||
items, err := nd.MetadataDS.Query(query.Query{
|
||||
Prefix: prefix,
|
||||
KeysOnly: true,
|
||||
Orders: []query.Order{query.OrderByKey{}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to list simulations: %w", err)
|
||||
}
|
||||
defer items.Close()
|
||||
var names []string
|
||||
for {
|
||||
select {
|
||||
case result, ok := <-items.Next():
|
||||
if !ok {
|
||||
return names, nil
|
||||
}
|
||||
if result.Error != nil {
|
||||
return nil, xerrors.Errorf("failed to retrieve next simulation: %w", result.Error)
|
||||
}
|
||||
names = append(names, strings.TrimPrefix(result.Key, prefix+"/"))
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (nd *Node) DeleteSim(ctx context.Context, name string) error {
|
||||
// TODO: make this a bit more generic?
|
||||
keys := []datastore.Key{
|
||||
simulationPrefix.ChildString("head").ChildString(name),
|
||||
simulationPrefix.ChildString("config").ChildString(name),
|
||||
}
|
||||
var err error
|
||||
for _, key := range keys {
|
||||
err = multierr.Append(err, nd.MetadataDS.Delete(key))
|
||||
}
|
||||
return err
|
||||
}
|
58
cmd/lotus-sim/simulation/power.go
Normal file
58
cmd/lotus-sim/simulation/power.go
Normal file
@ -0,0 +1,58 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
|
||||
)
|
||||
|
||||
type powerInfo struct {
|
||||
powerLookback, powerNow abi.StoragePower
|
||||
}
|
||||
|
||||
// Load all power claims at the given height.
|
||||
func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (map[address.Address]power.Claim, error) {
|
||||
powerTable := make(map[address.Address]power.Claim)
|
||||
store := sim.Chainstore.ActorStore(ctx)
|
||||
|
||||
ts, err := sim.Chainstore.GetTipsetByHeight(ctx, height, sim.head, true)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("when projecting growth, failed to lookup lookback epoch: %w", err)
|
||||
}
|
||||
|
||||
powerActor, err := sim.sm.LoadActor(ctx, power.Address, ts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
powerState, err := power.Load(store, powerActor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error {
|
||||
powerTable[miner] = claim
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return powerTable, nil
|
||||
}
|
||||
|
||||
// Compute the number of sectors a miner has from their power claim.
|
||||
func sectorsFromClaim(sectorSize abi.SectorSize, c power.Claim) int64 {
|
||||
if c.RawBytePower.Int == nil {
|
||||
return 0
|
||||
}
|
||||
sectorCount := big.Div(c.RawBytePower, big.NewIntUnsigned(uint64(sectorSize)))
|
||||
if !sectorCount.IsInt64() {
|
||||
panic("impossible number of sectors")
|
||||
}
|
||||
return sectorCount.Int64()
|
||||
}
|
205
cmd/lotus-sim/simulation/precommit.go
Normal file
205
cmd/lotus-sim/simulation/precommit.go
Normal file
@ -0,0 +1,205 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
||||
"github.com/filecoin-project/lotus/chain/actors/policy"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/ipfs/go-cid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner"
|
||||
tutils "github.com/filecoin-project/specs-actors/v5/support/testing"
|
||||
)
|
||||
|
||||
func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid {
|
||||
return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix)
|
||||
}
|
||||
|
||||
var (
|
||||
targetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL"))
|
||||
minFunds = abi.TokenAmount(types.MustParseFIL("100FIL"))
|
||||
)
|
||||
|
||||
func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (full bool, _err error) {
|
||||
var top1Count, top10Count, restCount int
|
||||
defer func() {
|
||||
if _err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugw("packed pre commits",
|
||||
"done", top1Count+top10Count+restCount,
|
||||
"top1", top1Count,
|
||||
"top10", top10Count,
|
||||
"rest", restCount,
|
||||
"filled-block", full,
|
||||
)
|
||||
}()
|
||||
|
||||
var top1Miners, top10Miners, restMiners int
|
||||
for i := 0; ; i++ {
|
||||
var (
|
||||
minerAddr address.Address
|
||||
count *int
|
||||
)
|
||||
switch {
|
||||
case (i%3) <= 0 && top1Miners < ss.minerDist.top1.len():
|
||||
count = &top1Count
|
||||
minerAddr = ss.minerDist.top1.next()
|
||||
top1Miners++
|
||||
case (i%3) <= 1 && top10Miners < ss.minerDist.top10.len():
|
||||
count = &top10Count
|
||||
minerAddr = ss.minerDist.top10.next()
|
||||
top10Miners++
|
||||
case (i%3) <= 2 && restMiners < ss.minerDist.rest.len():
|
||||
count = &restCount
|
||||
minerAddr = ss.minerDist.rest.next()
|
||||
restMiners++
|
||||
default:
|
||||
// Well, we've run through all miners.
|
||||
return false, nil
|
||||
}
|
||||
added, full, err := ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize)
|
||||
if err != nil {
|
||||
return false, xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err)
|
||||
}
|
||||
*count += added
|
||||
if full {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) {
|
||||
epoch := ss.nextEpoch()
|
||||
nv := ss.sm.GetNtwkVersion(ctx, epoch)
|
||||
st, err := ss.stateTree(ctx)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
actor, err := st.GetActor(minerAddr)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), actor)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
minerInfo, err := ss.getMinerInfo(ctx, minerAddr)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
// Make sure the miner is funded.
|
||||
minerBalance, err := minerState.AvailableBalance(actor.Balance)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
if big.Cmp(minerBalance, minFunds) < 0 {
|
||||
full, err := cb(&types.Message{
|
||||
From: builtin.BurntFundsActorAddr,
|
||||
To: minerAddr,
|
||||
Value: targetFunds,
|
||||
Method: builtin.MethodSend,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, false, xerrors.Errorf("failed to fund miner %s: %w", minerAddr, err)
|
||||
}
|
||||
if full {
|
||||
return 0, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
sealType, err := miner.PreferredSealProofTypeFromWindowPoStType(
|
||||
nv, minerInfo.WindowPoStProofType,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
sectorNos, err := minerState.UnallocatedSectorNumbers(count)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
expiration := epoch + policy.GetMaxSectorExpirationExtension()
|
||||
infos := make([]miner.SectorPreCommitInfo, len(sectorNos))
|
||||
for i, sno := range sectorNos {
|
||||
infos[i] = miner.SectorPreCommitInfo{
|
||||
SealProof: sealType,
|
||||
SectorNumber: sno,
|
||||
SealedCID: makeCommR(minerAddr, sno),
|
||||
SealRandEpoch: epoch - 1,
|
||||
Expiration: expiration,
|
||||
}
|
||||
}
|
||||
added := 0
|
||||
if nv >= network.Version13 {
|
||||
targetBatchSize := maxPreCommitBatchSize
|
||||
for targetBatchSize >= minPreCommitBatchSize && len(infos) >= minPreCommitBatchSize {
|
||||
batch := infos
|
||||
if len(batch) > targetBatchSize {
|
||||
batch = batch[:targetBatchSize]
|
||||
}
|
||||
params := miner5.PreCommitSectorBatchParams{
|
||||
Sectors: batch,
|
||||
}
|
||||
enc, err := actors.SerializeParams(¶ms)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if full, err := sendAndFund(cb, &types.Message{
|
||||
To: minerAddr,
|
||||
From: minerInfo.Worker,
|
||||
Value: abi.NewTokenAmount(0),
|
||||
Method: miner.Methods.PreCommitSectorBatch,
|
||||
Params: enc,
|
||||
}); err != nil {
|
||||
return 0, false, err
|
||||
} else if full {
|
||||
// try again with a smaller batch.
|
||||
targetBatchSize /= 2
|
||||
continue
|
||||
}
|
||||
|
||||
for _, info := range batch {
|
||||
if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
added++
|
||||
}
|
||||
infos = infos[len(batch):]
|
||||
}
|
||||
}
|
||||
for _, info := range infos {
|
||||
enc, err := actors.SerializeParams(&info)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if full, err := sendAndFund(cb, &types.Message{
|
||||
To: minerAddr,
|
||||
From: minerInfo.Worker,
|
||||
Value: abi.NewTokenAmount(0),
|
||||
Method: miner.Methods.PreCommitSector,
|
||||
Params: enc,
|
||||
}); full || err != nil {
|
||||
return added, full, err
|
||||
}
|
||||
|
||||
if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
added++
|
||||
}
|
||||
return added, false, nil
|
||||
}
|
219
cmd/lotus-sim/simulation/provecommit.go
Normal file
219
cmd/lotus-sim/simulation/provecommit.go
Normal file
@ -0,0 +1,219 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/exitcode"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
||||
"github.com/filecoin-project/lotus/chain/actors/policy"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
|
||||
miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner"
|
||||
power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power"
|
||||
)
|
||||
|
||||
func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_full bool, _err error) {
|
||||
ss.commitQueue.advanceEpoch(ss.nextEpoch())
|
||||
|
||||
var failed, done, unbatched, count int
|
||||
defer func() {
|
||||
if _err != nil {
|
||||
return
|
||||
}
|
||||
remaining := ss.commitQueue.ready()
|
||||
log.Debugw("packed prove commits",
|
||||
"remaining", remaining,
|
||||
"done", done,
|
||||
"failed", failed,
|
||||
"unbatched", unbatched,
|
||||
"miners-processed", count,
|
||||
"filled-block", _full,
|
||||
)
|
||||
}()
|
||||
|
||||
for {
|
||||
addr, pending, ok := ss.commitQueue.nextMiner()
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
res, full, err := ss.packProveCommitsMiner(ctx, cb, addr, pending)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
failed += res.failed
|
||||
done += res.done
|
||||
unbatched += res.unbatched
|
||||
count++
|
||||
if full {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type proveCommitResult struct {
|
||||
done, failed, unbatched int
|
||||
}
|
||||
|
||||
func sendAndFund(send packFunc, msg *types.Message) (bool, error) {
|
||||
full, err := send(msg)
|
||||
aerr, ok := err.(aerrors.ActorError)
|
||||
if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds {
|
||||
return full, err
|
||||
}
|
||||
// Ok, insufficient funds. Let's fund this miner and try again.
|
||||
full, err = send(&types.Message{
|
||||
From: builtin.BurntFundsActorAddr,
|
||||
To: msg.To,
|
||||
Value: targetFunds,
|
||||
Method: builtin.MethodSend,
|
||||
})
|
||||
if err != nil {
|
||||
return false, xerrors.Errorf("failed to fund %s: %w", msg.To, err)
|
||||
}
|
||||
// ok, nothing's going to work.
|
||||
if full {
|
||||
return true, nil
|
||||
}
|
||||
return send(msg)
|
||||
}
|
||||
|
||||
// Enqueue a single prove commit from the given miner.
|
||||
func (ss *simulationState) packProveCommitsMiner(
|
||||
ctx context.Context, cb packFunc, minerAddr address.Address,
|
||||
pending minerPendingCommits,
|
||||
) (res proveCommitResult, full bool, _err error) {
|
||||
info, err := ss.getMinerInfo(ctx, minerAddr)
|
||||
if err != nil {
|
||||
return res, false, err
|
||||
}
|
||||
|
||||
nv := ss.sm.GetNtwkVersion(ctx, ss.nextEpoch())
|
||||
for sealType, snos := range pending {
|
||||
if nv >= network.Version13 {
|
||||
for len(snos) > minProveCommitBatchSize {
|
||||
batchSize := maxProveCommitBatchSize
|
||||
if len(snos) < batchSize {
|
||||
batchSize = len(snos)
|
||||
}
|
||||
batch := snos[:batchSize]
|
||||
snos = snos[batchSize:]
|
||||
|
||||
proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize)
|
||||
if err != nil {
|
||||
return res, false, err
|
||||
}
|
||||
|
||||
params := miner5.ProveCommitAggregateParams{
|
||||
SectorNumbers: bitfield.New(),
|
||||
AggregateProof: proof,
|
||||
}
|
||||
for _, sno := range batch {
|
||||
params.SectorNumbers.Set(uint64(sno))
|
||||
}
|
||||
|
||||
enc, err := actors.SerializeParams(¶ms)
|
||||
if err != nil {
|
||||
return res, false, err
|
||||
}
|
||||
|
||||
if full, err := sendAndFund(cb, &types.Message{
|
||||
From: info.Worker,
|
||||
To: minerAddr,
|
||||
Value: abi.NewTokenAmount(0),
|
||||
Method: miner.Methods.ProveCommitAggregate,
|
||||
Params: enc,
|
||||
}); err != nil {
|
||||
// If we get a random error, or a fatal actor error, bail.
|
||||
// Otherwise, just log it.
|
||||
if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() {
|
||||
return res, false, err
|
||||
}
|
||||
log.Errorw("failed to prove commit sector(s)",
|
||||
"error", err,
|
||||
"miner", minerAddr,
|
||||
"sectors", batch,
|
||||
"epoch", ss.nextEpoch(),
|
||||
)
|
||||
res.failed += batchSize
|
||||
} else if full {
|
||||
return res, true, nil
|
||||
} else {
|
||||
res.done += batchSize
|
||||
}
|
||||
pending.finish(sealType, batchSize)
|
||||
}
|
||||
}
|
||||
for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch {
|
||||
sno := snos[0]
|
||||
snos = snos[1:]
|
||||
|
||||
proof, err := mockSealProof(sealType, minerAddr)
|
||||
if err != nil {
|
||||
return res, false, err
|
||||
}
|
||||
params := miner.ProveCommitSectorParams{
|
||||
SectorNumber: sno,
|
||||
Proof: proof,
|
||||
}
|
||||
enc, err := actors.SerializeParams(¶ms)
|
||||
if err != nil {
|
||||
return res, false, err
|
||||
}
|
||||
if full, err := sendAndFund(cb, &types.Message{
|
||||
From: info.Worker,
|
||||
To: minerAddr,
|
||||
Value: abi.NewTokenAmount(0),
|
||||
Method: miner.Methods.ProveCommitSector,
|
||||
Params: enc,
|
||||
}); err != nil {
|
||||
if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() {
|
||||
return res, false, err
|
||||
}
|
||||
log.Errorw("failed to prove commit sector(s)",
|
||||
"error", err,
|
||||
"miner", minerAddr,
|
||||
"sectors", []abi.SectorNumber{sno},
|
||||
"epoch", ss.nextEpoch(),
|
||||
)
|
||||
res.failed++
|
||||
} else if full {
|
||||
return res, true, nil
|
||||
} else {
|
||||
res.unbatched++
|
||||
res.done++
|
||||
}
|
||||
// mark it as "finished" regardless so we skip it.
|
||||
pending.finish(sealType, 1)
|
||||
}
|
||||
// if we get here, we can't pre-commit anything more.
|
||||
}
|
||||
return res, false, nil
|
||||
}
|
||||
|
||||
// Enqueue all pending prove-commits for the given miner.
|
||||
func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr address.Address, minerState miner.State) error {
|
||||
// Find all pending prove commits and group by proof type. Really, there should never
|
||||
// (except during upgrades be more than one type.
|
||||
nextEpoch := ss.nextEpoch()
|
||||
nv := ss.sm.GetNtwkVersion(ctx, nextEpoch)
|
||||
av := actors.VersionForNetwork(nv)
|
||||
|
||||
return minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error {
|
||||
msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof)
|
||||
if nextEpoch > info.PreCommitEpoch+msd {
|
||||
log.Warnw("dropping old pre-commit")
|
||||
return nil
|
||||
}
|
||||
return ss.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info)
|
||||
})
|
||||
}
|
274
cmd/lotus-sim/simulation/simulation.go
Normal file
274
cmd/lotus-sim/simulation/simulation.go
Normal file
@ -0,0 +1,274 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/ipfs/go-datastore"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
|
||||
miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner"
|
||||
)
|
||||
|
||||
var log = logging.Logger("simulation")
|
||||
|
||||
const onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks
|
||||
|
||||
const (
|
||||
minPreCommitBatchSize = 1
|
||||
maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize
|
||||
minProveCommitBatchSize = 4
|
||||
maxProveCommitBatchSize = miner5.MaxAggregatedSectors
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Upgrades map[network.Version]abi.ChainEpoch
|
||||
}
|
||||
|
||||
func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) {
|
||||
upgradeSchedule := stmgr.DefaultUpgradeSchedule()
|
||||
expected := make(map[network.Version]struct{}, len(c.Upgrades))
|
||||
for nv := range c.Upgrades {
|
||||
expected[nv] = struct{}{}
|
||||
}
|
||||
newUpgradeSchedule := upgradeSchedule[:0]
|
||||
for _, upgrade := range upgradeSchedule {
|
||||
if height, ok := c.Upgrades[upgrade.Network]; ok {
|
||||
delete(expected, upgrade.Network)
|
||||
if height < 0 {
|
||||
continue
|
||||
}
|
||||
upgrade.Height = height
|
||||
}
|
||||
newUpgradeSchedule = append(newUpgradeSchedule, upgrade)
|
||||
}
|
||||
if len(expected) > 0 {
|
||||
missing := make([]network.Version, 0, len(expected))
|
||||
for nv := range expected {
|
||||
missing = append(missing, nv)
|
||||
}
|
||||
return nil, xerrors.Errorf("unknown network versions %v in config", missing)
|
||||
}
|
||||
return newUpgradeSchedule, nil
|
||||
}
|
||||
|
||||
type Simulation struct {
|
||||
*Node
|
||||
|
||||
name string
|
||||
config config
|
||||
sm *stmgr.StateManager
|
||||
|
||||
// head
|
||||
st *state.StateTree
|
||||
head *types.TipSet
|
||||
|
||||
// lazy-loaded state
|
||||
// access through `simState(ctx)` to load on-demand.
|
||||
state *simulationState
|
||||
}
|
||||
|
||||
func (sim *Simulation) loadConfig() error {
|
||||
configBytes, err := sim.MetadataDS.Get(sim.key("config"))
|
||||
if err == nil {
|
||||
err = json.Unmarshal(configBytes, &sim.config)
|
||||
}
|
||||
switch err {
|
||||
case nil:
|
||||
case datastore.ErrNotFound:
|
||||
sim.config = config{}
|
||||
default:
|
||||
return xerrors.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) {
|
||||
if sim.st == nil {
|
||||
st, _, err := sim.sm.TipSetState(ctx, sim.head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sim.st, err = sim.sm.StateTree(st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sim.st, nil
|
||||
}
|
||||
|
||||
// Loads the simulation state. The state is memoized so this will be fast except the first time.
|
||||
func (sim *Simulation) simState(ctx context.Context) (*simulationState, error) {
|
||||
if sim.state == nil {
|
||||
log.Infow("loading simulation")
|
||||
state, err := loadSimulationState(ctx, sim)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load simulation state: %w", err)
|
||||
}
|
||||
sim.state = state
|
||||
log.Infow("simulation loaded", "miners", len(sim.state.minerInfos))
|
||||
}
|
||||
|
||||
return sim.state, nil
|
||||
}
|
||||
|
||||
var simulationPrefix = datastore.NewKey("/simulation")
|
||||
|
||||
func (sim *Simulation) key(subkey string) datastore.Key {
|
||||
return simulationPrefix.ChildString(subkey).ChildString(sim.name)
|
||||
}
|
||||
|
||||
// Load loads the simulation state. This will happen automatically on first use, but it can be
|
||||
// useful to preload for timing reasons.
|
||||
func (sim *Simulation) Load(ctx context.Context) error {
|
||||
_, err := sim.simState(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (sim *Simulation) GetHead() *types.TipSet {
|
||||
return sim.head
|
||||
}
|
||||
|
||||
func (sim *Simulation) SetHead(head *types.TipSet) error {
|
||||
if err := sim.MetadataDS.Put(sim.key("head"), head.Key().Bytes()); err != nil {
|
||||
return xerrors.Errorf("failed to store simulation head: %w", err)
|
||||
}
|
||||
sim.st = nil // we'll compute this on-demand.
|
||||
sim.head = head
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sim *Simulation) Name() string {
|
||||
return sim.name
|
||||
}
|
||||
|
||||
func (sim *Simulation) postChainCommitInfo(ctx context.Context, epoch abi.ChainEpoch) (abi.Randomness, error) {
|
||||
commitRand, err := sim.Chainstore.GetChainRandomness(
|
||||
ctx, sim.head.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true)
|
||||
return commitRand, err
|
||||
}
|
||||
|
||||
const beaconPrefix = "mockbeacon:"
|
||||
|
||||
func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry {
|
||||
parentBeacons := sim.head.Blocks()[0].BeaconEntries
|
||||
lastBeacon := parentBeacons[len(parentBeacons)-1]
|
||||
beaconRound := lastBeacon.Round + 1
|
||||
|
||||
buf := make([]byte, len(beaconPrefix)+8)
|
||||
copy(buf, beaconPrefix)
|
||||
binary.BigEndian.PutUint64(buf[len(beaconPrefix):], beaconRound)
|
||||
beaconRand := sha256.Sum256(buf)
|
||||
return []types.BeaconEntry{{
|
||||
Round: beaconRound,
|
||||
Data: beaconRand[:],
|
||||
}}
|
||||
}
|
||||
|
||||
func (sim *Simulation) nextTicket() *types.Ticket {
|
||||
newProof := sha256.Sum256(sim.head.MinTicket().VRFProof)
|
||||
return &types.Ticket{
|
||||
VRFProof: newProof[:],
|
||||
}
|
||||
}
|
||||
|
||||
func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) {
|
||||
parentTs := sim.head
|
||||
parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to compute parent tipset: %w", err)
|
||||
}
|
||||
msgsCid, err := sim.storeMessages(ctx, messages)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to store block messages: %w", err)
|
||||
}
|
||||
|
||||
uts := parentTs.MinTimestamp() + build.BlockDelaySecs
|
||||
|
||||
blks := []*types.BlockHeader{{
|
||||
Miner: parentTs.MinTicketBlock().Miner, // keep reusing the same miner.
|
||||
Ticket: sim.nextTicket(),
|
||||
BeaconEntries: sim.nextBeaconEntries(),
|
||||
Parents: parentTs.Cids(),
|
||||
Height: parentTs.Height() + 1,
|
||||
ParentStateRoot: parentState,
|
||||
ParentMessageReceipts: parentRec,
|
||||
Messages: msgsCid,
|
||||
ParentBaseFee: baseFee,
|
||||
Timestamp: uts,
|
||||
ElectionProof: &types.ElectionProof{WinCount: 1},
|
||||
}}
|
||||
err = sim.Chainstore.PersistBlockHeaders(blks...)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to persist block headers: %w", err)
|
||||
}
|
||||
newTipSet, err := types.NewTipSet(blks)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to create new tipset: %w", err)
|
||||
}
|
||||
now := time.Now()
|
||||
_, _, err = sim.sm.TipSetState(ctx, newTipSet)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to compute new tipset: %w", err)
|
||||
}
|
||||
duration := time.Since(now)
|
||||
log.Infow("computed tipset", "duration", duration, "height", newTipSet.Height())
|
||||
|
||||
return newTipSet, nil
|
||||
}
|
||||
|
||||
func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch) (_err error) {
|
||||
if epoch <= sim.head.Height() {
|
||||
return xerrors.Errorf("cannot set upgrade height in the past (%d <= %d)", epoch, sim.head.Height())
|
||||
}
|
||||
|
||||
if sim.config.Upgrades == nil {
|
||||
sim.config.Upgrades = make(map[network.Version]abi.ChainEpoch, 1)
|
||||
}
|
||||
|
||||
sim.config.Upgrades[nv] = epoch
|
||||
defer func() {
|
||||
if _err != nil {
|
||||
// try to restore the old config on error.
|
||||
_ = sim.loadConfig()
|
||||
}
|
||||
}()
|
||||
|
||||
newUpgradeSchedule, err := sim.config.upgradeSchedule()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Chainstore, newUpgradeSchedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = sim.saveConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sim.sm = sm
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sim *Simulation) saveConfig() error {
|
||||
buf, err := json.Marshal(sim.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sim.MetadataDS.Put(sim.key("config"), buf)
|
||||
}
|
190
cmd/lotus-sim/simulation/state.go
Normal file
190
cmd/lotus-sim/simulation/state.go
Normal file
@ -0,0 +1,190 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
type perm struct {
|
||||
miners []address.Address
|
||||
offset int
|
||||
}
|
||||
|
||||
func (p *perm) shuffle() {
|
||||
rand.Shuffle(len(p.miners), func(i, j int) {
|
||||
p.miners[i], p.miners[j] = p.miners[j], p.miners[i]
|
||||
})
|
||||
}
|
||||
|
||||
func (p *perm) next() address.Address {
|
||||
next := p.miners[p.offset]
|
||||
p.offset++
|
||||
p.offset %= len(p.miners)
|
||||
return next
|
||||
}
|
||||
|
||||
func (p *perm) add(addr address.Address) {
|
||||
p.miners = append(p.miners, addr)
|
||||
}
|
||||
|
||||
func (p *perm) len() int {
|
||||
return len(p.miners)
|
||||
}
|
||||
|
||||
type simulationState struct {
|
||||
*Simulation
|
||||
|
||||
// TODO Ideally we'd "learn" this distribution from the network. But this is good enough for
|
||||
// now. The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we
|
||||
// seal a group of sectors for the top 1%, a group (half that size) for the top 10%, and one
|
||||
// sector for everyone else. We really should pick a better algorithm.
|
||||
minerDist struct {
|
||||
top1, top10, rest perm
|
||||
}
|
||||
|
||||
// We track the window post periods per miner and assume that no new miners are ever added.
|
||||
wpostPeriods map[int][]address.Address // (epoch % (epochs in a deadline)) -> miner
|
||||
// We cache all miner infos for active miners and assume no new miners join.
|
||||
minerInfos map[address.Address]*miner.MinerInfo
|
||||
|
||||
// We record all pending window post messages, and the epoch up through which we've
|
||||
// generated window post messages.
|
||||
pendingWposts []*types.Message
|
||||
nextWpostEpoch abi.ChainEpoch
|
||||
|
||||
commitQueue commitQueue
|
||||
}
|
||||
|
||||
func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState, error) {
|
||||
state := &simulationState{Simulation: sim}
|
||||
currentEpoch := sim.head.Height()
|
||||
|
||||
// Lookup the current power table and the power table 2 weeks ago (for onboarding rate
|
||||
// projections).
|
||||
currentPowerTable, err := sim.loadClaims(ctx, currentEpoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var lookbackEpoch abi.ChainEpoch
|
||||
//if epoch > onboardingProjectionLookback {
|
||||
// lookbackEpoch = epoch - onboardingProjectionLookback
|
||||
//}
|
||||
// TODO: Fixme? I really want this to not suck with snapshots.
|
||||
lookbackEpoch = 770139 // hard coded for now.
|
||||
lookbackPowerTable, err := sim.loadClaims(ctx, lookbackEpoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now load miner state info.
|
||||
store := sim.Chainstore.ActorStore(ctx)
|
||||
st, err := sim.stateTree(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type onboardingInfo struct {
|
||||
addr address.Address
|
||||
onboardingRate uint64
|
||||
}
|
||||
|
||||
commitRand, err := sim.postChainCommitInfo(ctx, currentEpoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sealList := make([]onboardingInfo, 0, len(currentPowerTable))
|
||||
state.wpostPeriods = make(map[int][]address.Address, miner.WPoStChallengeWindow)
|
||||
state.minerInfos = make(map[address.Address]*miner.MinerInfo, len(currentPowerTable))
|
||||
state.commitQueue.advanceEpoch(state.nextEpoch())
|
||||
for addr, claim := range currentPowerTable {
|
||||
// Load the miner state.
|
||||
minerActor, err := st.GetActor(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
minerState, err := miner.Load(store, minerActor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := minerState.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state.minerInfos[addr] = &info
|
||||
|
||||
// Queue up PoSts
|
||||
err = state.stepWindowPoStsMiner(ctx, addr, minerState, currentEpoch, commitRand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Qeueu up any pending prove commits.
|
||||
err = state.loadProveCommitsMiner(ctx, addr, minerState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Record when we need to prove for this miner.
|
||||
dinfo, err := minerState.DeadlineInfo(state.nextEpoch())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dinfo = dinfo.NextNotElapsed()
|
||||
|
||||
ppOffset := int(dinfo.PeriodStart % miner.WPoStChallengeWindow)
|
||||
state.wpostPeriods[ppOffset] = append(state.wpostPeriods[ppOffset], addr)
|
||||
|
||||
sectorsAdded := sectorsFromClaim(info.SectorSize, claim)
|
||||
if lookbackClaim, ok := lookbackPowerTable[addr]; !ok {
|
||||
sectorsAdded -= sectorsFromClaim(info.SectorSize, lookbackClaim)
|
||||
}
|
||||
|
||||
// NOTE: power _could_ have been lost, but that's too much of a pain to care
|
||||
// about. We _could_ look for faulty power by iterating through all
|
||||
// deadlines, but I'd rather not.
|
||||
if sectorsAdded > 0 {
|
||||
sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)})
|
||||
}
|
||||
}
|
||||
// We're already done loading for the _next_ epoch.
|
||||
// Next time, we need to load for the next, next epoch.
|
||||
// TODO: fix this insanity.
|
||||
state.nextWpostEpoch = state.nextEpoch() + 1
|
||||
|
||||
// Now that we have a list of sealing miners, sort them into percentiles.
|
||||
sort.Slice(sealList, func(i, j int) bool {
|
||||
return sealList[i].onboardingRate < sealList[j].onboardingRate
|
||||
})
|
||||
|
||||
for i, oi := range sealList {
|
||||
var dist *perm
|
||||
if i < len(sealList)/100 {
|
||||
dist = &state.minerDist.top1
|
||||
} else if i < len(sealList)/10 {
|
||||
dist = &state.minerDist.top10
|
||||
} else {
|
||||
dist = &state.minerDist.rest
|
||||
}
|
||||
dist.add(oi.addr)
|
||||
}
|
||||
|
||||
state.minerDist.top1.shuffle()
|
||||
state.minerDist.top10.shuffle()
|
||||
state.minerDist.rest.shuffle()
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (ss *simulationState) nextEpoch() abi.ChainEpoch {
|
||||
return ss.GetHead().Height() + 1
|
||||
}
|
196
cmd/lotus-sim/simulation/step.go
Normal file
196
cmd/lotus-sim/simulation/step.go
Normal file
@ -0,0 +1,196 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/account"
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"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/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
expectedBlocks = 5
|
||||
// TODO: This will produce invalid blocks but it will accurately model the amount of gas
|
||||
// we're willing to use per-tipset.
|
||||
// A more correct approach would be to produce 5 blocks. We can do that later.
|
||||
targetGas = build.BlockGasTarget * expectedBlocks
|
||||
)
|
||||
|
||||
var baseFee = abi.NewTokenAmount(0)
|
||||
|
||||
// Step steps the simulation forward one step. This may move forward by more than one epoch.
|
||||
func (sim *Simulation) Step(ctx context.Context) (*types.TipSet, error) {
|
||||
state, err := sim.simState(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts, err := state.step(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to step simulation: %w", err)
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) {
|
||||
log.Infow("step", "epoch", ss.head.Height()+1)
|
||||
messages, err := ss.popNextMessages(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to select messages for block: %w", err)
|
||||
}
|
||||
head, err := ss.makeTipSet(ctx, messages)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to make tipset: %w", err)
|
||||
}
|
||||
if err := ss.SetHead(head); err != nil {
|
||||
return nil, xerrors.Errorf("failed to update head: %w", err)
|
||||
}
|
||||
return head, nil
|
||||
}
|
||||
|
||||
type packFunc func(*types.Message) (full bool, err error)
|
||||
type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error)
|
||||
|
||||
func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Message, error) {
|
||||
parentTs := ss.head
|
||||
parentState, _, err := ss.sm.TipSetState(ctx, parentTs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextHeight := parentTs.Height() + 1
|
||||
prevVer := ss.sm.GetNtwkVersion(ctx, nextHeight-1)
|
||||
nextVer := ss.sm.GetNtwkVersion(ctx, nextHeight)
|
||||
if nextVer != prevVer {
|
||||
// So... we _could_ actually run the migration, but that's a pain. It's easier to
|
||||
// just have an empty block then let the state manager run the migration as normal.
|
||||
log.Warnw("packing no messages for version upgrade block",
|
||||
"old", prevVer,
|
||||
"new", nextVer,
|
||||
"epoch", nextHeight,
|
||||
)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Then we need to execute messages till we run out of gas. Those messages will become the
|
||||
// block's messages.
|
||||
r := store.NewChainRand(ss.sm.ChainStore(), parentTs.Cids())
|
||||
// TODO: Factor this out maybe?
|
||||
vmopt := &vm.VMOpts{
|
||||
StateBase: parentState,
|
||||
Epoch: nextHeight,
|
||||
Rand: r,
|
||||
Bstore: ss.sm.ChainStore().StateBlockstore(),
|
||||
Syscalls: ss.sm.ChainStore().VMSys(),
|
||||
CircSupplyCalc: ss.sm.GetVMCirculatingSupply,
|
||||
NtwkVersion: ss.sm.GetNtwkVersion,
|
||||
BaseFee: abi.NewTokenAmount(0), // FREE!
|
||||
LookbackState: stmgr.LookbackStateGetterForTipset(ss.sm, parentTs),
|
||||
}
|
||||
vmi, err := vm.NewVM(ctx, vmopt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: This is the wrong store and may not include important state for what we're doing
|
||||
// here....
|
||||
// Maybe we just track nonces separately? Yeah, probably better that way.
|
||||
vmStore := vmi.ActorStore(ctx)
|
||||
var gasTotal int64
|
||||
var messages []*types.Message
|
||||
tryPushMsg := func(msg *types.Message) (bool, error) {
|
||||
if gasTotal >= targetGas {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Copy the message before we start mutating it.
|
||||
msgCpy := *msg
|
||||
msg = &msgCpy
|
||||
st := vmi.StateTree().(*state.StateTree)
|
||||
|
||||
actor, err := st.GetActor(msg.From)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
msg.Nonce = actor.Nonce
|
||||
if msg.From.Protocol() == address.ID {
|
||||
state, err := account.Load(vmStore, actor)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
msg.From, err = state.PubkeyAddress()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Our gas estimation is broken for payment channels due to horrible hacks in
|
||||
// gasEstimateGasLimit.
|
||||
if msg.Value == types.EmptyInt {
|
||||
msg.Value = abi.NewTokenAmount(0)
|
||||
}
|
||||
msg.GasPremium = abi.NewTokenAmount(0)
|
||||
msg.GasFeeCap = abi.NewTokenAmount(0)
|
||||
msg.GasLimit = build.BlockGasLimit
|
||||
|
||||
// We manually snapshot so we can revert nonce changes, etc. on failure.
|
||||
st.Snapshot(ctx)
|
||||
defer st.ClearSnapshot()
|
||||
|
||||
ret, err := vmi.ApplyMessage(ctx, msg)
|
||||
if err != nil {
|
||||
_ = st.Revert()
|
||||
return false, err
|
||||
}
|
||||
if ret.ActorErr != nil {
|
||||
_ = st.Revert()
|
||||
return false, ret.ActorErr
|
||||
}
|
||||
|
||||
// Sometimes there are bugs. Let's catch them.
|
||||
if ret.GasUsed == 0 {
|
||||
_ = st.Revert()
|
||||
return false, xerrors.Errorf("used no gas",
|
||||
"msg", msg,
|
||||
"ret", ret,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: consider applying overestimation? We're likely going to "over pack" here by
|
||||
// ~25% because we're too accurate.
|
||||
|
||||
// Did we go over? Yes, revert.
|
||||
newTotal := gasTotal + ret.GasUsed
|
||||
if newTotal > targetGas {
|
||||
_ = st.Revert()
|
||||
return true, nil
|
||||
}
|
||||
gasTotal = newTotal
|
||||
|
||||
// Update the gas limit.
|
||||
msg.GasLimit = ret.GasUsed
|
||||
|
||||
messages = append(messages, msg)
|
||||
return false, nil
|
||||
}
|
||||
for _, mgen := range []messageGenerator{ss.packWindowPoSts, ss.packProveCommits, ss.packPreCommits} {
|
||||
if full, err := mgen(ctx, tryPushMsg); err != nil {
|
||||
name := runtime.FuncForPC(reflect.ValueOf(mgen).Pointer()).Name()
|
||||
lastDot := strings.LastIndexByte(name, '.')
|
||||
fName := name[lastDot+1 : len(name)-3]
|
||||
return nil, xerrors.Errorf("when packing messages with %s: %w", fName, err)
|
||||
} else if full {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
253
cmd/lotus-sim/simulation/wdpost.go
Normal file
253
cmd/lotus-sim/simulation/wdpost.go
Normal file
@ -0,0 +1,253 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Address) (*miner.MinerInfo, error) {
|
||||
minerInfo, ok := ss.minerInfos[addr]
|
||||
if !ok {
|
||||
st, err := ss.stateTree(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
act, err := st.GetActor(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := minerState.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minerInfo = &info
|
||||
ss.minerInfos[addr] = minerInfo
|
||||
}
|
||||
return minerInfo, nil
|
||||
}
|
||||
|
||||
func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (full bool, _err error) {
|
||||
// Push any new window posts into the queue.
|
||||
if err := ss.queueWindowPoSts(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
done := 0
|
||||
failed := 0
|
||||
defer func() {
|
||||
if _err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugw("packed window posts",
|
||||
"epoch", ss.nextEpoch(),
|
||||
"done", done,
|
||||
"failed", failed,
|
||||
"remaining", len(ss.pendingWposts),
|
||||
)
|
||||
}()
|
||||
// Then pack as many as we can.
|
||||
for len(ss.pendingWposts) > 0 {
|
||||
next := ss.pendingWposts[0]
|
||||
if full, err := cb(next); err != nil {
|
||||
if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() {
|
||||
return false, err
|
||||
}
|
||||
log.Errorw("failed to submit windowed post",
|
||||
"error", err,
|
||||
"miner", next.To,
|
||||
"epoch", ss.nextEpoch(),
|
||||
)
|
||||
failed++
|
||||
} else if full {
|
||||
return true, nil
|
||||
} else {
|
||||
done++
|
||||
}
|
||||
|
||||
ss.pendingWposts = ss.pendingWposts[1:]
|
||||
}
|
||||
ss.pendingWposts = nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Enqueue all missing window posts for the current epoch for the given miner.
|
||||
func (ss *simulationState) stepWindowPoStsMiner(
|
||||
ctx context.Context,
|
||||
addr address.Address, minerState miner.State,
|
||||
commitEpoch abi.ChainEpoch, commitRand abi.Randomness,
|
||||
) error {
|
||||
|
||||
if active, err := minerState.DeadlineCronActive(); err != nil {
|
||||
return err
|
||||
} else if !active {
|
||||
return nil
|
||||
}
|
||||
|
||||
minerInfo, err := ss.getMinerInfo(ctx, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
di, err := minerState.DeadlineInfo(ss.nextEpoch())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
di = di.NextNotElapsed()
|
||||
|
||||
dl, err := minerState.LoadDeadline(di.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provenBf, err := dl.PartitionsPoSted()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proven, err := provenBf.AllMap(math.MaxUint64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
partitions []miner.PoStPartition
|
||||
partitionGroups [][]miner.PoStPartition
|
||||
)
|
||||
// Only prove partitions with live sectors.
|
||||
err = dl.ForEachPartition(func(idx uint64, part miner.Partition) error {
|
||||
if proven[idx] {
|
||||
return nil
|
||||
}
|
||||
// TODO: set this to the actual limit from specs-actors.
|
||||
// NOTE: We're mimicing the behavior of wdpost_run.go here.
|
||||
if len(partitions) > 0 && idx%4 == 0 {
|
||||
partitionGroups = append(partitionGroups, partitions)
|
||||
partitions = nil
|
||||
|
||||
}
|
||||
live, err := part.LiveSectors()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
liveCount, err := live.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
faulty, err := part.FaultySectors()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
faultyCount, err := faulty.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if liveCount-faultyCount > 0 {
|
||||
partitions = append(partitions, miner.PoStPartition{Index: idx})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(partitions) > 0 {
|
||||
partitionGroups = append(partitionGroups, partitions)
|
||||
partitions = nil
|
||||
}
|
||||
|
||||
proof, err := mockWpostProof(minerInfo.WindowPoStProofType, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, group := range partitionGroups {
|
||||
params := miner.SubmitWindowedPoStParams{
|
||||
Deadline: di.Index,
|
||||
Partitions: group,
|
||||
Proofs: []proof5.PoStProof{{
|
||||
PoStProof: minerInfo.WindowPoStProofType,
|
||||
ProofBytes: proof,
|
||||
}},
|
||||
ChainCommitEpoch: commitEpoch,
|
||||
ChainCommitRand: commitRand,
|
||||
}
|
||||
enc, aerr := actors.SerializeParams(¶ms)
|
||||
if aerr != nil {
|
||||
return xerrors.Errorf("could not serialize submit window post parameters: %w", aerr)
|
||||
}
|
||||
msg := &types.Message{
|
||||
To: addr,
|
||||
From: minerInfo.Worker,
|
||||
Method: miner.Methods.SubmitWindowedPoSt,
|
||||
Params: enc,
|
||||
Value: types.NewInt(0),
|
||||
}
|
||||
ss.pendingWposts = append(ss.pendingWposts, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enqueue missing window posts for all miners with deadlines opening at the current epoch.
|
||||
func (ss *simulationState) queueWindowPoSts(ctx context.Context) error {
|
||||
targetHeight := ss.nextEpoch()
|
||||
|
||||
st, err := ss.stateTree(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
was := len(ss.pendingWposts)
|
||||
count := 0
|
||||
defer func() {
|
||||
log.Debugw("computed window posts",
|
||||
"miners", count,
|
||||
"count", len(ss.pendingWposts)-was,
|
||||
"duration", time.Since(now),
|
||||
)
|
||||
}()
|
||||
|
||||
// Perform a bit of catch up. This lets us do things like skip blocks at upgrades then catch
|
||||
// up to make the simualtion easier.
|
||||
for ; ss.nextWpostEpoch <= targetHeight; ss.nextWpostEpoch++ {
|
||||
if ss.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight {
|
||||
log.Warnw("skipping old window post", "epoch", ss.nextWpostEpoch)
|
||||
continue
|
||||
}
|
||||
commitEpoch := ss.nextWpostEpoch - 1
|
||||
commitRand, err := ss.postChainCommitInfo(ctx, commitEpoch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
store := ss.Chainstore.ActorStore(ctx)
|
||||
|
||||
for _, addr := range ss.wpostPeriods[int(ss.nextWpostEpoch%miner.WPoStChallengeWindow)] {
|
||||
minerActor, err := st.GetActor(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
minerState, err := miner.Load(store, minerActor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ss.stepWindowPoStsMiner(ctx, addr, minerState, commitEpoch, commitRand); err != nil {
|
||||
return err
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
46
cmd/lotus-sim/step.go
Normal file
46
cmd/lotus-sim/step.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var stepSimCommand = &cli.Command{
|
||||
Name: "step",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "epochs",
|
||||
Usage: "Advance at least the given number of epochs.",
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
node, err := open(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer node.Close()
|
||||
|
||||
sim, err := node.LoadSim(cctx.Context, cctx.String("simulation"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(cctx.App.Writer, "loading simulation")
|
||||
err = sim.Load(cctx.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(cctx.App.Writer, "running simulation")
|
||||
targetEpochs := cctx.Int("epochs")
|
||||
for i := 0; i < targetEpochs; i++ {
|
||||
ts, err := sim.Step(cctx.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cctx.App.Writer, "advanced to %d %s\n", ts.Height(), ts.Key())
|
||||
}
|
||||
fmt.Fprintln(cctx.App.Writer, "simulation done")
|
||||
return err
|
||||
},
|
||||
}
|
53
cmd/lotus-sim/upgrade.go
Normal file
53
cmd/lotus-sim/upgrade.go
Normal file
@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var setUpgradeCommand = &cli.Command{
|
||||
Name: "set-upgrade",
|
||||
ArgsUsage: "<network-version> [+]<epochs>",
|
||||
Description: "Set a network upgrade height. prefix with '+' to set it relative to the last epoch.",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
args := cctx.Args()
|
||||
if args.Len() != 2 {
|
||||
return fmt.Errorf("expected 2 arguments")
|
||||
}
|
||||
nvString := args.Get(0)
|
||||
networkVersion, err := strconv.ParseInt(nvString, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse network version %q: %w", nvString, err)
|
||||
}
|
||||
heightString := args.Get(1)
|
||||
relative := false
|
||||
if strings.HasPrefix(heightString, "+") {
|
||||
heightString = heightString[1:]
|
||||
relative = true
|
||||
}
|
||||
height, err := strconv.ParseInt(heightString, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse height version %q: %w", heightString, err)
|
||||
}
|
||||
|
||||
node, err := open(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer node.Close()
|
||||
|
||||
sim, err := node.LoadSim(cctx.Context, cctx.String("simulation"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relative {
|
||||
height += int64(sim.GetHead().Height())
|
||||
}
|
||||
return sim.SetUpgradeHeight(network.Version(networkVersion), abi.ChainEpoch(height))
|
||||
},
|
||||
}
|
18
cmd/lotus-sim/util.go
Normal file
18
cmd/lotus-sim/util.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/lotus/cmd/lotus-sim/simulation"
|
||||
"github.com/filecoin-project/lotus/lib/ulimit"
|
||||
)
|
||||
|
||||
func open(cctx *cli.Context) (*simulation.Node, error) {
|
||||
_, _, err := ulimit.ManageFdLimit()
|
||||
if err != nil {
|
||||
fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to raise ulimit: %s\n", err)
|
||||
}
|
||||
return simulation.OpenNode(cctx.Context, cctx.String("repo"))
|
||||
}
|
Loading…
Reference in New Issue
Block a user