feat: batch publish deal messages

This commit is contained in:
Dirk McCormick 2021-01-08 16:28:38 +01:00
parent 1b494acc9e
commit adac340f3f
16 changed files with 971 additions and 629 deletions

View File

@ -444,9 +444,9 @@ type GatewayStruct struct {
StateMinerProvingDeadline func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*dline.Info, error)
StateMinerPower func(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error)
StateMarketBalance func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error)
StateSearchMsg func(ctx context.Context, msg cid.Cid) (*api.MsgLookup, error)
StateMarketStorageDeal func(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error)
StateNetworkVersion func(ctx context.Context, tsk types.TipSetKey) (stnetwork.Version, error)
StateSearchMsg func(ctx context.Context, msg cid.Cid) (*api.MsgLookup, error)
StateSectorGetInfo func(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error)
StateVerifiedClientStatus func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
StateWaitMsg func(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error)

View File

@ -20,9 +20,14 @@ import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/types"
sealing "github.com/filecoin-project/lotus/extern/storage-sealing"
"github.com/filecoin-project/lotus/markets/storageadapter"
"github.com/filecoin-project/lotus/node"
"github.com/filecoin-project/lotus/node/config"
"github.com/filecoin-project/lotus/node/impl"
market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market"
ipld "github.com/ipfs/go-ipld-format"
dag "github.com/ipfs/go-merkledag"
dstest "github.com/ipfs/go-merkledag/test"
@ -88,6 +93,97 @@ func CreateClientFile(ctx context.Context, client api.FullNode, rseed int) (*api
return res, data, nil
}
func TestPublishDealsBatching(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) {
publishPeriod := 10 * time.Second
maxDealsPerMsg := uint64(2)
// Set max deals per publish deals message to 2
minerDef := []StorageMiner{{
Full: 0,
Opts: node.Override(
new(*storageadapter.DealPublisher),
storageadapter.NewDealPublisher(nil, &config.PublishMsgConfig{
PublishPeriod: config.Duration(publishPeriod),
MaxDealsPerMsg: maxDealsPerMsg,
})),
Preseal: PresealGenesis,
}}
// Create a connect client and miner node
n, sn := b(t, OneFull, minerDef)
client := n[0].FullNode.(*impl.FullNodeAPI)
miner := sn[0]
s := connectAndStartMining(t, b, blocktime, client, miner)
defer s.blockMiner.Stop()
// Starts a deal and waits until it's published
runDealTillPublish := func(rseed int) {
res, _, err := CreateClientFile(s.ctx, s.client, rseed)
require.NoError(t, err)
upds, err := client.ClientGetDealUpdates(s.ctx)
require.NoError(t, err)
startDeal(t, s.ctx, s.miner, s.client, res.Root, false, startEpoch)
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
time.Sleep(time.Second)
done := make(chan struct{})
go func() {
for upd := range upds {
if upd.DataRef.Root == res.Root && upd.State == storagemarket.StorageDealAwaitingPreCommit {
done <- struct{}{}
}
}
}()
<-done
}
// Run three deals in parallel
done := make(chan struct{}, maxDealsPerMsg+1)
for rseed := 1; rseed <= 3; rseed++ {
rseed := rseed
go func() {
runDealTillPublish(rseed)
done <- struct{}{}
}()
}
// Wait for two of the deals to be published
for i := 0; i < int(maxDealsPerMsg); i++ {
<-done
}
// Expect a single PublishStorageDeals message that includes the first two deals
msgCids, err := s.client.StateListMessages(s.ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1)
require.NoError(t, err)
count := 0
for _, msgCid := range msgCids {
msg, err := s.client.ChainGetMessage(s.ctx, msgCid)
require.NoError(t, err)
if msg.Method == market.Methods.PublishStorageDeals {
count++
var pubDealsParams market2.PublishStorageDealsParams
err = pubDealsParams.UnmarshalCBOR(bytes.NewReader(msg.Params))
require.NoError(t, err)
require.Len(t, pubDealsParams.Deals, int(maxDealsPerMsg))
}
}
require.Equal(t, 1, count)
// The third deal should be published once the publish period expires.
// Allow a little padding as it takes a moment for the state change to
// be noticed by the client.
padding := 10 * time.Second
select {
case <-time.After(publishPeriod + padding):
require.Fail(t, "Expected 3rd deal to be published once publish period elapsed")
case <-done: // Success
}
}
func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) {
s := setupOneClientOneMiner(t, b, blocktime)
defer s.blockMiner.Stop()

View File

@ -57,6 +57,7 @@ const GenesisPreseals = 2
// Options for setting up a mock storage miner
type StorageMiner struct {
Full int
Opts node.Option
Preseal int
}

View File

@ -35,11 +35,11 @@ import (
type ClientNodeAdapter struct {
*clientApi
*apiWrapper
fundmgr *market.FundManager
ev *events.Events
dsMatcher *dealStateMatcher
scMgr *SectorCommittedManager
}
type clientApi struct {
@ -50,14 +50,16 @@ type clientApi struct {
func NewClientNodeAdapter(stateapi full.StateAPI, chain full.ChainAPI, mpool full.MpoolAPI, fundmgr *market.FundManager) storagemarket.StorageClientNode {
capi := &clientApi{chain, stateapi, mpool}
return &ClientNodeAdapter{
clientApi: capi,
apiWrapper: &apiWrapper{api: capi},
ev := events.NewEvents(context.TODO(), capi)
a := &ClientNodeAdapter{
clientApi: capi,
fundmgr: fundmgr,
ev: events.NewEvents(context.TODO(), capi),
ev: ev,
dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(capi))),
}
a.scMgr = NewSectorCommittedManager(ev, a, &apiWrapper{api: capi})
return a
}
func (c *ClientNodeAdapter) ListStorageProviders(ctx context.Context, encodedTs shared.TipSetToken) ([]*storagemarket.StorageProviderInfo, error) {
@ -135,6 +137,7 @@ func (c *ClientNodeAdapter) GetBalance(ctx context.Context, addr address.Address
// ValidatePublishedDeal validates that the provided deal has appeared on chain and references the same ClientDeal
// returns the Deal id if there is no error
// TODO: Don't return deal ID
func (c *ClientNodeAdapter) ValidatePublishedDeal(ctx context.Context, deal storagemarket.ClientDeal) (abi.DealID, error) {
log.Infow("DEAL ACCEPTED!")
@ -216,14 +219,17 @@ func (c *ClientNodeAdapter) DealProviderCollateralBounds(ctx context.Context, si
return big.Mul(bounds.Min, big.NewInt(clientOverestimation)), bounds.Max, nil
}
// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer)
func (c *ClientNodeAdapter) OnDealSectorPreCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, proposal market2.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorPreCommittedCallback) error {
return OnDealSectorPreCommitted(ctx, c, c.ev, provider, dealID, marketactor.DealProposal(proposal), publishCid, cb)
return c.scMgr.OnDealSectorPreCommitted(ctx, provider, marketactor.DealProposal(proposal), *publishCid, cb)
}
// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer)
func (c *ClientNodeAdapter) OnDealSectorCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, sectorNumber abi.SectorNumber, proposal market2.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorCommittedCallback) error {
return OnDealSectorCommitted(ctx, c, c.ev, provider, dealID, sectorNumber, marketactor.DealProposal(proposal), publishCid, cb)
return c.scMgr.OnDealSectorCommitted(ctx, provider, sectorNumber, marketactor.DealProposal(proposal), *publishCid, cb)
}
// TODO: Replace dealID parameter with DealProposal
func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error {
head, err := c.ChainHead(ctx)
if err != nil {

View File

@ -0,0 +1,294 @@
package storageadapter
import (
"context"
"strings"
"sync"
"time"
"go.uber.org/fx"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/node/config"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/types"
market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
)
type dealPublisherAPI interface {
MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error)
StateMinerInfo(context.Context, address.Address, types.TipSetKey) (miner.MinerInfo, error)
}
// DealPublisher batches deal publishing so that many deals can be included in
// a single publish message. This saves gas for miners that publish deals
// frequently.
// When a deal is submitted, the DealPublisher waits a configurable amount of
// time for other deals to be submitted before sending the publish message.
// There is a configurable maximum number of deals that can be included in one
// message. When the limit is reached the DealPublisher immediately submits a
// publish message with all deals in the queue.
type DealPublisher struct {
api dealPublisherAPI
ctx context.Context
Shutdown context.CancelFunc
maxDealsPerPublishMsg uint64
publishPeriod time.Duration
publishSpec *api.MessageSendSpec
lk sync.Mutex
pending []*pendingDeal
cancelWaitForMoreDeals context.CancelFunc
publishPeriodStart time.Time
}
func NewDealPublisher(
feeConfig *config.MinerFeeConfig,
publishMsgCfg *config.PublishMsgConfig,
) func(lc fx.Lifecycle, full api.FullNode) *DealPublisher {
return func(lc fx.Lifecycle, full api.FullNode) *DealPublisher {
maxFee := abi.NewTokenAmount(0)
if feeConfig != nil {
maxFee = abi.TokenAmount(feeConfig.MaxPublishDealsFee)
}
publishSpec := &api.MessageSendSpec{MaxFee: maxFee}
dp := newDealPublisher(full, publishMsgCfg, publishSpec)
lc.Append(fx.Hook{
OnStop: func(ctx context.Context) error {
dp.Shutdown()
return nil
},
})
return dp
}
}
func newDealPublisher(
dpapi dealPublisherAPI,
publishMsgCfg *config.PublishMsgConfig,
publishSpec *api.MessageSendSpec,
) *DealPublisher {
publishPeriod := time.Duration(0)
maxDealsPerMsg := uint64(1)
if publishMsgCfg != nil {
publishPeriod = time.Duration(publishMsgCfg.PublishPeriod)
maxDealsPerMsg = publishMsgCfg.MaxDealsPerMsg
}
ctx, cancel := context.WithCancel(context.Background())
return &DealPublisher{
api: dpapi,
ctx: ctx,
Shutdown: cancel,
maxDealsPerPublishMsg: maxDealsPerMsg,
publishPeriod: publishPeriod,
publishSpec: publishSpec,
}
}
func (p *DealPublisher) Publish(ctx context.Context, deal market2.ClientDealProposal) (cid.Cid, error) {
pdeal := newPendingDeal(ctx, deal)
// Add the deal to the queue
p.processNewDeal(pdeal)
// Wait for the deal to be submitted
select {
case <-ctx.Done():
return cid.Undef, ctx.Err()
case res := <-pdeal.Result:
return res.msgCid, res.err
}
}
func (p *DealPublisher) processNewDeal(pdeal *pendingDeal) {
p.lk.Lock()
defer p.lk.Unlock()
// Filter out any cancelled deals
p.filterCancelledDeals()
// If all deals have been cancelled, clear the wait-for-deals timer
if len(p.pending) == 0 && p.cancelWaitForMoreDeals != nil {
p.cancelWaitForMoreDeals()
p.cancelWaitForMoreDeals = nil
}
// Make sure the new deal hasn't been cancelled
if pdeal.ctx.Err() != nil {
return
}
// Add the new deal to the queue
p.pending = append(p.pending, pdeal)
log.Infof("add deal with piece CID %s to publish deals queue - %d deals in queue (max queue size %d)",
pdeal.deal.Proposal.PieceCID, len(p.pending), p.maxDealsPerPublishMsg)
// If the maximum number of deals per message has been reached,
// send a publish message
if uint64(len(p.pending)) >= p.maxDealsPerPublishMsg {
log.Infof("publish deals queue has reached max size of %d, publishing deals", p.maxDealsPerPublishMsg)
p.publishAllDeals()
return
}
// Otherwise wait for more deals to arrive or the timeout to be reached
p.waitForMoreDeals()
}
func (p *DealPublisher) waitForMoreDeals() {
// Check if we're already waiting for deals
if !p.publishPeriodStart.IsZero() {
elapsed := time.Since(p.publishPeriodStart)
log.Infof("%s elapsed of / %s until publish deals queue is published",
elapsed, p.publishPeriod)
return
}
// Set a timeout to wait for more deals to arrive
log.Infof("waiting publish deals queue period of %s before publishing", p.publishPeriod)
ctx, cancel := context.WithCancel(p.ctx)
p.publishPeriodStart = time.Now()
p.cancelWaitForMoreDeals = cancel
go func() {
timer := time.NewTimer(p.publishPeriod)
select {
case <-ctx.Done():
timer.Stop()
case <-timer.C:
p.lk.Lock()
defer p.lk.Unlock()
// The timeout has expired so publish all pending deals
log.Infof("publish deals queue period of %s has expired, publishing deals", p.publishPeriod)
p.publishAllDeals()
}
}()
}
func (p *DealPublisher) publishAllDeals() {
// If the timeout hasn't yet been cancelled, cancel it
if p.cancelWaitForMoreDeals != nil {
p.cancelWaitForMoreDeals()
p.cancelWaitForMoreDeals = nil
p.publishPeriodStart = time.Time{}
}
// Filter out any deals that have been cancelled
p.filterCancelledDeals()
deals := p.pending[:]
p.pending = nil
// Send the publish message
go p.publishReady(deals)
}
func (p *DealPublisher) publishReady(ready []*pendingDeal) {
if len(ready) == 0 {
return
}
deals := make([]market2.ClientDealProposal, 0, len(ready))
for _, pd := range ready {
deals = append(deals, pd.deal)
}
// Send the publish message
msgCid, err := p.publishDealProposals(deals)
// Signal that each deal has been published
for _, pd := range ready {
pd := pd
go func() {
res := publishResult{
msgCid: msgCid,
err: err,
}
select {
case <-p.ctx.Done():
case pd.Result <- res:
}
}()
}
}
// Sends the publish message
func (p *DealPublisher) publishDealProposals(deals []market2.ClientDealProposal) (cid.Cid, error) {
log.Infof("publishing %d deals in publish deals queue with piece CIDs: %s", len(deals), pieceCids(deals))
provider := deals[0].Proposal.Provider
mi, err := p.api.StateMinerInfo(p.ctx, provider, types.EmptyTSK)
if err != nil {
return cid.Undef, err
}
params, err := actors.SerializeParams(&market2.PublishStorageDealsParams{
Deals: deals,
})
if err != nil {
return cid.Undef, xerrors.Errorf("serializing PublishStorageDeals params failed: %w", err)
}
smsg, err := p.api.MpoolPushMessage(p.ctx, &types.Message{
To: market.Address,
From: mi.Worker,
Value: types.NewInt(0),
Method: market.Methods.PublishStorageDeals,
Params: params,
}, p.publishSpec)
if err != nil {
return cid.Undef, err
}
return smsg.Cid(), nil
}
func pieceCids(deals []market2.ClientDealProposal) string {
cids := make([]string, 0, len(deals))
for _, dl := range deals {
cids = append(cids, dl.Proposal.PieceCID.String())
}
return strings.Join(cids, ", ")
}
// filter out deals that have been cancelled
func (p *DealPublisher) filterCancelledDeals() {
i := 0
for _, pd := range p.pending {
if pd.ctx.Err() == nil {
p.pending[i] = pd
i++
}
}
p.pending = p.pending[:i]
}
type publishResult struct {
msgCid cid.Cid
err error
}
type pendingDeal struct {
ctx context.Context
deal market2.ClientDealProposal
Result chan publishResult
}
func newPendingDeal(ctx context.Context, deal market2.ClientDealProposal) *pendingDeal {
return &pendingDeal{
ctx: ctx,
deal: deal,
Result: make(chan publishResult),
}
}

View File

@ -0,0 +1,227 @@
package storageadapter
import (
"bytes"
"context"
"testing"
"time"
"github.com/filecoin-project/go-state-types/crypto"
market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/require"
tutils "github.com/filecoin-project/specs-actors/v2/support/testing"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/types"
market0 "github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/node/config"
)
func TestDealPublisher(t *testing.T) {
testCases := []struct {
name string
publishPeriod time.Duration
maxDealsPerMsg uint64
dealCountWithinPublishPeriod int
expiredWithinPublishPeriod int
dealCountAfterPublishPeriod int
expectedDealsPerMsg []int
}{{
name: "publish one deal within publish period",
publishPeriod: 10 * time.Millisecond,
maxDealsPerMsg: 5,
dealCountWithinPublishPeriod: 1,
dealCountAfterPublishPeriod: 0,
expectedDealsPerMsg: []int{1},
}, {
name: "publish two deals within publish period",
publishPeriod: 10 * time.Millisecond,
maxDealsPerMsg: 5,
dealCountWithinPublishPeriod: 2,
dealCountAfterPublishPeriod: 0,
expectedDealsPerMsg: []int{2},
}, {
name: "publish one deal within publish period, and one after",
publishPeriod: 10 * time.Millisecond,
maxDealsPerMsg: 5,
dealCountWithinPublishPeriod: 1,
dealCountAfterPublishPeriod: 1,
expectedDealsPerMsg: []int{1, 1},
}, {
name: "publish deals that exceed max deals per message within publish period, and one after",
publishPeriod: 10 * time.Millisecond,
maxDealsPerMsg: 2,
dealCountWithinPublishPeriod: 3,
dealCountAfterPublishPeriod: 1,
expectedDealsPerMsg: []int{2, 1, 1},
}, {
name: "ignore expired deals",
publishPeriod: 10 * time.Millisecond,
maxDealsPerMsg: 5,
dealCountWithinPublishPeriod: 2,
expiredWithinPublishPeriod: 2,
dealCountAfterPublishPeriod: 1,
expectedDealsPerMsg: []int{2, 1},
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
client := tutils.NewActorAddr(t, "client")
provider := tutils.NewActorAddr(t, "provider")
worker := tutils.NewActorAddr(t, "worker")
dpapi := newDPAPI(t, worker)
// Create a deal publisher
dp := newDealPublisher(dpapi, &config.PublishMsgConfig{
PublishPeriod: config.Duration(tc.publishPeriod),
MaxDealsPerMsg: tc.maxDealsPerMsg,
}, &api.MessageSendSpec{MaxFee: abi.NewTokenAmount(1)})
// Keep a record of the deals that were submitted to be published
var dealsToPublish []market.ClientDealProposal
publishDeal := func(expired bool) {
pctx := ctx
var cancel context.CancelFunc
if expired {
pctx, cancel = context.WithCancel(ctx)
cancel()
}
deal := market.ClientDealProposal{
Proposal: market0.DealProposal{
PieceCID: generateCids(1)[0],
Client: client,
Provider: provider,
},
ClientSignature: crypto.Signature{
Type: crypto.SigTypeSecp256k1,
Data: []byte("signature data"),
},
}
if !expired {
dealsToPublish = append(dealsToPublish, deal)
}
go func() {
_, err := dp.Publish(pctx, deal)
if expired {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}()
}
// Publish deals within publish period
for i := 0; i < tc.dealCountWithinPublishPeriod; i++ {
publishDeal(false)
}
for i := 0; i < tc.expiredWithinPublishPeriod; i++ {
publishDeal(true)
}
// Wait until publish period has elapsed
time.Sleep(2 * tc.publishPeriod)
// Publish deals after publish period
for i := 0; i < tc.dealCountAfterPublishPeriod; i++ {
publishDeal(false)
}
// For each message that was expected to be sent
var publishedDeals []market.ClientDealProposal
for _, expectedDealsInMsg := range tc.expectedDealsPerMsg {
// Should have called StateMinerInfo with the provider address
stateMinerInfoAddr := <-dpapi.stateMinerInfoCalls
require.Equal(t, provider, stateMinerInfoAddr)
// Check the fields of the message that was sent
msg := <-dpapi.pushedMsgs
require.Equal(t, worker, msg.From)
require.Equal(t, market.Address, msg.To)
require.Equal(t, market.Methods.PublishStorageDeals, msg.Method)
// Check that the expected number of deals was included in the message
var params market2.PublishStorageDealsParams
err := params.UnmarshalCBOR(bytes.NewReader(msg.Params))
require.NoError(t, err)
require.Len(t, params.Deals, expectedDealsInMsg)
// Keep track of the deals that were sent
for _, d := range params.Deals {
publishedDeals = append(publishedDeals, d)
}
}
// Verify that all deals that were submitted to be published were
// sent out (we do this by ensuring all the piece CIDs are present)
require.True(t, matchPieceCids(publishedDeals, dealsToPublish))
})
}
}
func matchPieceCids(sent []market.ClientDealProposal, exp []market.ClientDealProposal) bool {
cidsA := dealPieceCids(sent)
cidsB := dealPieceCids(exp)
if len(cidsA) != len(cidsB) {
return false
}
s1 := cid.NewSet()
for _, c := range cidsA {
s1.Add(c)
}
for _, c := range cidsB {
if !s1.Has(c) {
return false
}
}
return true
}
func dealPieceCids(deals []market2.ClientDealProposal) []cid.Cid {
cids := make([]cid.Cid, 0, len(deals))
for _, dl := range deals {
cids = append(cids, dl.Proposal.PieceCID)
}
return cids
}
type dpAPI struct {
t *testing.T
worker address.Address
stateMinerInfoCalls chan address.Address
pushedMsgs chan *types.Message
}
func newDPAPI(t *testing.T, worker address.Address) *dpAPI {
return &dpAPI{
t: t,
worker: worker,
stateMinerInfoCalls: make(chan address.Address, 128),
pushedMsgs: make(chan *types.Message, 128),
}
}
func (d *dpAPI) StateMinerInfo(ctx context.Context, address address.Address, key types.TipSetKey) (miner.MinerInfo, error) {
d.stateMinerInfoCalls <- address
return miner.MinerInfo{Worker: d.worker}, nil
}
func (d *dpAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) {
d.pushedMsgs <- msg
return &types.SignedMessage{Message: *msg}, nil
}

View File

@ -1,102 +0,0 @@
package storageadapter
import (
"bytes"
"context"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/types"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
)
type getCurrentDealInfoAPI interface {
StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error)
StateMarketStorageDeal(context.Context, abi.DealID, types.TipSetKey) (*api.MarketDeal, error)
StateSearchMsg(context.Context, cid.Cid) (*api.MsgLookup, error)
diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*miner.PreCommitChanges, error)
}
// GetCurrentDealInfo gets current information on a deal, and corrects the deal ID as needed
func GetCurrentDealInfo(ctx context.Context, ts *types.TipSet, api getCurrentDealInfoAPI, dealID abi.DealID, proposal market.DealProposal, publishCid *cid.Cid) (abi.DealID, *api.MarketDeal, types.TipSetKey, error) {
marketDeal, dealErr := api.StateMarketStorageDeal(ctx, dealID, ts.Key())
if dealErr == nil {
equal, err := checkDealEquality(ctx, ts, api, proposal, marketDeal.Proposal)
if err != nil {
return dealID, nil, types.EmptyTSK, err
}
if equal {
return dealID, marketDeal, types.EmptyTSK, nil
}
dealErr = xerrors.Errorf("Deal proposals did not match")
}
if publishCid == nil {
return dealID, nil, types.EmptyTSK, dealErr
}
// attempt deal id correction
lookup, err := api.StateSearchMsg(ctx, *publishCid)
if err != nil {
return dealID, nil, types.EmptyTSK, err
}
if lookup.Receipt.ExitCode != exitcode.Ok {
return dealID, nil, types.EmptyTSK, xerrors.Errorf("looking for publish deal message %s: non-ok exit code: %s", *publishCid, lookup.Receipt.ExitCode)
}
var retval market.PublishStorageDealsReturn
if err := retval.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil {
return dealID, nil, types.EmptyTSK, xerrors.Errorf("looking for publish deal message: unmarshaling message return: %w", err)
}
if len(retval.IDs) != 1 {
// market currently only ever sends messages with 1 deal
return dealID, nil, types.EmptyTSK, xerrors.Errorf("can't recover dealIDs from publish deal message with more than 1 deal")
}
if retval.IDs[0] == dealID {
// DealID did not change, so we are stuck with the original lookup error
return dealID, nil, lookup.TipSet, dealErr
}
dealID = retval.IDs[0]
marketDeal, err = api.StateMarketStorageDeal(ctx, dealID, ts.Key())
if err == nil {
equal, err := checkDealEquality(ctx, ts, api, proposal, marketDeal.Proposal)
if err != nil {
return dealID, nil, types.EmptyTSK, err
}
if !equal {
return dealID, nil, types.EmptyTSK, xerrors.Errorf("Deal proposals did not match")
}
}
return dealID, marketDeal, lookup.TipSet, err
}
func checkDealEquality(ctx context.Context, ts *types.TipSet, api getCurrentDealInfoAPI, p1, p2 market.DealProposal) (bool, error) {
p1ClientID, err := api.StateLookupID(ctx, p1.Client, ts.Key())
if err != nil {
return false, err
}
p2ClientID, err := api.StateLookupID(ctx, p2.Client, ts.Key())
if err != nil {
return false, err
}
return p1.PieceCID.Equals(p2.PieceCID) &&
p1.PieceSize == p2.PieceSize &&
p1.VerifiedDeal == p2.VerifiedDeal &&
p1.Label == p2.Label &&
p1.StartEpoch == p2.StartEpoch &&
p1.EndEpoch == p2.EndEpoch &&
p1.StoragePricePerEpoch.Equals(p2.StoragePricePerEpoch) &&
p1.ProviderCollateral.Equals(p2.ProviderCollateral) &&
p1.ClientCollateral.Equals(p2.ClientCollateral) &&
p1.Provider == p2.Provider &&
p1ClientID == p2ClientID, nil
}

View File

@ -1,268 +0,0 @@
package storageadapter
import (
"bytes"
"errors"
"math/rand"
"testing"
"time"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
test "github.com/filecoin-project/lotus/chain/events/state/mock"
"github.com/filecoin-project/lotus/chain/types"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"golang.org/x/xerrors"
)
var errNotFound = errors.New("Could not find")
func TestGetCurrentDealInfo(t *testing.T) {
ctx := context.Background()
dummyCid, _ := cid.Parse("bafkqaaa")
startDealID := abi.DealID(rand.Uint64())
newDealID := abi.DealID(rand.Uint64())
twoValuesReturn := makePublishDealsReturnBytes(t, []abi.DealID{abi.DealID(rand.Uint64()), abi.DealID(rand.Uint64())})
sameValueReturn := makePublishDealsReturnBytes(t, []abi.DealID{startDealID})
newValueReturn := makePublishDealsReturnBytes(t, []abi.DealID{newDealID})
proposal := market.DealProposal{
PieceCID: dummyCid,
PieceSize: abi.PaddedPieceSize(rand.Uint64()),
Label: "success",
}
otherProposal := market.DealProposal{
PieceCID: dummyCid,
PieceSize: abi.PaddedPieceSize(rand.Uint64()),
Label: "other",
}
successDeal := &api.MarketDeal{
Proposal: proposal,
State: market.DealState{
SectorStartEpoch: 1,
LastUpdatedEpoch: 2,
},
}
otherDeal := &api.MarketDeal{
Proposal: otherProposal,
State: market.DealState{
SectorStartEpoch: 1,
LastUpdatedEpoch: 2,
},
}
testCases := map[string]struct {
searchMessageLookup *api.MsgLookup
searchMessageErr error
marketDeals map[abi.DealID]*api.MarketDeal
publishCid *cid.Cid
expectedDealID abi.DealID
expectedMarketDeal *api.MarketDeal
expectedError error
}{
"deal lookup succeeds": {
marketDeals: map[abi.DealID]*api.MarketDeal{
startDealID: successDeal,
},
expectedDealID: startDealID,
expectedMarketDeal: successDeal,
},
"publish CID = nil": {
expectedDealID: startDealID,
expectedError: errNotFound,
},
"publish CID = nil, other deal on lookup": {
marketDeals: map[abi.DealID]*api.MarketDeal{
startDealID: otherDeal,
},
expectedDealID: startDealID,
expectedError: xerrors.Errorf("Deal proposals did not match"),
},
"search message fails": {
publishCid: &dummyCid,
searchMessageErr: errors.New("something went wrong"),
expectedDealID: startDealID,
expectedError: errors.New("something went wrong"),
},
"return code not ok": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.ErrIllegalState,
},
},
expectedDealID: startDealID,
expectedError: xerrors.Errorf("looking for publish deal message %s: non-ok exit code: %s", dummyCid, exitcode.ErrIllegalState),
},
"unable to unmarshal params": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: []byte("applesauce"),
},
},
expectedDealID: startDealID,
expectedError: xerrors.Errorf("looking for publish deal message: unmarshaling message return: cbor input should be of type array"),
},
"more than one returned id": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: twoValuesReturn,
},
},
expectedDealID: startDealID,
expectedError: xerrors.Errorf("can't recover dealIDs from publish deal message with more than 1 deal"),
},
"deal ids still match": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: sameValueReturn,
},
},
expectedDealID: startDealID,
expectedError: errNotFound,
},
"new deal id success": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: newValueReturn,
},
},
marketDeals: map[abi.DealID]*api.MarketDeal{
newDealID: successDeal,
},
expectedDealID: newDealID,
expectedMarketDeal: successDeal,
},
"new deal id after other deal found": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: newValueReturn,
},
},
marketDeals: map[abi.DealID]*api.MarketDeal{
startDealID: otherDeal,
newDealID: successDeal,
},
expectedDealID: newDealID,
expectedMarketDeal: successDeal,
},
"new deal id failure": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: newValueReturn,
},
},
expectedDealID: newDealID,
expectedError: errNotFound,
},
"new deal id, failure due to other deal present": {
publishCid: &dummyCid,
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: newValueReturn,
},
},
marketDeals: map[abi.DealID]*api.MarketDeal{
newDealID: otherDeal,
},
expectedDealID: newDealID,
expectedError: xerrors.Errorf("Deal proposals did not match"),
},
}
runTestCase := func(testCase string, data struct {
searchMessageLookup *api.MsgLookup
searchMessageErr error
marketDeals map[abi.DealID]*api.MarketDeal
publishCid *cid.Cid
expectedDealID abi.DealID
expectedMarketDeal *api.MarketDeal
expectedError error
}) {
t.Run(testCase, func(t *testing.T) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
ts, err := test.MockTipset(address.TestAddress, rand.Uint64())
require.NoError(t, err)
marketDeals := make(map[marketDealKey]*api.MarketDeal)
for dealID, deal := range data.marketDeals {
marketDeals[marketDealKey{dealID, ts.Key()}] = deal
}
api := &mockGetCurrentDealInfoAPI{
SearchMessageLookup: data.searchMessageLookup,
SearchMessageErr: data.searchMessageErr,
MarketDeals: marketDeals,
}
dealID, marketDeal, _, err := GetCurrentDealInfo(ctx, ts, api, startDealID, proposal, data.publishCid)
require.Equal(t, data.expectedDealID, dealID)
require.Equal(t, data.expectedMarketDeal, marketDeal)
if data.expectedError == nil {
require.NoError(t, err)
} else {
require.EqualError(t, err, data.expectedError.Error())
}
})
}
for testCase, data := range testCases {
runTestCase(testCase, data)
}
}
type marketDealKey struct {
abi.DealID
types.TipSetKey
}
type mockGetCurrentDealInfoAPI struct {
SearchMessageLookup *api.MsgLookup
SearchMessageErr error
MarketDeals map[marketDealKey]*api.MarketDeal
}
func (mapi *mockGetCurrentDealInfoAPI) diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*miner.PreCommitChanges, error) {
return &miner.PreCommitChanges{}, nil
}
func (mapi *mockGetCurrentDealInfoAPI) StateMarketStorageDeal(ctx context.Context, dealID abi.DealID, ts types.TipSetKey) (*api.MarketDeal, error) {
deal, ok := mapi.MarketDeals[marketDealKey{dealID, ts}]
if !ok {
return nil, errNotFound
}
return deal, nil
}
func (mapi *mockGetCurrentDealInfoAPI) StateSearchMsg(context.Context, cid.Cid) (*api.MsgLookup, error) {
return mapi.SearchMessageLookup, mapi.SearchMessageErr
}
func (mapi *mockGetCurrentDealInfoAPI) StateLookupID(ctx context.Context, addr address.Address, ts types.TipSetKey) (address.Address, error) {
return addr, nil
}
func makePublishDealsReturnBytes(t *testing.T, dealIDs []abi.DealID) []byte {
buf := new(bytes.Buffer)
dealsReturn := market.PublishStorageDealsReturn{
IDs: dealIDs,
}
err := dealsReturn.MarshalCBOR(buf)
require.NoError(t, err)
return buf.Bytes()
}

View File

@ -5,6 +5,7 @@ import (
"context"
"sync"
sealing "github.com/filecoin-project/lotus/extern/storage-sealing"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
@ -19,11 +20,40 @@ import (
"github.com/filecoin-project/lotus/chain/types"
)
type sectorCommittedEventsAPI interface {
type eventsCalledAPI interface {
Called(check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error
}
func OnDealSectorPreCommitted(ctx context.Context, api getCurrentDealInfoAPI, eventsApi sectorCommittedEventsAPI, provider address.Address, dealID abi.DealID, proposal market.DealProposal, publishCid *cid.Cid, callback storagemarket.DealSectorPreCommittedCallback) error {
type dealInfoAPI interface {
GetCurrentDealInfo(ctx context.Context, tok sealing.TipSetToken, proposal *market.DealProposal, publishCid cid.Cid) (sealing.CurrentDealInfo, error)
}
type diffPreCommitsAPI interface {
diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*miner.PreCommitChanges, error)
}
type SectorCommittedManager struct {
ev eventsCalledAPI
dealInfo dealInfoAPI
dpc diffPreCommitsAPI
}
func NewSectorCommittedManager(ev eventsCalledAPI, tskAPI sealing.CurrentDealInfoTskAPI, dpcAPI diffPreCommitsAPI) *SectorCommittedManager {
dim := &sealing.CurrentDealInfoManager{
CDAPI: &sealing.CurrentDealInfoAPIAdapter{CurrentDealInfoTskAPI: tskAPI},
}
return newSectorCommittedManager(ev, dim, dpcAPI)
}
func newSectorCommittedManager(ev eventsCalledAPI, dealInfo dealInfoAPI, dpcAPI diffPreCommitsAPI) *SectorCommittedManager {
return &SectorCommittedManager{
ev: ev,
dealInfo: dealInfo,
dpc: dpcAPI,
}
}
func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, provider address.Address, proposal market.DealProposal, publishCid cid.Cid, callback storagemarket.DealSectorPreCommittedCallback) error {
// Ensure callback is only called once
var once sync.Once
cb := func(sectorNumber abi.SectorNumber, isActive bool, err error) {
@ -34,7 +64,7 @@ func OnDealSectorPreCommitted(ctx context.Context, api getCurrentDealInfoAPI, ev
// First check if the deal is already active, and if so, bail out
checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) {
di, isActive, publishTs, err := checkIfDealAlreadyActive(ctx, api, ts, dealID, proposal, publishCid)
dealInfo, isActive, err := mgr.checkIfDealAlreadyActive(ctx, ts, &proposal, publishCid)
if err != nil {
// Note: the error returned from here will end up being returned
// from OnDealSectorPreCommitted so no need to call the callback
@ -54,24 +84,19 @@ func OnDealSectorPreCommitted(ctx context.Context, api getCurrentDealInfoAPI, ev
// when the client node was down after the deal was published, and when
// the precommit containing it landed on chain)
if publishTs == types.EmptyTSK {
lookup, err := api.StateSearchMsg(ctx, *publishCid)
if err != nil {
return false, false, err
}
if lookup != nil { // can be nil in tests
publishTs = lookup.TipSet
}
publishTs, err := types.TipSetKeyFromBytes(dealInfo.PublishMsgTipSet)
if err != nil {
return false, false, err
}
diff, err := api.diffPreCommits(ctx, provider, publishTs, ts.Key())
diff, err := mgr.dpc.diffPreCommits(ctx, provider, publishTs, ts.Key())
if err != nil {
return false, false, err
}
for _, info := range diff.Added {
for _, d := range info.Info.DealIDs {
if d == di {
if d == dealInfo.DealID {
cb(info.Info.SectorNumber, false, nil)
return true, false, nil
}
@ -103,7 +128,7 @@ func OnDealSectorPreCommitted(ctx context.Context, api getCurrentDealInfoAPI, ev
// If the deal hasn't been activated by the proposed start epoch, the
// deal will timeout (when msg == nil it means the timeout epoch was reached)
if msg == nil {
err = xerrors.Errorf("deal %d was not activated by proposed deal start epoch %d", dealID, proposal.StartEpoch)
err = xerrors.Errorf("deal with piece CID %s was not activated by proposed deal start epoch %d", proposal.PieceCID, proposal.StartEpoch)
return false, err
}
@ -118,16 +143,16 @@ func OnDealSectorPreCommitted(ctx context.Context, api getCurrentDealInfoAPI, ev
return false, xerrors.Errorf("unmarshal pre commit: %w", err)
}
// When the deal is published, the deal ID may change, so get the
// When there is a reorg, the deal ID may change, so get the
// current deal ID from the publish message CID
dealID, _, _, err = GetCurrentDealInfo(ctx, ts, api, dealID, proposal, publishCid)
res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key().Bytes(), &proposal, publishCid)
if err != nil {
return false, err
}
// Check through the deal IDs associated with this message
for _, did := range params.DealIDs {
if did == dealID {
if did == res.DealID {
// Found the deal ID in this message. Callback with the sector ID.
cb(params.SectorNumber, false, nil)
return false, nil
@ -144,14 +169,14 @@ func OnDealSectorPreCommitted(ctx context.Context, api getCurrentDealInfoAPI, ev
return nil
}
if err := eventsApi.Called(checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil {
if err := mgr.ev.Called(checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil {
return xerrors.Errorf("failed to set up called handler: %w", err)
}
return nil
}
func OnDealSectorCommitted(ctx context.Context, api getCurrentDealInfoAPI, eventsApi sectorCommittedEventsAPI, provider address.Address, dealID abi.DealID, sectorNumber abi.SectorNumber, proposal market.DealProposal, publishCid *cid.Cid, callback storagemarket.DealSectorCommittedCallback) error {
func (mgr *SectorCommittedManager) OnDealSectorCommitted(ctx context.Context, provider address.Address, sectorNumber abi.SectorNumber, proposal market.DealProposal, publishCid cid.Cid, callback storagemarket.DealSectorCommittedCallback) error {
// Ensure callback is only called once
var once sync.Once
cb := func(err error) {
@ -162,7 +187,7 @@ func OnDealSectorCommitted(ctx context.Context, api getCurrentDealInfoAPI, event
// First check if the deal is already active, and if so, bail out
checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) {
_, isActive, _, err := checkIfDealAlreadyActive(ctx, api, ts, dealID, proposal, publishCid)
_, isActive, err := mgr.checkIfDealAlreadyActive(ctx, ts, &proposal, publishCid)
if err != nil {
// Note: the error returned from here will end up being returned
// from OnDealSectorCommitted so no need to call the callback
@ -208,7 +233,7 @@ func OnDealSectorCommitted(ctx context.Context, api getCurrentDealInfoAPI, event
// If the deal hasn't been activated by the proposed start epoch, the
// deal will timeout (when msg == nil it means the timeout epoch was reached)
if msg == nil {
err := xerrors.Errorf("deal %d was not activated by proposed deal start epoch %d", dealID, proposal.StartEpoch)
err := xerrors.Errorf("deal with piece CID %s was not activated by proposed deal start epoch %d", proposal.PieceCID, proposal.StartEpoch)
return false, err
}
@ -218,17 +243,17 @@ func OnDealSectorCommitted(ctx context.Context, api getCurrentDealInfoAPI, event
}
// Get the deal info
_, sd, _, err := GetCurrentDealInfo(ctx, ts, api, dealID, proposal, publishCid)
res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key().Bytes(), &proposal, publishCid)
if err != nil {
return false, xerrors.Errorf("failed to look up deal on chain: %w", err)
}
// Make sure the deal is active
if sd.State.SectorStartEpoch < 1 {
return false, xerrors.Errorf("deal wasn't active: deal=%d, parentState=%s, h=%d", dealID, ts.ParentState(), ts.Height())
if res.MarketDeal.State.SectorStartEpoch < 1 {
return false, xerrors.Errorf("deal wasn't active: deal=%d, parentState=%s, h=%d", res.DealID, ts.ParentState(), ts.Height())
}
log.Infof("Storage deal %d activated at epoch %d", dealID, sd.State.SectorStartEpoch)
log.Infof("Storage deal %d activated at epoch %d", res.DealID, res.MarketDeal.State.SectorStartEpoch)
cb(nil)
@ -241,29 +266,29 @@ func OnDealSectorCommitted(ctx context.Context, api getCurrentDealInfoAPI, event
return nil
}
if err := eventsApi.Called(checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil {
if err := mgr.ev.Called(checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil {
return xerrors.Errorf("failed to set up called handler: %w", err)
}
return nil
}
func checkIfDealAlreadyActive(ctx context.Context, api getCurrentDealInfoAPI, ts *types.TipSet, dealID abi.DealID, proposal market.DealProposal, publishCid *cid.Cid) (abi.DealID, bool, types.TipSetKey, error) {
di, sd, publishTs, err := GetCurrentDealInfo(ctx, ts, api, dealID, proposal, publishCid)
func (mgr *SectorCommittedManager) checkIfDealAlreadyActive(ctx context.Context, ts *types.TipSet, proposal *market.DealProposal, publishCid cid.Cid) (sealing.CurrentDealInfo, bool, error) {
res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key().Bytes(), proposal, publishCid)
if err != nil {
// TODO: This may be fine for some errors
return 0, false, types.EmptyTSK, xerrors.Errorf("failed to look up deal on chain: %w", err)
}
// Sector with deal is already active
if sd.State.SectorStartEpoch > 0 {
return 0, true, publishTs, nil
return res, false, xerrors.Errorf("failed to look up deal on chain: %w", err)
}
// Sector was slashed
if sd.State.SlashEpoch > 0 {
return 0, false, types.EmptyTSK, xerrors.Errorf("deal %d was slashed at epoch %d", dealID, sd.State.SlashEpoch)
if res.MarketDeal.State.SlashEpoch > 0 {
return res, false, xerrors.Errorf("deal %d was slashed at epoch %d", res.DealID, res.MarketDeal.State.SlashEpoch)
}
return di, false, publishTs, nil
// Sector with deal is already active
if res.MarketDeal.State.SectorStartEpoch > 0 {
return res, true, nil
}
return res, false, nil
}

View File

@ -7,6 +7,9 @@ import (
"fmt"
"math/rand"
"testing"
"time"
sealing "github.com/filecoin-project/lotus/extern/storage-sealing"
"golang.org/x/xerrors"
@ -15,13 +18,13 @@ import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/events"
test "github.com/filecoin-project/lotus/chain/events/state/mock"
"github.com/filecoin-project/lotus/chain/types"
tutils "github.com/filecoin-project/specs-actors/v2/support/testing"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/require"
)
@ -32,14 +35,17 @@ func TestOnDealSectorPreCommitted(t *testing.T) {
publishCid := generateCids(1)[0]
sealedCid := generateCids(1)[0]
pieceCid := generateCids(1)[0]
startDealID := abi.DealID(rand.Uint64())
newDealID := abi.DealID(rand.Uint64())
newValueReturn := makePublishDealsReturnBytes(t, []abi.DealID{newDealID})
dealID := abi.DealID(rand.Uint64())
sectorNumber := abi.SectorNumber(rand.Uint64())
proposal := market.DealProposal{
PieceCID: pieceCid,
PieceSize: abi.PaddedPieceSize(rand.Uint64()),
Label: "success",
PieceCID: pieceCid,
PieceSize: abi.PaddedPieceSize(rand.Uint64()),
Client: tutils.NewActorAddr(t, "client"),
Provider: tutils.NewActorAddr(t, "provider"),
StoragePricePerEpoch: abi.NewTokenAmount(1),
ProviderCollateral: abi.NewTokenAmount(1),
ClientCollateral: abi.NewTokenAmount(1),
Label: "success",
}
unfinishedDeal := &api.MarketDeal{
Proposal: proposal,
@ -48,17 +54,26 @@ func TestOnDealSectorPreCommitted(t *testing.T) {
LastUpdatedEpoch: 2,
},
}
successDeal := &api.MarketDeal{
activeDeal := &api.MarketDeal{
Proposal: proposal,
State: market.DealState{
SectorStartEpoch: 1,
LastUpdatedEpoch: 2,
},
}
slashedDeal := &api.MarketDeal{
Proposal: proposal,
State: market.DealState{
SectorStartEpoch: 1,
LastUpdatedEpoch: 2,
SlashEpoch: 2,
},
}
type testCase struct {
searchMessageLookup *api.MsgLookup
searchMessageErr error
checkTsDeals map[abi.DealID]*api.MarketDeal
currentDealInfo sealing.CurrentDealInfo
currentDealInfoErr error
currentDealInfoErr2 error
preCommitDiff *miner.PreCommitChanges
matchStates []matchState
dealStartEpochTimeout bool
expectedCBCallCount uint64
@ -69,45 +84,17 @@ func TestOnDealSectorPreCommitted(t *testing.T) {
}
testCases := map[string]testCase{
"normal sequence": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.PreCommitSector, &miner.SectorPreCommitInfo{
SectorNumber: sectorNumber,
SealedCID: sealedCid,
DealIDs: []abi.DealID{startDealID},
DealIDs: []abi.DealID{dealID},
}),
deals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
},
},
},
expectedCBCallCount: 1,
expectedCBIsActive: false,
expectedCBSectorNumber: sectorNumber,
},
"deal id changes in called": {
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: newValueReturn,
},
},
checkTsDeals: map[abi.DealID]*api.MarketDeal{
newDealID: unfinishedDeal,
},
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.PreCommitSector, &miner.SectorPreCommitInfo{
SectorNumber: sectorNumber,
SealedCID: sealedCid,
DealIDs: []abi.DealID{newDealID},
}),
deals: map[abi.DealID]*api.MarketDeal{
newDealID: unfinishedDeal,
},
},
},
expectedCBCallCount: 1,
@ -115,85 +102,98 @@ func TestOnDealSectorPreCommitted(t *testing.T) {
expectedCBSectorNumber: sectorNumber,
},
"ignores unsuccessful pre-commit message": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.PreCommitSector, &miner.SectorPreCommitInfo{
SectorNumber: sectorNumber,
SealedCID: sealedCid,
DealIDs: []abi.DealID{startDealID},
DealIDs: []abi.DealID{dealID},
}),
deals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
},
// non-zero exit code indicates unsuccessful pre-commit message
receipt: &types.MessageReceipt{ExitCode: 1},
},
},
expectedCBCallCount: 0,
},
"error on deal in check": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{},
searchMessageErr: errors.New("something went wrong"),
expectedCBCallCount: 0,
expectedError: errors.New("failed to set up called handler: failed to look up deal on chain: something went wrong"),
"deal already pre-committed": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
preCommitDiff: &miner.PreCommitChanges{
Added: []miner.SectorPreCommitOnChainInfo{{
Info: miner.SectorPreCommitInfo{
SectorNumber: sectorNumber,
DealIDs: []abi.DealID{dealID},
},
}},
},
expectedCBCallCount: 1,
expectedCBIsActive: false,
expectedCBSectorNumber: sectorNumber,
},
"sector start epoch > 0 in check": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: successDeal,
"error getting current deal info in check func": {
currentDealInfoErr: errors.New("something went wrong"),
expectedCBCallCount: 0,
expectedError: xerrors.Errorf("failed to set up called handler: failed to look up deal on chain: something went wrong"),
},
"sector already active": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: activeDeal,
},
expectedCBCallCount: 1,
expectedCBIsActive: true,
},
"error on deal in pre-commit": {
searchMessageErr: errors.New("something went wrong"),
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
"sector was slashed": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: slashedDeal,
PublishMsgTipSet: nil,
},
expectedCBCallCount: 0,
expectedError: xerrors.Errorf("failed to set up called handler: deal %d was slashed at epoch %d", dealID, slashedDeal.State.SlashEpoch),
},
"error getting current deal info in called func": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
currentDealInfoErr2: errors.New("something went wrong"),
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.PreCommitSector, &miner.SectorPreCommitInfo{
SectorNumber: sectorNumber,
SealedCID: sealedCid,
DealIDs: []abi.DealID{startDealID},
DealIDs: []abi.DealID{dealID},
}),
deals: map[abi.DealID]*api.MarketDeal{},
},
},
expectedCBCallCount: 0,
expectedError: errors.New("failed to set up called handler: something went wrong"),
expectedCBCallCount: 1,
expectedCBError: errors.New("handling applied event: something went wrong"),
},
"proposed deal epoch timeout": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: activeDeal,
},
dealStartEpochTimeout: true,
expectedCBCallCount: 1,
expectedCBError: xerrors.Errorf("handling applied event: deal %d was not activated by proposed deal start epoch 0", startDealID),
expectedCBError: xerrors.Errorf("handling applied event: deal with piece CID %s was not activated by proposed deal start epoch 0", unfinishedDeal.Proposal.PieceCID),
},
}
runTestCase := func(testCase string, data testCase) {
t.Run(testCase, func(t *testing.T) {
// ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
// defer cancel()
api := &mockGetCurrentDealInfoAPI{
SearchMessageLookup: data.searchMessageLookup,
SearchMessageErr: data.searchMessageErr,
MarketDeals: make(map[marketDealKey]*api.MarketDeal),
}
checkTs, err := test.MockTipset(provider, rand.Uint64())
require.NoError(t, err)
for dealID, deal := range data.checkTsDeals {
api.MarketDeals[marketDealKey{dealID, checkTs.Key()}] = deal
}
matchMessages := make([]matchMessage, len(data.matchStates))
for i, ms := range data.matchStates {
matchTs, err := test.MockTipset(provider, rand.Uint64())
require.NoError(t, err)
for dealID, deal := range ms.deals {
api.MarketDeals[marketDealKey{dealID, matchTs.Key()}] = deal
}
matchMessages[i] = matchMessage{
curH: 5,
msg: ms.msg,
@ -217,7 +217,18 @@ func TestOnDealSectorPreCommitted(t *testing.T) {
cbIsActive = isActive
cbError = err
}
err = OnDealSectorPreCommitted(ctx, api, eventsAPI, provider, startDealID, proposal, &publishCid, cb)
mockPCAPI := &mockPreCommitsAPI{
PCChanges: data.preCommitDiff,
}
mockDIAPI := &mockDealInfoAPI{
CurrentDealInfo: data.currentDealInfo,
CurrentDealInfo2: data.currentDealInfo,
Err: data.currentDealInfoErr,
Err2: data.currentDealInfoErr2,
}
scm := newSectorCommittedManager(eventsAPI, mockDIAPI, mockPCAPI)
err = scm.OnDealSectorPreCommitted(ctx, provider, proposal, publishCid, cb)
if data.expectedError == nil {
require.NoError(t, err)
} else {
@ -240,17 +251,19 @@ func TestOnDealSectorPreCommitted(t *testing.T) {
func TestOnDealSectorCommitted(t *testing.T) {
provider := address.TestAddress
ctx := context.Background()
publishCid := generateCids(1)[0]
pieceCid := generateCids(1)[0]
startDealID := abi.DealID(rand.Uint64())
newDealID := abi.DealID(rand.Uint64())
newValueReturn := makePublishDealsReturnBytes(t, []abi.DealID{newDealID})
dealID := abi.DealID(rand.Uint64())
sectorNumber := abi.SectorNumber(rand.Uint64())
proposal := market.DealProposal{
PieceCID: pieceCid,
PieceSize: abi.PaddedPieceSize(rand.Uint64()),
Label: "success",
PieceCID: pieceCid,
PieceSize: abi.PaddedPieceSize(rand.Uint64()),
Client: tutils.NewActorAddr(t, "client"),
Provider: tutils.NewActorAddr(t, "provider"),
StoragePricePerEpoch: abi.NewTokenAmount(1),
ProviderCollateral: abi.NewTokenAmount(1),
ClientCollateral: abi.NewTokenAmount(1),
Label: "success",
}
unfinishedDeal := &api.MarketDeal{
Proposal: proposal,
@ -259,17 +272,26 @@ func TestOnDealSectorCommitted(t *testing.T) {
LastUpdatedEpoch: 2,
},
}
successDeal := &api.MarketDeal{
activeDeal := &api.MarketDeal{
Proposal: proposal,
State: market.DealState{
SectorStartEpoch: 1,
LastUpdatedEpoch: 2,
},
}
slashedDeal := &api.MarketDeal{
Proposal: proposal,
State: market.DealState{
SectorStartEpoch: 1,
LastUpdatedEpoch: 2,
SlashEpoch: 2,
},
}
type testCase struct {
searchMessageLookup *api.MsgLookup
searchMessageErr error
checkTsDeals map[abi.DealID]*api.MarketDeal
currentDealInfo sealing.CurrentDealInfo
currentDealInfoErr error
currentDealInfo2 sealing.CurrentDealInfo
currentDealInfoErr2 error
matchStates []matchState
dealStartEpochTimeout bool
expectedCBCallCount uint64
@ -278,121 +300,118 @@ func TestOnDealSectorCommitted(t *testing.T) {
}
testCases := map[string]testCase{
"normal sequence": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
currentDealInfo2: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: activeDeal,
},
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.ProveCommitSector, &miner.ProveCommitSectorParams{
SectorNumber: sectorNumber,
}),
deals: map[abi.DealID]*api.MarketDeal{
startDealID: successDeal,
},
},
},
expectedCBCallCount: 1,
},
"deal id changes in called": {
searchMessageLookup: &api.MsgLookup{
Receipt: types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: newValueReturn,
},
},
checkTsDeals: map[abi.DealID]*api.MarketDeal{
newDealID: unfinishedDeal,
},
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.ProveCommitSector, &miner.ProveCommitSectorParams{
SectorNumber: sectorNumber,
}),
deals: map[abi.DealID]*api.MarketDeal{
newDealID: successDeal,
},
},
},
expectedCBCallCount: 1,
},
"ignores unsuccessful prove-commit message": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
currentDealInfo2: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: activeDeal,
},
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.ProveCommitSector, &miner.ProveCommitSectorParams{
SectorNumber: sectorNumber,
}),
deals: map[abi.DealID]*api.MarketDeal{
startDealID: successDeal,
},
// Exit-code 1 means the prove-commit was unsuccessful
receipt: &types.MessageReceipt{ExitCode: 1},
},
},
expectedCBCallCount: 0,
},
"error on deal in check": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{},
searchMessageErr: errors.New("something went wrong"),
"error getting current deal info in check func": {
currentDealInfoErr: errors.New("something went wrong"),
expectedCBCallCount: 0,
expectedError: errors.New("failed to set up called handler: failed to look up deal on chain: something went wrong"),
expectedError: xerrors.Errorf("failed to set up called handler: failed to look up deal on chain: something went wrong"),
},
"sector start epoch > 0 in check": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: successDeal,
"sector already active": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: activeDeal,
},
expectedCBCallCount: 1,
},
"error on deal in called": {
searchMessageErr: errors.New("something went wrong"),
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
"sector was slashed": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: slashedDeal,
},
expectedCBCallCount: 0,
expectedError: xerrors.Errorf("failed to set up called handler: deal %d was slashed at epoch %d", dealID, slashedDeal.State.SlashEpoch),
},
"error getting current deal info in called func": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
currentDealInfoErr2: errors.New("something went wrong"),
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.ProveCommitSector, &miner.ProveCommitSectorParams{
SectorNumber: sectorNumber,
}),
},
},
expectedCBCallCount: 1,
expectedCBError: xerrors.Errorf("handling applied event: failed to look up deal on chain: something went wrong"),
},
"proposed deal epoch timeout": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
dealStartEpochTimeout: true,
expectedCBCallCount: 1,
expectedCBError: xerrors.Errorf("handling applied event: deal with piece CID %s was not activated by proposed deal start epoch 0", unfinishedDeal.Proposal.PieceCID),
},
"got prove-commit but deal not active": {
currentDealInfo: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
currentDealInfo2: sealing.CurrentDealInfo{
DealID: dealID,
MarketDeal: unfinishedDeal,
},
matchStates: []matchState{
{
msg: makeMessage(t, provider, miner.Methods.ProveCommitSector, &miner.ProveCommitSectorParams{
SectorNumber: sectorNumber,
}),
deals: map[abi.DealID]*api.MarketDeal{
newDealID: successDeal,
},
},
},
expectedCBCallCount: 1,
expectedCBError: errors.New("handling applied event: failed to look up deal on chain: something went wrong"),
expectedError: errors.New("failed to set up called handler: failed to look up deal on chain: something went wrong"),
},
"proposed deal epoch timeout": {
checkTsDeals: map[abi.DealID]*api.MarketDeal{
startDealID: unfinishedDeal,
},
dealStartEpochTimeout: true,
expectedCBCallCount: 1,
expectedCBError: xerrors.Errorf("handling applied event: deal %d was not activated by proposed deal start epoch 0", startDealID),
expectedCBError: xerrors.Errorf("handling applied event: deal wasn't active: deal=%d, parentState=bafkqaaa, h=5", dealID),
},
}
runTestCase := func(testCase string, data testCase) {
t.Run(testCase, func(t *testing.T) {
// ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
// defer cancel()
api := &mockGetCurrentDealInfoAPI{
SearchMessageLookup: data.searchMessageLookup,
SearchMessageErr: data.searchMessageErr,
MarketDeals: make(map[marketDealKey]*api.MarketDeal),
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
checkTs, err := test.MockTipset(provider, rand.Uint64())
require.NoError(t, err)
for dealID, deal := range data.checkTsDeals {
api.MarketDeals[marketDealKey{dealID, checkTs.Key()}] = deal
}
matchMessages := make([]matchMessage, len(data.matchStates))
for i, ms := range data.matchStates {
matchTs, err := test.MockTipset(provider, rand.Uint64())
require.NoError(t, err)
for dealID, deal := range ms.deals {
api.MarketDeals[marketDealKey{dealID, matchTs.Key()}] = deal
}
matchMessages[i] = matchMessage{
curH: 5,
msg: ms.msg,
@ -412,7 +431,15 @@ func TestOnDealSectorCommitted(t *testing.T) {
cbCallCount++
cbError = err
}
err = OnDealSectorCommitted(ctx, api, eventsAPI, provider, startDealID, sectorNumber, proposal, &publishCid, cb)
mockPCAPI := &mockPreCommitsAPI{}
mockDIAPI := &mockDealInfoAPI{
CurrentDealInfo: data.currentDealInfo,
CurrentDealInfo2: data.currentDealInfo2,
Err: data.currentDealInfoErr,
Err2: data.currentDealInfoErr2,
}
scm := newSectorCommittedManager(eventsAPI, mockDIAPI, mockPCAPI)
err = scm.OnDealSectorCommitted(ctx, provider, sectorNumber, proposal, publishCid, cb)
if data.expectedError == nil {
require.NoError(t, err)
} else {
@ -434,7 +461,6 @@ func TestOnDealSectorCommitted(t *testing.T) {
type matchState struct {
msg *types.Message
receipt *types.MessageReceipt
deals map[abi.DealID]*api.MarketDeal
}
type matchMessage struct {
@ -476,7 +502,8 @@ func (fe *fakeEvents) Called(check events.CheckFunc, msgHnd events.MsgHandler, r
}
more, err := msgHnd(matchMessage.msg, receipt, matchMessage.ts, matchMessage.curH)
if err != nil {
return err
// error is handled through a callback rather than being returned
return nil
}
if matchMessage.doesRevert {
err := rev(fe.Ctx, matchMessage.ts)
@ -514,3 +541,32 @@ func generateCids(n int) []cid.Cid {
}
return cids
}
type mockPreCommitsAPI struct {
PCChanges *miner.PreCommitChanges
Err error
}
func (m *mockPreCommitsAPI) diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*miner.PreCommitChanges, error) {
pcc := &miner.PreCommitChanges{}
if m.PCChanges != nil {
pcc = m.PCChanges
}
return pcc, m.Err
}
type mockDealInfoAPI struct {
count int
CurrentDealInfo sealing.CurrentDealInfo
Err error
CurrentDealInfo2 sealing.CurrentDealInfo
Err2 error
}
func (m *mockDealInfoAPI) GetCurrentDealInfo(ctx context.Context, tok sealing.TipSetToken, proposal *market.DealProposal, publishCid cid.Cid) (sealing.CurrentDealInfo, error) {
m.count++
if m.count == 2 {
return m.CurrentDealInfo2, m.Err2
}
return m.CurrentDealInfo, m.Err
}

View File

@ -22,7 +22,6 @@ import (
"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/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/events"
"github.com/filecoin-project/lotus/chain/events/state"
@ -41,7 +40,6 @@ var log = logging.Logger("storageadapter")
type ProviderNodeAdapter struct {
api.FullNode
*apiWrapper
// this goes away with the data transfer module
dag dtypes.StagingDAG
@ -49,57 +47,36 @@ type ProviderNodeAdapter struct {
secb *sectorblocks.SectorBlocks
ev *events.Events
publishSpec, addBalanceSpec *api.MessageSendSpec
dsMatcher *dealStateMatcher
dealPublisher *DealPublisher
addBalanceSpec *api.MessageSendSpec
dsMatcher *dealStateMatcher
scMgr *SectorCommittedManager
}
func NewProviderNodeAdapter(fc *config.MinerFeeConfig) func(dag dtypes.StagingDAG, secb *sectorblocks.SectorBlocks, full api.FullNode) storagemarket.StorageProviderNode {
return func(dag dtypes.StagingDAG, secb *sectorblocks.SectorBlocks, full api.FullNode) storagemarket.StorageProviderNode {
func NewProviderNodeAdapter(fc *config.MinerFeeConfig) func(dag dtypes.StagingDAG, secb *sectorblocks.SectorBlocks, full api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode {
return func(dag dtypes.StagingDAG, secb *sectorblocks.SectorBlocks, full api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode {
ev := events.NewEvents(context.TODO(), full)
na := &ProviderNodeAdapter{
FullNode: full,
apiWrapper: &apiWrapper{api: full},
FullNode: full,
dag: dag,
secb: secb,
ev: events.NewEvents(context.TODO(), full),
dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(full))),
dag: dag,
secb: secb,
ev: ev,
dealPublisher: dealPublisher,
dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(full))),
}
if fc != nil {
na.publishSpec = &api.MessageSendSpec{MaxFee: abi.TokenAmount(fc.MaxPublishDealsFee)}
na.addBalanceSpec = &api.MessageSendSpec{MaxFee: abi.TokenAmount(fc.MaxMarketBalanceAddFee)}
}
na.scMgr = NewSectorCommittedManager(ev, na, &apiWrapper{api: full})
return na
}
}
func (n *ProviderNodeAdapter) PublishDeals(ctx context.Context, deal storagemarket.MinerDeal) (cid.Cid, error) {
log.Info("publishing deal")
mi, err := n.StateMinerInfo(ctx, deal.Proposal.Provider, types.EmptyTSK)
if err != nil {
return cid.Undef, err
}
params, err := actors.SerializeParams(&market2.PublishStorageDealsParams{
Deals: []market2.ClientDealProposal{deal.ClientDealProposal},
})
if err != nil {
return cid.Undef, xerrors.Errorf("serializing PublishStorageDeals params failed: %w", err)
}
// TODO: We may want this to happen after fetching data
smsg, err := n.MpoolPushMessage(ctx, &types.Message{
To: market.Address,
From: mi.Worker,
Value: types.NewInt(0),
Method: market.Methods.PublishStorageDeals,
Params: params,
}, n.publishSpec)
if err != nil {
return cid.Undef, err
}
return smsg.Cid(), nil
return n.dealPublisher.Publish(ctx, deal.ClientDealProposal)
}
func (n *ProviderNodeAdapter) OnDealComplete(ctx context.Context, deal storagemarket.MinerDeal, pieceSize abi.UnpaddedPieceSize, pieceData io.Reader) (*storagemarket.PackingResult, error) {
@ -273,12 +250,14 @@ func (n *ProviderNodeAdapter) DealProviderCollateralBounds(ctx context.Context,
return bounds.Min, bounds.Max, nil
}
// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer)
func (n *ProviderNodeAdapter) OnDealSectorPreCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, proposal market2.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorPreCommittedCallback) error {
return OnDealSectorPreCommitted(ctx, n, n.ev, provider, dealID, market.DealProposal(proposal), publishCid, cb)
return n.scMgr.OnDealSectorPreCommitted(ctx, provider, market.DealProposal(proposal), *publishCid, cb)
}
// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer)
func (n *ProviderNodeAdapter) OnDealSectorCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, sectorNumber abi.SectorNumber, proposal market2.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorCommittedCallback) error {
return OnDealSectorCommitted(ctx, n, n.ev, provider, dealID, sectorNumber, market.DealProposal(proposal), publishCid, cb)
return n.scMgr.OnDealSectorCommitted(ctx, provider, sectorNumber, market.DealProposal(proposal), *publishCid, cb)
}
func (n *ProviderNodeAdapter) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) {

View File

@ -377,6 +377,7 @@ func Online() Option {
Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(nil)),
Override(new(dtypes.RetrievalDealFilter), modules.RetrievalDealFilter(nil)),
Override(new(storagemarket.StorageProvider), modules.StorageProvider),
Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, nil)),
Override(new(storagemarket.StorageProviderNode), storageadapter.NewProviderNodeAdapter(nil)),
Override(HandleMigrateProviderFundsKey, modules.HandleMigrateProviderFunds),
Override(HandleRetrievalKey, modules.HandleRetrieval),
@ -519,6 +520,7 @@ func ConfigStorageMiner(c interface{}) Option {
Override(new(dtypes.RetrievalDealFilter), modules.RetrievalDealFilter(dealfilter.CliRetrievalDealFilter(cfg.Dealmaking.RetrievalFilter))),
),
Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(&cfg.Fees, &cfg.PublishMsg)),
Override(new(storagemarket.StorageProviderNode), storageadapter.NewProviderNodeAdapter(&cfg.Fees)),
Override(new(sectorstorage.SealerConfig), cfg.Storage),
@ -646,5 +648,6 @@ func Test() Option {
Unset(RunPeerMgrKey),
Unset(new(*peermgr.PeerMgr)),
Override(new(beacon.Schedule), testing.RandomBeacon),
Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, nil)),
)
}

View File

@ -33,6 +33,7 @@ type StorageMiner struct {
Common
Dealmaking DealmakingConfig
PublishMsg PublishMsgConfig
Sealing SealingConfig
Storage sectorstorage.SealerConfig
Fees MinerFeeConfig
@ -53,6 +54,15 @@ type DealmakingConfig struct {
RetrievalFilter string
}
type PublishMsgConfig struct {
// The amount of time to wait for more deals to arrive before
// publishing
PublishPeriod Duration
// The maximum number of deals to include in a single PublishStorageDeals
// message
MaxDealsPerMsg uint64
}
type SealingConfig struct {
// 0 = no limit
MaxWaitDealsSectors uint64
@ -209,6 +219,11 @@ func DefaultStorageMiner() *StorageMiner {
ExpectedSealDuration: Duration(time.Hour * 24),
},
PublishMsg: PublishMsgConfig{
PublishPeriod: Duration(time.Hour),
MaxDealsPerMsg: 8,
},
Fees: MinerFeeConfig{
MaxPreCommitGasFee: types.MustParseFIL("0.025"),
MaxCommitGasFee: types.MustParseFIL("0.05"),

View File

@ -54,8 +54,8 @@ type StateModuleAPI interface {
StateMinerProvingDeadline(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*dline.Info, error)
StateMinerPower(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error)
StateNetworkVersion(ctx context.Context, key types.TipSetKey) (network.Version, error)
StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error)
StateSearchMsg(ctx context.Context, msg cid.Cid) (*api.MsgLookup, error)
StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error)
StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error)
}

View File

@ -5,14 +5,12 @@ import (
"testing"
"time"
builder "github.com/filecoin-project/lotus/node/test"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/lib/lotuslog"
logging "github.com/ipfs/go-log/v2"
"github.com/filecoin-project/lotus/api/test"
"github.com/filecoin-project/lotus/chain/actors/policy"
"github.com/filecoin-project/lotus/lib/lotuslog"
builder "github.com/filecoin-project/lotus/node/test"
logging "github.com/ipfs/go-log/v2"
)
func init() {
@ -57,6 +55,9 @@ func TestAPIDealFlow(t *testing.T) {
t.Run("TestFastRetrievalDealFlow", func(t *testing.T) {
test.TestFastRetrievalDealFlow(t, builder.MockSbBuilder, blockTime, dealStartEpoch)
})
t.Run("TestPublishDealsBatching", func(t *testing.T) {
test.TestPublishDealsBatching(t, builder.MockSbBuilder, blockTime, dealStartEpoch)
})
}
func TestAPIDealFlowReal(t *testing.T) {

View File

@ -288,7 +288,11 @@ func mockBuilderOpts(t *testing.T, fullOpts []test.FullNodeOpts, storage []test.
genMiner := maddrs[i]
wa := genms[i].Worker
storers[i] = CreateTestStorageNode(ctx, t, wa, genMiner, pk, f, mn, node.Options())
opts := def.Opts
if opts == nil {
opts = node.Options()
}
storers[i] = CreateTestStorageNode(ctx, t, wa, genMiner, pk, f, mn, opts)
if err := storers[i].StorageAddLocal(ctx, presealDirs[i]); err != nil {
t.Fatalf("%+v", err)
}
@ -455,12 +459,17 @@ func mockSbBuilderOpts(t *testing.T, fullOpts []test.FullNodeOpts, storage []tes
}
}
opts := def.Opts
if opts == nil {
opts = node.Options()
}
storers[i] = CreateTestStorageNode(ctx, t, genms[i].Worker, maddrs[i], pidKeys[i], f, mn, node.Options(
node.Override(new(sectorstorage.SectorManager), func() (sectorstorage.SectorManager, error) {
return mock.NewMockSectorMgr(sectors), nil
}),
node.Override(new(ffiwrapper.Verifier), mock.MockVerifier),
node.Unset(new(*sectorstorage.Manager)),
opts,
))
if rpc {