on chain deals: Deals make it to the chain

This commit is contained in:
Łukasz Magiera 2019-10-23 19:39:14 +02:00
parent 61e14d0f4c
commit fabd074165
18 changed files with 219 additions and 82 deletions

View File

@ -121,9 +121,31 @@ func (sdp *StorageDealProposal) Verify() error {
return sdp.ProposerSignature.Verify(sdp.Client, buf.Bytes())
}
func (d *StorageDeal) Sign(ctx context.Context, sign SignFunc) error {
var buf bytes.Buffer
if err := d.Proposal.MarshalCBOR(&buf); err != nil {
return err
}
sig, err := sign(ctx, buf.Bytes())
if err != nil {
return err
}
d.CounterSignature = sig
return nil
}
func (d *StorageDeal) Verify(proposerWorker address.Address) error {
var buf bytes.Buffer
if err := d.Proposal.MarshalCBOR(&buf); err != nil {
return err
}
return d.CounterSignature.Verify(proposerWorker, buf.Bytes())
}
type StorageDeal struct {
Proposal StorageDealProposal
CounterSignature types.Signature
CounterSignature *types.Signature
}
type OnChainDeal struct {
@ -214,7 +236,7 @@ func (sma StorageMarketActor) AddBalance(act *types.Actor, vmctx types.VMContext
func setMarketBalances(vmctx types.VMContext, nd *hamt.Node, set map[address.Address]StorageParticipantBalance) (cid.Cid, ActorError) {
for addr, b := range set {
if err := nd.Set(vmctx.Context(), string(addr.Bytes()), b); err != nil {
if err := nd.Set(vmctx.Context(), string(addr.Bytes()), &b); err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "setting new balance")
}
}
@ -283,7 +305,6 @@ func (sma StorageMarketActor) PublishStorageDeals(act *types.Actor, vmctx types.
deals, err := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Deals)
if err != nil {
// TODO: kind of annoying that this can be caused by gas, otherwise could be fatal
return nil, aerrors.HandleExternalError(err, "loading deals amt")
}
@ -298,7 +319,7 @@ func (sma StorageMarketActor) PublishStorageDeals(act *types.Actor, vmctx types.
return nil, err
}
err := deals.Set(self.NextDealID, OnChainDeal{Deal: deal})
err := deals.Set(self.NextDealID, &OnChainDeal{Deal: deal})
if err != nil {
return nil, aerrors.HandleExternalError(err, "setting deal in deal AMT")
}
@ -338,37 +359,33 @@ func (self *StorageMarketState) validateDeal(vmctx types.VMContext, deal Storage
return aerrors.New(1, "deal proposal already expired")
}
var proposalBuf bytes.Buffer
err := deal.Proposal.MarshalCBOR(&proposalBuf)
if err != nil {
return aerrors.HandleExternalError(err, "serializing deal proposal failed")
if err := deal.Proposal.Verify(); err != nil {
return aerrors.Absorb(err, 2, "verifying proposer signature")
}
err = deal.Proposal.ProposerSignature.Verify(deal.Proposal.Client, proposalBuf.Bytes())
workerBytes, aerr := vmctx.Send(deal.Proposal.Provider, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
if aerr != nil {
return aerr
}
providerWorker, err := address.NewFromBytes(workerBytes)
if err != nil {
return aerrors.HandleExternalError(err, "verifying proposer signature")
return aerrors.HandleExternalError(err, "parsing provider worker address bytes")
}
var dealBuf bytes.Buffer
err = deal.MarshalCBOR(&dealBuf)
err = deal.Verify(providerWorker)
if err != nil {
return aerrors.HandleExternalError(err, "serializing deal failed")
}
err = deal.CounterSignature.Verify(deal.Proposal.Provider, dealBuf.Bytes())
if err != nil {
return aerrors.HandleExternalError(err, "verifying provider signature")
return aerrors.Absorb(err, 2, "verifying provider signature")
}
// TODO: maybe this is actually fine
if vmctx.Message().From != deal.Proposal.Provider && vmctx.Message().From != deal.Proposal.Client {
if vmctx.Message().From != providerWorker && vmctx.Message().From != deal.Proposal.Client {
return aerrors.New(4, "message not sent by deal participant")
}
// TODO: REVIEW: Do we want to check if provider exists in the power actor?
// TODO: do some caching (changes gas so needs to be in spec too)
b, bnd, aerr := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, deal.Proposal.Client, deal.Proposal.Provider)
b, bnd, aerr := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, deal.Proposal.Client, providerWorker)
if aerr != nil {
return aerrors.Wrap(aerr, "getting client, and provider balances")
}
@ -426,7 +443,16 @@ func (sma StorageMarketActor) ActivateStorageDeals(act *types.Actor, vmctx types
return nil, aerrors.HandleExternalError(err, "getting del info failed")
}
if vmctx.Message().From != dealInfo.Deal.Proposal.Provider {
workerBytes, err := vmctx.Send(dealInfo.Deal.Proposal.Provider, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
if err != nil {
return nil, err
}
providerWorker, eerr := address.NewFromBytes(workerBytes)
if eerr != nil {
return nil, aerrors.HandleExternalError(eerr, "parsing provider worker address bytes")
}
if vmctx.Message().From != providerWorker {
return nil, aerrors.New(1, "ActivateStorageDeals can only be called by deal provider")
}

View File

@ -3197,9 +3197,21 @@ func (t *StorageDeal) UnmarshalCBOR(r io.Reader) error {
{
pb, err := br.PeekByte()
if err != nil {
return err
}
if pb == cbg.CborNull[0] {
var nbuf [1]byte
if _, err := br.Read(nbuf[:]); err != nil {
return err
}
} else {
t.CounterSignature = new(types.Signature)
if err := t.CounterSignature.UnmarshalCBOR(br); err != nil {
return err
}
}
}
return nil

View File

@ -2,6 +2,7 @@ package deals
import (
"context"
"github.com/filecoin-project/lotus/node/impl/full"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
@ -42,6 +43,7 @@ type Client struct {
w *wallet.Wallet
dag dtypes.ClientDAG
discovery *discovery.Local
mpool full.MpoolAPI
deals ClientStateStore
conns map[cid.Cid]inet.Stream
@ -59,7 +61,7 @@ type clientDealUpdate struct {
err error
}
func NewClient(sm *stmgr.StateManager, chain *store.ChainStore, h host.Host, w *wallet.Wallet, ds dtypes.MetadataDS, dag dtypes.ClientDAG, discovery *discovery.Local) *Client {
func NewClient(sm *stmgr.StateManager, chain *store.ChainStore, h host.Host, w *wallet.Wallet, ds dtypes.MetadataDS, dag dtypes.ClientDAG, discovery *discovery.Local, mpool full.MpoolAPI) *Client {
c := &Client{
sm: sm,
chain: chain,
@ -67,6 +69,7 @@ func NewClient(sm *stmgr.StateManager, chain *store.ChainStore, h host.Host, w *
w: w,
dag: dag,
discovery: discovery,
mpool: mpool,
deals: ClientStateStore{StateStore{ds: namespace.Wrap(ds, datastore.NewKey("/deals/client"))}},
conns: map[cid.Cid]inet.Stream{},
@ -167,6 +170,37 @@ type ClientDealProposal struct {
}
func (c *Client) Start(ctx context.Context, p ClientDealProposal) (cid.Cid, error) {
// check market funds
clientMarketBalance, err := c.sm.MarketBalance(ctx, p.Client, nil)
if err != nil {
return cid.Undef, xerrors.Errorf("getting client market balance failed: %w", err)
}
if clientMarketBalance.Available.LessThan(p.TotalPrice) {
// TODO: move to a smarter market funds manager
smsg, err := c.mpool.MpoolPushMessage(ctx, &types.Message{
To: actors.StorageMarketAddress,
From: p.Client,
Value: p.TotalPrice,
GasPrice: types.NewInt(0),
GasLimit: types.NewInt(1000000),
Method: actors.SMAMethods.AddBalance,
})
if err != nil {
return cid.Undef, err
}
_, r, err := c.sm.WaitForMessage(ctx, smsg.Cid())
if err != nil {
return cid.Undef, err
}
if r.ExitCode != 0 {
return cid.Undef, xerrors.Errorf("adding funds to storage miner market actor failed: exit %d", r.ExitCode)
}
}
proposal := &actors.StorageDealProposal{
PieceRef: p.Data.Bytes(),
PieceSize: p.DataSize,

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/stmgr"
"golang.org/x/xerrors"
)
@ -43,22 +44,27 @@ func (c *Client) new(ctx context.Context, deal ClientDeal) error {
return xerrors.Errorf("getting deal pubsish message: %w", err)
}
if pubmsg.From != deal.Proposal.Provider {
return xerrors.Errorf("Deal wasn't published by storage provider: from=%s, provider=%s", pubmsg.From, deal.Proposal.Provider)
pw, err := stmgr.GetMinerWorker(ctx, c.sm, nil, deal.Proposal.Provider)
if err != nil {
return xerrors.Errorf("getting miner worker failed: %w", err)
}
if pubmsg.From != pw {
return xerrors.Errorf("deal wasn't published by storage provider: from=%s, provider=%s", pubmsg.From, deal.Proposal.Provider)
}
if pubmsg.To != actors.StorageMarketAddress {
return xerrors.Errorf("Deal publish message wasn't set to StorageMarket actor (to=%s)", pubmsg.To)
return xerrors.Errorf("deal publish message wasn't set to StorageMarket actor (to=%s)", pubmsg.To)
}
if pubmsg.Method != actors.SMAMethods.PublishStorageDeals {
return xerrors.Errorf("Deal publish message called incorrect method (method=%s)", pubmsg.Method)
return xerrors.Errorf("deal publish message called incorrect method (method=%s)", pubmsg.Method)
}
// TODO: timeout
_, ret, err := c.sm.WaitForMessage(ctx, *resp.PublishMessage)
if err != nil {
return xerrors.Errorf("Waiting for deal publish message: %w", err)
return xerrors.Errorf("waiting for deal publish message: %w", err)
}
if ret.ExitCode != 0 {
return xerrors.Errorf("deal publish failed: exit=%d", ret.ExitCode)

View File

@ -160,7 +160,7 @@ func (p *Provider) onIncoming(deal MinerDeal) {
func (p *Provider) onUpdated(ctx context.Context, update minerDealUpdate) {
log.Infof("Deal %s updated state to %d", update.id, update.newState)
if update.err != nil {
log.Errorf("deal %s failed: %s", update.id, update.err)
log.Errorf("deal %s (newSt: %d) failed: %s", update.id, update.newState, update.err)
p.failDeal(update.id, update.err)
return
}

View File

@ -145,7 +145,7 @@ func (p *Provider) saveAsk(a *types.SignedStorageAsk) error {
func (c *Client) checkAskSignature(ask *types.SignedStorageAsk) error {
tss := c.sm.ChainStore().GetHeaviestTipSet().ParentState()
w, err := stmgr.GetMinerWorker(context.TODO(), c.sm, tss, ask.Ask.Miner)
w, err := stmgr.GetMinerWorkerRaw(context.TODO(), c.sm, tss, ask.Ask.Miner)
if err != nil {
return xerrors.Errorf("failed to get worker for miner in ask", err)
}

View File

@ -2,15 +2,16 @@ package deals
import (
"context"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-sectorbuilder/sealing_state"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-merkledag"
unixfile "github.com/ipfs/go-unixfs/file"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/sectorbuilder"
"github.com/filecoin-project/lotus/storage/sectorblocks"
@ -40,11 +41,11 @@ func (p *Provider) handle(ctx context.Context, deal MinerDeal, cb providerHandle
// ACCEPTED
func (p *Provider) addMarketFunds(ctx context.Context, deal MinerDeal) error {
func (p *Provider) addMarketFunds(ctx context.Context, worker address.Address, deal MinerDeal) error {
log.Info("Adding market funds for storage collateral")
smsg, err := p.full.MpoolPushMessage(ctx, &types.Message{
To: actors.StorageMarketAddress,
From: deal.Proposal.Provider,
From: worker,
Value: deal.Proposal.StorageCollateral,
GasPrice: types.NewInt(0),
GasLimit: types.NewInt(1000000),
@ -75,6 +76,14 @@ func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal)
return nil, xerrors.Errorf("deal proposal with unsupported serialization: %s", deal.Proposal.PieceSerialization)
}
head, err := p.full.ChainHead(ctx)
if err != nil {
return nil, err
}
if head.Height() >= deal.Proposal.ProposalExpiration {
return nil, xerrors.Errorf("deal proposal already expired")
}
// TODO: check StorageCollateral / StoragePrice
// check market funds
@ -89,28 +98,48 @@ func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal)
return nil, xerrors.New("clientMarketBalance.Available too small")
}
providerMarketBalance, err := p.full.StateMarketBalance(ctx, deal.Proposal.Provider, nil)
waddr, err := p.full.StateMinerWorker(ctx, deal.Proposal.Provider, nil)
if err != nil {
return nil, err
}
providerMarketBalance, err := p.full.StateMarketBalance(ctx, waddr, nil)
if err != nil {
return nil, xerrors.Errorf("getting provider market balance failed: %w", err)
}
// TODO: this needs to be atomic
if providerMarketBalance.Available.LessThan(deal.Proposal.StorageCollateral) {
if err := p.addMarketFunds(ctx, deal); err != nil {
if err := p.addMarketFunds(ctx, waddr, deal); err != nil {
return nil, err
}
}
log.Info("publishing deal")
storageDeal := actors.StorageDeal{
Proposal: deal.Proposal,
}
if err := api.SignWith(ctx, p.full.WalletSign, waddr, &storageDeal); err != nil {
return nil, xerrors.Errorf("signing storage deal failed: ", err)
}
params, err := actors.SerializeParams(&actors.PublishStorageDealsParams{
Deals: []actors.StorageDeal{storageDeal},
})
if err != nil {
return nil, xerrors.Errorf("serializing PublishStorageDeals params failed: ", err)
}
// TODO: We may want this to happen after fetching data
smsg, err := p.full.MpoolPushMessage(ctx, &types.Message{
To: actors.StorageMarketAddress,
From: deal.Proposal.Provider,
From: waddr,
Value: types.NewInt(0),
GasPrice: types.NewInt(0),
GasLimit: types.NewInt(1000000),
Method: actors.SMAMethods.PublishStorageDeals,
Params: params,
})
if err != nil {
return nil, err

View File

@ -206,7 +206,7 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add
st := pts.ParentState()
worker, err := stmgr.GetMinerWorker(ctx, cg.sm, st, m)
worker, err := stmgr.GetMinerWorkerRaw(ctx, cg.sm, st, m)
if err != nil {
return nil, nil, err
}
@ -385,7 +385,7 @@ func (mca mca) StateMinerPower(ctx context.Context, maddr address.Address, ts *t
}
func (mca mca) StateMinerWorker(ctx context.Context, maddr address.Address, ts *types.TipSet) (address.Address, error) {
return stmgr.GetMinerWorker(ctx, mca.sm, ts.ParentState(), maddr)
return stmgr.GetMinerWorkerRaw(ctx, mca.sm, ts.ParentState(), maddr)
}
func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*types.Signature, error) {

View File

@ -27,7 +27,7 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wal
height := parents.Height() + uint64(len(tickets))
worker, err := stmgr.GetMinerWorker(ctx, sm, st, miner)
worker, err := stmgr.GetMinerWorkerRaw(ctx, sm, st, miner)
if err != nil {
return nil, xerrors.Errorf("failed to get miner worker: %w", err)
}

View File

@ -459,3 +459,17 @@ func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]
return out, nil
}
func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (actors.StorageParticipantBalance, error) {
var state actors.StorageMarketState
if _, err := sm.LoadActorState(ctx, actors.StorageMarketAddress, &state, ts); err != nil {
return actors.StorageParticipantBalance{}, err
}
cst := hamt.CSTFromBstore(sm.ChainStore().Blockstore())
b, _, err := actors.GetMarketBalances(ctx, cst, state.Balances, addr)
if err != nil {
return actors.StorageParticipantBalance{}, err
}
return b[0], nil
}

View File

@ -16,7 +16,7 @@ import (
"golang.org/x/xerrors"
)
func GetMinerWorker(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) {
func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) {
recp, err := sm.CallRaw(ctx, &types.Message{
To: maddr,
From: maddr,
@ -118,7 +118,7 @@ func GetMinerPeerID(ctx context.Context, sm *StateManager, ts *types.TipSet, mad
Method: actors.MAMethods.GetPeerID,
}, ts)
if err != nil {
return "", xerrors.Errorf("callRaw failed: %w", err)
return "", xerrors.Errorf("call failed: %w", err)
}
if recp.ExitCode != 0 {
@ -128,6 +128,23 @@ func GetMinerPeerID(ctx context.Context, sm *StateManager, ts *types.TipSet, mad
return peer.IDFromBytes(recp.Return)
}
func GetMinerWorker(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (address.Address, error) {
recp, err := sm.Call(ctx, &types.Message{
To: maddr,
From: maddr,
Method: actors.MAMethods.GetWorkerAddr,
}, ts)
if err != nil {
return address.Undef, xerrors.Errorf("call failed: %w", err)
}
if recp.ExitCode != 0 {
return address.Undef, xerrors.Errorf("getting miner peer ID failed (exit code %d)", recp.ExitCode)
}
return address.NewFromBytes(recp.Return)
}
func GetMinerProvingPeriodEnd(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (uint64, error) {
var mas actors.StorageMinerActorState
_, err := sm.LoadActorState(ctx, maddr, &mas, ts)

View File

@ -482,9 +482,9 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
return xerrors.Errorf("minerIsValid failed: %w", err)
}
waddr, err := stmgr.GetMinerWorker(ctx, syncer.sm, stateroot, h.Miner)
waddr, err := stmgr.GetMinerWorkerRaw(ctx, syncer.sm, stateroot, h.Miner)
if err != nil {
return xerrors.Errorf("GetMinerWorker failed: %w", err)
return xerrors.Errorf("GetMinerWorkerRaw failed: %w", err)
}
if err := h.CheckBlockSignature(ctx, waddr); err != nil {

View File

@ -212,7 +212,7 @@ func (h handlers) handle(ctx context.Context, req request, w func(func(io.Writer
if handler.errOut != -1 {
err := callResult[handler.errOut].Interface()
if err != nil {
log.Warnf("error in RPC call to '%s': %s", req.Method, err)
log.Warnf("error in RPC call to '%s': %+v", req.Method, err)
resp.Error = &respError{
Code: 1,
Message: err.(error).Error(),

View File

@ -6,6 +6,9 @@ import State from "./State"
import methods from "./chain/methods"
function truncAddr(addr, len) {
if (!addr) {
return "<!nil>"
}
if (addr.length > len) {
return <abbr title={addr}>{addr.substr(0, len - 3) + '..'}</abbr>
}

View File

@ -104,11 +104,19 @@ class MarketState extends React.Component {
return <div>
<div>
<div>Participants:</div>
{Object.keys(this.state.participants).map(p => <span>{p}</span>)}
<table>
<tr><td>Address</td><td>Available</td><td>Locked</td></tr>
{Object.keys(this.state.participants).map(p => <tr>
<td><Address addr={p} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
<td>{this.state.participants[p].Available}</td>
<td>{this.state.participants[p].Locked}</td>
</tr>)}
</table>
</div>
<div>
<div>---</div>
<div>Deals:</div>
{this.state.deals.map(d => <span>{d}</span>)}
{Object.keys(this.state.deals).map(d => <div>{d}</div>)}
</div>
</div>
}

View File

@ -1,10 +1,10 @@
export default {
"account": [
"filecoin/1.0/AccountActor": [
"Send",
"Constructor",
"GetAddress",
],
"smarket": [
"filecoin/1.0/StoragePowerActor": [
"Send",
"Constructor",
"CreateStorageMiner",
@ -15,7 +15,21 @@ export default {
"IsMiner",
"StorageCollateralForSize"
],
"sminer": [
"filecoin/1.0/StorageMarketActor": [
"Send",
"Constructor",
"WithdrawBalance",
"AddBalance",
"CheckLockedBalance",
"PublishStorageDeals",
"HandleCronAction",
"SettleExpiredDeals",
"ProcessStorageDealsPayment",
"SlashStorageDealCollateral",
"GetLastExpirationFromDealIDs",
"ActivateStorageDeals",
],
"filecoin/1.0/StorageMinerActor": [
"Send",
"Constructor",
"CommitSector",
@ -36,7 +50,7 @@ export default {
"PaymentVerifyInclusion",
"PaymentVerifySector",
],
"multisig": [
"filecoin/1.0/MultisigActor": [
"Send",
"Constructor",
"Propose",
@ -48,13 +62,13 @@ export default {
"SwapSigner",
"ChangeRequirement",
],
"init": [
"filecoin/1.0/InitActor": [
"Send",
"Constructor",
"Exec",
"GetIdForAddress"
],
"paych": [
"filecoin/1.0/PaymentChannelActor": [
"Send",
"Constructor",
"UpdateChannelState",

View File

@ -5,6 +5,7 @@ import (
"errors"
"golang.org/x/xerrors"
"io"
"math"
"os"
"github.com/ipfs/go-blockservice"
@ -81,6 +82,7 @@ func (a *API) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.A
proposal := deals.ClientDealProposal{
Data: data,
TotalPrice: total,
ProposalExpiration: math.MaxUint64, // TODO: set something reasonable
Duration: blocksDuration,
ProviderAddress: miner,
Client: self,

View File

@ -58,25 +58,7 @@ func (a *StateAPI) StateMinerPower(ctx context.Context, maddr address.Address, t
}
func (a *StateAPI) StateMinerWorker(ctx context.Context, m address.Address, ts *types.TipSet) (address.Address, error) {
ret, err := a.StateManager.Call(ctx, &types.Message{
From: m,
To: m,
Method: actors.MAMethods.GetWorkerAddr,
}, ts)
if err != nil {
return address.Undef, xerrors.Errorf("failed to get miner worker addr: %w", err)
}
if ret.ExitCode != 0 {
return address.Undef, xerrors.Errorf("failed to get miner worker addr (exit code %d)", ret.ExitCode)
}
w, err := address.NewFromBytes(ret.Return)
if err != nil {
return address.Undef, xerrors.Errorf("GetWorkerAddr returned malformed address: %w", err)
}
return w, nil
return stmgr.GetMinerWorker(ctx, a.StateManager, ts, m)
}
func (a *StateAPI) StateMinerPeerID(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) {
@ -232,17 +214,7 @@ func (a *StateAPI) StateListActors(ctx context.Context, ts *types.TipSet) ([]add
}
func (a *StateAPI) StateMarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (actors.StorageParticipantBalance, error) {
var state actors.StorageMarketState
if _, err := a.StateManager.LoadActorState(ctx, actors.StorageMarketAddress, &state, ts); err != nil {
return actors.StorageParticipantBalance{}, err
}
cst := hamt.CSTFromBstore(a.StateManager.ChainStore().Blockstore())
b, _, err := actors.GetMarketBalances(ctx, cst, state.Balances, addr)
if err != nil {
return actors.StorageParticipantBalance{}, err
}
return b[0], nil
return a.StateManager.MarketBalance(ctx, addr, ts)
}
func (a *StateAPI) StateMarketParticipants(ctx context.Context, ts *types.TipSet) (map[string]actors.StorageParticipantBalance, error) {