Merge pull request #415 from filecoin-project/feat/deals-on-chain

On-Chain deals
This commit is contained in:
Łukasz Magiera 2019-10-25 17:03:25 +02:00 committed by GitHub
commit eeca3d86df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 3774 additions and 1159 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
@ -132,6 +133,9 @@ type FullNode interface {
StateWaitMsg(context.Context, cid.Cid) (*MsgWait, error) StateWaitMsg(context.Context, cid.Cid) (*MsgWait, error)
StateListMiners(context.Context, *types.TipSet) ([]address.Address, error) StateListMiners(context.Context, *types.TipSet) ([]address.Address, error)
StateListActors(context.Context, *types.TipSet) ([]address.Address, error) StateListActors(context.Context, *types.TipSet) ([]address.Address, error)
StateMarketBalance(context.Context, address.Address, *types.TipSet) (actors.StorageParticipantBalance, error)
StateMarketParticipants(context.Context, *types.TipSet) (map[string]actors.StorageParticipantBalance, error)
StateMarketDeals(context.Context, *types.TipSet) (map[string]actors.OnChainDeal, error)
PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error) PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error)
PaychList(context.Context) ([]address.Address, error) PaychList(context.Context) ([]address.Address, error)
@ -179,6 +183,9 @@ type Version struct {
APIVersion uint32 APIVersion uint32
// TODO: git commit / os / genesis cid? // TODO: git commit / os / genesis cid?
// Seconds
BlockDelay uint64
} }
func (v Version) String() string { func (v Version) String() string {
@ -196,10 +203,9 @@ type Import struct {
type DealInfo struct { type DealInfo struct {
ProposalCid cid.Cid ProposalCid cid.Cid
State DealState State DealState
Miner address.Address Provider address.Address
PieceRef cid.Cid PieceRef []byte // cid bytes
CommP []byte
Size uint64 Size uint64
TotalPrice types.BigInt TotalPrice types.BigInt

View File

@ -8,6 +8,7 @@ import (
"github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
@ -85,20 +86,23 @@ type FullNodeStruct struct {
ClientRetrieve func(ctx context.Context, order RetrievalOrder, path string) error `perm:"admin"` ClientRetrieve func(ctx context.Context, order RetrievalOrder, path string) error `perm:"admin"`
ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*types.SignedStorageAsk, error) `perm:"read"` ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*types.SignedStorageAsk, error) `perm:"read"`
StateMinerSectors func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"` StateMinerSectors func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"`
StateMinerProvingSet func(context.Context, address.Address, *types.TipSet) ([]*SectorInfo, error) `perm:"read"` StateMinerProvingSet func(context.Context, address.Address, *types.TipSet) ([]*SectorInfo, error) `perm:"read"`
StateMinerPower func(context.Context, address.Address, *types.TipSet) (MinerPower, error) `perm:"read"` StateMinerPower func(context.Context, address.Address, *types.TipSet) (MinerPower, error) `perm:"read"`
StateMinerWorker func(context.Context, address.Address, *types.TipSet) (address.Address, error) `perm:"read"` StateMinerWorker func(context.Context, address.Address, *types.TipSet) (address.Address, error) `perm:"read"`
StateMinerPeerID func(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) `perm:"read"` StateMinerPeerID func(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) `perm:"read"`
StateMinerProvingPeriodEnd func(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error) `perm:"read"` StateMinerProvingPeriodEnd func(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error) `perm:"read"`
StateCall func(context.Context, *types.Message, *types.TipSet) (*types.MessageReceipt, error) `perm:"read"` StateCall func(context.Context, *types.Message, *types.TipSet) (*types.MessageReceipt, error) `perm:"read"`
StateReplay func(context.Context, *types.TipSet, cid.Cid) (*ReplayResults, error) `perm:"read"` StateReplay func(context.Context, *types.TipSet, cid.Cid) (*ReplayResults, error) `perm:"read"`
StateGetActor func(context.Context, address.Address, *types.TipSet) (*types.Actor, error) `perm:"read"` StateGetActor func(context.Context, address.Address, *types.TipSet) (*types.Actor, error) `perm:"read"`
StateReadState func(context.Context, *types.Actor, *types.TipSet) (*ActorState, error) `perm:"read"` StateReadState func(context.Context, *types.Actor, *types.TipSet) (*ActorState, error) `perm:"read"`
StatePledgeCollateral func(context.Context, *types.TipSet) (types.BigInt, error) `perm:"read"` StatePledgeCollateral func(context.Context, *types.TipSet) (types.BigInt, error) `perm:"read"`
StateWaitMsg func(context.Context, cid.Cid) (*MsgWait, error) `perm:"read"` StateWaitMsg func(context.Context, cid.Cid) (*MsgWait, error) `perm:"read"`
StateListMiners func(context.Context, *types.TipSet) ([]address.Address, error) `perm:"read"` StateListMiners func(context.Context, *types.TipSet) ([]address.Address, error) `perm:"read"`
StateListActors func(context.Context, *types.TipSet) ([]address.Address, error) `perm:"read"` StateListActors func(context.Context, *types.TipSet) ([]address.Address, error) `perm:"read"`
StateMarketBalance func(context.Context, address.Address, *types.TipSet) (actors.StorageParticipantBalance, error) `perm:"read"`
StateMarketParticipants func(context.Context, *types.TipSet) (map[string]actors.StorageParticipantBalance, error) `perm:"read"`
StateMarketDeals func(context.Context, *types.TipSet) (map[string]actors.OnChainDeal, error) `perm:"read"`
PaychGet func(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error) `perm:"sign"` PaychGet func(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error) `perm:"sign"`
PaychList func(context.Context) ([]address.Address, error) `perm:"read"` PaychList func(context.Context) ([]address.Address, error) `perm:"read"`
@ -388,6 +392,18 @@ func (c *FullNodeStruct) StateListActors(ctx context.Context, ts *types.TipSet)
return c.Internal.StateListActors(ctx, ts) return c.Internal.StateListActors(ctx, ts)
} }
func (c *FullNodeStruct) StateMarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (actors.StorageParticipantBalance, error) {
return c.Internal.StateMarketBalance(ctx, addr, ts)
}
func (c *FullNodeStruct) StateMarketParticipants(ctx context.Context, ts *types.TipSet) (map[string]actors.StorageParticipantBalance, error) {
return c.Internal.StateMarketParticipants(ctx, ts)
}
func (c *FullNodeStruct) StateMarketDeals(ctx context.Context, ts *types.TipSet) (map[string]actors.OnChainDeal, error) {
return c.Internal.StateMarketDeals(ctx, ts)
}
func (c *FullNodeStruct) PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error) { func (c *FullNodeStruct) PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error) {
return c.Internal.PaychGet(ctx, from, to, ensureFunds) return c.Internal.PaychGet(ctx, from, to, ensureFunds)
} }

View File

@ -6,22 +6,21 @@ import (
ma "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr"
) )
type DealState int type DealState = uint64
const ( const (
DealUnknown = DealState(iota) DealUnknown = DealState(iota)
DealRejected DealRejected // Provider didn't like the proposal
DealAccepted DealAccepted // Proposal accepted, data moved
DealStarted DealStaged // Data put into the sector
DealSealing // Data in process of being sealed
DealFailed DealFailed
DealStaged
DealSealing
DealComplete DealComplete
// Internal // Internal
DealError // deal failed with an unexpected error DealError // deal failed with an unexpected error
DealExpired
DealNoUpdate = DealUnknown DealNoUpdate = DealUnknown
) )

28
api/utils.go Normal file
View File

@ -0,0 +1,28 @@
package api
import (
"context"
"github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/types"
)
type SignFunc = func(context.Context, []byte) (*types.Signature, error)
type Signer func(context.Context, address.Address, []byte) (*types.Signature, error)
type Signable interface {
Sign(context.Context, SignFunc) error
}
func SignWith(ctx context.Context, signer Signer, addr address.Address, signable ...Signable) error {
for _, s := range signable {
err := s.Sign(ctx, func(ctx context.Context, b []byte) (*types.Signature, error) {
return signer(ctx, addr, b)
})
if err != nil {
return err
}
}
return nil
}

View File

@ -30,7 +30,7 @@ const MaxVouchersPerDeal = 768 // roughly one voucher per 10h over a year
// Consensus / Network // Consensus / Network
// Seconds // Seconds
const BlockDelay = 30 const BlockDelay = 3
// Seconds // Seconds
const AllowableClockDrift = BlockDelay * 2 const AllowableClockDrift = BlockDelay * 2
@ -51,7 +51,7 @@ const RandomnessLookback = 20
const ProvingPeriodDuration = 40 const ProvingPeriodDuration = 40
// Blocks // Blocks
const PoSTChallangeTime = 20 const PoSTChallangeTime = 35
const PowerCollateralProportion = 5 const PowerCollateralProportion = 5
const PerCapitaCollateralProportion = 1 const PerCapitaCollateralProportion = 1

View File

@ -162,7 +162,7 @@ func (ia InitActor) Exec(act *types.Actor, vmctx types.VMContext, p *ExecParams)
func IsBuiltinActor(code cid.Cid) bool { func IsBuiltinActor(code cid.Cid) bool {
switch code { switch code {
case StorageMarketActorCodeCid, StorageMinerCodeCid, AccountActorCodeCid, InitActorCodeCid, MultisigActorCodeCid, PaymentChannelActorCodeCid: case StorageMarketCodeCid, StoragePowerCodeCid, StorageMinerCodeCid, AccountCodeCid, InitCodeCid, MultisigCodeCid, PaymentChannelCodeCid:
return true return true
default: default:
return false return false
@ -170,7 +170,7 @@ func IsBuiltinActor(code cid.Cid) bool {
} }
func IsSingletonActor(code cid.Cid) bool { func IsSingletonActor(code cid.Cid) bool {
return code == StorageMarketActorCodeCid || code == InitActorCodeCid return code == StoragePowerCodeCid || code == StorageMarketCodeCid || code == InitCodeCid
} }
func (ias *InitActorState) AddActor(cst *hamt.CborIpldStore, addr address.Address) (address.Address, error) { func (ias *InitActorState) AddActor(cst *hamt.CborIpldStore, addr address.Address) (address.Address, error) {

View File

@ -18,8 +18,6 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
const POST_SECTORS_COUNT = 8192
type StorageMinerActor struct{} type StorageMinerActor struct{}
type StorageMinerActorState struct { type StorageMinerActorState struct {
@ -100,29 +98,27 @@ type StorageMinerConstructorParams struct {
} }
type maMethods struct { type maMethods struct {
Constructor uint64 Constructor uint64
CommitSector uint64 CommitSector uint64
SubmitPoSt uint64 SubmitPoSt uint64
SlashStorageFault uint64 SlashStorageFault uint64
GetCurrentProvingSet uint64 GetCurrentProvingSet uint64
ArbitrateDeal uint64 ArbitrateDeal uint64
DePledge uint64 DePledge uint64
GetOwner uint64 GetOwner uint64
GetWorkerAddr uint64 GetWorkerAddr uint64
GetPower uint64 GetPower uint64
GetPeerID uint64 GetPeerID uint64
GetSectorSize uint64 GetSectorSize uint64
UpdatePeerID uint64 UpdatePeerID uint64
ChangeWorker uint64 ChangeWorker uint64
IsSlashed uint64 IsSlashed uint64
IsLate uint64 IsLate uint64
PaymentVerifyInclusion uint64 AddFaults uint64
PaymentVerifySector uint64 SlashConsensusFault uint64
AddFaults uint64
SlashConsensusFault uint64
} }
var MAMethods = maMethods{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} var MAMethods = maMethods{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}
func (sma StorageMinerActor) Exports() []interface{} { func (sma StorageMinerActor) Exports() []interface{} {
return []interface{}{ return []interface{}{
@ -142,10 +138,8 @@ func (sma StorageMinerActor) Exports() []interface{} {
//14: sma.ChangeWorker, //14: sma.ChangeWorker,
//15: sma.IsSlashed, //15: sma.IsSlashed,
//16: sma.IsLate, //16: sma.IsLate,
17: sma.PaymentVerifyInclusion, 17: sma.AddFaults,
18: sma.PaymentVerifySector, 18: sma.SlashConsensusFault,
19: sma.AddFaults,
20: sma.SlashConsensusFault,
} }
} }
@ -205,15 +199,18 @@ func (sma StorageMinerActor) StorageMinerConstructor(act *types.Actor, vmctx typ
return nil, nil return nil, nil
} }
type CommitSectorParams struct { type OnChainSealVerifyInfo struct {
SectorID uint64 CommD []byte // TODO: update proofs code
CommD []byte
CommR []byte CommR []byte
CommRStar []byte CommRStar []byte
Proof []byte
//Epoch uint64
Proof []byte
DealIDs []uint64
SectorNumber uint64
} }
func (sma StorageMinerActor) CommitSector(act *types.Actor, vmctx types.VMContext, params *CommitSectorParams) ([]byte, ActorError) { func (sma StorageMinerActor) CommitSector(act *types.Actor, vmctx types.VMContext, params *OnChainSealVerifyInfo) ([]byte, ActorError) {
ctx := context.TODO() ctx := context.TODO()
oldstate, self, err := loadState(vmctx) oldstate, self, err := loadState(vmctx)
if err != nil { if err != nil {
@ -235,7 +232,7 @@ func (sma StorageMinerActor) CommitSector(act *types.Actor, vmctx types.VMContex
} }
// make sure the miner isnt trying to submit a pre-existing sector // make sure the miner isnt trying to submit a pre-existing sector
unique, err := SectorIsUnique(ctx, vmctx.Storage(), self.Sectors, params.SectorID) unique, err := SectorIsUnique(ctx, vmctx.Storage(), self.Sectors, params.SectorNumber)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -247,6 +244,7 @@ func (sma StorageMinerActor) CommitSector(act *types.Actor, vmctx types.VMContex
futurePower := types.BigAdd(self.Power, mi.SectorSize) futurePower := types.BigAdd(self.Power, mi.SectorSize)
collateralRequired := CollateralForPower(futurePower) collateralRequired := CollateralForPower(futurePower)
// TODO: grab from market?
if act.Balance.LessThan(collateralRequired) { if act.Balance.LessThan(collateralRequired) {
return nil, aerrors.New(3, "not enough collateral") return nil, aerrors.New(3, "not enough collateral")
} }
@ -254,7 +252,7 @@ func (sma StorageMinerActor) CommitSector(act *types.Actor, vmctx types.VMContex
// Note: There must exist a unique index in the miner's sector set for each // Note: There must exist a unique index in the miner's sector set for each
// sector ID. The `faults`, `recovered`, and `done` parameters of the // sector ID. The `faults`, `recovered`, and `done` parameters of the
// SubmitPoSt method express indices into this sector set. // SubmitPoSt method express indices into this sector set.
nssroot, err := AddToSectorSet(ctx, vmctx.Storage(), self.Sectors, params.SectorID, params.CommR, params.CommD) nssroot, err := AddToSectorSet(ctx, vmctx.Storage(), self.Sectors, params.SectorNumber, params.CommR, params.CommD)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -286,7 +284,15 @@ func (sma StorageMinerActor) CommitSector(act *types.Actor, vmctx types.VMContex
return nil, err return nil, err
} }
return nil, nil activateParams, err := SerializeParams(&ActivateStorageDealsParams{
Deals: params.DealIDs,
})
if err != nil {
return nil, err
}
_, err = vmctx.Send(StorageMarketAddress, SMAMethods.ActivateStorageDeals, types.NewInt(0), activateParams)
return nil, err
} }
type SubmitPoStParams struct { type SubmitPoStParams struct {
@ -433,7 +439,7 @@ func (sma StorageMinerActor) SubmitPoSt(act *types.Actor, vmctx types.VMContext,
return nil, err return nil, err
} }
_, err = vmctx.Send(StorageMarketAddress, SPAMethods.UpdateStorage, types.NewInt(0), enc) _, err = vmctx.Send(StoragePowerAddress, SPAMethods.UpdateStorage, types.NewInt(0), enc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -511,8 +517,8 @@ func GetFromSectorSet(ctx context.Context, s types.Storage, ss cid.Cid, sectorID
return true, comms[0], comms[1], nil return true, comms[0], comms[1], nil
} }
func ValidatePoRep(maddr address.Address, ssize types.BigInt, params *CommitSectorParams) (bool, ActorError) { func ValidatePoRep(maddr address.Address, ssize types.BigInt, params *OnChainSealVerifyInfo) (bool, ActorError) {
ok, err := sectorbuilder.VerifySeal(ssize.Uint64(), params.CommR, params.CommD, params.CommRStar, maddr, params.SectorID, params.Proof) ok, err := sectorbuilder.VerifySeal(ssize.Uint64(), params.CommR, params.CommD, params.CommRStar, maddr, params.SectorNumber, params.Proof)
if err != nil { if err != nil {
return false, aerrors.Absorb(err, 25, "verify seal failed") return false, aerrors.Absorb(err, 25, "verify seal failed")
} }
@ -634,84 +640,6 @@ type PaymentVerifyParams struct {
Proof []byte Proof []byte
} }
type PieceInclVoucherData struct { // TODO: Update spec at https://github.com/filecoin-project/specs/blob/master/actors.md#paymentverify
CommP []byte
PieceSize types.BigInt
}
type InclusionProof struct {
Sector uint64 // for CommD, also verifies the sector is in sector set
Proof []byte
}
func (sma StorageMinerActor) PaymentVerifyInclusion(act *types.Actor, vmctx types.VMContext, params *PaymentVerifyParams) ([]byte, ActorError) {
// params.Extra - PieceInclVoucherData
// params.Proof - InclusionProof
_, self, aerr := loadState(vmctx)
if aerr != nil {
return nil, aerr
}
mi, aerr := loadMinerInfo(vmctx, self)
if aerr != nil {
return nil, aerr
}
var voucherData PieceInclVoucherData
if err := cbor.DecodeInto(params.Extra, &voucherData); err != nil {
return nil, aerrors.Absorb(err, 2, "failed to decode storage voucher data for verification")
}
var proof InclusionProof
if err := cbor.DecodeInto(params.Proof, &proof); err != nil {
return nil, aerrors.Absorb(err, 3, "failed to decode storage payment proof")
}
ok, _, commD, aerr := GetFromSectorSet(context.TODO(), vmctx.Storage(), self.Sectors, proof.Sector)
if aerr != nil {
return nil, aerr
}
if !ok {
return nil, aerrors.New(4, "miner does not have required sector")
}
ok, err := sectorbuilder.VerifyPieceInclusionProof(mi.SectorSize.Uint64(), voucherData.PieceSize.Uint64(), voucherData.CommP, commD, proof.Proof)
if err != nil {
return nil, aerrors.Absorb(err, 5, "verify piece inclusion proof failed")
}
if !ok {
return nil, aerrors.New(6, "piece inclusion proof was invalid")
}
return nil, nil
}
func (sma StorageMinerActor) PaymentVerifySector(act *types.Actor, vmctx types.VMContext, params *PaymentVerifyParams) ([]byte, ActorError) {
// params.Extra - BigInt - sector id
// params.Proof - nil
_, self, aerr := loadState(vmctx)
if aerr != nil {
return nil, aerr
}
// TODO: ensure no sector ID reusability within related deal lifetime
sector := types.BigFromBytes(params.Extra)
if len(params.Proof) > 0 {
return nil, aerrors.New(1, "unexpected proof bytes")
}
ok, _, _, aerr := GetFromSectorSet(context.TODO(), vmctx.Storage(), self.Sectors, sector.Uint64())
if aerr != nil {
return nil, aerr
}
if !ok {
return nil, aerrors.New(2, "miner does not have required sector")
}
return nil, nil
}
type AddFaultsParams struct { type AddFaultsParams struct {
Faults types.BitField Faults types.BitField
} }
@ -753,7 +681,7 @@ type MinerSlashConsensusFault struct {
} }
func (sma StorageMinerActor) SlashConsensusFault(act *types.Actor, vmctx types.VMContext, params *MinerSlashConsensusFault) ([]byte, ActorError) { func (sma StorageMinerActor) SlashConsensusFault(act *types.Actor, vmctx types.VMContext, params *MinerSlashConsensusFault) ([]byte, ActorError) {
if vmctx.Message().From != StorageMarketAddress { if vmctx.Message().From != StoragePowerAddress {
return nil, aerrors.New(1, "SlashConsensusFault may only be called by the storage market actor") return nil, aerrors.New(1, "SlashConsensusFault may only be called by the storage market actor")
} }

View File

@ -23,7 +23,7 @@ func TestMultiSigCreate(t *testing.T) {
} }
h := NewHarness(t, opts...) h := NewHarness(t, opts...)
ret, _ := h.CreateActor(t, creatorAddr, actors.MultisigActorCodeCid, ret, _ := h.CreateActor(t, creatorAddr, actors.MultisigCodeCid,
&actors.MultiSigConstructorParams{ &actors.MultiSigConstructorParams{
Signers: []address.Address{creatorAddr, sig1Addr, sig2Addr}, Signers: []address.Address{creatorAddr, sig1Addr, sig2Addr},
Required: 2, Required: 2,
@ -49,7 +49,7 @@ func TestMultiSigOps(t *testing.T) {
HarnessAddr(&sig1Addr, 100000), HarnessAddr(&sig1Addr, 100000),
HarnessAddr(&sig2Addr, 100000), HarnessAddr(&sig2Addr, 100000),
HarnessAddr(&outsideAddr, 100000), HarnessAddr(&outsideAddr, 100000),
HarnessActor(&multSigAddr, &creatorAddr, actors.MultisigActorCodeCid, HarnessActor(&multSigAddr, &creatorAddr, actors.MultisigCodeCid,
func() cbg.CBORMarshaler { func() cbg.CBORMarshaler {
return &actors.MultiSigConstructorParams{ return &actors.MultiSigConstructorParams{
Signers: []address.Address{creatorAddr, sig1Addr, sig2Addr}, Signers: []address.Address{creatorAddr, sig1Addr, sig2Addr},

View File

@ -18,7 +18,7 @@ func TestPaychCreate(t *testing.T) {
} }
h := NewHarness(t, opts...) h := NewHarness(t, opts...)
ret, _ := h.CreateActor(t, creatorAddr, actors.PaymentChannelActorCodeCid, ret, _ := h.CreateActor(t, creatorAddr, actors.PaymentChannelCodeCid,
&actors.PCAConstructorParams{ &actors.PCAConstructorParams{
To: targetAddr, To: targetAddr,
}) })
@ -47,7 +47,7 @@ func TestPaychUpdate(t *testing.T) {
} }
h := NewHarness(t, opts...) h := NewHarness(t, opts...)
ret, _ := h.CreateActor(t, creatorAddr, actors.PaymentChannelActorCodeCid, ret, _ := h.CreateActor(t, creatorAddr, actors.PaymentChannelCodeCid,
&actors.PCAConstructorParams{ &actors.PCAConstructorParams{
To: targetAddr, To: targetAddr,
}) })

View File

@ -0,0 +1,604 @@
package actors
import (
"bytes"
"context"
"github.com/filecoin-project/go-amt-ipld"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/types"
)
type StorageMarketActor struct{}
type smaMethods struct {
Constructor uint64
WithdrawBalance uint64
AddBalance uint64
CheckLockedBalance uint64
PublishStorageDeals uint64
HandleCronAction uint64
SettleExpiredDeals uint64
ProcessStorageDealsPayment uint64
SlashStorageDealCollateral uint64
GetLastExpirationFromDealIDs uint64
ActivateStorageDeals uint64
}
var SMAMethods = smaMethods{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
func (sma StorageMarketActor) Exports() []interface{} {
return []interface{}{
2: sma.WithdrawBalance,
3: sma.AddBalance,
// 4: sma.CheckLockedBalance,
5: sma.PublishStorageDeals,
// 6: sma.HandleCronAction,
// 7: sma.SettleExpiredDeals,
// 8: sma.ProcessStorageDealsPayment,
// 9: sma.SlashStorageDealCollateral,
// 10: sma.GetLastExpirationFromDealIDs,
11: sma.ActivateStorageDeals, // TODO: move under PublishStorageDeals after specs team approves
}
}
type StorageParticipantBalance struct {
Locked types.BigInt
Available types.BigInt
}
type StorageMarketState struct {
Balances cid.Cid // hamt<addr, StorageParticipantBalance>
Deals cid.Cid // amt<StorageDeal>
NextDealID uint64 // TODO: spec
}
// TODO: Drop in favour of car storage
type SerializationMode = uint64
const (
SerializationUnixFSv0 = iota
// IPLD / car
)
type StorageDealProposal struct {
PieceRef []byte // cid bytes // TODO: spec says to use cid.Cid, probably not a good idea
PieceSize uint64
PieceSerialization SerializationMode // Needs to be here as it tells how data in the sector maps to PieceRef cid
Client address.Address
Provider address.Address
ProposalExpiration uint64
Duration uint64 // TODO: spec proposes 'DealExpiration', but that's awkward as it
// doesn't tell when the deal actually starts, so the price per block is impossible to
// calculate. It also doesn't incentivize the miner to seal / activate sooner, as he
// still get's paid the full amount specified in the deal
//
// Changing to duration makes sure that the price-per-block is defined, and the miner
// doesn't get paid when not storing the sector
StoragePrice types.BigInt
StorageCollateral types.BigInt
ProposerSignature *types.Signature
}
type SignFunc = func(context.Context, []byte) (*types.Signature, error)
func (sdp *StorageDealProposal) Sign(ctx context.Context, sign SignFunc) error {
if sdp.ProposerSignature != nil {
return xerrors.New("signature already present in StorageDealProposal")
}
var buf bytes.Buffer
if err := sdp.MarshalCBOR(&buf); err != nil {
return err
}
sig, err := sign(ctx, buf.Bytes())
if err != nil {
return err
}
sdp.ProposerSignature = sig
return nil
}
func (sdp *StorageDealProposal) Verify() error {
unsigned := *sdp
unsigned.ProposerSignature = nil
var buf bytes.Buffer
if err := unsigned.MarshalCBOR(&buf); err != nil {
return err
}
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
}
type OnChainDeal struct {
Deal StorageDeal
ActivationEpoch uint64 // 0 = inactive
}
type WithdrawBalanceParams struct {
Balance types.BigInt
}
func (sma StorageMarketActor) WithdrawBalance(act *types.Actor, vmctx types.VMContext, params *WithdrawBalanceParams) ([]byte, ActorError) {
// TODO: (spec) this should be 2-stage
var self StorageMarketState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
b, bnd, err := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, vmctx.Message().From)
if err != nil {
return nil, aerrors.Wrap(err, "could not get balance")
}
balance := b[0]
if balance.Available.LessThan(params.Balance) {
return nil, aerrors.Newf(1, "can not withdraw more funds than available: %s > %s", params.Balance, b[0].Available)
}
balance.Available = types.BigSub(balance.Available, params.Balance)
_, err = vmctx.Send(vmctx.Message().From, 0, params.Balance, nil)
if err != nil {
return nil, aerrors.Wrap(err, "sending funds failed")
}
bcid, err := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
vmctx.Message().From: balance,
})
if err != nil {
return nil, err
}
self.Balances = bcid
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, err
}
return nil, vmctx.Storage().Commit(old, nroot)
}
func (sma StorageMarketActor) AddBalance(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
var self StorageMarketState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
b, bnd, err := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, vmctx.Message().From)
if err != nil {
return nil, aerrors.Wrap(err, "could not get balance")
}
balance := b[0]
balance.Available = types.BigAdd(balance.Available, vmctx.Message().Value)
bcid, err := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
vmctx.Message().From: balance,
})
if err != nil {
return nil, err
}
self.Balances = bcid
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, err
}
return nil, vmctx.Storage().Commit(old, nroot)
}
func setMarketBalances(vmctx types.VMContext, nd *hamt.Node, set map[address.Address]StorageParticipantBalance) (cid.Cid, ActorError) {
for addr, b := range set {
balance := b // to stop linter complaining
if err := nd.Set(vmctx.Context(), string(addr.Bytes()), &balance); err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "setting new balance")
}
}
if err := nd.Flush(vmctx.Context()); err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "flushing balance hamt")
}
c, err := vmctx.Ipld().Put(vmctx.Context(), nd)
if err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "failed to balances storage")
}
return c, nil
}
func GetMarketBalances(ctx context.Context, store *hamt.CborIpldStore, rcid cid.Cid, addrs ...address.Address) ([]StorageParticipantBalance, *hamt.Node, ActorError) {
nd, err := hamt.LoadNode(ctx, store, rcid)
if err != nil {
return nil, nil, aerrors.HandleExternalError(err, "failed to load miner set")
}
out := make([]StorageParticipantBalance, len(addrs))
for i, a := range addrs {
var balance StorageParticipantBalance
err = nd.Find(ctx, string(a.Bytes()), &balance)
switch err {
case hamt.ErrNotFound:
out[i] = StorageParticipantBalance{
Locked: types.NewInt(0),
Available: types.NewInt(0),
}
case nil:
out[i] = balance
default:
return nil, nil, aerrors.HandleExternalError(err, "failed to do set lookup")
}
}
return out, nd, nil
}
/*
func (sma StorageMarketActor) CheckLockedBalance(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
}
*/
type PublishStorageDealsParams struct {
Deals []StorageDeal
}
type PublishStorageDealResponse struct {
DealIDs []uint64
}
func (sma StorageMarketActor) PublishStorageDeals(act *types.Actor, vmctx types.VMContext, params *PublishStorageDealsParams) ([]byte, ActorError) {
var self StorageMarketState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
deals, err := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Deals)
if err != nil {
return nil, aerrors.HandleExternalError(err, "loading deals amt")
}
// todo: handle duplicate deals
if len(params.Deals) == 0 {
return nil, aerrors.New(1, "no storage deals in params.Deals")
}
out := PublishStorageDealResponse{
DealIDs: make([]uint64, len(params.Deals)),
}
workerBytes, aerr := vmctx.Send(params.Deals[0].Proposal.Provider, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
if aerr != nil {
return nil, aerr
}
providerWorker, err := address.NewFromBytes(workerBytes)
if err != nil {
return nil, aerrors.HandleExternalError(err, "parsing provider worker address bytes")
}
// TODO: REVIEW: Do we want to check if provider exists in the power actor?
for i, deal := range params.Deals {
if err := self.validateDeal(vmctx, deal, providerWorker); err != nil {
return nil, err
}
err := deals.Set(self.NextDealID, &OnChainDeal{Deal: deal})
if err != nil {
return nil, aerrors.HandleExternalError(err, "setting deal in deal AMT")
}
out.DealIDs[i] = self.NextDealID
self.NextDealID++
}
dealsCid, err := deals.Flush()
if err != nil {
return nil, aerrors.HandleExternalError(err, "saving deals AMT")
}
self.Deals = dealsCid
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, aerrors.HandleExternalError(err, "storing state failed")
}
aerr = vmctx.Storage().Commit(old, nroot)
if aerr != nil {
return nil, aerr
}
var outBuf bytes.Buffer
if err := out.MarshalCBOR(&outBuf); err != nil {
return nil, aerrors.HandleExternalError(err, "serialising output")
}
return outBuf.Bytes(), nil
}
func (st *StorageMarketState) validateDeal(vmctx types.VMContext, deal StorageDeal, providerWorker address.Address) aerrors.ActorError {
if vmctx.BlockHeight() > deal.Proposal.ProposalExpiration {
return aerrors.New(1, "deal proposal already expired")
}
if err := deal.Proposal.Verify(); err != nil {
return aerrors.Absorb(err, 2, "verifying proposer signature")
}
err := deal.Verify(providerWorker)
if err != nil {
return aerrors.Absorb(err, 2, "verifying provider signature")
}
// TODO: maybe this is actually fine
if vmctx.Message().From != providerWorker && vmctx.Message().From != deal.Proposal.Client {
return aerrors.New(4, "message not sent by deal participant")
}
// TODO: do some caching (changes gas so needs to be in spec too)
b, bnd, aerr := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), st.Balances, deal.Proposal.Client, providerWorker)
if aerr != nil {
return aerrors.Wrap(aerr, "getting client, and provider balances")
}
clientBalance := b[0]
providerBalance := b[1]
if clientBalance.Available.LessThan(deal.Proposal.StoragePrice) {
return aerrors.Newf(5, "client doesn't have enough available funds to cover StoragePrice; %d < %d", clientBalance.Available, deal.Proposal.StoragePrice)
}
clientBalance = lockFunds(clientBalance, deal.Proposal.StoragePrice)
// TODO: REVIEW: Not clear who pays for this
if providerBalance.Available.LessThan(deal.Proposal.StorageCollateral) {
return aerrors.Newf(6, "provider doesn't have enough available funds to cover StorageCollateral; %d < %d", providerBalance.Available, deal.Proposal.StorageCollateral)
}
providerBalance = lockFunds(providerBalance, deal.Proposal.StorageCollateral)
// TODO: piece checks (e.g. size > sectorSize)?
bcid, aerr := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
deal.Proposal.Client: clientBalance,
providerWorker: providerBalance,
})
if aerr != nil {
return aerr
}
st.Balances = bcid
return nil
}
type ActivateStorageDealsParams struct {
Deals []uint64
}
func (sma StorageMarketActor) ActivateStorageDeals(act *types.Actor, vmctx types.VMContext, params *ActivateStorageDealsParams) ([]byte, ActorError) {
var self StorageMarketState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
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")
}
for _, deal := range params.Deals {
var dealInfo OnChainDeal
if err := deals.Get(deal, &dealInfo); err != nil {
if _, is := err.(*amt.ErrNotFound); is {
return nil, aerrors.New(3, "deal not found")
}
return nil, aerrors.HandleExternalError(err, "getting deal info failed")
}
if vmctx.Message().From != dealInfo.Deal.Proposal.Provider {
return nil, aerrors.New(1, "ActivateStorageDeals can only be called by the deal provider")
}
if vmctx.BlockHeight() > dealInfo.Deal.Proposal.ProposalExpiration {
return nil, aerrors.New(2, "deal cannot be activated: proposal expired")
}
if dealInfo.ActivationEpoch > 0 {
// this probably can't happen in practice
return nil, aerrors.New(3, "deal already active")
}
dealInfo.ActivationEpoch = vmctx.BlockHeight()
if err := deals.Set(deal, &dealInfo); err != nil {
return nil, aerrors.HandleExternalError(err, "setting deal info in AMT failed")
}
}
dealsCid, err := deals.Flush()
if err != nil {
return nil, aerrors.HandleExternalError(err, "saving deals AMT")
}
self.Deals = dealsCid
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, aerrors.HandleExternalError(err, "storing state failed")
}
aerr := vmctx.Storage().Commit(old, nroot)
if aerr != nil {
return nil, aerr
}
return nil, nil
}
type ProcessStorageDealsPaymentParams struct {
DealIDs []uint64
}
func (sma StorageMarketActor) ProcessStorageDealsPayment(act *types.Actor, vmctx types.VMContext, params *ProcessStorageDealsPaymentParams) ([]byte, ActorError) {
var self StorageMarketState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
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")
}
// TODO: Would be nice if send could assert actor type
workerBytes, aerr := vmctx.Send(vmctx.Message().From, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
if aerr != nil {
return nil, aerr
}
providerWorker, err := address.NewFromBytes(workerBytes)
if err != nil {
return nil, aerrors.HandleExternalError(err, "parsing provider worker address bytes")
}
for _, deal := range params.DealIDs {
var dealInfo OnChainDeal
if err := deals.Get(deal, &dealInfo); err != nil {
if _, is := err.(*amt.ErrNotFound); is {
return nil, aerrors.New(2, "deal not found")
}
return nil, aerrors.HandleExternalError(err, "getting deal info failed")
}
if dealInfo.Deal.Proposal.Provider != vmctx.Message().From {
return nil, aerrors.New(3, "ProcessStorageDealsPayment can only be called by deal provider")
}
if vmctx.BlockHeight() < dealInfo.ActivationEpoch {
// TODO: This is probably fatal
return nil, aerrors.New(4, "ActivationEpoch lower than block height")
}
if vmctx.BlockHeight() > dealInfo.ActivationEpoch+dealInfo.Deal.Proposal.Duration {
// Deal expired, miner should drop it
// TODO: process payment for the remainder of last proving period
return nil, nil
}
// todo: check math (written on a plane, also tired)
// TODO: division is hard, this more than likely has some off-by-one issue
toPay := types.BigDiv(types.BigMul(dealInfo.Deal.Proposal.StoragePrice, types.NewInt(build.ProvingPeriodDuration)), types.NewInt(dealInfo.Deal.Proposal.Duration))
b, bnd, aerr := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, dealInfo.Deal.Proposal.Client, providerWorker)
if aerr != nil {
return nil, aerr
}
clientBal := b[0]
providerBal := b[1]
clientBal.Locked, providerBal.Available = transferFunds(clientBal.Locked, providerBal.Available, toPay)
// TODO: call set once
bcid, aerr := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
dealInfo.Deal.Proposal.Client: clientBal,
providerWorker: providerBal,
})
if aerr != nil {
return nil, aerr
}
self.Balances = bcid
}
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, aerrors.HandleExternalError(err, "storing state failed")
}
aerr = vmctx.Storage().Commit(old, nroot)
if aerr != nil {
return nil, aerr
}
return nil, nil
}
func lockFunds(p StorageParticipantBalance, amt types.BigInt) StorageParticipantBalance {
p.Available, p.Locked = transferFunds(p.Available, p.Locked, amt)
return p
}
func transferFunds(from, to, amt types.BigInt) (types.BigInt, types.BigInt) {
// TODO: some asserts
return types.BigSub(from, amt), types.BigAdd(to, amt)
}
/*
func (sma StorageMarketActor) HandleCronAction(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
}
func (sma StorageMarketActor) SettleExpiredDeals(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
}
func (sma StorageMarketActor) SlashStorageDealCollateral(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
}
func (sma StorageMarketActor) GetLastExpirationFromDealIDs(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
}
*/

View File

@ -87,7 +87,7 @@ func (spa StoragePowerActor) CreateStorageMiner(act *types.Actor, vmctx types.VM
return nil, err return nil, err
} }
ret, err := vmctx.Send(InitActorAddress, IAMethods.Exec, vmctx.Message().Value, encoded) ret, err := vmctx.Send(InitAddress, IAMethods.Exec, vmctx.Message().Value, encoded)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -37,7 +37,7 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
// cheating the bootstrapping problem // cheating the bootstrapping problem
cheatStorageMarketTotal(t, h.vm, h.cs.Blockstore()) cheatStorageMarketTotal(t, h.vm, h.cs.Blockstore())
ret, _ := h.InvokeWithValue(t, ownerAddr, StorageMarketAddress, SPAMethods.CreateStorageMiner, ret, _ := h.InvokeWithValue(t, ownerAddr, StoragePowerAddress, SPAMethods.CreateStorageMiner,
types.NewInt(500000), types.NewInt(500000),
&CreateStorageMinerParams{ &CreateStorageMinerParams{
Owner: ownerAddr, Owner: ownerAddr,
@ -52,7 +52,7 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
} }
{ {
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SPAMethods.IsMiner, ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.IsMiner,
&IsMinerParam{Addr: minerAddr}) &IsMinerParam{Addr: minerAddr})
ApplyOK(t, ret) ApplyOK(t, ret)
@ -68,7 +68,7 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
} }
{ {
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SPAMethods.PowerLookup, ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.PowerLookup,
&PowerLookupParams{Miner: minerAddr}) &PowerLookupParams{Miner: minerAddr})
ApplyOK(t, ret) ApplyOK(t, ret)
power := types.BigFromBytes(ret.Return) power := types.BigFromBytes(ret.Return)
@ -93,7 +93,7 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
signBlock(t, h.w, workerAddr, b1) signBlock(t, h.w, workerAddr, b1)
signBlock(t, h.w, workerAddr, b2) signBlock(t, h.w, workerAddr, b2)
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SPAMethods.ArbitrateConsensusFault, ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.ArbitrateConsensusFault,
&ArbitrateConsensusFaultParams{ &ArbitrateConsensusFaultParams{
Block1: b1, Block1: b1,
Block2: b2, Block2: b2,
@ -102,13 +102,13 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
} }
{ {
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SPAMethods.PowerLookup, ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.PowerLookup,
&PowerLookupParams{Miner: minerAddr}) &PowerLookupParams{Miner: minerAddr})
assert.Equal(t, ret.ExitCode, byte(1)) assert.Equal(t, ret.ExitCode, byte(1))
} }
{ {
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SPAMethods.IsMiner, &IsMinerParam{minerAddr}) ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.IsMiner, &IsMinerParam{minerAddr})
ApplyOK(t, ret) ApplyOK(t, ret)
assert.Equal(t, ret.Return, cbg.CborBoolFalse) assert.Equal(t, ret.Return, cbg.CborBoolFalse)
} }
@ -117,7 +117,7 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
func cheatStorageMarketTotal(t *testing.T, vm *vm.VM, bs bstore.Blockstore) { func cheatStorageMarketTotal(t *testing.T, vm *vm.VM, bs bstore.Blockstore) {
t.Helper() t.Helper()
sma, err := vm.StateTree().GetActor(StorageMarketAddress) sma, err := vm.StateTree().GetActor(StoragePowerAddress)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -138,7 +138,7 @@ func cheatStorageMarketTotal(t *testing.T, vm *vm.VM, bs bstore.Blockstore) {
sma.Head = c sma.Head = c
if err := vm.StateTree().SetActor(StorageMarketAddress, sma); err != nil { if err := vm.StateTree().SetActor(StoragePowerAddress, sma); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }

View File

@ -7,16 +7,18 @@ import (
mh "github.com/multiformats/go-multihash" mh "github.com/multiformats/go-multihash"
) )
var AccountActorCodeCid cid.Cid var AccountCodeCid cid.Cid
var StorageMarketActorCodeCid cid.Cid var StoragePowerCodeCid cid.Cid
var StorageMarketCodeCid cid.Cid
var StorageMinerCodeCid cid.Cid var StorageMinerCodeCid cid.Cid
var MultisigActorCodeCid cid.Cid var MultisigCodeCid cid.Cid
var InitActorCodeCid cid.Cid var InitCodeCid cid.Cid
var PaymentChannelActorCodeCid cid.Cid var PaymentChannelCodeCid cid.Cid
var InitActorAddress = mustIDAddress(0) var InitAddress = mustIDAddress(0)
var NetworkAddress = mustIDAddress(1) var NetworkAddress = mustIDAddress(1)
var StorageMarketAddress = mustIDAddress(2) var StoragePowerAddress = mustIDAddress(2)
var StorageMarketAddress = mustIDAddress(3) // TODO: missing from spec
var BurntFundsAddress = mustIDAddress(99) var BurntFundsAddress = mustIDAddress(99)
func mustIDAddress(i uint64) address.Address { func mustIDAddress(i uint64) address.Address {
@ -37,10 +39,11 @@ func init() {
return c return c
} }
AccountActorCodeCid = mustSum("account") AccountCodeCid = mustSum("fil/1/account") // TODO: spec
StorageMarketActorCodeCid = mustSum("smarket") StoragePowerCodeCid = mustSum("fil/1/power")
StorageMinerCodeCid = mustSum("sminer") StorageMarketCodeCid = mustSum("fil/1/market")
MultisigActorCodeCid = mustSum("multisig") StorageMinerCodeCid = mustSum("fil/1/miner")
InitActorCodeCid = mustSum("init") MultisigCodeCid = mustSum("fil/1/multisig")
PaymentChannelActorCodeCid = mustSum("paych") InitCodeCid = mustSum("fil/1/init")
PaymentChannelCodeCid = mustSum("fil/1/paych")
} }

View File

@ -80,7 +80,7 @@ func TestVMInvokeMethod(t *testing.T) {
} }
msg := &types.Message{ msg := &types.Message{
To: InitActorAddress, To: InitAddress,
From: from, From: from,
Method: IAMethods.Exec, Method: IAMethods.Exec,
Params: enc, Params: enc,
@ -128,7 +128,7 @@ func TestStorageMarketActorCreateMiner(t *testing.T) {
} }
msg := &types.Message{ msg := &types.Message{
To: StorageMarketAddress, To: StoragePowerAddress,
From: from, From: from,
Method: SPAMethods.CreateStorageMiner, Method: SPAMethods.CreateStorageMiner,
Params: enc, Params: enc,

File diff suppressed because it is too large Load Diff

View File

@ -210,7 +210,7 @@ func (h *Harness) CreateActor(t testing.TB, from address.Address,
t.Helper() t.Helper()
return h.Apply(t, types.Message{ return h.Apply(t, types.Message{
To: actors.InitActorAddress, To: actors.InitAddress,
From: from, From: from,
Method: actors.IAMethods.Exec, Method: actors.IAMethods.Exec,
Params: DumpObject(t, Params: DumpObject(t,

778
chain/deals/cbor_gen.go Normal file
View File

@ -0,0 +1,778 @@
package deals
import (
"fmt"
"io"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/libp2p/go-libp2p-core/peer"
cbg "github.com/whyrusleeping/cbor-gen"
xerrors "golang.org/x/xerrors"
)
/* This file was generated by github.com/whyrusleeping/cbor-gen */
var _ = xerrors.Errorf
func (t *AskRequest) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{129}); err != nil {
return err
}
// t.t.Miner (address.Address)
if err := t.Miner.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *AskRequest) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 1 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Miner (address.Address)
{
if err := t.Miner.UnmarshalCBOR(br); err != nil {
return err
}
}
return nil
}
func (t *AskResponse) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{129}); err != nil {
return err
}
// t.t.Ask (types.SignedStorageAsk)
if err := t.Ask.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *AskResponse) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 1 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Ask (types.SignedStorageAsk)
{
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.Ask = new(types.SignedStorageAsk)
if err := t.Ask.UnmarshalCBOR(br); err != nil {
return err
}
}
}
return nil
}
func (t *Proposal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{129}); err != nil {
return err
}
// t.t.DealProposal (actors.StorageDealProposal)
if err := t.DealProposal.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *Proposal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 1 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.DealProposal (actors.StorageDealProposal)
{
if err := t.DealProposal.UnmarshalCBOR(br); err != nil {
return err
}
}
return nil
}
func (t *Response) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{134}); err != nil {
return err
}
// t.t.State (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.State)); err != nil {
return err
}
// t.t.Message (string)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Message)))); err != nil {
return err
}
if _, err := w.Write([]byte(t.Message)); err != nil {
return err
}
// t.t.Proposal (cid.Cid)
if err := cbg.WriteCid(w, t.Proposal); err != nil {
return xerrors.Errorf("failed to write cid field t.Proposal: %w", err)
}
// t.t.StorageDeal (actors.StorageDeal)
if err := t.StorageDeal.MarshalCBOR(w); err != nil {
return err
}
// t.t.PublishMessage (cid.Cid)
if t.PublishMessage == nil {
if _, err := w.Write(cbg.CborNull); err != nil {
return err
}
} else {
if err := cbg.WriteCid(w, *t.PublishMessage); err != nil {
return xerrors.Errorf("failed to write cid field t.PublishMessage: %w", err)
}
}
// t.t.CommitMessage (cid.Cid)
if t.CommitMessage == nil {
if _, err := w.Write(cbg.CborNull); err != nil {
return err
}
} else {
if err := cbg.WriteCid(w, *t.CommitMessage); err != nil {
return xerrors.Errorf("failed to write cid field t.CommitMessage: %w", err)
}
}
return nil
}
func (t *Response) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 6 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.State (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.State = extra
// t.t.Message (string)
{
sval, err := cbg.ReadString(br)
if err != nil {
return err
}
t.Message = string(sval)
}
// t.t.Proposal (cid.Cid)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Proposal: %w", err)
}
t.Proposal = c
}
// t.t.StorageDeal (actors.StorageDeal)
{
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.StorageDeal = new(actors.StorageDeal)
if err := t.StorageDeal.UnmarshalCBOR(br); err != nil {
return err
}
}
}
// t.t.PublishMessage (cid.Cid)
{
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 {
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.PublishMessage: %w", err)
}
t.PublishMessage = &c
}
}
// t.t.CommitMessage (cid.Cid)
{
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 {
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.CommitMessage: %w", err)
}
t.CommitMessage = &c
}
}
return nil
}
func (t *SignedResponse) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{130}); err != nil {
return err
}
// t.t.Response (deals.Response)
if err := t.Response.MarshalCBOR(w); err != nil {
return err
}
// t.t.Signature (types.Signature)
if err := t.Signature.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *SignedResponse) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 2 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Response (deals.Response)
{
if err := t.Response.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.Signature (types.Signature)
{
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.Signature = new(types.Signature)
if err := t.Signature.UnmarshalCBOR(br); err != nil {
return err
}
}
}
return nil
}
func (t *ClientDealProposal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{135}); err != nil {
return err
}
// t.t.Data (cid.Cid)
if err := cbg.WriteCid(w, t.Data); err != nil {
return xerrors.Errorf("failed to write cid field t.Data: %w", err)
}
// t.t.TotalPrice (types.BigInt)
if err := t.TotalPrice.MarshalCBOR(w); err != nil {
return err
}
// t.t.ProposalExpiration (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.ProposalExpiration)); err != nil {
return err
}
// t.t.Duration (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.Duration)); err != nil {
return err
}
// t.t.ProviderAddress (address.Address)
if err := t.ProviderAddress.MarshalCBOR(w); err != nil {
return err
}
// t.t.Client (address.Address)
if err := t.Client.MarshalCBOR(w); err != nil {
return err
}
// t.t.MinerID (peer.ID)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.MinerID)))); err != nil {
return err
}
if _, err := w.Write([]byte(t.MinerID)); err != nil {
return err
}
return nil
}
func (t *ClientDealProposal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 7 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Data (cid.Cid)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Data: %w", err)
}
t.Data = c
}
// t.t.TotalPrice (types.BigInt)
{
if err := t.TotalPrice.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.ProposalExpiration (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.ProposalExpiration = extra
// t.t.Duration (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.Duration = extra
// t.t.ProviderAddress (address.Address)
{
if err := t.ProviderAddress.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.Client (address.Address)
{
if err := t.Client.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.MinerID (peer.ID)
{
sval, err := cbg.ReadString(br)
if err != nil {
return err
}
t.MinerID = peer.ID(sval)
}
return nil
}
func (t *ClientDeal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{132}); err != nil {
return err
}
// t.t.ProposalCid (cid.Cid)
if err := cbg.WriteCid(w, t.ProposalCid); err != nil {
return xerrors.Errorf("failed to write cid field t.ProposalCid: %w", err)
}
// t.t.Proposal (actors.StorageDealProposal)
if err := t.Proposal.MarshalCBOR(w); err != nil {
return err
}
// t.t.State (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.State)); err != nil {
return err
}
// t.t.Miner (peer.ID)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Miner)))); err != nil {
return err
}
if _, err := w.Write([]byte(t.Miner)); err != nil {
return err
}
return nil
}
func (t *ClientDeal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 4 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.ProposalCid (cid.Cid)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.ProposalCid: %w", err)
}
t.ProposalCid = c
}
// t.t.Proposal (actors.StorageDealProposal)
{
if err := t.Proposal.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.State (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.State = extra
// t.t.Miner (peer.ID)
{
sval, err := cbg.ReadString(br)
if err != nil {
return err
}
t.Miner = peer.ID(sval)
}
return nil
}
func (t *MinerDeal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{135}); err != nil {
return err
}
// t.t.Client (peer.ID)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Client)))); err != nil {
return err
}
if _, err := w.Write([]byte(t.Client)); err != nil {
return err
}
// t.t.Proposal (actors.StorageDealProposal)
if err := t.Proposal.MarshalCBOR(w); err != nil {
return err
}
// t.t.ProposalCid (cid.Cid)
if err := cbg.WriteCid(w, t.ProposalCid); err != nil {
return xerrors.Errorf("failed to write cid field t.ProposalCid: %w", err)
}
// t.t.State (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.State)); err != nil {
return err
}
// t.t.Ref (cid.Cid)
if err := cbg.WriteCid(w, t.Ref); err != nil {
return xerrors.Errorf("failed to write cid field t.Ref: %w", err)
}
// t.t.DealID (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.DealID)); err != nil {
return err
}
// t.t.SectorID (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.SectorID)); err != nil {
return err
}
return nil
}
func (t *MinerDeal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 7 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Client (peer.ID)
{
sval, err := cbg.ReadString(br)
if err != nil {
return err
}
t.Client = peer.ID(sval)
}
// t.t.Proposal (actors.StorageDealProposal)
{
if err := t.Proposal.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.ProposalCid (cid.Cid)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.ProposalCid: %w", err)
}
t.ProposalCid = c
}
// t.t.State (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.State = extra
// t.t.Ref (cid.Cid)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Ref: %w", err)
}
t.Ref = c
}
// t.t.DealID (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.DealID = extra
// t.t.SectorID (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.SectorID = extra
return nil
}

View File

@ -2,12 +2,11 @@ package deals
import ( import (
"context" "context"
"math" "github.com/filecoin-project/lotus/node/impl/full"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace" "github.com/ipfs/go-datastore/namespace"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log" logging "github.com/ipfs/go-log"
"github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/host"
inet "github.com/libp2p/go-libp2p-core/network" inet "github.com/libp2p/go-libp2p-core/network"
@ -18,6 +17,7 @@ import (
"github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/chain/wallet"
"github.com/filecoin-project/lotus/lib/cborrpc" "github.com/filecoin-project/lotus/lib/cborrpc"
@ -25,22 +25,11 @@ import (
"github.com/filecoin-project/lotus/retrieval/discovery" "github.com/filecoin-project/lotus/retrieval/discovery"
) )
func init() {
cbor.RegisterCborType(ClientDeal{})
cbor.RegisterCborType(actors.PieceInclVoucherData{}) // TODO: USE CBORGEN!
cbor.RegisterCborType(types.SignedVoucher{})
cbor.RegisterCborType(types.ModVerifyParams{})
cbor.RegisterCborType(types.Signature{})
cbor.RegisterCborType(actors.PaymentInfo{})
cbor.RegisterCborType(api.PaymentInfo{})
cbor.RegisterCborType(actors.InclusionProof{})
}
var log = logging.Logger("deals") var log = logging.Logger("deals")
type ClientDeal struct { type ClientDeal struct {
ProposalCid cid.Cid ProposalCid cid.Cid
Proposal StorageDealProposal Proposal actors.StorageDealProposal
State api.DealState State api.DealState
Miner peer.ID Miner peer.ID
@ -49,15 +38,17 @@ type ClientDeal struct {
type Client struct { type Client struct {
sm *stmgr.StateManager sm *stmgr.StateManager
chain *store.ChainStore
h host.Host h host.Host
w *wallet.Wallet w *wallet.Wallet
dag dtypes.ClientDAG dag dtypes.ClientDAG
discovery *discovery.Local discovery *discovery.Local
mpool full.MpoolAPI
deals ClientStateStore deals ClientStateStore
conns map[cid.Cid]inet.Stream conns map[cid.Cid]inet.Stream
incoming chan ClientDeal incoming chan *ClientDeal
updated chan clientDealUpdate updated chan clientDealUpdate
stop chan struct{} stop chan struct{}
@ -70,18 +61,20 @@ type clientDealUpdate struct {
err error err error
} }
func NewClient(sm *stmgr.StateManager, 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{ c := &Client{
sm: sm, sm: sm,
chain: chain,
h: h, h: h,
w: w, w: w,
dag: dag, dag: dag,
discovery: discovery, discovery: discovery,
mpool: mpool,
deals: ClientStateStore{StateStore{ds: namespace.Wrap(ds, datastore.NewKey("/deals/client"))}}, deals: ClientStateStore{StateStore{ds: namespace.Wrap(ds, datastore.NewKey("/deals/client"))}},
conns: map[cid.Cid]inet.Stream{}, conns: map[cid.Cid]inet.Stream{},
incoming: make(chan ClientDeal, 16), incoming: make(chan *ClientDeal, 16),
updated: make(chan clientDealUpdate, 16), updated: make(chan clientDealUpdate, 16),
stop: make(chan struct{}), stop: make(chan struct{}),
@ -108,7 +101,7 @@ func (c *Client) Run(ctx context.Context) {
}() }()
} }
func (c *Client) onIncoming(deal ClientDeal) { func (c *Client) onIncoming(deal *ClientDeal) {
log.Info("incoming deal") log.Info("incoming deal")
if _, ok := c.conns[deal.ProposalCid]; ok { if _, ok := c.conns[deal.ProposalCid]; ok {
@ -166,70 +159,94 @@ func (c *Client) onUpdated(ctx context.Context, update clientDealUpdate) {
type ClientDealProposal struct { type ClientDealProposal struct {
Data cid.Cid Data cid.Cid
TotalPrice types.BigInt TotalPrice types.BigInt
Duration uint64 ProposalExpiration uint64
Duration uint64
Payment actors.PaymentInfo ProviderAddress address.Address
Client address.Address
MinerAddress address.Address MinerID peer.ID
ClientAddress address.Address
MinerID peer.ID
} }
func (c *Client) VerifyParams(ctx context.Context, data cid.Cid) (*actors.PieceInclVoucherData, error) { func (c *Client) Start(ctx context.Context, p ClientDealProposal) (cid.Cid, error) {
commP, size, err := c.commP(ctx, data) // check market funds
clientMarketBalance, err := c.sm.MarketBalance(ctx, p.Client, nil)
if err != nil { if err != nil {
return nil, err return cid.Undef, xerrors.Errorf("getting client market balance failed: %w", err)
} }
return &actors.PieceInclVoucherData{ if clientMarketBalance.Available.LessThan(p.TotalPrice) {
CommP: commP, // TODO: move to a smarter market funds manager
PieceSize: types.NewInt(uint64(size)),
}, nil
}
func (c *Client) Start(ctx context.Context, p ClientDealProposal, vd *actors.PieceInclVoucherData) (cid.Cid, error) { smsg, err := c.mpool.MpoolPushMessage(ctx, &types.Message{
proposal := StorageDealProposal{ To: actors.StorageMarketAddress,
PieceRef: p.Data, From: p.Client,
SerializationMode: SerializationUnixFs, Value: p.TotalPrice,
CommP: vd.CommP[:], GasPrice: types.NewInt(0),
Size: vd.PieceSize.Uint64(), GasLimit: types.NewInt(1000000),
TotalPrice: p.TotalPrice, Method: actors.SMAMethods.AddBalance,
Duration: p.Duration, })
Payment: p.Payment, if err != nil {
MinerAddress: p.MinerAddress, return cid.Undef, err
ClientAddress: p.ClientAddress, }
_, 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)
}
} }
s, err := c.h.NewStream(ctx, p.MinerID, ProtocolID) dataSize, err := c.dataSize(ctx, p.Data)
proposal := &actors.StorageDealProposal{
PieceRef: p.Data.Bytes(),
PieceSize: uint64(dataSize),
PieceSerialization: actors.SerializationUnixFSv0,
Client: p.Client,
Provider: p.ProviderAddress,
ProposalExpiration: p.ProposalExpiration,
Duration: p.Duration,
StoragePrice: p.TotalPrice,
StorageCollateral: types.NewInt(uint64(dataSize)), // TODO: real calc
}
if err := api.SignWith(ctx, c.w.Sign, p.Client, proposal); err != nil {
return cid.Undef, xerrors.Errorf("signing deal proposal failed: %w", err)
}
proposalNd, err := cborrpc.AsIpld(proposal)
if err != nil { if err != nil {
return cid.Undef, err return cid.Undef, xerrors.Errorf("getting proposal node failed: %w", err)
} }
if err := c.sendProposal(s, proposal, p.ClientAddress); err != nil { s, err := c.h.NewStream(ctx, p.MinerID, DealProtocolID)
return cid.Undef, err
}
proposalNd, err := cbor.WrapObject(proposal, math.MaxUint64, -1)
if err != nil { if err != nil {
return cid.Undef, err s.Reset()
return cid.Undef, xerrors.Errorf("connecting to storage provider failed: %w", err)
} }
deal := ClientDeal{ if err := cborrpc.WriteCborRPC(s, proposal); err != nil {
s.Reset()
return cid.Undef, xerrors.Errorf("sending proposal to storage provider failed: %w", err)
}
deal := &ClientDeal{
ProposalCid: proposalNd.Cid(), ProposalCid: proposalNd.Cid(),
Proposal: proposal, Proposal: *proposal,
State: api.DealUnknown, State: api.DealUnknown,
Miner: p.MinerID, Miner: p.MinerID,
s: s, s: s,
} }
// TODO: actually care about what happens with the deal after it was accepted
c.incoming <- deal c.incoming <- deal
// TODO: start tracking after the deal is sealed
return deal.ProposalCid, c.discovery.AddPeer(p.Data, discovery.RetrievalPeer{ return deal.ProposalCid, c.discovery.AddPeer(p.Data, discovery.RetrievalPeer{
Address: proposal.MinerAddress, Address: proposal.Provider,
ID: deal.Miner, ID: deal.Miner,
}) })
} }

View File

@ -3,11 +3,10 @@ package deals
import ( import (
"context" "context"
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/stmgr"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/lib/sectorbuilder"
) )
type clientHandlerFunc func(ctx context.Context, deal ClientDeal) error type clientHandlerFunc func(ctx context.Context, deal ClientDeal) error
@ -39,6 +38,39 @@ func (c *Client) new(ctx context.Context, deal ClientDeal) error {
return xerrors.Errorf("deal wasn't accepted (State=%d)", resp.State) return xerrors.Errorf("deal wasn't accepted (State=%d)", resp.State)
} }
// TODO: spec says it's optional
pubmsg, err := c.chain.GetMessage(*resp.PublishMessage)
if err != nil {
return xerrors.Errorf("getting deal pubsish message: %w", err)
}
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)
}
if pubmsg.Method != actors.SMAMethods.PublishStorageDeals {
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)
}
if ret.ExitCode != 0 {
return xerrors.Errorf("deal publish failed: exit=%d", ret.ExitCode)
}
// TODO: persist dealId
log.Info("DEAL ACCEPTED!") log.Info("DEAL ACCEPTED!")
return nil return nil
@ -75,13 +107,14 @@ func (c *Client) staged(ctx context.Context, deal ClientDeal) error {
log.Info("DEAL SEALED!") log.Info("DEAL SEALED!")
ok, err := sectorbuilder.VerifyPieceInclusionProof(build.SectorSize, deal.Proposal.Size, deal.Proposal.CommP, resp.CommD, resp.PieceInclusionProof.ProofElements) // TODO: want?
/*ok, err := sectorbuilder.VerifyPieceInclusionProof(build.SectorSize, deal.Proposal.PieceSize, deal.Proposal.CommP, resp.CommD, resp.PieceInclusionProof.ProofElements)
if err != nil { if err != nil {
return xerrors.Errorf("verifying piece inclusion proof in staged deal %s: %w", deal.ProposalCid, err) return xerrors.Errorf("verifying piece inclusion proof in staged deal %s: %w", deal.ProposalCid, err)
} }
if !ok { if !ok {
return xerrors.Errorf("verifying piece inclusion proof in staged deal %s failed", deal.ProposalCid) return xerrors.Errorf("verifying piece inclusion proof in staged deal %s failed", deal.ProposalCid)
} }*/
return nil return nil
} }

View File

@ -2,17 +2,13 @@ package deals
import ( import (
"context" "context"
"github.com/filecoin-project/lotus/lib/sectorbuilder"
"runtime" "runtime"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
files "github.com/ipfs/go-ipfs-files" files "github.com/ipfs/go-ipfs-files"
cbor "github.com/ipfs/go-ipld-cbor"
unixfile "github.com/ipfs/go-unixfs/file" unixfile "github.com/ipfs/go-unixfs/file"
inet "github.com/libp2p/go-libp2p-core/network"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/lib/cborrpc" "github.com/filecoin-project/lotus/lib/cborrpc"
) )
@ -32,68 +28,38 @@ func (c *Client) failDeal(id cid.Cid, cerr error) {
log.Errorf("deal %s failed: %s", id, cerr) log.Errorf("deal %s failed: %s", id, cerr)
} }
func (c *Client) commP(ctx context.Context, data cid.Cid) ([]byte, int64, error) { func (c *Client) dataSize(ctx context.Context, data cid.Cid) (int64, error) {
root, err := c.dag.Get(ctx, data) root, err := c.dag.Get(ctx, data)
if err != nil { if err != nil {
log.Errorf("failed to get file root for deal: %s", err) log.Errorf("failed to get file root for deal: %s", err)
return nil, 0, err return 0, err
} }
n, err := unixfile.NewUnixfsFile(ctx, c.dag, root) n, err := unixfile.NewUnixfsFile(ctx, c.dag, root)
if err != nil { if err != nil {
log.Errorf("cannot open unixfs file: %s", err) log.Errorf("cannot open unixfs file: %s", err)
return nil, 0, err return 0, err
} }
uf, ok := n.(files.File) uf, ok := n.(files.File)
if !ok { if !ok {
// TODO: we probably got directory, how should we handle this in unixfs mode? // TODO: we probably got directory, how should we handle this in unixfs mode?
return nil, 0, xerrors.New("unsupported unixfs type") return 0, xerrors.New("unsupported unixfs type")
} }
size, err := uf.Size() return uf.Size()
if err != nil {
return nil, 0, err
}
commP, err := sectorbuilder.GeneratePieceCommitment(uf, uint64(size))
if err != nil {
return nil, 0, err
}
return commP[:], size, err
} }
func (c *Client) sendProposal(s inet.Stream, proposal StorageDealProposal, from address.Address) error { func (c *Client) readStorageDealResp(deal ClientDeal) (*Response, error) {
log.Info("Sending deal proposal")
msg, err := cbor.DumpObject(proposal)
if err != nil {
return err
}
sig, err := c.w.Sign(context.TODO(), from, msg)
if err != nil {
return err
}
signedProposal := &SignedStorageDealProposal{
Proposal: proposal,
Signature: sig,
}
return cborrpc.WriteCborRPC(s, signedProposal)
}
func (c *Client) readStorageDealResp(deal ClientDeal) (*StorageDealResponse, error) {
s, ok := c.conns[deal.ProposalCid] s, ok := c.conns[deal.ProposalCid]
if !ok { if !ok {
// TODO: Try to re-establish the connection using query protocol // TODO: Try to re-establish the connection using query protocol
return nil, xerrors.Errorf("no connection to miner") return nil, xerrors.Errorf("no connection to miner")
} }
var resp SignedStorageDealResponse var resp SignedResponse
if err := cborrpc.ReadCborRPC(s, &resp); err != nil { if err := cborrpc.ReadCborRPC(s, &resp); err != nil {
log.Errorw("failed to read StorageDealResponse message", "error", err) log.Errorw("failed to read Response message", "error", err)
return nil, err return nil, err
} }

View File

@ -1,313 +0,0 @@
package deals
import (
"bytes"
"context"
"github.com/filecoin-project/go-sectorbuilder/sealing_state"
cbor "github.com/ipfs/go-ipld-cbor"
"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/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/sectorbuilder"
"github.com/filecoin-project/lotus/storage/sectorblocks"
)
type minerHandlerFunc func(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error)
func (h *Handler) handle(ctx context.Context, deal MinerDeal, cb minerHandlerFunc, next api.DealState) {
go func() {
mut, err := cb(ctx, deal)
if err == nil && next == api.DealNoUpdate {
return
}
select {
case h.updated <- minerDealUpdate{
newState: next,
id: deal.ProposalCid,
err: err,
mut: mut,
}:
case <-h.stop:
}
}()
}
// ACCEPTED
func (h *Handler) checkVoucher(ctx context.Context, deal MinerDeal, voucher *types.SignedVoucher, lane uint64, maxClose uint64, amount types.BigInt) error {
err := h.full.PaychVoucherCheckValid(ctx, deal.Proposal.Payment.PayChActor, voucher)
if err != nil {
return err
}
if voucher.Extra == nil {
return xerrors.New("voucher.Extra not set")
}
if voucher.Extra.Actor != deal.Proposal.MinerAddress {
return xerrors.Errorf("extra params actor didn't match miner address in proposal: '%s' != '%s'", voucher.Extra.Actor, deal.Proposal.MinerAddress)
}
if voucher.Extra.Method != actors.MAMethods.PaymentVerifyInclusion {
return xerrors.Errorf("expected extra method %d, got %d", actors.MAMethods.PaymentVerifyInclusion, voucher.Extra.Method)
}
var inclChallenge actors.PieceInclVoucherData
if err := cbor.DecodeInto(voucher.Extra.Data, &inclChallenge); err != nil {
return xerrors.Errorf("failed to decode storage voucher data for verification: %w", err)
}
if inclChallenge.PieceSize.Uint64() != deal.Proposal.Size {
return xerrors.Errorf("paych challenge piece size didn't match deal proposal size: %d != %d", inclChallenge.PieceSize.Uint64(), deal.Proposal.Size)
}
if !bytes.Equal(inclChallenge.CommP, deal.Proposal.CommP) {
return xerrors.New("paych challenge commP didn't match deal proposal")
}
if voucher.MinCloseHeight > maxClose {
return xerrors.Errorf("MinCloseHeight too high (%d), max expected: %d", voucher.MinCloseHeight, maxClose)
}
if voucher.TimeLock > maxClose {
return xerrors.Errorf("TimeLock too high (%d), max expected: %d", voucher.TimeLock, maxClose)
}
if len(voucher.Merges) > 0 {
return xerrors.New("didn't expect any merges")
}
if voucher.Amount.LessThan(amount) {
return xerrors.Errorf("not enough funds in the voucher: %s < %s; vl=%d", voucher.Amount, amount, len(deal.Proposal.Payment.Vouchers))
}
if voucher.Lane != lane {
return xerrors.Errorf("expected all vouchers on lane %d, found voucher on lane %d", lane, voucher.Lane)
}
return nil
}
func (h *Handler) consumeVouchers(ctx context.Context, deal MinerDeal) error {
curHead, err := h.full.ChainHead(ctx)
if err != nil {
return err
}
if len(deal.Proposal.Payment.Vouchers) == 0 {
return xerrors.Errorf("no payment vouchers for deal")
}
increment := deal.Proposal.Duration / uint64(len(deal.Proposal.Payment.Vouchers))
startH := deal.Proposal.Payment.Vouchers[0].TimeLock - increment
if startH > curHead.Height()+build.DealVoucherSkewLimit {
return xerrors.Errorf("deal starts too far into the future: start=%d; h=%d; max=%d; inc=%d", startH, curHead.Height(), curHead.Height()+build.DealVoucherSkewLimit, increment)
}
vspec := VoucherSpec(deal.Proposal.Duration, deal.Proposal.TotalPrice, startH, nil)
lane := deal.Proposal.Payment.Vouchers[0].Lane
for i, voucher := range deal.Proposal.Payment.Vouchers {
maxClose := curHead.Height() + (increment * uint64(i+1)) + build.DealVoucherSkewLimit
if err := h.checkVoucher(ctx, deal, voucher, lane, maxClose, vspec[i].Amount); err != nil {
return xerrors.Errorf("validating payment voucher %d: %w", i, err)
}
}
minPrice := types.BigMul(types.BigMul(h.pricePerByteBlock, types.NewInt(deal.Proposal.Size)), types.NewInt(deal.Proposal.Duration))
if types.BigCmp(minPrice, deal.Proposal.TotalPrice) > 0 {
return xerrors.Errorf("minimum price: %s", minPrice)
}
prevAmt := types.NewInt(0)
for i, voucher := range deal.Proposal.Payment.Vouchers {
delta, err := h.full.PaychVoucherAdd(ctx, deal.Proposal.Payment.PayChActor, voucher, nil, types.BigSub(vspec[i].Amount, prevAmt))
if err != nil {
return xerrors.Errorf("consuming payment voucher %d: %w", i, err)
}
prevAmt = types.BigAdd(prevAmt, delta)
}
return nil
}
func (h *Handler) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
switch deal.Proposal.SerializationMode {
//case SerializationRaw:
//case SerializationIPLD:
case SerializationUnixFs:
default:
return nil, xerrors.Errorf("deal proposal with unsupported serialization: %s", deal.Proposal.SerializationMode)
}
if deal.Proposal.Payment.ChannelMessage != nil {
log.Info("waiting for channel message to appear on chain")
if _, err := h.full.StateWaitMsg(ctx, *deal.Proposal.Payment.ChannelMessage); err != nil {
return nil, xerrors.Errorf("waiting for paych message: %w", err)
}
}
if err := h.consumeVouchers(ctx, deal); err != nil {
return nil, err
}
log.Info("fetching data for a deal")
err := h.sendSignedResponse(StorageDealResponse{
State: api.DealAccepted,
Message: "",
Proposal: deal.ProposalCid,
})
if err != nil {
return nil, err
}
return nil, merkledag.FetchGraph(ctx, deal.Ref, h.dag)
}
// STAGED
func (h *Handler) staged(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
err := h.sendSignedResponse(StorageDealResponse{
State: api.DealStaged,
Proposal: deal.ProposalCid,
})
if err != nil {
log.Warnf("Sending deal response failed: %s", err)
}
root, err := h.dag.Get(ctx, deal.Ref)
if err != nil {
return nil, xerrors.Errorf("failed to get file root for deal: %s", err)
}
// TODO: abstract this away into ReadSizeCloser + implement different modes
n, err := unixfile.NewUnixfsFile(ctx, h.dag, root)
if err != nil {
return nil, xerrors.Errorf("cannot open unixfs file: %s", err)
}
uf, ok := n.(sectorblocks.UnixfsReader)
if !ok {
// we probably got directory, unsupported for now
return nil, xerrors.Errorf("unsupported unixfs file type")
}
sectorID, err := h.secst.AddUnixfsPiece(deal.Proposal.PieceRef, uf, deal.Proposal.Duration)
if err != nil {
return nil, xerrors.Errorf("AddPiece failed: %s", err)
}
log.Warnf("New Sector: %d", sectorID)
return func(deal *MinerDeal) {
deal.SectorID = sectorID
}, nil
}
// SEALING
func getInclusionProof(ref string, status sectorbuilder.SectorSealingStatus) (PieceInclusionProof, error) {
for i, p := range status.Pieces {
if p.Key == ref {
return PieceInclusionProof{
Position: uint64(i),
ProofElements: p.InclusionProof,
}, nil
}
}
return PieceInclusionProof{}, xerrors.Errorf("pieceInclusionProof for %s in sector %d not found", ref, status.SectorID)
}
func (h *Handler) waitSealed(ctx context.Context, deal MinerDeal) (sectorbuilder.SectorSealingStatus, error) {
status, err := h.secst.WaitSeal(ctx, deal.SectorID)
if err != nil {
return sectorbuilder.SectorSealingStatus{}, err
}
switch status.State {
case sealing_state.Sealed:
case sealing_state.Failed:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("sealing sector %d for deal %s (ref=%s) failed: %s", deal.SectorID, deal.ProposalCid, deal.Ref, status.SealErrorMsg)
case sealing_state.Pending:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("sector status was 'pending' after call to WaitSeal (for sector %d)", deal.SectorID)
case sealing_state.Sealing:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("sector status was 'wait' after call to WaitSeal (for sector %d)", deal.SectorID)
default:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("unknown SealStatusCode: %d", status.SectorID)
}
return status, nil
}
func (h *Handler) sealing(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
status, err := h.waitSealed(ctx, deal)
if err != nil {
return nil, err
}
// TODO: don't hardcode unixfs
ip, err := getInclusionProof(string(sectorblocks.SerializationUnixfs0)+deal.Ref.String(), status)
if err != nil {
return nil, err
}
proof := &actors.InclusionProof{
Sector: deal.SectorID,
Proof: ip.ProofElements,
}
proofB, err := cbor.DumpObject(proof)
if err != nil {
return nil, err
}
// store proofs for channels
for i, v := range deal.Proposal.Payment.Vouchers {
if v.Extra.Method == actors.MAMethods.PaymentVerifyInclusion {
// TODO: Set correct minAmount
if _, err := h.full.PaychVoucherAdd(ctx, deal.Proposal.Payment.PayChActor, v, proofB, types.NewInt(0)); err != nil {
return nil, xerrors.Errorf("storing payment voucher %d proof: %w", i, err)
}
}
}
err = h.sendSignedResponse(StorageDealResponse{
State: api.DealSealing,
Proposal: deal.ProposalCid,
PieceInclusionProof: ip,
CommD: status.CommD[:],
})
if err != nil {
log.Warnf("Sending deal response failed: %s", err)
}
return nil, nil
}
func (h *Handler) complete(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
mcid, err := h.commt.WaitCommit(ctx, deal.Proposal.MinerAddress, deal.SectorID)
if err != nil {
log.Warnf("Waiting for sector commitment message: %s", err)
}
err = h.sendSignedResponse(StorageDealResponse{
State: api.DealComplete,
Proposal: deal.ProposalCid,
SectorCommitMessage: &mcid,
})
if err != nil {
log.Warnf("Sending deal response failed: %s", err)
}
return nil, nil
}

View File

@ -2,43 +2,40 @@ package deals
import ( import (
"context" "context"
"math"
"sync" "sync"
cid "github.com/ipfs/go-cid" cid "github.com/ipfs/go-cid"
datastore "github.com/ipfs/go-datastore" datastore "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace" "github.com/ipfs/go-datastore/namespace"
cbor "github.com/ipfs/go-ipld-cbor"
inet "github.com/libp2p/go-libp2p-core/network" inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api" "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/address"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/cborrpc"
"github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/modules/dtypes"
"github.com/filecoin-project/lotus/storage/commitment" "github.com/filecoin-project/lotus/storage/commitment"
"github.com/filecoin-project/lotus/storage/sectorblocks" "github.com/filecoin-project/lotus/storage/sectorblocks"
) )
func init() {
cbor.RegisterCborType(MinerDeal{})
}
type MinerDeal struct { type MinerDeal struct {
Client peer.ID Client peer.ID
Proposal StorageDealProposal Proposal actors.StorageDealProposal
ProposalCid cid.Cid ProposalCid cid.Cid
State api.DealState State api.DealState
Ref cid.Cid Ref cid.Cid
DealID uint64
SectorID uint64 // Set when State >= DealStaged SectorID uint64 // Set when State >= DealStaged
s inet.Stream s inet.Stream
} }
type Handler struct { type Provider struct {
pricePerByteBlock types.BigInt // how much we want for storing one byte for one block pricePerByteBlock types.BigInt // how much we want for storing one byte for one block
minPieceSize uint64 minPieceSize uint64
@ -73,7 +70,7 @@ type minerDealUpdate struct {
mut func(*MinerDeal) mut func(*MinerDeal)
} }
func NewHandler(ds dtypes.MetadataDS, secst *sectorblocks.SectorBlocks, commt *commitment.Tracker, dag dtypes.StagingDAG, fullNode api.FullNode) (*Handler, error) { func NewProvider(ds dtypes.MetadataDS, secst *sectorblocks.SectorBlocks, commt *commitment.Tracker, dag dtypes.StagingDAG, fullNode api.FullNode) (*Provider, error) {
addr, err := ds.Get(datastore.NewKey("miner-address")) addr, err := ds.Get(datastore.NewKey("miner-address"))
if err != nil { if err != nil {
return nil, err return nil, err
@ -83,7 +80,7 @@ func NewHandler(ds dtypes.MetadataDS, secst *sectorblocks.SectorBlocks, commt *c
return nil, err return nil, err
} }
h := &Handler{ h := &Provider{
secst: secst, secst: secst,
commt: commt, commt: commt,
dag: dag, dag: dag,
@ -120,40 +117,40 @@ func NewHandler(ds dtypes.MetadataDS, secst *sectorblocks.SectorBlocks, commt *c
return h, nil return h, nil
} }
func (h *Handler) Run(ctx context.Context) { func (p *Provider) Run(ctx context.Context) {
// TODO: restore state // TODO: restore state
go func() { go func() {
defer log.Warn("quitting deal handler loop") defer log.Warn("quitting deal provider loop")
defer close(h.stopped) defer close(p.stopped)
for { for {
select { select {
case deal := <-h.incoming: // DealAccepted case deal := <-p.incoming: // DealAccepted
h.onIncoming(deal) p.onIncoming(deal)
case update := <-h.updated: // DealStaged case update := <-p.updated: // DealStaged
h.onUpdated(ctx, update) p.onUpdated(ctx, update)
case <-h.stop: case <-p.stop:
return return
} }
} }
}() }()
} }
func (h *Handler) onIncoming(deal MinerDeal) { func (p *Provider) onIncoming(deal MinerDeal) {
log.Info("incoming deal") log.Info("incoming deal")
h.conns[deal.ProposalCid] = deal.s p.conns[deal.ProposalCid] = deal.s
if err := h.deals.Begin(deal.ProposalCid, deal); err != nil { if err := p.deals.Begin(deal.ProposalCid, &deal); err != nil {
// This can happen when client re-sends proposal // This can happen when client re-sends proposal
h.failDeal(deal.ProposalCid, err) p.failDeal(deal.ProposalCid, err)
log.Errorf("deal tracking failed: %s", err) log.Errorf("deal tracking failed: %s", err)
return return
} }
go func() { go func() {
h.updated <- minerDealUpdate{ p.updated <- minerDealUpdate{
newState: api.DealAccepted, newState: api.DealAccepted,
id: deal.ProposalCid, id: deal.ProposalCid,
err: nil, err: nil,
@ -161,15 +158,15 @@ func (h *Handler) onIncoming(deal MinerDeal) {
}() }()
} }
func (h *Handler) onUpdated(ctx context.Context, update minerDealUpdate) { func (p *Provider) onUpdated(ctx context.Context, update minerDealUpdate) {
log.Infof("Deal %s updated state to %d", update.id, update.newState) log.Infof("Deal %s updated state to %d", update.id, update.newState)
if update.err != nil { 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)
h.failDeal(update.id, update.err) p.failDeal(update.id, update.err)
return return
} }
var deal MinerDeal var deal MinerDeal
err := h.deals.MutateMiner(update.id, func(d *MinerDeal) error { err := p.deals.MutateMiner(update.id, func(d *MinerDeal) error {
d.State = update.newState d.State = update.newState
if update.mut != nil { if update.mut != nil {
update.mut(d) update.mut(d)
@ -178,30 +175,29 @@ func (h *Handler) onUpdated(ctx context.Context, update minerDealUpdate) {
return nil return nil
}) })
if err != nil { if err != nil {
h.failDeal(update.id, err) p.failDeal(update.id, err)
return return
} }
switch update.newState { switch update.newState {
case api.DealAccepted: case api.DealAccepted:
h.handle(ctx, deal, h.accept, api.DealStaged) p.handle(ctx, deal, p.accept, api.DealStaged)
case api.DealStaged: case api.DealStaged:
h.handle(ctx, deal, h.staged, api.DealSealing) p.handle(ctx, deal, p.staged, api.DealSealing)
case api.DealSealing: case api.DealSealing:
h.handle(ctx, deal, h.sealing, api.DealComplete) p.handle(ctx, deal, p.sealing, api.DealComplete)
case api.DealComplete: case api.DealComplete:
h.handle(ctx, deal, h.complete, api.DealNoUpdate) p.handle(ctx, deal, p.complete, api.DealNoUpdate)
} }
} }
func (h *Handler) newDeal(s inet.Stream, proposal StorageDealProposal) (MinerDeal, error) { func (p *Provider) newDeal(s inet.Stream, proposal actors.StorageDealProposal) (MinerDeal, error) {
// TODO: Review: Not signed? proposalNd, err := cborrpc.AsIpld(&proposal)
proposalNd, err := cbor.WrapObject(proposal, math.MaxUint64, -1)
if err != nil { if err != nil {
return MinerDeal{}, err return MinerDeal{}, err
} }
ref, err := cid.Parse(proposal.PieceRef) ref, err := cid.Cast(proposal.PieceRef)
if err != nil { if err != nil {
return MinerDeal{}, err return MinerDeal{}, err
} }
@ -218,27 +214,27 @@ func (h *Handler) newDeal(s inet.Stream, proposal StorageDealProposal) (MinerDea
}, nil }, nil
} }
func (h *Handler) HandleStream(s inet.Stream) { func (p *Provider) HandleStream(s inet.Stream) {
log.Info("Handling storage deal proposal!") log.Info("Handling storage deal proposal!")
proposal, err := h.readProposal(s) proposal, err := p.readProposal(s)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
s.Close() s.Close()
return return
} }
deal, err := h.newDeal(s, proposal.Proposal) deal, err := p.newDeal(s, proposal)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
s.Close() s.Close()
return return
} }
h.incoming <- deal p.incoming <- deal
} }
func (h *Handler) Stop() { func (p *Provider) Stop() {
close(h.stop) close(p.stop)
<-h.stopped <-p.stopped
} }

View File

@ -1,6 +1,7 @@
package deals package deals
import ( import (
"bytes"
"context" "context"
"time" "time"
@ -9,49 +10,48 @@ import (
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/cborrpc" "github.com/filecoin-project/lotus/lib/cborrpc"
datastore "github.com/ipfs/go-datastore" datastore "github.com/ipfs/go-datastore"
cbor "github.com/ipfs/go-ipld-cbor"
inet "github.com/libp2p/go-libp2p-core/network" inet "github.com/libp2p/go-libp2p-core/network"
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
func (h *Handler) SetPrice(p types.BigInt, ttlsecs int64) error { func (p *Provider) SetPrice(price types.BigInt, ttlsecs int64) error {
h.askLk.Lock() p.askLk.Lock()
defer h.askLk.Unlock() defer p.askLk.Unlock()
var seqno uint64 var seqno uint64
if h.ask != nil { if p.ask != nil {
seqno = h.ask.Ask.SeqNo + 1 seqno = p.ask.Ask.SeqNo + 1
} }
now := time.Now().Unix() now := time.Now().Unix()
ask := &types.StorageAsk{ ask := &types.StorageAsk{
Price: p, Price: price,
Timestamp: now, Timestamp: uint64(now),
Expiry: now + ttlsecs, Expiry: uint64(now + ttlsecs),
Miner: h.actor, Miner: p.actor,
SeqNo: seqno, SeqNo: seqno,
MinPieceSize: h.minPieceSize, MinPieceSize: p.minPieceSize,
} }
ssa, err := h.signAsk(ask) ssa, err := p.signAsk(ask)
if err != nil { if err != nil {
return err return err
} }
return h.saveAsk(ssa) return p.saveAsk(ssa)
} }
func (h *Handler) getAsk(m address.Address) *types.SignedStorageAsk { func (p *Provider) getAsk(m address.Address) *types.SignedStorageAsk {
h.askLk.Lock() p.askLk.Lock()
defer h.askLk.Unlock() defer p.askLk.Unlock()
if m != h.actor { if m != p.actor {
return nil return nil
} }
return h.ask return p.ask
} }
func (h *Handler) HandleAskStream(s inet.Stream) { func (p *Provider) HandleAskStream(s inet.Stream) {
defer s.Close() defer s.Close()
var ar AskRequest var ar AskRequest
if err := cborrpc.ReadCborRPC(s, &ar); err != nil { if err := cborrpc.ReadCborRPC(s, &ar); err != nil {
@ -59,7 +59,7 @@ func (h *Handler) HandleAskStream(s inet.Stream) {
return return
} }
resp := h.processAskRequest(&ar) resp := p.processAskRequest(&ar)
if err := cborrpc.WriteCborRPC(s, resp); err != nil { if err := cborrpc.WriteCborRPC(s, resp); err != nil {
log.Errorf("failed to write ask response: %s", err) log.Errorf("failed to write ask response: %s", err)
@ -67,19 +67,19 @@ func (h *Handler) HandleAskStream(s inet.Stream) {
} }
} }
func (h *Handler) processAskRequest(ar *AskRequest) *AskResponse { func (p *Provider) processAskRequest(ar *AskRequest) *AskResponse {
return &AskResponse{ return &AskResponse{
Ask: h.getAsk(ar.Miner), Ask: p.getAsk(ar.Miner),
} }
} }
var bestAskKey = datastore.NewKey("latest-ask") var bestAskKey = datastore.NewKey("latest-ask")
func (h *Handler) tryLoadAsk() error { func (p *Provider) tryLoadAsk() error {
h.askLk.Lock() p.askLk.Lock()
defer h.askLk.Unlock() defer p.askLk.Unlock()
err := h.loadAsk() err := p.loadAsk()
if err != nil { if err != nil {
if xerrors.Is(err, datastore.ErrNotFound) { if xerrors.Is(err, datastore.ErrNotFound) {
log.Warn("no previous ask found, miner will not accept deals until a price is set") log.Warn("no previous ask found, miner will not accept deals until a price is set")
@ -91,33 +91,33 @@ func (h *Handler) tryLoadAsk() error {
return nil return nil
} }
func (h *Handler) loadAsk() error { func (p *Provider) loadAsk() error {
askb, err := h.ds.Get(datastore.NewKey("latest-ask")) askb, err := p.ds.Get(datastore.NewKey("latest-ask"))
if err != nil { if err != nil {
return xerrors.Errorf("failed to load most recent ask from disk: %w", err) return xerrors.Errorf("failed to load most recent ask from disk: %w", err)
} }
var ssa types.SignedStorageAsk var ssa types.SignedStorageAsk
if err := cbor.DecodeInto(askb, &ssa); err != nil { if err := cborrpc.ReadCborRPC(bytes.NewReader(askb), &ssa); err != nil {
return err return err
} }
h.ask = &ssa p.ask = &ssa
return nil return nil
} }
func (h *Handler) signAsk(a *types.StorageAsk) (*types.SignedStorageAsk, error) { func (p *Provider) signAsk(a *types.StorageAsk) (*types.SignedStorageAsk, error) {
b, err := cbor.DumpObject(a) b, err := cborrpc.Dump(a)
if err != nil { if err != nil {
return nil, err return nil, err
} }
worker, err := h.getWorker(h.actor) worker, err := p.getWorker(p.actor)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to get worker to sign ask: %w", err) return nil, xerrors.Errorf("failed to get worker to sign ask: %w", err)
} }
sig, err := h.full.WalletSign(context.TODO(), worker, b) sig, err := p.full.WalletSign(context.TODO(), worker, b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -128,29 +128,29 @@ func (h *Handler) signAsk(a *types.StorageAsk) (*types.SignedStorageAsk, error)
}, nil }, nil
} }
func (h *Handler) saveAsk(a *types.SignedStorageAsk) error { func (p *Provider) saveAsk(a *types.SignedStorageAsk) error {
b, err := cbor.DumpObject(a) b, err := cborrpc.Dump(a)
if err != nil { if err != nil {
return err return err
} }
if err := h.ds.Put(bestAskKey, b); err != nil { if err := p.ds.Put(bestAskKey, b); err != nil {
return err return err
} }
h.ask = a p.ask = a
return nil return nil
} }
func (c *Client) checkAskSignature(ask *types.SignedStorageAsk) error { func (c *Client) checkAskSignature(ask *types.SignedStorageAsk) error {
tss := c.sm.ChainStore().GetHeaviestTipSet().ParentState() 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 { if err != nil {
return xerrors.Errorf("failed to get worker for miner in ask", err) return xerrors.Errorf("failed to get worker for miner in ask", err)
} }
sigb, err := cbor.DumpObject(ask.Ask) sigb, err := cborrpc.Dump(ask.Ask)
if err != nil { if err != nil {
return xerrors.Errorf("failed to re-serialize ask") return xerrors.Errorf("failed to re-serialize ask")
} }

View File

@ -0,0 +1,305 @@
package deals
import (
"bytes"
"context"
"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"
)
type providerHandlerFunc func(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error)
func (p *Provider) handle(ctx context.Context, deal MinerDeal, cb providerHandlerFunc, next api.DealState) {
go func() {
mut, err := cb(ctx, deal)
if err == nil && next == api.DealNoUpdate {
return
}
select {
case p.updated <- minerDealUpdate{
newState: next,
id: deal.ProposalCid,
err: err,
mut: mut,
}:
case <-p.stop:
}
}()
}
// ACCEPTED
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: worker,
Value: deal.Proposal.StorageCollateral,
GasPrice: types.NewInt(0),
GasLimit: types.NewInt(1000000),
Method: actors.SMAMethods.AddBalance,
})
if err != nil {
return err
}
r, err := p.full.StateWaitMsg(ctx, smsg.Cid())
if err != nil {
return err
}
if r.Receipt.ExitCode != 0 {
return xerrors.Errorf("adding funds to storage miner market actor failed: exit %d", r.Receipt.ExitCode)
}
return nil
}
func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
switch deal.Proposal.PieceSerialization {
//case SerializationRaw:
//case SerializationIPLD:
case actors.SerializationUnixFSv0:
default:
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
// TODO:
minPrice := types.BigMul(p.ask.Ask.Price, types.BigMul(types.NewInt(deal.Proposal.Duration), types.NewInt(deal.Proposal.PieceSize)))
if deal.Proposal.StoragePrice.LessThan(minPrice) {
return nil, xerrors.Errorf("storage price less than asking price: %s < %s", deal.Proposal.StoragePrice, minPrice)
}
if deal.Proposal.PieceSize < p.ask.Ask.MinPieceSize {
return nil, xerrors.Errorf("piece size less than minimum required size: %d < %d", deal.Proposal.PieceSize, p.ask.Ask.MinPieceSize)
}
// check market funds
clientMarketBalance, err := p.full.StateMarketBalance(ctx, deal.Proposal.Client, nil)
if err != nil {
return nil, xerrors.Errorf("getting client market balance failed: %w", err)
}
// This doesn't guarantee that the client won't withdraw / lock those funds
// but it's a decent first filter
if clientMarketBalance.Available.LessThan(deal.Proposal.StoragePrice) {
return nil, xerrors.New("clientMarketBalance.Available too small")
}
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, 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: 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
}
r, err := p.full.StateWaitMsg(ctx, smsg.Cid())
if err != nil {
return nil, err
}
if r.Receipt.ExitCode != 0 {
return nil, xerrors.Errorf("publishing deal failed: exit %d", r.Receipt.ExitCode)
}
var resp actors.PublishStorageDealResponse
if err := resp.UnmarshalCBOR(bytes.NewReader(r.Receipt.Return)); err != nil {
return nil, err
}
if len(resp.DealIDs) != 1 {
return nil, xerrors.Errorf("got unexpected number of DealIDs from")
}
log.Info("fetching data for a deal")
mcid := smsg.Cid()
err = p.sendSignedResponse(&Response{
State: api.DealAccepted,
Message: "",
Proposal: deal.ProposalCid,
PublishMessage: &mcid,
})
if err != nil {
return nil, err
}
return func(deal *MinerDeal) {
deal.DealID = resp.DealIDs[0]
}, merkledag.FetchGraph(ctx, deal.Ref, p.dag)
}
// STAGED
func (p *Provider) staged(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
err := p.sendSignedResponse(&Response{
State: api.DealStaged,
Proposal: deal.ProposalCid,
})
if err != nil {
log.Warnf("Sending deal response failed: %s", err)
}
root, err := p.dag.Get(ctx, deal.Ref)
if err != nil {
return nil, xerrors.Errorf("failed to get file root for deal: %s", err)
}
// TODO: abstract this away into ReadSizeCloser + implement different modes
n, err := unixfile.NewUnixfsFile(ctx, p.dag, root)
if err != nil {
return nil, xerrors.Errorf("cannot open unixfs file: %s", err)
}
uf, ok := n.(sectorblocks.UnixfsReader)
if !ok {
// we probably got directory, unsupported for now
return nil, xerrors.Errorf("unsupported unixfs file type")
}
// TODO: uf.Size() is user input, not trusted
// This won't be useful / here after we migrate to putting CARs into sectors
size, err := uf.Size()
if err != nil {
return nil, xerrors.Errorf("getting unixfs file size: %w", err)
}
if uint64(size) != deal.Proposal.PieceSize {
return nil, xerrors.Errorf("deal.Proposal.PieceSize didn't match unixfs file size")
}
pcid, err := cid.Cast(deal.Proposal.PieceRef)
if err != nil {
return nil, err
}
sectorID, err := p.secst.AddUnixfsPiece(pcid, uf, deal.DealID)
if err != nil {
return nil, xerrors.Errorf("AddPiece failed: %s", err)
}
log.Warnf("New Sector: %d", sectorID)
return func(deal *MinerDeal) {
deal.SectorID = sectorID
}, nil
}
// SEALING
func (p *Provider) waitSealed(ctx context.Context, deal MinerDeal) (sectorbuilder.SectorSealingStatus, error) {
status, err := p.secst.WaitSeal(ctx, deal.SectorID)
if err != nil {
return sectorbuilder.SectorSealingStatus{}, err
}
switch status.State {
case sealing_state.Sealed:
case sealing_state.Failed:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("sealing sector %d for deal %s (ref=%s) failed: %s", deal.SectorID, deal.ProposalCid, deal.Ref, status.SealErrorMsg)
case sealing_state.Pending:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("sector status was 'pending' after call to WaitSeal (for sector %d)", deal.SectorID)
case sealing_state.Sealing:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("sector status was 'wait' after call to WaitSeal (for sector %d)", deal.SectorID)
default:
return sectorbuilder.SectorSealingStatus{}, xerrors.Errorf("unknown SealStatusCode: %d", status.SectorID)
}
return status, nil
}
func (p *Provider) sealing(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
err := p.sendSignedResponse(&Response{
State: api.DealSealing,
Proposal: deal.ProposalCid,
})
if err != nil {
log.Warnf("Sending deal response failed: %s", err)
}
_, err = p.waitSealed(ctx, deal)
if err != nil {
return nil, err
}
// TODO: Spec doesn't say anything about inclusion proofs anywhere
// Not sure what mechanisms prevents miner from storing data that isn't
// clients' data
return nil, nil
}
func (p *Provider) complete(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
// TODO: Add dealID to commtracker (probably before sealing)
mcid, err := p.commt.WaitCommit(ctx, deal.Proposal.Provider, deal.SectorID)
if err != nil {
log.Warnf("Waiting for sector commitment message: %s", err)
}
err = p.sendSignedResponse(&Response{
State: api.DealComplete,
Proposal: deal.ProposalCid,
CommitMessage: &mcid,
})
if err != nil {
log.Warnf("Sending deal response failed: %s", err)
}
return nil, nil
}

View File

@ -12,13 +12,12 @@ import (
"github.com/filecoin-project/lotus/lib/cborrpc" "github.com/filecoin-project/lotus/lib/cborrpc"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
inet "github.com/libp2p/go-libp2p-core/network" inet "github.com/libp2p/go-libp2p-core/network"
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
func (h *Handler) failDeal(id cid.Cid, cerr error) { func (p *Provider) failDeal(id cid.Cid, cerr error) {
if err := h.deals.End(id); err != nil { if err := p.deals.End(id); err != nil {
log.Warnf("deals.End: %s", err) log.Warnf("deals.End: %s", err)
} }
@ -29,16 +28,16 @@ func (h *Handler) failDeal(id cid.Cid, cerr error) {
log.Errorf("deal %s failed: %s", id, cerr) log.Errorf("deal %s failed: %s", id, cerr)
err := h.sendSignedResponse(StorageDealResponse{ err := p.sendSignedResponse(&Response{
State: api.DealFailed, State: api.DealFailed,
Message: cerr.Error(), Message: cerr.Error(),
Proposal: id, Proposal: id,
}) })
s, ok := h.conns[id] s, ok := p.conns[id]
if ok { if ok {
_ = s.Reset() _ = s.Reset()
delete(h.conns, id) delete(p.conns, id)
} }
if err != nil { if err != nil {
@ -46,46 +45,50 @@ func (h *Handler) failDeal(id cid.Cid, cerr error) {
} }
} }
func (h *Handler) readProposal(s inet.Stream) (proposal SignedStorageDealProposal, err error) { func (p *Provider) readProposal(s inet.Stream) (proposal actors.StorageDealProposal, err error) {
if err := cborrpc.ReadCborRPC(s, &proposal); err != nil { if err := cborrpc.ReadCborRPC(s, &proposal); err != nil {
log.Errorw("failed to read proposal message", "error", err) log.Errorw("failed to read proposal message", "error", err)
return SignedStorageDealProposal{}, err return proposal, err
}
if err := proposal.Verify(); err != nil {
return proposal, xerrors.Errorf("verifying StorageDealProposal: %w", err)
} }
// TODO: Validate proposal maybe // TODO: Validate proposal maybe
// (and signature, obviously) // (and signature, obviously)
if proposal.Proposal.MinerAddress != h.actor { if proposal.Provider != p.actor {
log.Errorf("proposal with wrong MinerAddress: %s", proposal.Proposal.MinerAddress) log.Errorf("proposal with wrong ProviderAddress: %s", proposal.Provider)
return SignedStorageDealProposal{}, err return proposal, err
} }
return return
} }
func (h *Handler) sendSignedResponse(resp StorageDealResponse) error { func (p *Provider) sendSignedResponse(resp *Response) error {
s, ok := h.conns[resp.Proposal] s, ok := p.conns[resp.Proposal]
if !ok { if !ok {
return xerrors.New("couldn't send response: not connected") return xerrors.New("couldn't send response: not connected")
} }
msg, err := cbor.DumpObject(&resp) msg, err := cborrpc.Dump(resp)
if err != nil { if err != nil {
return xerrors.Errorf("serializing response: %w", err) return xerrors.Errorf("serializing response: %w", err)
} }
worker, err := h.getWorker(h.actor) worker, err := p.getWorker(p.actor)
if err != nil { if err != nil {
return err return err
} }
sig, err := h.full.WalletSign(context.TODO(), worker, msg) sig, err := p.full.WalletSign(context.TODO(), worker, msg)
if err != nil { if err != nil {
return xerrors.Errorf("failed to sign response message: %w", err) return xerrors.Errorf("failed to sign response message: %w", err)
} }
signedResponse := SignedStorageDealResponse{ signedResponse := &SignedResponse{
Response: resp, Response: *resp,
Signature: sig, Signature: sig,
} }
@ -93,18 +96,18 @@ func (h *Handler) sendSignedResponse(resp StorageDealResponse) error {
if err != nil { if err != nil {
// Assume client disconnected // Assume client disconnected
s.Close() s.Close()
delete(h.conns, resp.Proposal) delete(p.conns, resp.Proposal)
} }
return err return err
} }
func (h *Handler) getWorker(miner address.Address) (address.Address, error) { func (p *Provider) getWorker(miner address.Address) (address.Address, error) {
getworker := &types.Message{ getworker := &types.Message{
To: miner, To: miner,
From: miner, From: miner,
Method: actors.MAMethods.GetWorkerAddr, Method: actors.MAMethods.GetWorkerAddr,
} }
r, err := h.full.StateCall(context.TODO(), getworker, nil) r, err := p.full.StateCall(context.TODO(), getworker, nil)
if err != nil { if err != nil {
return address.Undef, xerrors.Errorf("getting worker address: %w", err) return address.Undef, xerrors.Errorf("getting worker address: %w", err)
} }

View File

@ -1,10 +1,11 @@
package deals package deals
import ( import (
"bytes"
"github.com/filecoin-project/lotus/lib/cborrpc"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/query" "github.com/ipfs/go-datastore/query"
cbor "github.com/ipfs/go-ipld-cbor"
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
@ -22,7 +23,7 @@ func (st *StateStore) Begin(i cid.Cid, state interface{}) error {
return xerrors.Errorf("Already tracking state for %s", i) return xerrors.Errorf("Already tracking state for %s", i)
} }
b, err := cbor.DumpObject(state) b, err := cborrpc.Dump(state)
if err != nil { if err != nil {
return err return err
} }
@ -75,17 +76,17 @@ func (st *MinerStateStore) MutateMiner(i cid.Cid, mutator func(*MinerDeal) error
func minerMutator(m func(*MinerDeal) error) func([]byte) ([]byte, error) { func minerMutator(m func(*MinerDeal) error) func([]byte) ([]byte, error) {
return func(in []byte) ([]byte, error) { return func(in []byte) ([]byte, error) {
var deal MinerDeal deal := new(MinerDeal)
err := cbor.DecodeInto(in, &deal) err := cborrpc.ReadCborRPC(bytes.NewReader(in), deal)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := m(&deal); err != nil { if err := m(deal); err != nil {
return nil, err return nil, err
} }
return cbor.DumpObject(deal) return cborrpc.Dump(deal)
} }
} }
@ -99,17 +100,17 @@ func (st *ClientStateStore) MutateClient(i cid.Cid, mutator func(*ClientDeal) er
func clientMutator(m func(*ClientDeal) error) func([]byte) ([]byte, error) { func clientMutator(m func(*ClientDeal) error) func([]byte) ([]byte, error) {
return func(in []byte) ([]byte, error) { return func(in []byte) ([]byte, error) {
var deal ClientDeal deal := new(ClientDeal)
err := cbor.DecodeInto(in, &deal) err := cborrpc.ReadCborRPC(bytes.NewReader(in), deal)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := m(&deal); err != nil { if err := m(deal); err != nil {
return nil, err return nil, err
} }
return cbor.DumpObject(deal) return cborrpc.Dump(deal)
} }
} }
@ -129,7 +130,7 @@ func (st *ClientStateStore) ListClient() ([]ClientDeal, error) {
} }
var deal ClientDeal var deal ClientDeal
err := cbor.DecodeInto(res.Value, &deal) err := cborrpc.ReadCborRPC(bytes.NewReader(res.Value), &deal)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,83 +2,37 @@ package deals
import ( import (
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
"github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/ipfs/go-cid"
) )
func init() { const DealProtocolID = "/fil/storage/mk/1.0.0"
cbor.RegisterCborType(StorageDealProposal{})
cbor.RegisterCborType(SignedStorageDealProposal{})
cbor.RegisterCborType(PieceInclusionProof{})
cbor.RegisterCborType(StorageDealResponse{})
cbor.RegisterCborType(SignedStorageDealResponse{})
cbor.RegisterCborType(AskRequest{})
cbor.RegisterCborType(AskResponse{})
}
const ProtocolID = "/fil/storage/mk/1.0.0"
const AskProtocolID = "/fil/storage/ask/1.0.0" const AskProtocolID = "/fil/storage/ask/1.0.0"
type SerializationMode string type Proposal struct {
DealProposal actors.StorageDealProposal
const (
SerializationUnixFs = "UnixFs"
SerializationRaw = "Raw"
SerializationIPLD = "IPLD"
)
type StorageDealProposal struct {
PieceRef cid.Cid // TODO: port to spec
SerializationMode SerializationMode
CommP []byte
Size uint64
TotalPrice types.BigInt
Duration uint64
Payment actors.PaymentInfo
MinerAddress address.Address
ClientAddress address.Address
} }
type SignedStorageDealProposal struct { type Response struct {
Proposal StorageDealProposal
Signature *types.Signature
}
// response
type PieceInclusionProof struct {
Position uint64
ProofElements []byte
}
type StorageDealResponse struct {
State api.DealState State api.DealState
// DealRejected / DealAccepted / DealFailed / DealStaged // DealProposalRejected
Message string Message string
Proposal cid.Cid Proposal cid.Cid
// DealSealing // DealAccepted
PieceInclusionProof PieceInclusionProof StorageDeal *actors.StorageDeal
CommD []byte // TODO: not in spec PublishMessage *cid.Cid
// DealComplete // DealComplete
SectorCommitMessage *cid.Cid CommitMessage *cid.Cid
} }
type SignedStorageDealResponse struct { // TODO: Do we actually need this to be signed?
Response StorageDealResponse type SignedResponse struct {
Response Response
Signature *types.Signature Signature *types.Signature
} }

View File

@ -1,31 +0,0 @@
package deals
import (
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
)
func VoucherSpec(blocksDuration uint64, price types.BigInt, start uint64, extra *types.ModVerifyParams) []api.VoucherSpec {
nVouchers := blocksDuration / build.MinDealVoucherIncrement
if nVouchers < 1 {
nVouchers = 1
}
if nVouchers > build.MaxVouchersPerDeal {
nVouchers = build.MaxVouchersPerDeal
}
hIncrements := blocksDuration / nVouchers
vouchers := make([]api.VoucherSpec, nVouchers)
for i := uint64(0); i < nVouchers; i++ {
vouchers[i] = api.VoucherSpec{
Amount: types.BigDiv(types.BigMul(price, types.NewInt(i+1)), types.NewInt(nVouchers)),
TimeLock: start + (hIncrements * (i + 1)),
MinClose: start + (hIncrements * (i + 1)),
Extra: extra,
}
}
return vouchers
}

View File

@ -206,7 +206,7 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add
st := pts.ParentState() st := pts.ParentState()
worker, err := stmgr.GetMinerWorker(ctx, cg.sm, st, m) worker, err := stmgr.GetMinerWorkerRaw(ctx, cg.sm, st, m)
if err != nil { if err != nil {
return nil, nil, err 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) { 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) { 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)) 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 { if err != nil {
return nil, xerrors.Errorf("failed to get miner worker: %w", err) return nil, xerrors.Errorf("failed to get miner worker: %w", err)
} }

View File

@ -5,6 +5,14 @@ import (
"fmt" "fmt"
amt "github.com/filecoin-project/go-amt-ipld" amt "github.com/filecoin-project/go-amt-ipld"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
hamt "github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
peer "github.com/libp2p/go-libp2p-peer"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
actors "github.com/filecoin-project/lotus/chain/actors" actors "github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/address"
@ -12,14 +20,6 @@ import (
"github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/chain/vm"
"golang.org/x/xerrors"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
hamt "github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
peer "github.com/libp2p/go-libp2p-peer"
cbg "github.com/whyrusleeping/cbor-gen"
) )
type GenesisBootstrap struct { type GenesisBootstrap struct {
@ -56,7 +56,7 @@ func SetupInitActor(bs bstore.Blockstore, addrs []address.Address) (*types.Actor
} }
act := &types.Actor{ act := &types.Actor{
Code: actors.InitActorCodeCid, Code: actors.InitCodeCid,
Head: statecid, Head: statecid,
} }
@ -85,10 +85,19 @@ func MakeInitialStateTree(bs bstore.Blockstore, actmap map[address.Address]types
return nil, xerrors.Errorf("setup init actor: %w", err) return nil, xerrors.Errorf("setup init actor: %w", err)
} }
if err := state.SetActor(actors.InitActorAddress, initact); err != nil { if err := state.SetActor(actors.InitAddress, initact); err != nil {
return nil, xerrors.Errorf("set init actor: %w", err) return nil, xerrors.Errorf("set init actor: %w", err)
} }
spact, err := SetupStoragePowerActor(bs)
if err != nil {
return nil, xerrors.Errorf("setup storage market actor: %w", err)
}
if err := state.SetActor(actors.StoragePowerAddress, spact); err != nil {
return nil, xerrors.Errorf("set storage market actor: %w", err)
}
smact, err := SetupStorageMarketActor(bs) smact, err := SetupStorageMarketActor(bs)
if err != nil { if err != nil {
return nil, xerrors.Errorf("setup storage market actor: %w", err) return nil, xerrors.Errorf("setup storage market actor: %w", err)
@ -104,7 +113,7 @@ func MakeInitialStateTree(bs bstore.Blockstore, actmap map[address.Address]types
} }
err = state.SetActor(actors.NetworkAddress, &types.Actor{ err = state.SetActor(actors.NetworkAddress, &types.Actor{
Code: actors.AccountActorCodeCid, Code: actors.AccountCodeCid,
Balance: netAmt, Balance: netAmt,
Head: emptyobject, Head: emptyobject,
}) })
@ -113,7 +122,7 @@ func MakeInitialStateTree(bs bstore.Blockstore, actmap map[address.Address]types
} }
err = state.SetActor(actors.BurntFundsAddress, &types.Actor{ err = state.SetActor(actors.BurntFundsAddress, &types.Actor{
Code: actors.AccountActorCodeCid, Code: actors.AccountCodeCid,
Balance: types.NewInt(0), Balance: types.NewInt(0),
Head: emptyobject, Head: emptyobject,
}) })
@ -123,7 +132,7 @@ func MakeInitialStateTree(bs bstore.Blockstore, actmap map[address.Address]types
for a, v := range actmap { for a, v := range actmap {
err = state.SetActor(a, &types.Actor{ err = state.SetActor(a, &types.Actor{
Code: actors.AccountActorCodeCid, Code: actors.AccountCodeCid,
Balance: v, Balance: v,
Head: emptyobject, Head: emptyobject,
}) })
@ -135,7 +144,7 @@ func MakeInitialStateTree(bs bstore.Blockstore, actmap map[address.Address]types
return state, nil return state, nil
} }
func SetupStorageMarketActor(bs bstore.Blockstore) (*types.Actor, error) { func SetupStoragePowerActor(bs bstore.Blockstore) (*types.Actor, error) {
cst := hamt.CSTFromBstore(bs) cst := hamt.CSTFromBstore(bs)
nd := hamt.NewNode(cst) nd := hamt.NewNode(cst)
emptyhamt, err := cst.Put(context.TODO(), nd) emptyhamt, err := cst.Put(context.TODO(), nd)
@ -154,7 +163,41 @@ func SetupStorageMarketActor(bs bstore.Blockstore) (*types.Actor, error) {
} }
return &types.Actor{ return &types.Actor{
Code: actors.StorageMarketActorCodeCid, Code: actors.StoragePowerCodeCid,
Head: stcid,
Nonce: 0,
Balance: types.NewInt(0),
}, nil
}
func SetupStorageMarketActor(bs bstore.Blockstore) (*types.Actor, error) {
cst := hamt.CSTFromBstore(bs)
nd := hamt.NewNode(cst)
emptyHAMT, err := cst.Put(context.TODO(), nd)
if err != nil {
return nil, err
}
blks := amt.WrapBlockstore(bs)
emptyAMT, err := amt.FromArray(blks, nil)
if err != nil {
return nil, xerrors.Errorf("amt build failed: %w", err)
}
sms := &actors.StorageMarketState{
Balances: emptyHAMT,
Deals: emptyAMT,
NextDealID: 0,
}
stcid, err := cst.Put(context.TODO(), sms)
if err != nil {
return nil, err
}
return &types.Actor{
Code: actors.StorageMarketCodeCid,
Head: stcid, Head: stcid,
Nonce: 0, Nonce: 0,
Balance: types.NewInt(0), Balance: types.NewInt(0),
@ -202,7 +245,7 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid
// TODO: hardcoding 7000000 here is a little fragile, it changes any // TODO: hardcoding 7000000 here is a little fragile, it changes any
// time anyone changes the initial account allocations // time anyone changes the initial account allocations
rval, err := doExecValue(ctx, vm, actors.StorageMarketAddress, owner, types.FromFil(6500), actors.SPAMethods.CreateStorageMiner, params) rval, err := doExecValue(ctx, vm, actors.StoragePowerAddress, owner, types.FromFil(6500), actors.SPAMethods.CreateStorageMiner, params)
if err != nil { if err != nil {
return cid.Undef, xerrors.Errorf("failed to create genesis miner: %w", err) return cid.Undef, xerrors.Errorf("failed to create genesis miner: %w", err)
} }
@ -216,7 +259,7 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid
params = mustEnc(&actors.UpdateStorageParams{Delta: types.NewInt(5000)}) params = mustEnc(&actors.UpdateStorageParams{Delta: types.NewInt(5000)})
_, err = doExec(ctx, vm, actors.StorageMarketAddress, maddr, actors.SPAMethods.UpdateStorage, params) _, err = doExec(ctx, vm, actors.StoragePowerAddress, maddr, actors.SPAMethods.UpdateStorage, params)
if err != nil { if err != nil {
return cid.Undef, xerrors.Errorf("failed to update total storage: %w", err) return cid.Undef, xerrors.Errorf("failed to update total storage: %w", err)
} }
@ -333,7 +376,7 @@ func MakeGenesisBlock(bs bstore.Blockstore, balances map[address.Address]types.B
} }
b := &types.BlockHeader{ b := &types.BlockHeader{
Miner: actors.InitActorAddress, Miner: actors.InitAddress,
Tickets: []*types.Ticket{genesisticket}, Tickets: []*types.Ticket{genesisticket},
ElectionProof: []byte("the Genesis block"), ElectionProof: []byte("the Genesis block"),
Parents: []cid.Cid{}, Parents: []cid.Cid{},

View File

@ -68,7 +68,7 @@ func (st *StateTree) SetActor(addr address.Address, act *types.Actor) error {
} }
func (st *StateTree) lookupID(addr address.Address) (address.Address, error) { func (st *StateTree) lookupID(addr address.Address) (address.Address, error) {
act, err := st.GetActor(actors.InitActorAddress) act, err := st.GetActor(actors.InitAddress)
if err != nil { if err != nil {
return address.Undef, xerrors.Errorf("getting init actor: %w", err) return address.Undef, xerrors.Errorf("getting init actor: %w", err)
} }
@ -143,7 +143,7 @@ func (st *StateTree) Snapshot() error {
func (st *StateTree) RegisterNewAddress(addr address.Address, act *types.Actor) (address.Address, error) { func (st *StateTree) RegisterNewAddress(addr address.Address, act *types.Actor) (address.Address, error) {
var out address.Address var out address.Address
err := st.MutateActor(actors.InitActorAddress, func(initact *types.Actor) error { err := st.MutateActor(actors.InitAddress, func(initact *types.Actor) error {
var ias actors.InitActorState var ias actors.InitActorState
if err := st.Store.Get(context.TODO(), initact.Head, &ias); err != nil { if err := st.Store.Get(context.TODO(), initact.Head, &ias); err != nil {
return err return err

View File

@ -27,7 +27,7 @@ func BenchmarkStateTreeSet(b *testing.B) {
err = st.SetActor(a, &types.Actor{ err = st.SetActor(a, &types.Actor{
Balance: types.NewInt(1258812523), Balance: types.NewInt(1258812523),
Code: actors.StorageMinerCodeCid, Code: actors.StorageMinerCodeCid,
Head: actors.AccountActorCodeCid, Head: actors.AccountCodeCid,
Nonce: uint64(i), Nonce: uint64(i),
}) })
if err != nil { if err != nil {
@ -54,7 +54,7 @@ func BenchmarkStateTreeSetFlush(b *testing.B) {
err = st.SetActor(a, &types.Actor{ err = st.SetActor(a, &types.Actor{
Balance: types.NewInt(1258812523), Balance: types.NewInt(1258812523),
Code: actors.StorageMinerCodeCid, Code: actors.StorageMinerCodeCid,
Head: actors.AccountActorCodeCid, Head: actors.AccountCodeCid,
Nonce: uint64(i), Nonce: uint64(i),
}) })
if err != nil { if err != nil {
@ -80,7 +80,7 @@ func BenchmarkStateTree10kGetActor(b *testing.B) {
err = st.SetActor(a, &types.Actor{ err = st.SetActor(a, &types.Actor{
Balance: types.NewInt(1258812523 + uint64(i)), Balance: types.NewInt(1258812523 + uint64(i)),
Code: actors.StorageMinerCodeCid, Code: actors.StorageMinerCodeCid,
Head: actors.AccountActorCodeCid, Head: actors.AccountCodeCid,
Nonce: uint64(i), Nonce: uint64(i),
}) })
if err != nil { if err != nil {
@ -123,7 +123,7 @@ func TestSetCache(t *testing.T) {
act := &types.Actor{ act := &types.Actor{
Balance: types.NewInt(0), Balance: types.NewInt(0),
Code: actors.StorageMinerCodeCid, Code: actors.StorageMinerCodeCid,
Head: actors.AccountActorCodeCid, Head: actors.AccountCodeCid,
Nonce: 0, Nonce: 0,
} }

View File

@ -462,3 +462,17 @@ func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]
return out, nil 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" "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{ recp, err := sm.CallRaw(ctx, &types.Message{
To: maddr, To: maddr,
From: maddr, From: maddr,
@ -80,7 +80,7 @@ func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr add
} }
ret, err := sm.Call(ctx, &types.Message{ ret, err := sm.Call(ctx, &types.Message{
From: maddr, From: maddr,
To: actors.StorageMarketAddress, To: actors.StoragePowerAddress,
Method: actors.SPAMethods.PowerLookup, Method: actors.SPAMethods.PowerLookup,
Params: enc, Params: enc,
}, ts) }, ts)
@ -95,8 +95,8 @@ func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr add
} }
ret, err := sm.Call(ctx, &types.Message{ ret, err := sm.Call(ctx, &types.Message{
From: actors.StorageMarketAddress, From: actors.StoragePowerAddress,
To: actors.StorageMarketAddress, To: actors.StoragePowerAddress,
Method: actors.SPAMethods.GetTotalStorage, Method: actors.SPAMethods.GetTotalStorage,
}, ts) }, ts)
if err != nil { if err != nil {
@ -118,7 +118,7 @@ func GetMinerPeerID(ctx context.Context, sm *StateManager, ts *types.TipSet, mad
Method: actors.MAMethods.GetPeerID, Method: actors.MAMethods.GetPeerID,
}, ts) }, ts)
if err != nil { if err != nil {
return "", xerrors.Errorf("callRaw failed: %w", err) return "", xerrors.Errorf("call failed: %w", err)
} }
if recp.ExitCode != 0 { if recp.ExitCode != 0 {
@ -128,6 +128,23 @@ func GetMinerPeerID(ctx context.Context, sm *StateManager, ts *types.TipSet, mad
return peer.IDFromBytes(recp.Return) 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) { func GetMinerProvingPeriodEnd(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (uint64, error) {
var mas actors.StorageMinerActorState var mas actors.StorageMinerActorState
_, err := sm.LoadActorState(ctx, maddr, &mas, ts) _, err := sm.LoadActorState(ctx, maddr, &mas, ts)

View File

@ -23,8 +23,8 @@ func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigIn
// >>> wFunction(totalPowerAtTipset(ts)) * 2^8 <<< + (wFunction(totalPowerAtTipset(ts)) * len(ts.blocks) * wRatio_num * 2^8) / (e * wRatio_den) // >>> wFunction(totalPowerAtTipset(ts)) * 2^8 <<< + (wFunction(totalPowerAtTipset(ts)) * len(ts.blocks) * wRatio_num * 2^8) / (e * wRatio_den)
ret, err := cs.call(ctx, &types.Message{ ret, err := cs.call(ctx, &types.Message{
From: actors.StorageMarketAddress, From: actors.StoragePowerAddress,
To: actors.StorageMarketAddress, To: actors.StoragePowerAddress,
Method: actors.SPAMethods.GetTotalStorage, Method: actors.SPAMethods.GetTotalStorage,
}, ts) }, ts)
if err != nil { if err != nil {

View File

@ -398,7 +398,7 @@ func (syncer *Syncer) minerIsValid(ctx context.Context, maddr address.Address, b
} }
ret, err := syncer.sm.Call(ctx, &types.Message{ ret, err := syncer.sm.Call(ctx, &types.Message{
To: actors.StorageMarketAddress, To: actors.StoragePowerAddress,
From: maddr, From: maddr,
Method: actors.SPAMethods.IsMiner, Method: actors.SPAMethods.IsMiner,
Params: enc, Params: enc,
@ -482,9 +482,9 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
return xerrors.Errorf("minerIsValid failed: %w", 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 { 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 { if err := h.CheckBlockSignature(ctx, waddr); err != nil {

View File

@ -19,7 +19,7 @@ type StorageAsk struct {
Price BigInt Price BigInt
MinPieceSize uint64 MinPieceSize uint64
Miner address.Address Miner address.Address
Timestamp int64 Timestamp uint64
Expiry int64 Expiry uint64
SeqNo uint64 SeqNo uint64
} }

View File

@ -1272,3 +1272,201 @@ func (t *BlockMsg) UnmarshalCBOR(r io.Reader) error {
return nil return nil
} }
func (t *SignedStorageAsk) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{130}); err != nil {
return err
}
// t.t.Ask (types.StorageAsk)
if err := t.Ask.MarshalCBOR(w); err != nil {
return err
}
// t.t.Signature (types.Signature)
if err := t.Signature.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *SignedStorageAsk) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 2 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Ask (types.StorageAsk)
{
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.Ask = new(StorageAsk)
if err := t.Ask.UnmarshalCBOR(br); err != nil {
return err
}
}
}
// t.t.Signature (types.Signature)
{
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.Signature = new(Signature)
if err := t.Signature.UnmarshalCBOR(br); err != nil {
return err
}
}
}
return nil
}
func (t *StorageAsk) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{134}); err != nil {
return err
}
// t.t.Price (types.BigInt)
if err := t.Price.MarshalCBOR(w); err != nil {
return err
}
// t.t.MinPieceSize (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.MinPieceSize)); err != nil {
return err
}
// t.t.Miner (address.Address)
if err := t.Miner.MarshalCBOR(w); err != nil {
return err
}
// t.t.Timestamp (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.Timestamp)); err != nil {
return err
}
// t.t.Expiry (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.Expiry)); err != nil {
return err
}
// t.t.SeqNo (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.SeqNo)); err != nil {
return err
}
return nil
}
func (t *StorageAsk) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 6 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Price (types.BigInt)
{
if err := t.Price.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.MinPieceSize (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.MinPieceSize = extra
// t.t.Miner (address.Address)
{
if err := t.Miner.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.Timestamp (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.Timestamp = extra
// t.t.Expiry (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.Expiry = extra
// t.t.SeqNo (uint64)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.SeqNo = extra
return nil
}

View File

@ -29,11 +29,12 @@ func newInvoker() *invoker {
} }
// add builtInCode using: register(cid, singleton) // add builtInCode using: register(cid, singleton)
inv.register(actors.InitActorCodeCid, actors.InitActor{}, actors.InitActorState{}) inv.register(actors.InitCodeCid, actors.InitActor{}, actors.InitActorState{})
inv.register(actors.StorageMarketActorCodeCid, actors.StoragePowerActor{}, actors.StoragePowerState{}) inv.register(actors.StoragePowerCodeCid, actors.StoragePowerActor{}, actors.StoragePowerState{})
inv.register(actors.StorageMarketCodeCid, actors.StorageMarketActor{}, actors.StorageMarketState{})
inv.register(actors.StorageMinerCodeCid, actors.StorageMinerActor{}, actors.StorageMinerActorState{}) inv.register(actors.StorageMinerCodeCid, actors.StorageMinerActor{}, actors.StorageMinerActorState{})
inv.register(actors.MultisigActorCodeCid, actors.MultiSigActor{}, actors.MultiSigActorState{}) inv.register(actors.MultisigCodeCid, actors.MultiSigActor{}, actors.MultiSigActorState{})
inv.register(actors.PaymentChannelActorCodeCid, actors.PaymentChannelActor{}, actors.PaymentChannelActorState{}) inv.register(actors.PaymentChannelCodeCid, actors.PaymentChannelActor{}, actors.PaymentChannelActorState{})
return inv return inv
} }

View File

@ -66,7 +66,7 @@ func NewBLSAccountActor(st *state.StateTree, addr address.Address) (*types.Actor
} }
nact := &types.Actor{ nact := &types.Actor{
Code: actors.AccountActorCodeCid, Code: actors.AccountCodeCid,
Balance: types.NewInt(0), Balance: types.NewInt(0),
Head: c, Head: c,
} }
@ -76,7 +76,7 @@ func NewBLSAccountActor(st *state.StateTree, addr address.Address) (*types.Actor
func NewSecp256k1AccountActor(st *state.StateTree, addr address.Address) (*types.Actor, aerrors.ActorError) { func NewSecp256k1AccountActor(st *state.StateTree, addr address.Address) (*types.Actor, aerrors.ActorError) {
nact := &types.Actor{ nact := &types.Actor{
Code: actors.AccountActorCodeCid, Code: actors.AccountCodeCid,
Balance: types.NewInt(0), Balance: types.NewInt(0),
Head: EmptyObjectCid, Head: EmptyObjectCid,
} }

View File

@ -176,7 +176,7 @@ func (vmc *VMContext) ChargeGas(amount uint64) aerrors.ActorError {
} }
func (vmc *VMContext) StateTree() (types.StateTree, aerrors.ActorError) { func (vmc *VMContext) StateTree() (types.StateTree, aerrors.ActorError) {
if vmc.msg.To != actors.InitActorAddress { if vmc.msg.To != actors.InitAddress {
return nil, aerrors.Escalate(fmt.Errorf("only init actor can access state tree directly"), "invalid use of StateTree") return nil, aerrors.Escalate(fmt.Errorf("only init actor can access state tree directly"), "invalid use of StateTree")
} }
@ -215,7 +215,7 @@ func ResolveToKeyAddr(state types.StateTree, cst *hamt.CborIpldStore, addr addre
return address.Undef, aerrors.Newf(1, "failed to find actor: %s", addr) return address.Undef, aerrors.Newf(1, "failed to find actor: %s", addr)
} }
if act.Code != actors.AccountActorCodeCid { if act.Code != actors.AccountCodeCid {
return address.Undef, aerrors.New(1, "address was not for an account actor") return address.Undef, aerrors.New(1, "address was not for an account actor")
} }

View File

@ -69,7 +69,7 @@ var createMinerCmd = &cli.Command{
} }
msg := &types.Message{ msg := &types.Message{
To: actors.StorageMarketAddress, To: actors.StoragePowerAddress,
From: addr, From: addr,
Method: actors.SPAMethods.CreateStorageMiner, Method: actors.SPAMethods.CreateStorageMiner,
Params: params, Params: params,

View File

@ -303,7 +303,7 @@ func createStorageMiner(ctx context.Context, api api.FullNode, peerid peer.ID, c
} }
createStorageMinerMsg := &types.Message{ createStorageMinerMsg := &types.Message{
To: actors.StorageMarketAddress, To: actors.StoragePowerAddress,
From: owner, From: owner,
Value: collateral, Value: collateral,

View File

@ -4,9 +4,11 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
gen "github.com/whyrusleeping/cbor-gen" gen "github.com/whyrusleeping/cbor-gen"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/deals"
"github.com/filecoin-project/lotus/chain/types"
) )
func main() { func main() {
@ -22,6 +24,8 @@ func main() {
types.Actor{}, types.Actor{},
types.MessageReceipt{}, types.MessageReceipt{},
types.BlockMsg{}, types.BlockMsg{},
types.SignedStorageAsk{},
types.StorageAsk{},
) )
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -46,11 +50,9 @@ func main() {
actors.AccountActorState{}, actors.AccountActorState{},
actors.StorageMinerActorState{}, actors.StorageMinerActorState{},
actors.StorageMinerConstructorParams{}, actors.StorageMinerConstructorParams{},
actors.CommitSectorParams{}, actors.OnChainSealVerifyInfo{},
actors.MinerInfo{}, actors.MinerInfo{},
actors.SubmitPoStParams{}, actors.SubmitPoStParams{},
actors.PieceInclVoucherData{},
actors.InclusionProof{},
actors.PaymentVerifyParams{}, actors.PaymentVerifyParams{},
actors.UpdatePeerIDParams{}, actors.UpdatePeerIDParams{},
actors.MultiSigActorState{}, actors.MultiSigActorState{},
@ -75,6 +77,31 @@ func main() {
actors.ArbitrateConsensusFaultParams{}, actors.ArbitrateConsensusFaultParams{},
actors.PledgeCollateralParams{}, actors.PledgeCollateralParams{},
actors.MinerSlashConsensusFault{}, actors.MinerSlashConsensusFault{},
actors.StorageParticipantBalance{},
actors.StorageMarketState{},
actors.WithdrawBalanceParams{},
actors.StorageDealProposal{},
actors.StorageDeal{},
actors.PublishStorageDealsParams{},
actors.PublishStorageDealResponse{},
actors.ActivateStorageDealsParams{},
actors.ProcessStorageDealsPaymentParams{},
actors.OnChainDeal{},
)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = gen.WriteTupleEncodersToFile("./chain/deals/cbor_gen.go", "deals",
deals.AskRequest{},
deals.AskResponse{},
deals.Proposal{},
deals.Response{},
deals.SignedResponse{},
deals.ClientDealProposal{},
deals.ClientDeal{},
deals.MinerDeal{},
) )
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)

View File

@ -1,10 +1,13 @@
package cborrpc package cborrpc
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"io" "io"
"math"
cbor "github.com/ipfs/go-ipld-cbor" cbor "github.com/ipfs/go-ipld-cbor"
ipld "github.com/ipfs/go-ipld-format"
logging "github.com/ipfs/go-log" logging "github.com/ipfs/go-log"
cbg "github.com/whyrusleeping/cbor-gen" cbg "github.com/whyrusleeping/cbor-gen"
) )
@ -43,3 +46,23 @@ func ReadCborRPC(r io.Reader, out interface{}) error {
} }
return cbor.DecodeReader(r, out) return cbor.DecodeReader(r, out)
} }
func Dump(obj interface{}) ([]byte, error) {
var out bytes.Buffer
if err := WriteCborRPC(&out, obj); err != nil {
return nil, err
}
return out.Bytes(), nil
}
// TODO: this is a bit ugly, and this package is not exactly the best place
func AsIpld(obj interface{}) (ipld.Node, error) {
if m, ok := obj.(cbg.CBORMarshaler); ok {
b, err := Dump(m)
if err != nil {
return nil, err
}
return cbor.Decode(b, math.MaxUint64, -1)
}
return cbor.WrapObject(obj, math.MaxUint64, -1)
}

View File

@ -212,7 +212,7 @@ func (h handlers) handle(ctx context.Context, req request, w func(func(io.Writer
if handler.errOut != -1 { if handler.errOut != -1 {
err := callResult[handler.errOut].Interface() err := callResult[handler.errOut].Interface()
if err != nil { 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{ resp.Error = &respError{
Code: 1, Code: 1,
Message: err.(error).Error(), Message: err.(error).Error(),

View File

@ -6,6 +6,8 @@ import (
"math/rand" "math/rand"
"testing" "testing"
"github.com/ipfs/go-datastore"
"github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/lib/sectorbuilder" "github.com/filecoin-project/lotus/lib/sectorbuilder"
"github.com/filecoin-project/lotus/storage/sector" "github.com/filecoin-project/lotus/storage/sector"
@ -40,7 +42,7 @@ func TestSealAndVerify(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
store := sector.NewStore(sb) store := sector.NewStore(sb, datastore.NewMapDatastore())
store.Service() store.Service()
ssinfo := <-store.Incoming() ssinfo := <-store.Incoming()

View File

@ -3457,6 +3457,11 @@
} }
} }
}, },
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
"clean-css": { "clean-css": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
@ -10663,6 +10668,15 @@
"workbox-webpack-plugin": "4.2.0" "workbox-webpack-plugin": "4.2.0"
} }
}, },
"react-tooltip": {
"version": "3.11.1",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-3.11.1.tgz",
"integrity": "sha512-YCMVlEC2KuHIzOQhPplTK5jmBBwoL+PYJJdJKXj7M/h7oevupd/QSVq6z5U7/ehIGXyHsAqvwpdxexDfyQ0o3A==",
"requires": {
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
}
},
"read-pkg": { "read-pkg": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",

View File

@ -13,6 +13,7 @@
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-router-dom": "^5.0.1", "react-router-dom": "^5.0.1",
"react-scripts": "3.0.1", "react-scripts": "3.0.1",
"react-tooltip": "^3.11.1",
"rpc-websockets": "^4.5.1", "rpc-websockets": "^4.5.1",
"styled-components": "^3.3.3", "styled-components": "^3.3.3",
"xterm": "^3.14.5", "xterm": "^3.14.5",

View File

@ -1,10 +1,15 @@
import React from 'react' import React from 'react'
import CID from 'cids' import CID from 'cids'
import * as multihash from "multihashes"; import ReactTooltip from 'react-tooltip'
import State from "./State"; import * as multihash from "multihashes"
import methods from "./chain/methods"; import State from "./State"
import methods from "./chain/methods"
import Fil from "./Fil";
function truncAddr(addr, len) { function truncAddr(addr, len) {
if (!addr) {
return "<!nil>"
}
if (addr.length > len) { if (addr.length > len) {
return <abbr title={addr}>{addr.substr(0, len - 3) + '..'}</abbr> return <abbr title={addr}>{addr.substr(0, len - 3) + '..'}</abbr>
} }
@ -62,7 +67,7 @@ class Address extends React.Component {
} }
openState() { openState() {
this.props.mountWindow((onClose) => <State addr={this.props.addr} actor={this.state.actor} client={this.props.client} onClose={onClose}/>) this.props.mountWindow((onClose) => <State addr={this.props.addr} actor={this.state.actor} client={this.props.client} onClose={onClose} mountWindow={this.props.mountWindow}/>)
} }
async actorInfo(actor) { async actorInfo(actor) {
@ -117,12 +122,12 @@ class Address extends React.Component {
nonce = <span>&nbsp;<abbr title={"Next nonce"}>Nc:{this.state.nonce}</abbr>{nonce}</span> nonce = <span>&nbsp;<abbr title={"Next nonce"}>Nc:{this.state.nonce}</abbr>{nonce}</span>
} }
let balance = <span>:&nbsp;{this.state.balance}&nbsp;</span> let balance = <span>:&nbsp;{<Fil>{this.state.balance}</Fil>}&nbsp;</span>
if(this.props.nobalance) { if(this.props.nobalance) {
balance = <span/> balance = <span/>
} }
if(this.props.short) { if(this.props.short) {
actInfo = <span/> actInfo = <ReactTooltip id={this.props.addr} place="top" type="dark" effect="solid">{actInfo}: {<Fil>this.state.balance</Fil>}</ReactTooltip>
balance = <span/> balance = <span/>
} }
@ -136,7 +141,7 @@ class Address extends React.Component {
minerInfo = <span>&nbsp;Power: {this.state.minerInfo.MinerPower} ({this.state.minerInfo.MinerPower/this.state.minerInfo.TotalPower*100}%)</span> minerInfo = <span>&nbsp;Power: {this.state.minerInfo.MinerPower} ({this.state.minerInfo.MinerPower/this.state.minerInfo.TotalPower*100}%)</span>
} }
return <span>{addr}{balance}{actInfo}{nonce}{add20k}{transfer}{minerInfo}</span> return <span data-tip data-for={this.props.addr}>{addr}{balance}{actInfo}{nonce}{add20k}{transfer}{minerInfo}</span>
} }
} }

View File

@ -64,7 +64,14 @@ class Block extends React.Component {
<div>Miner: {<Address client={this.props.conn} addr={head.Miner} mountWindow={this.props.mountWindow}/>}</div> <div>Miner: {<Address client={this.props.conn} addr={head.Miner} mountWindow={this.props.mountWindow}/>}</div>
<div>Messages: {head.Messages['/']} {/*TODO: link to message explorer */}</div> <div>Messages: {head.Messages['/']} {/*TODO: link to message explorer */}</div>
<div>Parent Receipts: {head.ParentMessageReceipts['/']}</div> <div>Parent Receipts: {head.ParentMessageReceipts['/']}</div>
<div>Parent State Root:&nbsp;{head.ParentStateRoot['/']}</div> <div>
<span>Parent State Root:&nbsp;{head.ParentStateRoot['/']}</span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t00" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t01" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t02" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t03" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t099" mountWindow={this.props.mountWindow}/></span>
</div>
<div>----</div> <div>----</div>
<div>{messages}</div> <div>{messages}</div>
</div> </div>

View File

@ -71,10 +71,10 @@ class ChainExplorer extends React.Component {
return return
} }
if(!base.Blocks) { if(!base.Blocks) {
console.log("base for H is nll blk", h, base) console.log("base for H is nil blk", h, base)
return return
} }
let cids = base.Blocks.map(b => b.Parents) let cids = base.Blocks.map(b => (b.Parents || []))
.reduce((acc, val) => { .reduce((acc, val) => {
let out = {...acc} let out = {...acc}
val.forEach(c => out[c['/']] = 8) val.forEach(c => out[c['/']] = 8)
@ -85,6 +85,10 @@ class ChainExplorer extends React.Component {
const blocks = await Promise.all(cids.map(cid => this.props.client.call('Filecoin.ChainGetBlock', [cid]))) const blocks = await Promise.all(cids.map(cid => this.props.client.call('Filecoin.ChainGetBlock', [cid])))
if (!blocks[0]) {
return
}
cache[h] = { cache[h] = {
Height: blocks[0].Height, Height: blocks[0].Height,
Cids: cids, Cids: cids,

View File

@ -1,18 +1,17 @@
import React from 'react'; import React from 'react';
import Address from "./Address"; import Address from "./Address";
import Window from "./Window"; import Window from "./Window";
import Fil from "./Fil";
const dealStates = [ const dealStates = [
"Unknown", "Unknown",
"Rejected", "Rejected",
"Accepted", "Accepted",
"Started",
"Failed",
"Staged", "Staged",
"Sealing", "Sealing",
"Failed",
"Complete", "Complete",
"Error", "Error",
"Expired"
] ]
@ -21,31 +20,43 @@ class Client extends React.Component {
super(props) super(props)
this.state = { this.state = {
miners: ["t0101"],
ask: {Price: "3"},
kbs: 1, kbs: 1,
blocks: 12, blocks: 12,
total: 36000, total: 36000,
miner: "t0101", miner: "t0101",
deals: [] deals: [],
blockDelay: 10,
} }
} }
componentDidMount() { async componentDidMount() {
let ver = await this.props.client.call('Filecoin.Version', [])
this.setState({blockDelay: ver.BlockDelay})
this.getDeals() this.getDeals()
setInterval(this.getDeals, 1325) setInterval(this.getDeals, 1325)
} }
getDeals = async () => { getDeals = async () => {
let miners = await this.props.client.call('Filecoin.StateListMiners', [null])
let deals = await this.props.client.call('Filecoin.ClientListDeals', []) let deals = await this.props.client.call('Filecoin.ClientListDeals', [])
this.setState({deals}) miners.sort()
this.setState({deals, miners})
} }
update = (name) => (e) => this.setState({ [name]: e.target.value }); update = (name) => (e) => this.setState({ [name]: e.target.value });
makeDeal = async () => { makeDeal = async () => {
let perBlk = this.state.ask.Price * this.state.kbs * 1000
let file = await this.props.pondClient.call('Pond.CreateRandomFile', [this.state.kbs * 1000]) // 1024 won't fit in 1k blocks :( let file = await this.props.pondClient.call('Pond.CreateRandomFile', [this.state.kbs * 1000]) // 1024 won't fit in 1k blocks :(
let cid = await this.props.client.call('Filecoin.ClientImport', [file]) let cid = await this.props.client.call('Filecoin.ClientImport', [file])
let dealcid = await this.props.client.call('Filecoin.ClientStartDeal', [cid, this.state.miner, `${Math.round(this.state.total / this.state.blocks)}`, Number(this.state.blocks)]) let dealcid = await this.props.client.call('Filecoin.ClientStartDeal', [cid, this.state.miner, `${Math.round(perBlk)}`, Number(this.state.blocks)])
console.log("deal cid: ", dealcid) console.log("deal cid: ", dealcid)
} }
@ -67,23 +78,29 @@ class Client extends React.Component {
} }
render() { render() {
let ppb = Math.round(this.state.total / this.state.blocks * 100) / 100 let perBlk = this.state.ask.Price * this.state.kbs * 1000
let ppmbb = Math.round(ppb / (this.state.kbs / 1000) * 100) / 100 let total = perBlk * this.state.blocks
let days = (this.state.blocks * this.state.blockDelay) / 60 / 60 / 24
let dealMaker = <div hidden={!this.props.pondClient}> let dealMaker = <div hidden={!this.props.pondClient}>
<span>Make Deal: </span> <div>
<select><option>t0101</option></select> <span>Make Deal: </span>
<abbr title="Data length">L:</abbr> <input placeholder="KBs" defaultValue={1} onChange={this.update("kbs")}/> <select>{this.state.miners.map(m => <option key={m} value={m}>{m}</option>)}</select>
<abbr title="Deal duration">Dur:</abbr><input placeholder="blocks" defaultValue={12} onChange={this.update("blocks")}/> <span> Ask: <b><Fil>{this.state.ask.Price}</Fil></b> Fil/Byte/Block</span>
Total: <input placeholder="total price" defaultValue={36000} onChange={this.update("total")}/> </div>
<span><abbr title="Price per block">PpB:</abbr> {ppb} </span> <div>
<span><abbr title="Price per block-MiB">PpMbB:</abbr> {ppmbb} </span> Data Size: <input type="text" placeholder="KBs" defaultValue={1} onChange={this.update("kbs")} style={{width: "5em"}}/>KB;
Duration:<input type="text" placeholder="blocks" defaultValue={12} onChange={this.update("blocks")} style={{width: "5em"}}/>Blocks
</div>
<div>
Total: <Fil>{total}</Fil>; {days} Days
</div>
<button onClick={this.makeDeal}>Deal!</button> <button onClick={this.makeDeal}>Deal!</button>
</div> </div>
let deals = this.state.deals.map((deal, i) => <div key={i}> let deals = this.state.deals.map((deal, i) => <div key={i}>
<ul> <ul>
<li>{i}. Proposal: {deal.ProposalCid['/'].substr(0, 18)}... <Address nobalance={true} client={this.props.client} addr={deal.Miner} mountWindow={this.props.mountWindow}/>: <b>{dealStates[deal.State]}</b> <li>{i}. Proposal: {deal.ProposalCid['/'].substr(0, 18)}... <Address nobalance={true} client={this.props.client} addr={deal.Provider} mountWindow={this.props.mountWindow}/>: <b>{dealStates[deal.State]}</b>
{dealStates[deal.State] === 'Complete' ? <span>&nbsp;<a href="#" onClick={this.retrieve(deal)}>[Retrieve]</a></span> : <span/> } {dealStates[deal.State] === 'Complete' ? <span>&nbsp;<a href="#" onClick={this.retrieve(deal)}>[Retrieve]</a></span> : <span/> }
<ul> <ul>
<li>Data: {deal.PieceRef['/']}, <b>{deal.Size}</b>B; Duration: <b>{deal.Duration}</b>Blocks</li> <li>Data: {deal.PieceRef['/']}, <b>{deal.Size}</b>B; Duration: <b>{deal.Duration}</b>Blocks</li>
@ -94,7 +111,7 @@ class Client extends React.Component {
</div>) </div>)
return <Window title={"Client - Node " + this.props.node.ID} onClose={this.props.onClose}> return <Window title={"Client - Node " + this.props.node.ID} onClose={this.props.onClose} initialSize={{width: 600, height: 400}}>
<div className="Client"> <div className="Client">
<div>{dealMaker}</div> <div>{dealMaker}</div>
<div>{deals}</div> <div>{deals}</div>

View File

@ -0,0 +1,22 @@
import React from "react";
function filStr(raw) {
if(typeof raw !== 'string') {
raw = String(raw)
}
if(raw.length < 19) {
raw = '0'.repeat(19 - raw.length).concat(raw)
}
let out = raw.substring(0, raw.length - 18).concat('.', raw.substring(raw.length - 18, raw.length)).replace(/\.0+$|0+$/g, '');
return out ? out : '0'
}
class Fil extends React.Component {
render() {
return filStr(this.props.children)
}
}
export default Fil

View File

@ -1,7 +1,17 @@
import React from 'react' import React from 'react'
import Window from "./Window"; import Window from "./Window";
import CID from "cids";
import * as multihash from "multihashes";
import code from "./chain/code";
import Address from "./Address";
class State extends React.Component { class State extends React.Component {
byCode = {
[code.init]: InitState,
[code.power]: PowerState,
[code.market]: MarketState,
}
constructor(props) { constructor(props) {
super(props) super(props)
@ -9,22 +19,118 @@ class State extends React.Component {
} }
async componentDidMount() { async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props const tipset = this.props.tipset || await this.props.client.call("Filecoin.ChainHead", [])
const actstate = await this.props.client.call('Filecoin.StateReadState', [this.props.actor, tipset]) const actstate = await this.props.client.call('Filecoin.StateReadState', [this.props.actor, tipset])
this.setState(actstate)
const c = new CID(this.props.actor.Code['/'])
const mh = multihash.decode(c.multihash)
let code = mh.digest.toString()
this.setState({...actstate, code: code})
} }
render() { render() {
let state
if(this.byCode[this.state.code]) {
const Stelem = this.byCode[this.state.code]
state = <Stelem client={this.props.client} mountWindow={this.props.mountWindow} tipset={this.props.tipset}/>
} else {
state = <div>{Object.keys(this.state.State).map(k => <div key={k}>{k}: <span>{JSON.stringify(this.state.State[k])}</span></div>)}</div>
}
const content = <div className="State"> const content = <div className="State">
<div>Balance: {this.state.Balance}</div> <div>Balance: {this.state.Balance}</div>
<div>---</div> <div>---</div>
<div>{Object.keys(this.state.State).map(k => <div key={k}>{k}: <span>{JSON.stringify(this.state.State[k])}</span></div>)}</div> {state}
</div> </div>
return <Window initialSize={{width: 700, height: 400}} onClose={this.props.onClose} title={`Actor ${this.props.addr} ${this.props.tipset && this.props.tipset.Height || ''} ${this.state.code}`}>
return <Window onClose={this.props.onClose} title={`Actor ${this.props.addr} @{this.props.ts.Height}`}>
{content} {content}
</Window> </Window>
} }
} }
class InitState extends React.Component {
constructor(props) {
super(props)
this.state = {actors: []}
}
async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props
const actors = await this.props.client.call("Filecoin.StateListActors", [tipset])
this.setState({actors: actors})
}
render() {
return this.state.actors.sort((a, b) => (Number(a.substr(1)) > Number(b.substr(1))))
.map(addr => <div key={addr}><Address addr={addr} client={this.props.client} mountWindow={this.props.mountWindow}/></div>)
}
}
class PowerState extends React.Component {
constructor(props) {
super(props)
this.state = {actors: []}
}
async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props
const actors = await this.props.client.call("Filecoin.StateListMiners", [tipset])
this.setState({actors: actors})
}
render() {
return this.state.actors.sort((a, b) => (Number(a.substr(1)) > Number(b.substr(1))))
.map(addr => <div key={addr}><Address miner={true} addr={addr} client={this.props.client} mountWindow={this.props.mountWindow}/></div>)
}
}
class MarketState extends React.Component {
constructor(props) {
super(props)
this.state = {participants: {}, deals: []}
}
async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props
const participants = await this.props.client.call("Filecoin.StateMarketParticipants", [tipset])
const deals = await this.props.client.call("Filecoin.StateMarketDeals", [tipset])
this.setState({participants, deals})
}
render() {
return <div>
<div>
<div>Participants:</div>
<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>
<table>
<tr><td>id</td><td>Active</td><td>Client</td><td>Provider</td><td>Size</td><td>Price</td><td>Duration</td></tr>
{Object.keys(this.state.deals).map(d => <tr>
<td>{d}</td>
<td>{this.state.deals[d].ActivationEpoch || "No"}</td>
<td><Address short={true} addr={this.state.deals[d].Deal.Proposal.Provider} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
<td><Address short={true} addr={this.state.deals[d].Deal.Proposal.Client} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
<td>{this.state.deals[d].Deal.Proposal.PieceSize}B</td>
<td>{this.state.deals[d].Deal.Proposal.StoragePrice}</td>
<td>{this.state.deals[d].Deal.Proposal.Duration}</td>
</tr>)}
</table>
</div>
</div>
}
}
export default State export default State

View File

@ -0,0 +1,9 @@
export default {
account: "fil/1/account",
power: "fil/1/power",
market: "fil/1/market",
miner: "fil/1/miner",
multisig: "fil/1/multisig",
init: "fil/1/init",
paych: "fil/1/paych",
}

View File

@ -1,10 +1,12 @@
import code from "./code";
export default { export default {
"account": [ [code.account]: [
"Send", "Send",
"Constructor", "Constructor",
"GetAddress", "GetAddress",
], ],
"smarket": [ [code.power]: [
"Send", "Send",
"Constructor", "Constructor",
"CreateStorageMiner", "CreateStorageMiner",
@ -15,7 +17,21 @@ export default {
"IsMiner", "IsMiner",
"StorageCollateralForSize" "StorageCollateralForSize"
], ],
"sminer": [ [code.market]: [
"Send",
"Constructor",
"WithdrawBalance",
"AddBalance",
"CheckLockedBalance",
"PublishStorageDeals",
"HandleCronAction",
"SettleExpiredDeals",
"ProcessStorageDealsPayment",
"SlashStorageDealCollateral",
"GetLastExpirationFromDealIDs",
"ActivateStorageDeals",
],
[code.miner]: [
"Send", "Send",
"Constructor", "Constructor",
"CommitSector", "CommitSector",
@ -36,7 +52,7 @@ export default {
"PaymentVerifyInclusion", "PaymentVerifyInclusion",
"PaymentVerifySector", "PaymentVerifySector",
], ],
"multisig": [ [code.multisig]: [
"Send", "Send",
"Constructor", "Constructor",
"Propose", "Propose",
@ -48,13 +64,13 @@ export default {
"SwapSigner", "SwapSigner",
"ChangeRequirement", "ChangeRequirement",
], ],
"init": [ [code.init]: [
"Send", "Send",
"Constructor", "Constructor",
"Exec", "Exec",
"GetIdForAddress" "GetIdForAddress"
], ],
"paych": [ [code.paych]: [
"Send", "Send",
"Constructor", "Constructor",
"UpdateChannelState", "UpdateChannelState",

View File

@ -11,3 +11,8 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;
} }
input[type=text] {
-webkit-appearance: none;
appearance: none;
}

View File

@ -240,7 +240,7 @@ func Online() Option {
Override(new(dtypes.StagingDAG), modules.StagingDAG), Override(new(dtypes.StagingDAG), modules.StagingDAG),
Override(new(*retrieval.Miner), retrieval.NewMiner), Override(new(*retrieval.Miner), retrieval.NewMiner),
Override(new(*deals.Handler), deals.NewHandler), Override(new(*deals.Provider), deals.NewProvider),
Override(HandleRetrievalKey, modules.HandleRetrieval), Override(HandleRetrievalKey, modules.HandleRetrieval),
Override(HandleDealsKey, modules.HandleDeals), Override(HandleDealsKey, modules.HandleDeals),
Override(RunSectorServiceKey, modules.RunSectorService), Override(RunSectorServiceKey, modules.RunSectorService),

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"io" "io"
"math"
"os" "os"
"github.com/ipfs/go-blockservice" "github.com/ipfs/go-blockservice"
@ -13,7 +14,6 @@ import (
chunker "github.com/ipfs/go-ipfs-chunker" chunker "github.com/ipfs/go-ipfs-chunker"
offline "github.com/ipfs/go-ipfs-exchange-offline" offline "github.com/ipfs/go-ipfs-exchange-offline"
files "github.com/ipfs/go-ipfs-files" files "github.com/ipfs/go-ipfs-files"
cbor "github.com/ipfs/go-ipld-cbor"
ipld "github.com/ipfs/go-ipld-format" ipld "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-merkledag" "github.com/ipfs/go-merkledag"
"github.com/ipfs/go-unixfs/importer/balanced" "github.com/ipfs/go-unixfs/importer/balanced"
@ -76,50 +76,20 @@ func (a *API) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.A
return nil, err return nil, err
} }
vd, err := a.DealClient.VerifyParams(ctx, data)
if err != nil {
return nil, err
}
voucherData, err := cbor.DumpObject(vd)
if err != nil {
return nil, err
}
// setup payments // setup payments
total := types.BigMul(price, types.NewInt(blocksDuration)) total := types.BigMul(price, types.NewInt(blocksDuration))
// TODO: at least ping the miner before creating paych / locking the money
extra := &types.ModVerifyParams{
Actor: miner,
Method: actors.MAMethods.PaymentVerifyInclusion,
Data: voucherData,
}
head := a.Chain.GetHeaviestTipSet()
vouchers := deals.VoucherSpec(blocksDuration, total, head.Height(), extra)
payment, err := a.PaychNewPayment(ctx, self, miner, vouchers)
if err != nil {
return nil, err
}
proposal := deals.ClientDealProposal{ proposal := deals.ClientDealProposal{
Data: data, Data: data,
TotalPrice: total, TotalPrice: total,
Duration: blocksDuration, ProposalExpiration: math.MaxUint64, // TODO: set something reasonable
Payment: actors.PaymentInfo{ Duration: blocksDuration,
PayChActor: payment.Channel, ProviderAddress: miner,
Payer: self, Client: self,
ChannelMessage: payment.ChannelMessage, MinerID: pid,
Vouchers: payment.Vouchers,
},
MinerAddress: miner,
ClientAddress: self,
MinerID: pid,
} }
c, err := a.DealClient.Start(ctx, proposal, vd) c, err := a.DealClient.Start(ctx, proposal)
// TODO: send updated voucher with PaymentVerifySector for cheaper validation (validate the sector the miner sent us first!) // TODO: send updated voucher with PaymentVerifySector for cheaper validation (validate the sector the miner sent us first!)
return &c, err return &c, err
} }
@ -135,13 +105,12 @@ func (a *API) ClientListDeals(ctx context.Context) ([]api.DealInfo, error) {
out[k] = api.DealInfo{ out[k] = api.DealInfo{
ProposalCid: v.ProposalCid, ProposalCid: v.ProposalCid,
State: v.State, State: v.State,
Miner: v.Proposal.MinerAddress, Provider: v.Proposal.Provider,
PieceRef: v.Proposal.PieceRef, PieceRef: v.Proposal.PieceRef,
CommP: v.Proposal.CommP, Size: v.Proposal.PieceSize,
Size: v.Proposal.Size,
TotalPrice: v.Proposal.TotalPrice, TotalPrice: v.Proposal.StoragePrice,
Duration: v.Proposal.Duration, Duration: v.Proposal.Duration,
} }
} }

View File

@ -87,6 +87,8 @@ func (a *CommonAPI) Version(context.Context) (api.Version, error) {
return api.Version{ return api.Version{
Version: build.Version, Version: build.Version,
APIVersion: build.APIVersion, APIVersion: build.APIVersion,
BlockDelay: build.BlockDelay,
}, nil }, nil
} }

View File

@ -1,11 +1,15 @@
package full package full
import ( import (
"bytes"
"context" "context"
"github.com/filecoin-project/go-amt-ipld"
"strconv"
cid "github.com/ipfs/go-cid" cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld" "github.com/ipfs/go-hamt-ipld"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
cbg "github.com/whyrusleeping/cbor-gen"
"go.uber.org/fx" "go.uber.org/fx"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@ -54,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) { func (a *StateAPI) StateMinerWorker(ctx context.Context, m address.Address, ts *types.TipSet) (address.Address, error) {
ret, err := a.StateManager.Call(ctx, &types.Message{ return stmgr.GetMinerWorker(ctx, a.StateManager, ts, m)
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
} }
func (a *StateAPI) StateMinerPeerID(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) { func (a *StateAPI) StateMinerPeerID(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) {
@ -90,8 +76,8 @@ func (a *StateAPI) StatePledgeCollateral(ctx context.Context, ts *types.TipSet)
} }
ret, aerr := a.StateManager.Call(ctx, &types.Message{ ret, aerr := a.StateManager.Call(ctx, &types.Message{
From: actors.StorageMarketAddress, From: actors.StoragePowerAddress,
To: actors.StorageMarketAddress, To: actors.StoragePowerAddress,
Method: actors.SPAMethods.PledgeCollateralForSize, Method: actors.SPAMethods.PledgeCollateralForSize,
Params: param, Params: param,
@ -210,7 +196,7 @@ func (a *StateAPI) StateWaitMsg(ctx context.Context, msg cid.Cid) (*api.MsgWait,
func (a *StateAPI) StateListMiners(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { func (a *StateAPI) StateListMiners(ctx context.Context, ts *types.TipSet) ([]address.Address, error) {
var state actors.StoragePowerState var state actors.StoragePowerState
if _, err := a.StateManager.LoadActorState(ctx, actors.StorageMarketAddress, &state, ts); err != nil { if _, err := a.StateManager.LoadActorState(ctx, actors.StoragePowerAddress, &state, ts); err != nil {
return nil, err return nil, err
} }
@ -226,3 +212,66 @@ func (a *StateAPI) StateListMiners(ctx context.Context, ts *types.TipSet) ([]add
func (a *StateAPI) StateListActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { func (a *StateAPI) StateListActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) {
return a.StateManager.ListAllActors(ctx, ts) return a.StateManager.ListAllActors(ctx, ts)
} }
func (a *StateAPI) StateMarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (actors.StorageParticipantBalance, error) {
return a.StateManager.MarketBalance(ctx, addr, ts)
}
func (a *StateAPI) StateMarketParticipants(ctx context.Context, ts *types.TipSet) (map[string]actors.StorageParticipantBalance, error) {
out := map[string]actors.StorageParticipantBalance{}
var state actors.StorageMarketState
if _, err := a.StateManager.LoadActorState(ctx, actors.StorageMarketAddress, &state, ts); err != nil {
return nil, err
}
cst := hamt.CSTFromBstore(a.StateManager.ChainStore().Blockstore())
nd, err := hamt.LoadNode(ctx, cst, state.Balances)
if err != nil {
return nil, err
}
err = nd.ForEach(ctx, func(k string, val interface{}) error {
cv := val.(*cbg.Deferred)
a, err := address.NewFromBytes([]byte(k))
if err != nil {
return err
}
var b actors.StorageParticipantBalance
if err := b.UnmarshalCBOR(bytes.NewReader(cv.Raw)); err != nil {
return err
}
out[a.String()] = b
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
func (a *StateAPI) StateMarketDeals(ctx context.Context, ts *types.TipSet) (map[string]actors.OnChainDeal, error) {
out := map[string]actors.OnChainDeal{}
var state actors.StorageMarketState
if _, err := a.StateManager.LoadActorState(ctx, actors.StorageMarketAddress, &state, ts); err != nil {
return nil, err
}
blks := amt.WrapBlockstore(a.StateManager.ChainStore().Blockstore())
da, err := amt.LoadAMT(blks, state.Deals)
if err != nil {
return nil, err
}
if err := da.ForEach(func(i uint64, v *cbg.Deferred) error {
var d actors.OnChainDeal
if err := d.UnmarshalCBOR(bytes.NewReader(v.Raw)); err != nil {
return err
}
out[strconv.FormatInt(int64(i), 10)] = d
return nil
}); err != nil {
return nil, err
}
return out, nil
}

View File

@ -33,8 +33,9 @@ func (sm *StorageMinerAPI) ActorAddress(context.Context) (address.Address, error
func (sm *StorageMinerAPI) StoreGarbageData(ctx context.Context) (uint64, error) { func (sm *StorageMinerAPI) StoreGarbageData(ctx context.Context) (uint64, error) {
size := sectorbuilder.UserBytesForSectorSize(build.SectorSize) size := sectorbuilder.UserBytesForSectorSize(build.SectorSize)
// TODO: create a deal
name := fmt.Sprintf("fake-file-%d", rand.Intn(100000000)) name := fmt.Sprintf("fake-file-%d", rand.Intn(100000000))
sectorId, err := sm.Sectors.AddPiece(name, size, io.LimitReader(rand.New(rand.NewSource(42)), int64(size))) sectorId, err := sm.Sectors.AddPiece(name, size, io.LimitReader(rand.New(rand.NewSource(42)), int64(size)), 0)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -98,13 +98,13 @@ func HandleRetrieval(host host.Host, lc fx.Lifecycle, m *retrieval.Miner) {
}) })
} }
func HandleDeals(mctx helpers.MetricsCtx, lc fx.Lifecycle, host host.Host, h *deals.Handler) { func HandleDeals(mctx helpers.MetricsCtx, lc fx.Lifecycle, host host.Host, h *deals.Provider) {
ctx := helpers.LifecycleCtx(mctx, lc) ctx := helpers.LifecycleCtx(mctx, lc)
lc.Append(fx.Hook{ lc.Append(fx.Hook{
OnStart: func(context.Context) error { OnStart: func(context.Context) error {
h.Run(ctx) h.Run(ctx)
host.SetStreamHandler(deals.ProtocolID, h.HandleStream) host.SetStreamHandler(deals.DealProtocolID, h.HandleStream)
host.SetStreamHandler(deals.AskProtocolID, h.HandleAskStream) host.SetStreamHandler(deals.AskProtocolID, h.HandleAskStream)
return nil return nil
}, },

View File

@ -19,14 +19,14 @@ func (pm *Manager) createPaych(ctx context.Context, from, to address.Address, am
enc, aerr := actors.SerializeParams(&actors.ExecParams{ enc, aerr := actors.SerializeParams(&actors.ExecParams{
Params: params, Params: params,
Code: actors.PaymentChannelActorCodeCid, Code: actors.PaymentChannelCodeCid,
}) })
if aerr != nil { if aerr != nil {
return address.Undef, cid.Undef, aerr return address.Undef, cid.Undef, aerr
} }
msg := &types.Message{ msg := &types.Message{
To: actors.InitActorAddress, To: actors.InitAddress,
From: from, From: from,
Value: amt, Value: amt,
Method: actors.IAMethods.Exec, Method: actors.IAMethods.Exec,

View File

@ -25,7 +25,7 @@ func init() {
var commitmentDsPrefix = datastore.NewKey("/commitments") var commitmentDsPrefix = datastore.NewKey("/commitments")
type Tracker struct { type Tracker struct {
commitDs datastore.Datastore commitments datastore.Datastore
lk sync.Mutex lk sync.Mutex
@ -34,35 +34,36 @@ type Tracker struct {
func NewTracker(ds dtypes.MetadataDS) *Tracker { func NewTracker(ds dtypes.MetadataDS) *Tracker {
return &Tracker{ return &Tracker{
commitDs: namespace.Wrap(ds, commitmentDsPrefix), commitments: namespace.Wrap(ds, commitmentDsPrefix),
waits: map[datastore.Key]chan struct{}{}, waits: map[datastore.Key]chan struct{}{},
} }
} }
type commitment struct { type commitment struct {
Msg cid.Cid DealIDs []uint64
Msg cid.Cid
} }
func commitmentKey(miner address.Address, sectorId uint64) datastore.Key { func commitmentKey(miner address.Address, sectorId uint64) datastore.Key {
return commitmentDsPrefix.ChildString(miner.String()).ChildString(fmt.Sprintf("%d", sectorId)) return commitmentDsPrefix.ChildString(miner.String()).ChildString(fmt.Sprintf("%d", sectorId))
} }
func (ct *Tracker) TrackCommitSectorMsg(miner address.Address, sectorId uint64, mcid cid.Cid) error { func (ct *Tracker) TrackCommitSectorMsg(miner address.Address, sectorId uint64, commitMsg cid.Cid) error {
key := commitmentKey(miner, sectorId) key := commitmentKey(miner, sectorId)
ct.lk.Lock() ct.lk.Lock()
defer ct.lk.Unlock() defer ct.lk.Unlock()
tracking, err := ct.commitDs.Get(key) tracking, err := ct.commitments.Get(key)
switch err { switch err {
case datastore.ErrNotFound: case datastore.ErrNotFound:
comm := &commitment{Msg: mcid} comm := &commitment{Msg: commitMsg}
commB, err := cbor.DumpObject(comm) commB, err := cbor.DumpObject(comm)
if err != nil { if err != nil {
return err return err
} }
if err := ct.commitDs.Put(key, commB); err != nil { if err := ct.commitments.Put(key, commB); err != nil {
return err return err
} }
@ -78,11 +79,11 @@ func (ct *Tracker) TrackCommitSectorMsg(miner address.Address, sectorId uint64,
return err return err
} }
if !comm.Msg.Equals(mcid) { if !comm.Msg.Equals(commitMsg) {
return xerrors.Errorf("commitment tracking for miner %s, sector %d: already tracking %s, got another commitment message: %s", miner, sectorId, comm.Msg, mcid) return xerrors.Errorf("commitment tracking for miner %s, sector %d: already tracking %s, got another commitment message: %s", miner, sectorId, comm.Msg, commitMsg)
} }
log.Warnf("commitment.TrackCommitSectorMsg called more than once for miner %s, sector %d, message %s", miner, sectorId, mcid) log.Warnf("commitment.TrackCommitSectorMsg called more than once for miner %s, sector %d, message %s", miner, sectorId, commitMsg)
return nil return nil
default: default:
return err return err
@ -94,7 +95,7 @@ func (ct *Tracker) WaitCommit(ctx context.Context, miner address.Address, sector
ct.lk.Lock() ct.lk.Lock()
tracking, err := ct.commitDs.Get(key) tracking, err := ct.commitments.Get(key)
if err != datastore.ErrNotFound { if err != datastore.ErrNotFound {
ct.lk.Unlock() ct.lk.Unlock()
@ -120,7 +121,7 @@ func (ct *Tracker) WaitCommit(ctx context.Context, miner address.Address, sector
select { select {
case <-wait: case <-wait:
tracking, err := ct.commitDs.Get(key) tracking, err := ct.commitments.Get(key)
if err != nil { if err != nil {
return cid.Undef, xerrors.Errorf("failed to get commitment after waiting: %w", err) return cid.Undef, xerrors.Errorf("failed to get commitment after waiting: %w", err)
} }

View File

@ -9,6 +9,7 @@ import (
logging "github.com/ipfs/go-log" logging "github.com/ipfs/go-log"
"github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/host"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
@ -129,12 +130,19 @@ func (m *Miner) commitSector(ctx context.Context, sinfo sectorbuilder.SectorSeal
log.Error("seal we just created failed verification") log.Error("seal we just created failed verification")
} }
params := &actors.CommitSectorParams{ deals, err := m.secst.DealsForCommit(sinfo.SectorID)
SectorID: sinfo.SectorID, if err != nil {
return xerrors.Errorf("getting sector deals failed: %w", err)
}
params := &actors.OnChainSealVerifyInfo{
CommD: sinfo.CommD[:], CommD: sinfo.CommD[:],
CommR: sinfo.CommR[:], CommR: sinfo.CommR[:],
CommRStar: sinfo.CommRStar[:], CommRStar: sinfo.CommRStar[:],
Proof: sinfo.Proof, Proof: sinfo.Proof,
DealIDs: deals,
SectorNumber: sinfo.SectorID,
} }
enc, aerr := actors.SerializeParams(params) enc, aerr := actors.SerializeParams(params)
if aerr != nil { if aerr != nil {

View File

@ -2,25 +2,45 @@ package sector
import ( import (
"context" "context"
"github.com/filecoin-project/go-sectorbuilder/sealing_state" "fmt"
"golang.org/x/xerrors"
"io" "io"
"sync" "sync"
"time" "time"
"github.com/filecoin-project/go-sectorbuilder/sealing_state"
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/lib/sectorbuilder" "github.com/filecoin-project/lotus/lib/sectorbuilder"
"github.com/filecoin-project/lotus/node/modules/dtypes"
logging "github.com/ipfs/go-log"
) )
func init() {
cbor.RegisterCborType(dealMapping{})
}
var log = logging.Logger("sectorstore") var log = logging.Logger("sectorstore")
var sectorDealsPrefix = datastore.NewKey("/sectordeals")
type dealMapping struct {
DealIDs []uint64
Committed bool
}
// TODO: eventually handle sector storage here instead of in rust-sectorbuilder // TODO: eventually handle sector storage here instead of in rust-sectorbuilder
type Store struct { type Store struct {
lk sync.Mutex waitingLk sync.Mutex
sb *sectorbuilder.SectorBuilder sb *sectorbuilder.SectorBuilder
dealsLk sync.Mutex
deals datastore.Datastore
waiting map[uint64]chan struct{} waiting map[uint64]chan struct{}
incoming []chan sectorbuilder.SectorSealingStatus incoming []chan sectorbuilder.SectorSealingStatus
// TODO: outdated chan // TODO: outdated chan
@ -28,9 +48,10 @@ type Store struct {
closeCh chan struct{} closeCh chan struct{}
} }
func NewStore(sb *sectorbuilder.SectorBuilder) *Store { func NewStore(sb *sectorbuilder.SectorBuilder, ds dtypes.MetadataDS) *Store {
return &Store{ return &Store{
sb: sb, sb: sb,
deals: namespace.Wrap(ds, sectorDealsPrefix),
waiting: map[uint64]chan struct{}{}, waiting: map[uint64]chan struct{}{},
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
} }
@ -44,13 +65,13 @@ func (s *Store) poll() {
log.Debug("polling for sealed sectors...") log.Debug("polling for sealed sectors...")
// get a list of sectors to poll // get a list of sectors to poll
s.lk.Lock() s.waitingLk.Lock()
toPoll := make([]uint64, 0, len(s.waiting)) toPoll := make([]uint64, 0, len(s.waiting))
for id := range s.waiting { for id := range s.waiting {
toPoll = append(toPoll, id) toPoll = append(toPoll, id)
} }
s.lk.Unlock() s.waitingLk.Unlock()
var done []sectorbuilder.SectorSealingStatus var done []sectorbuilder.SectorSealingStatus
@ -68,7 +89,7 @@ func (s *Store) poll() {
} }
// send updates // send updates
s.lk.Lock() s.waitingLk.Lock()
for _, sector := range done { for _, sector := range done {
watch, ok := s.waiting[sector.SectorID] watch, ok := s.waiting[sector.SectorID]
if ok { if ok {
@ -79,7 +100,7 @@ func (s *Store) poll() {
c <- sector // TODO: ctx! c <- sector // TODO: ctx!
} }
} }
s.lk.Unlock() s.waitingLk.Unlock()
} }
func (s *Store) service() { func (s *Store) service() {
@ -90,35 +111,97 @@ func (s *Store) service() {
case <-poll: case <-poll:
s.poll() s.poll()
case <-s.closeCh: case <-s.closeCh:
s.lk.Lock() s.waitingLk.Lock()
for _, c := range s.incoming { for _, c := range s.incoming {
close(c) close(c)
} }
s.lk.Unlock() s.waitingLk.Unlock()
return return
} }
} }
} }
func (s *Store) AddPiece(ref string, size uint64, r io.Reader) (sectorID uint64, err error) { func (s *Store) AddPiece(ref string, size uint64, r io.Reader, dealID uint64) (sectorID uint64, err error) {
sectorID, err = s.sb.AddPiece(ref, size, r) sectorID, err = s.sb.AddPiece(ref, size, r)
if err != nil { if err != nil {
return 0, err return 0, err
} }
s.lk.Lock() s.waitingLk.Lock()
_, exists := s.waiting[sectorID] _, exists := s.waiting[sectorID]
if !exists { // pieces can share sectors if !exists { // pieces can share sectors
s.waiting[sectorID] = make(chan struct{}) s.waiting[sectorID] = make(chan struct{})
} }
s.lk.Unlock() s.waitingLk.Unlock()
s.dealsLk.Lock()
defer s.dealsLk.Unlock()
k := datastore.NewKey(fmt.Sprint(sectorID))
e, err := s.deals.Get(k)
var deals dealMapping
switch err {
case nil:
if err := cbor.DecodeInto(e, &deals); err != nil {
return 0, err
}
if deals.Committed {
return 0, xerrors.Errorf("sector %d already committed", sectorID)
}
fallthrough
case datastore.ErrNotFound:
deals.DealIDs = append(deals.DealIDs, dealID)
d, err := cbor.DumpObject(&deals)
if err != nil {
return 0, err
}
if err := s.deals.Put(k, d); err != nil {
return 0, err
}
default:
return 0, err
}
return sectorID, nil return sectorID, nil
} }
func (s *Store) DealsForCommit(sectorID uint64) ([]uint64, error) {
s.dealsLk.Lock()
defer s.dealsLk.Unlock()
k := datastore.NewKey(fmt.Sprint(sectorID))
e, err := s.deals.Get(k)
switch err {
case nil:
var deals dealMapping
if err := cbor.DecodeInto(e, &deals); err != nil {
return nil, err
}
if deals.Committed {
log.Errorf("getting deal IDs for sector %d: sector already marked as committed", sectorID)
}
deals.Committed = true
d, err := cbor.DumpObject(&deals)
if err != nil {
return nil, err
}
if err := s.deals.Put(k, d); err != nil {
return nil, err
}
return deals.DealIDs, nil
case datastore.ErrNotFound:
log.Errorf("getting deal IDs for sector %d failed: %s", err)
return []uint64{}, nil
default:
return nil, err
}
}
func (s *Store) CloseIncoming(c <-chan sectorbuilder.SectorSealingStatus) { func (s *Store) CloseIncoming(c <-chan sectorbuilder.SectorSealingStatus) {
s.lk.Lock() s.waitingLk.Lock()
var at = -1 var at = -1
for i, ch := range s.incoming { for i, ch := range s.incoming {
if ch == c { if ch == c {
@ -126,7 +209,7 @@ func (s *Store) CloseIncoming(c <-chan sectorbuilder.SectorSealingStatus) {
} }
} }
if at == -1 { if at == -1 {
s.lk.Unlock() s.waitingLk.Unlock()
return return
} }
if len(s.incoming) > 1 { if len(s.incoming) > 1 {
@ -135,21 +218,21 @@ func (s *Store) CloseIncoming(c <-chan sectorbuilder.SectorSealingStatus) {
s.incoming[last] = nil s.incoming[last] = nil
} }
s.incoming = s.incoming[:len(s.incoming)-1] s.incoming = s.incoming[:len(s.incoming)-1]
s.lk.Unlock() s.waitingLk.Unlock()
} }
func (s *Store) Incoming() <-chan sectorbuilder.SectorSealingStatus { func (s *Store) Incoming() <-chan sectorbuilder.SectorSealingStatus {
ch := make(chan sectorbuilder.SectorSealingStatus, 8) ch := make(chan sectorbuilder.SectorSealingStatus, 8)
s.lk.Lock() s.waitingLk.Lock()
s.incoming = append(s.incoming, ch) s.incoming = append(s.incoming, ch)
s.lk.Unlock() s.waitingLk.Unlock()
return ch return ch
} }
func (s *Store) WaitSeal(ctx context.Context, sector uint64) (sectorbuilder.SectorSealingStatus, error) { func (s *Store) WaitSeal(ctx context.Context, sector uint64) (sectorbuilder.SectorSealingStatus, error) {
s.lk.Lock() s.waitingLk.Lock()
watch, ok := s.waiting[sector] watch, ok := s.waiting[sector]
s.lk.Unlock() s.waitingLk.Unlock()
if ok { if ok {
select { select {
case <-watch: case <-watch:

View File

@ -153,7 +153,7 @@ func (r *refStorer) Read(p []byte) (n int, err error) {
} }
} }
func (st *SectorBlocks) AddUnixfsPiece(ref cid.Cid, r UnixfsReader, keepAtLeast uint64) (sectorID uint64, err error) { func (st *SectorBlocks) AddUnixfsPiece(ref cid.Cid, r UnixfsReader, dealID uint64) (sectorID uint64, err error) {
size, err := r.Size() size, err := r.Size()
if err != nil { if err != nil {
return 0, err return 0, err
@ -166,7 +166,7 @@ func (st *SectorBlocks) AddUnixfsPiece(ref cid.Cid, r UnixfsReader, keepAtLeast
intermediate: st.intermediate, intermediate: st.intermediate,
} }
return st.Store.AddPiece(refst.pieceRef, uint64(size), refst) return st.Store.AddPiece(refst.pieceRef, uint64(size), refst, dealID)
} }
func (st *SectorBlocks) List() (map[cid.Cid][]api.SealedRef, error) { func (st *SectorBlocks) List() (map[cid.Cid][]api.SealedRef, error) {