From e2f5c494b009c3b8677f1f9e951d42f1a6375470 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 18 May 2021 17:01:30 -0700 Subject: [PATCH 01/94] feat: implement lotus-sim --- chain/actors/builtin/miner/actor.go.template | 4 + chain/actors/builtin/miner/miner.go | 4 + chain/actors/builtin/miner/state.go.template | 67 ++++- chain/actors/builtin/miner/v0.go | 63 ++++- chain/actors/builtin/miner/v2.go | 63 ++++- chain/actors/builtin/miner/v3.go | 63 ++++- chain/actors/builtin/miner/v4.go | 63 ++++- chain/actors/builtin/miner/v5.go | 63 ++++- chain/vm/vm.go | 6 + cmd/lotus-sim/create.go | 44 +++ cmd/lotus-sim/delete.go | 18 ++ cmd/lotus-sim/list.go | 35 +++ cmd/lotus-sim/main.go | 87 ++++++ cmd/lotus-sim/simulation/commit_queue.go | 187 +++++++++++++ cmd/lotus-sim/simulation/messages.go | 81 ++++++ cmd/lotus-sim/simulation/mock.go | 136 +++++++++ cmd/lotus-sim/simulation/node.go | 167 +++++++++++ cmd/lotus-sim/simulation/power.go | 58 ++++ cmd/lotus-sim/simulation/precommit.go | 205 ++++++++++++++ cmd/lotus-sim/simulation/provecommit.go | 219 +++++++++++++++ cmd/lotus-sim/simulation/simulation.go | 274 +++++++++++++++++++ cmd/lotus-sim/simulation/state.go | 190 +++++++++++++ cmd/lotus-sim/simulation/step.go | 196 +++++++++++++ cmd/lotus-sim/simulation/wdpost.go | 253 +++++++++++++++++ cmd/lotus-sim/step.go | 46 ++++ cmd/lotus-sim/upgrade.go | 53 ++++ cmd/lotus-sim/util.go | 18 ++ 27 files changed, 2651 insertions(+), 12 deletions(-) create mode 100644 cmd/lotus-sim/create.go create mode 100644 cmd/lotus-sim/delete.go create mode 100644 cmd/lotus-sim/list.go create mode 100644 cmd/lotus-sim/main.go create mode 100644 cmd/lotus-sim/simulation/commit_queue.go create mode 100644 cmd/lotus-sim/simulation/messages.go create mode 100644 cmd/lotus-sim/simulation/mock.go create mode 100644 cmd/lotus-sim/simulation/node.go create mode 100644 cmd/lotus-sim/simulation/power.go create mode 100644 cmd/lotus-sim/simulation/precommit.go create mode 100644 cmd/lotus-sim/simulation/provecommit.go create mode 100644 cmd/lotus-sim/simulation/simulation.go create mode 100644 cmd/lotus-sim/simulation/state.go create mode 100644 cmd/lotus-sim/simulation/step.go create mode 100644 cmd/lotus-sim/simulation/wdpost.go create mode 100644 cmd/lotus-sim/step.go create mode 100644 cmd/lotus-sim/upgrade.go create mode 100644 cmd/lotus-sim/util.go diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 619dc699d..8c0b10cb0 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -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) diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 6e35d4e9f..bb7f80340 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -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) diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template index b7e5f40df..eb7ab00bf 100644 --- a/chain/actors/builtin/miner/state.go.template +++ b/chain/actors/builtin/miner/state.go.template @@ -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 { diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index 344be1993..c5e887481 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -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 { diff --git a/chain/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go index 3e76d0b69..45d4a7165 100644 --- a/chain/actors/builtin/miner/v2.go +++ b/chain/actors/builtin/miner/v2.go @@ -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 { diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go index 72986233d..166abe1e7 100644 --- a/chain/actors/builtin/miner/v3.go +++ b/chain/actors/builtin/miner/v3.go @@ -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 { diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go index 96ed21f04..71a2b9f9d 100644 --- a/chain/actors/builtin/miner/v4.go +++ b/chain/actors/builtin/miner/v4.go @@ -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 { diff --git a/chain/actors/builtin/miner/v5.go b/chain/actors/builtin/miner/v5.go index 7996acf32..568834777 100644 --- a/chain/actors/builtin/miner/v5.go +++ b/chain/actors/builtin/miner/v5.go @@ -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 { diff --git a/chain/vm/vm.go b/chain/vm/vm.go index c5bfffc7f..1b8424eee 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -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: diff --git a/cmd/lotus-sim/create.go b/cmd/lotus-sim/create.go new file mode 100644 index 000000000..777f1723c --- /dev/null +++ b/cmd/lotus-sim/create.go @@ -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 + }, +} diff --git a/cmd/lotus-sim/delete.go b/cmd/lotus-sim/delete.go new file mode 100644 index 000000000..472a35a86 --- /dev/null +++ b/cmd/lotus-sim/delete.go @@ -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")) + }, +} diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go new file mode 100644 index 000000000..69809b188 --- /dev/null +++ b/cmd/lotus-sim/list.go @@ -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() + }, +} diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go new file mode 100644 index 000000000..9a4d40699 --- /dev/null +++ b/cmd/lotus-sim/main.go @@ -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 +} diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go new file mode 100644 index 000000000..957d301cf --- /dev/null +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -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 +} diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go new file mode 100644 index 000000000..76b100d75 --- /dev/null +++ b/cmd/lotus-sim/simulation/messages.go @@ -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 +} diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock.go new file mode 100644 index 000000000..e8a7b2d4a --- /dev/null +++ b/cmd/lotus-sim/simulation/mock.go @@ -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") +} diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go new file mode 100644 index 000000000..505f563e9 --- /dev/null +++ b/cmd/lotus-sim/simulation/node.go @@ -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 +} diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go new file mode 100644 index 000000000..9a64c3f3a --- /dev/null +++ b/cmd/lotus-sim/simulation/power.go @@ -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() +} diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go new file mode 100644 index 000000000..1ede3d5c4 --- /dev/null +++ b/cmd/lotus-sim/simulation/precommit.go @@ -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 +} diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go new file mode 100644 index 000000000..0d855bcd1 --- /dev/null +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -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) + }) +} diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go new file mode 100644 index 000000000..ac205e1c3 --- /dev/null +++ b/cmd/lotus-sim/simulation/simulation.go @@ -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) +} diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go new file mode 100644 index 000000000..ee664166e --- /dev/null +++ b/cmd/lotus-sim/simulation/state.go @@ -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 +} diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go new file mode 100644 index 000000000..b44f3be4d --- /dev/null +++ b/cmd/lotus-sim/simulation/step.go @@ -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 +} diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go new file mode 100644 index 000000000..7abb9a83a --- /dev/null +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -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 +} diff --git a/cmd/lotus-sim/step.go b/cmd/lotus-sim/step.go new file mode 100644 index 000000000..c2dc3f9e2 --- /dev/null +++ b/cmd/lotus-sim/step.go @@ -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 + }, +} diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go new file mode 100644 index 000000000..9fd25cb7d --- /dev/null +++ b/cmd/lotus-sim/upgrade.go @@ -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: " [+]", + 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)) + }, +} diff --git a/cmd/lotus-sim/util.go b/cmd/lotus-sim/util.go new file mode 100644 index 000000000..cd15cca0d --- /dev/null +++ b/cmd/lotus-sim/util.go @@ -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")) +} From 8000decac6f499e127ff1d081dec08a5a43e9844 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 14:54:20 -0700 Subject: [PATCH 02/94] feat(lotus-sim): add command to list pending upgrades --- cmd/lotus-sim/main.go | 2 +- cmd/lotus-sim/simulation/simulation.go | 15 ++++++++ cmd/lotus-sim/upgrade.go | 52 ++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 9a4d40699..53e7d660a 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -16,7 +16,7 @@ var root []*cli.Command = []*cli.Command{ deleteSimCommand, listSimCommand, stepSimCommand, - setUpgradeCommand, + upgradeCommand, } func main() { diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index ac205e1c3..8fffd9868 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -265,6 +265,21 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch return nil } +func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { + upgrades, err := sim.config.upgradeSchedule() + if err != nil { + return nil, err + } + var pending stmgr.UpgradeSchedule + for _, upgrade := range upgrades { + if upgrade.Height < sim.head.Height() { + continue + } + pending = append(pending, upgrade) + } + return pending, nil +} + func (sim *Simulation) saveConfig() error { buf, err := json.Marshal(sim.config) if err != nil { diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 9fd25cb7d..17993f847 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -4,16 +4,62 @@ import ( "fmt" "strconv" "strings" + "text/tabwriter" "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", +var upgradeCommand = &cli.Command{ + Name: "upgrade", + Description: "Modifies network upgrade heights.", + Subcommands: []*cli.Command{ + upgradeSetCommand, + }, +} + +var upgradeList = &cli.Command{ + Name: "list", + Description: "Lists all pending upgrades.", + Subcommands: []*cli.Command{ + upgradeSetCommand, + }, + 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 + } + upgrades, err := sim.ListUpgrades() + if err != nil { + return err + } + + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) + fmt.Fprintf(tw, "version\theight\tepochs\tmigration\texpensive") + epoch := sim.GetHead().Height() + for _, upgrade := range upgrades { + fmt.Fprintf( + tw, "%d\t%d\t%+d\t%t\t%t", + upgrade.Network, upgrade.Height, upgrade.Height-epoch, + upgrade.Migration != nil, + upgrade.Expensive, + ) + } + return nil + }, +} + +var upgradeSetCommand = &cli.Command{ + Name: "set", ArgsUsage: " [+]", - Description: "Set a network upgrade height. prefix with '+' to set it relative to the last epoch.", + 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 { From be9e30e39d8be65a6a7b8f2319d15a9d3d05350e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 14:55:39 -0700 Subject: [PATCH 03/94] fix(lotus-sim): rename step to run And make it run forever by default. --- cmd/lotus-sim/main.go | 2 +- cmd/lotus-sim/{step.go => run.go} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename cmd/lotus-sim/{step.go => run.go} (79%) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 53e7d660a..dfd7c92cd 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -15,7 +15,7 @@ var root []*cli.Command = []*cli.Command{ createSimCommand, deleteSimCommand, listSimCommand, - stepSimCommand, + runSimCommand, upgradeCommand, } diff --git a/cmd/lotus-sim/step.go b/cmd/lotus-sim/run.go similarity index 79% rename from cmd/lotus-sim/step.go rename to cmd/lotus-sim/run.go index c2dc3f9e2..479ce898d 100644 --- a/cmd/lotus-sim/step.go +++ b/cmd/lotus-sim/run.go @@ -6,13 +6,13 @@ import ( "github.com/urfave/cli/v2" ) -var stepSimCommand = &cli.Command{ - Name: "step", +var runSimCommand = &cli.Command{ + Name: "run", + Description: "Run the simulation.", Flags: []cli.Flag{ &cli.IntFlag{ Name: "epochs", - Usage: "Advance at least the given number of epochs.", - Value: 1, + Usage: "Advance the given number of epochs then stop.", }, }, Action: func(cctx *cli.Context) error { @@ -33,7 +33,7 @@ var stepSimCommand = &cli.Command{ } fmt.Fprintln(cctx.App.Writer, "running simulation") targetEpochs := cctx.Int("epochs") - for i := 0; i < targetEpochs; i++ { + for i := 0; targetEpochs == 0 || i < targetEpochs; i++ { ts, err := sim.Step(cctx.Context) if err != nil { return err From b7bfc06ebe3d1bad1c7fa3ca8a7a81b00ea956c0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 15:05:52 -0700 Subject: [PATCH 04/94] feat(lotus-sim): add an info command --- cmd/lotus-sim/main.go | 1 + cmd/lotus-sim/simulation/simulation.go | 4 ++++ cmd/lotus-sim/stat.go | 31 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 cmd/lotus-sim/stat.go diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index dfd7c92cd..8fe313355 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -16,6 +16,7 @@ var root []*cli.Command = []*cli.Command{ deleteSimCommand, listSimCommand, runSimCommand, + infoSimCommand, upgradeCommand, } diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 8fffd9868..384cc79cb 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -143,6 +143,10 @@ func (sim *Simulation) GetHead() *types.TipSet { return sim.head } +func (sim *Simulation) GetNetworkVersion() network.Version { + return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) +} + 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) diff --git a/cmd/lotus-sim/stat.go b/cmd/lotus-sim/stat.go new file mode 100644 index 000000000..25f9f5d51 --- /dev/null +++ b/cmd/lotus-sim/stat.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "text/tabwriter" + + "github.com/urfave/cli/v2" +) + +var infoSimCommand = &cli.Command{ + Name: "info", + Description: "Output information about the simulation.", + 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 + } + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) + fmt.Fprintln(tw, "Name:\t", sim.Name()) + fmt.Fprintln(tw, "Height:\t", sim.GetHead().Height()) + fmt.Fprintln(tw, "TipSet:\t", sim.GetHead()) + fmt.Fprintln(tw, "Network Version:\t", sim.GetNetworkVersion()) + return tw.Flush() + }, +} From 2f7d7aed31f95eff47e7e14111dd517baa6947fe Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 17:45:53 -0700 Subject: [PATCH 05/94] feat(lotus-sim): refactor and document Hopefully, this'll make this code a bit easier to approach. --- cmd/lotus-sim/simulation/actor_iter.go | 38 ++++++ cmd/lotus-sim/simulation/block.go | 81 +++++++++++++ cmd/lotus-sim/simulation/commit_queue.go | 24 ++-- cmd/lotus-sim/simulation/messages.go | 3 + cmd/lotus-sim/simulation/mock.go | 141 ++++++++++++----------- cmd/lotus-sim/simulation/node.go | 12 ++ cmd/lotus-sim/simulation/power.go | 4 - cmd/lotus-sim/simulation/precommit.go | 26 ++++- cmd/lotus-sim/simulation/provecommit.go | 21 +++- cmd/lotus-sim/simulation/simulation.go | 129 +++++++-------------- cmd/lotus-sim/simulation/state.go | 73 +++++++----- cmd/lotus-sim/simulation/step.go | 104 +++++++++++++---- cmd/lotus-sim/simulation/wdpost.go | 41 +++---- 13 files changed, 446 insertions(+), 251 deletions(-) create mode 100644 cmd/lotus-sim/simulation/actor_iter.go create mode 100644 cmd/lotus-sim/simulation/block.go diff --git a/cmd/lotus-sim/simulation/actor_iter.go b/cmd/lotus-sim/simulation/actor_iter.go new file mode 100644 index 000000000..5df395e11 --- /dev/null +++ b/cmd/lotus-sim/simulation/actor_iter.go @@ -0,0 +1,38 @@ +package simulation + +import ( + "math/rand" + + "github.com/filecoin-project/go-address" +) + +// actorIter is a simple persistent iterator that loops over a set of actors. +type actorIter struct { + actors []address.Address + offset int +} + +// shuffle randomly permutes the set of actors. +func (p *actorIter) shuffle() { + rand.Shuffle(len(p.actors), func(i, j int) { + p.actors[i], p.actors[j] = p.actors[j], p.actors[i] + }) +} + +// next returns the next actor's address and advances the iterator. +func (p *actorIter) next() address.Address { + next := p.actors[p.offset] + p.offset++ + p.offset %= len(p.actors) + return next +} + +// add adds a new actor to the iterator. +func (p *actorIter) add(addr address.Address) { + p.actors = append(p.actors, addr) +} + +// len returns the number of actors in the iterator. +func (p *actorIter) len() int { + return len(p.actors) +} diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go new file mode 100644 index 000000000..677ba7a2f --- /dev/null +++ b/cmd/lotus-sim/simulation/block.go @@ -0,0 +1,81 @@ +package simulation + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "time" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "golang.org/x/xerrors" +) + +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 +} diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go index 957d301cf..63a478120 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -9,9 +9,13 @@ import ( "github.com/filecoin-project/lotus/chain/actors/policy" ) +// pendingCommitTracker tracks pending commits per-miner for a single epohc. type pendingCommitTracker map[address.Address]minerPendingCommits + +// minerPendingCommits tracks a miner's pending commits during a single epoch (grouped by seal proof type). type minerPendingCommits map[abi.RegisteredSealProof][]abi.SectorNumber +// finish markes count sectors of the given proof type as "prove-committed". func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { snos := m[proof] if len(snos) < count { @@ -23,10 +27,12 @@ func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { } } +// empty returns true if there are no pending commits. func (m minerPendingCommits) empty() bool { return len(m) == 0 } +// count returns the number of pending commits. func (m minerPendingCommits) count() int { count := 0 for _, snos := range m { @@ -35,12 +41,17 @@ func (m minerPendingCommits) count() int { return count } +// commitQueue is used to track pending prove-commits. +// +// Miners are processed in round-robin where _all_ commits from a given miner are finished before +// moving on to the next. This is designed to maximize batching. type commitQueue struct { minerQueue []address.Address queue []pendingCommitTracker offset abi.ChainEpoch } +// ready returns the number of prove-commits ready to be proven at the current epoch. Useful for logging. func (q *commitQueue) ready() int { if len(q.queue) == 0 { return 0 @@ -52,6 +63,9 @@ func (q *commitQueue) ready() int { return count } +// nextMiner returns the next miner to be proved and the set of pending prove commits for that +// miner. When some number of sectors have successfully been proven, call "finish" so we don't try +// to prove them again. func (q *commitQueue) nextMiner() (address.Address, minerPendingCommits, bool) { if len(q.queue) == 0 { return address.Undef, nil, false @@ -72,6 +86,8 @@ func (q *commitQueue) nextMiner() (address.Address, minerPendingCommits, bool) { return address.Undef, nil, false } +// advanceEpoch will advance to the next epoch. If some sectors were left unproven in the current +// epoch, they will be "prepended" into the next epochs sector set. func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { if epoch < q.offset { panic("cannot roll epoch backwards") @@ -146,6 +162,7 @@ func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { }) } +// enquueProveCommit enqueues prove-commit for the given pre-commit for the given miner. 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() @@ -178,10 +195,3 @@ func (q *commitQueue) enqueueProveCommit(addr address.Address, preCommitEpoch ab 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 -} diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 76b100d75..3f1c55179 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -15,6 +15,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) +// toArray converts the given set of CIDs to an AMT. This is usually used to pack messages into blocks. func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { arr := blockadt.MakeEmptyArray(store) for i, c := range cids { @@ -26,6 +27,8 @@ func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { return arr.Root() } +// storeMessages packs a set of messages into a types.MsgMeta and returns the resulting CID. The +// resulting CID is valid for the BlocKHeader's Messages field. func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { var blsMessages, sekpMessages []cid.Cid fakeSig := make([]byte, 32) diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock.go index e8a7b2d4a..b81ee8629 100644 --- a/cmd/lotus-sim/simulation/mock.go +++ b/cmd/lotus-sim/simulation/mock.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" ) @@ -21,74 +22,11 @@ const ( 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") - } -} - +// mockVerifier is a simple mock for verifying "fake" proofs. type mockVerifier struct{} +var _ ffiwrapper.Verifier = mockVerifier{} + func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) { addr, err := address.NewIDAddress(uint64(proof.Miner)) if err != nil { @@ -134,3 +72,74 @@ func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoSt func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) { panic("should not be called") } + +// mockSealProof generates a mock "seal" proof tied to the specified proof type and the given miner. +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 +} + +// mockAggregateSealProof generates a mock "seal" aggregate proof tied to the specified proof type, +// the given miner, and the number of proven sectors. +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 +} + +// mockWpostProof generates a mock "window post" proof tied to the specified proof type, and the +// given miner. +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") + } +} diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 505f563e9..fa4d71028 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -19,6 +19,7 @@ import ( "github.com/filecoin-project/lotus/node/repo" ) +// Node represents the local lotus node, or at least the part of it we care about. type Node struct { Repo repo.LockedRepo Blockstore blockstore.Blockstore @@ -26,6 +27,7 @@ type Node struct { Chainstore *store.ChainStore } +// OpenNode opens the local lotus node for writing. This will fail if the node is online. func OpenNode(ctx context.Context, path string) (*Node, error) { var node Node r, err := repo.NewFS(path) @@ -55,6 +57,7 @@ func OpenNode(ctx context.Context, path string) (*Node, error) { return &node, nil } +// Close cleanly close the node. Please call this on shutdown to make sure everything is flushed. func (nd *Node) Close() error { var err error if closer, ok := nd.Blockstore.(io.Closer); ok && closer != nil { @@ -69,6 +72,7 @@ func (nd *Node) Close() error { return err } +// LoadSim loads func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { sim := &Simulation{ Node: nd, @@ -103,6 +107,10 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { return sim, nil } +// Create creates a new simulation. +// +// - This will fail if a simulation already exists with the given name. +// - Name must not contain a '/'. 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) @@ -125,6 +133,7 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) return sim, nil } +// ListSims lists all simulations. func (nd *Node) ListSims(ctx context.Context) ([]string, error) { prefix := simulationPrefix.ChildString("head").String() items, err := nd.MetadataDS.Query(query.Query{ @@ -153,6 +162,9 @@ func (nd *Node) ListSims(ctx context.Context) ([]string, error) { } } +// DeleteSim deletes a simulation and all related metadata. +// +// NOTE: This function does not delete associated messages, blocks, or chain state. func (nd *Node) DeleteSim(ctx context.Context, name string) error { // TODO: make this a bit more generic? keys := []datastore.Key{ diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go index 9a64c3f3a..a86b691f3 100644 --- a/cmd/lotus-sim/simulation/power.go +++ b/cmd/lotus-sim/simulation/power.go @@ -12,10 +12,6 @@ import ( "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) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 1ede3d5c4..055918c8c 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -20,15 +20,17 @@ import ( 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")) ) +// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner. +func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { + return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) +} + +// packPreCommits packs pre-commit messages until the block is full. func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (full bool, _err error) { var top1Count, top10Count, restCount int defer func() { @@ -50,6 +52,13 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful minerAddr address.Address count *int ) + + // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. + // This won't yeild the most accurate distribution... but it'll give us a good + // enough distribution. + + // NOTE: We submit at most _one_ 819 sector batch per-miner per-block. See the + // comment on packPreCommitsMiner for why. We should fix this. switch { case (i%3) <= 0 && top1Miners < ss.minerDist.top1.len(): count = &top1Count @@ -67,6 +76,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful // 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) @@ -78,7 +88,10 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful } } +// packPreCommitsMiner packs count pre-commits for the given miner. This should only be called once +// per-miner, per-epoch to avoid packing multiple pre-commits with the same sector numbers. func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) { + // Load everything. epoch := ss.nextEpoch() nv := ss.sm.GetNtwkVersion(ctx, epoch) st, err := ss.stateTree(ctx) @@ -120,6 +133,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } } + // Generate pre-commits. sealType, err := miner.PreferredSealProofTypeFromWindowPoStType( nv, minerInfo.WindowPoStProofType, ) @@ -143,6 +157,8 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, Expiration: expiration, } } + + // Commit the pre-commits. added := 0 if nv >= network.Version13 { targetBatchSize := maxPreCommitBatchSize @@ -158,6 +174,8 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, if err != nil { return 0, false, err } + // NOTE: just in-case, sendAndFund will "fund" and re-try for any message + // that fails due to "insufficient funds". if full, err := sendAndFund(cb, &types.Message{ To: minerAddr, From: minerInfo.Worker, diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 0d855bcd1..ec0c99c8a 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -21,7 +21,10 @@ import ( power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" ) +// packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the +// block or runs out. func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_full bool, _err error) { + // Roll the commitQueue forward. ss.commitQueue.advanceEpoch(ss.nextEpoch()) var failed, done, unbatched, count int @@ -64,6 +67,14 @@ type proveCommitResult struct { done, failed, unbatched int } +// sendAndFund "packs" the given message, funding the actor if necessary. It: +// +// 1. Tries to send the given message. +// 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. +// 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from +// somewhere) and re-tries the message.0 +// +// NOTE: If the message fails a second time, the funds won't be "unsent". func sendAndFund(send packFunc, msg *types.Message) (bool, error) { full, err := send(msg) aerr, ok := err.(aerrors.ActorError) @@ -87,7 +98,10 @@ func sendAndFund(send packFunc, msg *types.Message) (bool, error) { return send(msg) } -// Enqueue a single prove commit from the given miner. +// packProveCommitsMiner enqueues a prove commits from the given miner until it runs out of +// available prove-commits, batching as much as possible. +// +// This function will fund as necessary from the "burnt funds actor" (look, it's convenient). func (ss *simulationState) packProveCommitsMiner( ctx context.Context, cb packFunc, minerAddr address.Address, pending minerPendingCommits, @@ -200,7 +214,10 @@ func (ss *simulationState) packProveCommitsMiner( return res, false, nil } -// Enqueue all pending prove-commits for the given miner. +// loadProveCommitsMiner enqueue all pending prove-commits for the given miner. This is called on +// load to populate the commitQueue and should not need to be called later. +// +// It will drop any pre-commits that have already expired. 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. diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 384cc79cb..dc5cb3976 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -2,10 +2,7 @@ package simulation import ( "context" - "crypto/sha256" - "encoding/binary" "encoding/json" - "time" "golang.org/x/xerrors" @@ -13,9 +10,7 @@ import ( 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" @@ -35,16 +30,23 @@ const ( maxProveCommitBatchSize = miner5.MaxAggregatedSectors ) +// config is the simulation's config, persisted to the local metadata store and loaded on start. +// +// See simulationState.loadConfig and simulationState.saveConfig. type config struct { Upgrades map[network.Version]abi.ChainEpoch } +// upgradeSchedule constructs an stmgr.StateManager upgrade schedule, overriding any network upgrade +// epochs as specified in the config. 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{}{} } + + // Update network upgrade epochs. newUpgradeSchedule := upgradeSchedule[:0] for _, upgrade := range upgradeSchedule { if height, ok := c.Upgrades[upgrade.Network]; ok { @@ -56,6 +58,8 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { } newUpgradeSchedule = append(newUpgradeSchedule, upgrade) } + + // Make sure we didn't try to configure an unknown network version. if len(expected) > 0 { missing := make([]network.Version, 0, len(expected)) for nv := range expected { @@ -63,9 +67,16 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { } return nil, xerrors.Errorf("unknown network versions %v in config", missing) } + + // Finally, validate it. This ensures we don't change the order of the upgrade or anything + // like that. + if err := newUpgradeSchedule.Validate(); err != nil { + return nil, err + } return newUpgradeSchedule, nil } +// Simulation specifies a lotus-sim simulation. type Simulation struct { *Node @@ -82,6 +93,8 @@ type Simulation struct { state *simulationState } +// loadConfig loads a simulation's config from the datastore. This must be called on startup and may +// be called to restore the config from-disk. func (sim *Simulation) loadConfig() error { configBytes, err := sim.MetadataDS.Get(sim.key("config")) if err == nil { @@ -97,6 +110,18 @@ func (sim *Simulation) loadConfig() error { return nil } +// saveConfig saves the current config to the datastore. This must be called whenever the config is +// changed. +func (sim *Simulation) saveConfig() error { + buf, err := json.Marshal(sim.config) + if err != nil { + return err + } + return sim.MetadataDS.Put(sim.key("config"), buf) +} + +// stateTree returns the current state-tree for the current head, computing the tipset if necessary. +// The state-tree is cached until the head is changed. func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) { if sim.st == nil { st, _, err := sim.sm.TipSetState(ctx, sim.head) @@ -128,6 +153,8 @@ func (sim *Simulation) simState(ctx context.Context) (*simulationState, error) { var simulationPrefix = datastore.NewKey("/simulation") +// key returns the the key in the form /simulation//. For example, +// /simulation/head/default. func (sim *Simulation) key(subkey string) datastore.Key { return simulationPrefix.ChildString(subkey).ChildString(sim.name) } @@ -139,14 +166,18 @@ func (sim *Simulation) Load(ctx context.Context) error { return err } +// GetHead returns the current simulation head. func (sim *Simulation) GetHead() *types.TipSet { return sim.head } +// GetNetworkVersion returns the current network version for the simulation. func (sim *Simulation) GetNetworkVersion() network.Version { return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) } +// SetHead updates the current head of the simulation and stores it in the metadata store. This is +// called for every Simulation.Step. 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) @@ -156,85 +187,14 @@ func (sim *Simulation) SetHead(head *types.TipSet) error { return nil } +// Name returns the simulation's name. 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 -} - +// SetUpgradeHeight sets the height of the given network version change (and saves the config). +// +// This fails if the specified epoch has already passed or the new upgrade schedule is invalid. 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()) @@ -269,6 +229,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch return nil } +// ListUpgrades returns any future network upgrades. func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { upgrades, err := sim.config.upgradeSchedule() if err != nil { @@ -283,11 +244,3 @@ func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { } return pending, 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) -} diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index ee664166e..88971c9f0 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -2,7 +2,6 @@ package simulation import ( "context" - "math/rand" "sort" "github.com/filecoin-project/go-address" @@ -11,41 +10,19 @@ import ( "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) -} - +// simualtionState holds the "state" of the simulation. This is split from the Simulation type so we +// can load it on-dempand if and when we need to actually _run_ the simualation. Loading the +// simulation state requires walking all active miners. type simulationState struct { *Simulation + // 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 determine these rates by looking at two power tables. // 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. + // now. minerDist struct { - top1, top10, rest perm + top1, top10, rest actorIter } // We track the window post periods per miner and assume that no new miners are ever added. @@ -58,6 +35,9 @@ type simulationState struct { pendingWposts []*types.Message nextWpostEpoch abi.ChainEpoch + // We track the set of pending commits. On simulation load, and when a new pre-commit is + // added to the chain, we put the commit in this queue. advanceEpoch(currentEpoch) should be + // called on this queue at every epoch before using it. commitQueue commitQueue } @@ -167,7 +147,7 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState }) for i, oi := range sealList { - var dist *perm + var dist *actorIter if i < len(sealList)/100 { dist = &state.minerDist.top1 } else if i < len(sealList)/10 { @@ -185,6 +165,35 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState return state, nil } +// nextEpoch returns the next epoch (head+1). func (ss *simulationState) nextEpoch() abi.ChainEpoch { return ss.GetHead().Height() + 1 } + +// getMinerInfo returns the miner's cached info. +// +// NOTE: we assume that miner infos won't change. We'll need to fix this if we start supporting arbitrary message. +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 +} diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index b44f3be4d..79ace1db2 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -20,6 +20,8 @@ import ( ) const ( + // The number of expected blocks in a tipset. We use this to determine how much gas a tipset + // has. expectedBlocks = 5 // TODO: This will produce invalid blocks but it will accurately model the amount of gas // we're willing to use per-tipset. @@ -42,6 +44,7 @@ func (sim *Simulation) Step(ctx context.Context) (*types.TipSet, error) { return ts, nil } +// step steps the simulation state forward one step, producing and executing a new tipset. func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { log.Infow("step", "epoch", ss.head.Height()+1) messages, err := ss.popNextMessages(ctx) @@ -59,20 +62,25 @@ func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { } type packFunc func(*types.Message) (full bool, err error) -type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error) +// popNextMessages generates/picks a set of messages to be included in the next block. +// +// - This function is destructive and should only be called once per epoch. +// - This function does not store anything in the repo. +// - This function handles all gas estimation. The returned messages should all fit in a single +// block. 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 - } + + // First we make sure we don't have an upgrade at this epoch. If we do, we return no + // messages so we can just create an empty block at that epoch. + // + // This isn't what the network does, but it makes things easier. Otherwise, we'd need to run + // migrations before this epoch and I'd rather not deal with that. 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, @@ -81,10 +89,20 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag return nil, nil } - // Then we need to execute messages till we run out of gas. Those messages will become the - // block's messages. + // Next, we compute the state for the parent tipset. In practice, this will likely be + // cached. + parentState, _, err := ss.sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, err + } + + // Then we construct a VM to execute messages for gas estimation. + // + // Most parts of this VM are "real" except: + // 1. We don't charge a fee. + // 2. The runtime has "fake" proof logic. + // 3. We don't actually save any of the results. r := store.NewChainRand(ss.sm.ChainStore(), parentTs.Cids()) - // TODO: Factor this out maybe? vmopt := &vm.VMOpts{ StateBase: parentState, Epoch: nextHeight, @@ -100,9 +118,15 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag 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. + + // Next we define a helper function for "pushing" messages. This is the function that will + // be passed to the "pack" functions. + // + // It. + // + // 1. Tries to execute the message on-top-of the already pushed message. + // 2. Is careful to revert messages on failure to avoid nasties like nonce-gaps. + // 3. Resolves IDs as necessary, fills in missing parts of the message, etc. vmStore := vmi.ActorStore(ctx) var gasTotal int64 var messages []*types.Message @@ -181,16 +205,52 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag 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 - } + + // Finally, we generate a set of messages to be included in + if err := ss.packMessages(ctx, tryPushMsg); err != nil { + return nil, err } return messages, nil } + +// functionName extracts the name of given function. +func functionName(fn interface{}) string { + name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() + lastDot := strings.LastIndexByte(name, '.') + if lastDot >= 0 { + name = name[lastDot+1 : len(name)-3] + } + lastDash := strings.LastIndexByte(name, '-') + if lastDash > 0 { + name = name[:lastDash] + } + return name +} + +// packMessages packs messages with the given packFunc until the block is full (packFunc returns +// true). +// TODO: Make this more configurable for other simulations. +func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error { + type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error) + + // We pack messages in-order: + // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only + // miss them if/when we run out of chain bandwidth. + // 2. Prove commits. We do this eagerly to ensure they don't expire. + // 3. Finally, we fill the rest of the space with pre-commits. + messageGenerators := []messageGenerator{ + ss.packWindowPoSts, + ss.packProveCommits, + ss.packPreCommits, + } + + for _, mgen := range messageGenerators { + if full, err := mgen(ctx, cb); err != nil { + return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) + } else if full { + break + } + } + return nil +} diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index 7abb9a83a..fe93a5f0c 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -5,41 +5,29 @@ import ( "math" "time" + "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/crypto" + "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 +// postChainCommitInfo returns th +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 } +// packWindowPoSts packs window posts until either the block is full or all healty sectors +// have been proven. It does not recover sectors. 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 { @@ -84,7 +72,7 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu return false, nil } -// Enqueue all missing window posts for the current epoch for the given miner. +// stepWindowPoStsMiner enqueues 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, @@ -198,7 +186,8 @@ func (ss *simulationState) stepWindowPoStsMiner( return nil } -// Enqueue missing window posts for all miners with deadlines opening at the current epoch. +// queueWindowPoSts enqueues missing window posts for all miners with deadlines opening between the +// last epoch in which this function was called and the current epoch (head+1). func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { targetHeight := ss.nextEpoch() From 7925b695738b757a463fb7504756348dfa78d6a4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 17:52:57 -0700 Subject: [PATCH 06/94] doc(lotus-sim): document block generation logic --- cmd/lotus-sim/simulation/block.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 677ba7a2f..3a1181c1c 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -13,6 +13,7 @@ import ( const beaconPrefix = "mockbeacon:" +// nextBeaconEntries returns a fake beacon entries for the next block. func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { parentBeacons := sim.head.Blocks()[0].BeaconEntries lastBeacon := parentBeacons[len(parentBeacons)-1] @@ -28,6 +29,7 @@ func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { }} } +// nextTicket returns a fake ticket for the next block. func (sim *Simulation) nextTicket() *types.Ticket { newProof := sha256.Sum256(sim.head.MinTicket().VRFProof) return &types.Ticket{ @@ -35,6 +37,14 @@ func (sim *Simulation) nextTicket() *types.Ticket { } } +// makeTipSet generates and executes the next tipset from the given messages. This method: +// +// 1. Stores the given messages in the Chainstore. +// 2. Creates and persists a single block mined by f01000. +// 3. Creates a tipset from this block and executes it. +// 4. Returns the resulting tipset. +// +// This method does _not_ mutate local state (although it does add blocks to the datastore). func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { parentTs := sim.head parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs) From 0ccf716989256697844af875b35441ce4dedc30c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 18:18:20 -0700 Subject: [PATCH 07/94] fix(lotus-sim): refactor miner state loading Add a helper function so we don't need to constantly repeat ourselves. --- cmd/lotus-sim/simulation/precommit.go | 10 +------ cmd/lotus-sim/simulation/state.go | 41 +++++++++++++-------------- cmd/lotus-sim/simulation/wdpost.go | 13 +-------- 3 files changed, 21 insertions(+), 43 deletions(-) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 055918c8c..619ba467d 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -94,15 +94,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, // Load everything. 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) + actor, minerState, err := ss.getMinerState(ctx, minerAddr) if err != nil { return 0, false, err } diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index 88971c9f0..23de7038c 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -63,13 +63,6 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState 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 @@ -86,12 +79,7 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState 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) + _, minerState, err := state.getMinerState(ctx, addr) if err != nil { return nil, err } @@ -176,15 +164,7 @@ func (ss *simulationState) nextEpoch() abi.ChainEpoch { 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) + _, minerState, err := ss.getMinerState(ctx, addr) if err != nil { return nil, err } @@ -197,3 +177,20 @@ func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Addres } return minerInfo, nil } + +// getMinerState loads the miner actor & state. +func (ss *simulationState) getMinerState(ctx context.Context, addr address.Address) (*types.Actor, miner.State, error) { + st, err := ss.stateTree(ctx) + if err != nil { + return nil, nil, err + } + act, err := st.GetActor(addr) + if err != nil { + return nil, nil, err + } + state, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) + if err != nil { + return nil, nil, err + } + return act, state, err +} diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index fe93a5f0c..c940c8d51 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -191,11 +191,6 @@ func (ss *simulationState) stepWindowPoStsMiner( 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 @@ -220,14 +215,8 @@ func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { 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) + _, minerState, err := ss.getMinerState(ctx, addr) if err != nil { return err } From 2e4f526375b73fde3b130c137a685cb84c868821 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 19:21:18 -0700 Subject: [PATCH 08/94] fix(lotus-sim): skip (and log) missing/expired pre-commits --- cmd/lotus-sim/simulation/provecommit.go | 86 +++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index ec0c99c8a..968e4eb2f 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -120,7 +120,6 @@ func (ss *simulationState) packProveCommitsMiner( batchSize = len(snos) } batch := snos[:batchSize] - snos = snos[batchSize:] proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) if err != nil { @@ -149,22 +148,53 @@ func (ss *simulationState) packProveCommitsMiner( }); 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() { + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.IsFatal() { return res, false, err } + if aerr.RetCode() == exitcode.ErrNotFound { + good, expired, err := ss.filterProveCommits(ctx, minerAddr, batch) + if err != nil { + log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) + // fail with the original error. + return res, false, aerr + } + // If we've removed sectors (and kept some), try again. + // If we've removed all sectors, or no sectors, just + // move on and deliver the error. + if len(good) > 0 && len(expired) > 0 { + res.failed += len(expired) + + // update the pending sector numbers in-place to remove the expired ones. + snos = snos[len(expired):] + copy(snos, good) + pending.finish(sealType, len(expired)) + + log.Errorw("failed to prove commit expired/missing pre-commits", + "error", err, + "miner", minerAddr, + "expired", expired, + "discarded", len(expired), + "kept", len(good), + "epoch", ss.nextEpoch(), + ) + continue + } + } log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, "sectors", batch, "epoch", ss.nextEpoch(), ) - res.failed += batchSize + res.failed += len(batch) } else if full { return res, true, nil } else { - res.done += batchSize + res.done += len(batch) } pending.finish(sealType, batchSize) + snos = snos[batchSize:] } } for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch { @@ -225,12 +255,56 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) - return minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + var total, dropped int + err := minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + total++ msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) if nextEpoch > info.PreCommitEpoch+msd { - log.Warnw("dropping old pre-commit") + dropped++ return nil } return ss.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) }) + if err != nil { + return err + } + if dropped > 0 { + log.Warnw("dropped expired pre-commits on load", + "miner", addr, + "total", total, + "expired", dropped, + ) + } + return nil +} + +// filterProveCommits filters out expired prove-commits. +func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) (good, expired []abi.SectorNumber, err error) { + _, minerState, err := ss.getMinerState(ctx, minerAddr) + if err != nil { + return nil, nil, err + } + + nextEpoch := ss.nextEpoch() + nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) + av := actors.VersionForNetwork(nv) + + good = make([]abi.SectorNumber, 0, len(snos)) + for _, sno := range snos { + info, err := minerState.GetPrecommittedSector(sno) + if err != nil { + return nil, nil, err + } + if info == nil { + expired = append(expired, sno) + continue + } + msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) + if nextEpoch > info.PreCommitEpoch+msd { + expired = append(expired, sno) + continue + } + good = append(good, sno) + } + return good, expired, nil } From 82019ce4744ab57316f15b5b99f4cc2b4f9d45e1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 19:57:46 -0700 Subject: [PATCH 09/94] fix(lotus-sim): correctly merge forward commit queue. --- cmd/lotus-sim/simulation/commit_queue.go | 2 ++ cmd/lotus-sim/simulation/provecommit.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go index 63a478120..4cfb6e764 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -129,6 +129,8 @@ func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { currPending[ty] = append(currSnos, nextSnos...) } } + // Now replace next with the merged curr. + q.queue[0] = curr } q.offset = epoch if len(q.queue) == 0 { diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 968e4eb2f..b62a8bafe 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -171,7 +171,7 @@ func (ss *simulationState) packProveCommitsMiner( pending.finish(sealType, len(expired)) log.Errorw("failed to prove commit expired/missing pre-commits", - "error", err, + "error", aerr, "miner", minerAddr, "expired", expired, "discarded", len(expired), From 5b31ae39ea7fd4743feab29d31173644f030a38d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 20:18:59 -0700 Subject: [PATCH 10/94] fix: test commit queue --- cmd/lotus-sim/simulation/commit_queue_test.go | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 cmd/lotus-sim/simulation/commit_queue_test.go diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/commit_queue_test.go new file mode 100644 index 000000000..1fee98154 --- /dev/null +++ b/cmd/lotus-sim/simulation/commit_queue_test.go @@ -0,0 +1,118 @@ +package simulation + +import ( + "testing" + + "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" + "github.com/stretchr/testify/require" +) + +func TestCommitQueue(t *testing.T) { + var q commitQueue + addr1, err := address.NewIDAddress(1000) + require.NoError(t, err) + proofType := abi.RegisteredSealProof_StackedDrg64GiBV1_1 + require.NoError(t, q.enqueueProveCommit(addr1, 0, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 0, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 0, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 1, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 1, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 2, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 1, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 3, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 3, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 4, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 4, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 5, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 6, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 6, + })) + + epoch := abi.ChainEpoch(0) + q.advanceEpoch(epoch) + _, _, ok := q.nextMiner() + require.False(t, ok) + + epoch += policy.GetPreCommitChallengeDelay() + q.advanceEpoch(epoch) + _, _, ok = q.nextMiner() + require.False(t, ok) + + // 0 : empty + non-empty + epoch++ + q.advanceEpoch(epoch) + addr, sectors, ok := q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.Equal(t, addr, addr1) + sectors.finish(proofType, 1) + require.Equal(t, sectors.count(), 1) + require.EqualValues(t, []abi.SectorNumber{1}, sectors[proofType]) + + // 1 : non-empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + addr, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, addr, addr1) + require.Equal(t, sectors.count(), 3) + require.EqualValues(t, []abi.SectorNumber{1, 2, 3}, sectors[proofType]) + sectors.finish(proofType, 3) + require.Equal(t, sectors.count(), 0) + + // 2 : empty + empty + epoch += 1 + q.advanceEpoch(epoch) + _, _, ok = q.nextMiner() + require.False(t, ok) + + // 3 : empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 1) + require.EqualValues(t, []abi.SectorNumber{4}, sectors[proofType]) + + // 4 : non-empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) + + // 5 : empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) + sectors.finish(proofType, 1) + require.EqualValues(t, []abi.SectorNumber{5}, sectors[proofType]) + + // 6 + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) +} From dfdafa3c157466ab4867ea62420d7bf938c60724 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 21:14:57 -0700 Subject: [PATCH 11/94] fix(lotus-sim): pretend all messages are BLS It doesn't really matter, and it ensures they all get executed in-order. --- cmd/lotus-sim/simulation/messages.go | 47 ++++++---------------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 3f1c55179..8c12cac1a 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -3,15 +3,10 @@ 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" ) @@ -30,45 +25,23 @@ func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { // storeMessages packs a set of messages into a types.MsgMeta and returns the resulting CID. The // resulting CID is valid for the BlocKHeader's Messages field. func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { - var blsMessages, sekpMessages []cid.Cid - fakeSig := make([]byte, 32) + // We store all messages as "bls" messages so they're executed in-order. This ensures + // accurate gas accounting. It also ensures we don't, e.g., try to fund a miner after we + // fail a pre-commit... + var msgCids []cid.Cid 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()) + c, err := nd.Chainstore.PutMessage(msg) + if err != nil { + return cid.Undef, err } + msgCids = append(msgCids, c) } adtStore := nd.Chainstore.ActorStore(ctx) - blsMsgArr, err := toArray(adtStore, blsMessages) + blsMsgArr, err := toArray(adtStore, msgCids) if err != nil { return cid.Undef, err } - sekpMsgArr, err := toArray(adtStore, sekpMessages) + sekpMsgArr, err := toArray(adtStore, nil) if err != nil { return cid.Undef, err } From 0725019bdb37293e1e2190cdf061fedab1f6d06e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 21:36:40 -0700 Subject: [PATCH 12/94] feat(lotus-sim): completely pack block Instead of packing till we see "full". Prove-commits are large, we may have room for some more pre-commits. --- cmd/lotus-sim/simulation/step.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 79ace1db2..0b92ed13e 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -246,10 +246,11 @@ func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error } for _, mgen := range messageGenerators { - if full, err := mgen(ctx, cb); err != nil { + // We're intentionally ignoring the "full" signal so we can try to pack a few more + // messages. + _, err := mgen(ctx, cb) + if err != nil { return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) - } else if full { - break } } return nil From f9ebe3017d170a4b85bb3c09bbb58120fbd3ac45 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 21:41:40 -0700 Subject: [PATCH 13/94] test(lotus-sim): test commit-queue rollover --- cmd/lotus-sim/simulation/commit_queue_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/commit_queue_test.go index 1fee98154..1a7bd2749 100644 --- a/cmd/lotus-sim/simulation/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/commit_queue_test.go @@ -115,4 +115,12 @@ func TestCommitQueue(t *testing.T) { require.True(t, ok) require.Equal(t, sectors.count(), 2) require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) + + // 8 + epoch += 2 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) } From a57c509e1e401c20511514df4127e1345c7bab73 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 10:27:20 -0700 Subject: [PATCH 14/94] fix(lotus-sim): cleanup and document pre-commit filtering --- cmd/lotus-sim/simulation/provecommit.go | 44 ++++++++++++++----------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index b62a8bafe..3bcc3a720 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -147,34 +147,42 @@ func (ss *simulationState) packProveCommitsMiner( Params: enc, }); err != nil { // If we get a random error, or a fatal actor error, bail. - // Otherwise, just log it. aerr, ok := err.(aerrors.ActorError) if !ok || aerr.IsFatal() { return res, false, err } + // If we get a "not-found" error, try to remove any missing + // prove-commits and continue. This can happen either + // because: + // + // 1. The pre-commit failed on execution (but not when + // packing). This shouldn't happen, but we might as well + // gracefully handle it. + // 2. The pre-commit has expired. We'd have to be really + // backloged to hit this case, but we might as well handle + // it. if aerr.RetCode() == exitcode.ErrNotFound { - good, expired, err := ss.filterProveCommits(ctx, minerAddr, batch) + // First, split into "good" and "missing" + good, err := ss.filterProveCommits(ctx, minerAddr, batch) if err != nil { log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) // fail with the original error. return res, false, aerr } - // If we've removed sectors (and kept some), try again. - // If we've removed all sectors, or no sectors, just - // move on and deliver the error. - if len(good) > 0 && len(expired) > 0 { - res.failed += len(expired) + removed := len(batch) - len(good) + // If they're all missing, skip. If they're all good, skip too (and log). + if len(good) > 0 && removed > 0 { + res.failed += removed // update the pending sector numbers in-place to remove the expired ones. - snos = snos[len(expired):] + snos = snos[removed:] copy(snos, good) - pending.finish(sealType, len(expired)) + pending.finish(sealType, removed) log.Errorw("failed to prove commit expired/missing pre-commits", "error", aerr, "miner", minerAddr, - "expired", expired, - "discarded", len(expired), + "discarded", removed, "kept", len(good), "epoch", ss.nextEpoch(), ) @@ -278,33 +286,31 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre return nil } -// filterProveCommits filters out expired prove-commits. -func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) (good, expired []abi.SectorNumber, err error) { +// filterProveCommits filters out expired and/or missing pre-commits. +func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) ([]abi.SectorNumber, error) { _, minerState, err := ss.getMinerState(ctx, minerAddr) if err != nil { - return nil, nil, err + return nil, err } nextEpoch := ss.nextEpoch() nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) - good = make([]abi.SectorNumber, 0, len(snos)) + good := make([]abi.SectorNumber, 0, len(snos)) for _, sno := range snos { info, err := minerState.GetPrecommittedSector(sno) if err != nil { - return nil, nil, err + return nil, err } if info == nil { - expired = append(expired, sno) continue } msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) if nextEpoch > info.PreCommitEpoch+msd { - expired = append(expired, sno) continue } good = append(good, sno) } - return good, expired, nil + return good, nil } From 0faacbe154229083a38704981b6620962b977007 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 10:42:59 -0700 Subject: [PATCH 15/94] feat(lotus-sim): store start tipset --- cmd/lotus-sim/simulation/node.go | 19 ++++++++------ cmd/lotus-sim/simulation/simulation.go | 35 ++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index fa4d71028..5046222f3 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -78,17 +78,15 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { Node: nd, name: name, } - tskBytes, err := nd.MetadataDS.Get(sim.key("head")) + + var err error + sim.head, err = sim.loadNamedTipSet("head") if err != nil { - return nil, xerrors.Errorf("failed to load simulation %s: %w", name, err) + return nil, err } - tsk, err := types.TipSetKeyFromBytes(tskBytes) + sim.start, err = sim.loadNamedTipSet("start") 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) + return nil, err } err = sim.loadConfig() @@ -126,6 +124,10 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) return nil, xerrors.Errorf("simulation named %s already exists", name) } + if err := sim.storeNamedTipSet("start", head); err != nil { + return nil, xerrors.Errorf("failed to set simulation start: %w", err) + } + if err := sim.SetHead(head); err != nil { return nil, err } @@ -169,6 +171,7 @@ 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("start").ChildString(name), simulationPrefix.ChildString("config").ChildString(name), } var err error diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index dc5cb3976..4b13f52f7 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -82,6 +82,7 @@ type Simulation struct { name string config config + start *types.TipSet sm *stmgr.StateManager // head @@ -159,6 +160,31 @@ func (sim *Simulation) key(subkey string) datastore.Key { return simulationPrefix.ChildString(subkey).ChildString(sim.name) } +// loadNamedTipSet the tipset with the given name (for this simulation) +func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { + tskBytes, err := sim.MetadataDS.Get(sim.key(name)) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset %s/%s: %w", sim.name, name, err) + } + tsk, err := types.TipSetKeyFromBytes(tskBytes) + if err != nil { + return nil, xerrors.Errorf("failed to parse tipste %v (%s/%s): %w", tskBytes, sim.name, name, err) + } + ts, err := sim.Chainstore.LoadTipSet(tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset %s (%s/%s): %w", tsk, sim.name, name, err) + } + return ts, nil +} + +// storeNamedTipSet stores the tipset at name (relative to the simulation). +func (sim *Simulation) storeNamedTipSet(name string, ts *types.TipSet) error { + if err := sim.MetadataDS.Put(sim.key(name), ts.Key().Bytes()); err != nil { + return xerrors.Errorf("failed to store tipset (%s/%s): %w", sim.name, name, err) + } + return nil +} + // 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 { @@ -171,6 +197,11 @@ func (sim *Simulation) GetHead() *types.TipSet { return sim.head } +// GetStart returns simulation's parent tipset. +func (sim *Simulation) GetStart() *types.TipSet { + return sim.start +} + // GetNetworkVersion returns the current network version for the simulation. func (sim *Simulation) GetNetworkVersion() network.Version { return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) @@ -179,8 +210,8 @@ func (sim *Simulation) GetNetworkVersion() network.Version { // SetHead updates the current head of the simulation and stores it in the metadata store. This is // called for every Simulation.Step. 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) + if err := sim.storeNamedTipSet("head", head); err != nil { + return err } sim.st = nil // we'll compute this on-demand. sim.head = head From 5f6733fe4425f28ccd7af99b70bdafb0910a7140 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 11:22:11 -0700 Subject: [PATCH 16/94] feat(lotus-sim): expose StateManager from Simulation --- cmd/lotus-sim/simulation/block.go | 4 ++-- cmd/lotus-sim/simulation/node.go | 8 ++++---- cmd/lotus-sim/simulation/power.go | 2 +- cmd/lotus-sim/simulation/precommit.go | 2 +- cmd/lotus-sim/simulation/provecommit.go | 6 +++--- cmd/lotus-sim/simulation/simulation.go | 10 +++++----- cmd/lotus-sim/simulation/step.go | 18 +++++++++--------- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 3a1181c1c..b3d0288b8 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -47,7 +47,7 @@ func (sim *Simulation) nextTicket() *types.Ticket { // This method does _not_ mutate local state (although it does add blocks to the datastore). func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { parentTs := sim.head - parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs) + parentState, parentRec, err := sim.StateManager.TipSetState(ctx, parentTs) if err != nil { return nil, xerrors.Errorf("failed to compute parent tipset: %w", err) } @@ -80,7 +80,7 @@ func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message return nil, xerrors.Errorf("failed to create new tipset: %w", err) } now := time.Now() - _, _, err = sim.sm.TipSetState(ctx, newTipSet) + _, _, err = sim.StateManager.TipSetState(ctx, newTipSet) if err != nil { return nil, xerrors.Errorf("failed to compute new tipset: %w", err) } diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 5046222f3..105c4c490 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -98,7 +98,7 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { 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) + sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us) if err != nil { return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) } @@ -114,9 +114,9 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name) } sim := &Simulation{ - name: name, - Node: nd, - sm: stmgr.NewStateManager(nd.Chainstore), + name: name, + Node: nd, + StateManager: stmgr.NewStateManager(nd.Chainstore), } if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { return nil, err diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go index a86b691f3..f05dadf19 100644 --- a/cmd/lotus-sim/simulation/power.go +++ b/cmd/lotus-sim/simulation/power.go @@ -22,7 +22,7 @@ func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (m return nil, xerrors.Errorf("when projecting growth, failed to lookup lookback epoch: %w", err) } - powerActor, err := sim.sm.LoadActor(ctx, power.Address, ts) + powerActor, err := sim.StateManager.LoadActor(ctx, power.Address, ts) if err != nil { return nil, err } diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 619ba467d..38b745a52 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -93,7 +93,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) { // Load everything. epoch := ss.nextEpoch() - nv := ss.sm.GetNtwkVersion(ctx, epoch) + nv := ss.StateManager.GetNtwkVersion(ctx, epoch) actor, minerState, err := ss.getMinerState(ctx, minerAddr) if err != nil { return 0, false, err diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 3bcc3a720..208af38a7 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -111,7 +111,7 @@ func (ss *simulationState) packProveCommitsMiner( return res, false, err } - nv := ss.sm.GetNtwkVersion(ctx, ss.nextEpoch()) + nv := ss.StateManager.GetNtwkVersion(ctx, ss.nextEpoch()) for sealType, snos := range pending { if nv >= network.Version13 { for len(snos) > minProveCommitBatchSize { @@ -260,7 +260,7 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre // 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) + nv := ss.StateManager.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) var total, dropped int @@ -294,7 +294,7 @@ func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr add } nextEpoch := ss.nextEpoch() - nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) + nv := ss.StateManager.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) good := make([]abi.SectorNumber, 0, len(snos)) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 4b13f52f7..d33f3e94f 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -79,11 +79,11 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { // Simulation specifies a lotus-sim simulation. type Simulation struct { *Node + StateManager *stmgr.StateManager name string config config start *types.TipSet - sm *stmgr.StateManager // head st *state.StateTree @@ -125,11 +125,11 @@ func (sim *Simulation) saveConfig() error { // The state-tree is cached until the head is changed. func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) { if sim.st == nil { - st, _, err := sim.sm.TipSetState(ctx, sim.head) + st, _, err := sim.StateManager.TipSetState(ctx, sim.head) if err != nil { return nil, err } - sim.st, err = sim.sm.StateTree(st) + sim.st, err = sim.StateManager.StateTree(st) if err != nil { return nil, err } @@ -204,7 +204,7 @@ func (sim *Simulation) GetStart() *types.TipSet { // GetNetworkVersion returns the current network version for the simulation. func (sim *Simulation) GetNetworkVersion() network.Version { - return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) + return sim.StateManager.GetNtwkVersion(context.TODO(), sim.head.Height()) } // SetHead updates the current head of the simulation and stores it in the metadata store. This is @@ -256,7 +256,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch return err } - sim.sm = sm + sim.StateManager = sm return nil } diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 0b92ed13e..9eddd039b 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -78,8 +78,8 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // This isn't what the network does, but it makes things easier. Otherwise, we'd need to run // migrations before this epoch and I'd rather not deal with that. nextHeight := parentTs.Height() + 1 - prevVer := ss.sm.GetNtwkVersion(ctx, nextHeight-1) - nextVer := ss.sm.GetNtwkVersion(ctx, nextHeight) + prevVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight-1) + nextVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight) if nextVer != prevVer { log.Warnw("packing no messages for version upgrade block", "old", prevVer, @@ -91,7 +91,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // Next, we compute the state for the parent tipset. In practice, this will likely be // cached. - parentState, _, err := ss.sm.TipSetState(ctx, parentTs) + parentState, _, err := ss.StateManager.TipSetState(ctx, parentTs) if err != nil { return nil, err } @@ -102,17 +102,17 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // 1. We don't charge a fee. // 2. The runtime has "fake" proof logic. // 3. We don't actually save any of the results. - r := store.NewChainRand(ss.sm.ChainStore(), parentTs.Cids()) + r := store.NewChainRand(ss.StateManager.ChainStore(), parentTs.Cids()) 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, + Bstore: ss.StateManager.ChainStore().StateBlockstore(), + Syscalls: ss.StateManager.ChainStore().VMSys(), + CircSupplyCalc: ss.StateManager.GetVMCirculatingSupply, + NtwkVersion: ss.StateManager.GetNtwkVersion, BaseFee: abi.NewTokenAmount(0), // FREE! - LookbackState: stmgr.LookbackStateGetterForTipset(ss.sm, parentTs), + LookbackState: stmgr.LookbackStateGetterForTipset(ss.StateManager, parentTs), } vmi, err := vm.NewVM(ctx, vmopt) if err != nil { From ccdd660f0ddf057e91e02c86b0089d1982a1c4e8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 11:23:51 -0700 Subject: [PATCH 17/94] feat(lotus-sim): more stats --- cmd/lotus-sim/stat.go | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/stat.go b/cmd/lotus-sim/stat.go index 25f9f5d51..b51853b31 100644 --- a/cmd/lotus-sim/stat.go +++ b/cmd/lotus-sim/stat.go @@ -1,12 +1,31 @@ package main import ( + "context" "fmt" "text/tabwriter" + "time" "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" ) +func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { + actor, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return power.Claim{}, err + } + state, err := power.Load(sm.ChainStore().ActorStore(ctx), actor) + if err != nil { + return power.Claim{}, err + } + return state.TotalPower() +} + var infoSimCommand = &cli.Command{ Name: "info", Description: "Output information about the simulation.", @@ -21,11 +40,45 @@ var infoSimCommand = &cli.Command{ if err != nil { return err } + + powerNow, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetHead()) + if err != nil { + return err + } + powerStart, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetStart()) + if err != nil { + return err + } + powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) - fmt.Fprintln(tw, "Name:\t", sim.Name()) - fmt.Fprintln(tw, "Height:\t", sim.GetHead().Height()) - fmt.Fprintln(tw, "TipSet:\t", sim.GetHead()) - fmt.Fprintln(tw, "Network Version:\t", sim.GetNetworkVersion()) + + head := sim.GetHead() + start := sim.GetStart() + headEpoch := head.Height() + firstEpoch := start.Height() + 1 + + headTime := time.Unix(int64(head.MinTimestamp()), 0) + startTime := time.Unix(int64(start.MinTimestamp()), 0) + duration := headTime.Sub(startTime) + + // growth rate in size/day + growthRate := big.Div( + big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), + big.NewInt(int64(duration)), + ) + + fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) + fmt.Fprintf(tw, "Head:\t%s\n", head) + fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) + fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) + fmt.Fprintf(tw, "Date:\t%s\n", headTime) + fmt.Fprintf(tw, "Duration:\t%s\n", duration) + fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) + fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) + fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) return tw.Flush() }, } From c5dc67ccd8b3d2fd2d83394930b961e7a69416ab Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 11:58:19 -0700 Subject: [PATCH 18/94] feat(lotus-sim): add a walk function This way, we can easily walk the chain and: 1. Inspect messages and their results. 2. Inspect tipset state. --- cmd/lotus-sim/simulation/simulation.go | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index d33f3e94f..4b571a808 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -6,6 +6,7 @@ import ( "golang.org/x/xerrors" + "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -16,6 +17,7 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" ) @@ -275,3 +277,55 @@ func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { } return pending, nil } + +type AppliedMessage struct { + types.Message + types.MessageReceipt +} + +// Walk walks the simulation's chain from the current head back to the first tipset. +func (sim *Simulation) Walk( + ctx context.Context, + cb func(sm *stmgr.StateManager, + ts *types.TipSet, + stCid cid.Cid, + messages []*AppliedMessage) error, +) error { + store := sim.Chainstore.ActorStore(ctx) + ts := sim.head + stCid, recCid, err := sim.StateManager.TipSetState(ctx, ts) + if err != nil { + return err + } + for !ts.Equals(sim.start) { + msgs, err := sim.Chainstore.MessagesForTipset(ts) + if err != nil { + return err + } + + recs, err := blockadt.AsArray(store, recCid) + if err != nil { + return xerrors.Errorf("amt load: %w", err) + } + applied := make([]*AppliedMessage, len(msgs)) + var rec types.MessageReceipt + err = recs.ForEach(&rec, func(i int64) error { + applied[i] = &AppliedMessage{ + Message: *msgs[i].VMMessage(), + MessageReceipt: rec, + } + return nil + }) + if err != nil { + return err + } + + if err := cb(sim.StateManager, ts, stCid, applied); err != nil { + return err + } + + stCid = ts.MinTicketBlock().ParentStateRoot + recCid = ts.MinTicketBlock().ParentMessageReceipts + } + return nil +} From 8410b0f79f5d51612613ef5fc2a4d034bded3544 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 13:32:32 -0700 Subject: [PATCH 19/94] feat(lotus-sim): add a feature to copy/rename simulation. Useful to backup old simulations before creating a new one. --- cmd/lotus-sim/simulation/node.go | 52 +++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 105c4c490..085a94c32 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -164,19 +164,57 @@ func (nd *Node) ListSims(ctx context.Context) ([]string, error) { } } +var simFields = []string{"head", "start", "config"} + // DeleteSim deletes a simulation and all related metadata. // // NOTE: This function does not delete associated messages, blocks, or chain state. 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("start").ChildString(name), - simulationPrefix.ChildString("config").ChildString(name), - } var err error - for _, key := range keys { + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(name) err = multierr.Append(err, nd.MetadataDS.Delete(key)) } return err } + +// CopySim copies a simulation. +func (nd *Node) CopySim(ctx context.Context, oldName, newName string) error { + values := make(map[string][]byte) + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(oldName) + value, err := nd.MetadataDS.Get(key) + if err == datastore.ErrNotFound { + continue + } else if err != nil { + return err + } + values[field] = value + } + + if _, ok := values["head"]; !ok { + return xerrors.Errorf("simulation named %s not found", oldName) + } + + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(newName) + var err error + if value, ok := values[field]; ok { + err = nd.MetadataDS.Put(key, value) + } else { + err = nd.MetadataDS.Delete(key) + } + if err != nil { + return err + } + } + return nil +} + +// RenameSim renames a simulation. +func (nd *Node) RenameSim(ctx context.Context, oldName, newName string) error { + if err := nd.CopySim(ctx, oldName, newName); err != nil { + return err + } + return nd.DeleteSim(ctx, oldName) +} From 4f0b9eefc159f025b5b12e0b2d76e2ed4d9e92c4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 13:42:01 -0700 Subject: [PATCH 20/94] fix(lotus-sim): check for slash in names on copy --- cmd/lotus-sim/simulation/node.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 085a94c32..73c739e5b 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -180,6 +180,13 @@ func (nd *Node) DeleteSim(ctx context.Context, name string) error { // CopySim copies a simulation. func (nd *Node) CopySim(ctx context.Context, oldName, newName string) error { + if strings.Contains(newName, "/") { + return xerrors.Errorf("simulation name %q cannot contain a '/'", newName) + } + if strings.Contains(oldName, "/") { + return xerrors.Errorf("simulation name %q cannot contain a '/'", oldName) + } + values := make(map[string][]byte) for _, field := range simFields { key := simulationPrefix.ChildString(field).ChildString(oldName) From bb4753ffbfc7ce2a62554d7eddb80b76d1bdad0c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 13:43:39 -0700 Subject: [PATCH 21/94] feat(lotus-sim): add commands to rename and copy --- cmd/lotus-sim/copy.go | 24 ++++++++++++++++++++++++ cmd/lotus-sim/main.go | 3 +++ cmd/lotus-sim/rename.go | 24 ++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 cmd/lotus-sim/copy.go create mode 100644 cmd/lotus-sim/rename.go diff --git a/cmd/lotus-sim/copy.go b/cmd/lotus-sim/copy.go new file mode 100644 index 000000000..eeb8eb1aa --- /dev/null +++ b/cmd/lotus-sim/copy.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var copySimCommand = &cli.Command{ + Name: "copy", + ArgsUsage: "", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + if cctx.NArg() != 1 { + return fmt.Errorf("expected 1 argument") + } + name := cctx.Args().First() + return node.CopySim(cctx.Context, cctx.String("simulation"), name) + }, +} diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 8fe313355..cf8903d72 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -14,7 +14,10 @@ import ( var root []*cli.Command = []*cli.Command{ createSimCommand, deleteSimCommand, + copySimCommand, + renameSimCommand, listSimCommand, + runSimCommand, infoSimCommand, upgradeCommand, diff --git a/cmd/lotus-sim/rename.go b/cmd/lotus-sim/rename.go new file mode 100644 index 000000000..833a57e96 --- /dev/null +++ b/cmd/lotus-sim/rename.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var renameSimCommand = &cli.Command{ + Name: "rename", + ArgsUsage: "", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + if cctx.NArg() != 1 { + return fmt.Errorf("expected 1 argument") + } + name := cctx.Args().First() + return node.RenameSim(cctx.Context, cctx.String("simulation"), name) + }, +} From 77f0fee58e407d6e62babb19798276ba6d2e8bf9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:09:47 -0700 Subject: [PATCH 22/94] chore(lotus-sim): fix comment about simulation block miner --- cmd/lotus-sim/simulation/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index b3d0288b8..31e7a1a79 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -40,7 +40,7 @@ func (sim *Simulation) nextTicket() *types.Ticket { // makeTipSet generates and executes the next tipset from the given messages. This method: // // 1. Stores the given messages in the Chainstore. -// 2. Creates and persists a single block mined by f01000. +// 2. Creates and persists a single block mined by the same miner as the parent. // 3. Creates a tipset from this block and executes it. // 4. Returns the resulting tipset. // From 4578e0dd8d11061700f3dc466214808512be3649 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:13:42 -0700 Subject: [PATCH 23/94] chore(lotus-sim): remove dead code --- cmd/lotus-sim/main.go | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index cf8903d72..13b5d2282 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -6,9 +6,6 @@ import ( 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{ @@ -53,39 +50,3 @@ func main() { 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 -} From e097ba8640acd532e2cba41f77e4088d77317179 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:22:16 -0700 Subject: [PATCH 24/94] feat(lotus-sim): wire up signal handler --- cmd/lotus-sim/main.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 13b5d2282..bfcad728d 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -1,8 +1,11 @@ package main import ( + "context" "fmt" "os" + "os/signal" + "syscall" logging "github.com/ipfs/go-log/v2" "github.com/urfave/cli/v2" @@ -44,7 +47,11 @@ func main() { }, } - if err := app.Run(os.Args); err != nil { + ctx, cancel := signal.NotifyContext(context.Background(), + syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) + defer cancel() + + if err := app.RunContext(ctx, os.Args); err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) os.Exit(1) return From ba65a1ba9bf468d5c311e9aa2b2aab56e573534f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:54:18 -0700 Subject: [PATCH 25/94] chore(lotus-sim): rename stat to info --- cmd/lotus-sim/{stat.go => info.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/lotus-sim/{stat.go => info.go} (100%) diff --git a/cmd/lotus-sim/stat.go b/cmd/lotus-sim/info.go similarity index 100% rename from cmd/lotus-sim/stat.go rename to cmd/lotus-sim/info.go From 747b3d3e572ab3f4f504be00acd5e0d44c13681b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 15:20:52 -0700 Subject: [PATCH 26/94] fix(lotus-sim): skip miners without power when loading --- cmd/lotus-sim/simulation/power.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go index f05dadf19..9d0aceafe 100644 --- a/cmd/lotus-sim/simulation/power.go +++ b/cmd/lotus-sim/simulation/power.go @@ -32,6 +32,10 @@ func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (m return nil, err } err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { + // skip miners without power + if claim.RawBytePower.IsZero() { + return nil + } powerTable[miner] = claim return nil }) From 88af350774dfb7563c11f9386b7eebfd66c1ecc8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 17:05:03 -0700 Subject: [PATCH 27/94] fix(lotus-sim): use global base-fee value --- cmd/lotus-sim/simulation/step.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 9eddd039b..b99f318d6 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -111,7 +111,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag Syscalls: ss.StateManager.ChainStore().VMSys(), CircSupplyCalc: ss.StateManager.GetVMCirculatingSupply, NtwkVersion: ss.StateManager.GetNtwkVersion, - BaseFee: abi.NewTokenAmount(0), // FREE! + BaseFee: baseFee, // FREE! LookbackState: stmgr.LookbackStateGetterForTipset(ss.StateManager, parentTs), } vmi, err := vm.NewVM(ctx, vmopt) From 0075abea5e280cc355dc524dff246304d8b8e99d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 13:00:56 -0700 Subject: [PATCH 28/94] fix(vm): always specify an ActorErr when ApplyMessage fails. This case shouldn't actually happen, but we might as well be consistent. --- chain/vm/vm.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 1b8424eee..9f9398630 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -439,6 +439,8 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, }, GasCosts: &gasOutputs, Duration: time.Since(start), + ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, + "message gas limit does not cover on-chain gas costs"), }, nil } From 86e459f58502833ffb8d49082026123ec291110c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 13:47:38 -0700 Subject: [PATCH 29/94] feat(lotus-sim): return receipt Instead of returning a "full" boolean and an error, return a receipt and an error. We now use the error to indicate if the block is "full". --- cmd/lotus-sim/simulation/precommit.go | 45 +++--- cmd/lotus-sim/simulation/provecommit.go | 173 +++++++++++++----------- cmd/lotus-sim/simulation/step.go | 34 +++-- cmd/lotus-sim/simulation/wdpost.go | 12 +- 4 files changed, 148 insertions(+), 116 deletions(-) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 38b745a52..b048aa66c 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -31,8 +31,11 @@ func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { } // packPreCommits packs pre-commit messages until the block is full. -func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (full bool, _err error) { - var top1Count, top10Count, restCount int +func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_err error) { + var ( + full bool + top1Count, top10Count, restCount int + ) defer func() { if _err != nil { return @@ -74,16 +77,20 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful restMiners++ default: // Well, we've run through all miners. - return false, nil + return nil } - added, full, err := ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize) + var ( + added int + err error + ) + 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) + return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) } *count += added if full { - return true, nil + return nil } } } @@ -111,17 +118,15 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if big.Cmp(minerBalance, minFunds) < 0 { - full, err := cb(&types.Message{ + if _, 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 { + }); err == ErrOutOfGas { return 0, true, nil + } else if err != nil { + return 0, false, xerrors.Errorf("failed to fund miner %s: %w", minerAddr, err) } } @@ -168,18 +173,18 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } // NOTE: just in-case, sendAndFund will "fund" and re-try for any message // that fails due to "insufficient funds". - if full, err := sendAndFund(cb, &types.Message{ + if _, 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 { + }); err == ErrOutOfGas { // try again with a smaller batch. targetBatchSize /= 2 continue + } else if err != nil { + return 0, false, err } for _, info := range batch { @@ -196,14 +201,16 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, if err != nil { return 0, false, err } - if full, err := sendAndFund(cb, &types.Message{ + if _, 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 + }); err == ErrOutOfGas { + return added, true, nil + } else if err != nil { + return added, false, err } if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 208af38a7..1a615d830 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -23,11 +23,11 @@ import ( // packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the // block or runs out. -func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_full bool, _err error) { +func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_err error) { // Roll the commitQueue forward. ss.commitQueue.advanceEpoch(ss.nextEpoch()) - var failed, done, unbatched, count int + var full, failed, done, unbatched, count int defer func() { if _err != nil { return @@ -39,32 +39,33 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ "failed", failed, "unbatched", unbatched, "miners-processed", count, - "filled-block", _full, + "filled-block", full, ) }() for { addr, pending, ok := ss.commitQueue.nextMiner() if !ok { - return false, nil + return nil } - res, full, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) + res, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) if err != nil { - return false, err + return err } failed += res.failed done += res.done unbatched += res.unbatched count++ - if full { - return true, nil + if res.full { + return nil } } } type proveCommitResult struct { done, failed, unbatched int + full bool } // sendAndFund "packs" the given message, funding the actor if necessary. It: @@ -75,25 +76,24 @@ type proveCommitResult struct { // somewhere) and re-tries the message.0 // // NOTE: If the message fails a second time, the funds won't be "unsent". -func sendAndFund(send packFunc, msg *types.Message) (bool, error) { - full, err := send(msg) +func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { + res, err := send(msg) aerr, ok := err.(aerrors.ActorError) if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { - return full, err + return res, err } // Ok, insufficient funds. Let's fund this miner and try again. - full, err = send(&types.Message{ + _, 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 + if err != ErrOutOfGas { + err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + return nil, err } return send(msg) } @@ -105,10 +105,10 @@ func sendAndFund(send packFunc, msg *types.Message) (bool, error) { func (ss *simulationState) packProveCommitsMiner( ctx context.Context, cb packFunc, minerAddr address.Address, pending minerPendingCommits, -) (res proveCommitResult, full bool, _err error) { +) (res proveCommitResult, _err error) { info, err := ss.getMinerInfo(ctx, minerAddr) if err != nil { - return res, false, err + return res, err } nv := ss.StateManager.GetNtwkVersion(ctx, ss.nextEpoch()) @@ -123,7 +123,7 @@ func (ss *simulationState) packProveCommitsMiner( proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) if err != nil { - return res, false, err + return res, err } params := miner5.ProveCommitAggregateParams{ @@ -136,24 +136,27 @@ func (ss *simulationState) packProveCommitsMiner( enc, err := actors.SerializeParams(¶ms) if err != nil { - return res, false, err + return res, err } - if full, err := sendAndFund(cb, &types.Message{ + if _, err := sendAndFund(cb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), Method: miner.Methods.ProveCommitAggregate, Params: enc, - }); err != nil { + }); err == nil { + res.done += len(batch) + } else if err == ErrOutOfGas { + res.full = true + return res, nil + } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { // If we get a random error, or a fatal actor error, bail. - aerr, ok := err.(aerrors.ActorError) - if !ok || aerr.IsFatal() { - return res, false, err - } - // If we get a "not-found" error, try to remove any missing - // prove-commits and continue. This can happen either - // because: + return res, err + } else if aerr.RetCode() == exitcode.ErrNotFound || aerr.RetCode() == exitcode.ErrIllegalArgument { + // If we get a "not-found" or illegal argument error, try to + // remove any missing prove-commits and continue. This can + // happen either because: // // 1. The pre-commit failed on execution (but not when // packing). This shouldn't happen, but we might as well @@ -161,34 +164,56 @@ func (ss *simulationState) packProveCommitsMiner( // 2. The pre-commit has expired. We'd have to be really // backloged to hit this case, but we might as well handle // it. - if aerr.RetCode() == exitcode.ErrNotFound { - // First, split into "good" and "missing" - good, err := ss.filterProveCommits(ctx, minerAddr, batch) - if err != nil { - log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) - // fail with the original error. - return res, false, aerr - } - removed := len(batch) - len(good) - // If they're all missing, skip. If they're all good, skip too (and log). - if len(good) > 0 && removed > 0 { - res.failed += removed - - // update the pending sector numbers in-place to remove the expired ones. - snos = snos[removed:] - copy(snos, good) - pending.finish(sealType, removed) - - log.Errorw("failed to prove commit expired/missing pre-commits", - "error", aerr, - "miner", minerAddr, - "discarded", removed, - "kept", len(good), - "epoch", ss.nextEpoch(), - ) - continue - } + // First, split into "good" and "missing" + good, err := ss.filterProveCommits(ctx, minerAddr, batch) + if err != nil { + log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) + // fail with the original error. + return res, aerr } + removed := len(batch) - len(good) + if removed == 0 { + log.Errorw("failed to prove-commit for unknown reasons", + "error", aerr, + "miner", minerAddr, + "sectors", batch, + "epoch", ss.nextEpoch(), + ) + res.failed += len(batch) + } else if len(good) == 0 { + log.Errorw("failed to prove commit missing pre-commits", + "error", aerr, + "miner", minerAddr, + "discarded", removed, + "epoch", ss.nextEpoch(), + ) + res.failed += len(batch) + } else { + // update the pending sector numbers in-place to remove the expired ones. + snos = snos[removed:] + copy(snos, good) + pending.finish(sealType, removed) + + log.Errorw("failed to prove commit expired/missing pre-commits", + "error", aerr, + "miner", minerAddr, + "discarded", removed, + "kept", len(good), + "epoch", ss.nextEpoch(), + ) + res.failed += removed + + // Then try again. + continue + } + log.Errorw("failed to prove commit missing sector(s)", + "error", err, + "miner", minerAddr, + "sectors", batch, + "epoch", ss.nextEpoch(), + ) + res.failed += len(batch) + } else { log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, @@ -196,13 +221,9 @@ func (ss *simulationState) packProveCommitsMiner( "epoch", ss.nextEpoch(), ) res.failed += len(batch) - } else if full { - return res, true, nil - } else { - res.done += len(batch) } - pending.finish(sealType, batchSize) - snos = snos[batchSize:] + pending.finish(sealType, len(batch)) + snos = snos[len(batch):] } } for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch { @@ -211,7 +232,7 @@ func (ss *simulationState) packProveCommitsMiner( proof, err := mockSealProof(sealType, minerAddr) if err != nil { - return res, false, err + return res, err } params := miner.ProveCommitSectorParams{ SectorNumber: sno, @@ -219,18 +240,23 @@ func (ss *simulationState) packProveCommitsMiner( } enc, err := actors.SerializeParams(¶ms) if err != nil { - return res, false, err + return res, err } - if full, err := sendAndFund(cb, &types.Message{ + if _, 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 - } + }); err == nil { + res.unbatched++ + res.done++ + } else if err == ErrOutOfGas { + res.full = true + return res, nil + } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return res, err + } else { log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, @@ -238,18 +264,13 @@ func (ss *simulationState) packProveCommitsMiner( "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 + return res, nil } // loadProveCommitsMiner enqueue all pending prove-commits for the given miner. This is called on diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index b99f318d6..e076b7b86 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -2,6 +2,7 @@ package simulation import ( "context" + "errors" "reflect" "runtime" "strings" @@ -61,7 +62,13 @@ func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { return head, nil } -type packFunc func(*types.Message) (full bool, err error) +var ErrOutOfGas = errors.New("out of gas") + +// packFunc takes a message and attempts to pack it into a block. +// +// - If the block is full, returns the error ErrOutOfGas. +// - If message execution fails, check if error is an ActorError to get the return code. +type packFunc func(*types.Message) (*types.MessageReceipt, error) // popNextMessages generates/picks a set of messages to be included in the next block. // @@ -130,9 +137,9 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag vmStore := vmi.ActorStore(ctx) var gasTotal int64 var messages []*types.Message - tryPushMsg := func(msg *types.Message) (bool, error) { + tryPushMsg := func(msg *types.Message) (*types.MessageReceipt, error) { if gasTotal >= targetGas { - return true, nil + return nil, ErrOutOfGas } // Copy the message before we start mutating it. @@ -142,17 +149,17 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag actor, err := st.GetActor(msg.From) if err != nil { - return false, err + return nil, err } msg.Nonce = actor.Nonce if msg.From.Protocol() == address.ID { state, err := account.Load(vmStore, actor) if err != nil { - return false, err + return nil, err } msg.From, err = state.PubkeyAddress() if err != nil { - return false, err + return nil, err } } @@ -172,17 +179,17 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag ret, err := vmi.ApplyMessage(ctx, msg) if err != nil { _ = st.Revert() - return false, err + return nil, err } if ret.ActorErr != nil { _ = st.Revert() - return false, ret.ActorErr + return nil, ret.ActorErr } // Sometimes there are bugs. Let's catch them. if ret.GasUsed == 0 { _ = st.Revert() - return false, xerrors.Errorf("used no gas", + return nil, xerrors.Errorf("used no gas", "msg", msg, "ret", ret, ) @@ -195,7 +202,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag newTotal := gasTotal + ret.GasUsed if newTotal > targetGas { _ = st.Revert() - return true, nil + return nil, ErrOutOfGas } gasTotal = newTotal @@ -203,7 +210,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag msg.GasLimit = ret.GasUsed messages = append(messages, msg) - return false, nil + return &ret.MessageReceipt, nil } // Finally, we generate a set of messages to be included in @@ -232,7 +239,7 @@ func functionName(fn interface{}) string { // true). // TODO: Make this more configurable for other simulations. func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error { - type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error) + type messageGenerator func(ctx context.Context, cb packFunc) error // We pack messages in-order: // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only @@ -248,8 +255,7 @@ func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error for _, mgen := range messageGenerators { // We're intentionally ignoring the "full" signal so we can try to pack a few more // messages. - _, err := mgen(ctx, cb) - if err != nil { + if err := mgen(ctx, cb); err != nil && !xerrors.Is(err, ErrOutOfGas) { return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) } } diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index c940c8d51..3e8d482ff 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -28,10 +28,10 @@ func (sim *Simulation) postChainCommitInfo(ctx context.Context, epoch abi.ChainE // packWindowPoSts packs window posts until either the block is full or all healty sectors // have been proven. It does not recover sectors. -func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (full bool, _err error) { +func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (_err error) { // Push any new window posts into the queue. if err := ss.queueWindowPoSts(ctx); err != nil { - return false, err + return err } done := 0 failed := 0 @@ -50,9 +50,9 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu // Then pack as many as we can. for len(ss.pendingWposts) > 0 { next := ss.pendingWposts[0] - if full, err := cb(next); err != nil { + if _, err := cb(next); err != nil { if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { - return false, err + return err } log.Errorw("failed to submit windowed post", "error", err, @@ -60,8 +60,6 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu "epoch", ss.nextEpoch(), ) failed++ - } else if full { - return true, nil } else { done++ } @@ -69,7 +67,7 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu ss.pendingWposts = ss.pendingWposts[1:] } ss.pendingWposts = nil - return false, nil + return nil } // stepWindowPoStsMiner enqueues all missing window posts for the current epoch for the given miner. From e7b1e09ade1e118a5ee27bef4fe4aa0805e24425 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 14:53:14 -0700 Subject: [PATCH 30/94] feat(multisig): expose ApproveReturn --- chain/actors/builtin/multisig/multisig.go | 1 + 1 file changed, 1 insertion(+) diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index ae6a9daf3..82f1963be 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -185,6 +185,7 @@ type MessageBuilder interface { // this type is the same between v0 and v2 type ProposalHashData = msig5.ProposalHashData type ProposeReturn = msig5.ProposeReturn +type ApproveReturn = msig5.ApproveReturn type ProposeParams = msig5.ProposeParams func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { From 2b4f865665a879f3e158d741ddff85e3a9470a39 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 14:51:56 -0700 Subject: [PATCH 31/94] feat(lotus-sim): fund from a funding account Instead of funding the simulation from the burnt-funds actor, fund from a custom account and take a "tax" to fund that account. Otherwise, we run out of funds... --- cmd/lotus-sim/simulation/funding.go | 287 ++++++++++++++++++++++++ cmd/lotus-sim/simulation/precommit.go | 16 +- cmd/lotus-sim/simulation/provecommit.go | 32 --- cmd/lotus-sim/simulation/step.go | 6 +- 4 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 cmd/lotus-sim/simulation/funding.go diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go new file mode 100644 index 000000000..1d57d8d0f --- /dev/null +++ b/cmd/lotus-sim/simulation/funding.go @@ -0,0 +1,287 @@ +package simulation + +import ( + "bytes" + "context" + "sort" + "time" + + "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/exitcode" + "golang.org/x/xerrors" + + "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/multisig" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + fundAccount = func() address.Address { + addr, err := address.NewIDAddress(100) + if err != nil { + panic(err) + } + return addr + }() + minFundAcctFunds = abi.TokenAmount(types.MustParseFIL("1000000FIL")) + maxFundAcctFunds = abi.TokenAmount(types.MustParseFIL("100000000FIL")) + taxMin = abi.TokenAmount(types.MustParseFIL("1000FIL")) +) + +func fund(send packFunc, target address.Address) error { + _, err := send(&types.Message{ + From: fundAccount, + To: target, + Value: targetFunds, + Method: builtin.MethodSend, + }) + return err +} + +// sendAndFund "packs" the given message, funding the actor if necessary. It: +// +// 1. Tries to send the given message. +// 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. +// 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from +// somewhere) and re-tries the message.0 +// +// NOTE: If the message fails a second time, the funds won't be "unsent". +func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { + res, err := send(msg) + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { + return res, err + } + // Ok, insufficient funds. Let's fund this miner and try again. + err = fund(send, msg.To) + if err != nil { + if err != ErrOutOfGas { + err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + return nil, err + } + return send(msg) +} + +func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err error) { + st, err := ss.stateTree(ctx) + if err != nil { + return err + } + fundAccActor, err := st.GetActor(fundAccount) + if err != nil { + return err + } + if minFundAcctFunds.LessThan(fundAccActor.Balance) { + return nil + } + + // Ok, we're going to go fund this thing. + start := time.Now() + + type actor struct { + types.Actor + Address address.Address + } + + var targets []*actor + err = st.ForEach(func(addr address.Address, act *types.Actor) error { + if act.Balance.LessThan(taxMin) { + return nil + } + if !(builtin.IsAccountActor(act.Code) || builtin.IsMultisigActor(act.Code)) { + return nil + } + targets = append(targets, &actor{*act, addr}) + return nil + }) + if err != nil { + return err + } + + balance := fundAccActor.Balance.Copy() + + sort.Slice(targets, func(i, j int) bool { + return targets[i].Balance.GreaterThan(targets[j].Balance) + }) + + store := ss.Chainstore.ActorStore(ctx) + + epoch := ss.nextEpoch() + + nv := ss.StateManager.GetNtwkVersion(ctx, epoch) + actorsVersion := actors.VersionForNetwork(nv) + + var accounts, multisigs int + defer func() { + if _err != nil { + return + } + log.Infow("finished funding the simulation", + "duration", time.Since(start), + "targets", len(targets), + "epoch", epoch, + "new-balance", types.FIL(balance), + "old-balance", types.FIL(fundAccActor.Balance), + "multisigs", multisigs, + "accounts", accounts, + ) + }() + + for _, actor := range targets { + switch { + case builtin.IsAccountActor(actor.Code): + if _, err := cb(&types.Message{ + From: actor.Address, + To: fundAccount, + Value: actor.Balance, + }); err == ErrOutOfGas { + return nil + } else if err != nil { + return err + } + accounts++ + case builtin.IsMultisigActor(actor.Code): + msigState, err := multisig.Load(store, &actor.Actor) + if err != nil { + return err + } + + threshold, err := msigState.Threshold() + if err != nil { + return err + } + + if threshold > 16 { + log.Debugw("ignoring multisig with high threshold", + "multisig", actor.Address, + "threshold", threshold, + "max", 16, + ) + continue + } + + locked, err := msigState.LockedBalance(epoch) + if err != nil { + return err + } + + if locked.LessThan(taxMin) { + continue // not worth it. + } + + allSigners, err := msigState.Signers() + if err != nil { + return err + } + signers := make([]address.Address, 0, threshold) + for _, signer := range allSigners { + actor, err := st.GetActor(signer) + if err != nil { + return err + } + if !builtin.IsAccountActor(actor.Code) { + // I am so not dealing with this mess. + continue + } + if uint64(len(signers)) >= threshold { + break + } + } + // Ok, we're not dealing with this one. + if uint64(len(signers)) < threshold { + continue + } + + available := big.Sub(actor.Balance, locked) + + var txnId uint64 + { + msg, err := multisig.Message(actorsVersion, signers[0]).Propose( + actor.Address, fundAccount, available, + builtin.MethodSend, nil, + ) + if err != nil { + return err + } + res, err := cb(msg) + if err != nil { + if err == ErrOutOfGas { + err = nil + } + return err + } + var ret multisig.ProposeReturn + err = ret.UnmarshalCBOR(bytes.NewReader(res.Return)) + if err != nil { + return err + } + if ret.Applied { + if !ret.Code.IsSuccess() { + log.Errorw("failed to tax multisig", + "multisig", actor.Address, + "exitcode", ret.Code, + ) + } + break + } + txnId = uint64(ret.TxnID) + } + var ret multisig.ProposeReturn + for _, signer := range signers[1:] { + msg, err := multisig.Message(actorsVersion, signer).Approve(actor.Address, txnId, nil) + if err != nil { + return err + } + res, err := cb(msg) + if err != nil { + if err == ErrOutOfGas { + err = nil + } + return err + } + var ret multisig.ProposeReturn + err = ret.UnmarshalCBOR(bytes.NewReader(res.Return)) + if err != nil { + return err + } + // A bit redundant, but nice. + if ret.Applied { + break + } + + } + if !ret.Applied { + log.Errorw("failed to apply multisig transaction", + "multisig", actor.Address, + "txnid", txnId, + "signers", len(signers), + "threshold", threshold, + ) + continue + } + if !ret.Code.IsSuccess() { + log.Errorw("failed to tax multisig", + "multisig", actor.Address, + "txnid", txnId, + "exitcode", ret.Code, + ) + } else { + multisigs++ + } + default: + panic("impossible case") + } + balance = big.Int{Int: balance.Add(balance.Int, actor.Balance.Int)} + if balance.GreaterThanEqual(maxFundAcctFunds) { + // There's no need to get greedy. + // Well, really, we're trying to avoid messing with state _too_ much. + return nil + } + } + return nil +} diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index b048aa66c..23534c257 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -9,7 +9,6 @@ import ( "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" @@ -118,15 +117,12 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if big.Cmp(minerBalance, minFunds) < 0 { - if _, err := cb(&types.Message{ - From: builtin.BurntFundsActorAddr, - To: minerAddr, - Value: targetFunds, - Method: builtin.MethodSend, - }); err == ErrOutOfGas { - return 0, true, nil - } else if err != nil { - return 0, false, xerrors.Errorf("failed to fund miner %s: %w", minerAddr, err) + err := fund(cb, minerAddr) + if err != nil { + if err == ErrOutOfGas { + return 0, true, nil + } + return 0, false, err } } diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 1a615d830..d40b8714b 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -4,7 +4,6 @@ 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" @@ -12,7 +11,6 @@ import ( "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" @@ -68,36 +66,6 @@ type proveCommitResult struct { full bool } -// sendAndFund "packs" the given message, funding the actor if necessary. It: -// -// 1. Tries to send the given message. -// 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. -// 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from -// somewhere) and re-tries the message.0 -// -// NOTE: If the message fails a second time, the funds won't be "unsent". -func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { - res, err := send(msg) - aerr, ok := err.(aerrors.ActorError) - if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { - return res, err - } - // Ok, insufficient funds. Let's fund this miner and try again. - _, err = send(&types.Message{ - From: builtin.BurntFundsActorAddr, - To: msg.To, - Value: targetFunds, - Method: builtin.MethodSend, - }) - if err != nil { - if err != ErrOutOfGas { - err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) - } - return nil, err - } - return send(msg) -} - // packProveCommitsMiner enqueues a prove commits from the given miner until it runs out of // available prove-commits, batching as much as possible. // diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index e076b7b86..c7b5ecdf2 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -244,10 +244,12 @@ func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error // We pack messages in-order: // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only // miss them if/when we run out of chain bandwidth. - // 2. Prove commits. We do this eagerly to ensure they don't expire. - // 3. Finally, we fill the rest of the space with pre-commits. + // 2. We then move funds to our "funding" account, if it's running low. + // 3. Prove commits. We do this eagerly to ensure they don't expire. + // 4. Finally, we fill the rest of the space with pre-commits. messageGenerators := []messageGenerator{ ss.packWindowPoSts, + ss.packFunding, ss.packProveCommits, ss.packPreCommits, } From 868231adc751daa1d0661dfa8764d7b8bfb69c8d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 17:09:03 -0700 Subject: [PATCH 32/94] fix(lotus-sim): don't take from the fund account when funding --- cmd/lotus-sim/simulation/funding.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index 1d57d8d0f..1648a0be9 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -90,6 +90,10 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e var targets []*actor err = st.ForEach(func(addr address.Address, act *types.Actor) error { + // Don't steal from ourselves! + if addr == fundAccount { + return nil + } if act.Balance.LessThan(taxMin) { return nil } From 1802ae31a6de48dd713e192e9c87ad746b9d2a92 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 19:35:22 -0700 Subject: [PATCH 33/94] feat(lotus-sim): record timing information for pre/prove-commit packing --- cmd/lotus-sim/simulation/precommit.go | 3 +++ cmd/lotus-sim/simulation/provecommit.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 23534c257..02ea45ca1 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -3,6 +3,7 @@ package simulation import ( "context" "fmt" + "time" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -35,6 +36,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_er full bool top1Count, top10Count, restCount int ) + start := time.Now() defer func() { if _err != nil { return @@ -45,6 +47,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_er "top10", top10Count, "rest", restCount, "filled-block", full, + "duration", time.Since(start), ) }() diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index d40b8714b..5ba4a8f00 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -2,6 +2,7 @@ package simulation import ( "context" + "time" "github.com/filecoin-project/go-bitfield" @@ -25,6 +26,7 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ // Roll the commitQueue forward. ss.commitQueue.advanceEpoch(ss.nextEpoch()) + start := time.Now() var full, failed, done, unbatched, count int defer func() { if _err != nil { @@ -38,6 +40,7 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ "unbatched", unbatched, "miners-processed", count, "filled-block", full, + "duration", time.Since(start), ) }() From 2b77c1754626521534f1ea3bc2f11282f1ce11d0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 19:40:00 -0700 Subject: [PATCH 34/94] chore(lotus-sim): fix import grouping I got a bit lazy when developing this. --- cmd/lotus-sim/create.go | 3 ++- cmd/lotus-sim/info.go | 1 + cmd/lotus-sim/main.go | 3 ++- cmd/lotus-sim/simulation/block.go | 3 ++- cmd/lotus-sim/simulation/commit_queue.go | 1 + cmd/lotus-sim/simulation/commit_queue_test.go | 4 +++- cmd/lotus-sim/simulation/funding.go | 3 ++- cmd/lotus-sim/simulation/messages.go | 3 ++- cmd/lotus-sim/simulation/mock.go | 3 ++- cmd/lotus-sim/simulation/precommit.go | 12 +++++++----- cmd/lotus-sim/simulation/provecommit.go | 10 +++++----- cmd/lotus-sim/simulation/simulation.go | 10 +++++----- cmd/lotus-sim/simulation/state.go | 1 + cmd/lotus-sim/simulation/step.go | 3 ++- cmd/lotus-sim/simulation/wdpost.go | 4 ++-- cmd/lotus-sim/upgrade.go | 3 ++- 16 files changed, 41 insertions(+), 26 deletions(-) diff --git a/cmd/lotus-sim/create.go b/cmd/lotus-sim/create.go index 777f1723c..cfd93c789 100644 --- a/cmd/lotus-sim/create.go +++ b/cmd/lotus-sim/create.go @@ -3,9 +3,10 @@ package main import ( "fmt" + "github.com/urfave/cli/v2" + "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" - "github.com/urfave/cli/v2" ) var createSimCommand = &cli.Command{ diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index b51853b31..9b4e9daaf 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -9,6 +9,7 @@ import ( "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index bfcad728d..5c954a8d6 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -7,8 +7,9 @@ import ( "os/signal" "syscall" - logging "github.com/ipfs/go-log/v2" "github.com/urfave/cli/v2" + + logging "github.com/ipfs/go-log/v2" ) var root []*cli.Command = []*cli.Command{ diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 31e7a1a79..6b3c96e78 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -6,9 +6,10 @@ import ( "encoding/binary" "time" + "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" - "golang.org/x/xerrors" ) const beaconPrefix = "mockbeacon:" diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go index 4cfb6e764..75dc6f034 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -5,6 +5,7 @@ import ( "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" ) diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/commit_queue_test.go index 1a7bd2749..7c6bc6c8f 100644 --- a/cmd/lotus-sim/simulation/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/commit_queue_test.go @@ -3,11 +3,13 @@ package simulation import ( "testing" + "github.com/stretchr/testify/require" + "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" - "github.com/stretchr/testify/require" ) func TestCommitQueue(t *testing.T) { diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index 1648a0be9..a88d07ae8 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -6,11 +6,12 @@ import ( "sort" "time" + "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/go-state-types/exitcode" - "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 8c12cac1a..08e4c12d2 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -3,10 +3,11 @@ package simulation import ( "context" - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/lotus/chain/types" ) diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock.go index b81ee8629..37f0a2c6c 100644 --- a/cmd/lotus-sim/simulation/mock.go +++ b/cmd/lotus-sim/simulation/mock.go @@ -8,9 +8,10 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) // Ideally, we'd use extern/sector-storage/mock. Unfortunately, those mocks are a bit _too_ accurate diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 02ea45ca1..41c3f363e 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -5,19 +5,21 @@ import ( "fmt" "time" + "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/go-state-types/network" + "github.com/ipfs/go-cid" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + tutils "github.com/filecoin-project/specs-actors/v5/support/testing" + "github.com/filecoin-project/lotus/chain/actors" "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" ) var ( diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 5ba4a8f00..2916c7eaa 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -4,20 +4,20 @@ import ( "context" "time" - "github.com/filecoin-project/go-bitfield" - "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/network" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" + "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/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" ) // packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 4b571a808..dbce16f4e 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -6,19 +6,19 @@ import ( "golang.org/x/xerrors" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" "github.com/ipfs/go-cid" "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/network" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "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" - - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" ) var log = logging.Logger("simulation") diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index 23de7038c..a45e1ac45 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -6,6 +6,7 @@ import ( "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" ) diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index c7b5ecdf2..1106e0d6e 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -7,10 +7,11 @@ import ( "runtime" "strings" - "github.com/filecoin-project/go-address" "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" "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" diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index 3e8d482ff..7e6f2401e 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -11,12 +11,12 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + "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" ) // postChainCommitInfo returns th diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 17993f847..3a30e869b 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -6,9 +6,10 @@ import ( "strings" "text/tabwriter" + "github.com/urfave/cli/v2" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/network" - "github.com/urfave/cli/v2" ) var upgradeCommand = &cli.Command{ From dcdb0abe271694ec86c6825ad73d7a3e414b62a1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:27:33 -0700 Subject: [PATCH 35/94] feat(lotus-sim): profile on SIGUSR2 --- cmd/lotus-sim/profile.go | 87 ++++++++++++++++++++++++++++++++++++++++ cmd/lotus-sim/run.go | 3 ++ 2 files changed, 90 insertions(+) create mode 100644 cmd/lotus-sim/profile.go diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go new file mode 100644 index 000000000..e18fd22f6 --- /dev/null +++ b/cmd/lotus-sim/profile.go @@ -0,0 +1,87 @@ +package main + +import ( + "archive/tar" + "context" + "fmt" + "io" + "os" + "os/signal" + "runtime/pprof" + "time" + + "github.com/urfave/cli/v2" +) + +func takeProfiles(ctx context.Context) (fname string, _err error) { + file, err := os.CreateTemp(".", ".profiles*.tar") + if err != nil { + return "", err + } + defer file.Close() + + if err := writeProfiles(ctx, file); err != nil { + _ = os.Remove(file.Name()) + return "", err + } + + fname = fmt.Sprintf("pprof-simulation-%s.tar", time.Now()) + if err := os.Rename(file.Name(), fname); err != nil { + _ = os.Remove(file.Name()) + return "", err + } + return fname, nil +} + +func writeProfiles(ctx context.Context, w io.Writer) error { + tw := tar.NewWriter(w) + for _, profile := range pprof.Profiles() { + if err := tw.WriteHeader(&tar.Header{Name: profile.Name()}); err != nil { + return err + } + if err := profile.WriteTo(tw, 0); err != nil { + return err + } + if err := ctx.Err(); err != nil { + return err + } + } + + if err := tw.WriteHeader(&tar.Header{Name: "cpu"}); err != nil { + return err + } + if err := pprof.StartCPUProfile(tw); err != nil { + return err + } + select { + case <-time.After(30 * time.Second): + case <-ctx.Done(): + pprof.StopCPUProfile() + return ctx.Err() + } + pprof.StopCPUProfile() + return tw.Close() +} + +func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, signals...) + defer signal.Stop(ch) + + for range ch { + select { + case <-ch: + fname, err := takeProfiles(cctx.Context) + switch err { + case context.Canceled: + return + case nil: + fmt.Fprintf(cctx.App.ErrWriter, "Wrote profile to %q\n", fname) + default: + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to write profile: %s\n", err) + } + case <-cctx.Done(): + return + } + } +} diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 479ce898d..f696243fa 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "syscall" "github.com/urfave/cli/v2" ) @@ -22,6 +23,8 @@ var runSimCommand = &cli.Command{ } defer node.Close() + go profileOnSignal(cctx, syscall.SIGUSR2) + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { return err From 936659d087ecba1f66b503e6e13900a266d6868e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:27:48 -0700 Subject: [PATCH 36/94] feat(lotus-sim): print info on SIGUSR1 --- cmd/lotus-sim/info.go | 85 +++++++++++++++++++++++-------------------- cmd/lotus-sim/run.go | 19 ++++++++++ 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 9b4e9daaf..187c2236e 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "io" "text/tabwriter" "time" @@ -13,6 +14,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" ) func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { @@ -27,6 +29,48 @@ func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet return state.TotalPower() } +func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) error { + powerNow, err := getTotalPower(ctx, sim.StateManager, sim.GetHead()) + if err != nil { + return err + } + powerStart, err := getTotalPower(ctx, sim.StateManager, sim.GetStart()) + if err != nil { + return err + } + powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) + + tw := tabwriter.NewWriter(out, 8, 8, 1, ' ', 0) + + head := sim.GetHead() + start := sim.GetStart() + headEpoch := head.Height() + firstEpoch := start.Height() + 1 + + headTime := time.Unix(int64(head.MinTimestamp()), 0) + startTime := time.Unix(int64(start.MinTimestamp()), 0) + duration := headTime.Sub(startTime) + + // growth rate in size/day + growthRate := big.Div( + big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), + big.NewInt(int64(duration)), + ) + + fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) + fmt.Fprintf(tw, "Head:\t%s\n", head) + fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) + fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) + fmt.Fprintf(tw, "Date:\t%s\n", headTime) + fmt.Fprintf(tw, "Duration:\t%s\n", duration) + fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) + fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) + fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) + return tw.Flush() +} + var infoSimCommand = &cli.Command{ Name: "info", Description: "Output information about the simulation.", @@ -41,45 +85,6 @@ var infoSimCommand = &cli.Command{ if err != nil { return err } - - powerNow, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetHead()) - if err != nil { - return err - } - powerStart, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetStart()) - if err != nil { - return err - } - powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) - - tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) - - head := sim.GetHead() - start := sim.GetStart() - headEpoch := head.Height() - firstEpoch := start.Height() + 1 - - headTime := time.Unix(int64(head.MinTimestamp()), 0) - startTime := time.Unix(int64(start.MinTimestamp()), 0) - duration := headTime.Sub(startTime) - - // growth rate in size/day - growthRate := big.Div( - big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), - big.NewInt(int64(duration)), - ) - - fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) - fmt.Fprintf(tw, "Head:\t%s\n", head) - fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) - fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) - fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) - fmt.Fprintf(tw, "Date:\t%s\n", headTime) - fmt.Fprintf(tw, "Duration:\t%s\n", duration) - fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) - fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) - fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) - fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) - return tw.Flush() + return printInfo(cctx.Context, sim, cctx.App.Writer) }, } diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index f696243fa..388b8f763 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "os" + "os/signal" "syscall" "github.com/urfave/cli/v2" @@ -36,12 +38,29 @@ var runSimCommand = &cli.Command{ } fmt.Fprintln(cctx.App.Writer, "running simulation") targetEpochs := cctx.Int("epochs") + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + defer signal.Stop(ch) + for i := 0; targetEpochs == 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()) + + // Print + select { + case <-ch: + if err := printInfo(cctx.Context, sim, cctx.App.Writer); err != nil { + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to print info: %s\n", err) + } + case <-cctx.Context.Done(): + return cctx.Err() + default: + } } fmt.Fprintln(cctx.App.Writer, "simulation done") return err From 4a80c8353316d4d40fde895c01cb0771fbb0e9f6 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:28:05 -0700 Subject: [PATCH 37/94] fix(lotus-sim): fix spelling --- cmd/lotus-sim/simulation/provecommit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 2916c7eaa..7e2344890 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -20,7 +20,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -// packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the +// packProveCommits packs all prove-commits for all "ready to be proven" sectors until it fills the // block or runs out. func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_err error) { // Roll the commitQueue forward. From 576600237073e0a84dc1eb47c1f84aa32c0ad281 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:31:32 -0700 Subject: [PATCH 38/94] fix(lotus-sim): guard info with dashes --- cmd/lotus-sim/run.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 388b8f763..0be70ff49 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -54,9 +54,11 @@ var runSimCommand = &cli.Command{ // Print select { case <-ch: + fmt.Fprintln(cctx.App.Writer, "---------------------") if err := printInfo(cctx.Context, sim, cctx.App.Writer); err != nil { fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to print info: %s\n", err) } + fmt.Fprintln(cctx.App.Writer, "---------------------") case <-cctx.Context.Done(): return cctx.Err() default: From a3f64e0768f183f8fec5a5ace439fb70c1a9a05e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:35:26 -0700 Subject: [PATCH 39/94] fix(lotus-sim): profile signal handling --- cmd/lotus-sim/profile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go index e18fd22f6..f2058949f 100644 --- a/cmd/lotus-sim/profile.go +++ b/cmd/lotus-sim/profile.go @@ -68,7 +68,7 @@ func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { signal.Notify(ch, signals...) defer signal.Stop(ch) - for range ch { + for { select { case <-ch: fname, err := takeProfiles(cctx.Context) From 7a8bfd87255593349621b61d3ab0040ff261802a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:39:18 -0700 Subject: [PATCH 40/94] doc(lotus-sim): document signals --- cmd/lotus-sim/run.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 0be70ff49..58eeb1a5e 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -10,8 +10,12 @@ import ( ) var runSimCommand = &cli.Command{ - Name: "run", - Description: "Run the simulation.", + Name: "run", + Description: `Run the simulation. + +Signals: +- SIGUSR1: Print information about the current simulation (equivalent to 'lotus-sim info'). +- SIGUSR2: Write a pprof profile to pprof-simulation-$DATE.tar`, Flags: []cli.Flag{ &cli.IntFlag{ Name: "epochs", From 977bf1cad992e803ea9374a1b7e9a8962c76a556 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:51:59 -0700 Subject: [PATCH 41/94] fix(lotus-sim): write pprof profiles to a directory We need to know the sizes up-front for tar, and that's not happening. --- cmd/lotus-sim/profile.go | 43 +++++++++++++++++++++++----------------- cmd/lotus-sim/run.go | 2 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go index f2058949f..63e0ef3bd 100644 --- a/cmd/lotus-sim/profile.go +++ b/cmd/lotus-sim/profile.go @@ -1,12 +1,11 @@ package main import ( - "archive/tar" "context" "fmt" - "io" "os" "os/signal" + "path/filepath" "runtime/pprof" "time" @@ -14,32 +13,35 @@ import ( ) func takeProfiles(ctx context.Context) (fname string, _err error) { - file, err := os.CreateTemp(".", ".profiles*.tar") + dir, err := os.MkdirTemp(".", ".profiles-temp*") if err != nil { return "", err } - defer file.Close() - if err := writeProfiles(ctx, file); err != nil { - _ = os.Remove(file.Name()) + if err := writeProfiles(ctx, dir); err != nil { + _ = os.RemoveAll(dir) return "", err } - fname = fmt.Sprintf("pprof-simulation-%s.tar", time.Now()) - if err := os.Rename(file.Name(), fname); err != nil { - _ = os.Remove(file.Name()) + fname = fmt.Sprintf("pprof-simulation-%s", time.Now().Format(time.RFC3339)) + if err := os.Rename(dir, fname); err != nil { + _ = os.RemoveAll(dir) return "", err } return fname, nil } -func writeProfiles(ctx context.Context, w io.Writer) error { - tw := tar.NewWriter(w) +func writeProfiles(ctx context.Context, dir string) error { for _, profile := range pprof.Profiles() { - if err := tw.WriteHeader(&tar.Header{Name: profile.Name()}); err != nil { + file, err := os.Create(filepath.Join(dir, profile.Name()+".pprof.gz")) + if err != nil { return err } - if err := profile.WriteTo(tw, 0); err != nil { + if err := profile.WriteTo(file, 0); err != nil { + _ = file.Close() + return err + } + if err := file.Close(); err != nil { return err } if err := ctx.Err(); err != nil { @@ -47,20 +49,25 @@ func writeProfiles(ctx context.Context, w io.Writer) error { } } - if err := tw.WriteHeader(&tar.Header{Name: "cpu"}); err != nil { + file, err := os.Create(filepath.Join(dir, "cpu.pprof.gz")) + if err != nil { return err } - if err := pprof.StartCPUProfile(tw); err != nil { + + if err := pprof.StartCPUProfile(file); err != nil { + _ = file.Close() return err } select { case <-time.After(30 * time.Second): case <-ctx.Done(): - pprof.StopCPUProfile() - return ctx.Err() } pprof.StopCPUProfile() - return tw.Close() + err = file.Close() + if err := ctx.Err(); err != nil { + return err + } + return err } func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 58eeb1a5e..ba6534b4b 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -15,7 +15,7 @@ var runSimCommand = &cli.Command{ Signals: - SIGUSR1: Print information about the current simulation (equivalent to 'lotus-sim info'). -- SIGUSR2: Write a pprof profile to pprof-simulation-$DATE.tar`, +- SIGUSR2: Write pprof profiles to ./pprof-simulation-$DATE/`, Flags: []cli.Flag{ &cli.IntFlag{ Name: "epochs", From c18ca60d288ef7c2f50e3c100f215898928abe5f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:56:47 -0700 Subject: [PATCH 42/94] fix(lotus-sim): specify ErrWriter Apparently, it defaults to nil... --- cmd/lotus-sim/main.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 5c954a8d6..c785f4045 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -29,9 +29,11 @@ func main() { _ = logging.SetLogLevel("simulation", "DEBUG") } app := &cli.App{ - Name: "lotus-sim", - Usage: "A tool to simulate a network.", - Commands: root, + Name: "lotus-sim", + Usage: "A tool to simulate a network.", + Commands: root, + Writer: os.Stdout, + ErrWriter: os.Stderr, Flags: []cli.Flag{ &cli.StringFlag{ Name: "repo", From be713ec04a8207a9309c94426a6a9c8222b26629 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 22:13:38 -0700 Subject: [PATCH 43/94] fix(lotus-sim): we always fill the block with pre-commits --- cmd/lotus-sim/simulation/provecommit.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 7e2344890..12c67ba8b 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -27,7 +27,7 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ ss.commitQueue.advanceEpoch(ss.nextEpoch()) start := time.Now() - var full, failed, done, unbatched, count int + var failed, done, unbatched, count int defer func() { if _err != nil { return @@ -39,7 +39,6 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ "failed", failed, "unbatched", unbatched, "miners-processed", count, - "filled-block", full, "duration", time.Since(start), ) }() From 783dc5a33da34afbd9bd3c00c743df25e144aab1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 07:39:29 -0700 Subject: [PATCH 44/94] fix(lotus-sim): fund multiple times Sometimes, a miner is deep in the red. --- cmd/lotus-sim/simulation/funding.go | 44 ++++++++++++++++----------- cmd/lotus-sim/simulation/precommit.go | 2 +- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index a88d07ae8..a58b8c0e4 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -33,11 +33,18 @@ var ( taxMin = abi.TokenAmount(types.MustParseFIL("1000FIL")) ) -func fund(send packFunc, target address.Address) error { +func fund(send packFunc, target address.Address, times int) error { + amt := targetFunds + if times >= 1 { + if times >= 8 { + times = 8 // cap + } + amt = big.Lsh(amt, uint(times)) + } _, err := send(&types.Message{ From: fundAccount, To: target, - Value: targetFunds, + Value: amt, Method: builtin.MethodSend, }) return err @@ -49,23 +56,26 @@ func fund(send packFunc, target address.Address) error { // 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. // 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from // somewhere) and re-tries the message.0 -// -// NOTE: If the message fails a second time, the funds won't be "unsent". -func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { - res, err := send(msg) - aerr, ok := err.(aerrors.ActorError) - if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { - return res, err - } - // Ok, insufficient funds. Let's fund this miner and try again. - err = fund(send, msg.To) - if err != nil { - if err != ErrOutOfGas { - err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) +func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, err error) { + for i := 0; i < 10; i++ { + res, err = send(msg) + if err != nil { + return res, nil + } + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { + return nil, err + } + + // Ok, insufficient funds. Let's fund this miner and try again. + if err := fund(send, msg.To, i); err != nil { + if err != ErrOutOfGas { + err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + return nil, err } - return nil, err } - return send(msg) + return res, err } func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err error) { diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 41c3f363e..51fdaedc6 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -122,7 +122,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if big.Cmp(minerBalance, minFunds) < 0 { - err := fund(cb, minerAddr) + err := fund(cb, minerAddr, 1) if err != nil { if err == ErrOutOfGas { return 0, true, nil From 8d734d81d924433be69dfde50dfea46df6f4e792 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 07:50:51 -0700 Subject: [PATCH 45/94] fix(lotus-sim): log failed pre-commits and continue --- cmd/lotus-sim/simulation/precommit.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 51fdaedc6..854722f6a 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -17,6 +17,7 @@ import ( tutils "github.com/filecoin-project/specs-actors/v5/support/testing" "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/actors/policy" "github.com/filecoin-project/lotus/chain/types" @@ -170,7 +171,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } enc, err := actors.SerializeParams(¶ms) if err != nil { - return 0, false, err + return added, false, err } // NOTE: just in-case, sendAndFund will "fund" and re-try for any message // that fails due to "insufficient funds". @@ -184,13 +185,22 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, // try again with a smaller batch. targetBatchSize /= 2 continue + } else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() { + // Log the error and move on. No reason to stop. + log.Errorw("failed to pre-commit for unknown reasons", + "error", aerr, + "miner", minerAddr, + "sectors", batch, + "epoch", ss.nextEpoch(), + ) + return added, false, nil } else if err != nil { - return 0, false, err + return added, false, err } for _, info := range batch { if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return 0, false, err + return added, false, err } added++ } @@ -215,7 +225,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return 0, false, err + return added, false, err } added++ } From 16449007ab8e39150575a3f726945836698afc7c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 07:57:27 -0700 Subject: [PATCH 46/94] fix(lotus-sim): fix funding error check --- cmd/lotus-sim/simulation/funding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index a58b8c0e4..e29f4f1b8 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -59,7 +59,7 @@ func fund(send packFunc, target address.Address, times int) error { func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, err error) { for i := 0; i < 10; i++ { res, err = send(msg) - if err != nil { + if err == nil { return res, nil } aerr, ok := err.(aerrors.ActorError) From ca9eadd7c7787fd30db624e1b944550669a5fa03 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 10 Jun 2021 18:39:57 +0200 Subject: [PATCH 47/94] Add gas info command Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 70 ++++++++++++++++++++++++++ cmd/lotus-sim/simulation/simulation.go | 4 ++ 2 files changed, 74 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 187c2236e..d87b3da63 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -1,15 +1,21 @@ package main import ( + "bytes" "context" "fmt" "io" + "os" "text/tabwriter" "time" + "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/specs-actors/v5/actors/builtin" + "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" @@ -74,6 +80,9 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e var infoSimCommand = &cli.Command{ Name: "info", Description: "Output information about the simulation.", + Subcommands: []*cli.Command{ + infoCommitGasSimCommand, + }, Action: func(cctx *cli.Context) error { node, err := open(cctx) if err != nil { @@ -88,3 +97,64 @@ var infoSimCommand = &cli.Command{ return printInfo(cctx.Context, sim, cctx.App.Writer) }, } + +var infoCommitGasSimCommand = &cli.Command{ + Name: "commit-gas", + Description: "Output information about the gas for committs", + Action: func(cctx *cli.Context) error { + log := func(f string, i ...interface{}) { + fmt.Fprintf(os.Stderr, f, i...) + } + 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 + } + + var gasAgg, proofsAgg uint64 + var gasAggMax, proofsAggMax uint64 + + sim.Walk(cctx.Context, func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == builtin.MethodsMiner.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil + } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil + } + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c + } + } + + if m.Method == builtin.MethodsMiner.ProveCommitSector { + } + } + + return nil + }) + idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg) + + fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg)) + + return nil + }, +} diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index dbce16f4e..eb225f651 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -326,6 +326,10 @@ func (sim *Simulation) Walk( stCid = ts.MinTicketBlock().ParentStateRoot recCid = ts.MinTicketBlock().ParentMessageReceipts + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return xerrors.Errorf("loading parent: %w", err) + } } return nil } From 68593ce9951d82d174bca33d0cdb9ddde3599cca Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 09:54:14 -0700 Subject: [PATCH 48/94] fix(lotus-sim): obey context in walk --- cmd/lotus-sim/simulation/simulation.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index eb225f651..2d8b4f388 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -297,7 +297,7 @@ func (sim *Simulation) Walk( if err != nil { return err } - for !ts.Equals(sim.start) { + for !ts.Equals(sim.start) && ctx.Err() == nil { msgs, err := sim.Chainstore.MessagesForTipset(ts) if err != nil { return err @@ -331,5 +331,5 @@ func (sim *Simulation) Walk( return xerrors.Errorf("loading parent: %w", err) } } - return nil + return ctx.Err() } From 3d3c26fa0c797b6ebae73e8b2f335df50da4ef72 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 10 Jun 2021 19:04:15 +0200 Subject: [PATCH 49/94] Add lookback limit Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 61 ++++++++++++++------------ cmd/lotus-sim/simulation/simulation.go | 8 +++- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index d87b3da63..252642eea 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -101,6 +101,12 @@ var infoSimCommand = &cli.Command{ var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", Description: "Output information about the gas for committs", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, Action: func(cctx *cli.Context) error { log := func(f string, i ...interface{}) { fmt.Fprintf(os.Stderr, f, i...) @@ -119,38 +125,39 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAgg, proofsAgg uint64 var gasAggMax, proofsAggMax uint64 - sim.Walk(cctx.Context, func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage) error { - for _, m := range messages { - if m.ExitCode != exitcode.Ok { - continue - } - if m.Method == builtin.MethodsMiner.ProveCommitAggregate { - param := miner.ProveCommitAggregateParams{} - err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) - if err != nil { - log("failed to decode params: %+v", err) - return nil + sim.Walk(cctx.Context, cctx.Int64("lookback"), + func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue } - c, err := param.SectorNumbers.Count() - if err != nil { - log("failed to count sectors") - return nil + if m.Method == builtin.MethodsMiner.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil + } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil + } + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c + } } - gasAgg += uint64(m.GasUsed) - proofsAgg += c - if c == 819 { - gasAggMax += uint64(m.GasUsed) - proofsAggMax += c + + if m.Method == builtin.MethodsMiner.ProveCommitSector { } } - if m.Method == builtin.MethodsMiner.ProveCommitSector { - } - } - - return nil - }) + return nil + }) idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg) fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg)) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 2d8b4f388..ab021ddd5 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -286,6 +286,7 @@ type AppliedMessage struct { // Walk walks the simulation's chain from the current head back to the first tipset. func (sim *Simulation) Walk( ctx context.Context, + maxLookback int64, cb func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, @@ -297,7 +298,12 @@ func (sim *Simulation) Walk( if err != nil { return err } - for !ts.Equals(sim.start) && ctx.Err() == nil { + minEpoch := abi.ChainEpoch(0) + if maxLookback != 0 { + minEpoch = ts.Height() - abi.ChainEpoch(maxLookback) + } + + for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { msgs, err := sim.Chainstore.MessagesForTipset(ts) if err != nil { return err From ab59474c4c1038f2477a7161f8f2dd72e27afef0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 10:26:09 -0700 Subject: [PATCH 50/94] fix(lotus-sim): count single prove-commits when computing efficiency --- cmd/lotus-sim/info.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 252642eea..9523b5936 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -124,6 +124,7 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAgg, proofsAgg uint64 var gasAggMax, proofsAggMax uint64 + var gasSingle, proofsSingle uint64 sim.Walk(cctx.Context, cctx.Int64("lookback"), func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, @@ -153,14 +154,16 @@ var infoCommitGasSimCommand = &cli.Command{ } if m.Method == builtin.MethodsMiner.ProveCommitSector { + gasSingle += uint64(m.GasUsed) + proofsSingle++ } } return nil }) - idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg) + idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) - fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg)) + fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) return nil }, From 500fae6a525022895377a506ae50171940b79b4a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 10:26:43 -0700 Subject: [PATCH 51/94] fix(lotus-sim): less indentation in info --- cmd/lotus-sim/info.go | 61 ++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 9523b5936..9f3c81dca 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -126,41 +126,42 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAggMax, proofsAggMax uint64 var gasSingle, proofsSingle uint64 - sim.Walk(cctx.Context, cctx.Int64("lookback"), - func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage) error { - for _, m := range messages { - if m.ExitCode != exitcode.Ok { - continue + sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == builtin.MethodsMiner.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil } - if m.Method == builtin.MethodsMiner.ProveCommitAggregate { - param := miner.ProveCommitAggregateParams{} - err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) - if err != nil { - log("failed to decode params: %+v", err) - return nil - } - c, err := param.SectorNumbers.Count() - if err != nil { - log("failed to count sectors") - return nil - } - gasAgg += uint64(m.GasUsed) - proofsAgg += c - if c == 819 { - gasAggMax += uint64(m.GasUsed) - proofsAggMax += c - } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil } - - if m.Method == builtin.MethodsMiner.ProveCommitSector { - gasSingle += uint64(m.GasUsed) - proofsSingle++ + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c } } - return nil - }) + if m.Method == builtin.MethodsMiner.ProveCommitSector { + gasSingle += uint64(m.GasUsed) + proofsSingle++ + } + } + + return nil + }) idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) From fbaffe86da429ea0b9527a11a5bc9ca49fc0cada Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 11:14:02 -0700 Subject: [PATCH 52/94] fix(lotus-sim): return error from walk --- cmd/lotus-sim/info.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 9f3c81dca..36d4cd3e0 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -126,7 +126,7 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAggMax, proofsAggMax uint64 var gasSingle, proofsSingle uint64 - sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, messages []*simulation.AppliedMessage, ) error { @@ -162,6 +162,9 @@ var infoCommitGasSimCommand = &cli.Command{ return nil }) + if err != nil { + return err + } idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) From 1df5445ed25137f32a8c5be0176051af40c6cbd7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 11:16:26 -0700 Subject: [PATCH 53/94] feat(lotus-sim): make walk parallel --- cmd/lotus-sim/simulation/simulation.go | 165 ++++++++++++++++++++----- 1 file changed, 131 insertions(+), 34 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index ab021ddd5..78e6c8e87 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -3,7 +3,9 @@ package simulation import ( "context" "encoding/json" + "runtime" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/abi" @@ -293,49 +295,144 @@ func (sim *Simulation) Walk( messages []*AppliedMessage) error, ) error { store := sim.Chainstore.ActorStore(ctx) - ts := sim.head - stCid, recCid, err := sim.StateManager.TipSetState(ctx, ts) - if err != nil { - return err - } minEpoch := abi.ChainEpoch(0) if maxLookback != 0 { - minEpoch = ts.Height() - abi.ChainEpoch(maxLookback) + minEpoch = sim.head.Height() - abi.ChainEpoch(maxLookback) } - for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { - msgs, err := sim.Chainstore.MessagesForTipset(ts) + // Given tha loading messages and receipts can be a little bit slow, we do this in parallel. + // + // 1. We spin up some number of workers. + // 2. We hand tipsets to workers in round-robin order. + // 3. We pull "resolved" tipsets in the same round-robin order. + // 4. We serially call the callback in reverse-chain order. + // + // We have a buffer of size 1 for both resolved tipsets and unresolved tipsets. This should + // ensure that we never block unecessarily. + + type work struct { + ts *types.TipSet + stCid cid.Cid + recCid cid.Cid + } + type result struct { + ts *types.TipSet + stCid cid.Cid + messages []*AppliedMessage + } + + // This is more disk bound than CPU bound, but eh... + workerCount := runtime.NumCPU() * 2 + + workQs := make([]chan *work, workerCount) + resultQs := make([]chan *result, workerCount) + + for i := range workQs { + workQs[i] = make(chan *work, 1) + } + + for i := range resultQs { + resultQs[i] = make(chan *result, 1) + } + + grp, ctx := errgroup.WithContext(ctx) + + // Walk the chain and fire off work items. + grp.Go(func() error { + ts := sim.head + stCid, recCid, err := sim.StateManager.TipSetState(ctx, ts) if err != nil { return err } - - recs, err := blockadt.AsArray(store, recCid) - if err != nil { - return xerrors.Errorf("amt load: %w", err) - } - applied := make([]*AppliedMessage, len(msgs)) - var rec types.MessageReceipt - err = recs.ForEach(&rec, func(i int64) error { - applied[i] = &AppliedMessage{ - Message: *msgs[i].VMMessage(), - MessageReceipt: rec, + i := 0 + for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { + select { + case workQs[i] <- &work{ts, stCid, recCid}: + case <-ctx.Done(): + return ctx.Err() } + + stCid = ts.MinTicketBlock().ParentStateRoot + recCid = ts.MinTicketBlock().ParentMessageReceipts + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return xerrors.Errorf("loading parent: %w", err) + } + i = (i + 1) % workerCount + } + for _, q := range workQs { + close(q) + } + return nil + }) + + // Spin up one worker per queue pair. + for i := 0; i < workerCount; i++ { + workQ := workQs[i] + resultQ := resultQs[i] + grp.Go(func() error { + for job := range workQ { + msgs, err := sim.Chainstore.MessagesForTipset(job.ts) + if err != nil { + return err + } + + recs, err := blockadt.AsArray(store, job.recCid) + if err != nil { + return xerrors.Errorf("amt load: %w", err) + } + applied := make([]*AppliedMessage, len(msgs)) + var rec types.MessageReceipt + err = recs.ForEach(&rec, func(i int64) error { + applied[i] = &AppliedMessage{ + Message: *msgs[i].VMMessage(), + MessageReceipt: rec, + } + return nil + }) + if err != nil { + return err + } + select { + case resultQ <- &result{ + ts: job.ts, + stCid: job.stCid, + messages: applied, + }: + case <-ctx.Done(): + return ctx.Err() + } + } + close(resultQ) return nil }) - if err != nil { - return err - } - - if err := cb(sim.StateManager, ts, stCid, applied); err != nil { - return err - } - - stCid = ts.MinTicketBlock().ParentStateRoot - recCid = ts.MinTicketBlock().ParentMessageReceipts - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) - if err != nil { - return xerrors.Errorf("loading parent: %w", err) - } } - return ctx.Err() + + // Process results in the same order we enqueued them. + grp.Go(func() error { + qs := resultQs + for len(qs) > 0 { + newQs := qs[:0] + for _, q := range qs { + select { + case r, ok := <-q: + if !ok { + continue + } + err := cb(sim.StateManager, r.ts, r.stCid, r.messages) + if err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + newQs = append(newQs, q) + } + qs = newQs + } + return nil + }) + + // Wait for everything to finish. + return grp.Wait() } From d551f2b4bd7b43d94a0127c3fb54e20af82f7b2a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 12:32:41 -0700 Subject: [PATCH 54/94] feat(lotus-sim): print duration info in days --- cmd/lotus-sim/info.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 36d4cd3e0..11d0a7efd 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -65,11 +65,12 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) fmt.Fprintf(tw, "Head:\t%s\n", head) - fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) - fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "Start Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "End Epoch:\t%d\n", headEpoch) fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) - fmt.Fprintf(tw, "Date:\t%s\n", headTime) - fmt.Fprintf(tw, "Duration:\t%s\n", duration) + fmt.Fprintf(tw, "Start Date:\t%s\n", startTime) + fmt.Fprintf(tw, "End Date:\t%s\n", headTime) + fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) From 707b3bf08a8aa72225d74b807b9f7058fdb232cd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 12:32:52 -0700 Subject: [PATCH 55/94] fix(lotus-sim): refuse to start simulation with no miners --- cmd/lotus-sim/simulation/state.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index a45e1ac45..383ef158a 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" @@ -125,6 +126,10 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)}) } } + if len(sealList) == 0 { + return nil, xerrors.Errorf("simulation has no miners") + } + // We're already done loading for the _next_ epoch. // Next time, we need to load for the next, next epoch. // TODO: fix this insanity. From 985994cc0f21fcc20a1baff4c3cde80520979bc4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 14:37:20 -0700 Subject: [PATCH 56/94] feat(lotus-sim): add command for analyzing post stats over time --- cmd/lotus-sim/info.go | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 11d0a7efd..ebfe63545 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -83,6 +83,7 @@ var infoSimCommand = &cli.Command{ Description: "Output information about the simulation.", Subcommands: []*cli.Command{ infoCommitGasSimCommand, + infoWindowPostBandwidthSimCommand, }, Action: func(cctx *cli.Context) error { node, err := open(cctx) @@ -99,6 +100,54 @@ var infoSimCommand = &cli.Command{ }, } +var infoWindowPostBandwidthSimCommand = &cli.Command{ + Name: "post-bandwidth", + 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 + } + + var postGas, totalGas int64 + printStats := func() { + fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) + } + idx := 0 + err = sim.Walk(cctx.Context, 0, func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + totalGas += m.GasUsed + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == builtin.MethodsMiner.SubmitWindowedPoSt { + postGas += m.GasUsed + } + } + idx++ + idx %= builtin.EpochsInDay + if idx == 0 { + printStats() + postGas = 0 + totalGas = 0 + } + return nil + }) + if idx > 0 { + printStats() + } + return err + }, +} + var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", Description: "Output information about the gas for committs", From 2721279e871f785cf5e50473ef62d6e147d102df Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 14:43:15 -0700 Subject: [PATCH 57/94] fix(lotus-sim): describe info commands --- cmd/lotus-sim/info.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index ebfe63545..757c6d6e7 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -101,7 +101,8 @@ var infoSimCommand = &cli.Command{ } var infoWindowPostBandwidthSimCommand = &cli.Command{ - Name: "post-bandwidth", + Name: "post-bandwidth", + Description: "List average chain bandwidth used by window posts for each day of the simulation.", Action: func(cctx *cli.Context) error { node, err := open(cctx) if err != nil { @@ -150,7 +151,7 @@ var infoWindowPostBandwidthSimCommand = &cli.Command{ var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", - Description: "Output information about the gas for committs", + Description: "Output information about the gas for commits", Flags: []cli.Flag{ &cli.Int64Flag{ Name: "lookback", From 7dd58efb84cb0cfb429946fed97e22cfeae796d0 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 11 Jun 2021 15:35:13 +0200 Subject: [PATCH 58/94] Add quantiles and histogram Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 49 +++++++++++++++++++ go.mod | 1 + go.sum | 2 + lib/stati/covar.go | 104 ++++++++++++++++++++++++++++++++++++++++ lib/stati/histo.go | 56 ++++++++++++++++++++++ lib/stati/meanvar.go | 66 +++++++++++++++++++++++++ lib/stati/stats_test.go | 47 ++++++++++++++++++ 7 files changed, 325 insertions(+) create mode 100644 lib/stati/covar.go create mode 100644 lib/stati/histo.go create mode 100644 lib/stati/meanvar.go create mode 100644 lib/stati/stats_test.go diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 757c6d6e7..6a37f7258 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -10,6 +10,7 @@ import ( "time" "github.com/ipfs/go-cid" + "github.com/streadway/quantile" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" @@ -21,6 +22,7 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" ) func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { @@ -177,6 +179,31 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAggMax, proofsAggMax uint64 var gasSingle, proofsSingle uint64 + qpoints := []struct{ q, tol float64 }{ + {0.01, 0.0005}, + {0.05, 0.001}, + {0.20, 0.01}, + {0.25, 0.01}, + {0.30, 0.01}, + {0.40, 0.01}, + {0.45, 0.01}, + {0.50, 0.01}, + {0.60, 0.01}, + {0.80, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1, 3, 5, 7, 15, 30, 50, 100, 200, 400, 600, 700, 819}) + if err != nil { + return err + } + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, messages []*simulation.AppliedMessage, @@ -203,11 +230,17 @@ var infoCommitGasSimCommand = &cli.Command{ gasAggMax += uint64(m.GasUsed) proofsAggMax += c } + for i := uint64(0); i < c; i++ { + qua.Add(float64(c)) + } + hist.Observe(float64(c)) } if m.Method == builtin.MethodsMiner.ProveCommitSector { gasSingle += uint64(m.GasUsed) proofsSingle++ + qua.Add(1) + hist.Observe(1) } } @@ -220,6 +253,22 @@ var infoCommitGasSimCommand = &cli.Command{ fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) + fmt.Printf("Proofs in singles: %d\n", proofsSingle) + fmt.Printf("Proofs in Aggs: %d\n", proofsAgg) + fmt.Printf("Proofs in Aggs(819): %d\n", proofsAggMax) + + fmt.Println() + fmt.Println("Quantiles of proofs in given aggregate size:") + for _, p := range qpoints { + fmt.Printf("%.0f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of messages:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\n", b, hist.Get(i)) + } + return nil }, } diff --git a/go.mod b/go.mod index 411522a36..5bf9094f0 100644 --- a/go.mod +++ b/go.mod @@ -133,6 +133,7 @@ require ( github.com/prometheus/client_golang v1.6.0 github.com/raulk/clock v1.1.0 github.com/raulk/go-watchdog v1.0.1 + github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 diff --git a/go.sum b/go.sum index 5573587fd..cfc1d4221 100644 --- a/go.sum +++ b/go.sum @@ -1513,6 +1513,8 @@ github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 h1:7z3LSn867ex6VSaahyKadf4WtSsJIgne6A1WLOAGM8A= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= diff --git a/lib/stati/covar.go b/lib/stati/covar.go new file mode 100644 index 000000000..c92fd8b74 --- /dev/null +++ b/lib/stati/covar.go @@ -0,0 +1,104 @@ +package stati + +import "math" + +type Covar struct { + meanX float64 + meanY float64 + c float64 + n float64 + m2x float64 + m2y float64 +} + +func (cov1 *Covar) MeanX() float64 { + return cov1.meanX +} + +func (cov1 *Covar) MeanY() float64 { + return cov1.meanY +} + +func (cov1 *Covar) N() float64 { + return cov1.n +} + +func (cov1 *Covar) Covariance() float64 { + return cov1.c / (cov1.n - 1) +} + +func (cov1 *Covar) VarianceX() float64 { + return cov1.m2x / (cov1.n - 1) +} + +func (cov1 *Covar) StddevX() float64 { + return math.Sqrt(cov1.VarianceX()) +} + +func (cov1 *Covar) VarianceY() float64 { + return cov1.m2y / (cov1.n - 1) +} + +func (cov1 *Covar) StddevY() float64 { + return math.Sqrt(cov1.VarianceY()) +} + +func (cov1 *Covar) AddPoint(x, y float64) { + cov1.n++ + + dx := x - cov1.meanX + cov1.meanX += dx / cov1.n + dx2 := x - cov1.meanX + cov1.m2x += dx * dx2 + + dy := y - cov1.meanY + cov1.meanY += dy / cov1.n + dy2 := y - cov1.meanY + cov1.m2y += dy * dy2 + + cov1.c += dx * dy +} + +func (cov1 *Covar) Combine(cov2 *Covar) { + if cov1.n == 0 { + *cov1 = *cov2 + return + } + if cov2.n == 0 { + return + } + + if cov1.n == 1 { + cpy := *cov2 + cpy.AddPoint(cov2.meanX, cov2.meanY) + *cov1 = cpy + return + } + if cov2.n == 1 { + cov1.AddPoint(cov2.meanX, cov2.meanY) + } + + out := Covar{} + out.n = cov1.n + cov2.n + + dx := cov1.meanX - cov2.meanX + out.meanX = cov1.meanX - dx*cov2.n/out.n + out.m2x = cov1.m2x + cov2.m2x + dx*dx*cov1.n*cov2.n/out.n + + dy := cov1.meanY - cov2.meanY + out.meanY = cov1.meanY - dy*cov2.n/out.n + out.m2y = cov1.m2y + cov2.m2y + dy*dy*cov1.n*cov2.n/out.n + + out.c = cov1.c + cov2.c + dx*dy*cov1.n*cov2.n/out.n + *cov1 = out +} + +func (cov1 *Covar) A() float64 { + return cov1.Covariance() / cov1.VarianceX() +} +func (cov1 *Covar) B() float64 { + return cov1.meanY - cov1.meanX*cov1.A() +} +func (cov1 *Covar) Correl() float64 { + return cov1.Covariance() / cov1.StddevX() / cov1.StddevY() +} diff --git a/lib/stati/histo.go b/lib/stati/histo.go new file mode 100644 index 000000000..3c410c0d0 --- /dev/null +++ b/lib/stati/histo.go @@ -0,0 +1,56 @@ +package stati + +import ( + "math" + + "golang.org/x/xerrors" +) + +type Histogram struct { + Buckets []float64 + Counts []uint64 +} + +// NewHistogram creates a histograme with buckets defined as: +// {x > -Inf, x >= buckets[0], x >= buckets[1], ..., x >= buckets[i]} +func NewHistogram(buckets []float64) (*Histogram, error) { + if len(buckets) == 0 { + return nil, xerrors.Errorf("empty buckets") + } + prev := buckets[0] + for i, v := range buckets[1:] { + if v < prev { + return nil, xerrors.Errorf("bucket at index %d is smaller than previous %f < %f", i+1, v, prev) + } + prev = v + } + h := &Histogram{ + Buckets: append([]float64{math.Inf(-1)}, buckets...), + Counts: make([]uint64, len(buckets)+1), + } + return h, nil +} + +func (h *Histogram) Observe(x float64) { + for i, b := range h.Buckets { + if x >= b { + h.Counts[i]++ + } else { + break + } + } +} + +func (h *Histogram) Total() uint64 { + return h.Counts[0] +} + +func (h *Histogram) Get(i int) uint64 { + if i >= len(h.Counts)-2 { + return h.Counts[i] + } + return h.Counts[i+1] - h.Counts[i+2] +} +func (h *Histogram) GetRatio(i int) float64 { + return float64(h.Get(i)) / float64(h.Total()) +} diff --git a/lib/stati/meanvar.go b/lib/stati/meanvar.go new file mode 100644 index 000000000..b77aaa638 --- /dev/null +++ b/lib/stati/meanvar.go @@ -0,0 +1,66 @@ +package stati + +import ( + "fmt" + "math" +) + +type MeanVar struct { + n float64 + mean float64 + m2 float64 +} + +func (v1 *MeanVar) AddPoint(value float64) { + // based on https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm + v1.n++ + delta := value - v1.mean + v1.mean += delta / v1.n + delta2 := value - v1.mean + v1.m2 += delta * delta2 +} + +func (v1 *MeanVar) Mean() float64 { + return v1.mean +} +func (v1 *MeanVar) N() float64 { + return v1.n +} +func (v1 *MeanVar) Variance() float64 { + return v1.m2 / (v1.n - 1) +} +func (v1 *MeanVar) Stddev() float64 { + return math.Sqrt(v1.Variance()) +} + +func (v1 MeanVar) String() string { + return fmt.Sprintf("%f stddev: %f (%.0f)", v1.Mean(), v1.Stddev(), v1.N()) +} + +func (v1 *MeanVar) Combine(v2 *MeanVar) { + if v1.n == 0 { + *v1 = *v2 + return + } + if v2.n == 0 { + return + } + if v1.n == 1 { + cpy := *v2 + cpy.AddPoint(v1.mean) + *v1 = cpy + return + } + if v2.n == 1 { + v1.AddPoint(v2.mean) + return + } + + newCount := v1.n + v2.n + delta := v2.mean - v1.mean + meanDelta := delta * v2.n / newCount + m2 := v1.m2 + v2.m2 + delta*meanDelta*v1.n + v1.n = newCount + v1.mean += meanDelta + v1.m2 = m2 +} diff --git a/lib/stati/stats_test.go b/lib/stati/stats_test.go new file mode 100644 index 000000000..fa92913b6 --- /dev/null +++ b/lib/stati/stats_test.go @@ -0,0 +1,47 @@ +package stati + +import ( + "math/rand" + "testing" +) + +func TestMeanVar(t *testing.T) { + N := 16 + ss := make([]*MeanVar, N) + rng := rand.New(rand.NewSource(1)) + for i := 0; i < N; i++ { + ss[i] = &MeanVar{} + maxJ := rng.Intn(1000) + for j := 0; j < maxJ; j++ { + ss[i].AddPoint(rng.NormFloat64()*5 + 500) + } + t.Logf("mean: %f, stddev: %f, count %f", ss[i].mean, ss[i].Stddev(), ss[i].n) + } + out := &MeanVar{} + for i := 0; i < N; i++ { + out.Combine(ss[i]) + t.Logf("combine: mean: %f, stddev: %f", out.mean, out.Stddev()) + } +} + +func TestCovar(t *testing.T) { + N := 16 + ss := make([]*Covar, N) + rng := rand.New(rand.NewSource(1)) + for i := 0; i < N; i++ { + ss[i] = &Covar{} + maxJ := rng.Intn(1000) + 500 + for j := 0; j < maxJ; j++ { + x := rng.NormFloat64()*5 + 500 + ss[i].AddPoint(x, x*2-1000) + } + t.Logf("corell: %f, y = %f*x+%f @%.0f", ss[i].Correl(), ss[i].A(), ss[i].B(), ss[i].n) + t.Logf("\txVar: %f yVar: %f covar: %f", ss[i].StddevX(), ss[i].StddevY(), ss[i].Covariance()) + } + out := &Covar{} + for i := 0; i < N; i++ { + out.Combine(ss[i]) + t.Logf("combine: corell: %f, y = %f*x+%f", out.Correl(), out.A(), out.B()) + t.Logf("\txVar: %f yVar: %f covar: %f", out.StddevX(), out.StddevY(), out.Covariance()) + } +} From 8a215df46b6cf6dac97197304caf8bc34acbea17 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 17:41:23 -0700 Subject: [PATCH 59/94] fix(statetree): make StateTree.ForEach take layers into account This likely isn't used anywhere, but this _should_ take layers into account (and I kind of just assumed it did). --- chain/state/statetree.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 40955c48b..72269e4f2 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -504,6 +504,25 @@ func (st *StateTree) MutateActor(addr address.Address, f func(*types.Actor) erro } func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error { + // Walk through layers, if any. + seen := make(map[address.Address]struct{}) + for i := len(st.snaps.layers) - 1; i >= 0; i-- { + for addr, op := range st.snaps.layers[i].actors { + if _, ok := seen[addr]; ok { + continue + } + seen[addr] = struct{}{} + if op.Delete { + continue + } + if err := f(addr, &op.Act); err != nil { + return err + } + } + + } + + // Now walk through the saved actors. var act types.Actor return st.root.ForEach(&act, func(k string) error { act := act // copy @@ -512,6 +531,12 @@ func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error return xerrors.Errorf("invalid address (%x) found in state tree key: %w", []byte(k), err) } + // no need to record anything here, there are no duplicates in the actors HAMT + // iself. + if _, ok := seen[addr]; ok { + return nil + } + return f(addr, &act) }) } From 52261fb814e18850356f0e9bb39b3f74be25b11f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 11 Jun 2021 18:39:15 -0700 Subject: [PATCH 60/94] refactor(lotus-sim): enterprise grade While the previous version "worked", this version nicely separates out the state for the separate stages. Hopefully, we'll be able to use this to build different pipelines with different configs. --- cmd/lotus-sim/run.go | 6 - cmd/lotus-sim/simulation/block.go | 3 +- .../simulation/blockbuilder/blockbuilder.go | 279 ++++++++++++++ .../simulation/blockbuilder/errors.go | 25 ++ cmd/lotus-sim/simulation/{ => mock}/mock.go | 30 +- cmd/lotus-sim/simulation/node.go | 19 +- cmd/lotus-sim/simulation/power.go | 58 --- cmd/lotus-sim/simulation/precommit.go | 233 ------------ cmd/lotus-sim/simulation/simulation.go | 37 +- .../simulation/{ => stages}/actor_iter.go | 2 +- .../simulation/{ => stages}/commit_queue.go | 2 +- .../{ => stages}/commit_queue_test.go | 2 +- .../{funding.go => stages/funding_stage.go} | 134 ++++--- cmd/lotus-sim/simulation/stages/interface.go | 27 ++ cmd/lotus-sim/simulation/stages/pipeline.go | 31 ++ .../simulation/stages/precommit_stage.go | 359 ++++++++++++++++++ .../provecommit_stage.go} | 153 +++++--- cmd/lotus-sim/simulation/stages/util.go | 81 ++++ .../simulation/stages/windowpost_stage.go | 312 +++++++++++++++ cmd/lotus-sim/simulation/state.go | 202 ---------- cmd/lotus-sim/simulation/step.go | 229 +---------- cmd/lotus-sim/simulation/wdpost.go | 229 ----------- 22 files changed, 1353 insertions(+), 1100 deletions(-) create mode 100644 cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go create mode 100644 cmd/lotus-sim/simulation/blockbuilder/errors.go rename cmd/lotus-sim/simulation/{ => mock}/mock.go (75%) delete mode 100644 cmd/lotus-sim/simulation/power.go delete mode 100644 cmd/lotus-sim/simulation/precommit.go rename cmd/lotus-sim/simulation/{ => stages}/actor_iter.go (97%) rename cmd/lotus-sim/simulation/{ => stages}/commit_queue.go (99%) rename cmd/lotus-sim/simulation/{ => stages}/commit_queue_test.go (99%) rename cmd/lotus-sim/simulation/{funding.go => stages/funding_stage.go} (68%) create mode 100644 cmd/lotus-sim/simulation/stages/interface.go create mode 100644 cmd/lotus-sim/simulation/stages/pipeline.go create mode 100644 cmd/lotus-sim/simulation/stages/precommit_stage.go rename cmd/lotus-sim/simulation/{provecommit.go => stages/provecommit_stage.go} (62%) create mode 100644 cmd/lotus-sim/simulation/stages/util.go create mode 100644 cmd/lotus-sim/simulation/stages/windowpost_stage.go delete mode 100644 cmd/lotus-sim/simulation/state.go delete mode 100644 cmd/lotus-sim/simulation/wdpost.go diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index ba6534b4b..00a3bddd9 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -35,12 +35,6 @@ Signals: 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") ch := make(chan os.Signal, 1) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 6b3c96e78..47d482f4e 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -8,6 +8,7 @@ import ( "golang.org/x/xerrors" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" ) @@ -68,7 +69,7 @@ func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message ParentStateRoot: parentState, ParentMessageReceipts: parentRec, Messages: msgsCid, - ParentBaseFee: baseFee, + ParentBaseFee: abi.NewTokenAmount(0), Timestamp: uts, ElectionProof: &types.ElectionProof{WinCount: 1}, }} diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go new file mode 100644 index 000000000..4406f8a4f --- /dev/null +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -0,0 +1,279 @@ +package blockbuilder + +import ( + "context" + + "go.uber.org/zap" + "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/network" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "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 ( + // The number of expected blocks in a tipset. We use this to determine how much gas a tipset + // has. + 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 +) + +type BlockBuilder struct { + ctx context.Context + logger *zap.SugaredLogger + + parentTs *types.TipSet + parentSt *state.StateTree + vm *vm.VM + sm *stmgr.StateManager + + gasTotal int64 + messages []*types.Message +} + +// NewBlockBuilder constructs a new block builder from the parent state. Use this to pack a block +// with messages. +// +// NOTE: The context applies to the life of the block builder itself (but does not need to be canceled). +func NewBlockBuilder(ctx context.Context, logger *zap.SugaredLogger, sm *stmgr.StateManager, parentTs *types.TipSet) (*BlockBuilder, error) { + parentState, _, err := sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, err + } + parentSt, err := sm.StateTree(parentState) + if err != nil { + return nil, err + } + + bb := &BlockBuilder{ + ctx: ctx, + logger: logger.With("epoch", parentTs.Height()+1), + sm: sm, + parentTs: parentTs, + parentSt: parentSt, + } + + // Then we construct a VM to execute messages for gas estimation. + // + // Most parts of this VM are "real" except: + // 1. We don't charge a fee. + // 2. The runtime has "fake" proof logic. + // 3. We don't actually save any of the results. + r := store.NewChainRand(sm.ChainStore(), parentTs.Cids()) + vmopt := &vm.VMOpts{ + StateBase: parentState, + Epoch: parentTs.Height() + 1, + Rand: r, + Bstore: sm.ChainStore().StateBlockstore(), + Syscalls: sm.ChainStore().VMSys(), + CircSupplyCalc: sm.GetVMCirculatingSupply, + NtwkVersion: sm.GetNtwkVersion, + BaseFee: abi.NewTokenAmount(0), + LookbackState: stmgr.LookbackStateGetterForTipset(sm, parentTs), + } + bb.vm, err = vm.NewVM(bb.ctx, vmopt) + if err != nil { + return nil, err + } + return bb, nil +} + +// PushMessages tries to push the specified message into the block. +// +// 1. All messages will be executed in-order. +// 2. Gas computation & nonce selection will be handled internally. +// 3. The base-fee is 0 so the sender does not need funds. +// 4. As usual, the sender must be an account (any account). +// 5. If the message fails to execute, this method will fail. +// +// Returns ErrOutOfGas when out of gas. Check BlockBuilder.GasRemaining and try pushing a cheaper +// message. +func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, error) { + if bb.gasTotal >= targetGas { + return nil, new(ErrOutOfGas) + } + + st := bb.StateTree() + store := bb.ActorStore() + + // Copy the message before we start mutating it. + msgCpy := *msg + msg = &msgCpy + + actor, err := st.GetActor(msg.From) + if err != nil { + return nil, err + } + if !builtin.IsAccountActor(actor.Code) { + return nil, xerrors.Errorf( + "messags may only be sent from account actors, got message from %s (%s)", + msg.From, builtin.ActorNameByCode(actor.Code), + ) + } + msg.Nonce = actor.Nonce + if msg.From.Protocol() == address.ID { + state, err := account.Load(store, actor) + if err != nil { + return nil, err + } + msg.From, err = state.PubkeyAddress() + if err != nil { + return nil, 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(bb.ctx) + defer st.ClearSnapshot() + + ret, err := bb.vm.ApplyMessage(bb.ctx, msg) + if err != nil { + _ = st.Revert() + return nil, err + } + if ret.ActorErr != nil { + _ = st.Revert() + return nil, ret.ActorErr + } + + // Sometimes there are bugs. Let's catch them. + if ret.GasUsed == 0 { + _ = st.Revert() + return nil, 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 := bb.gasTotal + ret.GasUsed + if newTotal > targetGas { + _ = st.Revert() + return nil, &ErrOutOfGas{Available: targetGas - bb.gasTotal, Required: ret.GasUsed} + } + bb.gasTotal = newTotal + + // Update the gas limit. + msg.GasLimit = ret.GasUsed + + bb.messages = append(bb.messages, msg) + return &ret.MessageReceipt, nil +} + +// ActorStore returns the VM's current (pending) blockstore. +func (bb *BlockBuilder) ActorStore() adt.Store { + return bb.vm.ActorStore(bb.ctx) +} + +// StateTree returns the VM's current (pending) state-tree. This includes any changes made by +// successfully pushed messages. +// +// You probably want ParentStateTree +func (bb *BlockBuilder) StateTree() *state.StateTree { + return bb.vm.StateTree().(*state.StateTree) +} + +// ParentStateTree returns the parent state-tree (not the paren't tipset's parent state-tree). +func (bb *BlockBuilder) ParentStateTree() *state.StateTree { + return bb.parentSt +} + +// StateTreeByHeight will return a state-tree up through and including the current in-progress +// epoch. +// +// NOTE: This will return the state after the given epoch, not the parent state for the epoch. +func (bb *BlockBuilder) StateTreeByHeight(epoch abi.ChainEpoch) (*state.StateTree, error) { + now := bb.Height() + if epoch > now { + return nil, xerrors.Errorf( + "cannot load state-tree from future: %d > %d", epoch, bb.Height(), + ) + } else if epoch <= 0 { + return nil, xerrors.Errorf( + "cannot load state-tree: epoch %d <= 0", epoch, + ) + } + + // Manually handle "now" and "previous". + switch epoch { + case now: + return bb.StateTree(), nil + case now - 1: + return bb.ParentStateTree(), nil + } + + // Get the tipset of the block _after_ the target epoch so we can use its parent state. + targetTs, err := bb.sm.ChainStore().GetTipsetByHeight(bb.ctx, epoch+1, bb.parentTs, false) + if err != nil { + return nil, err + } + + return bb.sm.StateTree(targetTs.ParentState()) +} + +// Messages returns all messages currently packed into the next block. +// 1. DO NOT modify the slice, copy it. +// 2. DO NOT retain the slice, copy it. +func (bb *BlockBuilder) Messages() []*types.Message { + return bb.messages +} + +// GasRemaining returns the amount of remaining gas in the next block. +func (bb *BlockBuilder) GasRemaining() int64 { + return targetGas - bb.gasTotal +} + +// ParentTipSet returns the parent tipset. +func (bb *BlockBuilder) ParentTipSet() *types.TipSet { + return bb.parentTs +} + +// Height returns the epoch for the target block. +func (bb *BlockBuilder) Height() abi.ChainEpoch { + return bb.parentTs.Height() + 1 +} + +// NetworkVersion returns the network version for the target block. +func (bb *BlockBuilder) NetworkVersion() network.Version { + return bb.sm.GetNtwkVersion(bb.ctx, bb.Height()) +} + +// StateManager returns the stmgr.StateManager. +func (bb *BlockBuilder) StateManager() *stmgr.StateManager { + return bb.sm +} + +// ActorsVersion returns the actors version for the target block. +func (bb *BlockBuilder) ActorsVersion() actors.Version { + return actors.VersionForNetwork(bb.NetworkVersion()) +} + +func (bb *BlockBuilder) L() *zap.SugaredLogger { + return bb.logger +} diff --git a/cmd/lotus-sim/simulation/blockbuilder/errors.go b/cmd/lotus-sim/simulation/blockbuilder/errors.go new file mode 100644 index 000000000..ddf08ea18 --- /dev/null +++ b/cmd/lotus-sim/simulation/blockbuilder/errors.go @@ -0,0 +1,25 @@ +package blockbuilder + +import ( + "errors" + "fmt" +) + +// ErrOutOfGas is returned from BlockBuilder.PushMessage when the block does not have enough gas to +// fit the given message. +type ErrOutOfGas struct { + Available, Required int64 +} + +func (e *ErrOutOfGas) Error() string { + if e.Available == 0 { + return "out of gas: block full" + } + return fmt.Sprintf("out of gas: %d < %d", e.Required, e.Available) +} + +// IsOutOfGas returns true if the error is an "out of gas" error. +func IsOutOfGas(err error) bool { + var oog *ErrOutOfGas + return errors.As(err, &oog) +} diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock/mock.go similarity index 75% rename from cmd/lotus-sim/simulation/mock.go rename to cmd/lotus-sim/simulation/mock/mock.go index 37f0a2c6c..e6651aca0 100644 --- a/cmd/lotus-sim/simulation/mock.go +++ b/cmd/lotus-sim/simulation/mock/mock.go @@ -1,4 +1,4 @@ -package simulation +package mock import ( "bytes" @@ -8,8 +8,11 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + tutils "github.com/filecoin-project/specs-actors/v5/support/testing" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) @@ -26,14 +29,14 @@ const ( // mockVerifier is a simple mock for verifying "fake" proofs. type mockVerifier struct{} -var _ ffiwrapper.Verifier = mockVerifier{} +var Verifier ffiwrapper.Verifier = mockVerifier{} 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) + mockProof, err := MockSealProof(proof.SealProof, addr) if err != nil { return false, err } @@ -45,7 +48,7 @@ func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyPro if err != nil { return false, err } - mockProof, err := mockAggregateSealProof(aggregate.SealProof, addr, len(aggregate.Infos)) + mockProof, err := MockAggregateSealProof(aggregate.SealProof, addr, len(aggregate.Infos)) if err != nil { return false, err } @@ -63,7 +66,7 @@ func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoSt if err != nil { return false, err } - mockProof, err := mockWpostProof(proof.PoStProof, addr) + mockProof, err := MockWindowPoStProof(proof.PoStProof, addr) if err != nil { return false, err } @@ -74,8 +77,8 @@ func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.Regi panic("should not be called") } -// mockSealProof generates a mock "seal" proof tied to the specified proof type and the given miner. -func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { +// MockSealProof generates a mock "seal" proof tied to the specified proof type and the given miner. +func MockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { plen, err := proofType.ProofSize() if err != nil { return nil, err @@ -88,9 +91,9 @@ func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) return proof, nil } -// mockAggregateSealProof generates a mock "seal" aggregate proof tied to the specified proof type, +// MockAggregateSealProof generates a mock "seal" aggregate proof tied to the specified proof type, // the given miner, and the number of proven sectors. -func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) { +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)) @@ -102,9 +105,9 @@ func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address return proof, nil } -// mockWpostProof generates a mock "window post" proof tied to the specified proof type, and the +// MockWindowPoStProof generates a mock "window post" proof tied to the specified proof type, and the // given miner. -func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { +func MockWindowPoStProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { plen, err := proofType.ProofSize() if err != nil { return nil, err @@ -115,6 +118,11 @@ func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address return proof, nil } +// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner. +func MockCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { + return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) +} + // TODO: dedup func aggProofLen(nproofs int) int { switch { diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 73c739e5b..0be2de182 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -16,6 +16,8 @@ import ( "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/cmd/lotus-sim/simulation/mock" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" "github.com/filecoin-project/lotus/node/repo" ) @@ -53,7 +55,7 @@ func OpenNode(ctx context.Context, path string) (*Node, error) { return nil, err } - node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mockVerifier{}), nil) + node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mock.Verifier), nil) return &node, nil } @@ -74,12 +76,16 @@ func (nd *Node) Close() error { // LoadSim loads func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { + stages, err := stages.DefaultPipeline() + if err != nil { + return nil, err + } sim := &Simulation{ - Node: nd, - name: name, + Node: nd, + name: name, + stages: stages, } - var err error sim.head, err = sim.loadNamedTipSet("head") if err != nil { return nil, err @@ -113,10 +119,15 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) if strings.Contains(name, "/") { return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name) } + stages, err := stages.DefaultPipeline() + if err != nil { + return nil, err + } sim := &Simulation{ name: name, Node: nd, StateManager: stmgr.NewStateManager(nd.Chainstore), + stages: stages, } if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { return nil, err diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go deleted file mode 100644 index 9d0aceafe..000000000 --- a/cmd/lotus-sim/simulation/power.go +++ /dev/null @@ -1,58 +0,0 @@ -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" -) - -// 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.StateManager.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 { - // skip miners without power - if claim.RawBytePower.IsZero() { - return nil - } - 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() -} diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go deleted file mode 100644 index 854722f6a..000000000 --- a/cmd/lotus-sim/simulation/precommit.go +++ /dev/null @@ -1,233 +0,0 @@ -package simulation - -import ( - "context" - "fmt" - "time" - - "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/go-state-types/network" - "github.com/ipfs/go-cid" - - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" - tutils "github.com/filecoin-project/specs-actors/v5/support/testing" - - "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/actors/policy" - "github.com/filecoin-project/lotus/chain/types" -) - -var ( - targetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL")) - minFunds = abi.TokenAmount(types.MustParseFIL("100FIL")) -) - -// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner. -func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { - return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) -} - -// packPreCommits packs pre-commit messages until the block is full. -func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_err error) { - var ( - full bool - top1Count, top10Count, restCount int - ) - start := time.Now() - defer func() { - if _err != nil { - return - } - log.Debugw("packed pre commits", - "done", top1Count+top10Count+restCount, - "top1", top1Count, - "top10", top10Count, - "rest", restCount, - "filled-block", full, - "duration", time.Since(start), - ) - }() - - var top1Miners, top10Miners, restMiners int - for i := 0; ; i++ { - var ( - minerAddr address.Address - count *int - ) - - // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. - // This won't yeild the most accurate distribution... but it'll give us a good - // enough distribution. - - // NOTE: We submit at most _one_ 819 sector batch per-miner per-block. See the - // comment on packPreCommitsMiner for why. We should fix this. - 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 nil - } - - var ( - added int - err error - ) - added, full, err = ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize) - if err != nil { - return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) - } - *count += added - if full { - return nil - } - } -} - -// packPreCommitsMiner packs count pre-commits for the given miner. This should only be called once -// per-miner, per-epoch to avoid packing multiple pre-commits with the same sector numbers. -func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) { - // Load everything. - epoch := ss.nextEpoch() - nv := ss.StateManager.GetNtwkVersion(ctx, epoch) - actor, minerState, err := ss.getMinerState(ctx, minerAddr) - 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 { - err := fund(cb, minerAddr, 1) - if err != nil { - if err == ErrOutOfGas { - return 0, true, nil - } - return 0, false, err - } - } - - // Generate pre-commits. - 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, - } - } - - // Commit the pre-commits. - 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 added, false, err - } - // NOTE: just in-case, sendAndFund will "fund" and re-try for any message - // that fails due to "insufficient funds". - if _, err := sendAndFund(cb, &types.Message{ - To: minerAddr, - From: minerInfo.Worker, - Value: abi.NewTokenAmount(0), - Method: miner.Methods.PreCommitSectorBatch, - Params: enc, - }); err == ErrOutOfGas { - // try again with a smaller batch. - targetBatchSize /= 2 - continue - } else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() { - // Log the error and move on. No reason to stop. - log.Errorw("failed to pre-commit for unknown reasons", - "error", aerr, - "miner", minerAddr, - "sectors", batch, - "epoch", ss.nextEpoch(), - ) - return added, false, nil - } else if err != nil { - return added, false, err - } - - for _, info := range batch { - if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return added, false, err - } - added++ - } - infos = infos[len(batch):] - } - } - for _, info := range infos { - enc, err := actors.SerializeParams(&info) - if err != nil { - return 0, false, err - } - if _, err := sendAndFund(cb, &types.Message{ - To: minerAddr, - From: minerInfo.Worker, - Value: abi.NewTokenAmount(0), - Method: miner.Methods.PreCommitSector, - Params: enc, - }); err == ErrOutOfGas { - return added, true, nil - } else if err != nil { - return added, false, err - } - - if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return added, false, err - } - added++ - } - return added, false, nil -} diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 78e6c8e87..0af1120c2 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -15,28 +15,21 @@ import ( logging "github.com/ipfs/go-log/v2" blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" "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" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" ) 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 -) - // config is the simulation's config, persisted to the local metadata store and loaded on start. // -// See simulationState.loadConfig and simulationState.saveConfig. +// See Simulation.loadConfig and Simulation.saveConfig. type config struct { Upgrades map[network.Version]abi.ChainEpoch } @@ -93,9 +86,7 @@ type Simulation struct { st *state.StateTree head *types.TipSet - // lazy-loaded state - // access through `simState(ctx)` to load on-demand. - state *simulationState + stages []stages.Stage } // loadConfig loads a simulation's config from the datastore. This must be called on startup and may @@ -141,21 +132,6 @@ func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) 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") // key returns the the key in the form /simulation//. For example, @@ -189,13 +165,6 @@ func (sim *Simulation) storeNamedTipSet(name string, ts *types.TipSet) error { return nil } -// 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 -} - // GetHead returns the current simulation head. func (sim *Simulation) GetHead() *types.TipSet { return sim.head diff --git a/cmd/lotus-sim/simulation/actor_iter.go b/cmd/lotus-sim/simulation/stages/actor_iter.go similarity index 97% rename from cmd/lotus-sim/simulation/actor_iter.go rename to cmd/lotus-sim/simulation/stages/actor_iter.go index 5df395e11..b2c14ebdb 100644 --- a/cmd/lotus-sim/simulation/actor_iter.go +++ b/cmd/lotus-sim/simulation/stages/actor_iter.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "math/rand" diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/stages/commit_queue.go similarity index 99% rename from cmd/lotus-sim/simulation/commit_queue.go rename to cmd/lotus-sim/simulation/stages/commit_queue.go index 75dc6f034..515e080a0 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "sort" diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/stages/commit_queue_test.go similarity index 99% rename from cmd/lotus-sim/simulation/commit_queue_test.go rename to cmd/lotus-sim/simulation/stages/commit_queue_test.go index 7c6bc6c8f..180624493 100644 --- a/cmd/lotus-sim/simulation/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue_test.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "testing" diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/stages/funding_stage.go similarity index 68% rename from cmd/lotus-sim/simulation/funding.go rename to cmd/lotus-sim/simulation/stages/funding_stage.go index e29f4f1b8..a0d9f4a22 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/stages/funding_stage.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "bytes" @@ -13,41 +13,44 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/exitcode" - "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/multisig" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" ) var ( - fundAccount = func() address.Address { - addr, err := address.NewIDAddress(100) - if err != nil { - panic(err) - } - return addr - }() - minFundAcctFunds = abi.TokenAmount(types.MustParseFIL("1000000FIL")) - maxFundAcctFunds = abi.TokenAmount(types.MustParseFIL("100000000FIL")) - taxMin = abi.TokenAmount(types.MustParseFIL("1000FIL")) + TargetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL")) + MinimumFunds = abi.TokenAmount(types.MustParseFIL("100FIL")) ) -func fund(send packFunc, target address.Address, times int) error { - amt := targetFunds - if times >= 1 { - if times >= 8 { - times = 8 // cap - } - amt = big.Lsh(amt, uint(times)) +type FundingStage struct { + fundAccount address.Address + taxMin abi.TokenAmount + minFunds, maxFunds abi.TokenAmount +} + +func NewFundingStage() (*FundingStage, error) { + // TODO: make all this configurable. + addr, err := address.NewIDAddress(100) + if err != nil { + return nil, err } - _, err := send(&types.Message{ - From: fundAccount, - To: target, - Value: amt, - Method: builtin.MethodSend, - }) - return err + return &FundingStage{ + fundAccount: addr, + taxMin: abi.TokenAmount(types.MustParseFIL("1000FIL")), + minFunds: abi.TokenAmount(types.MustParseFIL("1000000FIL")), + maxFunds: abi.TokenAmount(types.MustParseFIL("100000000FIL")), + }, nil +} + +func (*FundingStage) Name() string { + return "funding" +} + +func (fs *FundingStage) Fund(bb *blockbuilder.BlockBuilder, target address.Address) error { + return fs.fund(bb, target, 0) } // sendAndFund "packs" the given message, funding the actor if necessary. It: @@ -56,9 +59,9 @@ func fund(send packFunc, target address.Address, times int) error { // 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. // 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from // somewhere) and re-tries the message.0 -func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, err error) { +func (fs *FundingStage) SendAndFund(bb *blockbuilder.BlockBuilder, msg *types.Message) (res *types.MessageReceipt, err error) { for i := 0; i < 10; i++ { - res, err = send(msg) + res, err = bb.PushMessage(msg) if err == nil { return res, nil } @@ -68,8 +71,8 @@ func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, } // Ok, insufficient funds. Let's fund this miner and try again. - if err := fund(send, msg.To, i); err != nil { - if err != ErrOutOfGas { + if err := fs.fund(bb, msg.To, i); err != nil { + if !blockbuilder.IsOutOfGas(err) { err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) } return nil, err @@ -78,16 +81,30 @@ func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, return res, err } -func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err error) { - st, err := ss.stateTree(ctx) +func (fs *FundingStage) fund(bb *blockbuilder.BlockBuilder, target address.Address, times int) error { + amt := TargetFunds + if times > 0 { + if times >= 8 { + times = 8 // cap + } + amt = big.Lsh(amt, uint(times)) + } + _, err := bb.PushMessage(&types.Message{ + From: fs.fundAccount, + To: target, + Value: amt, + Method: builtin.MethodSend, + }) + return err +} + +func (fs *FundingStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + st := bb.StateTree() + fundAccActor, err := st.GetActor(fs.fundAccount) if err != nil { return err } - fundAccActor, err := st.GetActor(fundAccount) - if err != nil { - return err - } - if minFundAcctFunds.LessThan(fundAccActor.Balance) { + if fs.minFunds.LessThan(fundAccActor.Balance) { return nil } @@ -102,10 +119,10 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e var targets []*actor err = st.ForEach(func(addr address.Address, act *types.Actor) error { // Don't steal from ourselves! - if addr == fundAccount { + if addr == fs.fundAccount { return nil } - if act.Balance.LessThan(taxMin) { + if act.Balance.LessThan(fs.taxMin) { return nil } if !(builtin.IsAccountActor(act.Code) || builtin.IsMultisigActor(act.Code)) { @@ -124,19 +141,16 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e return targets[i].Balance.GreaterThan(targets[j].Balance) }) - store := ss.Chainstore.ActorStore(ctx) - - epoch := ss.nextEpoch() - - nv := ss.StateManager.GetNtwkVersion(ctx, epoch) - actorsVersion := actors.VersionForNetwork(nv) + store := bb.ActorStore() + epoch := bb.Height() + actorsVersion := bb.ActorsVersion() var accounts, multisigs int defer func() { if _err != nil { return } - log.Infow("finished funding the simulation", + bb.L().Infow("finished funding the simulation", "duration", time.Since(start), "targets", len(targets), "epoch", epoch, @@ -150,11 +164,11 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e for _, actor := range targets { switch { case builtin.IsAccountActor(actor.Code): - if _, err := cb(&types.Message{ + if _, err := bb.PushMessage(&types.Message{ From: actor.Address, - To: fundAccount, + To: fs.fundAccount, Value: actor.Balance, - }); err == ErrOutOfGas { + }); blockbuilder.IsOutOfGas(err) { return nil } else if err != nil { return err @@ -172,7 +186,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e } if threshold > 16 { - log.Debugw("ignoring multisig with high threshold", + bb.L().Debugw("ignoring multisig with high threshold", "multisig", actor.Address, "threshold", threshold, "max", 16, @@ -185,7 +199,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e return err } - if locked.LessThan(taxMin) { + if locked.LessThan(fs.taxMin) { continue // not worth it. } @@ -217,15 +231,15 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e var txnId uint64 { msg, err := multisig.Message(actorsVersion, signers[0]).Propose( - actor.Address, fundAccount, available, + actor.Address, fs.fundAccount, available, builtin.MethodSend, nil, ) if err != nil { return err } - res, err := cb(msg) + res, err := bb.PushMessage(msg) if err != nil { - if err == ErrOutOfGas { + if blockbuilder.IsOutOfGas(err) { err = nil } return err @@ -237,7 +251,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e } if ret.Applied { if !ret.Code.IsSuccess() { - log.Errorw("failed to tax multisig", + bb.L().Errorw("failed to tax multisig", "multisig", actor.Address, "exitcode", ret.Code, ) @@ -252,9 +266,9 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e if err != nil { return err } - res, err := cb(msg) + res, err := bb.PushMessage(msg) if err != nil { - if err == ErrOutOfGas { + if blockbuilder.IsOutOfGas(err) { err = nil } return err @@ -271,7 +285,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e } if !ret.Applied { - log.Errorw("failed to apply multisig transaction", + bb.L().Errorw("failed to apply multisig transaction", "multisig", actor.Address, "txnid", txnId, "signers", len(signers), @@ -280,7 +294,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e continue } if !ret.Code.IsSuccess() { - log.Errorw("failed to tax multisig", + bb.L().Errorw("failed to tax multisig", "multisig", actor.Address, "txnid", txnId, "exitcode", ret.Code, @@ -292,7 +306,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e panic("impossible case") } balance = big.Int{Int: balance.Add(balance.Int, actor.Balance.Int)} - if balance.GreaterThanEqual(maxFundAcctFunds) { + if balance.GreaterThanEqual(fs.maxFunds) { // There's no need to get greedy. // Well, really, we're trying to avoid messing with state _too_ much. return nil diff --git a/cmd/lotus-sim/simulation/stages/interface.go b/cmd/lotus-sim/simulation/stages/interface.go new file mode 100644 index 000000000..0c40a9b23 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/interface.go @@ -0,0 +1,27 @@ +package stages + +import ( + "context" + + "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" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +// Stage is a stage of the simulation. It's asked to pack messages for every block. +type Stage interface { + Name() string + PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) error +} + +type Funding interface { + SendAndFund(*blockbuilder.BlockBuilder, *types.Message) (*types.MessageReceipt, error) + Fund(*blockbuilder.BlockBuilder, address.Address) error +} + +type Committer interface { + EnqueueProveCommit(addr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo) error +} diff --git a/cmd/lotus-sim/simulation/stages/pipeline.go b/cmd/lotus-sim/simulation/stages/pipeline.go new file mode 100644 index 000000000..317e5b5a9 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/pipeline.go @@ -0,0 +1,31 @@ +package stages + +// DefaultPipeline returns the default stage pipeline. This pipeline. +// +// 1. Funds a "funding" actor, if necessary. +// 2. Submits any ready window posts. +// 3. Submits any ready prove commits. +// 4. Submits pre-commits with the remaining gas. +func DefaultPipeline() ([]Stage, error) { + // TODO: make this configurable. E.g., through DI? + // Ideally, we'd also be able to change priority, limit throughput (by limiting gas in the + // block builder, etc. + funding, err := NewFundingStage() + if err != nil { + return nil, err + } + wdpost, err := NewWindowPoStStage() + if err != nil { + return nil, err + } + provecommit, err := NewProveCommitStage(funding) + if err != nil { + return nil, err + } + precommit, err := NewPreCommitStage(funding, provecommit) + if err != nil { + return nil, err + } + + return []Stage{funding, wdpost, provecommit, precommit}, nil +} diff --git a/cmd/lotus-sim/simulation/stages/precommit_stage.go b/cmd/lotus-sim/simulation/stages/precommit_stage.go new file mode 100644 index 000000000..641292e0e --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/precommit_stage.go @@ -0,0 +1,359 @@ +package stages + +import ( + "context" + "sort" + "time" + + "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/go-state-types/network" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + + "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/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" +) + +const ( + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize +) + +type PreCommitStage struct { + funding Funding + committer Committer + + // 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 determine these rates by looking at two power tables. + // TODO Ideally we'd "learn" this distribution from the network. But this is good enough for + // now. + top1, top10, rest actorIter + initialized bool +} + +func NewPreCommitStage(funding Funding, committer Committer) (*PreCommitStage, error) { + return &PreCommitStage{ + funding: funding, + committer: committer, + }, nil +} + +func (*PreCommitStage) Name() string { + return "pre-commit" +} + +// packPreCommits packs pre-commit messages until the block is full. +func (stage *PreCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + if !stage.initialized { + if err := stage.load(ctx, bb); err != nil { + return err + } + } + + var ( + full bool + top1Count, top10Count, restCount int + ) + start := time.Now() + defer func() { + if _err != nil { + return + } + bb.L().Debugw("packed pre commits", + "done", top1Count+top10Count+restCount, + "top1", top1Count, + "top10", top10Count, + "rest", restCount, + "filled-block", full, + "duration", time.Since(start), + ) + }() + + var top1Miners, top10Miners, restMiners int + for i := 0; ; i++ { + var ( + minerAddr address.Address + count *int + ) + + // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. + // This won't yeild the most accurate distribution... but it'll give us a good + // enough distribution. + switch { + case (i%3) <= 0 && top1Miners < stage.top1.len(): + count = &top1Count + minerAddr = stage.top1.next() + top1Miners++ + case (i%3) <= 1 && top10Miners < stage.top10.len(): + count = &top10Count + minerAddr = stage.top10.next() + top10Miners++ + case (i%3) <= 2 && restMiners < stage.rest.len(): + count = &restCount + minerAddr = stage.rest.next() + restMiners++ + default: + // Well, we've run through all miners. + return nil + } + + var ( + added int + err error + ) + added, full, err = stage.packMiner(ctx, bb, minerAddr, maxProveCommitBatchSize) + if err != nil { + return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) + } + *count += added + if full { + return nil + } + } +} + +// packPreCommitsMiner packs count pre-commits for the given miner. +func (stage *PreCommitStage) packMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, + minerAddr address.Address, count int, +) (int, bool, error) { + log := bb.L().With("miner", minerAddr) + epoch := bb.Height() + nv := bb.NetworkVersion() + + minerActor, err := bb.StateTree().GetActor(minerAddr) + if err != nil { + return 0, false, err + } + minerState, err := miner.Load(bb.ActorStore(), minerActor) + if err != nil { + return 0, false, err + } + + minerInfo, err := minerState.Info() + if err != nil { + return 0, false, err + } + + // Make sure the miner is funded. + minerBalance, err := minerState.AvailableBalance(minerActor.Balance) + if err != nil { + return 0, false, err + } + + if big.Cmp(minerBalance, MinimumFunds) < 0 { + err := stage.funding.Fund(bb, minerAddr) + if err != nil { + if blockbuilder.IsOutOfGas(err) { + return 0, true, nil + } + return 0, false, err + } + } + + // Generate pre-commits. + 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: mock.MockCommR(minerAddr, sno), + SealRandEpoch: epoch - 1, + Expiration: expiration, + } + } + + // Commit the pre-commits. + 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 added, false, err + } + // NOTE: just in-case, sendAndFund will "fund" and re-try for any message + // that fails due to "insufficient funds". + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSectorBatch, + Params: enc, + }); blockbuilder.IsOutOfGas(err) { + // try again with a smaller batch. + targetBatchSize /= 2 + continue + } else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() { + // Log the error and move on. No reason to stop. + log.Errorw("failed to pre-commit for unknown reasons", + "error", aerr, + "sectors", batch, + ) + return added, false, nil + } else if err != nil { + return added, false, err + } + + for _, info := range batch { + if err := stage.committer.EnqueueProveCommit(minerAddr, epoch, info); err != nil { + return added, false, err + } + added++ + } + infos = infos[len(batch):] + } + } + for _, info := range infos { + enc, err := actors.SerializeParams(&info) + if err != nil { + return 0, false, err + } + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSector, + Params: enc, + }); blockbuilder.IsOutOfGas(err) { + return added, true, nil + } else if err != nil { + return added, false, err + } + + if err := stage.committer.EnqueueProveCommit(minerAddr, epoch, info); err != nil { + return added, false, err + } + added++ + } + return added, false, nil +} + +func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + bb.L().Infow("loading miner power for pre-commits") + start := time.Now() + defer func() { + if _err != nil { + return + } + bb.L().Infow("loaded miner power for pre-commits", + "duration", time.Since(start), + "top1", ps.top1.len(), + "top10", ps.top10.len(), + "rest", ps.rest.len(), + ) + }() + lookbackEpoch := bb.Height() - (14 * builtin.EpochsInDay) + lookbackPowerTable, err := loadClaims(ctx, bb, lookbackEpoch) + if err != nil { + return xerrors.Errorf("failed to load claims from lookback epoch %d: %w", lookbackEpoch, err) + } + + store := bb.ActorStore() + st := bb.ParentStateTree() + powerState, err := loadPower(store, st) + if err != nil { + return xerrors.Errorf("failed to power actor: %w", err) + } + + type onboardingInfo struct { + addr address.Address + onboardingRate uint64 + } + sealList := make([]onboardingInfo, 0, len(lookbackPowerTable)) + err = powerState.ForEachClaim(func(addr address.Address, claim power.Claim) error { + if claim.RawBytePower.IsZero() { + return nil + } + + minerState, err := loadMiner(store, st, addr) + if err != nil { + return err + } + info, err := minerState.Info() + if err != nil { + return err + } + + 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)}) + } + return nil + }) + if err != nil { + return err + } + + if len(sealList) == 0 { + return xerrors.Errorf("simulation has no miners") + } + + // 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 + }) + + // reset, just in case. + ps.top1 = actorIter{} + ps.top10 = actorIter{} + ps.rest = actorIter{} + + for i, oi := range sealList { + var dist *actorIter + if i < len(sealList)/100 { + dist = &ps.top1 + } else if i < len(sealList)/10 { + dist = &ps.top10 + } else { + dist = &ps.rest + } + dist.add(oi.addr) + } + + ps.top1.shuffle() + ps.top10.shuffle() + ps.rest.shuffle() + + ps.initialized = true + return nil +} diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go similarity index 62% rename from cmd/lotus-sim/simulation/provecommit.go rename to cmd/lotus-sim/simulation/stages/provecommit_stage.go index 12c67ba8b..c2ffb8416 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "context" @@ -16,15 +16,50 @@ import ( "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/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" ) +const ( + minProveCommitBatchSize = 4 + maxProveCommitBatchSize = miner5.MaxAggregatedSectors +) + +type ProveCommitStage struct { + funding Funding + // We track the set of pending commits. On simulation load, and when a new pre-commit is + // added to the chain, we put the commit in this queue. advanceEpoch(currentEpoch) should be + // called on this queue at every epoch before using it. + commitQueue commitQueue + initialized bool +} + +func NewProveCommitStage(funding Funding) (*ProveCommitStage, error) { + return &ProveCommitStage{ + funding: funding, + }, nil +} + +func (*ProveCommitStage) Name() string { + return "prove-commit" +} + +func (stage *ProveCommitStage) EnqueueProveCommit( + minerAddr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo, +) error { + return stage.commitQueue.enqueueProveCommit(minerAddr, preCommitEpoch, info) +} + // packProveCommits packs all prove-commits for all "ready to be proven" sectors until it fills the // block or runs out. -func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_err error) { +func (stage *ProveCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + if !stage.initialized { + } // Roll the commitQueue forward. - ss.commitQueue.advanceEpoch(ss.nextEpoch()) + stage.commitQueue.advanceEpoch(bb.Height()) start := time.Now() var failed, done, unbatched, count int @@ -32,8 +67,8 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ if _err != nil { return } - remaining := ss.commitQueue.ready() - log.Debugw("packed prove commits", + remaining := stage.commitQueue.ready() + bb.L().Debugw("packed prove commits", "remaining", remaining, "done", done, "failed", failed, @@ -44,12 +79,12 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ }() for { - addr, pending, ok := ss.commitQueue.nextMiner() + addr, pending, ok := stage.commitQueue.nextMiner() if !ok { return nil } - res, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) + res, err := stage.packProveCommitsMiner(ctx, bb, addr, pending) if err != nil { return err } @@ -72,16 +107,26 @@ type proveCommitResult struct { // available prove-commits, batching as much as possible. // // This function will fund as necessary from the "burnt funds actor" (look, it's convenient). -func (ss *simulationState) packProveCommitsMiner( - ctx context.Context, cb packFunc, minerAddr address.Address, +func (stage *ProveCommitStage) packProveCommitsMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, minerAddr address.Address, pending minerPendingCommits, ) (res proveCommitResult, _err error) { - info, err := ss.getMinerInfo(ctx, minerAddr) + minerActor, err := bb.StateTree().GetActor(minerAddr) + if err != nil { + return res, err + } + minerState, err := miner.Load(bb.ActorStore(), minerActor) + if err != nil { + return res, err + } + info, err := minerState.Info() if err != nil { return res, err } - nv := ss.StateManager.GetNtwkVersion(ctx, ss.nextEpoch()) + log := bb.L().With("miner", minerAddr) + + nv := bb.NetworkVersion() for sealType, snos := range pending { if nv >= network.Version13 { for len(snos) > minProveCommitBatchSize { @@ -91,7 +136,7 @@ func (ss *simulationState) packProveCommitsMiner( } batch := snos[:batchSize] - proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) + proof, err := mock.MockAggregateSealProof(sealType, minerAddr, batchSize) if err != nil { return res, err } @@ -109,7 +154,7 @@ func (ss *simulationState) packProveCommitsMiner( return res, err } - if _, err := sendAndFund(cb, &types.Message{ + if _, err := stage.funding.SendAndFund(bb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), @@ -117,7 +162,7 @@ func (ss *simulationState) packProveCommitsMiner( Params: enc, }); err == nil { res.done += len(batch) - } else if err == ErrOutOfGas { + } else if blockbuilder.IsOutOfGas(err) { res.full = true return res, nil } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { @@ -135,9 +180,9 @@ func (ss *simulationState) packProveCommitsMiner( // backloged to hit this case, but we might as well handle // it. // First, split into "good" and "missing" - good, err := ss.filterProveCommits(ctx, minerAddr, batch) + good, err := stage.filterProveCommits(ctx, bb, minerAddr, batch) if err != nil { - log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) + log.Errorw("failed to filter prove commits", "error", err) // fail with the original error. return res, aerr } @@ -145,17 +190,13 @@ func (ss *simulationState) packProveCommitsMiner( if removed == 0 { log.Errorw("failed to prove-commit for unknown reasons", "error", aerr, - "miner", minerAddr, "sectors", batch, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } else if len(good) == 0 { log.Errorw("failed to prove commit missing pre-commits", "error", aerr, - "miner", minerAddr, "discarded", removed, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } else { @@ -166,10 +207,8 @@ func (ss *simulationState) packProveCommitsMiner( log.Errorw("failed to prove commit expired/missing pre-commits", "error", aerr, - "miner", minerAddr, "discarded", removed, "kept", len(good), - "epoch", ss.nextEpoch(), ) res.failed += removed @@ -178,17 +217,13 @@ func (ss *simulationState) packProveCommitsMiner( } log.Errorw("failed to prove commit missing sector(s)", "error", err, - "miner", minerAddr, "sectors", batch, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } else { log.Errorw("failed to prove commit sector(s)", "error", err, - "miner", minerAddr, "sectors", batch, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } @@ -200,7 +235,7 @@ func (ss *simulationState) packProveCommitsMiner( sno := snos[0] snos = snos[1:] - proof, err := mockSealProof(sealType, minerAddr) + proof, err := mock.MockSealProof(sealType, minerAddr) if err != nil { return res, err } @@ -212,7 +247,7 @@ func (ss *simulationState) packProveCommitsMiner( if err != nil { return res, err } - if _, err := sendAndFund(cb, &types.Message{ + if _, err := stage.funding.SendAndFund(bb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), @@ -221,7 +256,7 @@ func (ss *simulationState) packProveCommitsMiner( }); err == nil { res.unbatched++ res.done++ - } else if err == ErrOutOfGas { + } else if blockbuilder.IsOutOfGas(err) { res.full = true return res, nil } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { @@ -229,9 +264,7 @@ func (ss *simulationState) packProveCommitsMiner( } else { log.Errorw("failed to prove commit sector(s)", "error", err, - "miner", minerAddr, "sectors", []abi.SectorNumber{sno}, - "epoch", ss.nextEpoch(), ) res.failed++ } @@ -243,32 +276,35 @@ func (ss *simulationState) packProveCommitsMiner( return res, nil } -// loadProveCommitsMiner enqueue all pending prove-commits for the given miner. This is called on -// load to populate the commitQueue and should not need to be called later. +// loadMiner enqueue all pending prove-commits for the given miner. This is called on load to +// populate the commitQueue and should not need to be called later. // // It will drop any pre-commits that have already expired. -func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr address.Address, minerState miner.State) error { +func (stage *ProveCommitStage) loadMiner(ctx context.Context, bb *blockbuilder.BlockBuilder, addr address.Address) error { + epoch := bb.Height() + av := bb.ActorsVersion() + minerState, err := loadMiner(bb.ActorStore(), bb.ParentStateTree(), addr) + if err != nil { + return err + } + // 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.StateManager.GetNtwkVersion(ctx, nextEpoch) - av := actors.VersionForNetwork(nv) - var total, dropped int - err := minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + err = minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { total++ msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) - if nextEpoch > info.PreCommitEpoch+msd { + if epoch > info.PreCommitEpoch+msd { dropped++ return nil } - return ss.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) + return stage.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) }) if err != nil { return err } if dropped > 0 { - log.Warnw("dropped expired pre-commits on load", + bb.L().Warnw("dropped expired pre-commits on load", "miner", addr, "total", total, "expired", dropped, @@ -278,15 +314,22 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre } // filterProveCommits filters out expired and/or missing pre-commits. -func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) ([]abi.SectorNumber, error) { - _, minerState, err := ss.getMinerState(ctx, minerAddr) +func (stage *ProveCommitStage) filterProveCommits( + ctx context.Context, bb *blockbuilder.BlockBuilder, + minerAddr address.Address, snos []abi.SectorNumber, +) ([]abi.SectorNumber, error) { + act, err := bb.StateTree().GetActor(minerAddr) if err != nil { return nil, err } - nextEpoch := ss.nextEpoch() - nv := ss.StateManager.GetNtwkVersion(ctx, nextEpoch) - av := actors.VersionForNetwork(nv) + minerState, err := miner.Load(bb.ActorStore(), act) + if err != nil { + return nil, err + } + + nextEpoch := bb.Height() + av := bb.ActorsVersion() good := make([]abi.SectorNumber, 0, len(snos)) for _, sno := range snos { @@ -305,3 +348,19 @@ func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr add } return good, nil } + +func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + powerState, err := loadPower(bb.ActorStore(), bb.ParentStateTree()) + if err != nil { + return err + } + + return powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + // TODO: If we want to finish pre-commits for "new" miners, we'll need to change + // this. + if claim.RawBytePower.IsZero() { + return nil + } + return stage.loadMiner(ctx, bb, minerAddr) + }) +} diff --git a/cmd/lotus-sim/simulation/stages/util.go b/cmd/lotus-sim/simulation/stages/util.go new file mode 100644 index 000000000..4c23a83d6 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/util.go @@ -0,0 +1,81 @@ +package stages + +import ( + "context" + + "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/crypto" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +func loadMiner(store adt.Store, st types.StateTree, addr address.Address) (miner.State, error) { + minerActor, err := st.GetActor(addr) + if err != nil { + return nil, err + } + return miner.Load(store, minerActor) +} + +func loadPower(store adt.Store, st types.StateTree) (power.State, error) { + powerActor, err := st.GetActor(power.Address) + if err != nil { + return nil, err + } + return power.Load(store, powerActor) +} + +// 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() +} + +// loadClaims will load all non-zero claims at the given epoch. +func loadClaims( + ctx context.Context, bb *blockbuilder.BlockBuilder, height abi.ChainEpoch, +) (map[address.Address]power.Claim, error) { + powerTable := make(map[address.Address]power.Claim) + + st, err := bb.StateTreeByHeight(height) + if err != nil { + return nil, err + } + + powerState, err := loadPower(bb.ActorStore(), st) + if err != nil { + return nil, err + } + + err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { + // skip miners without power + if claim.RawBytePower.IsZero() { + return nil + } + powerTable[miner] = claim + return nil + }) + if err != nil { + return nil, err + } + return powerTable, nil +} + +func postChainCommitInfo(ctx context.Context, bb *blockbuilder.BlockBuilder, epoch abi.ChainEpoch) (abi.Randomness, error) { + cs := bb.StateManager().ChainStore() + ts := bb.ParentTipSet() + commitRand, err := cs.GetChainRandomness(ctx, ts.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true) + return commitRand, err +} diff --git a/cmd/lotus-sim/simulation/stages/windowpost_stage.go b/cmd/lotus-sim/simulation/stages/windowpost_stage.go new file mode 100644 index 000000000..e6583012d --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/windowpost_stage.go @@ -0,0 +1,312 @@ +package stages + +import ( + "context" + "math" + "time" + + "golang.org/x/xerrors" + + "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" + + "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/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" +) + +type WindowPoStStage struct { + // We track the window post periods per miner and assume that no new miners are ever added. + + // We record all pending window post messages, and the epoch up through which we've + // generated window post messages. + pendingWposts []*types.Message + wpostPeriods [][]address.Address // (epoch % (epochs in a deadline)) -> miner + nextWpostEpoch abi.ChainEpoch +} + +func NewWindowPoStStage() (*WindowPoStStage, error) { + return new(WindowPoStStage), nil +} + +func (*WindowPoStStage) Name() string { + return "window-post" +} + +// packWindowPoSts packs window posts until either the block is full or all healty sectors +// have been proven. It does not recover sectors. +func (stage *WindowPoStStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + // Push any new window posts into the queue. + if err := stage.tick(ctx, bb); err != nil { + return err + } + done := 0 + failed := 0 + defer func() { + if _err != nil { + return + } + + bb.L().Debugw("packed window posts", + "done", done, + "failed", failed, + "remaining", len(stage.pendingWposts), + ) + }() + // Then pack as many as we can. + for len(stage.pendingWposts) > 0 { + next := stage.pendingWposts[0] + if _, err := bb.PushMessage(next); err != nil { + if blockbuilder.IsOutOfGas(err) { + return nil + } + if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return err + } + bb.L().Errorw("failed to submit windowed post", + "error", err, + "miner", next.To, + ) + failed++ + } else { + done++ + } + + stage.pendingWposts = stage.pendingWposts[1:] + } + stage.pendingWposts = nil + return nil +} + +// stepWindowPoStsMiner enqueues all missing window posts for the current epoch for the given miner. +func (stage *WindowPoStStage) queueMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, + 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 := minerState.Info() + if err != nil { + return err + } + + di, err := minerState.DeadlineInfo(bb.Height()) + 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 := mock.MockWindowPoStProof(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), + } + stage.pendingWposts = append(stage.pendingWposts, msg) + } + return nil +} + +func (stage *WindowPoStStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + bb.L().Info("loading window post info") + + start := time.Now() + defer func() { + if _err != nil { + return + } + + bb.L().Infow("loaded window post info", "duration", time.Since(start)) + }() + + // reset + stage.wpostPeriods = make([][]address.Address, miner.WPoStChallengeWindow) + stage.pendingWposts = nil + stage.nextWpostEpoch = bb.Height() + 1 + + st := bb.ParentStateTree() + store := bb.ActorStore() + + powerState, err := loadPower(store, st) + if err != nil { + return err + } + + commitEpoch := bb.ParentTipSet().Height() + commitRand, err := postChainCommitInfo(ctx, bb, commitEpoch) + if err != nil { + return err + } + + return powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + // TODO: If we start recovering power, we'll need to change this. + if claim.RawBytePower.IsZero() { + return nil + } + + minerState, err := loadMiner(store, st, minerAddr) + if err != nil { + return err + } + + // Shouldn't be necessary if the miner has power, but we might as well be safe. + if active, err := minerState.DeadlineCronActive(); err != nil { + return err + } else if !active { + return nil + } + + // Record when we need to prove for this miner. + dinfo, err := minerState.DeadlineInfo(bb.Height()) + if err != nil { + return err + } + dinfo = dinfo.NextNotElapsed() + + ppOffset := int(dinfo.PeriodStart % miner.WPoStChallengeWindow) + stage.wpostPeriods[ppOffset] = append(stage.wpostPeriods[ppOffset], minerAddr) + + return stage.queueMiner(ctx, bb, minerAddr, minerState, commitEpoch, commitRand) + }) +} + +func (stage *WindowPoStStage) tick(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + // If this is our first time, load from scratch. + if stage.wpostPeriods == nil { + return stage.load(ctx, bb) + } + + targetHeight := bb.Height() + now := time.Now() + was := len(stage.pendingWposts) + count := 0 + defer func() { + bb.L().Debugw("computed window posts", + "miners", count, + "count", len(stage.pendingWposts)-was, + "duration", time.Since(now), + ) + }() + + st := bb.ParentStateTree() + store := bb.ActorStore() + + // 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 ; stage.nextWpostEpoch <= targetHeight; stage.nextWpostEpoch++ { + if stage.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight { + bb.L().Warnw("skipping old window post", "deadline-open", stage.nextWpostEpoch) + continue + } + commitEpoch := stage.nextWpostEpoch - 1 + commitRand, err := postChainCommitInfo(ctx, bb, commitEpoch) + if err != nil { + return err + } + + for _, addr := range stage.wpostPeriods[int(stage.nextWpostEpoch%miner.WPoStChallengeWindow)] { + minerState, err := loadMiner(store, st, addr) + if err != nil { + return err + } + + if err := stage.queueMiner(ctx, bb, addr, minerState, commitEpoch, commitRand); err != nil { + return err + } + count++ + } + + } + return nil +} diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go deleted file mode 100644 index 383ef158a..000000000 --- a/cmd/lotus-sim/simulation/state.go +++ /dev/null @@ -1,202 +0,0 @@ -package simulation - -import ( - "context" - "sort" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" -) - -// simualtionState holds the "state" of the simulation. This is split from the Simulation type so we -// can load it on-dempand if and when we need to actually _run_ the simualation. Loading the -// simulation state requires walking all active miners. -type simulationState struct { - *Simulation - - // 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 determine these rates by looking at two power tables. - // TODO Ideally we'd "learn" this distribution from the network. But this is good enough for - // now. - minerDist struct { - top1, top10, rest actorIter - } - - // 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 - - // We track the set of pending commits. On simulation load, and when a new pre-commit is - // added to the chain, we put the commit in this queue. advanceEpoch(currentEpoch) should be - // called on this queue at every epoch before using it. - 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 - } - - 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. - _, minerState, err := state.getMinerState(ctx, addr) - 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)}) - } - } - if len(sealList) == 0 { - return nil, xerrors.Errorf("simulation has no miners") - } - - // 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 *actorIter - 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 -} - -// nextEpoch returns the next epoch (head+1). -func (ss *simulationState) nextEpoch() abi.ChainEpoch { - return ss.GetHead().Height() + 1 -} - -// getMinerInfo returns the miner's cached info. -// -// NOTE: we assume that miner infos won't change. We'll need to fix this if we start supporting arbitrary message. -func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Address) (*miner.MinerInfo, error) { - minerInfo, ok := ss.minerInfos[addr] - if !ok { - _, minerState, err := ss.getMinerState(ctx, addr) - 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 -} - -// getMinerState loads the miner actor & state. -func (ss *simulationState) getMinerState(ctx context.Context, addr address.Address) (*types.Actor, miner.State, error) { - st, err := ss.stateTree(ctx) - if err != nil { - return nil, nil, err - } - act, err := st.GetActor(addr) - if err != nil { - return nil, nil, err - } - state, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) - if err != nil { - return nil, nil, err - } - return act, state, err -} diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 1106e0d6e..902f2ad6c 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -2,83 +2,38 @@ package simulation import ( "context" - "errors" - "reflect" - "runtime" - "strings" "golang.org/x/xerrors" - "github.com/filecoin-project/go-address" - "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" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" ) -const ( - // The number of expected blocks in a tipset. We use this to determine how much gas a tipset - // has. - 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 -} - -// step steps the simulation state forward one step, producing and executing a new tipset. -func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { - log.Infow("step", "epoch", ss.head.Height()+1) - messages, err := ss.popNextMessages(ctx) + log.Infow("step", "epoch", sim.head.Height()+1) + messages, err := sim.popNextMessages(ctx) if err != nil { return nil, xerrors.Errorf("failed to select messages for block: %w", err) } - head, err := ss.makeTipSet(ctx, messages) + head, err := sim.makeTipSet(ctx, messages) if err != nil { return nil, xerrors.Errorf("failed to make tipset: %w", err) } - if err := ss.SetHead(head); err != nil { + if err := sim.SetHead(head); err != nil { return nil, xerrors.Errorf("failed to update head: %w", err) } return head, nil } -var ErrOutOfGas = errors.New("out of gas") - -// packFunc takes a message and attempts to pack it into a block. -// -// - If the block is full, returns the error ErrOutOfGas. -// - If message execution fails, check if error is an ActorError to get the return code. -type packFunc func(*types.Message) (*types.MessageReceipt, error) - // popNextMessages generates/picks a set of messages to be included in the next block. // // - This function is destructive and should only be called once per epoch. // - This function does not store anything in the repo. // - This function handles all gas estimation. The returned messages should all fit in a single // block. -func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Message, error) { - parentTs := ss.head +func (sim *Simulation) popNextMessages(ctx context.Context) ([]*types.Message, error) { + parentTs := sim.head // First we make sure we don't have an upgrade at this epoch. If we do, we return no // messages so we can just create an empty block at that epoch. @@ -86,8 +41,8 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // This isn't what the network does, but it makes things easier. Otherwise, we'd need to run // migrations before this epoch and I'd rather not deal with that. nextHeight := parentTs.Height() + 1 - prevVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight-1) - nextVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight) + prevVer := sim.StateManager.GetNtwkVersion(ctx, nextHeight-1) + nextVer := sim.StateManager.GetNtwkVersion(ctx, nextHeight) if nextVer != prevVer { log.Warnw("packing no messages for version upgrade block", "old", prevVer, @@ -97,170 +52,20 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag return nil, nil } - // Next, we compute the state for the parent tipset. In practice, this will likely be - // cached. - parentState, _, err := ss.StateManager.TipSetState(ctx, parentTs) + bb, err := blockbuilder.NewBlockBuilder( + ctx, log.With("simulation", sim.name), + sim.StateManager, parentTs, + ) if err != nil { return nil, err } - // Then we construct a VM to execute messages for gas estimation. - // - // Most parts of this VM are "real" except: - // 1. We don't charge a fee. - // 2. The runtime has "fake" proof logic. - // 3. We don't actually save any of the results. - r := store.NewChainRand(ss.StateManager.ChainStore(), parentTs.Cids()) - vmopt := &vm.VMOpts{ - StateBase: parentState, - Epoch: nextHeight, - Rand: r, - Bstore: ss.StateManager.ChainStore().StateBlockstore(), - Syscalls: ss.StateManager.ChainStore().VMSys(), - CircSupplyCalc: ss.StateManager.GetVMCirculatingSupply, - NtwkVersion: ss.StateManager.GetNtwkVersion, - BaseFee: baseFee, // FREE! - LookbackState: stmgr.LookbackStateGetterForTipset(ss.StateManager, parentTs), - } - vmi, err := vm.NewVM(ctx, vmopt) - if err != nil { - return nil, err - } - - // Next we define a helper function for "pushing" messages. This is the function that will - // be passed to the "pack" functions. - // - // It. - // - // 1. Tries to execute the message on-top-of the already pushed message. - // 2. Is careful to revert messages on failure to avoid nasties like nonce-gaps. - // 3. Resolves IDs as necessary, fills in missing parts of the message, etc. - vmStore := vmi.ActorStore(ctx) - var gasTotal int64 - var messages []*types.Message - tryPushMsg := func(msg *types.Message) (*types.MessageReceipt, error) { - if gasTotal >= targetGas { - return nil, ErrOutOfGas - } - - // 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 nil, err - } - msg.Nonce = actor.Nonce - if msg.From.Protocol() == address.ID { - state, err := account.Load(vmStore, actor) - if err != nil { - return nil, err - } - msg.From, err = state.PubkeyAddress() - if err != nil { - return nil, 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 nil, err - } - if ret.ActorErr != nil { - _ = st.Revert() - return nil, ret.ActorErr - } - - // Sometimes there are bugs. Let's catch them. - if ret.GasUsed == 0 { - _ = st.Revert() - return nil, 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 nil, ErrOutOfGas - } - gasTotal = newTotal - - // Update the gas limit. - msg.GasLimit = ret.GasUsed - - messages = append(messages, msg) - return &ret.MessageReceipt, nil - } - - // Finally, we generate a set of messages to be included in - if err := ss.packMessages(ctx, tryPushMsg); err != nil { - return nil, err - } - - return messages, nil -} - -// functionName extracts the name of given function. -func functionName(fn interface{}) string { - name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() - lastDot := strings.LastIndexByte(name, '.') - if lastDot >= 0 { - name = name[lastDot+1 : len(name)-3] - } - lastDash := strings.LastIndexByte(name, '-') - if lastDash > 0 { - name = name[:lastDash] - } - return name -} - -// packMessages packs messages with the given packFunc until the block is full (packFunc returns -// true). -// TODO: Make this more configurable for other simulations. -func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error { - type messageGenerator func(ctx context.Context, cb packFunc) error - - // We pack messages in-order: - // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only - // miss them if/when we run out of chain bandwidth. - // 2. We then move funds to our "funding" account, if it's running low. - // 3. Prove commits. We do this eagerly to ensure they don't expire. - // 4. Finally, we fill the rest of the space with pre-commits. - messageGenerators := []messageGenerator{ - ss.packWindowPoSts, - ss.packFunding, - ss.packProveCommits, - ss.packPreCommits, - } - - for _, mgen := range messageGenerators { + for _, stage := range sim.stages { // We're intentionally ignoring the "full" signal so we can try to pack a few more // messages. - if err := mgen(ctx, cb); err != nil && !xerrors.Is(err, ErrOutOfGas) { - return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) + if err := stage.PackMessages(ctx, bb); err != nil && !blockbuilder.IsOutOfGas(err) { + return nil, xerrors.Errorf("when packing messages with %s: %w", stage.Name(), err) } } - return nil + return bb.Messages(), nil } diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go deleted file mode 100644 index 7e6f2401e..000000000 --- a/cmd/lotus-sim/simulation/wdpost.go +++ /dev/null @@ -1,229 +0,0 @@ -package simulation - -import ( - "context" - "math" - "time" - - "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/crypto" - - proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" - - "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" -) - -// postChainCommitInfo returns th -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 -} - -// packWindowPoSts packs window posts until either the block is full or all healty sectors -// have been proven. It does not recover sectors. -func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (_err error) { - // Push any new window posts into the queue. - if err := ss.queueWindowPoSts(ctx); err != nil { - return 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 _, err := cb(next); err != nil { - if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { - return err - } - log.Errorw("failed to submit windowed post", - "error", err, - "miner", next.To, - "epoch", ss.nextEpoch(), - ) - failed++ - } else { - done++ - } - - ss.pendingWposts = ss.pendingWposts[1:] - } - ss.pendingWposts = nil - return nil -} - -// stepWindowPoStsMiner enqueues 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 -} - -// queueWindowPoSts enqueues missing window posts for all miners with deadlines opening between the -// last epoch in which this function was called and the current epoch (head+1). -func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { - targetHeight := ss.nextEpoch() - - 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 - } - - for _, addr := range ss.wpostPeriods[int(ss.nextWpostEpoch%miner.WPoStChallengeWindow)] { - _, minerState, err := ss.getMinerState(ctx, addr) - if err != nil { - return err - } - if err := ss.stepWindowPoStsMiner(ctx, addr, minerState, commitEpoch, commitRand); err != nil { - return err - } - count++ - } - - } - return nil -} From f6043a025036c1f457cfecd101eb1d20af6c3450 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 09:57:27 -0700 Subject: [PATCH 61/94] feat(lotus-sim): measure daily power growth --- cmd/lotus-sim/info.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 6a37f7258..cae8c9375 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -38,20 +38,34 @@ func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet } func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) error { - powerNow, err := getTotalPower(ctx, sim.StateManager, sim.GetHead()) + head := sim.GetHead() + start := sim.GetStart() + + powerNow, err := getTotalPower(ctx, sim.StateManager, head) if err != nil { return err } - powerStart, err := getTotalPower(ctx, sim.StateManager, sim.GetStart()) + powerLookbackEpoch := head.Height() - builtin.EpochsInDay + if powerLookbackEpoch < start.Height() { + powerLookbackEpoch = start.Height() + } + lookbackTs, err := sim.Chainstore.GetTipsetByHeight(ctx, powerLookbackEpoch, head, false) if err != nil { return err } - powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) + powerLookback, err := getTotalPower(ctx, sim.StateManager, lookbackTs) + if err != nil { + return err + } + // growth rate in size/day + growthRate := big.Div( + big.Mul(big.Sub(powerNow.RawBytePower, powerLookback.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(head.Height()-lookbackTs.Height())), + ) tw := tabwriter.NewWriter(out, 8, 8, 1, ' ', 0) - head := sim.GetHead() - start := sim.GetStart() headEpoch := head.Height() firstEpoch := start.Height() + 1 @@ -59,12 +73,6 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e startTime := time.Unix(int64(start.MinTimestamp()), 0) duration := headTime.Sub(startTime) - // growth rate in size/day - growthRate := big.Div( - big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), - big.NewInt(int64(duration)), - ) - fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) fmt.Fprintf(tw, "Head:\t%s\n", head) fmt.Fprintf(tw, "Start Epoch:\t%d\n", firstEpoch) @@ -74,8 +82,7 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e fmt.Fprintf(tw, "End Date:\t%s\n", headTime) fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) - fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) - fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Daily Power Growth:\t%s/day\n", types.SizeStr(growthRate)) fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) return tw.Flush() } From ec3f969e9a671ab17f27cc0b24d070019b7e69d0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 10:08:36 -0700 Subject: [PATCH 62/94] feat(lotus-sim): allow walking back past the start --- cmd/lotus-sim/simulation/simulation.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 0af1120c2..a8a0e7197 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -257,16 +257,16 @@ type AppliedMessage struct { // Walk walks the simulation's chain from the current head back to the first tipset. func (sim *Simulation) Walk( ctx context.Context, - maxLookback int64, + lookback int64, cb func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, messages []*AppliedMessage) error, ) error { store := sim.Chainstore.ActorStore(ctx) - minEpoch := abi.ChainEpoch(0) - if maxLookback != 0 { - minEpoch = sim.head.Height() - abi.ChainEpoch(maxLookback) + minEpoch := sim.start.Height() + if lookback != 0 { + minEpoch = sim.head.Height() - abi.ChainEpoch(lookback) } // Given tha loading messages and receipts can be a little bit slow, we do this in parallel. @@ -314,7 +314,7 @@ func (sim *Simulation) Walk( return err } i := 0 - for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { + for ctx.Err() == nil && ts.Height() > minEpoch { select { case workQs[i] <- &work{ts, stCid, recCid}: case <-ctx.Done(): From f9d2a231329e21584fef67d57fe528eb2a34bc46 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 10:16:50 -0700 Subject: [PATCH 63/94] fix(lotus-sim): correctly handle cancellation in walk 1. Select order is not guaranteed, always check if the context has been canceled explicitly. 2. Never close a work channel unless we're actually done. This can yield out-of-order results due to buffering. --- cmd/lotus-sim/simulation/simulation.go | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index a8a0e7197..ef4278178 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -314,7 +314,11 @@ func (sim *Simulation) Walk( return err } i := 0 - for ctx.Err() == nil && ts.Height() > minEpoch { + for ts.Height() > minEpoch { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + select { case workQs[i] <- &work{ts, stCid, recCid}: case <-ctx.Done(): @@ -340,7 +344,23 @@ func (sim *Simulation) Walk( workQ := workQs[i] resultQ := resultQs[i] grp.Go(func() error { - for job := range workQ { + for { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + + var job *work + var ok bool + select { + case job, ok = <-workQ: + case <-ctx.Done(): + return ctx.Err() + } + + if !ok { + break + } + msgs, err := sim.Chainstore.MessagesForTipset(job.ts) if err != nil { return err @@ -381,6 +401,10 @@ func (sim *Simulation) Walk( grp.Go(func() error { qs := resultQs for len(qs) > 0 { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + newQs := qs[:0] for _, q := range qs { select { From 0af7dcdedb7f3f52f447e7be4ebd993e65b4f717 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 10:18:17 -0700 Subject: [PATCH 64/94] fix(lotus-sim): rename power to capacity --- cmd/lotus-sim/info.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index cae8c9375..f21adb304 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -81,8 +81,8 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e fmt.Fprintf(tw, "Start Date:\t%s\n", startTime) fmt.Fprintf(tw, "End Date:\t%s\n", headTime) fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) - fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) - fmt.Fprintf(tw, "Daily Power Growth:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Capacity:\t%s\n", types.SizeStr(powerNow.RawBytePower)) + fmt.Fprintf(tw, "Daily Capacity Growth:\t%s/day\n", types.SizeStr(growthRate)) fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) return tw.Flush() } From 63178ce982c9bcdf46f8cf282e90c1d4ecd7bbcd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 11:44:38 -0700 Subject: [PATCH 65/94] feat(lotus-sim): daily capacity growth --- cmd/lotus-sim/info.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index f21adb304..591e35c80 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -93,6 +93,7 @@ var infoSimCommand = &cli.Command{ Subcommands: []*cli.Command{ infoCommitGasSimCommand, infoWindowPostBandwidthSimCommand, + infoCapacityGrowthSimCommand, }, Action: func(cctx *cli.Context) error { node, err := open(cctx) @@ -158,6 +159,57 @@ var infoWindowPostBandwidthSimCommand = &cli.Command{ }, } +var infoCapacityGrowthSimCommand = &cli.Command{ + Name: "capacity-growth", + Description: "List daily capacity growth over the course of the simulation starting at the end.", + 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 + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + lastHeight := ts.Height() + + for ts.Height() > firstEpoch && cctx.Err() == nil { + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + newEpoch := ts.Height() + if newEpoch != firstEpoch && newEpoch+builtin.EpochsInDay > lastHeight { + continue + } + + newPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + + growthRate := big.Div( + big.Mul(big.Sub(lastPower.RawBytePower, newPower.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(lastHeight-newEpoch)), + ) + lastPower = newPower + lastHeight = newEpoch + fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) + } + return cctx.Err() + }, +} + var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", Description: "Output information about the gas for commits", From 8fffaa5c47518c5949ba9f43d522d3df11b0e649 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 11:51:35 -0700 Subject: [PATCH 66/94] fix(lotus-sim): average over 2 days There's too much noise per day. --- cmd/lotus-sim/info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 591e35c80..82e531015 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -45,7 +45,7 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e if err != nil { return err } - powerLookbackEpoch := head.Height() - builtin.EpochsInDay + powerLookbackEpoch := head.Height() - builtin.EpochsInDay*2 if powerLookbackEpoch < start.Height() { powerLookbackEpoch = start.Height() } From 95cf57744726b860533481cc2362a47a4bfbf462 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 12:02:36 -0700 Subject: [PATCH 67/94] fix(lotus-sim): really cancel walk immediately --- cmd/lotus-sim/simulation/simulation.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index ef4278178..0b8ab1e56 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -401,12 +401,11 @@ func (sim *Simulation) Walk( grp.Go(func() error { qs := resultQs for len(qs) > 0 { - if err := ctx.Err(); err != nil { - return ctx.Err() - } - newQs := qs[:0] for _, q := range qs { + if err := ctx.Err(); err != nil { + return ctx.Err() + } select { case r, ok := <-q: if !ok { From 5d7b7ce5c156beb0c562b88ea574994477611e7d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 12:11:55 -0700 Subject: [PATCH 68/94] feat(lotus-sim): allow profile info --- cmd/lotus-sim/info.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 82e531015..ef20b7f26 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "syscall" "text/tabwriter" "time" @@ -229,6 +230,8 @@ var infoCommitGasSimCommand = &cli.Command{ } defer node.Close() + go profileOnSignal(cctx, syscall.SIGUSR2) + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { return err From 22267eb45dd5fcabb31ec52f49bc3892491b2a9c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 14 Jun 2021 13:05:28 -0700 Subject: [PATCH 69/94] feat(lotus-sim): split info command file --- chain/actors/builtin/miner/miner.go | 2 + cmd/lotus-sim/info.go | 234 +--------------------------- cmd/lotus-sim/info_capacity.go | 63 ++++++++ cmd/lotus-sim/info_commit.go | 144 +++++++++++++++++ cmd/lotus-sim/info_wdpost.go | 65 ++++++++ 5 files changed, 275 insertions(+), 233 deletions(-) create mode 100644 cmd/lotus-sim/info_capacity.go create mode 100644 cmd/lotus-sim/info_commit.go create mode 100644 cmd/lotus-sim/info_wdpost.go diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index bb7f80340..995dc78cb 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -22,6 +22,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" @@ -239,6 +240,7 @@ type DeclareFaultsRecoveredParams = miner0.DeclareFaultsRecoveredParams type SubmitWindowedPoStParams = miner0.SubmitWindowedPoStParams type ProveCommitSectorParams = miner0.ProveCommitSectorParams type DisputeWindowedPoStParams = miner3.DisputeWindowedPoStParams +type ProveCommitAggregateParams = miner5.ProveCommitAggregateParams func PreferredSealProofTypeFromWindowPoStType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredSealProof, error) { // We added support for the new proofs in network version 7, and removed support for the old diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index ef20b7f26..4cd453440 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -1,29 +1,21 @@ package main import ( - "bytes" "context" "fmt" "io" - "os" - "syscall" "text/tabwriter" "time" - "github.com/ipfs/go-cid" - "github.com/streadway/quantile" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/exitcode" - "github.com/filecoin-project/specs-actors/v5/actors/builtin" - "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" - "github.com/filecoin-project/lotus/lib/stati" ) func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { @@ -110,227 +102,3 @@ var infoSimCommand = &cli.Command{ return printInfo(cctx.Context, sim, cctx.App.Writer) }, } - -var infoWindowPostBandwidthSimCommand = &cli.Command{ - Name: "post-bandwidth", - Description: "List average chain bandwidth used by window posts for each day of the simulation.", - 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 - } - - var postGas, totalGas int64 - printStats := func() { - fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) - } - idx := 0 - err = sim.Walk(cctx.Context, 0, func( - sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage, - ) error { - for _, m := range messages { - totalGas += m.GasUsed - if m.ExitCode != exitcode.Ok { - continue - } - if m.Method == builtin.MethodsMiner.SubmitWindowedPoSt { - postGas += m.GasUsed - } - } - idx++ - idx %= builtin.EpochsInDay - if idx == 0 { - printStats() - postGas = 0 - totalGas = 0 - } - return nil - }) - if idx > 0 { - printStats() - } - return err - }, -} - -var infoCapacityGrowthSimCommand = &cli.Command{ - Name: "capacity-growth", - Description: "List daily capacity growth over the course of the simulation starting at the end.", - 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 - } - - firstEpoch := sim.GetStart().Height() - ts := sim.GetHead() - lastPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) - if err != nil { - return err - } - lastHeight := ts.Height() - - for ts.Height() > firstEpoch && cctx.Err() == nil { - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) - if err != nil { - return err - } - newEpoch := ts.Height() - if newEpoch != firstEpoch && newEpoch+builtin.EpochsInDay > lastHeight { - continue - } - - newPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) - if err != nil { - return err - } - - growthRate := big.Div( - big.Mul(big.Sub(lastPower.RawBytePower, newPower.RawBytePower), - big.NewInt(builtin.EpochsInDay)), - big.NewInt(int64(lastHeight-newEpoch)), - ) - lastPower = newPower - lastHeight = newEpoch - fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) - } - return cctx.Err() - }, -} - -var infoCommitGasSimCommand = &cli.Command{ - Name: "commit-gas", - Description: "Output information about the gas for commits", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "lookback", - Value: 0, - }, - }, - Action: func(cctx *cli.Context) error { - log := func(f string, i ...interface{}) { - fmt.Fprintf(os.Stderr, f, i...) - } - node, err := open(cctx) - if err != nil { - return err - } - defer node.Close() - - go profileOnSignal(cctx, syscall.SIGUSR2) - - sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) - if err != nil { - return err - } - - var gasAgg, proofsAgg uint64 - var gasAggMax, proofsAggMax uint64 - var gasSingle, proofsSingle uint64 - - qpoints := []struct{ q, tol float64 }{ - {0.01, 0.0005}, - {0.05, 0.001}, - {0.20, 0.01}, - {0.25, 0.01}, - {0.30, 0.01}, - {0.40, 0.01}, - {0.45, 0.01}, - {0.50, 0.01}, - {0.60, 0.01}, - {0.80, 0.01}, - {0.95, 0.001}, - {0.99, 0.0005}, - } - estims := make([]quantile.Estimate, len(qpoints)) - for i, p := range qpoints { - estims[i] = quantile.Known(p.q, p.tol) - } - qua := quantile.New(estims...) - hist, err := stati.NewHistogram([]float64{ - 1, 3, 5, 7, 15, 30, 50, 100, 200, 400, 600, 700, 819}) - if err != nil { - return err - } - - err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( - sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage, - ) error { - for _, m := range messages { - if m.ExitCode != exitcode.Ok { - continue - } - if m.Method == builtin.MethodsMiner.ProveCommitAggregate { - param := miner.ProveCommitAggregateParams{} - err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) - if err != nil { - log("failed to decode params: %+v", err) - return nil - } - c, err := param.SectorNumbers.Count() - if err != nil { - log("failed to count sectors") - return nil - } - gasAgg += uint64(m.GasUsed) - proofsAgg += c - if c == 819 { - gasAggMax += uint64(m.GasUsed) - proofsAggMax += c - } - for i := uint64(0); i < c; i++ { - qua.Add(float64(c)) - } - hist.Observe(float64(c)) - } - - if m.Method == builtin.MethodsMiner.ProveCommitSector { - gasSingle += uint64(m.GasUsed) - proofsSingle++ - qua.Add(1) - hist.Observe(1) - } - } - - return nil - }) - if err != nil { - return err - } - idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) - - fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) - - fmt.Printf("Proofs in singles: %d\n", proofsSingle) - fmt.Printf("Proofs in Aggs: %d\n", proofsAgg) - fmt.Printf("Proofs in Aggs(819): %d\n", proofsAggMax) - - fmt.Println() - fmt.Println("Quantiles of proofs in given aggregate size:") - for _, p := range qpoints { - fmt.Printf("%.0f%%\t%.0f\n", p.q*100, qua.Get(p.q)) - } - fmt.Println() - fmt.Println("Histogram of messages:") - fmt.Printf("Total\t%d\n", hist.Total()) - for i, b := range hist.Buckets[1:] { - fmt.Printf("%.0f\t%d\n", b, hist.Get(i)) - } - - return nil - }, -} diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go new file mode 100644 index 000000000..14ee36f08 --- /dev/null +++ b/cmd/lotus-sim/info_capacity.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +var infoCapacityGrowthSimCommand = &cli.Command{ + Name: "capacity-growth", + Description: "List daily capacity growth over the course of the simulation starting at the end.", + 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 + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + lastHeight := ts.Height() + + for ts.Height() > firstEpoch && cctx.Err() == nil { + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + newEpoch := ts.Height() + if newEpoch != firstEpoch && newEpoch+builtin.EpochsInDay > lastHeight { + continue + } + + newPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + + growthRate := big.Div( + big.Mul(big.Sub(lastPower.RawBytePower, newPower.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(lastHeight-newEpoch)), + ) + lastPower = newPower + lastHeight = newEpoch + fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) + } + return cctx.Err() + }, +} diff --git a/cmd/lotus-sim/info_commit.go b/cmd/lotus-sim/info_commit.go new file mode 100644 index 000000000..f6b08ea05 --- /dev/null +++ b/cmd/lotus-sim/info_commit.go @@ -0,0 +1,144 @@ +package main + +import ( + "bytes" + "fmt" + "os" + + "github.com/ipfs/go-cid" + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" + "syscall" + + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" +) + +var infoCommitGasSimCommand = &cli.Command{ + Name: "commit-gas", + Description: "Output information about the gas for commits", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, + Action: func(cctx *cli.Context) error { + log := func(f string, i ...interface{}) { + fmt.Fprintf(os.Stderr, f, i...) + } + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + go profileOnSignal(cctx, syscall.SIGUSR2) + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + var gasAgg, proofsAgg uint64 + var gasAggMax, proofsAggMax uint64 + var gasSingle, proofsSingle uint64 + + qpoints := []struct{ q, tol float64 }{ + {0.01, 0.0005}, + {0.05, 0.001}, + {0.20, 0.01}, + {0.25, 0.01}, + {0.30, 0.01}, + {0.40, 0.01}, + {0.45, 0.01}, + {0.50, 0.01}, + {0.60, 0.01}, + {0.80, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1, 3, 5, 7, 15, 30, 50, 100, 200, 400, 600, 700, 819}) + if err != nil { + return err + } + + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == miner.Methods.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil + } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil + } + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c + } + for i := uint64(0); i < c; i++ { + qua.Add(float64(c)) + } + hist.Observe(float64(c)) + } + + if m.Method == miner.Methods.ProveCommitSector { + gasSingle += uint64(m.GasUsed) + proofsSingle++ + qua.Add(1) + hist.Observe(1) + } + } + + return nil + }) + if err != nil { + return err + } + idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) + + fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) + + fmt.Printf("Proofs in singles: %d\n", proofsSingle) + fmt.Printf("Proofs in Aggs: %d\n", proofsAgg) + fmt.Printf("Proofs in Aggs(819): %d\n", proofsAggMax) + + fmt.Println() + fmt.Println("Quantiles of proofs in given aggregate size:") + for _, p := range qpoints { + fmt.Printf("%.0f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of messages:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\n", b, hist.Get(i)) + } + + return nil + }, +} diff --git a/cmd/lotus-sim/info_wdpost.go b/cmd/lotus-sim/info_wdpost.go new file mode 100644 index 000000000..d52cd5a8c --- /dev/null +++ b/cmd/lotus-sim/info_wdpost.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" +) + +var infoWindowPostBandwidthSimCommand = &cli.Command{ + Name: "post-bandwidth", + Description: "List average chain bandwidth used by window posts for each day of the simulation.", + 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 + } + + var postGas, totalGas int64 + printStats := func() { + fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) + } + idx := 0 + err = sim.Walk(cctx.Context, 0, func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + totalGas += m.GasUsed + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == miner.Methods.SubmitWindowedPoSt { + postGas += m.GasUsed + } + } + idx++ + idx %= builtin.EpochsInDay + if idx == 0 { + printStats() + postGas = 0 + totalGas = 0 + } + return nil + }) + if idx > 0 { + printStats() + } + return err + }, +} From 73ae1924bc4236438ba18d7a794a40cf9542b939 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 14 Jun 2021 17:38:55 -0700 Subject: [PATCH 70/94] feat(lotus-sim): state size command --- cmd/lotus-sim/info.go | 1 + cmd/lotus-sim/info_state.go | 139 ++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 cmd/lotus-sim/info_state.go diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 4cd453440..8288bb99f 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -87,6 +87,7 @@ var infoSimCommand = &cli.Command{ infoCommitGasSimCommand, infoWindowPostBandwidthSimCommand, infoCapacityGrowthSimCommand, + infoStateGrowthSimCommand, }, Action: func(cctx *cli.Context) error { node, err := open(cctx) diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go new file mode 100644 index 000000000..c1710ba01 --- /dev/null +++ b/cmd/lotus-sim/info_state.go @@ -0,0 +1,139 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "math" + "runtime" + "sync" + "sync/atomic" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +var infoStateGrowthSimCommand = &cli.Command{ + Name: "state-size", + Description: "List daily state size over the course of the simulation starting at the end.", + 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 + } + + // NOTE: This code is entirely read-bound. + store := node.Chainstore.StateBlockstore() + stateSize := func(ctx context.Context, c cid.Cid) (uint64, error) { + seen := cid.NewSet() + sema := make(chan struct{}, 40) + var lock sync.Mutex + var recSize func(cid.Cid) (uint64, error) + recSize = func(c cid.Cid) (uint64, error) { + // Not a part of the chain state. + if err := ctx.Err(); err != nil { + return 0, err + } + + lock.Lock() + visit := seen.Visit(c) + lock.Unlock() + // Already seen? + if !visit { + return 0, nil + } + + var links []cid.Cid + var totalSize uint64 + if err := store.View(c, func(data []byte) error { + totalSize += uint64(len(data)) + cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + if c.Prefix().Codec != cid.DagCBOR { + return + } + + links = append(links, c) + }) + return nil + }); err != nil { + return 0, err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + cb := func(c cid.Cid) { + size, err := recSize(c) + if err != nil { + select { + case errCh <- err: + default: + } + return + } + atomic.AddUint64(&totalSize, size) + } + asyncCb := func(c cid.Cid) { + wg.Add(1) + go func() { + defer wg.Done() + defer func() { <-sema }() + cb(c) + }() + } + for _, link := range links { + select { + case sema <- struct{}{}: + asyncCb(link) + default: + cb(link) + } + + } + wg.Wait() + + select { + case err := <-errCh: + return 0, err + default: + } + + return totalSize, nil + } + return recSize(c) + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastHeight := abi.ChainEpoch(math.MaxInt64) + for ts.Height() > firstEpoch && cctx.Err() == nil { + if ts.Height()+builtin.EpochsInDay <= lastHeight { + lastHeight = ts.Height() + + parentStateSize, err := stateSize(cctx.Context, ts.ParentState()) + if err != nil { + return err + } + + fmt.Fprintf(cctx.App.Writer, "%d: %s\n", ts.Height(), types.SizeStr(types.NewInt(parentStateSize))) + } + + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + } + return cctx.Err() + }, +} From 885062f7123be7357a9bb2d4994618b9794bd7d9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 18:16:46 -0700 Subject: [PATCH 71/94] fix(lotus-sim): fix info state imports --- cmd/lotus-sim/info_state.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go index c1710ba01..4dbc65848 100644 --- a/cmd/lotus-sim/info_state.go +++ b/cmd/lotus-sim/info_state.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "math" - "runtime" "sync" "sync/atomic" From af33d69357322c5af9e29bbfb89c0ddf78cb5a69 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 18:22:24 -0700 Subject: [PATCH 72/94] fix(lotus-sim): don't close node on list --- cmd/lotus-sim/list.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go index 69809b188..8ded8a133 100644 --- a/cmd/lotus-sim/list.go +++ b/cmd/lotus-sim/list.go @@ -28,7 +28,6 @@ var listSimCommand = &cli.Command{ } head := sim.GetHead() fmt.Fprintf(tw, "%s\t%s\t%s\n", name, head.Height(), head.Key()) - sim.Close() } return tw.Flush() }, From e41f0842b0488016607eadb2b1f3b07751005531 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 23:54:01 -0700 Subject: [PATCH 73/94] fix(lotus-sim): load prove-commits (regression from refactor) --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index c2ffb8416..cf5f1afbf 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -57,6 +57,9 @@ func (stage *ProveCommitStage) EnqueueProveCommit( // block or runs out. func (stage *ProveCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { if !stage.initialized { + if err := stage.load(ctx, bb); err != nil { + return err + } } // Roll the commitQueue forward. stage.commitQueue.advanceEpoch(bb.Height()) From ec5fab09a1043f9f6fbc0ade031d27732e0030a4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:13:15 -0700 Subject: [PATCH 74/94] feat(lotus-sim): log failing proofs --- cmd/lotus-sim/simulation/mock/mock.go | 31 ++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/simulation/mock/mock.go b/cmd/lotus-sim/simulation/mock/mock.go index e6651aca0..38648f758 100644 --- a/cmd/lotus-sim/simulation/mock/mock.go +++ b/cmd/lotus-sim/simulation/mock/mock.go @@ -9,6 +9,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" @@ -26,6 +27,8 @@ const ( mockPoStProofPrefix = "valid post proof:" ) +var log = logging.Logger("simulation-mock") + // mockVerifier is a simple mock for verifying "fake" proofs. type mockVerifier struct{} @@ -40,7 +43,11 @@ func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) { if err != nil { return false, err } - return bytes.Equal(proof.Proof, mockProof), nil + if bytes.Equal(proof.Proof, mockProof) { + return true, nil + } + log.Debugw("invalid seal proof", "expected", mockProof, "actual", proof.Proof, "miner", addr) + return false, nil } func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { @@ -52,7 +59,16 @@ func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyPro if err != nil { return false, err } - return bytes.Equal(aggregate.Proof, mockProof), nil + if bytes.Equal(aggregate.Proof, mockProof) { + return true, nil + } + log.Debugw("invalid aggregate seal proof", + "expected", mockProof, + "actual", aggregate.Proof, + "count", len(aggregate.Infos), + "miner", addr, + ) + return false, nil } func (mockVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { panic("should not be called") @@ -70,7 +86,16 @@ func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoSt if err != nil { return false, err } - return bytes.Equal(proof.ProofBytes, mockProof), nil + if bytes.Equal(proof.ProofBytes, mockProof) { + return true, nil + } + + log.Debugw("invalid window post proof", + "expected", mockProof, + "actual", info.Proofs[0], + "miner", addr, + ) + return false, nil } func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) { From 28e6fa592385be07762212b90cf5d34528841a17 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:13:34 -0700 Subject: [PATCH 75/94] fix(lotus-sim): remove duplicate error handling --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index cf5f1afbf..2693fa9ed 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -218,11 +218,6 @@ func (stage *ProveCommitStage) packProveCommitsMiner( // Then try again. continue } - log.Errorw("failed to prove commit missing sector(s)", - "error", err, - "sectors", batch, - ) - res.failed += len(batch) } else { log.Errorw("failed to prove commit sector(s)", "error", err, From ce29a0ac178fbcf98fd95fb92a0e2e60527f018e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 23:58:17 -0700 Subject: [PATCH 76/94] fix(lotus-sim): initialize commit queue --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index 2693fa9ed..28e3fbfd2 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -348,6 +348,8 @@ func (stage *ProveCommitStage) filterProveCommits( } func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + stage.commitQueue.advanceEpoch(bb.Height()) + powerState, err := loadPower(bb.ActorStore(), bb.ParentStateTree()) if err != nil { return err From f0d0b40bd384dc0bba98a701ac0815f94452be76 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:19:35 -0700 Subject: [PATCH 77/94] fix(lotus-sim): debug log mock --- cmd/lotus-sim/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index c785f4045..e6cd5d993 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -27,6 +27,7 @@ var root []*cli.Command = []*cli.Command{ func main() { if _, set := os.LookupEnv("GOLOG_LOG_LEVEL"); !set { _ = logging.SetLogLevel("simulation", "DEBUG") + _ = logging.SetLogLevel("simulation-mock", "DEBUG") } app := &cli.App{ Name: "lotus-sim", From a26cd5a809a439f7c58eab512d19e8fe8cfec461 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:30:42 -0700 Subject: [PATCH 78/94] fix(lotus-sim): mark provecommit stage initialized --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index 28e3fbfd2..6cbca7de9 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -348,14 +348,14 @@ func (stage *ProveCommitStage) filterProveCommits( } func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) error { - stage.commitQueue.advanceEpoch(bb.Height()) - + stage.initialized = false // in case something failes while we're doing this. + stage.commitQueue = commitQueue{offset: bb.Height()} powerState, err := loadPower(bb.ActorStore(), bb.ParentStateTree()) if err != nil { return err } - return powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + err = powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { // TODO: If we want to finish pre-commits for "new" miners, we'll need to change // this. if claim.RawBytePower.IsZero() { @@ -363,4 +363,10 @@ func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockB } return stage.loadMiner(ctx, bb, minerAddr) }) + if err != nil { + return err + } + + stage.initialized = true + return nil } From bc2698a988eefdcb3cecd1553d737aef5c7234ad Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 12:20:17 -0700 Subject: [PATCH 79/94] fix(lotus-sim): simulate using realistic gas numbers Previously, we assumed the network was "optimal". Now, we're using real numbers. --- .../simulation/blockbuilder/blockbuilder.go | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index 4406f8a4f..e4dc79b24 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -2,6 +2,7 @@ package blockbuilder import ( "context" + "math" "go.uber.org/zap" "golang.org/x/xerrors" @@ -23,9 +24,12 @@ import ( ) const ( + // 0.25 is the default, but the number below is from the network. + gasOverestimation = 1.0 / 0.808 // The number of expected blocks in a tipset. We use this to determine how much gas a tipset // has. - expectedBlocks = 5 + // 5 per tipset, but we effectively get 4 blocks worth of messages. + expectedBlocks = 4 // 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. @@ -143,7 +147,7 @@ func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, } msg.GasPremium = abi.NewTokenAmount(0) msg.GasFeeCap = abi.NewTokenAmount(0) - msg.GasLimit = build.BlockGasLimit + msg.GasLimit = build.BlockGasTarget // We manually snapshot so we can revert nonce changes, etc. on failure. st.Snapshot(bb.ctx) @@ -162,26 +166,20 @@ func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, // Sometimes there are bugs. Let's catch them. if ret.GasUsed == 0 { _ = st.Revert() - return nil, xerrors.Errorf("used no gas", - "msg", msg, - "ret", ret, - ) + return nil, xerrors.Errorf("used no gas %v -> %v", msg, ret) } - // TODO: consider applying overestimation? We're likely going to "over pack" here by - // ~25% because we're too accurate. + // Update the gas limit taking overestimation into account. + msg.GasLimit = int64(math.Ceil(float64(ret.GasUsed) * gasOverestimation)) // Did we go over? Yes, revert. - newTotal := bb.gasTotal + ret.GasUsed + newTotal := bb.gasTotal + msg.GasLimit if newTotal > targetGas { _ = st.Revert() - return nil, &ErrOutOfGas{Available: targetGas - bb.gasTotal, Required: ret.GasUsed} + return nil, &ErrOutOfGas{Available: targetGas - bb.gasTotal, Required: msg.GasLimit} } bb.gasTotal = newTotal - // Update the gas limit. - msg.GasLimit = ret.GasUsed - bb.messages = append(bb.messages, msg) return &ret.MessageReceipt, nil } From 2aedd82c72278ebff6cbc0ff3a32284e26bdd4c5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 17:02:18 -0700 Subject: [PATCH 80/94] fix(lotus-sim): correct window post batch sizes --- cmd/lotus-sim/simulation/stages/windowpost_stage.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/windowpost_stage.go b/cmd/lotus-sim/simulation/stages/windowpost_stage.go index e6583012d..e5bbf8145 100644 --- a/cmd/lotus-sim/simulation/stages/windowpost_stage.go +++ b/cmd/lotus-sim/simulation/stages/windowpost_stage.go @@ -16,6 +16,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" @@ -122,6 +123,11 @@ func (stage *WindowPoStStage) queueMiner( return err } + poStBatchSize, err := policy.GetMaxPoStPartitions(bb.NetworkVersion(), minerInfo.WindowPoStProofType) + if err != nil { + return err + } + var ( partitions []miner.PoStPartition partitionGroups [][]miner.PoStPartition @@ -131,9 +137,8 @@ func (stage *WindowPoStStage) queueMiner( 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 { + if len(partitions) > 0 && idx%uint64(poStBatchSize) == 0 { partitionGroups = append(partitionGroups, partitions) partitions = nil From eb2b706156ff744d24b92ac0fae8ff0b5771b9dd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 11:17:35 -0700 Subject: [PATCH 81/94] chore: fix lint errors in simulation --- chain/vm/vm.go | 2 +- cmd/lotus-sim/copy.go | 8 +++- cmd/lotus-sim/create.go | 8 +++- cmd/lotus-sim/delete.go | 8 +++- cmd/lotus-sim/info.go | 8 +++- cmd/lotus-sim/info_capacity.go | 8 +++- cmd/lotus-sim/info_commit.go | 16 +++++--- cmd/lotus-sim/info_state.go | 11 ++++-- cmd/lotus-sim/info_wdpost.go | 8 +++- cmd/lotus-sim/list.go | 8 +++- cmd/lotus-sim/rename.go | 9 ++++- cmd/lotus-sim/run.go | 8 +++- .../simulation/blockbuilder/blockbuilder.go | 5 ++- cmd/lotus-sim/simulation/node.go | 10 +++-- cmd/lotus-sim/simulation/simulation.go | 3 -- .../simulation/stages/commit_queue.go | 2 +- .../simulation/stages/commit_queue_test.go | 12 +++--- .../simulation/stages/precommit_stage.go | 39 ++++++++++--------- .../simulation/stages/windowpost_stage.go | 2 +- cmd/lotus-sim/upgrade.go | 17 ++++++-- 20 files changed, 124 insertions(+), 68 deletions(-) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 9f9398630..34aaa028c 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -672,7 +672,7 @@ func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { } // Get the buffered blockstore associated with the VM. This includes any temporary blocks produced -// during thsi VM's execution. +// during this VM's execution. func (vm *VM) ActorStore(ctx context.Context) adt.Store { return adt.WrapStore(ctx, vm.cst) } diff --git a/cmd/lotus-sim/copy.go b/cmd/lotus-sim/copy.go index eeb8eb1aa..5faba69f2 100644 --- a/cmd/lotus-sim/copy.go +++ b/cmd/lotus-sim/copy.go @@ -9,12 +9,16 @@ import ( var copySimCommand = &cli.Command{ Name: "copy", ArgsUsage: "", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() if cctx.NArg() != 1 { return fmt.Errorf("expected 1 argument") } diff --git a/cmd/lotus-sim/create.go b/cmd/lotus-sim/create.go index cfd93c789..4867a5da5 100644 --- a/cmd/lotus-sim/create.go +++ b/cmd/lotus-sim/create.go @@ -12,12 +12,16 @@ import ( var createSimCommand = &cli.Command{ Name: "create", ArgsUsage: "[tipset]", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() var ts *types.TipSet switch cctx.NArg() { diff --git a/cmd/lotus-sim/delete.go b/cmd/lotus-sim/delete.go index 472a35a86..c19b3d27d 100644 --- a/cmd/lotus-sim/delete.go +++ b/cmd/lotus-sim/delete.go @@ -6,12 +6,16 @@ import ( var deleteSimCommand = &cli.Command{ Name: "delete", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() return node.DeleteSim(cctx.Context, cctx.String("simulation")) }, diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 8288bb99f..67e53b34c 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -89,12 +89,16 @@ var infoSimCommand = &cli.Command{ infoCapacityGrowthSimCommand, infoStateGrowthSimCommand, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go index 14ee36f08..8ba603c20 100644 --- a/cmd/lotus-sim/info_capacity.go +++ b/cmd/lotus-sim/info_capacity.go @@ -14,12 +14,16 @@ import ( var infoCapacityGrowthSimCommand = &cli.Command{ Name: "capacity-growth", Description: "List daily capacity growth over the course of the simulation starting at the end.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { diff --git a/cmd/lotus-sim/info_commit.go b/cmd/lotus-sim/info_commit.go index f6b08ea05..738fcde95 100644 --- a/cmd/lotus-sim/info_commit.go +++ b/cmd/lotus-sim/info_commit.go @@ -4,13 +4,13 @@ import ( "bytes" "fmt" "os" - - "github.com/ipfs/go-cid" - "github.com/streadway/quantile" - "github.com/urfave/cli/v2" "syscall" + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/ipfs/go-cid" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/stmgr" @@ -28,7 +28,7 @@ var infoCommitGasSimCommand = &cli.Command{ Value: 0, }, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { log := func(f string, i ...interface{}) { fmt.Fprintf(os.Stderr, f, i...) } @@ -36,7 +36,11 @@ var infoCommitGasSimCommand = &cli.Command{ if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() go profileOnSignal(cctx, syscall.SIGUSR2) diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go index 4dbc65848..19a31e19f 100644 --- a/cmd/lotus-sim/info_state.go +++ b/cmd/lotus-sim/info_state.go @@ -21,12 +21,16 @@ import ( var infoStateGrowthSimCommand = &cli.Command{ Name: "state-size", Description: "List daily state size over the course of the simulation starting at the end.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { @@ -58,14 +62,13 @@ var infoStateGrowthSimCommand = &cli.Command{ var totalSize uint64 if err := store.View(c, func(data []byte) error { totalSize += uint64(len(data)) - cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { if c.Prefix().Codec != cid.DagCBOR { return } links = append(links, c) }) - return nil }); err != nil { return 0, err } diff --git a/cmd/lotus-sim/info_wdpost.go b/cmd/lotus-sim/info_wdpost.go index d52cd5a8c..719a133b1 100644 --- a/cmd/lotus-sim/info_wdpost.go +++ b/cmd/lotus-sim/info_wdpost.go @@ -18,12 +18,16 @@ import ( var infoWindowPostBandwidthSimCommand = &cli.Command{ Name: "post-bandwidth", Description: "List average chain bandwidth used by window posts for each day of the simulation.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go index 8ded8a133..37e767b9a 100644 --- a/cmd/lotus-sim/list.go +++ b/cmd/lotus-sim/list.go @@ -9,12 +9,16 @@ import ( var listSimCommand = &cli.Command{ Name: "list", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() list, err := node.ListSims(cctx.Context) if err != nil { diff --git a/cmd/lotus-sim/rename.go b/cmd/lotus-sim/rename.go index 833a57e96..c336717c7 100644 --- a/cmd/lotus-sim/rename.go +++ b/cmd/lotus-sim/rename.go @@ -9,12 +9,17 @@ import ( var renameSimCommand = &cli.Command{ Name: "rename", ArgsUsage: "", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + if cctx.NArg() != 1 { return fmt.Errorf("expected 1 argument") } diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 00a3bddd9..a985fdf9e 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -22,12 +22,16 @@ Signals: Usage: "Advance the given number of epochs then stop.", }, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() go profileOnSignal(cctx, syscall.SIGUSR2) diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index e4dc79b24..2ffc0bf14 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -150,7 +150,10 @@ func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, msg.GasLimit = build.BlockGasTarget // We manually snapshot so we can revert nonce changes, etc. on failure. - st.Snapshot(bb.ctx) + err = st.Snapshot(bb.ctx) + if err != nil { + return nil, xerrors.Errorf("failed to take a snapshot while estimating message gas: %w", err) + } defer st.ClearSnapshot() ret, err := bb.vm.ApplyMessage(bb.ctx, msg) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 0be2de182..f97808eef 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -39,19 +39,19 @@ func OpenNode(ctx context.Context, path string) (*Node, error) { node.Repo, err = r.Lock(repo.FullNode) if err != nil { - node.Close() + _ = node.Close() return nil, err } node.Blockstore, err = node.Repo.Blockstore(ctx, repo.UniversalBlockstore) if err != nil { - node.Close() + _ = node.Close() return nil, err } node.MetadataDS, err = node.Repo.Datastore(ctx, "/metadata") if err != nil { - node.Close() + _ = node.Close() return nil, err } @@ -157,7 +157,9 @@ func (nd *Node) ListSims(ctx context.Context) ([]string, error) { if err != nil { return nil, xerrors.Errorf("failed to list simulations: %w", err) } - defer items.Close() + + defer func() { _ = items.Close() }() + var names []string for { select { diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 0b8ab1e56..18ccf1c0c 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -16,7 +16,6 @@ import ( blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "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" @@ -25,8 +24,6 @@ import ( var log = logging.Logger("simulation") -const onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks - // config is the simulation's config, persisted to the local metadata store and loaded on start. // // See Simulation.loadConfig and Simulation.saveConfig. diff --git a/cmd/lotus-sim/simulation/stages/commit_queue.go b/cmd/lotus-sim/simulation/stages/commit_queue.go index 515e080a0..851dd78f1 100644 --- a/cmd/lotus-sim/simulation/stages/commit_queue.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue.go @@ -16,7 +16,7 @@ type pendingCommitTracker map[address.Address]minerPendingCommits // minerPendingCommits tracks a miner's pending commits during a single epoch (grouped by seal proof type). type minerPendingCommits map[abi.RegisteredSealProof][]abi.SectorNumber -// finish markes count sectors of the given proof type as "prove-committed". +// finish marks count sectors of the given proof type as "prove-committed". func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { snos := m[proof] if len(snos) < count { diff --git a/cmd/lotus-sim/simulation/stages/commit_queue_test.go b/cmd/lotus-sim/simulation/stages/commit_queue_test.go index 180624493..8ab05250e 100644 --- a/cmd/lotus-sim/simulation/stages/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue_test.go @@ -68,7 +68,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{1}, sectors[proofType]) // 1 : non-empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) addr, sectors, ok = q.nextMiner() require.True(t, ok) @@ -79,13 +79,13 @@ func TestCommitQueue(t *testing.T) { require.Equal(t, sectors.count(), 0) // 2 : empty + empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, _, ok = q.nextMiner() require.False(t, ok) // 3 : empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) @@ -93,7 +93,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{4}, sectors[proofType]) // 4 : non-empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) @@ -101,7 +101,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) // 5 : empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) @@ -111,7 +111,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{5}, sectors[proofType]) // 6 - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) diff --git a/cmd/lotus-sim/simulation/stages/precommit_stage.go b/cmd/lotus-sim/simulation/stages/precommit_stage.go index 641292e0e..3dcfee28f 100644 --- a/cmd/lotus-sim/simulation/stages/precommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/precommit_stage.go @@ -26,8 +26,9 @@ import ( ) const ( - minPreCommitBatchSize = 1 - maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize + onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks ) type PreCommitStage struct { @@ -89,7 +90,7 @@ func (stage *PreCommitStage) PackMessages(ctx context.Context, bb *blockbuilder. ) // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. - // This won't yeild the most accurate distribution... but it'll give us a good + // This won't yield the most accurate distribution... but it'll give us a good // enough distribution. switch { case (i%3) <= 0 && top1Miners < stage.top1.len(): @@ -237,7 +238,7 @@ func (stage *PreCommitStage) packMiner( } } for _, info := range infos { - enc, err := actors.SerializeParams(&info) + enc, err := actors.SerializeParams(&info) //nolint if err != nil { return 0, false, err } @@ -261,7 +262,7 @@ func (stage *PreCommitStage) packMiner( return added, false, nil } -func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { +func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { bb.L().Infow("loading miner power for pre-commits") start := time.Now() defer func() { @@ -270,12 +271,12 @@ func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilde } bb.L().Infow("loaded miner power for pre-commits", "duration", time.Since(start), - "top1", ps.top1.len(), - "top10", ps.top10.len(), - "rest", ps.rest.len(), + "top1", stage.top1.len(), + "top10", stage.top10.len(), + "rest", stage.rest.len(), ) }() - lookbackEpoch := bb.Height() - (14 * builtin.EpochsInDay) + lookbackEpoch := bb.Height() - onboardingProjectionLookback lookbackPowerTable, err := loadClaims(ctx, bb, lookbackEpoch) if err != nil { return xerrors.Errorf("failed to load claims from lookback epoch %d: %w", lookbackEpoch, err) @@ -334,26 +335,26 @@ func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilde }) // reset, just in case. - ps.top1 = actorIter{} - ps.top10 = actorIter{} - ps.rest = actorIter{} + stage.top1 = actorIter{} + stage.top10 = actorIter{} + stage.rest = actorIter{} for i, oi := range sealList { var dist *actorIter if i < len(sealList)/100 { - dist = &ps.top1 + dist = &stage.top1 } else if i < len(sealList)/10 { - dist = &ps.top10 + dist = &stage.top10 } else { - dist = &ps.rest + dist = &stage.rest } dist.add(oi.addr) } - ps.top1.shuffle() - ps.top10.shuffle() - ps.rest.shuffle() + stage.top1.shuffle() + stage.top10.shuffle() + stage.rest.shuffle() - ps.initialized = true + stage.initialized = true return nil } diff --git a/cmd/lotus-sim/simulation/stages/windowpost_stage.go b/cmd/lotus-sim/simulation/stages/windowpost_stage.go index e5bbf8145..68f8ea179 100644 --- a/cmd/lotus-sim/simulation/stages/windowpost_stage.go +++ b/cmd/lotus-sim/simulation/stages/windowpost_stage.go @@ -288,7 +288,7 @@ func (stage *WindowPoStStage) tick(ctx context.Context, bb *blockbuilder.BlockBu store := bb.ActorStore() // Perform a bit of catch up. This lets us do things like skip blocks at upgrades then catch - // up to make the simualtion easier. + // up to make the simulation easier. for ; stage.nextWpostEpoch <= targetHeight; stage.nextWpostEpoch++ { if stage.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight { bb.L().Warnw("skipping old window post", "deadline-open", stage.nextWpostEpoch) diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 3a30e869b..0cda2c8ee 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -17,6 +17,7 @@ var upgradeCommand = &cli.Command{ Description: "Modifies network upgrade heights.", Subcommands: []*cli.Command{ upgradeSetCommand, + upgradeList, }, } @@ -26,12 +27,16 @@ var upgradeList = &cli.Command{ Subcommands: []*cli.Command{ upgradeSetCommand, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { @@ -61,7 +66,7 @@ var upgradeSetCommand = &cli.Command{ Name: "set", ArgsUsage: " [+]", Description: "Set a network upgrade height. Prefix with '+' to set it relative to the last epoch.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { args := cctx.Args() if args.Len() != 2 { return fmt.Errorf("expected 2 arguments") @@ -86,7 +91,11 @@ var upgradeSetCommand = &cli.Command{ if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { From b30b5dd629ccc93b452a399c23f70ef39ae805f4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 13:52:03 -0700 Subject: [PATCH 82/94] fix: move actors changes to template files --- chain/actors/builtin/miner/actor.go.template | 2 ++ chain/actors/builtin/multisig/actor.go.template | 1 + chain/actors/builtin/multisig/multisig.go | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 8c0b10cb0..8d46f99fd 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -22,6 +22,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" {{range .versions}} builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" {{end}} @@ -180,6 +181,7 @@ type DeclareFaultsRecoveredParams = miner0.DeclareFaultsRecoveredParams type SubmitWindowedPoStParams = miner0.SubmitWindowedPoStParams type ProveCommitSectorParams = miner0.ProveCommitSectorParams type DisputeWindowedPoStParams = miner3.DisputeWindowedPoStParams +type ProveCommitAggregateParams = miner5.ProveCommitAggregateParams func PreferredSealProofTypeFromWindowPoStType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredSealProof, error) { // We added support for the new proofs in network version 7, and removed support for the old diff --git a/chain/actors/builtin/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template index 77bc13f67..b899815a6 100644 --- a/chain/actors/builtin/multisig/actor.go.template +++ b/chain/actors/builtin/multisig/actor.go.template @@ -115,6 +115,7 @@ type MessageBuilder interface { type ProposalHashData = msig{{.latestVersion}}.ProposalHashData type ProposeReturn = msig{{.latestVersion}}.ProposeReturn type ProposeParams = msig{{.latestVersion}}.ProposeParams +type ApproveReturn = msig{{.latestVersion}}.ApproveReturn func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { params := msig{{.latestVersion}}.TxnIDParams{ID: msig{{.latestVersion}}.TxnID(id)} diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index 82f1963be..c950ced90 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -185,8 +185,8 @@ type MessageBuilder interface { // this type is the same between v0 and v2 type ProposalHashData = msig5.ProposalHashData type ProposeReturn = msig5.ProposeReturn -type ApproveReturn = msig5.ApproveReturn type ProposeParams = msig5.ProposeParams +type ApproveReturn = msig5.ApproveReturn func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { params := msig5.TxnIDParams{ID: msig5.TxnID(id)} From eb0a15faf0f122fccf303f47fe207a8b919a60ce Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 14:20:48 -0700 Subject: [PATCH 83/94] fix(genesis): set initial balances to 0 --- chain/gen/genesis/f00_system.go | 6 ++++-- chain/gen/genesis/f01_init.go | 6 ++++-- chain/gen/genesis/f03_cron.go | 6 ++++-- chain/gen/genesis/f04_power.go | 6 ++++-- chain/gen/genesis/f05_market.go | 6 ++++-- chain/gen/genesis/f06_vreg.go | 6 ++++-- chain/gen/genesis/genesis.go | 7 +++---- chain/state/statetree.go | 3 ++- 8 files changed, 29 insertions(+), 17 deletions(-) diff --git a/chain/gen/genesis/f00_system.go b/chain/gen/genesis/f00_system.go index d1dd203b6..4fde27107 100644 --- a/chain/gen/genesis/f00_system.go +++ b/chain/gen/genesis/f00_system.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/system" @@ -32,8 +33,9 @@ func SetupSystemActor(ctx context.Context, bs bstore.Blockstore, av actors.Versi } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f01_init.go b/chain/gen/genesis/f01_init.go index 88d409221..61ec91703 100644 --- a/chain/gen/genesis/f01_init.go +++ b/chain/gen/genesis/f01_init.go @@ -11,6 +11,7 @@ import ( "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/specs-actors/actors/util/adt" @@ -181,8 +182,9 @@ func SetupInitActor(ctx context.Context, bs bstore.Blockstore, netname string, i } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return counter, act, keyToId, nil diff --git a/chain/gen/genesis/f03_cron.go b/chain/gen/genesis/f03_cron.go index c6fd2422a..c9dd0d341 100644 --- a/chain/gen/genesis/f03_cron.go +++ b/chain/gen/genesis/f03_cron.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/cron" @@ -31,8 +32,9 @@ func SetupCronActor(ctx context.Context, bs bstore.Blockstore, av actors.Version } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f04_power.go b/chain/gen/genesis/f04_power.go index 6fe4d75c0..b5e08cebe 100644 --- a/chain/gen/genesis/f04_power.go +++ b/chain/gen/genesis/f04_power.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors" @@ -33,8 +34,9 @@ func SetupStoragePowerActor(ctx context.Context, bs bstore.Blockstore, av actors } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f05_market.go b/chain/gen/genesis/f05_market.go index 5c39ef38f..ac32294c9 100644 --- a/chain/gen/genesis/f05_market.go +++ b/chain/gen/genesis/f05_market.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/market" @@ -31,8 +32,9 @@ func SetupStorageMarketActor(ctx context.Context, bs bstore.Blockstore, av actor } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f06_vreg.go b/chain/gen/genesis/f06_vreg.go index d8f5ee2a0..e61c951f5 100644 --- a/chain/gen/genesis/f06_vreg.go +++ b/chain/gen/genesis/f06_vreg.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/actors" @@ -46,8 +47,9 @@ func SetupVerifiedRegistryActor(ctx context.Context, bs bstore.Blockstore, av ac } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index 94badbbfb..6dec3fea6 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -313,11 +313,10 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge totalFilAllocated := big.Zero() - // flush as ForEach works on the HAMT - if _, err := state.Flush(ctx); err != nil { - return nil, nil, err - } err = state.ForEach(func(addr address.Address, act *types.Actor) error { + if act.Balance.Nil() { + panic(fmt.Sprintf("actor %s (%s) has nil balance", addr, builtin.ActorNameByCode(act.Code))) + } totalFilAllocated = big.Add(totalFilAllocated, act.Balance) return nil }) diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 72269e4f2..dbf150ecd 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -515,7 +515,8 @@ func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error if op.Delete { continue } - if err := f(addr, &op.Act); err != nil { + act := op.Act // copy + if err := f(addr, &act); err != nil { return err } } From 2fdf49e7da7b0cb7ba8f6437c5d1fcb4381460e1 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 17 Jun 2021 20:32:15 +0200 Subject: [PATCH 84/94] Add histogram and quantiles for message sizes Resolves https://github.com/filecoin-project/lotus/issues/6513 Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 1 + cmd/lotus-sim/info_message.go | 91 +++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 cmd/lotus-sim/info_message.go diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 67e53b34c..759f2d815 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -85,6 +85,7 @@ var infoSimCommand = &cli.Command{ Description: "Output information about the simulation.", Subcommands: []*cli.Command{ infoCommitGasSimCommand, + infoMessageSizeSimCommand, infoWindowPostBandwidthSimCommand, infoCapacityGrowthSimCommand, infoStateGrowthSimCommand, diff --git a/cmd/lotus-sim/info_message.go b/cmd/lotus-sim/info_message.go new file mode 100644 index 000000000..b30fadd65 --- /dev/null +++ b/cmd/lotus-sim/info_message.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + "syscall" + + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" + "github.com/ipfs/go-cid" + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" +) + +var infoMessageSizeSimCommand = &cli.Command{ + Name: "message-size", + Description: "Output information about message size distribution", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + go profileOnSignal(cctx, syscall.SIGUSR2) + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + qpoints := []struct{ q, tol float64 }{ + {0.30, 0.01}, + {0.40, 0.01}, + {0.60, 0.01}, + {0.70, 0.01}, + {0.80, 0.01}, + {0.85, 0.01}, + {0.90, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + {0.999, 0.0001}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1 << 8, 1 << 10, 1 << 11, 1 << 12, 1 << 13, 1 << 14, 1 << 15, 1 << 16, + }) + if err != nil { + return err + } + + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + msgSize := float64(m.ChainLength()) + qua.Add(msgSize) + hist.Observe(msgSize) + } + + return nil + }) + if err != nil { + return err + } + fmt.Println("Quantiles of message sizes:") + for _, p := range qpoints { + fmt.Printf("%.1f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of message sizes:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\t%.1f%%\n", b, hist.Get(i), 100*hist.GetRatio(i)) + } + + return nil + }, +} From 69a8a6bc0edb6342fae3d031f0c48c78d9de4465 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 14:43:09 -0700 Subject: [PATCH 85/94] fix(lotus-sim): lint --- cmd/lotus-sim/info_message.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info_message.go b/cmd/lotus-sim/info_message.go index b30fadd65..33c45e728 100644 --- a/cmd/lotus-sim/info_message.go +++ b/cmd/lotus-sim/info_message.go @@ -22,12 +22,16 @@ var infoMessageSizeSimCommand = &cli.Command{ Value: 0, }, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() go profileOnSignal(cctx, syscall.SIGUSR2) From d6abcff63c444a00bcb5b5229481649054b6af57 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 09:05:48 -0700 Subject: [PATCH 86/94] fix(lotus-sim): apply code review from magik6k MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ɓukasz Magiera --- chain/vm/vm.go | 2 +- cmd/lotus-sim/simulation/stages/commit_queue.go | 2 +- cmd/lotus-sim/upgrade.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 34aaa028c..5a31187b7 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -439,7 +439,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, }, GasCosts: &gasOutputs, Duration: time.Since(start), - ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, + ActorErr: aerrors.Newf(exitcode.SysErrOutOfGas, "message gas limit does not cover on-chain gas costs"), }, nil } diff --git a/cmd/lotus-sim/simulation/stages/commit_queue.go b/cmd/lotus-sim/simulation/stages/commit_queue.go index 851dd78f1..d625dedb6 100644 --- a/cmd/lotus-sim/simulation/stages/commit_queue.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue.go @@ -10,7 +10,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/policy" ) -// pendingCommitTracker tracks pending commits per-miner for a single epohc. +// pendingCommitTracker tracks pending commits per-miner for a single epoch. type pendingCommitTracker map[address.Address]minerPendingCommits // minerPendingCommits tracks a miner's pending commits during a single epoch (grouped by seal proof type). diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 0cda2c8ee..dfc726d6b 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -72,7 +72,7 @@ var upgradeSetCommand = &cli.Command{ return fmt.Errorf("expected 2 arguments") } nvString := args.Get(0) - networkVersion, err := strconv.ParseInt(nvString, 10, 64) + networkVersion, err := strconv.ParseUint(nvString, 10, 32) if err != nil { return fmt.Errorf("failed to parse network version %q: %w", nvString, err) } From ffb63a93ff47073782794eb579b3d6ad77ad528c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 09:09:06 -0700 Subject: [PATCH 87/94] fix(lotus-sim): make 'fund' easier to understand --- cmd/lotus-sim/simulation/stages/funding_stage.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/funding_stage.go b/cmd/lotus-sim/simulation/stages/funding_stage.go index a0d9f4a22..f57f85293 100644 --- a/cmd/lotus-sim/simulation/stages/funding_stage.go +++ b/cmd/lotus-sim/simulation/stages/funding_stage.go @@ -81,13 +81,15 @@ func (fs *FundingStage) SendAndFund(bb *blockbuilder.BlockBuilder, msg *types.Me return res, err } -func (fs *FundingStage) fund(bb *blockbuilder.BlockBuilder, target address.Address, times int) error { +// fund funds the target actor with 'TargetFunds << shift' FIL. The "shift" parameter allows us to +// keep doubling the amount until the intended operation succeeds. +func (fs *FundingStage) fund(bb *blockbuilder.BlockBuilder, target address.Address, shift int) error { amt := TargetFunds - if times > 0 { - if times >= 8 { - times = 8 // cap + if shift > 0 { + if shift >= 8 { + shift = 8 // cap } - amt = big.Lsh(amt, uint(times)) + amt = big.Lsh(amt, uint(shift)) } _, err := bb.PushMessage(&types.Message{ From: fs.fundAccount, From b5f91487487167222db6862c73b5c2143d0d97f7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 09:12:00 -0700 Subject: [PATCH 88/94] build(lotus-sim): add a makefile target --- .gitignore | 1 + Makefile | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index eddee0590..467f315b8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /lotus-health /lotus-chainwatch /lotus-shed +/lotus-sim /lotus-pond /lotus-townhall /lotus-fountain diff --git a/Makefile b/Makefile index d20343f55..7522706fc 100644 --- a/Makefile +++ b/Makefile @@ -234,6 +234,12 @@ BINS+=tvx install-chainwatch: lotus-chainwatch install -C ./lotus-chainwatch /usr/local/bin/lotus-chainwatch +lotus-sim: $(BUILD_DEPS) + rm -f lotus-sim + go build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim +.PHONY: lotus-sim +BINS+=lotus-sim + # SYSTEMD install-daemon-service: install-daemon From 80eba1069ad1bd0ef49d4d5bc0a5b93af74d4d3b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:23:48 -0700 Subject: [PATCH 89/94] feat(lotus-sim): NewNode to construct a node without a repo --- cmd/lotus-sim/simulation/node.go | 59 ++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index f97808eef..eb4e62c8a 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -2,7 +2,6 @@ package simulation import ( "context" - "io" "strings" "go.uber.org/multierr" @@ -23,55 +22,63 @@ import ( // Node represents the local lotus node, or at least the part of it we care about. type Node struct { - Repo repo.LockedRepo + repo repo.LockedRepo Blockstore blockstore.Blockstore MetadataDS datastore.Batching Chainstore *store.ChainStore } // OpenNode opens the local lotus node for writing. This will fail if the node is online. -func OpenNode(ctx context.Context, path string) (*Node, error) { - var node Node +func OpenNode(ctx context.Context, path string) (node *Node, _err error) { r, err := repo.NewFS(path) if err != nil { return nil, err } - node.Repo, err = r.Lock(repo.FullNode) + lr, err := r.Lock(repo.FullNode) + if err != nil { + return nil, err + } + defer func() { + if _err != nil { + lr.Close() + } + }() + + bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore) if err != nil { - _ = node.Close() return nil, err } - node.Blockstore, err = node.Repo.Blockstore(ctx, repo.UniversalBlockstore) + ds, err := lr.Datastore(ctx, "/metadata") 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 = NewNode(bs, ds) + node.repo = lr - node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mock.Verifier), nil) - return &node, nil + return node, nil } -// Close cleanly close the node. Please call this on shutdown to make sure everything is flushed. +// NewNode will construct a new simulation node from a datastore (used to store simulation +// configuration) and a blockstore (chain + state). +func NewNode(bs blockstore.Blockstore, ds datastore.Batching) *Node { + return &Node{ + Chainstore: store.NewChainStore(bs, bs, ds, vm.Syscalls(mock.Verifier), nil), + MetadataDS: ds, + Blockstore: bs, + } +} + +// Close cleanly close the repo. Please call this on shutdown to make sure everything is flushed. +// +// This function is a no-op when the node is manually constructed with `NewNode`. 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.repo != nil { + return nd.repo.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 + return nil } // LoadSim loads From c532b1b819b3452062bc6552701ba58f87f12402 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:25:03 -0700 Subject: [PATCH 90/94] fix(lotus-sim): remove unused field and function --- cmd/lotus-sim/simulation/simulation.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 18ccf1c0c..c75be3261 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -16,7 +16,6 @@ import ( blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" @@ -80,7 +79,6 @@ type Simulation struct { start *types.TipSet // head - st *state.StateTree head *types.TipSet stages []stages.Stage @@ -113,22 +111,6 @@ func (sim *Simulation) saveConfig() error { return sim.MetadataDS.Put(sim.key("config"), buf) } -// stateTree returns the current state-tree for the current head, computing the tipset if necessary. -// The state-tree is cached until the head is changed. -func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) { - if sim.st == nil { - st, _, err := sim.StateManager.TipSetState(ctx, sim.head) - if err != nil { - return nil, err - } - sim.st, err = sim.StateManager.StateTree(st) - if err != nil { - return nil, err - } - } - return sim.st, nil -} - var simulationPrefix = datastore.NewKey("/simulation") // key returns the the key in the form /simulation//. For example, @@ -183,7 +165,6 @@ func (sim *Simulation) SetHead(head *types.TipSet) error { if err := sim.storeNamedTipSet("head", head); err != nil { return err } - sim.st = nil // we'll compute this on-demand. sim.head = head return nil } From b7c36bc02cf258c04458afddd367a7ad779028cb Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:32:19 -0700 Subject: [PATCH 91/94] fix(lotus-sim): make NewNode take a repo This is primarily for testing, so we can just pass an in-memory repo. --- cmd/lotus-sim/simulation/node.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index eb4e62c8a..acd63955d 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -29,12 +29,17 @@ type Node struct { } // OpenNode opens the local lotus node for writing. This will fail if the node is online. -func OpenNode(ctx context.Context, path string) (node *Node, _err error) { +func OpenNode(ctx context.Context, path string) (*Node, error) { r, err := repo.NewFS(path) if err != nil { return nil, err } + return NewNode(ctx, r) +} + +// NewNode constructs a new node from the given repo. +func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { lr, err := r.Lock(repo.FullNode) if err != nil { return nil, err @@ -54,26 +59,15 @@ func OpenNode(ctx context.Context, path string) (node *Node, _err error) { if err != nil { return nil, err } - - node = NewNode(bs, ds) - node.repo = lr - - return node, nil -} - -// NewNode will construct a new simulation node from a datastore (used to store simulation -// configuration) and a blockstore (chain + state). -func NewNode(bs blockstore.Blockstore, ds datastore.Batching) *Node { return &Node{ + repo: lr, Chainstore: store.NewChainStore(bs, bs, ds, vm.Syscalls(mock.Verifier), nil), MetadataDS: ds, Blockstore: bs, - } + }, err } // Close cleanly close the repo. Please call this on shutdown to make sure everything is flushed. -// -// This function is a no-op when the node is manually constructed with `NewNode`. func (nd *Node) Close() error { if nd.repo != nil { return nd.repo.Close() From 0b06de2bd3524bcdfff3a84d8dd628e666ad5bfc Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:41:06 -0700 Subject: [PATCH 92/94] fix(lotus-sim): unembed Node from Simulation I wanted to expose the node's _fields_, but this also exposed the methods. That got rather confusing. (probably could have used a new type, but eh) foo --- cmd/lotus-sim/info.go | 2 +- cmd/lotus-sim/info_capacity.go | 2 +- cmd/lotus-sim/info_state.go | 2 +- cmd/lotus-sim/simulation/block.go | 2 +- cmd/lotus-sim/simulation/messages.go | 6 +++--- cmd/lotus-sim/simulation/simulation.go | 20 ++++++++++---------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 759f2d815..864adb3bc 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -42,7 +42,7 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e if powerLookbackEpoch < start.Height() { powerLookbackEpoch = start.Height() } - lookbackTs, err := sim.Chainstore.GetTipsetByHeight(ctx, powerLookbackEpoch, head, false) + lookbackTs, err := sim.Node.Chainstore.GetTipsetByHeight(ctx, powerLookbackEpoch, head, false) if err != nil { return err } diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go index 8ba603c20..4372ee34a 100644 --- a/cmd/lotus-sim/info_capacity.go +++ b/cmd/lotus-sim/info_capacity.go @@ -39,7 +39,7 @@ var infoCapacityGrowthSimCommand = &cli.Command{ lastHeight := ts.Height() for ts.Height() > firstEpoch && cctx.Err() == nil { - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) if err != nil { return err } diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go index 19a31e19f..5c9541513 100644 --- a/cmd/lotus-sim/info_state.go +++ b/cmd/lotus-sim/info_state.go @@ -131,7 +131,7 @@ var infoStateGrowthSimCommand = &cli.Command{ fmt.Fprintf(cctx.App.Writer, "%d: %s\n", ts.Height(), types.SizeStr(types.NewInt(parentStateSize))) } - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) if err != nil { return err } diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 47d482f4e..93e6a3191 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -73,7 +73,7 @@ func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message Timestamp: uts, ElectionProof: &types.ElectionProof{WinCount: 1}, }} - err = sim.Chainstore.PersistBlockHeaders(blks...) + err = sim.Node.Chainstore.PersistBlockHeaders(blks...) if err != nil { return nil, xerrors.Errorf("failed to persist block headers: %w", err) } diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 08e4c12d2..5bed27436 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -25,19 +25,19 @@ func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { // storeMessages packs a set of messages into a types.MsgMeta and returns the resulting CID. The // resulting CID is valid for the BlocKHeader's Messages field. -func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { +func (sim *Simulation) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { // We store all messages as "bls" messages so they're executed in-order. This ensures // accurate gas accounting. It also ensures we don't, e.g., try to fund a miner after we // fail a pre-commit... var msgCids []cid.Cid for _, msg := range messages { - c, err := nd.Chainstore.PutMessage(msg) + c, err := sim.Node.Chainstore.PutMessage(msg) if err != nil { return cid.Undef, err } msgCids = append(msgCids, c) } - adtStore := nd.Chainstore.ActorStore(ctx) + adtStore := sim.Node.Chainstore.ActorStore(ctx) blsMsgArr, err := toArray(adtStore, msgCids) if err != nil { return cid.Undef, err diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index c75be3261..d91d30eda 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -71,7 +71,7 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { // Simulation specifies a lotus-sim simulation. type Simulation struct { - *Node + Node *Node StateManager *stmgr.StateManager name string @@ -87,7 +87,7 @@ type Simulation struct { // loadConfig loads a simulation's config from the datastore. This must be called on startup and may // be called to restore the config from-disk. func (sim *Simulation) loadConfig() error { - configBytes, err := sim.MetadataDS.Get(sim.key("config")) + configBytes, err := sim.Node.MetadataDS.Get(sim.key("config")) if err == nil { err = json.Unmarshal(configBytes, &sim.config) } @@ -108,7 +108,7 @@ func (sim *Simulation) saveConfig() error { if err != nil { return err } - return sim.MetadataDS.Put(sim.key("config"), buf) + return sim.Node.MetadataDS.Put(sim.key("config"), buf) } var simulationPrefix = datastore.NewKey("/simulation") @@ -121,7 +121,7 @@ func (sim *Simulation) key(subkey string) datastore.Key { // loadNamedTipSet the tipset with the given name (for this simulation) func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { - tskBytes, err := sim.MetadataDS.Get(sim.key(name)) + tskBytes, err := sim.Node.MetadataDS.Get(sim.key(name)) if err != nil { return nil, xerrors.Errorf("failed to load tipset %s/%s: %w", sim.name, name, err) } @@ -129,7 +129,7 @@ func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { if err != nil { return nil, xerrors.Errorf("failed to parse tipste %v (%s/%s): %w", tskBytes, sim.name, name, err) } - ts, err := sim.Chainstore.LoadTipSet(tsk) + ts, err := sim.Node.Chainstore.LoadTipSet(tsk) if err != nil { return nil, xerrors.Errorf("failed to load tipset %s (%s/%s): %w", tsk, sim.name, name, err) } @@ -138,7 +138,7 @@ func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { // storeNamedTipSet stores the tipset at name (relative to the simulation). func (sim *Simulation) storeNamedTipSet(name string, ts *types.TipSet) error { - if err := sim.MetadataDS.Put(sim.key(name), ts.Key().Bytes()); err != nil { + if err := sim.Node.MetadataDS.Put(sim.key(name), ts.Key().Bytes()); err != nil { return xerrors.Errorf("failed to store tipset (%s/%s): %w", sim.name, name, err) } return nil @@ -198,7 +198,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch if err != nil { return err } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Chainstore, newUpgradeSchedule) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, newUpgradeSchedule) if err != nil { return err } @@ -241,7 +241,7 @@ func (sim *Simulation) Walk( stCid cid.Cid, messages []*AppliedMessage) error, ) error { - store := sim.Chainstore.ActorStore(ctx) + store := sim.Node.Chainstore.ActorStore(ctx) minEpoch := sim.start.Height() if lookback != 0 { minEpoch = sim.head.Height() - abi.ChainEpoch(lookback) @@ -305,7 +305,7 @@ func (sim *Simulation) Walk( stCid = ts.MinTicketBlock().ParentStateRoot recCid = ts.MinTicketBlock().ParentMessageReceipts - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) if err != nil { return xerrors.Errorf("loading parent: %w", err) } @@ -339,7 +339,7 @@ func (sim *Simulation) Walk( break } - msgs, err := sim.Chainstore.MessagesForTipset(job.ts) + msgs, err := sim.Node.Chainstore.MessagesForTipset(job.ts) if err != nil { return err } From 87c306fd47f1baab662b6357bc66b616c2487a31 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 22 Jun 2021 14:35:30 -0700 Subject: [PATCH 93/94] feat(lotus-sim): use current power instead of lookback I'd _really_ like to use lookback, but don't have that when starting from a snapshot. --- .../simulation/stages/precommit_stage.go | 31 ++++++------------- cmd/lotus-sim/simulation/stages/util.go | 30 ------------------ 2 files changed, 9 insertions(+), 52 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/precommit_stage.go b/cmd/lotus-sim/simulation/stages/precommit_stage.go index 3dcfee28f..5b9fed09e 100644 --- a/cmd/lotus-sim/simulation/stages/precommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/precommit_stage.go @@ -16,7 +16,6 @@ import ( "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/builtin/power" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -26,9 +25,8 @@ import ( ) const ( - minPreCommitBatchSize = 1 - maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize - onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize ) type PreCommitStage struct { @@ -276,11 +274,6 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui "rest", stage.rest.len(), ) }() - lookbackEpoch := bb.Height() - onboardingProjectionLookback - lookbackPowerTable, err := loadClaims(ctx, bb, lookbackEpoch) - if err != nil { - return xerrors.Errorf("failed to load claims from lookback epoch %d: %w", lookbackEpoch, err) - } store := bb.ActorStore() st := bb.ParentStateTree() @@ -290,10 +283,10 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui } type onboardingInfo struct { - addr address.Address - onboardingRate uint64 + addr address.Address + sectorCount uint64 } - sealList := make([]onboardingInfo, 0, len(lookbackPowerTable)) + var sealList []onboardingInfo err = powerState.ForEachClaim(func(addr address.Address, claim power.Claim) error { if claim.RawBytePower.IsZero() { return nil @@ -308,16 +301,10 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui return err } - sectorsAdded := sectorsFromClaim(info.SectorSize, claim) - if lookbackClaim, ok := lookbackPowerTable[addr]; !ok { - sectorsAdded -= sectorsFromClaim(info.SectorSize, lookbackClaim) - } + sectorCount := sectorsFromClaim(info.SectorSize, claim) - // 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)}) + if sectorCount > 0 { + sealList = append(sealList, onboardingInfo{addr, uint64(sectorCount)}) } return nil }) @@ -331,7 +318,7 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui // 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 + return sealList[i].sectorCount < sealList[j].sectorCount }) // reset, just in case. diff --git a/cmd/lotus-sim/simulation/stages/util.go b/cmd/lotus-sim/simulation/stages/util.go index 4c23a83d6..97c1e57af 100644 --- a/cmd/lotus-sim/simulation/stages/util.go +++ b/cmd/lotus-sim/simulation/stages/util.go @@ -43,36 +43,6 @@ func sectorsFromClaim(sectorSize abi.SectorSize, c power.Claim) int64 { return sectorCount.Int64() } -// loadClaims will load all non-zero claims at the given epoch. -func loadClaims( - ctx context.Context, bb *blockbuilder.BlockBuilder, height abi.ChainEpoch, -) (map[address.Address]power.Claim, error) { - powerTable := make(map[address.Address]power.Claim) - - st, err := bb.StateTreeByHeight(height) - if err != nil { - return nil, err - } - - powerState, err := loadPower(bb.ActorStore(), st) - if err != nil { - return nil, err - } - - err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { - // skip miners without power - if claim.RawBytePower.IsZero() { - return nil - } - powerTable[miner] = claim - return nil - }) - if err != nil { - return nil, err - } - return powerTable, nil -} - func postChainCommitInfo(ctx context.Context, bb *blockbuilder.BlockBuilder, epoch abi.ChainEpoch) (abi.Randomness, error) { cs := bb.StateManager().ChainStore() ts := bb.ParentTipSet() From 63e2caae81c168a0cf0f239ed3927c154c2b1150 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 22 Jun 2021 15:06:44 -0700 Subject: [PATCH 94/94] lint(lotus-sim): handle error --- cmd/lotus-sim/simulation/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index acd63955d..5b8bf2bf9 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -46,7 +46,7 @@ func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { } defer func() { if _err != nil { - lr.Close() + _ = lr.Close() } }()