Compare commits
114
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8006a39a0f | ||
|
|
517c0a53b1 | ||
|
|
44aadb7962 | ||
|
|
4ca5b0ede9 | ||
|
|
24b958cffd | ||
|
|
2a6d0e5f6e | ||
|
|
48912e8f09 | ||
|
|
1ba0e4b997 | ||
|
|
1e29616832 | ||
|
|
739d61c698 | ||
|
|
5b31b79d58 | ||
|
|
fc719e710f | ||
|
|
28951e5919 | ||
|
|
173dad45ed | ||
|
|
36b5f485cd | ||
|
|
2fca475dd4 | ||
|
|
dfd3b10182 | ||
|
|
e3c796c848 | ||
|
|
16b9787db9 | ||
|
|
594c52b965 | ||
|
|
b6c1400d21 | ||
|
|
4d90d0475f | ||
|
|
9e4f1a4d23 | ||
|
|
7fea1795d6 | ||
|
|
a27e0a8b41 | ||
|
|
424bab336c | ||
|
|
86c3a69904 | ||
|
|
fc0df4afb8 | ||
|
|
5daaa3fe46 | ||
|
|
26b4866841 | ||
|
|
e414acb1f5 | ||
|
|
4e55147895 | ||
|
|
c38193af73 | ||
|
|
de64fda524 | ||
|
|
cc66654a6c | ||
|
|
9240776237 | ||
|
|
6e7dc9532a | ||
|
|
84535ca6fe | ||
|
|
eeac1aa17d | ||
|
|
db673ef28d | ||
|
|
2310740584 | ||
|
|
2fd8602c5a | ||
|
|
6cc506f5cf | ||
|
|
120502408c | ||
|
|
b997f4acec | ||
|
|
ce5e242d76 | ||
|
|
96fa081ca6 | ||
|
|
196cd0b739 | ||
|
|
042bbfa415 | ||
|
|
42aa8451f8 | ||
|
|
3c67d0f720 | ||
|
|
3d0e6d0363 | ||
|
|
fa36613a5e | ||
|
|
87ae60a2bc | ||
|
|
183e35f7b7 | ||
|
|
8be9e5345b | ||
|
|
047898f64d | ||
|
|
3d061ba0ae | ||
|
|
2636815311 | ||
|
|
1c10a6e238 | ||
|
|
d365f7b0a1 | ||
|
|
425661772d | ||
|
|
99c98b5de0 | ||
|
|
3bed440d2d | ||
|
|
10130a32d7 | ||
|
|
b76b5ef4b7 | ||
|
|
a12dbaac8e | ||
|
|
f5daf95261 | ||
|
|
7b921c6761 | ||
|
|
782e09a37d | ||
|
|
e25d834a17 | ||
|
|
84bba333c4 | ||
|
|
c0942ebc2c | ||
|
|
867a7b1c01 | ||
|
|
10ed18ba12 | ||
|
|
0ec2acbf20 | ||
|
|
228b306370 | ||
|
|
17915dce5a | ||
|
|
e1b69f84d6 | ||
|
|
b563a36a99 | ||
|
|
4b0ca30daf | ||
|
|
dcc72a43ee | ||
|
|
d0e9502bfe | ||
|
|
78d7ccd391 | ||
|
|
b269a1ba35 | ||
|
|
a4c94957e6 | ||
|
|
7af42796db | ||
|
|
4b5a665422 | ||
|
|
090b98ad33 | ||
|
|
14f9770859 | ||
|
|
fdd013c103 | ||
|
|
b58daf5340 | ||
|
|
479018e3b3 | ||
|
|
41203f7b8e | ||
|
|
5e58f64380 | ||
|
|
f0fec13ec9 | ||
|
|
326a0356a4 | ||
|
|
6c62e6d8e2 | ||
|
|
587c3fde58 | ||
|
|
cd75ea0fe4 | ||
|
|
8b2ef40f4e | ||
|
|
00e34436b3 | ||
|
|
0131781808 | ||
|
|
0d7d906abf | ||
|
|
825564a994 | ||
|
|
b412d8c179 | ||
|
|
70d2899ead | ||
|
|
8fcf59facc | ||
|
|
ae84f335cc | ||
|
|
dd6720c61b | ||
|
|
e358b3b358 | ||
|
|
7710a57fa3 | ||
|
|
11827034b9 | ||
|
|
93288fac72 |
@@ -355,7 +355,7 @@ fiximports:
|
||||
./scripts/fiximports
|
||||
|
||||
gen: actors-code-gen type-gen cfgdoc-gen docsgen api-gen circleci fiximports
|
||||
@echo ">>> IF YOU'VE MODIFIED THE CLI OR CONFIG, REMEMBER TO ALSO MAKE docsgen-cli"
|
||||
@echo ">>> IF YOU'VE MODIFIED THE CLI OR CONFIG, REMEMBER TO ALSO RUN 'make docsgen-cli'"
|
||||
.PHONY: gen
|
||||
|
||||
jen: gen
|
||||
|
||||
@@ -33,6 +33,9 @@ import (
|
||||
// * Generate openrpc blobs
|
||||
|
||||
type Gateway interface {
|
||||
MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error)
|
||||
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
|
||||
MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*MiningBaseInfo, error)
|
||||
StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (MinerSectors, error)
|
||||
GasEstimateGasPremium(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error)
|
||||
StateReplay(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error)
|
||||
|
||||
@@ -86,6 +86,7 @@ func init() {
|
||||
}
|
||||
|
||||
ExampleValues[reflect.TypeOf(addr)] = addr
|
||||
ExampleValues[reflect.TypeOf(&addr)] = &addr
|
||||
|
||||
pid, err := peer.Decode("12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf")
|
||||
if err != nil {
|
||||
|
||||
@@ -632,6 +632,8 @@ type GatewayStruct struct {
|
||||
}
|
||||
|
||||
type GatewayMethods struct {
|
||||
ChainGetBlock func(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) ``
|
||||
|
||||
ChainGetBlockMessages func(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) ``
|
||||
|
||||
ChainGetGenesis func(p0 context.Context) (*types.TipSet, error) ``
|
||||
@@ -734,8 +736,12 @@ type GatewayMethods struct {
|
||||
|
||||
GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) ``
|
||||
|
||||
MinerGetBaseInfo func(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) ``
|
||||
|
||||
MpoolGetNonce func(p0 context.Context, p1 address.Address) (uint64, error) ``
|
||||
|
||||
MpoolPending func(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) ``
|
||||
|
||||
MpoolPush func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) ``
|
||||
|
||||
MsigGetAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) ``
|
||||
@@ -4109,6 +4115,17 @@ func (s *FullNodeStub) Web3ClientVersion(p0 context.Context) (string, error) {
|
||||
return "", ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) {
|
||||
if s.Internal.ChainGetBlock == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.ChainGetBlock(p0, p1)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) {
|
||||
if s.Internal.ChainGetBlockMessages == nil {
|
||||
return nil, ErrNotSupported
|
||||
@@ -4670,6 +4687,17 @@ func (s *GatewayStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Messag
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) {
|
||||
if s.Internal.MinerGetBaseInfo == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.MinerGetBaseInfo(p0, p1, p2, p3)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) {
|
||||
if s.Internal.MpoolGetNonce == nil {
|
||||
return 0, ErrNotSupported
|
||||
@@ -4681,6 +4709,17 @@ func (s *GatewayStub) MpoolGetNonce(p0 context.Context, p1 address.Address) (uin
|
||||
return 0, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) {
|
||||
if s.Internal.MpoolPending == nil {
|
||||
return *new([]*types.SignedMessage), ErrNotSupported
|
||||
}
|
||||
return s.Internal.MpoolPending(p0, p1)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) {
|
||||
return *new([]*types.SignedMessage), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) {
|
||||
if s.Internal.MpoolPush == nil {
|
||||
return *new(cid.Cid), ErrNotSupported
|
||||
|
||||
@@ -299,6 +299,7 @@ type MinerInfo struct {
|
||||
SectorSize abi.SectorSize
|
||||
WindowPoStPartitionSectors uint64
|
||||
ConsensusFaultElapsed abi.ChainEpoch
|
||||
PendingOwnerAddress *address.Address
|
||||
Beneficiary address.Address
|
||||
BeneficiaryTerm *miner.BeneficiaryTerm
|
||||
PendingBeneficiaryTerm *miner.PendingBeneficiaryChange
|
||||
|
||||
@@ -35,6 +35,9 @@ import (
|
||||
// * Generate openrpc blobs
|
||||
|
||||
type Gateway interface {
|
||||
MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error)
|
||||
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
|
||||
MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*api.MiningBaseInfo, error)
|
||||
StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error)
|
||||
GasEstimateGasPremium(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error)
|
||||
StateReplay(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error)
|
||||
|
||||
@@ -431,6 +431,8 @@ type GatewayStruct struct {
|
||||
}
|
||||
|
||||
type GatewayMethods struct {
|
||||
ChainGetBlock func(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) ``
|
||||
|
||||
ChainGetBlockMessages func(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) ``
|
||||
|
||||
ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, error) ``
|
||||
@@ -453,8 +455,12 @@ type GatewayMethods struct {
|
||||
|
||||
GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) ``
|
||||
|
||||
MinerGetBaseInfo func(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*api.MiningBaseInfo, error) ``
|
||||
|
||||
MpoolGetNonce func(p0 context.Context, p1 address.Address) (uint64, error) ``
|
||||
|
||||
MpoolPending func(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) ``
|
||||
|
||||
MpoolPush func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) ``
|
||||
|
||||
MsigGetAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) ``
|
||||
@@ -2581,6 +2587,17 @@ func (s *FullNodeStub) WalletVerify(p0 context.Context, p1 address.Address, p2 [
|
||||
return false, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) {
|
||||
if s.Internal.ChainGetBlock == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.ChainGetBlock(p0, p1)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) {
|
||||
if s.Internal.ChainGetBlockMessages == nil {
|
||||
return nil, ErrNotSupported
|
||||
@@ -2702,6 +2719,17 @@ func (s *GatewayStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Messag
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*api.MiningBaseInfo, error) {
|
||||
if s.Internal.MinerGetBaseInfo == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.MinerGetBaseInfo(p0, p1, p2, p3)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*api.MiningBaseInfo, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) {
|
||||
if s.Internal.MpoolGetNonce == nil {
|
||||
return 0, ErrNotSupported
|
||||
@@ -2713,6 +2741,17 @@ func (s *GatewayStub) MpoolGetNonce(p0 context.Context, p1 address.Address) (uin
|
||||
return 0, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) {
|
||||
if s.Internal.MpoolPending == nil {
|
||||
return *new([]*types.SignedMessage), ErrNotSupported
|
||||
}
|
||||
return s.Internal.MpoolPending(p0, p1)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) {
|
||||
return *new([]*types.SignedMessage), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) {
|
||||
if s.Internal.MpoolPush == nil {
|
||||
return *new(cid.Cid), ErrNotSupported
|
||||
|
||||
@@ -660,6 +660,60 @@ func (b *Blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetMany implements a GetMany method for the badgerDB blockstore
|
||||
// This method returns empty slices and an error if an error other than ErrKeyNotFound is encountered
|
||||
// For every ErrKeyNotFound, the missing CID is returned but the rest of the results are returned without error
|
||||
func (b *Blockstore) GetMany(ctx context.Context, cids []cid.Cid) ([]blocks.Block, []cid.Cid, error) {
|
||||
if err := b.access(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer b.viewers.Done()
|
||||
|
||||
b.lockDB()
|
||||
defer b.unlockDB()
|
||||
|
||||
blks := make([]blocks.Block, 0, len(cids))
|
||||
missingCids := make([]cid.Cid, 0, len(cids))
|
||||
err := b.db.View(func(txn *badger.Txn) error {
|
||||
f := func(c cid.Cid) error { // encapsulate in an anonymous function to handle key pool deferral for each key
|
||||
if !c.Defined() {
|
||||
return ipld.ErrNotFound{Cid: c}
|
||||
}
|
||||
k, pooled := b.PooledStorageKey(c)
|
||||
if pooled {
|
||||
defer KeyPool.Put(k)
|
||||
}
|
||||
switch item, err := txn.Get(k); err {
|
||||
case nil:
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blk, err := blocks.NewBlockWithCid(val, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blks = append(blks, blk)
|
||||
case badger.ErrKeyNotFound:
|
||||
missingCids = append(missingCids, c)
|
||||
default:
|
||||
return fmt.Errorf("failed to get block from badger blockstore: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, c := range cids {
|
||||
if err := f(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return blks, missingCids, nil
|
||||
}
|
||||
|
||||
// Get implements Blockstore.Get.
|
||||
func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) {
|
||||
if !cid.Defined() {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -49,16 +49,11 @@ var UpgradeHyperdriveHeight = abi.ChainEpoch(-16)
|
||||
var UpgradeChocolateHeight = abi.ChainEpoch(-17)
|
||||
var UpgradeOhSnapHeight = abi.ChainEpoch(-18)
|
||||
var UpgradeSkyrHeight = abi.ChainEpoch(-19)
|
||||
var UpgradeSharkHeight = abi.ChainEpoch(-20)
|
||||
var UpgradeHyggeHeight = abi.ChainEpoch(-21)
|
||||
var UpgradeLightningHeight = abi.ChainEpoch(-22)
|
||||
|
||||
const UpgradeSharkHeight = abi.ChainEpoch(-20)
|
||||
|
||||
const UpgradeHyggeHeight = abi.ChainEpoch(100)
|
||||
|
||||
// ??????????
|
||||
const UpgradeLightningHeight = 200
|
||||
|
||||
// ??????????????????
|
||||
const UpgradeThunderHeight = 300
|
||||
const UpgradeThunderHeight = 50
|
||||
|
||||
var DrandSchedule = map[abi.ChainEpoch]DrandEnum{
|
||||
0: DrandMainnet,
|
||||
|
||||
@@ -61,6 +61,10 @@ const (
|
||||
MaxPreCommitRandomnessLookback = builtin11.EpochsInDay + SealRandomnessLookback
|
||||
)
|
||||
|
||||
var (
|
||||
MarketDefaultAllocationTermBuffer = market11.MarketDefaultAllocationTermBuffer
|
||||
)
|
||||
|
||||
// SetSupportedProofTypes sets supported proof types, across all actor versions.
|
||||
// This should only be used for testing.
|
||||
func SetSupportedProofTypes(types ...abi.RegisteredSealProof) {
|
||||
|
||||
@@ -39,6 +39,10 @@ const (
|
||||
MaxPreCommitRandomnessLookback = builtin{{.latestVersion}}.EpochsInDay + SealRandomnessLookback
|
||||
)
|
||||
|
||||
var (
|
||||
MarketDefaultAllocationTermBuffer = market{{.latestVersion}}.MarketDefaultAllocationTermBuffer
|
||||
)
|
||||
|
||||
// SetSupportedProofTypes sets supported proof types, across all actor versions.
|
||||
// This should only be used for testing.
|
||||
func SetSupportedProofTypes(types ...abi.RegisteredSealProof) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
@@ -27,24 +26,20 @@ func New(dstore ds.Batching) *SlashFilter {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error {
|
||||
if build.IsNearUpgrade(bh.Height, build.UpgradeOrangeHeight) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) (cid.Cid, error) {
|
||||
epochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, bh.Height))
|
||||
{
|
||||
// double-fork mining (2 blocks at one epoch)
|
||||
if err := checkFault(ctx, f.byEpoch, epochKey, bh, "double-fork mining faults"); err != nil {
|
||||
return err
|
||||
if witness, err := checkFault(ctx, f.byEpoch, epochKey, bh, "double-fork mining faults"); err != nil {
|
||||
return witness, xerrors.Errorf("check double-fork mining faults: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
parentsKey := ds.NewKey(fmt.Sprintf("/%s/%x", bh.Miner, types.NewTipSetKey(bh.Parents...).Bytes()))
|
||||
{
|
||||
// time-offset mining faults (2 blocks with the same parents)
|
||||
if err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults"); err != nil {
|
||||
return err
|
||||
if witness, err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults"); err != nil {
|
||||
return witness, xerrors.Errorf("check time-offset mining faults: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,19 +50,19 @@ func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, par
|
||||
parentEpochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, parentEpoch))
|
||||
have, err := f.byEpoch.Has(ctx, parentEpochKey)
|
||||
if err != nil {
|
||||
return err
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
if have {
|
||||
// If we had, make sure it's in our parent tipset
|
||||
cidb, err := f.byEpoch.Get(ctx, parentEpochKey)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting other block cid: %w", err)
|
||||
return cid.Undef, xerrors.Errorf("getting other block cid: %w", err)
|
||||
}
|
||||
|
||||
_, parent, err := cid.CidFromBytes(cidb)
|
||||
if err != nil {
|
||||
return err
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
var found bool
|
||||
@@ -78,45 +73,45 @@ func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, par
|
||||
}
|
||||
|
||||
if !found {
|
||||
return xerrors.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent)
|
||||
return parent, xerrors.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.byParents.Put(ctx, parentsKey, bh.Cid().Bytes()); err != nil {
|
||||
return xerrors.Errorf("putting byEpoch entry: %w", err)
|
||||
return cid.Undef, xerrors.Errorf("putting byEpoch entry: %w", err)
|
||||
}
|
||||
|
||||
if err := f.byEpoch.Put(ctx, epochKey, bh.Cid().Bytes()); err != nil {
|
||||
return xerrors.Errorf("putting byEpoch entry: %w", err)
|
||||
return cid.Undef, xerrors.Errorf("putting byEpoch entry: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return cid.Undef, nil
|
||||
}
|
||||
|
||||
func checkFault(ctx context.Context, t ds.Datastore, key ds.Key, bh *types.BlockHeader, faultType string) error {
|
||||
func checkFault(ctx context.Context, t ds.Datastore, key ds.Key, bh *types.BlockHeader, faultType string) (cid.Cid, error) {
|
||||
fault, err := t.Has(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
return cid.Undef, xerrors.Errorf("failed to read from datastore: %w", err)
|
||||
}
|
||||
|
||||
if fault {
|
||||
cidb, err := t.Get(ctx, key)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting other block cid: %w", err)
|
||||
return cid.Undef, xerrors.Errorf("getting other block cid: %w", err)
|
||||
}
|
||||
|
||||
_, other, err := cid.CidFromBytes(cidb)
|
||||
if err != nil {
|
||||
return err
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
if other == bh.Cid() {
|
||||
return nil
|
||||
return cid.Undef, nil
|
||||
}
|
||||
|
||||
return xerrors.Errorf("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other)
|
||||
return other, xerrors.Errorf("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other)
|
||||
}
|
||||
|
||||
return nil
|
||||
return cid.Undef, nil
|
||||
}
|
||||
|
||||
+11
-1
@@ -37,7 +37,17 @@ var dbDefs = []string{
|
||||
)`,
|
||||
`INSERT OR IGNORE INTO _meta (version) VALUES (1)`,
|
||||
}
|
||||
var dbPragmas = []string{}
|
||||
|
||||
var dbPragmas = []string{
|
||||
"PRAGMA synchronous = normal",
|
||||
"PRAGMA temp_store = memory",
|
||||
"PRAGMA mmap_size = 30000000000",
|
||||
"PRAGMA page_size = 32768",
|
||||
"PRAGMA auto_vacuum = NONE",
|
||||
"PRAGMA automatic_index = OFF",
|
||||
"PRAGMA journal_mode = WAL",
|
||||
"PRAGMA read_uncommitted = ON",
|
||||
}
|
||||
|
||||
const (
|
||||
// prepared stmts
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"github.com/ipfs/go-datastore"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
big2 "github.com/filecoin-project/go-state-types/big"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin"
|
||||
@@ -524,6 +526,36 @@ func TestPruningSimple(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGasRewardNegative(t *testing.T) {
|
||||
var mp MessagePool
|
||||
|
||||
msg := types.SignedMessage{
|
||||
Message: types.Message{
|
||||
GasLimit: 1000,
|
||||
GasFeeCap: big2.NewInt(20000),
|
||||
GasPremium: big2.NewInt(15000),
|
||||
},
|
||||
}
|
||||
baseFee := big2.NewInt(30000)
|
||||
// Over the GasPremium, but under the BaseFee
|
||||
gr1 := mp.getGasReward(&msg, baseFee)
|
||||
|
||||
msg.Message.GasFeeCap = big2.NewInt(15000)
|
||||
// Equal to GasPremium, under the BaseFee
|
||||
gr2 := mp.getGasReward(&msg, baseFee)
|
||||
|
||||
msg.Message.GasFeeCap = big2.NewInt(10000)
|
||||
// Under both GasPremium and BaseFee
|
||||
gr3 := mp.getGasReward(&msg, baseFee)
|
||||
|
||||
require.True(t, gr1.Sign() < 0)
|
||||
require.True(t, gr2.Sign() < 0)
|
||||
require.True(t, gr3.Sign() < 0)
|
||||
|
||||
require.True(t, gr1.Cmp(gr2) > 0)
|
||||
require.True(t, gr2.Cmp(gr3) > 0)
|
||||
}
|
||||
|
||||
func TestLoadLocal(t *testing.T) {
|
||||
tma := newTestMpoolAPI()
|
||||
ds := datastore.NewMapDatastore()
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-datastore"
|
||||
@@ -1690,3 +1691,188 @@ readLoop:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRealWorldSelectionTiming(t *testing.T) {
|
||||
//stm: @TOKEN_WALLET_NEW_001, @TOKEN_WALLET_SIGN_001, @CHAIN_MEMPOOL_SELECT_001
|
||||
|
||||
// load test-messages.json.gz and rewrite the messages so that
|
||||
// 1) we map each real actor to a test actor so that we can sign the messages
|
||||
// 2) adjust the nonces so that they start from 0
|
||||
file, err := os.Open("test-messages2.json.gz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gzr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(gzr)
|
||||
|
||||
var msgs []*types.SignedMessage
|
||||
baseNonces := make(map[address.Address]uint64)
|
||||
|
||||
readLoop:
|
||||
for {
|
||||
m := new(types.SignedMessage)
|
||||
err := dec.Decode(m)
|
||||
switch err {
|
||||
case nil:
|
||||
msgs = append(msgs, m)
|
||||
nonce, ok := baseNonces[m.Message.From]
|
||||
if !ok || m.Message.Nonce < nonce {
|
||||
baseNonces[m.Message.From] = m.Message.Nonce
|
||||
}
|
||||
|
||||
case io.EOF:
|
||||
break readLoop
|
||||
|
||||
default:
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
actorMap := make(map[address.Address]address.Address)
|
||||
actorWallets := make(map[address.Address]api.Wallet)
|
||||
|
||||
for _, m := range msgs {
|
||||
baseNonce := baseNonces[m.Message.From]
|
||||
|
||||
localActor, ok := actorMap[m.Message.From]
|
||||
if !ok {
|
||||
w, err := wallet.NewWallet(wallet.NewMemKeyStore())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a, err := w.WalletNew(context.Background(), types.KTSecp256k1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actorMap[m.Message.From] = a
|
||||
actorWallets[a] = w
|
||||
localActor = a
|
||||
}
|
||||
|
||||
w, ok := actorWallets[localActor]
|
||||
if !ok {
|
||||
t.Fatalf("failed to lookup wallet for actor %s", localActor)
|
||||
}
|
||||
|
||||
m.Message.From = localActor
|
||||
m.Message.Nonce -= baseNonce
|
||||
|
||||
sig, err := w.WalletSign(context.TODO(), localActor, m.Message.Cid().Bytes(), api.MsgMeta{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m.Signature = *sig
|
||||
}
|
||||
|
||||
mp, tma := makeTestMpool()
|
||||
|
||||
block := tma.nextBlockWithHeight(uint64(build.UpgradeHyggeHeight) + 10)
|
||||
ts := mock.TipSet(block)
|
||||
tma.applyBlock(t, block)
|
||||
|
||||
for _, a := range actorMap {
|
||||
tma.setBalance(a, 1000000)
|
||||
}
|
||||
|
||||
tma.baseFee = types.NewInt(800_000_000)
|
||||
|
||||
sort.Slice(msgs, func(i, j int) bool {
|
||||
return msgs[i].Message.Nonce < msgs[j].Message.Nonce
|
||||
})
|
||||
|
||||
// add the messages
|
||||
for _, m := range msgs {
|
||||
mustAdd(t, mp, m)
|
||||
}
|
||||
|
||||
// do message selection and check block packing
|
||||
minGasLimit := int64(0.9 * float64(build.BlockGasLimit))
|
||||
|
||||
// greedy first
|
||||
start := time.Now()
|
||||
selected, err := mp.SelectMessages(context.Background(), ts, 1.0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("selected %d messages in %s", len(selected), time.Since(start))
|
||||
|
||||
gasLimit := int64(0)
|
||||
for _, m := range selected {
|
||||
gasLimit += m.Message.GasLimit
|
||||
}
|
||||
if gasLimit < minGasLimit {
|
||||
t.Fatalf("failed to pack with tq=1.0; packed %d, minimum packing: %d", gasLimit, minGasLimit)
|
||||
}
|
||||
|
||||
// high quality ticket
|
||||
start = time.Now()
|
||||
selected, err = mp.SelectMessages(context.Background(), ts, .8)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("selected %d messages in %s", len(selected), time.Since(start))
|
||||
|
||||
gasLimit = int64(0)
|
||||
for _, m := range selected {
|
||||
gasLimit += m.Message.GasLimit
|
||||
}
|
||||
if gasLimit < minGasLimit {
|
||||
t.Fatalf("failed to pack with tq=0.8; packed %d, minimum packing: %d", gasLimit, minGasLimit)
|
||||
}
|
||||
|
||||
// mid quality ticket
|
||||
start = time.Now()
|
||||
selected, err = mp.SelectMessages(context.Background(), ts, .4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("selected %d messages in %s", len(selected), time.Since(start))
|
||||
|
||||
gasLimit = int64(0)
|
||||
for _, m := range selected {
|
||||
gasLimit += m.Message.GasLimit
|
||||
}
|
||||
if gasLimit < minGasLimit {
|
||||
t.Fatalf("failed to pack with tq=0.4; packed %d, minimum packing: %d", gasLimit, minGasLimit)
|
||||
}
|
||||
|
||||
// low quality ticket
|
||||
start = time.Now()
|
||||
selected, err = mp.SelectMessages(context.Background(), ts, .1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("selected %d messages in %s", len(selected), time.Since(start))
|
||||
|
||||
gasLimit = int64(0)
|
||||
for _, m := range selected {
|
||||
gasLimit += m.Message.GasLimit
|
||||
}
|
||||
if gasLimit < minGasLimit {
|
||||
t.Fatalf("failed to pack with tq=0.1; packed %d, minimum packing: %d", gasLimit, minGasLimit)
|
||||
}
|
||||
|
||||
// very low quality ticket
|
||||
start = time.Now()
|
||||
selected, err = mp.SelectMessages(context.Background(), ts, .01)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("selected %d messages in %s", len(selected), time.Since(start))
|
||||
|
||||
gasLimit = int64(0)
|
||||
for _, m := range selected {
|
||||
gasLimit += m.Message.GasLimit
|
||||
}
|
||||
if gasLimit < minGasLimit {
|
||||
t.Fatalf("failed to pack with tq=0.01; packed %d, minimum packing: %d", gasLimit, minGasLimit)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
+16
-1
@@ -72,7 +72,7 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch,
|
||||
|
||||
base, trace, err := sm.ExecutionTrace(ctx, ts)
|
||||
if err != nil {
|
||||
return cid.Undef, nil, err
|
||||
return cid.Undef, nil, xerrors.Errorf("failed to compute base state: %w", err)
|
||||
}
|
||||
|
||||
for i := ts.Height(); i < height; i++ {
|
||||
@@ -116,6 +116,21 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch,
|
||||
if ret.ExitCode != 0 {
|
||||
log.Infof("compute state apply message %d failed (exit: %d): %s", i, ret.ExitCode, ret.ActorErr)
|
||||
}
|
||||
|
||||
ir := &api.InvocResult{
|
||||
MsgCid: msg.Cid(),
|
||||
Msg: msg,
|
||||
MsgRct: &ret.MessageReceipt,
|
||||
ExecutionTrace: ret.ExecutionTrace,
|
||||
Duration: ret.Duration,
|
||||
}
|
||||
if ret.ActorErr != nil {
|
||||
ir.Error = ret.ActorErr.Error()
|
||||
}
|
||||
if ret.GasCosts != nil {
|
||||
ir.GasCost = MakeMsgGasCost(msg, ret)
|
||||
}
|
||||
trace = append(trace, ir)
|
||||
}
|
||||
|
||||
root, err := vmi.Flush(ctx)
|
||||
|
||||
@@ -358,7 +358,11 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrNonceGap):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrGasFeeCapTooLow):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrNonceTooLow):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrExistingNonce):
|
||||
return pubsub.ValidationIgnore
|
||||
default:
|
||||
return pubsub.ValidationReject
|
||||
|
||||
@@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
@@ -14,10 +15,13 @@ type BlockMsg struct {
|
||||
|
||||
func DecodeBlockMsg(b []byte) (*BlockMsg, error) {
|
||||
var bm BlockMsg
|
||||
if err := bm.UnmarshalCBOR(bytes.NewReader(b)); err != nil {
|
||||
data := bytes.NewReader(b)
|
||||
if err := bm.UnmarshalCBOR(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l := data.Len(); l != 0 {
|
||||
return nil, fmt.Errorf("extraneous data in BlockMsg CBOR encoding: got %d unexpected bytes", l)
|
||||
}
|
||||
return &bm, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeBlockMsg(t *testing.T) {
|
||||
type args struct {
|
||||
b []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
want *BlockMsg
|
||||
wantErr bool
|
||||
}{
|
||||
{"decode empty BlockMsg with extra data at the end", []byte{0x83, 0xf6, 0x80, 0x80, 0x20}, nil, true},
|
||||
{"decode valid empty BlockMsg", []byte{0x83, 0xf6, 0x80, 0x80}, new(BlockMsg), false},
|
||||
{"decode invalid cbor", []byte{0x83, 0xf6, 0x80}, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
data := tt.data
|
||||
want := tt.want
|
||||
wantErr := tt.wantErr
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := DecodeBlockMsg(data)
|
||||
if wantErr {
|
||||
assert.Errorf(t, err, "DecodeBlockMsg(%x)", data)
|
||||
return
|
||||
}
|
||||
assert.NoErrorf(t, err, "DecodeBlockMsg(%x)", data)
|
||||
assert.Equalf(t, want, got, "DecodeBlockMsg(%x)", data)
|
||||
serialized, err := got.Serialize()
|
||||
assert.NoErrorf(t, err, "DecodeBlockMsg(%x)", data)
|
||||
assert.Equalf(t, serialized, data, "DecodeBlockMsg(%x)", data)
|
||||
})
|
||||
}
|
||||
}
|
||||
+9
-2
@@ -1065,12 +1065,19 @@ var StateComputeStateCmd = &cli.Command{
|
||||
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
ts, err := LoadTipSet(ctx, cctx, api)
|
||||
h := abi.ChainEpoch(cctx.Uint64("vm-height"))
|
||||
var ts *types.TipSet
|
||||
if tss := cctx.String("tipset"); tss != "" {
|
||||
ts, err = ParseTipSetRef(ctx, api, tss)
|
||||
} else if h > 0 {
|
||||
ts, err = api.ChainGetTipSetByHeight(ctx, h, types.EmptyTSK)
|
||||
} else {
|
||||
ts, err = api.ChainHead(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := abi.ChainEpoch(cctx.Uint64("vm-height"))
|
||||
if h == 0 {
|
||||
h = ts.Height()
|
||||
}
|
||||
|
||||
@@ -98,14 +98,16 @@ func main() {
|
||||
log.Info("Starting lotus-bench")
|
||||
|
||||
app := &cli.App{
|
||||
Name: "lotus-bench",
|
||||
Usage: "Benchmark performance of lotus on your hardware",
|
||||
Version: build.UserVersion(),
|
||||
Name: "lotus-bench",
|
||||
Usage: "Benchmark performance of lotus on your hardware",
|
||||
Version: build.UserVersion(),
|
||||
DisableSliceFlagSeparator: true,
|
||||
Commands: []*cli.Command{
|
||||
proveCmd,
|
||||
sealBenchCmd,
|
||||
simpleCmd,
|
||||
importBenchCmd,
|
||||
rpcCmd,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,576 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var rpcCmd = &cli.Command{
|
||||
Name: "rpc",
|
||||
Usage: "Runs a concurrent stress test on one or more rpc methods and prints the performance metrics including latency distribution and histogram",
|
||||
Description: `This benchmark is designed to stress test the rpc methods of a lotus node so that we can simulate real world usage and measure the performance of rpc methods on the node.
|
||||
|
||||
This benchmark has the following features:
|
||||
* Can query each method both sequentially and concurrently
|
||||
* Supports rate limiting
|
||||
* Can query multiple different endpoints at once (supporting different concurrency level and rate limiting for each method)
|
||||
* Gives a nice reporting summary of the stress testing of each method (including latency distribution, histogram and more)
|
||||
* Easy to use
|
||||
|
||||
To use this benchmark you must specify the rpc methods you want to test using the --method options, the format of it is:
|
||||
|
||||
--method=NAME[:CONCURRENCY][:QPS][:PARAMS] where only NAME is required.
|
||||
|
||||
Here are some real examples:
|
||||
lotus-bench rpc --method='eth_chainId' // run eth_chainId with default concurrency and qps
|
||||
lotus-bench rpc --method='eth_chainId:3' // override concurrency to 3
|
||||
lotus-bench rpc --method='eth_chainId::100' // override to 100 qps while using default concurrency
|
||||
lotus-bench rpc --method='eth_chainId:3:100' // run using 3 workers but limit to 100 qps
|
||||
lotus-bench rpc --method='eth_getTransactionCount:::["0xd4c70007F3F502f212c7e6794b94C06F36173B36", "latest"]' // run using optional params while using default concurrency and qps
|
||||
lotus-bench rpc --method='eth_chainId' --method='eth_getTransactionCount:10:0:["0xd4c70007F3F502f212c7e6794b94C06F36173B36", "latest"]' // run multiple methods at once
|
||||
|
||||
NOTE: The last two examples will not work until we upgrade urfave dependency (tracked in https://github.com/urfave/cli/issues/1734)`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "endpoint",
|
||||
Value: "http://127.0.0.1:1234/rpc/v1",
|
||||
Usage: "The rpc endpoint to benchmark",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "duration",
|
||||
Value: 60 * time.Second,
|
||||
Usage: "Duration of benchmark in seconds",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 10,
|
||||
Usage: "How many workers should be used per rpc method (can be overridden per method)",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "qps",
|
||||
Value: 0,
|
||||
Usage: "How many requests per second should be sent per rpc method (can be overridden per method), a value of 0 means no limit",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "method",
|
||||
Usage: `Method to benchmark, you can specify multiple methods by repeating this flag. You can also specify method specific options to set the concurrency and qps for each method (see usage).
|
||||
`,
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "watch",
|
||||
Value: 0 * time.Second,
|
||||
Usage: "If >0 then generates reports every N seconds (only supports linux/unix)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "print-response",
|
||||
Value: false,
|
||||
Usage: "print the response of each request",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if len(cctx.StringSlice("method")) == 0 {
|
||||
return errors.New("you must specify and least one method to benchmark")
|
||||
}
|
||||
|
||||
var rpcMethods []*RPCMethod
|
||||
for _, str := range cctx.StringSlice("method") {
|
||||
entries := strings.SplitN(str, ":", 4)
|
||||
if len(entries) == 0 {
|
||||
return errors.New("invalid method format")
|
||||
}
|
||||
|
||||
// check if concurrency was specified
|
||||
concurrency := cctx.Int("concurrency")
|
||||
if len(entries) > 1 {
|
||||
if len(entries[1]) > 0 {
|
||||
var err error
|
||||
concurrency, err = strconv.Atoi(entries[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse concurrency value from method %s: %v", entries[0], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if qps was specified
|
||||
qps := cctx.Int("qps")
|
||||
if len(entries) > 2 {
|
||||
if len(entries[2]) > 0 {
|
||||
var err error
|
||||
qps, err = strconv.Atoi(entries[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse qps value from method %s: %v", entries[0], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if params was specified
|
||||
params := "[]"
|
||||
if len(entries) > 3 {
|
||||
params = entries[3]
|
||||
}
|
||||
|
||||
rpcMethods = append(rpcMethods, &RPCMethod{
|
||||
w: os.Stdout,
|
||||
uri: cctx.String("endpoint"),
|
||||
method: entries[0],
|
||||
concurrency: concurrency,
|
||||
qps: qps,
|
||||
params: params,
|
||||
printResp: cctx.Bool("print-response"),
|
||||
})
|
||||
}
|
||||
|
||||
// terminate early on ctrl+c
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
fmt.Println("Received interrupt, stopping...")
|
||||
for _, method := range rpcMethods {
|
||||
method.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
// stop all threads after duration
|
||||
go func() {
|
||||
time.Sleep(cctx.Duration("duration"))
|
||||
for _, e := range rpcMethods {
|
||||
e.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
// start all threads
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(rpcMethods))
|
||||
|
||||
for _, e := range rpcMethods {
|
||||
go func(e *RPCMethod) {
|
||||
defer wg.Done()
|
||||
err := e.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("error running rpc method: %v\n", err)
|
||||
}
|
||||
}(e)
|
||||
}
|
||||
|
||||
// if watch is set then print a report every N seconds
|
||||
var progressCh chan struct{}
|
||||
if cctx.Duration("watch") > 0 {
|
||||
progressCh = make(chan struct{}, 1)
|
||||
go func(progressCh chan struct{}) {
|
||||
ticker := time.NewTicker(cctx.Duration("watch"))
|
||||
for {
|
||||
clearAndPrintReport := func() {
|
||||
// clear the screen move the curser to the top left
|
||||
fmt.Print("\033[2J")
|
||||
fmt.Printf("\033[%d;%dH", 1, 1)
|
||||
for i, e := range rpcMethods {
|
||||
e.Report()
|
||||
if i < len(rpcMethods)-1 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ticker.C:
|
||||
clearAndPrintReport()
|
||||
case <-progressCh:
|
||||
clearAndPrintReport()
|
||||
return
|
||||
}
|
||||
}
|
||||
}(progressCh)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if progressCh != nil {
|
||||
// wait for the watch go routine to return
|
||||
progressCh <- struct{}{}
|
||||
|
||||
// no need to print the report again
|
||||
return nil
|
||||
}
|
||||
|
||||
// print the report for each endpoint
|
||||
for i, e := range rpcMethods {
|
||||
e.Report()
|
||||
if i < len(rpcMethods)-1 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// RPCMethod handles the benchmarking of a single endpoint method.
|
||||
type RPCMethod struct {
|
||||
w io.Writer
|
||||
// the endpoint uri
|
||||
uri string
|
||||
// the rpc method we want to benchmark
|
||||
method string
|
||||
// the number of concurrent requests to make to this endpoint
|
||||
concurrency int
|
||||
// if >0 then limit to qps is the max number of requests per second to make to this endpoint (0 = no limit)
|
||||
qps int
|
||||
// many endpoints require specific parameters to be passed
|
||||
params string
|
||||
// whether or not to print the response of each request (useful for debugging)
|
||||
printResp bool
|
||||
// instruct the worker go routines to stop
|
||||
stopCh chan struct{}
|
||||
// when the endpoint bencharking started
|
||||
start time.Time
|
||||
// results channel is used by the workers to send results to the reporter
|
||||
results chan *result
|
||||
// reporter handles reading the results from workers and printing the report statistics
|
||||
reporter *Reporter
|
||||
}
|
||||
|
||||
// result is the result of a single rpc method request.
|
||||
type result struct {
|
||||
err error
|
||||
statusCode *int
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func (rpc *RPCMethod) Run() error {
|
||||
client := &http.Client{
|
||||
Timeout: 0,
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(rpc.concurrency)
|
||||
|
||||
rpc.results = make(chan *result, rpc.concurrency*1_000)
|
||||
rpc.stopCh = make(chan struct{}, rpc.concurrency)
|
||||
|
||||
go func() {
|
||||
rpc.reporter = NewReporter(rpc.results, rpc.w)
|
||||
rpc.reporter.Run()
|
||||
}()
|
||||
|
||||
rpc.start = time.Now()
|
||||
|
||||
// throttle the number of requests per second
|
||||
var qpsTicker *time.Ticker
|
||||
if rpc.qps > 0 {
|
||||
qpsTicker = time.NewTicker(time.Second / time.Duration(rpc.qps))
|
||||
}
|
||||
|
||||
for i := 0; i < rpc.concurrency; i++ {
|
||||
go func() {
|
||||
rpc.startWorker(client, qpsTicker)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// close the results channel so reporter will stop
|
||||
close(rpc.results)
|
||||
|
||||
// wait until the reporter is done
|
||||
<-rpc.reporter.doneCh
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rpc *RPCMethod) startWorker(client *http.Client, qpsTicker *time.Ticker) {
|
||||
for {
|
||||
// check if we should stop
|
||||
select {
|
||||
case <-rpc.stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// wait for the next tick if we are rate limiting this endpoint
|
||||
if qpsTicker != nil {
|
||||
<-qpsTicker.C
|
||||
}
|
||||
|
||||
req, err := rpc.buildRequest()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var statusCode *int
|
||||
|
||||
// send request the endpoint
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("HTTP error: %s", err.Error())
|
||||
} else {
|
||||
statusCode = &resp.StatusCode
|
||||
|
||||
// there was not a HTTP error but we need to still check the json response for errrors
|
||||
var data []byte
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// we are only interested if it has the error field in the response
|
||||
type respData struct {
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
// unmarshal the response into a struct so we can check for errors
|
||||
var d respData
|
||||
err = json.Unmarshal(data, &d)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// if the response has an error json message then it should be considered an error just like any http error
|
||||
if len(d.Error.Message) > 0 {
|
||||
// truncate the error message if it is too long
|
||||
if len(d.Error.Message) > 1000 {
|
||||
d.Error.Message = d.Error.Message[:1000] + "..."
|
||||
}
|
||||
// remove newlines from the error message so we don't screw up the report
|
||||
d.Error.Message = strings.ReplaceAll(d.Error.Message, "\n", "")
|
||||
|
||||
err = fmt.Errorf("JSON error: code:%d, message:%s", d.Error.Code, d.Error.Message)
|
||||
}
|
||||
|
||||
if rpc.printResp {
|
||||
fmt.Printf("[%s] %s", rpc.method, string(data))
|
||||
}
|
||||
|
||||
resp.Body.Close() //nolint:errcheck
|
||||
}
|
||||
|
||||
rpc.results <- &result{
|
||||
statusCode: statusCode,
|
||||
err: err,
|
||||
duration: time.Since(start),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rpc *RPCMethod) buildRequest() (*http.Request, error) {
|
||||
jreq, err := json.Marshal(struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
ID int `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}{
|
||||
Jsonrpc: "2.0",
|
||||
Method: rpc.method,
|
||||
Params: json.RawMessage(rpc.params),
|
||||
ID: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", rpc.uri, bytes.NewReader(jreq))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (rpc *RPCMethod) Stop() {
|
||||
for i := 0; i < rpc.concurrency; i++ {
|
||||
rpc.stopCh <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (rpc *RPCMethod) Report() {
|
||||
total := time.Since(rpc.start)
|
||||
fmt.Fprintf(rpc.w, "[%s]:\n", rpc.method)
|
||||
fmt.Fprintf(rpc.w, "- Options:\n")
|
||||
fmt.Fprintf(rpc.w, " - concurrency: %d\n", rpc.concurrency)
|
||||
fmt.Fprintf(rpc.w, " - params: %s\n", rpc.params)
|
||||
fmt.Fprintf(rpc.w, " - qps: %d\n", rpc.qps)
|
||||
rpc.reporter.Print(total, rpc.w)
|
||||
}
|
||||
|
||||
// Reporter reads the results from the workers through the results channel and aggregates the results.
|
||||
type Reporter struct {
|
||||
// write the report to this writer
|
||||
w io.Writer
|
||||
// the reporter read the results from this channel
|
||||
results chan *result
|
||||
// doneCh is used to signal that the reporter has finished reading the results (channel has closed)
|
||||
doneCh chan bool
|
||||
|
||||
// lock protect the following fields during critical sections (if --watch was specified)
|
||||
lock sync.Mutex
|
||||
// the latencies of all requests
|
||||
latencies []int64
|
||||
// the number of requests that returned each status code
|
||||
statusCodes map[int]int
|
||||
// the number of errors that occurred
|
||||
errors map[string]int
|
||||
}
|
||||
|
||||
func NewReporter(results chan *result, w io.Writer) *Reporter {
|
||||
return &Reporter{
|
||||
w: w,
|
||||
results: results,
|
||||
doneCh: make(chan bool, 1),
|
||||
statusCodes: make(map[int]int),
|
||||
errors: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reporter) Run() {
|
||||
for res := range r.results {
|
||||
r.lock.Lock()
|
||||
|
||||
r.latencies = append(r.latencies, res.duration.Milliseconds())
|
||||
|
||||
if res.statusCode != nil {
|
||||
r.statusCodes[*res.statusCode]++
|
||||
}
|
||||
|
||||
if res.err != nil {
|
||||
if len(r.errors) < 1_000_000 {
|
||||
r.errors[res.err.Error()]++
|
||||
} else {
|
||||
// we don't want to store too many errors in memory
|
||||
r.errors["hidden"]++
|
||||
}
|
||||
} else {
|
||||
r.errors["nil"]++
|
||||
}
|
||||
|
||||
r.lock.Unlock()
|
||||
}
|
||||
|
||||
r.doneCh <- true
|
||||
}
|
||||
|
||||
func (r *Reporter) Print(elapsed time.Duration, w io.Writer) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
nrReq := int64(len(r.latencies))
|
||||
if nrReq == 0 {
|
||||
fmt.Println("No requests were made")
|
||||
return
|
||||
}
|
||||
|
||||
// we need to sort the latencies slice to calculate the percentiles
|
||||
sort.Slice(r.latencies, func(i, j int) bool {
|
||||
return r.latencies[i] < r.latencies[j]
|
||||
})
|
||||
|
||||
var totalLatency int64 = 0
|
||||
for _, latency := range r.latencies {
|
||||
totalLatency += latency
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "- Total Requests: %d\n", nrReq)
|
||||
fmt.Fprintf(w, "- Total Duration: %dms\n", elapsed.Milliseconds())
|
||||
fmt.Fprintf(w, "- Requests/sec: %f\n", float64(nrReq)/elapsed.Seconds())
|
||||
fmt.Fprintf(w, "- Avg latency: %dms\n", totalLatency/nrReq)
|
||||
fmt.Fprintf(w, "- Median latency: %dms\n", r.latencies[nrReq/2])
|
||||
fmt.Fprintf(w, "- Latency distribution:\n")
|
||||
percentiles := []float64{0.1, 0.5, 0.9, 0.95, 0.99, 0.999}
|
||||
for _, p := range percentiles {
|
||||
idx := int64(p * float64(nrReq))
|
||||
fmt.Fprintf(w, " %s%% in %dms\n", fmt.Sprintf("%.2f", p*100.0), r.latencies[idx])
|
||||
}
|
||||
|
||||
// create a simple histogram with 10 buckets spanning the range of latency
|
||||
// into equal ranges
|
||||
//
|
||||
nrBucket := 10
|
||||
buckets := make([]Bucket, nrBucket)
|
||||
latencyRange := r.latencies[len(r.latencies)-1]
|
||||
bucketRange := latencyRange / int64(nrBucket)
|
||||
|
||||
// mark the end of each bucket
|
||||
for i := 0; i < nrBucket; i++ {
|
||||
buckets[i].start = int64(i) * bucketRange
|
||||
buckets[i].end = buckets[i].start + bucketRange
|
||||
// extend the last bucked by any remaning range caused by the integer division
|
||||
if i == nrBucket-1 {
|
||||
buckets[i].end = latencyRange
|
||||
}
|
||||
}
|
||||
|
||||
// count the number of requests in each bucket
|
||||
currBucket := 0
|
||||
for i := 0; i < len(r.latencies); {
|
||||
if r.latencies[i] <= buckets[currBucket].end {
|
||||
buckets[currBucket].cnt++
|
||||
i++
|
||||
} else {
|
||||
currBucket++
|
||||
}
|
||||
}
|
||||
|
||||
// print the histogram using a tabwriter which will align the columns nicely
|
||||
fmt.Fprintf(w, "- Histogram:\n")
|
||||
const padding = 2
|
||||
tabWriter := tabwriter.NewWriter(w, 0, 0, padding, ' ', tabwriter.AlignRight|tabwriter.Debug)
|
||||
for i := 0; i < nrBucket; i++ {
|
||||
ratio := float64(buckets[i].cnt) / float64(nrReq)
|
||||
bars := strings.Repeat("#", int(ratio*100))
|
||||
fmt.Fprintf(tabWriter, " %d-%dms\t%d\t%s (%s%%)\n", buckets[i].start, buckets[i].end, buckets[i].cnt, bars, fmt.Sprintf("%.2f", ratio*100))
|
||||
}
|
||||
tabWriter.Flush() //nolint:errcheck
|
||||
|
||||
fmt.Fprintf(w, "- Status codes:\n")
|
||||
for code, cnt := range r.statusCodes {
|
||||
fmt.Fprintf(w, " [%d]: %d\n", code, cnt)
|
||||
}
|
||||
|
||||
// print the 10 most occurring errors (in case error values are not unique)
|
||||
//
|
||||
type kv struct {
|
||||
err string
|
||||
cnt int
|
||||
}
|
||||
var sortedErrors []kv
|
||||
for err, cnt := range r.errors {
|
||||
sortedErrors = append(sortedErrors, kv{err, cnt})
|
||||
}
|
||||
sort.Slice(sortedErrors, func(i, j int) bool {
|
||||
return sortedErrors[i].cnt > sortedErrors[j].cnt
|
||||
})
|
||||
fmt.Fprintf(w, "- Errors (top 10):\n")
|
||||
for i, se := range sortedErrors {
|
||||
if i > 10 {
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(w, " [%s]: %d\n", se.err, se.cnt)
|
||||
}
|
||||
}
|
||||
|
||||
type Bucket struct {
|
||||
start int64
|
||||
// the end value of the bucket
|
||||
end int64
|
||||
// how many entries are in the bucket
|
||||
cnt int
|
||||
}
|
||||
@@ -78,7 +78,7 @@ var runCmd = &cli.Command{
|
||||
&cli.Uint64Flag{
|
||||
Name: "data-cap",
|
||||
EnvVars: []string{"LOTUS_DATACAP_AMOUNT"},
|
||||
Value: 10240,
|
||||
Value: verifregtypes9.MinVerifiedDealSize.Uint64(),
|
||||
},
|
||||
&cli.Float64Flag{
|
||||
Name: "captcha-threshold",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script src="https://www.google.com/recaptcha/api.js"></script>
|
||||
<script>
|
||||
function onSubmit(token) {
|
||||
document.getElementById("funds-form").submit();
|
||||
document.getElementById("datacap-form").submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+267
-33
@@ -2,9 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -318,6 +320,9 @@ var sectorsListCmd = &cli.Command{
|
||||
Value: parallelSectorChecks,
|
||||
},
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
sectorsListUpgradeBoundsCmd,
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
// http mode allows for parallel json decoding/encoding, which was a bottleneck here
|
||||
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx, cliutil.StorageMinerUseHttp)
|
||||
@@ -585,6 +590,169 @@ var sectorsListCmd = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var sectorsListUpgradeBoundsCmd = &cli.Command{
|
||||
Name: "upgrade-bounds",
|
||||
Usage: "Output upgrade bounds for available sectors",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "buckets",
|
||||
Value: 25,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "csv",
|
||||
Usage: "output machine-readable values",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "deal-terms",
|
||||
Usage: "bucket by how many deal-sectors can start at a given expiration",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx, cliutil.StorageMinerUseHttp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
fullApi, closer2, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer2()
|
||||
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
list, err := minerApi.SectorsListInStates(ctx, []api.SectorState{
|
||||
api.SectorState(sealing.Available),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting sector list: %w", err)
|
||||
}
|
||||
|
||||
head, err := fullApi.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting chain head: %w", err)
|
||||
}
|
||||
|
||||
filter := bitfield.New()
|
||||
|
||||
for _, s := range list {
|
||||
filter.Set(uint64(s))
|
||||
}
|
||||
|
||||
maddr, err := minerApi.ActorAddress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sset, err := fullApi.StateMinerSectors(ctx, maddr, &filter, head.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(sset) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var minExpiration, maxExpiration abi.ChainEpoch
|
||||
|
||||
for _, s := range sset {
|
||||
if s.Expiration < minExpiration || minExpiration == 0 {
|
||||
minExpiration = s.Expiration
|
||||
}
|
||||
if s.Expiration > maxExpiration {
|
||||
maxExpiration = s.Expiration
|
||||
}
|
||||
}
|
||||
|
||||
buckets := cctx.Int("buckets")
|
||||
bucketSize := (maxExpiration - minExpiration) / abi.ChainEpoch(buckets)
|
||||
bucketCounts := make([]int, buckets+1)
|
||||
|
||||
for b := range bucketCounts {
|
||||
bucketMin := minExpiration + abi.ChainEpoch(b)*bucketSize
|
||||
bucketMax := minExpiration + abi.ChainEpoch(b+1)*bucketSize
|
||||
|
||||
if cctx.Bool("deal-terms") {
|
||||
bucketMax = bucketMax + policy.MarketDefaultAllocationTermBuffer
|
||||
}
|
||||
|
||||
for _, s := range sset {
|
||||
isInBucket := s.Expiration >= bucketMin && s.Expiration < bucketMax
|
||||
|
||||
if isInBucket {
|
||||
bucketCounts[b]++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Creating CSV writer
|
||||
writer := csv.NewWriter(os.Stdout)
|
||||
|
||||
// Writing CSV headers
|
||||
err = writer.Write([]string{"Max Expiration in Bucket", "Sector Count"})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("writing csv headers: %w", err)
|
||||
}
|
||||
|
||||
// Writing bucket details
|
||||
|
||||
if cctx.Bool("csv") {
|
||||
for i := 0; i < buckets; i++ {
|
||||
maxExp := minExpiration + abi.ChainEpoch(i+1)*bucketSize
|
||||
|
||||
timeStr := strconv.FormatInt(int64(maxExp), 10)
|
||||
|
||||
err = writer.Write([]string{
|
||||
timeStr,
|
||||
strconv.Itoa(bucketCounts[i]),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("writing csv row: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush to make sure all data is written to the underlying writer
|
||||
writer.Flush()
|
||||
|
||||
if err := writer.Error(); err != nil {
|
||||
return xerrors.Errorf("flushing csv writer: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tw := tablewriter.New(
|
||||
tablewriter.Col("Bucket Expiration"),
|
||||
tablewriter.Col("Sector Count"),
|
||||
tablewriter.Col("Bar"),
|
||||
)
|
||||
|
||||
var barCols = 40
|
||||
var maxCount int
|
||||
|
||||
for _, c := range bucketCounts {
|
||||
if c > maxCount {
|
||||
maxCount = c
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < buckets; i++ {
|
||||
maxExp := minExpiration + abi.ChainEpoch(i+1)*bucketSize
|
||||
timeStr := cliutil.EpochTime(head.Height(), maxExp)
|
||||
|
||||
tw.Write(map[string]interface{}{
|
||||
"Bucket Expiration": timeStr,
|
||||
"Sector Count": color.YellowString("%d", bucketCounts[i]),
|
||||
"Bar": "[" + color.GreenString(strings.Repeat("|", bucketCounts[i]*barCols/maxCount)) + strings.Repeat(" ", barCols-bucketCounts[i]*barCols/maxCount) + "]",
|
||||
})
|
||||
}
|
||||
|
||||
return tw.Flush(os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var sectorsRefsCmd = &cli.Command{
|
||||
Name: "refs",
|
||||
Usage: "List References to sectors",
|
||||
@@ -2056,7 +2224,6 @@ func yesno(b bool) string {
|
||||
return color.RedString("NO")
|
||||
}
|
||||
|
||||
// TODO simulate this call if --really-do-it is not used
|
||||
var sectorsCompactPartitionsCmd = &cli.Command{
|
||||
Name: "compact-partitions",
|
||||
Usage: "removes dead sectors from partitions and reduces the number of partitions used if possible",
|
||||
@@ -2082,12 +2249,7 @@ var sectorsCompactPartitionsCmd = &cli.Command{
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if !cctx.Bool("really-do-it") {
|
||||
fmt.Println("Pass --really-do-it to actually execute this action")
|
||||
return nil
|
||||
}
|
||||
|
||||
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
||||
fullNodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2100,7 +2262,7 @@ var sectorsCompactPartitionsCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
minfo, err := fullNodeAPI.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2116,46 +2278,118 @@ var sectorsCompactPartitionsCmd = &cli.Command{
|
||||
}
|
||||
fmt.Printf("compacting %d paritions\n", len(parts))
|
||||
|
||||
var makeMsgForPartitions func(partitionsBf bitfield.BitField) ([]*types.Message, error)
|
||||
makeMsgForPartitions = func(partitionsBf bitfield.BitField) ([]*types.Message, error) {
|
||||
params := miner.CompactPartitionsParams{
|
||||
Deadline: deadline,
|
||||
Partitions: partitionsBf,
|
||||
}
|
||||
|
||||
sp, aerr := actors.SerializeParams(¶ms)
|
||||
if aerr != nil {
|
||||
return nil, xerrors.Errorf("serializing params: %w", err)
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
From: minfo.Worker,
|
||||
To: maddr,
|
||||
Method: builtin.MethodsMiner.CompactPartitions,
|
||||
Value: big.Zero(),
|
||||
Params: sp,
|
||||
}
|
||||
|
||||
estimatedMsg, err := fullNodeAPI.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK)
|
||||
if err != nil && xerrors.Is(err, &api.ErrOutOfGas{}) {
|
||||
// the message is too big -- split into 2
|
||||
partitionsSlice, err := partitionsBf.All(math.MaxUint64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
partitions1 := bitfield.New()
|
||||
for i := 0; i < len(partitionsSlice)/2; i++ {
|
||||
partitions1.Set(uint64(i))
|
||||
}
|
||||
|
||||
msgs1, err := makeMsgForPartitions(partitions1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// time for the second half
|
||||
partitions2 := bitfield.New()
|
||||
for i := len(partitionsSlice) / 2; i < len(partitionsSlice); i++ {
|
||||
partitions2.Set(uint64(i))
|
||||
}
|
||||
|
||||
msgs2, err := makeMsgForPartitions(partitions2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(msgs1, msgs2...), nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []*types.Message{estimatedMsg}, nil
|
||||
}
|
||||
|
||||
partitions := bitfield.New()
|
||||
for _, partition := range parts {
|
||||
partitions.Set(uint64(partition))
|
||||
}
|
||||
|
||||
params := miner.CompactPartitionsParams{
|
||||
Deadline: deadline,
|
||||
Partitions: partitions,
|
||||
}
|
||||
|
||||
sp, err := actors.SerializeParams(¶ms)
|
||||
msgs, err := makeMsgForPartitions(partitions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("serializing params: %w", err)
|
||||
return xerrors.Errorf("failed to make messages: %w", err)
|
||||
}
|
||||
|
||||
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
|
||||
From: minfo.Worker,
|
||||
To: maddr,
|
||||
Method: builtin.MethodsMiner.CompactPartitions,
|
||||
Value: big.Zero(),
|
||||
Params: sp,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("mpool push: %w", err)
|
||||
// Actually send the messages if really-do-it provided, simulate otherwise
|
||||
if cctx.Bool("really-do-it") {
|
||||
smsgs, err := fullNodeAPI.MpoolBatchPushMessage(ctx, msgs, nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("mpool push: %w", err)
|
||||
}
|
||||
|
||||
if len(smsgs) == 1 {
|
||||
fmt.Printf("Requested compact partitions in message %s\n", smsgs[0].Cid())
|
||||
} else {
|
||||
fmt.Printf("Requested compact partitions in %d messages\n\n", len(smsgs))
|
||||
for _, v := range smsgs {
|
||||
fmt.Println(v.Cid())
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range smsgs {
|
||||
wait, err := fullNodeAPI.StateWaitMsg(ctx, v.Cid(), 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check it executed successfully
|
||||
if wait.Receipt.ExitCode.IsError() {
|
||||
fmt.Println(cctx.App.Writer, "compact partitions msg %s failed!", v.Cid())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Requested compact partitions in message %s\n", smsg.Cid())
|
||||
for i, v := range msgs {
|
||||
fmt.Printf("total of %d CompactPartitions msgs would be sent\n", len(msgs))
|
||||
|
||||
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
estMsg, err := fullNodeAPI.GasEstimateMessageGas(ctx, v, nil, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check it executed successfully
|
||||
if wait.Receipt.ExitCode.IsError() {
|
||||
fmt.Println(cctx.App.Writer, "compact partitions failed!")
|
||||
return err
|
||||
fmt.Printf("msg %d would cost up to %s\n", i+1, types.FIL(estMsg.RequiredFunds()))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
@@ -23,6 +24,7 @@ var ethCmd = &cli.Command{
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
checkTipsetsCmd,
|
||||
computeEthHashCmd,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -70,3 +72,36 @@ var checkTipsetsCmd = &cli.Command{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var computeEthHashCmd = &cli.Command{
|
||||
Name: "compute-eth-hash",
|
||||
Usage: "Compute the eth hash for a given message CID",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if cctx.NArg() != 1 {
|
||||
return lcli.IncorrectNumArgs(cctx)
|
||||
}
|
||||
|
||||
msg, err := messageFromString(cctx, cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *types.SignedMessage:
|
||||
tx, err := ethtypes.EthTxFromSignedEthMessage(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert from signed message: %w", err)
|
||||
}
|
||||
|
||||
tx.Hash, err = tx.TxHash()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to call TxHash: %w", err)
|
||||
}
|
||||
fmt.Println(tx.Hash)
|
||||
default:
|
||||
return fmt.Errorf("not a signed message")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
func withCategory(cat string, cmd *cli.Command) *cli.Command {
|
||||
cmd.Category = strings.ToUpper(cat)
|
||||
return cmd
|
||||
}
|
||||
|
||||
var indexesCmd = &cli.Command{
|
||||
Name: "indexes",
|
||||
Usage: "Commands related to managing sqlite indexes",
|
||||
HideHelpCommand: true,
|
||||
Subcommands: []*cli.Command{
|
||||
withCategory("msgindex", backfillMsgIndexCmd),
|
||||
withCategory("msgindex", pruneMsgIndexCmd),
|
||||
withCategory("txhash", backfillTxHashCmd),
|
||||
},
|
||||
}
|
||||
|
||||
var backfillMsgIndexCmd = &cli.Command{
|
||||
Name: "backfill-msgindex",
|
||||
Usage: "Backfill the msgindex.db for a number of epochs starting from a specified height",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "from",
|
||||
Value: 0,
|
||||
Usage: "height to start the backfill; uses the current head if omitted",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "epochs",
|
||||
Value: 1800,
|
||||
Usage: "number of epochs to backfill; defaults to 1800 (2 finalities)",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
curTs, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
startHeight := int64(cctx.Int("from"))
|
||||
if startHeight == 0 {
|
||||
startHeight = int64(curTs.Height()) - 1
|
||||
}
|
||||
epochs := cctx.Int("epochs")
|
||||
|
||||
basePath, err := homedir.Expand(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbPath := path.Join(basePath, "sqlite", "msgindex.db")
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: closing db: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
insertStmt, err := db.Prepare("INSERT OR IGNORE INTO messages (cid, tipset_cid, epoch) VALUES (?, ?, ?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var nrRowsAffected int64
|
||||
for i := 0; i < epochs; i++ {
|
||||
epoch := abi.ChainEpoch(startHeight - int64(i))
|
||||
|
||||
if i%100 == 0 {
|
||||
log.Infof("%d/%d processing epoch:%d, nrRowsAffected:%d", i, epochs, epoch, nrRowsAffected)
|
||||
}
|
||||
|
||||
ts, err := api.ChainGetTipSetByHeight(ctx, epoch, curTs.Key())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tipset at epoch %d: %w", epoch, err)
|
||||
}
|
||||
|
||||
tsCid, err := ts.Key().Cid()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tipset cid at epoch %d: %w", epoch, err)
|
||||
}
|
||||
|
||||
msgs, err := api.ChainGetMessagesInTipset(ctx, ts.Key())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get messages in tipset at epoch %d: %w", epoch, err)
|
||||
}
|
||||
|
||||
for _, msg := range msgs {
|
||||
key := msg.Cid.String()
|
||||
tskey := tsCid.String()
|
||||
res, err := insertStmt.Exec(key, tskey, int64(epoch))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert message cid %s in tipset %s at epoch %d: %w", key, tskey, epoch, err)
|
||||
}
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get rows affected for message cid %s in tipset %s at epoch %d: %w", key, tskey, epoch, err)
|
||||
}
|
||||
nrRowsAffected += rowsAffected
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Done backfilling, nrRowsAffected:%d", nrRowsAffected)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var pruneMsgIndexCmd = &cli.Command{
|
||||
Name: "prune-msgindex",
|
||||
Usage: "Prune the msgindex.db for messages included before a given epoch",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "from",
|
||||
Usage: "height to start the prune; if negative it indicates epochs from current head",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
startHeight := int64(cctx.Int("from"))
|
||||
if startHeight < 0 {
|
||||
curTs, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
startHeight += int64(curTs.Height())
|
||||
|
||||
if startHeight < 0 {
|
||||
return xerrors.Errorf("bogus start height %d", startHeight)
|
||||
}
|
||||
}
|
||||
|
||||
basePath, err := homedir.Expand(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbPath := path.Join(basePath, "sqlite", "msgindex.db")
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: closing db: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec("DELETE FROM messages WHERE epoch < ?", startHeight); err != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
fmt.Printf("ERROR: rollback: %s", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var backfillTxHashCmd = &cli.Command{
|
||||
Name: "backfill-txhash",
|
||||
Usage: "Backfills the txhash.db for a number of epochs starting from a specified height",
|
||||
Flags: []cli.Flag{
|
||||
&cli.UintFlag{
|
||||
Name: "from",
|
||||
Value: 0,
|
||||
Usage: "the tipset height to start backfilling from (0 is head of chain)",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "epochs",
|
||||
Value: 2000,
|
||||
Usage: "the number of epochs to backfill",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
curTs, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
startHeight := int64(cctx.Int("from"))
|
||||
if startHeight == 0 {
|
||||
startHeight = int64(curTs.Height()) - 1
|
||||
}
|
||||
|
||||
epochs := cctx.Int("epochs")
|
||||
|
||||
basePath, err := homedir.Expand(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(basePath, "sqlite", "txhash.db")
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: closing db: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
insertStmt, err := db.Prepare("INSERT OR IGNORE INTO eth_tx_hashes(hash, cid) VALUES(?, ?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var totalRowsAffected int64 = 0
|
||||
for i := 0; i < epochs; i++ {
|
||||
epoch := abi.ChainEpoch(startHeight - int64(i))
|
||||
|
||||
select {
|
||||
case <-cctx.Done():
|
||||
fmt.Println("request cancelled")
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
curTsk := curTs.Parents()
|
||||
execTs, err := api.ChainGetTipSet(ctx, curTsk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to call ChainGetTipSet for %s: %w", curTsk, err)
|
||||
}
|
||||
|
||||
if i%100 == 0 {
|
||||
log.Infof("%d/%d processing epoch:%d", i, epochs, epoch)
|
||||
}
|
||||
|
||||
for _, blockheader := range execTs.Blocks() {
|
||||
blkMsgs, err := api.ChainGetBlockMessages(ctx, blockheader.Cid())
|
||||
if err != nil {
|
||||
log.Infof("Could not get block messages at epoch: %d, stopping walking up the chain", epoch)
|
||||
epochs = i
|
||||
break
|
||||
}
|
||||
|
||||
for _, smsg := range blkMsgs.SecpkMessages {
|
||||
if smsg.Signature.Type != crypto.SigTypeDelegated {
|
||||
continue
|
||||
}
|
||||
|
||||
tx, err := ethtypes.EthTxFromSignedEthMessage(smsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert from signed message: %w at epoch: %d", err, epoch)
|
||||
}
|
||||
|
||||
tx.Hash, err = tx.TxHash()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate hash for ethTx: %w at epoch: %d", err, epoch)
|
||||
}
|
||||
|
||||
res, err := insertStmt.Exec(tx.Hash.String(), smsg.Cid().String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error inserting tx mapping to db: %s at epoch: %d", err, epoch)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting rows affected: %s at epoch: %d", err, epoch)
|
||||
}
|
||||
|
||||
if rowsAffected > 0 {
|
||||
log.Debugf("Inserted txhash %s, cid: %s at epoch: %d", tx.Hash.String(), smsg.Cid().String(), epoch)
|
||||
}
|
||||
|
||||
totalRowsAffected += rowsAffected
|
||||
}
|
||||
}
|
||||
|
||||
curTs = execTs
|
||||
}
|
||||
|
||||
log.Infof("Done, inserted %d missing txhashes", totalRowsAffected)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
+17
-2
@@ -1,8 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -84,7 +86,7 @@ func main() {
|
||||
invariantsCmd,
|
||||
gasTraceCmd,
|
||||
replayOfflineCmd,
|
||||
msgindexCmd,
|
||||
indexesCmd,
|
||||
FevmAnalyticsCmd,
|
||||
mismatchesCmd,
|
||||
}
|
||||
@@ -118,7 +120,20 @@ func main() {
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
// terminate early on ctrl+c
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
<-c
|
||||
cancel()
|
||||
fmt.Println("Received interrupt, shutting down... Press CTRL+C again to force shutdown")
|
||||
<-c
|
||||
fmt.Println("Forcing stop")
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
if err := app.RunContext(ctx, os.Args); err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
var msgindexCmd = &cli.Command{
|
||||
Name: "msgindex",
|
||||
Usage: "Tools for managing the message index",
|
||||
Subcommands: []*cli.Command{
|
||||
msgindexBackfillCmd,
|
||||
msgindexPruneCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var msgindexBackfillCmd = &cli.Command{
|
||||
Name: "backfill",
|
||||
Usage: "Backfill the message index for a number of epochs starting from a specified height",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "from",
|
||||
Value: 0,
|
||||
Usage: "height to start the backfill; uses the current head if omitted",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "epochs",
|
||||
Value: 1800,
|
||||
Usage: "number of epochs to backfill; defaults to 1800 (2 finalities)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "repo",
|
||||
Value: "~/.lotus",
|
||||
Usage: "path to the repo",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
curTs, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
startHeight := int64(cctx.Int("from"))
|
||||
if startHeight == 0 {
|
||||
startHeight = int64(curTs.Height()) - 1
|
||||
}
|
||||
epochs := cctx.Int("epochs")
|
||||
|
||||
basePath, err := homedir.Expand(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbPath := path.Join(basePath, "sqlite", "msgindex.db")
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: closing db: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
insertStmt, err := tx.Prepare("INSERT INTO messages VALUES (?, ?, ?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
insertMsg := func(cid, tsCid cid.Cid, epoch abi.ChainEpoch) error {
|
||||
key := cid.String()
|
||||
tskey := tsCid.String()
|
||||
if _, err := insertStmt.Exec(key, tskey, int64(epoch)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
rollback := func() {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
fmt.Printf("ERROR: rollback: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < epochs; i++ {
|
||||
epoch := abi.ChainEpoch(startHeight - int64(i))
|
||||
|
||||
ts, err := api.ChainGetTipSetByHeight(ctx, epoch, curTs.Key())
|
||||
if err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
tsCid, err := ts.Key().Cid()
|
||||
if err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
msgs, err := api.ChainGetMessagesInTipset(ctx, ts.Key())
|
||||
if err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
for _, msg := range msgs {
|
||||
if err := insertMsg(msg.Cid, tsCid, epoch); err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var msgindexPruneCmd = &cli.Command{
|
||||
Name: "prune",
|
||||
Usage: "Prune the message index for messages included before a given epoch",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "from",
|
||||
Usage: "height to start the prune; if negative it indicates epochs from current head",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "repo",
|
||||
Value: "~/.lotus",
|
||||
Usage: "path to the repo",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
startHeight := int64(cctx.Int("from"))
|
||||
if startHeight < 0 {
|
||||
curTs, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
startHeight += int64(curTs.Height())
|
||||
|
||||
if startHeight < 0 {
|
||||
return xerrors.Errorf("bogus start height %d", startHeight)
|
||||
}
|
||||
}
|
||||
|
||||
basePath, err := homedir.Expand(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbPath := path.Join(basePath, "sqlite", "msgindex.db")
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: closing db: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec("DELETE FROM messages WHERE epoch < ?", startHeight); err != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
fmt.Printf("ERROR: rollback: %s", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -653,7 +653,7 @@ fr32 padding is removed from the output.`,
|
||||
return xerrors.Errorf("getting reader: %w", err)
|
||||
}
|
||||
|
||||
rd, err := readStarter(0)
|
||||
rd, err := readStarter(0, storiface.PaddedByteIndex(length))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("starting reader: %w", err)
|
||||
}
|
||||
|
||||
+149
-1
@@ -16,9 +16,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/DataDog/zstd"
|
||||
"github.com/ipfs/go-cid"
|
||||
levelds "github.com/ipfs/go-ds-leveldb"
|
||||
metricsprom "github.com/ipfs/go-metrics-prometheus"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
ldbopts "github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.opencensus.io/plugin/runmetrics"
|
||||
"go.opencensus.io/stats"
|
||||
@@ -27,13 +30,19 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
"gopkg.in/cheggaaa/pb.v1"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
cborutil "github.com/filecoin-project/go-cbor-util"
|
||||
"github.com/filecoin-project/go-jsonrpc"
|
||||
"github.com/filecoin-project/go-paramfetch"
|
||||
"github.com/filecoin-project/go-state-types/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
|
||||
lapi "github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/consensus"
|
||||
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
||||
"github.com/filecoin-project/lotus/chain/gen/slashfilter"
|
||||
"github.com/filecoin-project/lotus/chain/index"
|
||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
@@ -160,6 +169,19 @@ var DaemonCmd = &cli.Command{
|
||||
Name: "restore-config",
|
||||
Usage: "config file to use when restoring from backup",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "slash-consensus",
|
||||
Usage: "Report consensus fault",
|
||||
Value: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "slasher-sender",
|
||||
Usage: "optionally specify the account to report consensus from",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "slashdb-dir",
|
||||
Value: "slash watch db dir path",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
isLite := cctx.Bool("lite")
|
||||
@@ -380,7 +402,14 @@ var DaemonCmd = &cli.Command{
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start json-rpc endpoint: %s", err)
|
||||
}
|
||||
|
||||
if cctx.IsSet("slash-consensus") && cctx.IsSet("slashdb-dir") {
|
||||
go func() {
|
||||
err := slashConsensus(api, cctx.String("slashdb-dir"), cctx.String("slasher-sender"))
|
||||
if err != nil {
|
||||
panic("slashConsensus error")
|
||||
}
|
||||
}()
|
||||
}
|
||||
// Monitor for shutdown.
|
||||
finishCh := node.MonitorShutdown(shutdownChan,
|
||||
node.ShutdownHandler{Component: "rpc server", StopFunc: rpcStopper},
|
||||
@@ -574,3 +603,122 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func slashConsensus(a lapi.FullNode, p string, from string) error {
|
||||
ctx := context.Background()
|
||||
var fromAddr address.Address
|
||||
|
||||
ds, err := levelds.NewDatastore(p, &levelds.Options{
|
||||
Compression: ldbopts.NoCompression,
|
||||
NoSync: false,
|
||||
Strict: ldbopts.StrictAll,
|
||||
ReadOnly: false,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("open leveldb: %w", err)
|
||||
}
|
||||
sf := slashfilter.New(ds)
|
||||
if from == "" {
|
||||
defaddr, err := a.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fromAddr = defaddr
|
||||
} else {
|
||||
addr, err := address.NewFromString(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromAddr = addr
|
||||
}
|
||||
|
||||
blocks, err := a.SyncIncomingBlocks(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("sync incoming blocks failed: %w", err)
|
||||
}
|
||||
for block := range blocks {
|
||||
log.Infof("deal with block: %d, %v, %s", block.Height, block.Miner, block.Cid())
|
||||
if otherBlock, extraBlock, err := slashFilterMinedBlock(ctx, sf, a, block); err != nil {
|
||||
if otherBlock == nil {
|
||||
continue
|
||||
}
|
||||
log.Errorf("<!!> SLASH FILTER ERROR: %s", err)
|
||||
bh1, err := cborutil.Dump(otherBlock)
|
||||
if err != nil {
|
||||
log.Errorf("could not dump otherblock:%s, err:%s", otherBlock.Cid(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
bh2, err := cborutil.Dump(block)
|
||||
if err != nil {
|
||||
log.Errorf("could not dump block:%s, err:%s", block.Cid(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
params := miner.ReportConsensusFaultParams{
|
||||
BlockHeader1: bh1,
|
||||
BlockHeader2: bh2,
|
||||
}
|
||||
if extraBlock != nil {
|
||||
be, err := cborutil.Dump(extraBlock)
|
||||
if err != nil {
|
||||
log.Errorf("could not dump block:%s, err:%s", block.Cid(), err)
|
||||
continue
|
||||
}
|
||||
params.BlockHeaderExtra = be
|
||||
}
|
||||
|
||||
enc, err := actors.SerializeParams(¶ms)
|
||||
if err != nil {
|
||||
log.Errorf("could not serialize declare faults parameters: %s", err)
|
||||
continue
|
||||
}
|
||||
message, err := a.MpoolPushMessage(ctx, &types.Message{
|
||||
To: block.Miner,
|
||||
From: fromAddr,
|
||||
Value: types.NewInt(0),
|
||||
Method: builtin.MethodsMiner.ReportConsensusFault,
|
||||
Params: enc,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
log.Errorf("ReportConsensusFault to messagepool error:%w", err)
|
||||
continue
|
||||
}
|
||||
log.Infof("ReportConsensusFault message CID:%s", message.Cid())
|
||||
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func slashFilterMinedBlock(ctx context.Context, sf *slashfilter.SlashFilter, a lapi.FullNode, blockB *types.BlockHeader) (*types.BlockHeader, *types.BlockHeader, error) {
|
||||
blockC, err := a.ChainGetBlock(ctx, blockB.Parents[0])
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("chain get block error:%s", err)
|
||||
}
|
||||
otherCid, err := sf.MinedBlock(ctx, blockB, blockC.Height)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("slash filter check block error:%s", err)
|
||||
}
|
||||
if otherCid != cid.Undef {
|
||||
otherHeader, err := a.ChainGetBlock(ctx, otherCid)
|
||||
return otherHeader, nil, xerrors.Errorf("chain get other block error:%s", err)
|
||||
}
|
||||
blockA, err := a.ChainGetBlock(ctx, otherCid)
|
||||
|
||||
// (c) parent-grinding fault
|
||||
// Here extra is the "witness", a third block that shows the connection between A and B as
|
||||
// A's sibling and B's parent.
|
||||
// Specifically, since A is of lower height, it must be that B was mined omitting A from its tipset
|
||||
//
|
||||
// B
|
||||
// |
|
||||
// [A, C]
|
||||
if types.CidArrsEqual(blockA.Parents, blockC.Parents) && blockA.Height == blockC.Height &&
|
||||
types.CidArrsContains(blockB.Parents, blockC.Cid()) && !types.CidArrsContains(blockB.Parents, blockA.Cid()) {
|
||||
return blockA, blockC, xerrors.Errorf("chain get other block error:%s", err)
|
||||
}
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
@@ -4202,7 +4202,7 @@ Inputs:
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"Channel": "\u003cempty\u003e",
|
||||
"Channel": "f01234",
|
||||
"From": "f01234",
|
||||
"To": "f01234",
|
||||
"ConfirmedAmt": "0",
|
||||
@@ -4233,7 +4233,7 @@ Inputs:
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"Channel": "\u003cempty\u003e",
|
||||
"Channel": "f01234",
|
||||
"From": "f01234",
|
||||
"To": "f01234",
|
||||
"ConfirmedAmt": "0",
|
||||
@@ -4953,7 +4953,7 @@ Response:
|
||||
},
|
||||
"Nonce": 42,
|
||||
"Balance": "0",
|
||||
"Address": "\u003cempty\u003e"
|
||||
"Address": "f01234"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -5242,7 +5242,7 @@ Response:
|
||||
},
|
||||
"Nonce": 42,
|
||||
"Balance": "0",
|
||||
"Address": "\u003cempty\u003e"
|
||||
"Address": "f01234"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -5985,6 +5985,7 @@ Response:
|
||||
"SectorSize": 34359738368,
|
||||
"WindowPoStPartitionSectors": 42,
|
||||
"ConsensusFaultElapsed": 10101,
|
||||
"PendingOwnerAddress": "f01234",
|
||||
"Beneficiary": "f01234",
|
||||
"BeneficiaryTerm": {
|
||||
"Quota": "0",
|
||||
|
||||
@@ -5576,7 +5576,7 @@ Inputs:
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"Channel": "\u003cempty\u003e",
|
||||
"Channel": "f01234",
|
||||
"From": "f01234",
|
||||
"To": "f01234",
|
||||
"ConfirmedAmt": "0",
|
||||
@@ -5607,7 +5607,7 @@ Inputs:
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"Channel": "\u003cempty\u003e",
|
||||
"Channel": "f01234",
|
||||
"From": "f01234",
|
||||
"To": "f01234",
|
||||
"ConfirmedAmt": "0",
|
||||
@@ -6390,7 +6390,7 @@ Response:
|
||||
},
|
||||
"Nonce": 42,
|
||||
"Balance": "0",
|
||||
"Address": "\u003cempty\u003e"
|
||||
"Address": "f01234"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -6730,7 +6730,7 @@ Response:
|
||||
},
|
||||
"Nonce": 42,
|
||||
"Balance": "0",
|
||||
"Address": "\u003cempty\u003e"
|
||||
"Address": "f01234"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -7505,6 +7505,7 @@ Response:
|
||||
"SectorSize": 34359738368,
|
||||
"WindowPoStPartitionSectors": 42,
|
||||
"ConsensusFaultElapsed": 10101,
|
||||
"PendingOwnerAddress": "f01234",
|
||||
"Beneficiary": "f01234",
|
||||
"BeneficiaryTerm": {
|
||||
"Quota": "0",
|
||||
|
||||
+247
-269
File diff suppressed because it is too large
Load Diff
@@ -19,12 +19,11 @@ COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--enable-gpu-proving enable use of GPU for mining operations (default: true) [$LOTUS_WORKER_ENABLE_GPU_PROVING]
|
||||
--help, -h show help (default: false)
|
||||
--miner-repo value, --storagerepo value Specify miner repo path. flag storagerepo and env LOTUS_STORAGE_PATH are DEPRECATION, will REMOVE SOON (default: "~/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH]
|
||||
--version, -v print the version (default: false)
|
||||
--worker-repo value, --workerrepo value Specify worker repo path. flag workerrepo and env WORKER_PATH are DEPRECATION, will REMOVE SOON (default: "~/.lotusworker") [$LOTUS_WORKER_PATH, $WORKER_PATH]
|
||||
|
||||
--miner-repo value, --storagerepo value Specify miner repo path. flag storagerepo and env LOTUS_STORAGE_PATH are DEPRECATION, will REMOVE SOON (default: "~/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH]
|
||||
--enable-gpu-proving enable use of GPU for mining operations (default: true) [$LOTUS_WORKER_ENABLE_GPU_PROVING]
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
## lotus-worker run
|
||||
@@ -36,29 +35,29 @@ USAGE:
|
||||
lotus-worker run [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--addpiece enable addpiece (default: true) [$LOTUS_WORKER_ADDPIECE]
|
||||
--commit enable commit (default: true) [$LOTUS_WORKER_COMMIT]
|
||||
--data-cid Run the data-cid task. true|false (default: inherits --addpiece)
|
||||
--http-server-timeout value (default: "30s")
|
||||
--listen value host address and port the worker api will listen on (default: "0.0.0.0:3456") [$LOTUS_WORKER_LISTEN]
|
||||
--name value custom worker name (default: hostname) [$LOTUS_WORKER_NAME]
|
||||
--no-default disable all default compute tasks, use the worker for storage/fetching only (default: false) [$LOTUS_WORKER_NO_DEFAULT]
|
||||
--no-local-storage don't use storageminer repo for sector storage (default: false) [$LOTUS_WORKER_NO_LOCAL_STORAGE]
|
||||
--no-swap don't use swap (default: false) [$LOTUS_WORKER_NO_SWAP]
|
||||
--name value custom worker name (default: hostname) [$LOTUS_WORKER_NAME]
|
||||
--addpiece enable addpiece (default: true) [$LOTUS_WORKER_ADDPIECE]
|
||||
--precommit1 enable precommit1 (default: true) [$LOTUS_WORKER_PRECOMMIT1]
|
||||
--unseal enable unsealing (default: true) [$LOTUS_WORKER_UNSEAL]
|
||||
--precommit2 enable precommit2 (default: true) [$LOTUS_WORKER_PRECOMMIT2]
|
||||
--commit enable commit (default: true) [$LOTUS_WORKER_COMMIT]
|
||||
--replica-update enable replica update (default: true) [$LOTUS_WORKER_REPLICA_UPDATE]
|
||||
--prove-replica-update2 enable prove replica update 2 (default: true) [$LOTUS_WORKER_PROVE_REPLICA_UPDATE2]
|
||||
--regen-sector-key enable regen sector key (default: true) [$LOTUS_WORKER_REGEN_SECTOR_KEY]
|
||||
--sector-download enable external sector data download (default: false) [$LOTUS_WORKER_SECTOR_DOWNLOAD]
|
||||
--windowpost enable window post (default: false) [$LOTUS_WORKER_WINDOWPOST]
|
||||
--winningpost enable winning post (default: false) [$LOTUS_WORKER_WINNINGPOST]
|
||||
--no-default disable all default compute tasks, use the worker for storage/fetching only (default: false) [$LOTUS_WORKER_NO_DEFAULT]
|
||||
--parallel-fetch-limit value maximum fetch operations to run in parallel (default: 5) [$LOTUS_WORKER_PARALLEL_FETCH_LIMIT]
|
||||
--post-parallel-reads value maximum number of parallel challenge reads (0 = no limit) (default: 32) [$LOTUS_WORKER_POST_PARALLEL_READS]
|
||||
--post-read-timeout value time limit for reading PoSt challenges (0 = no limit) (default: 0s) [$LOTUS_WORKER_POST_READ_TIMEOUT]
|
||||
--precommit1 enable precommit1 (default: true) [$LOTUS_WORKER_PRECOMMIT1]
|
||||
--precommit2 enable precommit2 (default: true) [$LOTUS_WORKER_PRECOMMIT2]
|
||||
--prove-replica-update2 enable prove replica update 2 (default: true) [$LOTUS_WORKER_PROVE_REPLICA_UPDATE2]
|
||||
--regen-sector-key enable regen sector key (default: true) [$LOTUS_WORKER_REGEN_SECTOR_KEY]
|
||||
--replica-update enable replica update (default: true) [$LOTUS_WORKER_REPLICA_UPDATE]
|
||||
--sector-download enable external sector data download (default: false) [$LOTUS_WORKER_SECTOR_DOWNLOAD]
|
||||
--timeout value used when 'listen' is unspecified. must be a valid duration recognized by golang's time.ParseDuration function (default: "30m") [$LOTUS_WORKER_TIMEOUT]
|
||||
--unseal enable unsealing (default: true) [$LOTUS_WORKER_UNSEAL]
|
||||
--windowpost enable window post (default: false) [$LOTUS_WORKER_WINDOWPOST]
|
||||
--winningpost enable winning post (default: false) [$LOTUS_WORKER_WINNINGPOST]
|
||||
|
||||
--http-server-timeout value (default: "30s")
|
||||
--data-cid Run the data-cid task. true|false (default: inherits --addpiece)
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
## lotus-worker stop
|
||||
@@ -70,8 +69,7 @@ USAGE:
|
||||
lotus-worker stop [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help (default: false)
|
||||
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
## lotus-worker info
|
||||
@@ -83,8 +81,7 @@ USAGE:
|
||||
lotus-worker info [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help (default: false)
|
||||
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
## lotus-worker storage
|
||||
@@ -96,14 +93,13 @@ USAGE:
|
||||
lotus-worker storage command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
attach attach local storage path
|
||||
detach detach local storage path
|
||||
redeclare redeclare sectors in a local storage path
|
||||
help, h Shows a list of commands or help for one command
|
||||
attach attach local storage path
|
||||
detach detach local storage path
|
||||
redeclare redeclare sectors in a local storage path
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help (default: false)
|
||||
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
### lotus-worker storage attach
|
||||
@@ -115,14 +111,14 @@ USAGE:
|
||||
lotus-worker storage attach [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--allow-to value [ --allow-to value ] path groups allowed to pull data from this path (allow all if not specified)
|
||||
--groups value [ --groups value ] path group names
|
||||
--init initialize the path first (default: false)
|
||||
--max-storage value (for init) limit storage space for sectors (expensive for very large paths!)
|
||||
--weight value (for init) path weight (default: 10)
|
||||
--seal (for init) use path for sealing (default: false)
|
||||
--store (for init) use path for long-term storage (default: false)
|
||||
--weight value (for init) path weight (default: 10)
|
||||
|
||||
--max-storage value (for init) limit storage space for sectors (expensive for very large paths!)
|
||||
--groups value [ --groups value ] path group names
|
||||
--allow-to value [ --allow-to value ] path groups allowed to pull data from this path (allow all if not specified)
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
### lotus-worker storage detach
|
||||
@@ -135,7 +131,7 @@ USAGE:
|
||||
|
||||
OPTIONS:
|
||||
--really-do-it (default: false)
|
||||
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
### lotus-worker storage redeclare
|
||||
@@ -147,10 +143,10 @@ USAGE:
|
||||
lotus-worker storage redeclare [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--id value storage path ID
|
||||
--all redeclare all storage paths (default: false)
|
||||
--drop-missing Drop index entries with missing files (default: true)
|
||||
--id value storage path ID
|
||||
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
## lotus-worker resources
|
||||
@@ -162,9 +158,9 @@ USAGE:
|
||||
lotus-worker resources [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--all print all resource envvars (default: false)
|
||||
--default print default resource envvars (default: false)
|
||||
|
||||
--all print all resource envvars (default: false)
|
||||
--default print default resource envvars (default: false)
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
## lotus-worker tasks
|
||||
@@ -176,13 +172,12 @@ USAGE:
|
||||
lotus-worker tasks command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
enable Enable a task type
|
||||
disable Disable a task type
|
||||
help, h Shows a list of commands or help for one command
|
||||
enable Enable a task type
|
||||
disable Disable a task type
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help (default: false)
|
||||
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
### lotus-worker tasks enable
|
||||
@@ -194,8 +189,8 @@ USAGE:
|
||||
lotus-worker tasks enable [command options] --all | [UNS|C2|PC2|PC1|PR2|RU|AP|DC|GSK]
|
||||
|
||||
OPTIONS:
|
||||
--all Enable all task types (default: false)
|
||||
|
||||
--all Enable all task types (default: false)
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
### lotus-worker tasks disable
|
||||
@@ -207,6 +202,6 @@ USAGE:
|
||||
lotus-worker tasks disable [command options] --all | [UNS|C2|PC2|PC1|PR2|RU|AP|DC|GSK]
|
||||
|
||||
OPTIONS:
|
||||
--all Disable all task types (default: false)
|
||||
|
||||
--all Disable all task types (default: false)
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
+490
-585
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@
|
||||
#
|
||||
# type: []string
|
||||
# env var: LOTUS_LIBP2P_LISTENADDRESSES
|
||||
#ListenAddresses = ["/ip4/0.0.0.0/tcp/0", "/ip6/::/tcp/0"]
|
||||
#ListenAddresses = ["/ip4/0.0.0.0/tcp/0", "/ip6/::/tcp/0", "/ip4/0.0.0.0/udp/0/quic-v1", "/ip6/::/udp/0/quic-v1", "/ip4/0.0.0.0/udp/0/quic-v1/webtransport", "/ip6/::/udp/0/quic-v1/webtransport"]
|
||||
|
||||
# Addresses to explicitally announce to other peers. If not specified,
|
||||
# all interface addresses are announced
|
||||
@@ -391,6 +391,7 @@
|
||||
|
||||
|
||||
[Index]
|
||||
# EXPERIMENTAL FEATURE. USE WITH CAUTION
|
||||
# EnableMsgIndex enables indexing of messages on chain.
|
||||
#
|
||||
# type: bool
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
#
|
||||
# type: []string
|
||||
# env var: LOTUS_LIBP2P_LISTENADDRESSES
|
||||
#ListenAddresses = ["/ip4/0.0.0.0/tcp/0", "/ip6/::/tcp/0"]
|
||||
#ListenAddresses = ["/ip4/0.0.0.0/tcp/0", "/ip6/::/tcp/0", "/ip4/0.0.0.0/udp/0/quic-v1", "/ip6/::/udp/0/quic-v1", "/ip4/0.0.0.0/udp/0/quic-v1/webtransport", "/ip6/::/udp/0/quic-v1/webtransport"]
|
||||
|
||||
# Addresses to explicitally announce to other peers. If not specified,
|
||||
# all interface addresses are announced
|
||||
@@ -515,15 +515,7 @@
|
||||
# env var: LOTUS_SEALING_MINUPGRADESECTOREXPIRATION
|
||||
#MinUpgradeSectorExpiration = 0
|
||||
|
||||
# When set to a non-zero value, minimum number of epochs until sector expiration above which upgrade candidates will
|
||||
# be selected based on lowest initial pledge.
|
||||
#
|
||||
# Target sector expiration is calculated by looking at the input deal queue, sorting it by deal expiration, and
|
||||
# selecting N deals from the queue up to sector size. The target expiration will be Nth deal end epoch, or in case
|
||||
# where there weren't enough deals to fill a sector, DealMaxDuration (540 days = 1555200 epochs)
|
||||
#
|
||||
# Setting this to a high value (for example to maximum deal duration - 1555200) will disable selection based on
|
||||
# initial pledge - upgrade sectors will always be chosen based on longest expiration
|
||||
# DEPRECATED: Target expiration is no longer used
|
||||
#
|
||||
# type: uint64
|
||||
# env var: LOTUS_SEALING_MINTARGETUPGRADESECTOREXPIRATION
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
The gas balancing process targets to set gas costs of syscalls to be in line with
|
||||
10 gas per nanosecond on reference hardware.
|
||||
The process can be either performed for all syscalls based on existing messages and chain or targeted
|
||||
at single syscall.
|
||||
The process can be either performed for all syscalls based on existing messages and chains or targeted
|
||||
at a single syscall.
|
||||
|
||||
#### Reference hardware
|
||||
|
||||
@@ -12,14 +12,14 @@ may be subject to change.
|
||||
|
||||
### Complete gas balancing
|
||||
|
||||
Complete gas balancing is performed using `lotus-bench` the process is based on importing a chain export
|
||||
Complete gas balancing is performed using a `lotus-bench` the process is based on importing a chain export
|
||||
and collecting gas traces which are later aggregated.
|
||||
|
||||
Before building `lotus-bench` make sure `EnableDetailedTracing` in `chain/vm/runtime.go` is set to `true`.
|
||||
|
||||
The process can be started using `./lotus-bench import` with `--car` flag set to the location of
|
||||
CAR chain export. `--start-epoch` and `--end-epoch` can be used to to limit the range of epochs to run
|
||||
the benchmark. Note that state tree of `start-epoch` needs to be in the CAR file or has to be previously computed
|
||||
CAR chain export. `--start-epoch` and `--end-epoch` can be used to limit the range of epochs to run
|
||||
the benchmark. Note that the state tree of `start-epoch` needs to be in the CAR file or has to be previously computed
|
||||
to work.
|
||||
|
||||
The output will be a `bench.json` file containing information about every syscall invoked
|
||||
@@ -29,7 +29,7 @@ spare space.
|
||||
After the bench run is complete the `bench.json` file can be analyzed with `./lotus-bench import analyze bench.json`.
|
||||
|
||||
It will compute means, standard deviations and co-variances (when applicable) of syscall runtimes.
|
||||
The output is in nanoseconds, so the gas values for syscalls should be 10x that. In cases where co-variance of
|
||||
The output is in nanoseconds, so the gas values for syscalls should be 10x that. In cases where the co-variance of
|
||||
execution time to some parameter is evaluated, the strength of the correlation should be taken into account.
|
||||
|
||||
#### Special cases
|
||||
@@ -40,15 +40,15 @@ during block execution (when gas traces are formed) objects are only written to
|
||||
|
||||
### Targeted gas balancing
|
||||
|
||||
In some cases complete gas balancing is infeasible, either new syscall gets introduced or
|
||||
In some cases complete gas balancing is infeasible, either a new syscall gets introduced or
|
||||
complete balancing is too time consuming.
|
||||
|
||||
In these cases the recommended way to estimate gas for given syscall is to perform an `in-vivo` benchmark.
|
||||
In these cases, the recommended way to estimate gas for a given syscall is to perform an `in-vivo` benchmark.
|
||||
In the past `in-vitro` as in standalone benchmarks were found to be highly inaccurate when compared to results
|
||||
of real execution.
|
||||
|
||||
A in-vivo benchmark can be performed by running an example of such syscall during block execution.
|
||||
The best place to hook-in such benchmark is message execution loop in
|
||||
`chain/stmgr/stmgr.go` in `ApplyBlocks()`. Depending of time required to complete the syscall it might be
|
||||
An in-vivo benchmark can be performed by running an example of such a syscall during block execution.
|
||||
The best place to hook-in such a benchmark is the message execution loop in
|
||||
`chain/stmgr/stmgr.go` in `ApplyBlocks()`. Depending on the time required to complete the syscall it might be
|
||||
advisable to run the execution only once every few messages.
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ const (
|
||||
// TargetAPI defines the API methods that the Node depends on
|
||||
// (to make it easy to mock for tests)
|
||||
type TargetAPI interface {
|
||||
MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error)
|
||||
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
|
||||
MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*api.MiningBaseInfo, error)
|
||||
GasEstimateGasPremium(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error)
|
||||
StateReplay(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error)
|
||||
StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error)
|
||||
|
||||
@@ -23,6 +23,33 @@ import (
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
func (gw *Node) MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*types.SignedMessage, error) {
|
||||
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := gw.checkTipsetKey(ctx, tsk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gw.target.MpoolPending(ctx, tsk)
|
||||
}
|
||||
|
||||
func (gw *Node) ChainGetBlock(ctx context.Context, c cid.Cid) (*types.BlockHeader, error) {
|
||||
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gw.target.ChainGetBlock(ctx, c)
|
||||
}
|
||||
|
||||
func (gw *Node) MinerGetBaseInfo(ctx context.Context, addr address.Address, h abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) {
|
||||
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := gw.checkTipsetKey(ctx, tsk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gw.target.MinerGetBaseInfo(ctx, addr, h, tsk)
|
||||
}
|
||||
|
||||
func (gw *Node) StateReplay(ctx context.Context, tsk types.TipSetKey, c cid.Cid) (*api.InvocResult, error) {
|
||||
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -41,7 +41,7 @@ require (
|
||||
github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc7
|
||||
github.com/filecoin-project/go-fil-commcid v0.1.0
|
||||
github.com/filecoin-project/go-fil-commp-hashhash v0.1.0
|
||||
github.com/filecoin-project/go-fil-markets v1.28.1
|
||||
github.com/filecoin-project/go-fil-markets v1.28.2
|
||||
github.com/filecoin-project/go-jsonrpc v0.3.1
|
||||
github.com/filecoin-project/go-padreader v0.0.1
|
||||
github.com/filecoin-project/go-paramfetch v0.0.4
|
||||
@@ -116,7 +116,7 @@ require (
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/koalacxr/quantile v0.0.1
|
||||
github.com/libp2p/go-buffer-pool v0.1.0
|
||||
github.com/libp2p/go-libp2p v0.27.3
|
||||
github.com/libp2p/go-libp2p v0.27.5
|
||||
github.com/libp2p/go-libp2p-consensus v0.0.1
|
||||
github.com/libp2p/go-libp2p-gorpc v0.5.0
|
||||
github.com/libp2p/go-libp2p-kad-dht v0.21.1
|
||||
@@ -144,7 +144,7 @@ require (
|
||||
github.com/raulk/go-watchdog v1.3.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
github.com/urfave/cli/v2 v2.16.3
|
||||
github.com/urfave/cli/v2 v2.25.5
|
||||
github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba
|
||||
github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa
|
||||
github.com/whyrusleeping/ledger-filecoin-go v0.9.1-0.20201010031517-c3dcc1bddce4
|
||||
|
||||
@@ -321,8 +321,8 @@ github.com/filecoin-project/go-fil-commcid v0.1.0 h1:3R4ds1A9r6cr8mvZBfMYxTS88Oq
|
||||
github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ=
|
||||
github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 h1:imrrpZWEHRnNqqv0tN7LXep5bFEVOVmQWHJvl2mgsGo=
|
||||
github.com/filecoin-project/go-fil-commp-hashhash v0.1.0/go.mod h1:73S8WSEWh9vr0fDJVnKADhfIv/d6dCbAGaAGWbdJEI8=
|
||||
github.com/filecoin-project/go-fil-markets v1.28.1 h1:2iq1o/ZY1RSbVU8OX7nwWO66SYtdjKEngshc4Jeua/Y=
|
||||
github.com/filecoin-project/go-fil-markets v1.28.1/go.mod h1:qy9LNu9t77I184VB6Pa4WKRtGfB8Vl0t8zfOLHkDqWY=
|
||||
github.com/filecoin-project/go-fil-markets v1.28.2 h1:Ev9o8BYow+lo97Bwc6oOmZ2OxdiHeIDCQsfF/w/Vldc=
|
||||
github.com/filecoin-project/go-fil-markets v1.28.2/go.mod h1:qy9LNu9t77I184VB6Pa4WKRtGfB8Vl0t8zfOLHkDqWY=
|
||||
github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM=
|
||||
github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24=
|
||||
github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM=
|
||||
@@ -1005,8 +1005,8 @@ github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xS
|
||||
github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw=
|
||||
github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o=
|
||||
github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0=
|
||||
github.com/libp2p/go-libp2p v0.27.3 h1:tkV/zm3KCZ4R5er9Xcs2pt0YNB4JH0iBfGAtHJdLHRs=
|
||||
github.com/libp2p/go-libp2p v0.27.3/go.mod h1:FAvvfQa/YOShUYdiSS03IR9OXzkcJXwcNA2FUCh9ImE=
|
||||
github.com/libp2p/go-libp2p v0.27.5 h1:KwA7pXKXpz8hG6Cr1fMA7UkgleogcwQj0sxl5qquWRg=
|
||||
github.com/libp2p/go-libp2p v0.27.5/go.mod h1:oMfQGTb9CHnrOuSM6yMmyK2lXz3qIhnkn2+oK3B1Y2g=
|
||||
github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s=
|
||||
github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w=
|
||||
github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8=
|
||||
@@ -1630,8 +1630,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.0.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
|
||||
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||
github.com/urfave/cli/v2 v2.25.5 h1:d0NIAyhh5shGscroL7ek/Ya9QYQE0KNabJgiUinIQkc=
|
||||
github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
|
||||
@@ -48,9 +48,9 @@ func TestDMLevelPartialRetrieval(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
kit.QuietMiningLogs()
|
||||
client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC(), kit.MockProofs())
|
||||
client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC())
|
||||
dh := kit.NewDealHarness(t, client, miner, miner)
|
||||
ens.InterconnectAll().BeginMining(50 * time.Millisecond)
|
||||
ens.InterconnectAll().BeginMiningMustPost(50 * time.Millisecond)
|
||||
|
||||
_, err := client.ClientImport(ctx, api.FileRef{Path: adlFixtureCar, IsCAR: true})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -184,7 +184,8 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur
|
||||
|
||||
dlinfo, err := bm.miner.FullNode.StateMinerProvingDeadline(ctx, bm.miner.ActorAddr, ts.Key())
|
||||
require.NoError(bm.t, err)
|
||||
if ts.Height()+1+abi.ChainEpoch(nulls) >= dlinfo.Last() { // Next block brings us past the last epoch in dline, we need to wait for miner to post
|
||||
if ts.Height()+5+abi.ChainEpoch(nulls) >= dlinfo.Last() { // Next block brings us past the last epoch in dline, we need to wait for miner to post
|
||||
bm.t.Logf("forcing post to get in before deadline closes at %d", dlinfo.Last())
|
||||
bm.forcePoSt(ctx, ts, dlinfo)
|
||||
}
|
||||
|
||||
@@ -216,7 +217,8 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur
|
||||
}
|
||||
if !success {
|
||||
// if we are mining a new null block and it brings us past deadline boundary we need to wait for miner to post
|
||||
if ts.Height()+1+abi.ChainEpoch(nulls+i) >= dlinfo.Last() {
|
||||
if ts.Height()+5+abi.ChainEpoch(nulls+i) >= dlinfo.Last() {
|
||||
bm.t.Logf("forcing post to get in before deadline closes at %d", dlinfo.Last())
|
||||
bm.forcePoSt(ctx, ts, dlinfo)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-7
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
@@ -220,14 +221,17 @@ func TestNetBlockIPAddr(t *testing.T) {
|
||||
firstAddrInfo, _ := firstNode.NetAddrsListen(ctx)
|
||||
secondAddrInfo, _ := secondNode.NetAddrsListen(ctx)
|
||||
|
||||
var secondNodeIPs []string
|
||||
|
||||
secondNodeIPsMap := map[string]struct{}{} // so we can deduplicate
|
||||
for _, addr := range secondAddrInfo.Addrs {
|
||||
ip, err := manet.ToIP(addr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
secondNodeIPs = append(secondNodeIPs, ip.String())
|
||||
secondNodeIPsMap[ip.String()] = struct{}{}
|
||||
}
|
||||
var secondNodeIPs []string
|
||||
for s := range secondNodeIPsMap {
|
||||
secondNodeIPs = append(secondNodeIPs, s)
|
||||
}
|
||||
|
||||
// Sanity check that we're not already connected somehow
|
||||
@@ -243,6 +247,8 @@ func TestNetBlockIPAddr(t *testing.T) {
|
||||
list, err := firstNode.NetBlockList(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Println(list.IPAddrs)
|
||||
fmt.Println(secondNodeIPs)
|
||||
require.Equal(t, len(list.IPAddrs), len(secondNodeIPs), "expected %d blocked IPs", len(secondNodeIPs))
|
||||
for _, blockedIP := range list.IPAddrs {
|
||||
found := false
|
||||
@@ -256,10 +262,14 @@ func TestNetBlockIPAddr(t *testing.T) {
|
||||
require.True(t, found, "blocked IP %s is not one of secondNodeIPs", blockedIP)
|
||||
}
|
||||
|
||||
require.Error(t, secondNode.NetConnect(ctx, firstAddrInfo), "shouldn't be able to connect to second node")
|
||||
connectedness, err = secondNode.NetConnectedness(ctx, firstAddrInfo.ID)
|
||||
require.NoError(t, err, "failed to determine connectedness")
|
||||
require.NotEqual(t, connectedness, network.Connected)
|
||||
// a QUIC connection might still succeed when gated, but will be killed right after the handshake
|
||||
_ = secondNode.NetConnect(ctx, firstAddrInfo)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
connectedness, err = secondNode.NetConnectedness(ctx, firstAddrInfo.ID)
|
||||
require.NoError(t, err, "failed to determine connectedness")
|
||||
return connectedness != network.Connected
|
||||
}, time.Second*5, time.Millisecond*10)
|
||||
|
||||
// stm: @NETWORK_COMMON_BLOCK_REMOVE_001
|
||||
err = firstNode.NetBlockRemove(ctx, api.NetBlockList{IPAddrs: secondNodeIPs})
|
||||
|
||||
@@ -31,8 +31,8 @@ func TestDealsWithFinalizeEarly(t *testing.T) {
|
||||
var blockTime = 50 * time.Millisecond
|
||||
|
||||
// We use two miners so that in case the actively tested miner misses PoSt, we still have a blockchain
|
||||
client, miner, _, ens := kit.EnsembleOneTwo(t, kit.ThroughRPC(), kit.MutateSealingConfig(func(sc *config.SealingConfig) { sc.FinalizeEarly = true })) // no mock proofs.
|
||||
ens.InterconnectAll().BeginMining(blockTime)
|
||||
client, miner, poster, ens := kit.EnsembleOneTwo(t, kit.ThroughRPC(), kit.MutateSealingConfig(func(sc *config.SealingConfig) { sc.FinalizeEarly = true })) // no mock proofs.
|
||||
ens.InterconnectAll().BeginMiningMustPost(blockTime, poster)
|
||||
dh := kit.NewDealHarness(t, client, miner, miner)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package itests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -13,13 +14,17 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
miner11 "github.com/filecoin-project/go-state-types/builtin/v11/miner"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/itests/kit"
|
||||
"github.com/filecoin-project/lotus/node"
|
||||
"github.com/filecoin-project/lotus/node/config"
|
||||
"github.com/filecoin-project/lotus/node/impl"
|
||||
"github.com/filecoin-project/lotus/node/modules"
|
||||
"github.com/filecoin-project/lotus/node/repo"
|
||||
"github.com/filecoin-project/lotus/storage/paths"
|
||||
sealing "github.com/filecoin-project/lotus/storage/pipeline"
|
||||
@@ -500,3 +505,228 @@ func TestWorkerName(t *testing.T) {
|
||||
|
||||
require.True(t, found)
|
||||
}
|
||||
|
||||
// Tests that V1_1 proofs on post workers with faults
|
||||
func TestWindowPostV1P1NV20WorkerFault(t *testing.T) {
|
||||
kit.QuietMiningLogs()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
blocktime := 2 * time.Millisecond
|
||||
|
||||
sectors := 2 * 48 * 2
|
||||
var badsector uint64 = 100000
|
||||
|
||||
client, miner, _, ens := kit.EnsembleWorker(t,
|
||||
kit.PresealSectors(sectors), // 2 sectors per partition, 2 partitions in all 48 deadlines
|
||||
kit.GenesisNetworkVersion(network.Version20),
|
||||
kit.ConstructorOpts(
|
||||
node.Override(new(config.ProvingConfig), func() config.ProvingConfig {
|
||||
c := config.DefaultStorageMiner()
|
||||
c.Proving.DisableBuiltinWindowPoSt = true
|
||||
return c.Proving
|
||||
}),
|
||||
node.Override(new(*wdpost.WindowPoStScheduler), modules.WindowPostScheduler(
|
||||
config.DefaultStorageMiner().Fees,
|
||||
config.ProvingConfig{
|
||||
DisableBuiltinWindowPoSt: true,
|
||||
DisableBuiltinWinningPoSt: false,
|
||||
DisableWDPoStPreChecks: false,
|
||||
},
|
||||
)),
|
||||
node.Override(new(paths.Store), func(store *paths.Remote) paths.Store {
|
||||
return &badWorkerStorage{
|
||||
Store: store,
|
||||
badsector: &badsector,
|
||||
notBadCount: 1,
|
||||
}
|
||||
})),
|
||||
kit.ThroughRPC(),
|
||||
kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWindowPoSt}),
|
||||
kit.WithWorkerStorage(func(store paths.Store) paths.Store {
|
||||
return &badWorkerStorage{
|
||||
Store: store,
|
||||
badsector: &badsector,
|
||||
}
|
||||
}))
|
||||
|
||||
bm := ens.InterconnectAll().BeginMining(blocktime)[0]
|
||||
|
||||
maddr, err := miner.ActorAddress(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// wait for sectors to be committed
|
||||
require.Eventually(t, func() bool {
|
||||
di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
parts, err := client.StateMinerPartitions(ctx, maddr, di.Index, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
return len(parts) > 1
|
||||
}, 30*time.Second, 100*time.Millisecond)
|
||||
|
||||
// Wait until just before a deadline opens
|
||||
{
|
||||
di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
di = di.NextNotElapsed()
|
||||
|
||||
t.Log("Running one proving period")
|
||||
waitUntil := di.Open + di.WPoStChallengeWindow - di.WPoStChallengeLookback - 1
|
||||
client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil))
|
||||
|
||||
t.Log("Waiting for post message")
|
||||
bm.Stop()
|
||||
}
|
||||
|
||||
// Remove one sector in the next deadline (so it's skipped)
|
||||
{
|
||||
di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
parts, err := client.StateMinerPartitions(ctx, maddr, di.Index+1, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(parts), 0)
|
||||
|
||||
secs := parts[0].AllSectors
|
||||
n, err := secs.Count()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(2), n)
|
||||
|
||||
// Drop the sector in first partition
|
||||
sid, err := secs.First()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("Drop sector %d; dl %d part %d", sid, di.Index, 0)
|
||||
|
||||
atomic.StoreUint64(&badsector, sid)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
bm.MineBlocksMustPost(ctx, 2*time.Millisecond)
|
||||
|
||||
mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
en := wact.Nonce
|
||||
|
||||
// wait for a new message to be sent from worker address, it will be a PoSt
|
||||
|
||||
waitForProof:
|
||||
for {
|
||||
//stm: @CHAIN_STATE_GET_ACTOR_001
|
||||
wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
if wact.Nonce > en {
|
||||
break waitForProof
|
||||
}
|
||||
|
||||
build.Clock.Sleep(blocktime)
|
||||
}
|
||||
|
||||
slm, err := client.StateListMessages(ctx, &api.MessageMatch{To: maddr}, types.EmptyTSK, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
pmr, err := client.StateSearchMsg(ctx, types.EmptyTSK, slm[0], -1, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
nv, err := client.StateNetworkVersion(ctx, pmr.TipSet)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, network.Version20, nv)
|
||||
|
||||
require.True(t, pmr.Receipt.ExitCode.IsSuccess())
|
||||
|
||||
slmsg, err := client.ChainGetMessage(ctx, slm[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
var params miner11.SubmitWindowedPoStParams
|
||||
require.NoError(t, params.UnmarshalCBOR(bytes.NewBuffer(slmsg.Params)))
|
||||
require.Equal(t, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1, params.Proofs[0].PoStProof)
|
||||
|
||||
require.Len(t, params.Partitions, 2)
|
||||
sc0, err := params.Partitions[0].Skipped.Count()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), sc0)
|
||||
sc1, err := params.Partitions[1].Skipped.Count()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), sc1)
|
||||
}
|
||||
|
||||
// Tests that V1_1 proofs on post worker
|
||||
func TestWindowPostV1P1NV20Worker(t *testing.T) {
|
||||
kit.QuietMiningLogs()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
blocktime := 2 * time.Millisecond
|
||||
|
||||
client, miner, _, ens := kit.EnsembleWorker(t,
|
||||
kit.GenesisNetworkVersion(network.Version20),
|
||||
kit.ConstructorOpts(
|
||||
node.Override(new(config.ProvingConfig), func() config.ProvingConfig {
|
||||
c := config.DefaultStorageMiner()
|
||||
c.Proving.DisableBuiltinWindowPoSt = true
|
||||
return c.Proving
|
||||
}),
|
||||
node.Override(new(*wdpost.WindowPoStScheduler), modules.WindowPostScheduler(
|
||||
config.DefaultStorageMiner().Fees,
|
||||
config.ProvingConfig{
|
||||
DisableBuiltinWindowPoSt: true,
|
||||
DisableBuiltinWinningPoSt: false,
|
||||
DisableWDPoStPreChecks: false,
|
||||
},
|
||||
))),
|
||||
kit.ThroughRPC(),
|
||||
kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWindowPoSt}))
|
||||
|
||||
ens.InterconnectAll().BeginMining(blocktime)
|
||||
|
||||
maddr, err := miner.ActorAddress(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
en := wact.Nonce
|
||||
|
||||
// wait for a new message to be sent from worker address, it will be a PoSt
|
||||
|
||||
waitForProof:
|
||||
for {
|
||||
//stm: @CHAIN_STATE_GET_ACTOR_001
|
||||
wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
if wact.Nonce > en {
|
||||
break waitForProof
|
||||
}
|
||||
|
||||
build.Clock.Sleep(blocktime)
|
||||
}
|
||||
|
||||
slm, err := client.StateListMessages(ctx, &api.MessageMatch{To: maddr}, types.EmptyTSK, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
pmr, err := client.StateSearchMsg(ctx, types.EmptyTSK, slm[0], -1, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
nv, err := client.StateNetworkVersion(ctx, pmr.TipSet)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, network.Version20, nv)
|
||||
|
||||
require.True(t, pmr.Receipt.ExitCode.IsSuccess())
|
||||
|
||||
slmsg, err := client.ChainGetMessage(ctx, slm[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
var params miner11.SubmitWindowedPoStParams
|
||||
require.NoError(t, params.UnmarshalCBOR(bytes.NewBuffer(slmsg.Params)))
|
||||
require.Equal(t, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1, params.Proofs[0].PoStProof)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package readerutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewReadSeekerFromReaderAt returns a new io.ReadSeeker from a io.ReaderAt.
|
||||
// The returned io.ReadSeeker will read from the io.ReaderAt starting at the
|
||||
// given base offset.
|
||||
func NewReadSeekerFromReaderAt(readerAt io.ReaderAt, base int64) io.ReadSeeker {
|
||||
return &readSeekerFromReaderAt{
|
||||
readerAt: readerAt,
|
||||
base: base,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
type readSeekerFromReaderAt struct {
|
||||
readerAt io.ReaderAt
|
||||
base int64
|
||||
pos int64
|
||||
}
|
||||
|
||||
func (rs *readSeekerFromReaderAt) Read(p []byte) (n int, err error) {
|
||||
n, err = rs.readerAt.ReadAt(p, rs.pos+rs.base)
|
||||
rs.pos += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (rs *readSeekerFromReaderAt) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
rs.pos = offset
|
||||
case io.SeekCurrent:
|
||||
rs.pos += offset
|
||||
case io.SeekEnd:
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
default:
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
return rs.pos, nil
|
||||
}
|
||||
+42
-2
@@ -61,6 +61,10 @@ var (
|
||||
|
||||
// vm execution
|
||||
ExecutionLane, _ = tag.NewKey("lane")
|
||||
|
||||
// piecereader
|
||||
PRReadType, _ = tag.NewKey("pr_type") // seq / rand
|
||||
PRReadSize, _ = tag.NewKey("pr_size") // small / big
|
||||
)
|
||||
|
||||
// Measures
|
||||
@@ -153,8 +157,9 @@ var (
|
||||
SchedCycleOpenWindows = stats.Int64("sched/assigner_cycle_open_window", "Number of open windows in scheduling cycles", stats.UnitDimensionless)
|
||||
SchedCycleQueueSize = stats.Int64("sched/assigner_cycle_task_queue_entry", "Number of task queue entries in scheduling cycles", stats.UnitDimensionless)
|
||||
|
||||
DagStorePRInitCount = stats.Int64("dagstore/pr_init_count", "PieceReader init count", stats.UnitDimensionless)
|
||||
DagStorePRBytesRequested = stats.Int64("dagstore/pr_requested_bytes", "PieceReader requested bytes", stats.UnitBytes)
|
||||
DagStorePRInitCount = stats.Int64("dagstore/pr_init_count", "PieceReader init count", stats.UnitDimensionless)
|
||||
DagStorePRBytesRequested = stats.Int64("dagstore/pr_requested_bytes", "PieceReader requested bytes", stats.UnitBytes)
|
||||
|
||||
DagStorePRBytesDiscarded = stats.Int64("dagstore/pr_discarded_bytes", "PieceReader discarded bytes", stats.UnitBytes)
|
||||
DagStorePRDiscardCount = stats.Int64("dagstore/pr_discard_count", "PieceReader discard count", stats.UnitDimensionless)
|
||||
DagStorePRSeekBackCount = stats.Int64("dagstore/pr_seek_back_count", "PieceReader seek back count", stats.UnitDimensionless)
|
||||
@@ -162,6 +167,12 @@ var (
|
||||
DagStorePRSeekBackBytes = stats.Int64("dagstore/pr_seek_back_bytes", "PieceReader seek back bytes", stats.UnitBytes)
|
||||
DagStorePRSeekForwardBytes = stats.Int64("dagstore/pr_seek_forward_bytes", "PieceReader seek forward bytes", stats.UnitBytes)
|
||||
|
||||
DagStorePRAtHitBytes = stats.Int64("dagstore/pr_at_hit_bytes", "PieceReader ReadAt bytes from cache", stats.UnitBytes)
|
||||
DagStorePRAtHitCount = stats.Int64("dagstore/pr_at_hit_count", "PieceReader ReadAt from cache hits", stats.UnitDimensionless)
|
||||
DagStorePRAtCacheFillCount = stats.Int64("dagstore/pr_at_cache_fill_count", "PieceReader ReadAt full cache fill count", stats.UnitDimensionless)
|
||||
DagStorePRAtReadBytes = stats.Int64("dagstore/pr_at_read_bytes", "PieceReader ReadAt bytes read from source", stats.UnitBytes) // PRReadSize tag
|
||||
DagStorePRAtReadCount = stats.Int64("dagstore/pr_at_read_count", "PieceReader ReadAt reads from source", stats.UnitDimensionless) // PRReadSize tag
|
||||
|
||||
// splitstore
|
||||
SplitstoreMiss = stats.Int64("splitstore/miss", "Number of misses in hotstre access", stats.UnitDimensionless)
|
||||
SplitstoreCompactionTimeSeconds = stats.Float64("splitstore/compaction_time", "Compaction time in seconds", stats.UnitSeconds)
|
||||
@@ -487,6 +498,7 @@ var (
|
||||
DagStorePRBytesRequestedView = &view.View{
|
||||
Measure: DagStorePRBytesRequested,
|
||||
Aggregation: view.Sum(),
|
||||
TagKeys: []tag.Key{PRReadType},
|
||||
}
|
||||
DagStorePRBytesDiscardedView = &view.View{
|
||||
Measure: DagStorePRBytesDiscarded,
|
||||
@@ -513,6 +525,29 @@ var (
|
||||
Aggregation: view.Sum(),
|
||||
}
|
||||
|
||||
DagStorePRAtHitBytesView = &view.View{
|
||||
Measure: DagStorePRAtHitBytes,
|
||||
Aggregation: view.Sum(),
|
||||
}
|
||||
DagStorePRAtHitCountView = &view.View{
|
||||
Measure: DagStorePRAtHitCount,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
DagStorePRAtCacheFillCountView = &view.View{
|
||||
Measure: DagStorePRAtCacheFillCount,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
DagStorePRAtReadBytesView = &view.View{
|
||||
Measure: DagStorePRAtReadBytes,
|
||||
Aggregation: view.Sum(),
|
||||
TagKeys: []tag.Key{PRReadSize},
|
||||
}
|
||||
DagStorePRAtReadCountView = &view.View{
|
||||
Measure: DagStorePRAtReadCount,
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{PRReadSize},
|
||||
}
|
||||
|
||||
// splitstore
|
||||
SplitstoreMissView = &view.View{
|
||||
Measure: SplitstoreMiss,
|
||||
@@ -779,6 +814,11 @@ var MinerNodeViews = append([]*view.View{
|
||||
DagStorePRSeekForwardCountView,
|
||||
DagStorePRSeekBackBytesView,
|
||||
DagStorePRSeekForwardBytesView,
|
||||
DagStorePRAtHitBytesView,
|
||||
DagStorePRAtHitCountView,
|
||||
DagStorePRAtCacheFillCountView,
|
||||
DagStorePRAtReadBytesView,
|
||||
DagStorePRAtReadCountView,
|
||||
}, DefaultViews...)
|
||||
|
||||
var GatewayNodeViews = append([]*view.View{
|
||||
|
||||
+1
-1
@@ -324,7 +324,7 @@ minerLoop:
|
||||
"block-time", btime, "time", build.Clock.Now(), "difference", build.Clock.Since(btime))
|
||||
}
|
||||
|
||||
if err := m.sf.MinedBlock(ctx, b.Header, base.TipSet.Height()+base.NullRounds); err != nil {
|
||||
if _, err = m.sf.MinedBlock(ctx, b.Header, base.TipSet.Height()+base.NullRounds); err != nil {
|
||||
log.Errorf("<!!> SLASH FILTER ERROR: %s", err)
|
||||
if os.Getenv("LOTUS_MINER_NO_SLASHFILTER") != "_yes_i_know_i_can_and_probably_will_lose_all_my_fil_and_power_" {
|
||||
continue
|
||||
|
||||
@@ -89,6 +89,7 @@ const (
|
||||
|
||||
// health checks
|
||||
CheckFDLimit
|
||||
CheckFvmConcurrency
|
||||
LegacyMarketsEOL
|
||||
|
||||
// libp2p
|
||||
@@ -165,6 +166,7 @@ func defaults() []Option {
|
||||
Override(new(dtypes.NodeStartTime), FromVal(dtypes.NodeStartTime(time.Now()))),
|
||||
|
||||
Override(CheckFDLimit, modules.CheckFdLimit(build.DefaultFDLimit)),
|
||||
Override(CheckFvmConcurrency, modules.CheckFvmConcurrency()),
|
||||
|
||||
Override(new(system.MemoryConstraints), modules.MemoryConstraints),
|
||||
Override(InitMemoryWatchdog, modules.MemoryWatchdog),
|
||||
|
||||
@@ -57,6 +57,10 @@ func defCommon() Common {
|
||||
ListenAddresses: []string{
|
||||
"/ip4/0.0.0.0/tcp/0",
|
||||
"/ip6/::/tcp/0",
|
||||
"/ip4/0.0.0.0/udp/0/quic-v1",
|
||||
"/ip6/::/udp/0/quic-v1",
|
||||
"/ip4/0.0.0.0/udp/0/quic-v1/webtransport",
|
||||
"/ip6/::/udp/0/quic-v1/webtransport",
|
||||
},
|
||||
AnnounceAddresses: []string{},
|
||||
NoAnnounceAddresses: []string{},
|
||||
|
||||
+3
-10
@@ -473,7 +473,8 @@ Set to 0 to keep all mappings`,
|
||||
Name: "EnableMsgIndex",
|
||||
Type: "bool",
|
||||
|
||||
Comment: `EnableMsgIndex enables indexing of messages on chain.`,
|
||||
Comment: `EXPERIMENTAL FEATURE. USE WITH CAUTION
|
||||
EnableMsgIndex enables indexing of messages on chain.`,
|
||||
},
|
||||
},
|
||||
"IndexProviderConfig": []DocField{
|
||||
@@ -1106,15 +1107,7 @@ required to have expiration of at least the soonest-ending deal`,
|
||||
Name: "MinTargetUpgradeSectorExpiration",
|
||||
Type: "uint64",
|
||||
|
||||
Comment: `When set to a non-zero value, minimum number of epochs until sector expiration above which upgrade candidates will
|
||||
be selected based on lowest initial pledge.
|
||||
|
||||
Target sector expiration is calculated by looking at the input deal queue, sorting it by deal expiration, and
|
||||
selecting N deals from the queue up to sector size. The target expiration will be Nth deal end epoch, or in case
|
||||
where there weren't enough deals to fill a sector, DealMaxDuration (540 days = 1555200 epochs)
|
||||
|
||||
Setting this to a high value (for example to maximum deal duration - 1555200) will disable selection based on
|
||||
initial pledge - upgrade sectors will always be chosen based on longest expiration`,
|
||||
Comment: `DEPRECATED: Target expiration is no longer used`,
|
||||
},
|
||||
{
|
||||
Name: "CommittedCapacitySectorLifetime",
|
||||
|
||||
@@ -352,15 +352,7 @@ type SealingConfig struct {
|
||||
// required to have expiration of at least the soonest-ending deal
|
||||
MinUpgradeSectorExpiration uint64
|
||||
|
||||
// When set to a non-zero value, minimum number of epochs until sector expiration above which upgrade candidates will
|
||||
// be selected based on lowest initial pledge.
|
||||
//
|
||||
// Target sector expiration is calculated by looking at the input deal queue, sorting it by deal expiration, and
|
||||
// selecting N deals from the queue up to sector size. The target expiration will be Nth deal end epoch, or in case
|
||||
// where there weren't enough deals to fill a sector, DealMaxDuration (540 days = 1555200 epochs)
|
||||
//
|
||||
// Setting this to a high value (for example to maximum deal duration - 1555200) will disable selection based on
|
||||
// initial pledge - upgrade sectors will always be chosen based on longest expiration
|
||||
// DEPRECATED: Target expiration is no longer used
|
||||
MinTargetUpgradeSectorExpiration uint64
|
||||
|
||||
// CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will
|
||||
@@ -736,6 +728,7 @@ type Events struct {
|
||||
}
|
||||
|
||||
type IndexConfig struct {
|
||||
// EXPERIMENTAL FEATURE. USE WITH CAUTION
|
||||
// EnableMsgIndex enables indexing of messages on chain.
|
||||
EnableMsgIndex bool
|
||||
}
|
||||
|
||||
@@ -1537,6 +1537,8 @@ func ethLogFromEvent(entries []types.EventEntry) (data []byte, topics []ethtypes
|
||||
topicsFoundCount int
|
||||
dataFound bool
|
||||
)
|
||||
// Topics must be non-nil, even if empty. So we might as well pre-allocate for 4 (the max).
|
||||
topics = make([]ethtypes.EthHash, 0, 4)
|
||||
for _, entry := range entries {
|
||||
// Drop events with non-raw topics to avoid mistakes.
|
||||
if entry.Codec != cid.Raw {
|
||||
|
||||
@@ -17,7 +17,8 @@ func TestEthLogFromEvent(t *testing.T) {
|
||||
data, topics, ok := ethLogFromEvent(nil)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, data)
|
||||
require.Nil(t, topics)
|
||||
require.Empty(t, topics)
|
||||
require.NotNil(t, topics)
|
||||
|
||||
// basic topic
|
||||
data, topics, ok = ethLogFromEvent([]types.EventEntry{{
|
||||
|
||||
@@ -180,6 +180,7 @@ func (m *StateModule) StateMinerInfo(ctx context.Context, actor address.Address,
|
||||
SectorSize: info.SectorSize,
|
||||
WindowPoStPartitionSectors: info.WindowPoStPartitionSectors,
|
||||
ConsensusFaultElapsed: info.ConsensusFaultElapsed,
|
||||
PendingOwnerAddress: info.PendingOwnerAddress,
|
||||
Beneficiary: info.Beneficiary,
|
||||
BeneficiaryTerm: &info.BeneficiaryTerm,
|
||||
PendingBeneficiaryTerm: info.PendingBeneficiaryTerm,
|
||||
|
||||
@@ -58,7 +58,7 @@ func (a *SyncAPI) SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) erro
|
||||
}
|
||||
|
||||
if a.SlashFilter != nil && os.Getenv("LOTUS_NO_SLASHFILTER") != "_yes_i_know_i_can_and_probably_will_lose_all_my_fil_and_power_" {
|
||||
if err := a.SlashFilter.MinedBlock(ctx, blk.Header, parent.Height); err != nil {
|
||||
if _, err = a.SlashFilter.MinedBlock(ctx, blk.Header, parent.Height); err != nil {
|
||||
log.Errorf("<!!> SLASH FILTER ERROR: %s", err)
|
||||
return xerrors.Errorf("<!!> SLASH FILTER ERROR: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package modules
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/filecoin-project/lotus/journal/alerting"
|
||||
"github.com/filecoin-project/lotus/lib/ulimit"
|
||||
)
|
||||
@@ -42,6 +45,35 @@ func LegacyMarketsEOL(al *alerting.Alerting) {
|
||||
})
|
||||
}
|
||||
|
||||
func CheckFvmConcurrency() func(al *alerting.Alerting) {
|
||||
return func(al *alerting.Alerting) {
|
||||
fvmConcurrency, ok := os.LookupEnv("LOTUS_FVM_CONCURRENCY")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fvmConcurrencyVal, err := strconv.Atoi(fvmConcurrency)
|
||||
if err != nil {
|
||||
alert := al.AddAlertType("process", "fvm-concurrency")
|
||||
al.Raise(alert, map[string]string{
|
||||
"message": "LOTUS_FVM_CONCURRENCY is not an integer",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Raise alert if LOTUS_FVM_CONCURRENCY is set to a high value
|
||||
if fvmConcurrencyVal > 24 {
|
||||
alert := al.AddAlertType("process", "fvm-concurrency")
|
||||
al.Raise(alert, map[string]interface{}{
|
||||
"message": "LOTUS_FVM_CONCURRENCY is set to a high value that can cause chain sync panics on network migrations/upgrades",
|
||||
"set_value": fvmConcurrencyVal,
|
||||
"recommended": "24 or less during network upgrades",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: More things:
|
||||
// * Space in repo dirs (taking into account mounts)
|
||||
// * Miner
|
||||
|
||||
@@ -4,9 +4,12 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/ethhashlookup"
|
||||
"github.com/filecoin-project/lotus/chain/events"
|
||||
"github.com/filecoin-project/lotus/chain/messagepool"
|
||||
@@ -54,6 +57,17 @@ func EthModuleAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRep
|
||||
}
|
||||
}
|
||||
|
||||
// prefill the whole skiplist cache maintained internally by the GetTipsetByHeight
|
||||
go func() {
|
||||
start := time.Now()
|
||||
log.Infoln("Start prefilling GetTipsetByHeight cache")
|
||||
_, err := cs.GetTipsetByHeight(mctx, abi.ChainEpoch(0), cs.GetHeaviestTipSet(), false)
|
||||
if err != nil {
|
||||
log.Warnf("error when prefilling GetTipsetByHeight cache: %w", err)
|
||||
}
|
||||
log.Infof("Prefilling GetTipsetByHeight done in %s", time.Since(start))
|
||||
}()
|
||||
|
||||
ctx := helpers.LifecycleCtx(mctx, lc)
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(context.Context) error {
|
||||
|
||||
@@ -983,19 +983,18 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error
|
||||
return func(cfg sealiface.Config) (err error) {
|
||||
err = mutateSealingCfg(r, func(c config.SealingConfiger) {
|
||||
newCfg := config.SealingConfig{
|
||||
MaxWaitDealsSectors: cfg.MaxWaitDealsSectors,
|
||||
MaxSealingSectors: cfg.MaxSealingSectors,
|
||||
MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals,
|
||||
PreferNewSectorsForDeals: cfg.PreferNewSectorsForDeals,
|
||||
MaxUpgradingSectors: cfg.MaxUpgradingSectors,
|
||||
CommittedCapacitySectorLifetime: config.Duration(cfg.CommittedCapacitySectorLifetime),
|
||||
WaitDealsDelay: config.Duration(cfg.WaitDealsDelay),
|
||||
MakeNewSectorForDeals: cfg.MakeNewSectorForDeals,
|
||||
MinUpgradeSectorExpiration: cfg.MinUpgradeSectorExpiration,
|
||||
MinTargetUpgradeSectorExpiration: cfg.MinTargetUpgradeSectorExpiration,
|
||||
MakeCCSectorsAvailable: cfg.MakeCCSectorsAvailable,
|
||||
AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy,
|
||||
FinalizeEarly: cfg.FinalizeEarly,
|
||||
MaxWaitDealsSectors: cfg.MaxWaitDealsSectors,
|
||||
MaxSealingSectors: cfg.MaxSealingSectors,
|
||||
MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals,
|
||||
PreferNewSectorsForDeals: cfg.PreferNewSectorsForDeals,
|
||||
MaxUpgradingSectors: cfg.MaxUpgradingSectors,
|
||||
CommittedCapacitySectorLifetime: config.Duration(cfg.CommittedCapacitySectorLifetime),
|
||||
WaitDealsDelay: config.Duration(cfg.WaitDealsDelay),
|
||||
MakeNewSectorForDeals: cfg.MakeNewSectorForDeals,
|
||||
MinUpgradeSectorExpiration: cfg.MinUpgradeSectorExpiration,
|
||||
MakeCCSectorsAvailable: cfg.MakeCCSectorsAvailable,
|
||||
AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy,
|
||||
FinalizeEarly: cfg.FinalizeEarly,
|
||||
|
||||
CollateralFromMinerBalance: cfg.CollateralFromMinerBalance,
|
||||
AvailableBalanceBuffer: types.FIL(cfg.AvailableBalanceBuffer),
|
||||
@@ -1027,13 +1026,12 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error
|
||||
|
||||
func ToSealingConfig(dealmakingCfg config.DealmakingConfig, sealingCfg config.SealingConfig) sealiface.Config {
|
||||
return sealiface.Config{
|
||||
MaxWaitDealsSectors: sealingCfg.MaxWaitDealsSectors,
|
||||
MaxSealingSectors: sealingCfg.MaxSealingSectors,
|
||||
MaxSealingSectorsForDeals: sealingCfg.MaxSealingSectorsForDeals,
|
||||
PreferNewSectorsForDeals: sealingCfg.PreferNewSectorsForDeals,
|
||||
MinUpgradeSectorExpiration: sealingCfg.MinUpgradeSectorExpiration,
|
||||
MinTargetUpgradeSectorExpiration: sealingCfg.MinTargetUpgradeSectorExpiration,
|
||||
MaxUpgradingSectors: sealingCfg.MaxUpgradingSectors,
|
||||
MaxWaitDealsSectors: sealingCfg.MaxWaitDealsSectors,
|
||||
MaxSealingSectors: sealingCfg.MaxSealingSectors,
|
||||
MaxSealingSectorsForDeals: sealingCfg.MaxSealingSectorsForDeals,
|
||||
PreferNewSectorsForDeals: sealingCfg.PreferNewSectorsForDeals,
|
||||
MinUpgradeSectorExpiration: sealingCfg.MinUpgradeSectorExpiration,
|
||||
MaxUpgradingSectors: sealingCfg.MaxUpgradingSectors,
|
||||
|
||||
StartEpochSealingBuffer: abi.ChainEpoch(dealmakingCfg.StartEpochSealingBuffer),
|
||||
MakeNewSectorForDeals: sealingCfg.MakeNewSectorForDeals,
|
||||
|
||||
@@ -3,6 +3,7 @@ package paths
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -35,7 +36,7 @@ func (d *DefaultPartialFileHandler) HasAllocated(pf *partialfile.PartialFile, of
|
||||
return pf.HasAllocated(offset, size)
|
||||
}
|
||||
|
||||
func (d *DefaultPartialFileHandler) Reader(pf *partialfile.PartialFile, offset storiface.PaddedByteIndex, size abi.PaddedPieceSize) (*os.File, error) {
|
||||
func (d *DefaultPartialFileHandler) Reader(pf *partialfile.PartialFile, offset storiface.PaddedByteIndex, size abi.PaddedPieceSize) (io.Reader, error) {
|
||||
return pf.Reader(offset, size)
|
||||
}
|
||||
|
||||
|
||||
@@ -207,8 +207,8 @@ func TestRemoteGetAllocated(t *testing.T) {
|
||||
pfhandler := mocks.NewMockPartialFileHandler(mockCtrl)
|
||||
|
||||
handler := &paths.FetchHandler{
|
||||
lstore,
|
||||
pfhandler,
|
||||
Local: lstore,
|
||||
PfHandler: pfhandler,
|
||||
}
|
||||
|
||||
// run http server
|
||||
|
||||
@@ -2,7 +2,7 @@ package paths
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"io"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
@@ -24,7 +24,7 @@ type PartialFileHandler interface {
|
||||
HasAllocated(pf *partialfile.PartialFile, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize) (bool, error)
|
||||
|
||||
// Reader returns a file from which we can read the unsealed piece in the partial file.
|
||||
Reader(pf *partialfile.PartialFile, offset storiface.PaddedByteIndex, size abi.PaddedPieceSize) (*os.File, error)
|
||||
Reader(pf *partialfile.PartialFile, offset storiface.PaddedByteIndex, size abi.PaddedPieceSize) (io.Reader, error)
|
||||
|
||||
// Close closes the partial file
|
||||
Close(pf *partialfile.PartialFile) error
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
os "os"
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
@@ -84,10 +84,10 @@ func (mr *MockPartialFileHandlerMockRecorder) OpenPartialFile(arg0, arg1 interfa
|
||||
}
|
||||
|
||||
// Reader mocks base method.
|
||||
func (m *MockPartialFileHandler) Reader(arg0 *partialfile.PartialFile, arg1 storiface.PaddedByteIndex, arg2 abi.PaddedPieceSize) (*os.File, error) {
|
||||
func (m *MockPartialFileHandler) Reader(arg0 *partialfile.PartialFile, arg1 storiface.PaddedByteIndex, arg2 abi.PaddedPieceSize) (io.Reader, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Reader", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*os.File)
|
||||
ret0, _ := ret[0].(io.Reader)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
+67
-32
@@ -14,6 +14,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/storage/sealer/fsutil"
|
||||
"github.com/filecoin-project/lotus/storage/sealer/partialfile"
|
||||
"github.com/filecoin-project/lotus/storage/sealer/storiface"
|
||||
)
|
||||
|
||||
@@ -28,6 +30,10 @@ var FetchTempSubdir = "fetching"
|
||||
|
||||
var CopyBuf = 1 << 20
|
||||
|
||||
// LocalReaderTimeout is the timeout for keeping local reader files open without
|
||||
// any read activity.
|
||||
var LocalReaderTimeout = 5 * time.Second
|
||||
|
||||
type Remote struct {
|
||||
local Store
|
||||
index SectorIndex
|
||||
@@ -563,7 +569,7 @@ func (r *Remote) CheckIsUnsealed(ctx context.Context, s storiface.SectorRef, off
|
||||
// 1. no worker(local worker included) has an unsealed file for the given sector OR
|
||||
// 2. no worker(local worker included) has the unsealed piece in their unsealed sector file.
|
||||
// Will return a nil reader and a nil error in such a case.
|
||||
func (r *Remote) Reader(ctx context.Context, s storiface.SectorRef, offset, size abi.PaddedPieceSize) (func(startOffsetAligned storiface.PaddedByteIndex) (io.ReadCloser, error), error) {
|
||||
func (r *Remote) Reader(ctx context.Context, s storiface.SectorRef, offset, size abi.PaddedPieceSize) (func(startOffsetAligned, endOffsetAligned storiface.PaddedByteIndex) (io.ReadCloser, error), error) {
|
||||
ft := storiface.FTUnsealed
|
||||
|
||||
// check if we have the unsealed sector file locally
|
||||
@@ -602,20 +608,67 @@ func (r *Remote) Reader(ctx context.Context, s storiface.SectorRef, offset, size
|
||||
if has {
|
||||
log.Infof("returning piece reader for local unsealed piece sector=%+v, (offset=%d, size=%d)", s.ID, offset, size)
|
||||
|
||||
return func(startOffsetAligned storiface.PaddedByteIndex) (io.ReadCloser, error) {
|
||||
// don't reuse between readers unless closed
|
||||
f := pf
|
||||
pf = nil
|
||||
// refs keep track of the currently opened pf
|
||||
// if they drop to 0 for longer than LocalReaderTimeout, pf will be closed
|
||||
var refsLk sync.Mutex
|
||||
refs := 0
|
||||
|
||||
if f == nil {
|
||||
f, err = r.pfHandler.OpenPartialFile(abi.PaddedPieceSize(ssize), path)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("opening partial file: %w", err)
|
||||
cleanupIdle := func() {
|
||||
lastRefs := 1
|
||||
|
||||
for range time.After(LocalReaderTimeout) {
|
||||
refsLk.Lock()
|
||||
if refs == 0 && lastRefs == 0 && pf != nil { // pf can't really be nil here, but better be safe
|
||||
log.Infow("closing idle partial file", "path", path)
|
||||
err := pf.Close()
|
||||
if err != nil {
|
||||
log.Errorw("closing idle partial file", "path", path, "error", err)
|
||||
}
|
||||
|
||||
pf = nil
|
||||
refsLk.Unlock()
|
||||
return
|
||||
}
|
||||
log.Debugf("local partial file (re)opened %s (+%d,%d)", path, offset, size)
|
||||
lastRefs = refs
|
||||
refsLk.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
getPF := func() (*partialfile.PartialFile, func() error, error) {
|
||||
refsLk.Lock()
|
||||
defer refsLk.Unlock()
|
||||
|
||||
if pf == nil {
|
||||
// got closed in the meantime, reopen
|
||||
|
||||
var err error
|
||||
pf, err = r.pfHandler.OpenPartialFile(abi.PaddedPieceSize(ssize), path)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("reopening partial file: %w", err)
|
||||
}
|
||||
log.Debugf("local partial file reopened %s (+%d,%d)", path, offset, size)
|
||||
|
||||
go cleanupIdle()
|
||||
}
|
||||
|
||||
r, err := r.pfHandler.Reader(f, storiface.PaddedByteIndex(offset)+startOffsetAligned, size-abi.PaddedPieceSize(startOffsetAligned))
|
||||
refs++
|
||||
|
||||
return pf, func() error {
|
||||
refsLk.Lock()
|
||||
defer refsLk.Unlock()
|
||||
|
||||
refs--
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
return func(startOffsetAligned, endOffsetAligned storiface.PaddedByteIndex) (io.ReadCloser, error) {
|
||||
pf, done, err := getPF()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting partialfile handle: %w", err)
|
||||
}
|
||||
|
||||
r, err := r.pfHandler.Reader(pf, storiface.PaddedByteIndex(offset)+startOffsetAligned, abi.PaddedPieceSize(endOffsetAligned-startOffsetAligned))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -625,25 +678,7 @@ func (r *Remote) Reader(ctx context.Context, s storiface.SectorRef, offset, size
|
||||
io.Closer
|
||||
}{
|
||||
Reader: r,
|
||||
Closer: funcCloser(func() error {
|
||||
// if we already have a reader cached, close this one
|
||||
if pf != nil {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
if pf == f {
|
||||
pf = nil
|
||||
}
|
||||
|
||||
tmp := f
|
||||
f = nil
|
||||
return tmp.Close()
|
||||
}
|
||||
|
||||
// otherwise stash it away for reuse
|
||||
pf = f
|
||||
return nil
|
||||
}),
|
||||
Closer: funcCloser(done),
|
||||
}, nil
|
||||
}, nil
|
||||
|
||||
@@ -689,10 +724,10 @@ func (r *Remote) Reader(ctx context.Context, s storiface.SectorRef, offset, size
|
||||
continue
|
||||
}
|
||||
|
||||
return func(startOffsetAligned storiface.PaddedByteIndex) (io.ReadCloser, error) {
|
||||
return func(startOffsetAligned, endOffsetAligned storiface.PaddedByteIndex) (io.ReadCloser, error) {
|
||||
// readRemote fetches a reader that we can use to read the unsealed piece from the remote worker.
|
||||
// It uses a ranged HTTP query to ensure we ONLY read the unsealed piece and not the entire unsealed file.
|
||||
rd, err := r.readRemote(ctx, url, offset+abi.PaddedPieceSize(startOffsetAligned), size)
|
||||
rd, err := r.readRemote(ctx, url, offset+abi.PaddedPieceSize(startOffsetAligned), offset+abi.PaddedPieceSize(endOffsetAligned))
|
||||
if err != nil {
|
||||
log.Warnw("reading from remote", "url", url, "error", err)
|
||||
return nil, err
|
||||
|
||||
@@ -477,7 +477,7 @@ func TestReader(t *testing.T) {
|
||||
require.Nil(t, rdg)
|
||||
require.Contains(t, err.Error(), tc.errStr)
|
||||
} else {
|
||||
rd, err = rdg(0)
|
||||
rd, err = rdg(0, storiface.PaddedByteIndex(size))
|
||||
require.Error(t, err)
|
||||
require.Nil(t, rd)
|
||||
require.Contains(t, err.Error(), tc.errStr)
|
||||
@@ -490,7 +490,7 @@ func TestReader(t *testing.T) {
|
||||
require.Nil(t, rd)
|
||||
} else {
|
||||
require.NotNil(t, rdg)
|
||||
rd, err := rdg(0)
|
||||
rd, err := rdg(0, storiface.PaddedByteIndex(size))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
|
||||
+25
-27
@@ -417,6 +417,13 @@ func (m *Sealing) addPendingPiece(ctx context.Context, size abi.UnpaddedPieceSiz
|
||||
close(pp.doneCh)
|
||||
}
|
||||
|
||||
log.Debugw("new pending piece", "dealId", deal.DealID,
|
||||
"piece", deal.DealProposal.PieceCID,
|
||||
"size", size,
|
||||
"dealStart", deal.DealSchedule.StartEpoch,
|
||||
"dealEnd", deal.DealSchedule.EndEpoch,
|
||||
"termEnd", ct.claimTermEnd)
|
||||
|
||||
m.pendingPieces[proposalCID(deal)] = pp
|
||||
go func() {
|
||||
defer m.inputLk.Unlock()
|
||||
@@ -694,7 +701,6 @@ func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealP
|
||||
return &pieceBounds[f-1]
|
||||
}
|
||||
|
||||
targetExpirationEpoch := ts.Height() + abi.ChainEpoch(cfg.MinTargetUpgradeSectorExpiration)
|
||||
minExpirationEpoch := ts.Height() + abi.ChainEpoch(cfg.MinUpgradeSectorExpiration)
|
||||
|
||||
var candidate abi.SectorID
|
||||
@@ -732,33 +738,25 @@ func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealP
|
||||
continue
|
||||
}
|
||||
|
||||
// if the sector has less than one sector worth of candidate deals, and
|
||||
// the best candidate has more candidate deals, this sector isn't better
|
||||
if pb.dealBytesInBound.Padded() < abi.PaddedPieceSize(ssize) {
|
||||
if bestDealBytes > pb.dealBytesInBound.Padded() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// if best is below target, we want larger expirations
|
||||
// if best is above target, we want lower pledge, but only if still above target
|
||||
|
||||
// todo: after nv17 "target expiration" doesn't really make that much sense
|
||||
// (tho to be fair it doesn't make too much sense now either)
|
||||
// we probably want the lowest expiration that's still above the configured
|
||||
// minimum, and can fit most candidate deals
|
||||
|
||||
if bestExpiration < targetExpirationEpoch {
|
||||
if expirationEpoch > bestExpiration && slowChecks(s.Number) {
|
||||
bestExpiration = expirationEpoch
|
||||
bestPledge = pledge
|
||||
bestDealBytes = pb.dealBytesInBound.Padded()
|
||||
candidate = s
|
||||
}
|
||||
if pb.dealBytesInBound.Padded() == 0 {
|
||||
log.Debugw("skipping available sector", "sector", s.Number, "reason", "no deals in expiration bounds", "expiration", expirationEpoch)
|
||||
continue
|
||||
}
|
||||
|
||||
if expirationEpoch >= targetExpirationEpoch && pledge.LessThan(bestPledge) && slowChecks(s.Number) {
|
||||
// if the sector has less than one sector worth of candidate deals, and
|
||||
// the best candidate has more candidate deals, this sector isn't better
|
||||
|
||||
lessThanSectorOfData := pb.dealBytesInBound.Padded() < abi.PaddedPieceSize(ssize)
|
||||
moreDealsThanBest := pb.dealBytesInBound.Padded() > bestDealBytes
|
||||
|
||||
// we want lower pledge, but only if we have more than one sector worth of deals
|
||||
|
||||
preferDueToDealSize := lessThanSectorOfData && moreDealsThanBest
|
||||
preferDueToPledge := pledge.LessThan(bestPledge) && !lessThanSectorOfData
|
||||
|
||||
prefer := preferDueToDealSize || preferDueToPledge
|
||||
|
||||
if prefer && slowChecks(s.Number) {
|
||||
bestExpiration = expirationEpoch
|
||||
bestPledge = pledge
|
||||
bestDealBytes = pb.dealBytesInBound.Padded()
|
||||
@@ -767,12 +765,12 @@ func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealP
|
||||
}
|
||||
|
||||
if bestExpiration < minExpirationEpoch {
|
||||
log.Infow("Not upgrading any sectors", "available", len(m.available), "pieces", len(m.pendingPieces), "bestExp", bestExpiration, "target", targetExpirationEpoch, "min", minExpirationEpoch, "candidate", candidate)
|
||||
log.Infow("Not upgrading any sectors", "available", len(m.available), "pieces", len(m.pendingPieces), "bestExp", bestExpiration, "min", minExpirationEpoch, "candidate", candidate)
|
||||
// didn't find a good sector / no sectors were available
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Infow("Upgrading sector", "number", candidate.Number, "type", "deal", "proofType", sp, "expiration", bestExpiration, "pledge", types.FIL(bestPledge))
|
||||
log.Infow("Upgrading sector", "number", candidate.Number, "type", "deal", "proofType", sp, "expiration", bestExpiration, "pledge", types.FIL(bestPledge), "pieces", len(m.pendingPieces), "dealBytesAtExp", bestDealBytes)
|
||||
delete(m.available, candidate)
|
||||
m.nextDealSector = &candidate.Number
|
||||
return true, m.sectors.Send(uint64(candidate.Number), SectorStartCCUpdate{})
|
||||
|
||||
@@ -22,8 +22,6 @@ type Config struct {
|
||||
|
||||
MinUpgradeSectorExpiration uint64
|
||||
|
||||
MinTargetUpgradeSectorExpiration uint64
|
||||
|
||||
MaxUpgradingSectors uint64
|
||||
|
||||
MakeNewSectorForDeals bool
|
||||
|
||||
@@ -8,6 +8,21 @@ import (
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
)
|
||||
|
||||
// UnpaddedFr32Chunk is the minimum amount of data which can be fr32-padded
|
||||
// Fr32 padding inserts two zero bits every 254 bits, so the minimum amount of
|
||||
// data which can be padded is 254 bits. 127 bytes is the smallest multiple of
|
||||
// 254 bits which has a whole number of bytes.
|
||||
const UnpaddedFr32Chunk abi.UnpaddedPieceSize = 127
|
||||
|
||||
// PaddedFr32Chunk is the size of a UnpaddedFr32Chunk chunk after fr32 padding
|
||||
const PaddedFr32Chunk abi.PaddedPieceSize = 128
|
||||
|
||||
func init() {
|
||||
if PaddedFr32Chunk != UnpaddedFr32Chunk.Padded() {
|
||||
panic("bad math")
|
||||
}
|
||||
}
|
||||
|
||||
var MTTresh = uint64(512 << 10)
|
||||
|
||||
func mtChunkCount(usz abi.PaddedPieceSize) uint64 {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
rlepluslazy "github.com/filecoin-project/go-bitfield/rle"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/lib/readerutil"
|
||||
"github.com/filecoin-project/lotus/storage/sealer/fsutil"
|
||||
"github.com/filecoin-project/lotus/storage/sealer/storiface"
|
||||
)
|
||||
@@ -249,7 +250,10 @@ func (pf *PartialFile) Free(offset storiface.PaddedByteIndex, size abi.PaddedPie
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pf *PartialFile) Reader(offset storiface.PaddedByteIndex, size abi.PaddedPieceSize) (*os.File, error) {
|
||||
// Reader forks off a new reader from the underlying file, and returns a reader
|
||||
// starting at the given offset and reading the given size. Safe for concurrent
|
||||
// use.
|
||||
func (pf *PartialFile) Reader(offset storiface.PaddedByteIndex, size abi.PaddedPieceSize) (io.Reader, error) {
|
||||
if _, err := pf.file.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return nil, xerrors.Errorf("seek piece start: %w", err)
|
||||
}
|
||||
@@ -275,7 +279,7 @@ func (pf *PartialFile) Reader(offset storiface.PaddedByteIndex, size abi.PaddedP
|
||||
}
|
||||
}
|
||||
|
||||
return pf.file, nil
|
||||
return io.LimitReader(readerutil.NewReadSeekerFromReaderAt(pf.file, int64(offset)), int64(size)), nil
|
||||
}
|
||||
|
||||
func (pf *PartialFile) Allocated() (rlepluslazy.RunIterator, error) {
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
pool "github.com/libp2p/go-buffer-pool"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/dagstore/mount"
|
||||
@@ -71,7 +73,7 @@ func (p *pieceProvider) IsUnsealed(ctx context.Context, sector storiface.SectorR
|
||||
// It will NOT try to schedule an Unseal of a sealed sector file for the read.
|
||||
//
|
||||
// Returns a nil reader if the piece does NOT exist in any unsealed file or there is no unsealed file for the given sector on any of the workers.
|
||||
func (p *pieceProvider) tryReadUnsealedPiece(ctx context.Context, pc cid.Cid, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize) (mount.Reader, error) {
|
||||
func (p *pieceProvider) tryReadUnsealedPiece(ctx context.Context, pc cid.Cid, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, pieceSize abi.UnpaddedPieceSize) (mount.Reader, error) {
|
||||
// acquire a lock purely for reading unsealed sectors
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
if err := p.index.StorageLock(ctx, sector.ID, storiface.FTUnsealed, storiface.FTNone); err != nil {
|
||||
@@ -82,30 +84,37 @@ func (p *pieceProvider) tryReadUnsealedPiece(ctx context.Context, pc cid.Cid, se
|
||||
// Reader returns a reader getter for an unsealed piece at the given offset in the given sector.
|
||||
// The returned reader will be nil if none of the workers has an unsealed sector file containing
|
||||
// the unsealed piece.
|
||||
rg, err := p.storage.Reader(ctx, sector, abi.PaddedPieceSize(pieceOffset.Padded()), size.Padded())
|
||||
readerGetter, err := p.storage.Reader(ctx, sector, abi.PaddedPieceSize(pieceOffset.Padded()), pieceSize.Padded())
|
||||
if err != nil {
|
||||
cancel()
|
||||
log.Debugf("did not get storage reader;sector=%+v, err:%s", sector.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
if rg == nil {
|
||||
if readerGetter == nil {
|
||||
cancel()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, fr32.BufSize(size.Padded()))
|
||||
|
||||
pr, err := (&pieceReader{
|
||||
ctx: ctx,
|
||||
getReader: func(ctx context.Context, startOffset uint64) (io.ReadCloser, error) {
|
||||
startOffsetAligned := storiface.UnpaddedByteIndex(startOffset / 127 * 127) // floor to multiple of 127
|
||||
getReader: func(startOffset, readSize uint64) (io.ReadCloser, error) {
|
||||
// The request is for unpadded bytes, at any offset.
|
||||
// storage.Reader readers give us fr32-padded bytes, so we need to
|
||||
// do the unpadding here.
|
||||
|
||||
r, err := rg(startOffsetAligned.Padded())
|
||||
startOffsetAligned := storiface.UnpaddedFloor(startOffset)
|
||||
startOffsetDiff := int(startOffset - uint64(startOffsetAligned))
|
||||
|
||||
endOffset := startOffset + readSize
|
||||
endOffsetAligned := storiface.UnpaddedCeil(endOffset)
|
||||
|
||||
r, err := readerGetter(startOffsetAligned.Padded(), endOffsetAligned.Padded())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting reader at +%d: %w", startOffsetAligned, err)
|
||||
}
|
||||
|
||||
upr, err := fr32.NewUnpadReaderBuf(r, size.Padded(), buf)
|
||||
buf := pool.Get(fr32.BufSize(pieceSize.Padded()))
|
||||
|
||||
upr, err := fr32.NewUnpadReaderBuf(r, pieceSize.Padded(), buf)
|
||||
if err != nil {
|
||||
r.Close() // nolint
|
||||
return nil, xerrors.Errorf("creating unpadded reader: %w", err)
|
||||
@@ -113,26 +122,31 @@ func (p *pieceProvider) tryReadUnsealedPiece(ctx context.Context, pc cid.Cid, se
|
||||
|
||||
bir := bufio.NewReaderSize(upr, 127)
|
||||
if startOffset > uint64(startOffsetAligned) {
|
||||
if _, err := bir.Discard(int(startOffset - uint64(startOffsetAligned))); err != nil {
|
||||
if _, err := bir.Discard(startOffsetDiff); err != nil {
|
||||
r.Close() // nolint
|
||||
return nil, xerrors.Errorf("discarding bytes for startOffset: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var closeOnce sync.Once
|
||||
|
||||
return struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}{
|
||||
Reader: bir,
|
||||
Closer: funcCloser(func() error {
|
||||
closeOnce.Do(func() {
|
||||
pool.Put(buf)
|
||||
})
|
||||
return r.Close()
|
||||
}),
|
||||
}, nil
|
||||
},
|
||||
len: size,
|
||||
len: pieceSize,
|
||||
onClose: cancel,
|
||||
pieceCid: pc,
|
||||
}).init()
|
||||
}).init(ctx)
|
||||
if err != nil || pr == nil { // pr == nil to make sure we don't return typed nil
|
||||
cancel()
|
||||
return nil, err
|
||||
|
||||
+139
-31
@@ -6,8 +6,10 @@ import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/ipfs/go-cid"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/dagstore/mount"
|
||||
@@ -21,29 +23,48 @@ import (
|
||||
var MaxPieceReaderBurnBytes int64 = 1 << 20 // 1M
|
||||
var ReadBuf = 128 * (127 * 8) // unpadded(128k)
|
||||
|
||||
type pieceGetter func(ctx context.Context, offset uint64) (io.ReadCloser, error)
|
||||
var MinRandomReadSize = int64(4 << 10)
|
||||
|
||||
type pieceGetter func(offset, size uint64) (io.ReadCloser, error)
|
||||
|
||||
type pieceReader struct {
|
||||
ctx context.Context
|
||||
getReader pieceGetter
|
||||
pieceCid cid.Cid
|
||||
len abi.UnpaddedPieceSize
|
||||
onClose context.CancelFunc
|
||||
|
||||
seqMCtx context.Context
|
||||
atMCtx context.Context
|
||||
|
||||
closed bool
|
||||
seqAt int64 // next byte to be read by io.Reader
|
||||
|
||||
mu sync.Mutex
|
||||
r io.ReadCloser
|
||||
br *bufio.Reader
|
||||
rAt int64
|
||||
// sequential reader
|
||||
seqMu sync.Mutex
|
||||
r io.ReadCloser
|
||||
br *bufio.Reader
|
||||
rAt int64
|
||||
|
||||
// random read cache
|
||||
remReads *lru.Cache[int64, []byte] // data start offset -> data
|
||||
// todo try carrying a "bytes read sequentially so far" counter with those
|
||||
// cacahed byte buffers, increase buffer sizes when we see that we're doing
|
||||
// a long sequential read
|
||||
}
|
||||
|
||||
func (p *pieceReader) init() (_ *pieceReader, err error) {
|
||||
stats.Record(p.ctx, metrics.DagStorePRInitCount.M(1))
|
||||
func (p *pieceReader) init(ctx context.Context) (_ *pieceReader, err error) {
|
||||
stats.Record(ctx, metrics.DagStorePRInitCount.M(1))
|
||||
|
||||
p.seqMCtx, _ = tag.New(ctx, tag.Upsert(metrics.PRReadType, "seq"))
|
||||
p.atMCtx, _ = tag.New(ctx, tag.Upsert(metrics.PRReadType, "rand"))
|
||||
|
||||
p.remReads, err = lru.New[int64, []byte](100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.rAt = 0
|
||||
p.r, err = p.getReader(p.ctx, uint64(p.rAt))
|
||||
p.r, err = p.getReader(uint64(p.rAt), uint64(p.len))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,17 +86,14 @@ func (p *pieceReader) check() error {
|
||||
}
|
||||
|
||||
func (p *pieceReader) Close() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.seqMu.Lock()
|
||||
defer p.seqMu.Unlock()
|
||||
|
||||
if err := p.check(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.r != nil {
|
||||
if err := p.r.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.r.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,21 +108,21 @@ func (p *pieceReader) Close() error {
|
||||
}
|
||||
|
||||
func (p *pieceReader) Read(b []byte) (int, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.seqMu.Lock()
|
||||
defer p.seqMu.Unlock()
|
||||
|
||||
if err := p.check(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err := p.readAtUnlocked(b, p.seqAt)
|
||||
n, err := p.readSeqReader(b)
|
||||
p.seqAt += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (p *pieceReader) Seek(offset int64, whence int) (int64, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.seqMu.Lock()
|
||||
defer p.seqMu.Unlock()
|
||||
|
||||
if err := p.check(); err != nil {
|
||||
return 0, err
|
||||
@@ -124,19 +142,14 @@ func (p *pieceReader) Seek(offset int64, whence int) (int64, error) {
|
||||
return p.seqAt, nil
|
||||
}
|
||||
|
||||
func (p *pieceReader) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
func (p *pieceReader) readSeqReader(b []byte) (n int, err error) {
|
||||
off := p.seqAt
|
||||
|
||||
return p.readAtUnlocked(b, off)
|
||||
}
|
||||
|
||||
func (p *pieceReader) readAtUnlocked(b []byte, off int64) (n int, err error) {
|
||||
if err := p.check(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stats.Record(p.ctx, metrics.DagStorePRBytesRequested.M(int64(len(b))))
|
||||
stats.Record(p.seqMCtx, metrics.DagStorePRBytesRequested.M(int64(len(b))))
|
||||
|
||||
// 1. Get the backing reader into the correct position
|
||||
|
||||
@@ -154,13 +167,13 @@ func (p *pieceReader) readAtUnlocked(b []byte, off int64) (n int, err error) {
|
||||
log.Debugw("pieceReader new stream", "piece", p.pieceCid, "at", p.rAt, "off", off-p.rAt, "n", len(b))
|
||||
|
||||
if off > p.rAt {
|
||||
stats.Record(p.ctx, metrics.DagStorePRSeekForwardBytes.M(off-p.rAt), metrics.DagStorePRSeekForwardCount.M(1))
|
||||
stats.Record(p.seqMCtx, metrics.DagStorePRSeekForwardBytes.M(off-p.rAt), metrics.DagStorePRSeekForwardCount.M(1))
|
||||
} else {
|
||||
stats.Record(p.ctx, metrics.DagStorePRSeekBackBytes.M(p.rAt-off), metrics.DagStorePRSeekBackCount.M(1))
|
||||
stats.Record(p.seqMCtx, metrics.DagStorePRSeekBackBytes.M(p.rAt-off), metrics.DagStorePRSeekBackCount.M(1))
|
||||
}
|
||||
|
||||
p.rAt = off
|
||||
p.r, err = p.getReader(p.ctx, uint64(p.rAt))
|
||||
p.r, err = p.getReader(uint64(p.rAt), uint64(p.len))
|
||||
p.br = bufio.NewReaderSize(p.r, ReadBuf)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("getting backing reader: %w", err)
|
||||
@@ -169,7 +182,7 @@ func (p *pieceReader) readAtUnlocked(b []byte, off int64) (n int, err error) {
|
||||
|
||||
// 2. Check if we need to burn some bytes
|
||||
if off > p.rAt {
|
||||
stats.Record(p.ctx, metrics.DagStorePRBytesDiscarded.M(off-p.rAt), metrics.DagStorePRDiscardCount.M(1))
|
||||
stats.Record(p.seqMCtx, metrics.DagStorePRBytesDiscarded.M(off-p.rAt), metrics.DagStorePRDiscardCount.M(1))
|
||||
|
||||
n, err := io.CopyN(io.Discard, p.br, off-p.rAt)
|
||||
p.rAt += n
|
||||
@@ -196,4 +209,99 @@ func (p *pieceReader) readAtUnlocked(b []byte, off int64) (n int, err error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (p *pieceReader) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
stats.Record(p.atMCtx, metrics.DagStorePRBytesRequested.M(int64(len(b))))
|
||||
|
||||
var filled int64
|
||||
|
||||
// try to get a buf from lru
|
||||
data, ok := p.remReads.Get(off)
|
||||
if ok {
|
||||
n = copy(b, data)
|
||||
filled += int64(n)
|
||||
|
||||
if n < len(data) {
|
||||
p.remReads.Add(off+int64(n), data[n:])
|
||||
|
||||
// keep the header buffered
|
||||
if off != 0 {
|
||||
p.remReads.Remove(off)
|
||||
}
|
||||
}
|
||||
|
||||
stats.Record(p.atMCtx, metrics.DagStorePRAtHitBytes.M(int64(n)), metrics.DagStorePRAtHitCount.M(1))
|
||||
// dagstore/pr_at_hit_bytes, dagstore/pr_at_hit_count
|
||||
}
|
||||
if filled == int64(len(b)) {
|
||||
// dagstore/pr_at_cache_fill_count
|
||||
stats.Record(p.atMCtx, metrics.DagStorePRAtCacheFillCount.M(1))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
readOff := off + filled
|
||||
readSize := int64(len(b)) - filled
|
||||
|
||||
smallRead := readSize < MinRandomReadSize
|
||||
|
||||
if smallRead {
|
||||
// read into small read buf
|
||||
readBuf := make([]byte, MinRandomReadSize)
|
||||
bn, err := p.readInto(readBuf, readOff)
|
||||
if err != nil && err != io.EOF {
|
||||
return int(filled), err
|
||||
}
|
||||
|
||||
_ = stats.RecordWithTags(p.atMCtx, []tag.Mutator{tag.Insert(metrics.PRReadSize, "small")}, metrics.DagStorePRAtReadBytes.M(int64(bn)), metrics.DagStorePRAtReadCount.M(1))
|
||||
|
||||
// reslice so that the slice is the data
|
||||
readBuf = readBuf[:bn]
|
||||
|
||||
// fill user data
|
||||
used := copy(b[filled:], readBuf[:])
|
||||
filled += int64(used)
|
||||
readBuf = readBuf[used:]
|
||||
|
||||
// cache the rest
|
||||
if len(readBuf) > 0 {
|
||||
p.remReads.Add(readOff+int64(used), readBuf)
|
||||
}
|
||||
} else {
|
||||
// read into user buf
|
||||
bn, err := p.readInto(b[filled:], readOff)
|
||||
if err != nil {
|
||||
return int(filled), err
|
||||
}
|
||||
filled += int64(bn)
|
||||
|
||||
_ = stats.RecordWithTags(p.atMCtx, []tag.Mutator{tag.Insert(metrics.PRReadSize, "big")}, metrics.DagStorePRAtReadBytes.M(int64(bn)), metrics.DagStorePRAtReadCount.M(1))
|
||||
}
|
||||
|
||||
if filled < int64(len(b)) {
|
||||
return int(filled), io.EOF
|
||||
}
|
||||
|
||||
return int(filled), nil
|
||||
}
|
||||
|
||||
func (p *pieceReader) readInto(b []byte, off int64) (n int, err error) {
|
||||
rd, err := p.getReader(uint64(off), uint64(len(b)))
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("getting reader: %w", err)
|
||||
}
|
||||
|
||||
n, err = io.ReadFull(rd, b)
|
||||
|
||||
cerr := rd.Close()
|
||||
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, cerr
|
||||
}
|
||||
|
||||
var _ mount.Reader = (*pieceReader)(nil)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/storage/sealer/fr32"
|
||||
)
|
||||
|
||||
var ErrSectorNotFound = errors.New("sector not found")
|
||||
@@ -26,6 +28,14 @@ func (i UnpaddedByteIndex) Valid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnpaddedFloor(n uint64) UnpaddedByteIndex {
|
||||
return UnpaddedByteIndex(n / uint64(fr32.UnpaddedFr32Chunk) * uint64(fr32.UnpaddedFr32Chunk))
|
||||
}
|
||||
|
||||
func UnpaddedCeil(n uint64) UnpaddedByteIndex {
|
||||
return UnpaddedByteIndex((n + uint64(fr32.UnpaddedFr32Chunk-1)) / uint64(fr32.UnpaddedFr32Chunk) * uint64(fr32.UnpaddedFr32Chunk))
|
||||
}
|
||||
|
||||
type PaddedByteIndex uint64
|
||||
|
||||
type RGetter func(ctx context.Context, id abi.SectorID) (sealed cid.Cid, update bool, err error)
|
||||
|
||||
Reference in New Issue
Block a user