2019-07-29 18:57:23 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-09-19 17:38:48 +00:00
|
|
|
"encoding/base64"
|
2019-07-29 18:57:23 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
"github.com/ipfs/go-datastore"
|
|
|
|
logging "github.com/ipfs/go-log"
|
2019-09-18 13:32:21 +00:00
|
|
|
"github.com/libp2p/go-libp2p-core/host"
|
2019-07-29 18:57:23 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-09-18 02:50:03 +00:00
|
|
|
"golang.org/x/xerrors"
|
2019-09-19 16:17:49 +00:00
|
|
|
"sync"
|
2019-07-29 18:57:23 +00:00
|
|
|
|
2019-08-27 19:54:39 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/api"
|
|
|
|
"github.com/filecoin-project/go-lotus/build"
|
|
|
|
"github.com/filecoin-project/go-lotus/chain/actors"
|
|
|
|
"github.com/filecoin-project/go-lotus/chain/address"
|
2019-09-18 11:45:52 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/chain/events"
|
2019-08-27 19:54:39 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/chain/store"
|
|
|
|
"github.com/filecoin-project/go-lotus/chain/types"
|
2019-07-29 18:57:23 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/lib/sectorbuilder"
|
2019-09-16 16:40:26 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/storage/commitment"
|
2019-08-27 19:54:39 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/storage/sector"
|
2019-07-29 18:57:23 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var log = logging.Logger("storageminer")
|
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
const PoStConfidence = 1
|
2019-09-18 13:32:21 +00:00
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
type Miner struct {
|
2019-09-18 11:45:52 +00:00
|
|
|
api storageMinerApi
|
|
|
|
events *events.Events
|
2019-07-29 18:57:23 +00:00
|
|
|
|
2019-08-14 20:27:10 +00:00
|
|
|
secst *sector.Store
|
2019-09-16 16:40:26 +00:00
|
|
|
commt *commitment.Tracker
|
2019-07-29 18:57:23 +00:00
|
|
|
|
|
|
|
maddr address.Address
|
|
|
|
|
|
|
|
worker address.Address
|
|
|
|
|
|
|
|
h host.Host
|
|
|
|
|
|
|
|
ds datastore.Batching
|
2019-09-19 16:17:49 +00:00
|
|
|
|
|
|
|
schedLk sync.Mutex
|
2019-09-26 20:57:20 +00:00
|
|
|
schedPost uint64
|
2019-07-29 18:57:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type storageMinerApi interface {
|
|
|
|
// I think I want this... but this is tricky
|
|
|
|
//ReadState(ctx context.Context, addr address.Address) (????, error)
|
|
|
|
|
|
|
|
// Call a read only method on actors (no interaction with the chain required)
|
2019-09-06 06:26:02 +00:00
|
|
|
StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error)
|
2019-09-18 02:50:03 +00:00
|
|
|
StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
|
|
|
StateMinerProvingPeriodEnd(context.Context, address.Address, *types.TipSet) (uint64, error)
|
|
|
|
StateMinerProvingSet(context.Context, address.Address, *types.TipSet) ([]*api.SectorInfo, error)
|
2019-07-29 18:57:23 +00:00
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error)
|
2019-07-29 18:57:23 +00:00
|
|
|
|
2019-09-18 13:32:21 +00:00
|
|
|
ChainHead(context.Context) (*types.TipSet, error)
|
2019-07-29 18:57:23 +00:00
|
|
|
ChainWaitMsg(context.Context, cid.Cid) (*api.MsgWait, error)
|
2019-09-18 11:01:52 +00:00
|
|
|
ChainNotify(context.Context) (<-chan []*store.HeadChange, error)
|
2019-09-18 02:50:03 +00:00
|
|
|
ChainGetRandomness(context.Context, *types.TipSet, []*types.Ticket, int) ([]byte, error)
|
2019-09-18 00:08:49 +00:00
|
|
|
ChainGetTipSetByHeight(context.Context, uint64, *types.TipSet) (*types.TipSet, error)
|
2019-09-18 11:45:52 +00:00
|
|
|
ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error)
|
2019-08-08 04:24:49 +00:00
|
|
|
|
|
|
|
WalletBalance(context.Context, address.Address) (types.BigInt, error)
|
2019-08-08 17:29:23 +00:00
|
|
|
WalletHas(context.Context, address.Address) (bool, error)
|
2019-07-29 18:57:23 +00:00
|
|
|
}
|
|
|
|
|
2019-09-16 16:40:26 +00:00
|
|
|
func NewMiner(api storageMinerApi, addr address.Address, h host.Host, ds datastore.Batching, secst *sector.Store, commt *commitment.Tracker) (*Miner, error) {
|
2019-07-29 18:57:23 +00:00
|
|
|
return &Miner{
|
2019-09-18 18:07:39 +00:00
|
|
|
api: api,
|
2019-09-18 11:45:52 +00:00
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
maddr: addr,
|
|
|
|
h: h,
|
|
|
|
ds: ds,
|
2019-08-14 20:27:10 +00:00
|
|
|
secst: secst,
|
2019-09-16 16:40:26 +00:00
|
|
|
commt: commt,
|
2019-07-29 18:57:23 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) Run(ctx context.Context) error {
|
|
|
|
if err := m.runPreflightChecks(ctx); err != nil {
|
|
|
|
return errors.Wrap(err, "miner preflight checks failed")
|
|
|
|
}
|
|
|
|
|
2019-09-18 18:07:39 +00:00
|
|
|
m.events = events.NewEvents(ctx, m.api)
|
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
go m.handlePostingSealedSectors(ctx)
|
2019-09-26 20:57:20 +00:00
|
|
|
go m.beginPosting(ctx)
|
2019-07-29 18:57:23 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) handlePostingSealedSectors(ctx context.Context) {
|
2019-08-14 20:27:10 +00:00
|
|
|
incoming := m.secst.Incoming()
|
|
|
|
defer m.secst.CloseIncoming(incoming)
|
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
for {
|
|
|
|
select {
|
2019-08-14 20:27:10 +00:00
|
|
|
case sinfo, ok := <-incoming:
|
2019-07-29 18:57:23 +00:00
|
|
|
if !ok {
|
|
|
|
// TODO: set some state variable so that this state can be
|
|
|
|
// visible via some status command
|
|
|
|
log.Warning("sealed sector channel closed, aborting process")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-30 00:46:56 +00:00
|
|
|
if err := m.commitSector(ctx, sinfo); err != nil {
|
|
|
|
log.Errorf("failed to commit sector: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
log.Warning("exiting seal posting routine")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-30 00:46:56 +00:00
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
func (m *Miner) commitSector(ctx context.Context, sinfo sectorbuilder.SectorSealingStatus) error {
|
2019-08-12 21:48:18 +00:00
|
|
|
log.Info("committing sector")
|
|
|
|
|
2019-08-27 19:54:14 +00:00
|
|
|
ok, err := sectorbuilder.VerifySeal(build.SectorSize, sinfo.CommR[:], sinfo.CommD[:], sinfo.CommRStar[:], m.maddr, sinfo.SectorID, sinfo.Proof)
|
2019-08-08 01:16:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to verify seal we just created: ", err)
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
log.Error("seal we just created failed verification")
|
|
|
|
}
|
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
params := &actors.CommitSectorParams{
|
2019-08-29 00:01:46 +00:00
|
|
|
SectorID: sinfo.SectorID,
|
2019-07-30 00:46:56 +00:00
|
|
|
CommD: sinfo.CommD[:],
|
|
|
|
CommR: sinfo.CommR[:],
|
|
|
|
CommRStar: sinfo.CommRStar[:],
|
2019-07-29 18:57:23 +00:00
|
|
|
Proof: sinfo.Proof,
|
|
|
|
}
|
2019-07-30 00:46:56 +00:00
|
|
|
enc, aerr := actors.SerializeParams(params)
|
|
|
|
if aerr != nil {
|
|
|
|
return errors.Wrap(aerr, "could not serialize commit sector parameters")
|
2019-07-29 18:57:23 +00:00
|
|
|
}
|
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
msg := &types.Message{
|
2019-07-29 18:57:23 +00:00
|
|
|
To: m.maddr,
|
|
|
|
From: m.worker,
|
|
|
|
Method: actors.MAMethods.CommitSector,
|
2019-07-30 00:46:56 +00:00
|
|
|
Params: enc,
|
2019-07-29 18:57:23 +00:00
|
|
|
Value: types.NewInt(0), // TODO: need to ensure sufficient collateral
|
2019-09-06 22:35:56 +00:00
|
|
|
GasLimit: types.NewInt(100000 /* i dont know help */),
|
2019-07-29 18:57:23 +00:00
|
|
|
GasPrice: types.NewInt(1),
|
|
|
|
}
|
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
smsg, err := m.api.MpoolPushMessage(ctx, msg)
|
2019-07-29 18:57:23 +00:00
|
|
|
if err != nil {
|
2019-09-19 16:17:49 +00:00
|
|
|
return errors.Wrap(err, "pushing message to mpool")
|
2019-07-29 18:57:23 +00:00
|
|
|
}
|
|
|
|
|
2019-09-16 18:05:22 +00:00
|
|
|
if err := m.commt.TrackCommitSectorMsg(m.maddr, sinfo.SectorID, smsg.Cid()); err != nil {
|
2019-09-16 16:40:26 +00:00
|
|
|
return errors.Wrap(err, "tracking sector commitment")
|
|
|
|
}
|
2019-07-29 18:57:23 +00:00
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
go func() {
|
|
|
|
_, err := m.api.ChainWaitMsg(ctx, smsg.Cid())
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
m.beginPosting(ctx)
|
2019-09-19 16:17:49 +00:00
|
|
|
}()
|
|
|
|
|
2019-09-16 16:40:26 +00:00
|
|
|
return nil
|
2019-07-29 18:57:23 +00:00
|
|
|
}
|
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
func (m *Miner) beginPosting(ctx context.Context) {
|
|
|
|
ts, err := m.api.ChainHead(context.TODO())
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ppe, err := m.api.StateMinerProvingPeriodEnd(ctx, m.maddr, ts)
|
2019-09-17 22:43:47 +00:00
|
|
|
if err != nil {
|
2019-09-18 13:32:21 +00:00
|
|
|
log.Errorf("failed to get proving period end for miner: %s", err)
|
2019-09-17 22:43:47 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-18 13:32:21 +00:00
|
|
|
if ppe == 0 {
|
|
|
|
log.Errorf("Proving period end == 0")
|
2019-09-17 22:43:47 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
m.schedLk.Lock()
|
2019-09-26 20:57:20 +00:00
|
|
|
if m.schedPost >= 0 {
|
|
|
|
log.Warnf("PoSts already running %d", m.schedPost)
|
|
|
|
m.schedLk.Unlock()
|
|
|
|
return
|
2019-09-19 16:17:49 +00:00
|
|
|
}
|
2019-09-26 20:57:20 +00:00
|
|
|
|
|
|
|
provingPeriodOffset := ppe % build.ProvingPeriodDuration
|
|
|
|
provingPeriod := (ts.Height()-provingPeriodOffset-1)/build.ProvingPeriodDuration + 1
|
|
|
|
m.schedPost = provingPeriod*build.ProvingPeriodDuration + provingPeriodOffset
|
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
m.schedLk.Unlock()
|
|
|
|
|
|
|
|
log.Infof("Scheduling post at height %d", ppe-build.PoSTChallangeTime)
|
2019-09-26 20:57:20 +00:00
|
|
|
err = m.events.ChainAt(m.computePost(m.schedPost), func(ts *types.TipSet) error { // Revert
|
2019-09-18 13:32:21 +00:00
|
|
|
// TODO: Cancel post
|
2019-09-26 20:57:20 +00:00
|
|
|
log.Errorf("TODO: Cancel PoSt, re-run")
|
2019-09-18 13:32:21 +00:00
|
|
|
return nil
|
2019-09-19 16:17:49 +00:00
|
|
|
}, PoStConfidence, ppe-build.PoSTChallangeTime)
|
2019-09-18 02:50:03 +00:00
|
|
|
if err != nil {
|
2019-09-18 13:32:21 +00:00
|
|
|
// TODO: This is BAD, figure something out
|
|
|
|
log.Errorf("scheduling PoSt failed: %s", err)
|
2019-09-18 02:50:03 +00:00
|
|
|
return
|
|
|
|
}
|
2019-09-18 13:32:21 +00:00
|
|
|
}
|
2019-09-17 22:43:47 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
func (m *Miner) scheduleNextPost(ppe uint64) {
|
|
|
|
ts, err := m.api.ChainHead(context.TODO())
|
2019-09-19 16:17:49 +00:00
|
|
|
if err != nil {
|
2019-09-26 20:57:20 +00:00
|
|
|
log.Error(err)
|
|
|
|
// TODO: retry
|
|
|
|
return
|
2019-09-19 16:17:49 +00:00
|
|
|
}
|
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
provingPeriodOffset := ppe % build.ProvingPeriodDuration
|
|
|
|
provingPeriod := (ts.Height()-provingPeriodOffset-1)/build.ProvingPeriodDuration + 1
|
|
|
|
headPPE := provingPeriod*build.ProvingPeriodDuration + provingPeriodOffset
|
|
|
|
if headPPE > ppe {
|
|
|
|
log.Warn("PoSt computation running behind chain")
|
|
|
|
ppe = headPPE
|
2019-09-17 22:43:47 +00:00
|
|
|
}
|
2019-09-18 13:32:21 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
m.schedLk.Lock()
|
|
|
|
if m.schedPost >= ppe {
|
|
|
|
// this probably can't happen
|
|
|
|
log.Error("PoSt already scheduled: %d >= %d", m.schedPost, ppe)
|
|
|
|
m.schedLk.Unlock()
|
|
|
|
return
|
2019-09-18 13:32:21 +00:00
|
|
|
}
|
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
m.schedPost = ppe
|
|
|
|
m.schedLk.Unlock()
|
2019-09-17 22:43:47 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
log.Infof("Scheduling post at height %d", ppe-build.PoSTChallangeTime)
|
|
|
|
err = m.events.ChainAt(m.computePost(ppe), func(ts *types.TipSet) error { // Revert
|
|
|
|
// TODO: Cancel post
|
|
|
|
log.Errorf("TODO: Cancel PoSt, re-run")
|
|
|
|
return nil
|
|
|
|
}, PoStConfidence, ppe-build.PoSTChallangeTime)
|
2019-09-18 00:08:49 +00:00
|
|
|
if err != nil {
|
2019-09-26 20:57:20 +00:00
|
|
|
// TODO: This is BAD, figure something out
|
|
|
|
log.Errorf("scheduling PoSt failed: %s", err)
|
|
|
|
return
|
2019-09-18 00:08:49 +00:00
|
|
|
}
|
2019-09-26 20:57:20 +00:00
|
|
|
}
|
2019-09-18 00:08:49 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
func (m *Miner) computePost(ppe uint64) func(ts *types.TipSet, curH uint64) error {
|
|
|
|
return func(ts *types.TipSet, curH uint64) error {
|
2019-09-18 00:08:49 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
ctx := context.TODO()
|
2019-09-18 00:08:49 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
sset, err := m.api.StateMinerProvingSet(ctx, m.maddr, ts)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to get proving set for miner: %w", err)
|
|
|
|
}
|
2019-09-18 00:08:49 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
r, err := m.api.ChainGetRandomness(ctx, ts, nil, int(int64(ts.Height())-int64(ppe)+int64(build.PoSTChallangeTime))) // TODO: review: check math
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to get chain randomness for post: %w", err)
|
|
|
|
}
|
2019-09-17 22:43:47 +00:00
|
|
|
|
2019-09-19 17:38:48 +00:00
|
|
|
log.Infof("running PoSt computation, rh=%d r=%s, ppe=%d, h=%d", ts.Height()-(ts.Height()-ppe+build.PoSTChallangeTime), base64.StdEncoding.EncodeToString(r), ppe, ts.Height())
|
2019-09-18 03:32:52 +00:00
|
|
|
var faults []uint64
|
|
|
|
proof, err := m.secst.RunPoSt(ctx, sset, r, faults)
|
2019-09-18 02:50:03 +00:00
|
|
|
if err != nil {
|
2019-09-26 20:57:20 +00:00
|
|
|
return xerrors.Errorf("running post failed: %w", err)
|
2019-09-18 02:50:03 +00:00
|
|
|
}
|
2019-09-18 00:08:49 +00:00
|
|
|
|
2019-09-19 17:38:48 +00:00
|
|
|
log.Infof("submitting PoSt pLen=%d", len(proof))
|
2019-09-19 16:17:49 +00:00
|
|
|
|
|
|
|
params := &actors.SubmitPoStParams{
|
|
|
|
Proof: proof,
|
|
|
|
DoneSet: types.BitFieldFromSet(sectorIdList(sset)),
|
|
|
|
}
|
|
|
|
|
|
|
|
enc, aerr := actors.SerializeParams(params)
|
|
|
|
if aerr != nil {
|
2019-09-26 20:57:20 +00:00
|
|
|
return xerrors.Errorf("could not serialize submit post parameters: %w", err)
|
2019-09-19 16:17:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
msg := &types.Message{
|
|
|
|
To: m.maddr,
|
|
|
|
From: m.worker,
|
|
|
|
Method: actors.MAMethods.SubmitPoSt,
|
|
|
|
Params: enc,
|
2019-09-26 18:54:31 +00:00
|
|
|
Value: types.NewInt(1000), // currently hard-coded late fee in actor, returned if not late
|
2019-09-19 16:17:49 +00:00
|
|
|
GasLimit: types.NewInt(100000 /* i dont know help */),
|
|
|
|
GasPrice: types.NewInt(1),
|
|
|
|
}
|
|
|
|
|
2019-09-19 18:38:58 +00:00
|
|
|
smsg, err := m.api.MpoolPushMessage(ctx, msg)
|
2019-09-19 16:17:49 +00:00
|
|
|
if err != nil {
|
2019-09-26 20:57:20 +00:00
|
|
|
return xerrors.Errorf("pushing message to mpool: %w", err)
|
2019-09-19 16:17:49 +00:00
|
|
|
}
|
2019-09-18 02:50:03 +00:00
|
|
|
|
|
|
|
// make sure it succeeds...
|
2019-09-26 20:57:20 +00:00
|
|
|
rec, err := m.api.ChainWaitMsg(ctx, smsg.Cid())
|
2019-09-19 18:38:58 +00:00
|
|
|
if err != nil {
|
2019-09-26 20:57:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if rec.Receipt.ExitCode != 0 {
|
|
|
|
log.Warnf("SubmitPoSt EXIT: %d", rec.Receipt.ExitCode)
|
|
|
|
// TODO: Do something
|
2019-09-19 18:38:58 +00:00
|
|
|
}
|
2019-09-18 00:08:49 +00:00
|
|
|
|
2019-09-26 20:57:20 +00:00
|
|
|
m.scheduleNextPost(ppe + build.ProvingPeriodDuration)
|
|
|
|
return nil
|
|
|
|
}
|
2019-07-29 18:57:23 +00:00
|
|
|
}
|
|
|
|
|
2019-09-19 16:17:49 +00:00
|
|
|
func sectorIdList(si []*api.SectorInfo) []uint64 {
|
|
|
|
out := make([]uint64, len(si))
|
|
|
|
for i, s := range si {
|
|
|
|
out[i] = s.SectorID
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2019-07-29 18:57:23 +00:00
|
|
|
func (m *Miner) runPreflightChecks(ctx context.Context) error {
|
2019-09-18 00:08:49 +00:00
|
|
|
worker, err := m.api.StateMinerWorker(ctx, m.maddr, nil)
|
2019-07-29 18:57:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.worker = worker
|
|
|
|
|
2019-08-08 17:29:23 +00:00
|
|
|
has, err := m.api.WalletHas(ctx, worker)
|
2019-07-30 00:46:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to check wallet for worker key")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !has {
|
|
|
|
return errors.New("key for worker not found in local wallet")
|
2019-07-29 18:57:23 +00:00
|
|
|
}
|
|
|
|
|
2019-07-30 00:46:56 +00:00
|
|
|
log.Infof("starting up miner %s, worker addr %s", m.maddr, m.worker)
|
2019-07-29 18:57:23 +00:00
|
|
|
return nil
|
|
|
|
}
|