lotus/chain/actors/actor_storagepower.go
2019-11-14 00:00:11 +01:00

700 lines
19 KiB
Go

package actors
import (
"bytes"
"context"
"github.com/filecoin-project/go-amt-ipld"
"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"
cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
"github.com/libp2p/go-libp2p-core/peer"
cbg "github.com/whyrusleeping/cbor-gen"
xerrors "golang.org/x/xerrors"
)
type StoragePowerActor struct{}
type spaMethods struct {
Constructor uint64
CreateStorageMiner uint64
ArbitrateConsensusFault uint64
UpdateStorage uint64
GetTotalStorage uint64
PowerLookup uint64
IsValidMiner uint64
PledgeCollateralForSize uint64
CheckProofSubmissions uint64
}
var SPAMethods = spaMethods{1, 2, 3, 4, 5, 6, 7, 8, 9}
func (spa StoragePowerActor) Exports() []interface{} {
return []interface{}{
//1: spa.StoragePowerConstructor,
2: spa.CreateStorageMiner,
3: spa.ArbitrateConsensusFault,
4: spa.UpdateStorage,
5: spa.GetTotalStorage,
6: spa.PowerLookup,
7: spa.IsValidMiner,
8: spa.PledgeCollateralForSize,
9: spa.CheckProofSubmissions,
}
}
type StoragePowerState struct {
Miners cid.Cid
ProvingBuckets cid.Cid // amt[ProvingPeriodBucket]hamt[minerAddress]struct{}
MinerCount uint64
TotalStorage types.BigInt
}
type CreateStorageMinerParams struct {
Owner address.Address
Worker address.Address
SectorSize uint64
PeerID peer.ID
}
func (spa StoragePowerActor) CreateStorageMiner(act *types.Actor, vmctx types.VMContext, params *CreateStorageMinerParams) ([]byte, ActorError) {
if !build.SupportedSectorSize(params.SectorSize) {
return nil, aerrors.New(1, "Unsupported sector size")
}
var self StoragePowerState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
reqColl, err := pledgeCollateralForSize(vmctx, types.NewInt(0), self.TotalStorage, self.MinerCount+1)
if err != nil {
return nil, err
}
if vmctx.Message().Value.LessThan(reqColl) {
return nil, aerrors.Newf(1, "not enough funds passed to cover required miner collateral (needed %s, got %s)", reqColl, vmctx.Message().Value)
}
encoded, err := CreateExecParams(StorageMinerCodeCid, &StorageMinerConstructorParams{
Owner: params.Owner,
Worker: params.Worker,
SectorSize: params.SectorSize,
PeerID: params.PeerID,
})
if err != nil {
return nil, err
}
ret, err := vmctx.Send(InitAddress, IAMethods.Exec, vmctx.Message().Value, encoded)
if err != nil {
return nil, err
}
naddr, nerr := address.NewFromBytes(ret)
if nerr != nil {
return nil, aerrors.Absorb(nerr, 2, "could not read address of new actor")
}
ncid, err := MinerSetAdd(context.TODO(), vmctx, self.Miners, naddr)
if err != nil {
return nil, err
}
self.Miners = ncid
self.MinerCount++
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, err
}
if err := vmctx.Storage().Commit(old, nroot); err != nil {
return nil, err
}
return naddr.Bytes(), nil
}
type ArbitrateConsensusFaultParams struct {
Block1 *types.BlockHeader
Block2 *types.BlockHeader
}
func (spa StoragePowerActor) ArbitrateConsensusFault(act *types.Actor, vmctx types.VMContext, params *ArbitrateConsensusFaultParams) ([]byte, ActorError) {
if params.Block1.Miner != params.Block2.Miner {
return nil, aerrors.New(2, "blocks must be from the same miner")
}
rval, err := vmctx.Send(params.Block1.Miner, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
if err != nil {
return nil, aerrors.Wrap(err, "failed to get miner worker")
}
worker, oerr := address.NewFromBytes(rval)
if oerr != nil {
// REVIEW: should this be fatal? i can't think of a real situation that would get us here
return nil, aerrors.Absorb(oerr, 3, "response from 'GetWorkerAddr' was not a valid address")
}
if err := params.Block1.CheckBlockSignature(vmctx.Context(), worker); err != nil {
return nil, aerrors.Absorb(err, 4, "block1 did not have valid signature")
}
if err := params.Block2.CheckBlockSignature(vmctx.Context(), worker); err != nil {
return nil, aerrors.Absorb(err, 5, "block2 did not have valid signature")
}
// see the "Consensus Faults" section of the faults spec (faults.md)
// for details on these slashing conditions.
if !shouldSlash(params.Block1, params.Block2) {
return nil, aerrors.New(6, "blocks do not prove a slashable offense")
}
var self StoragePowerState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
if types.BigCmp(self.TotalStorage, types.NewInt(0)) == 0 {
return nil, aerrors.Fatal("invalid state, storage power actor has zero total storage")
}
miner := params.Block1.Miner
if has, err := MinerSetHas(vmctx, self.Miners, miner); err != nil {
return nil, aerrors.Wrapf(err, "failed to check miner in set")
} else if !has {
return nil, aerrors.New(7, "either already slashed or not a miner")
}
minerPower, err := powerLookup(context.TODO(), vmctx, &self, miner)
if err != nil {
return nil, err
}
slashedCollateral, err := pledgeCollateralForSize(vmctx, minerPower, self.TotalStorage, self.MinerCount)
if err != nil {
return nil, err
}
enc, err := SerializeParams(&MinerSlashConsensusFault{
Slasher: vmctx.Message().From,
AtHeight: params.Block1.Height,
SlashedCollateral: slashedCollateral,
})
if err != nil {
return nil, err
}
_, err = vmctx.Send(miner, MAMethods.SlashConsensusFault, types.NewInt(0), enc)
if err != nil {
return nil, err
}
// Remove the miner from the list of network miners
ncid, err := MinerSetRemove(context.TODO(), vmctx, self.Miners, miner)
if err != nil {
return nil, err
}
self.Miners = ncid
self.MinerCount--
self.TotalStorage = types.BigSub(self.TotalStorage, minerPower)
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, err
}
if err := vmctx.Storage().Commit(old, nroot); err != nil {
return nil, err
}
return nil, nil
}
func cidArrContains(a []cid.Cid, b cid.Cid) bool {
for _, c := range a {
if b == c {
return true
}
}
return false
}
func shouldSlash(block1, block2 *types.BlockHeader) bool {
// First slashing condition, blocks have the same ticket round
if block1.Height == block2.Height {
return true
}
/* Second slashing condition requires having access to the parent tipset blocks
// This might not always be available, needs some thought on the best way to deal with this
// Second slashing condition, miner ignored own block when mining
// Case A: block2 could have been in block1's parent set but is not
b1ParentHeight := block1.Height - len(block1.Tickets)
block1ParentTipSet := block1.Parents
if !cidArrContains(block1.Parents, block2.Cid()) &&
b1ParentHeight == block2.Height &&
block1ParentTipSet.ParentCids == block2.ParentCids {
return true
}
// Case B: block1 could have been in block2's parent set but is not
block2ParentTipSet := parentOf(block2)
if !block2Parent.contains(block1) &&
block2ParentTipSet.Height == block1.Height &&
block2ParentTipSet.ParentCids == block1.ParentCids {
return true
}
*/
return false
}
type UpdateStorageParams struct {
Delta types.BigInt
NextProvingPeriodEnd uint64
PreviousProvingPeriodEnd uint64
}
func (spa StoragePowerActor) UpdateStorage(act *types.Actor, vmctx types.VMContext, params *UpdateStorageParams) ([]byte, ActorError) {
var self StoragePowerState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
if vmctx.Message().From.Protocol() != address.Actor {
return nil, aerrors.New(1, "update storage must only be called by an actor")
}
self.TotalStorage = types.BigAdd(self.TotalStorage, params.Delta)
previousBucket := params.PreviousProvingPeriodEnd % build.ProvingPeriodDuration
nextBucket := params.NextProvingPeriodEnd % build.ProvingPeriodDuration
if previousBucket == nextBucket && params.PreviousProvingPeriodEnd != 0 {
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, err
}
if err := vmctx.Storage().Commit(old, nroot); err != nil {
return nil, err
}
return nil, nil // Nothing to do
}
buckets, eerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.ProvingBuckets)
if eerr != nil {
return nil, aerrors.HandleExternalError(eerr, "loading proving buckets amt")
}
if params.PreviousProvingPeriodEnd != 0 { // delete from previous bucket
var bucket cid.Cid
err := buckets.Get(previousBucket, &bucket)
switch err.(type) {
case amt.ErrNotFound:
return nil, aerrors.HandleExternalError(err, "proving bucket missing")
case nil: // noop
default:
return nil, aerrors.HandleExternalError(err, "getting proving bucket")
}
bhamt, err := hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), bucket)
if err != nil {
return nil, aerrors.HandleExternalError(err, "failed to load proving bucket")
}
err = bhamt.Delete(vmctx.Context(), string(vmctx.Message().From.Bytes()))
if err != nil {
return nil, aerrors.HandleExternalError(err, "deleting miner from proving bucket")
}
err = bhamt.Flush(vmctx.Context())
if err != nil {
return nil, aerrors.HandleExternalError(err, "flushing previous proving bucket")
}
bucket, err = vmctx.Ipld().Put(vmctx.Context(), bhamt)
if err != nil {
return nil, aerrors.HandleExternalError(err, "putting previous proving bucket hamt")
}
err = buckets.Set(previousBucket, bucket)
if err != nil {
return nil, aerrors.HandleExternalError(err, "setting previous proving bucket cid in amt")
}
}
{ // set in next bucket
var bhamt *hamt.Node
var bucket cid.Cid
err := buckets.Get(nextBucket, &bucket)
switch err.(type) {
case amt.ErrNotFound:
bhamt = hamt.NewNode(vmctx.Ipld())
case nil:
bhamt, err = hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), bucket)
if err != nil {
return nil, aerrors.HandleExternalError(err, "failed to load proving bucket")
}
default:
return nil, aerrors.HandleExternalError(err, "getting proving bucket")
}
err = bhamt.Set(vmctx.Context(), string(vmctx.Message().From.Bytes()), &struct{}{})
if err != nil {
return nil, aerrors.HandleExternalError(err, "setting miner in proving bucket")
}
err = bhamt.Flush(vmctx.Context())
if err != nil {
return nil, aerrors.HandleExternalError(err, "flushing previous proving bucket")
}
bucket, err = vmctx.Ipld().Put(vmctx.Context(), bhamt)
if err != nil {
return nil, aerrors.HandleExternalError(err, "putting previous proving bucket hamt")
}
err = buckets.Set(nextBucket, bucket)
if err != nil {
return nil, aerrors.HandleExternalError(err, "setting previous proving bucket cid in amt")
}
}
self.ProvingBuckets, eerr = buckets.Flush()
if eerr != nil {
return nil, aerrors.HandleExternalError(eerr, "flushing proving buckets")
}
nroot, err := vmctx.Storage().Put(&self)
if err != nil {
return nil, err
}
if err := vmctx.Storage().Commit(old, nroot); err != nil {
return nil, err
}
return nil, nil
}
func (spa StoragePowerActor) GetTotalStorage(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
var self StoragePowerState
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
return nil, err
}
return self.TotalStorage.Bytes(), nil
}
type PowerLookupParams struct {
Miner address.Address
}
func (spa StoragePowerActor) PowerLookup(act *types.Actor, vmctx types.VMContext, params *PowerLookupParams) ([]byte, ActorError) {
var self StoragePowerState
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
return nil, aerrors.Wrap(err, "getting head")
}
pow, err := powerLookup(context.TODO(), vmctx, &self, params.Miner)
if err != nil {
return nil, err
}
return pow.Bytes(), nil
}
func powerLookup(ctx context.Context, vmctx types.VMContext, self *StoragePowerState, miner address.Address) (types.BigInt, ActorError) {
has, err := MinerSetHas(vmctx, self.Miners, miner)
if err != nil {
return types.EmptyInt, err
}
if !has {
return types.EmptyInt, aerrors.New(1, "miner not registered with storage power actor")
}
// TODO: Use local amt
ret, err := vmctx.Send(miner, MAMethods.GetPower, types.NewInt(0), nil)
if err != nil {
return types.EmptyInt, aerrors.Wrap(err, "invoke Miner.GetPower")
}
return types.BigFromBytes(ret), nil
}
type IsMinerParam struct {
Addr address.Address
}
func (spa StoragePowerActor) IsValidMiner(act *types.Actor, vmctx types.VMContext, param *IsMinerParam) ([]byte, ActorError) {
var self StoragePowerState
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
return nil, err
}
has, err := MinerSetHas(vmctx, self.Miners, param.Addr)
if err != nil {
return nil, err
}
if !has {
return cbg.CborBoolFalse, nil
}
ret, err := vmctx.Send(param.Addr, MAMethods.IsLate, types.NewInt(0), nil)
if err != nil {
return nil, err
}
late := bytes.Equal(ret, cbg.CborBoolTrue)
return cbg.EncodeBool(!late), nil
}
type PledgeCollateralParams struct {
Size types.BigInt
}
func (spa StoragePowerActor) PledgeCollateralForSize(act *types.Actor, vmctx types.VMContext, param *PledgeCollateralParams) ([]byte, ActorError) {
var self StoragePowerState
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
return nil, err
}
totalCollateral, err := pledgeCollateralForSize(vmctx, param.Size, self.TotalStorage, self.MinerCount)
if err != nil {
return nil, err
}
return totalCollateral.Bytes(), nil
}
func pledgeCollateralForSize(vmctx types.VMContext, size, totalStorage types.BigInt, minerCount uint64) (types.BigInt, aerrors.ActorError) {
netBalance, err := vmctx.GetBalance(NetworkAddress)
if err != nil {
return types.EmptyInt, err
}
// TODO: the spec says to also grab 'total vested filecoin' and include it as available
// If we don't factor that in, we effectively assume all of the locked up filecoin is 'available'
// the blocker on that right now is that its hard to tell how much filecoin is unlocked
availableFilecoin := types.BigSub(
types.BigMul(types.NewInt(build.TotalFilecoin), types.NewInt(build.FilecoinPrecision)),
netBalance,
)
totalPowerCollateral := types.BigDiv(
types.BigMul(
availableFilecoin,
types.NewInt(build.PowerCollateralProportion),
),
types.NewInt(build.CollateralPrecision),
)
totalPerCapitaCollateral := types.BigDiv(
types.BigMul(
availableFilecoin,
types.NewInt(build.PerCapitaCollateralProportion),
),
types.NewInt(build.CollateralPrecision),
)
// REVIEW: for bootstrapping purposes, we skip the power portion of the
// collateral if there is no collateral in the network yet
powerCollateral := types.NewInt(0)
if types.BigCmp(totalStorage, types.NewInt(0)) != 0 {
powerCollateral = types.BigDiv(
types.BigMul(
totalPowerCollateral,
size,
),
totalStorage,
)
}
perCapCollateral := types.BigDiv(
totalPerCapitaCollateral,
types.NewInt(minerCount),
)
return types.BigAdd(powerCollateral, perCapCollateral), nil
}
func (spa StoragePowerActor) CheckProofSubmissions(act *types.Actor, vmctx types.VMContext, param *struct{}) ([]byte, ActorError) {
if vmctx.Message().From != StoragePowerAddress {
return nil, aerrors.New(1, "CheckProofSubmissions is only callable as a part of tipset state computation")
}
bucketID := vmctx.BlockHeight() % build.ProvingPeriodDuration
var self StoragePowerState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
}
buckets, eerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.ProvingBuckets)
if eerr != nil {
return nil, aerrors.HandleExternalError(eerr, "loading proving buckets amt")
}
var bucket cid.Cid
err := buckets.Get(bucketID, &bucket)
switch err.(type) {
case amt.ErrNotFound:
return nil, nil // nothing to do
case nil:
default:
return nil, aerrors.HandleExternalError(err, "getting proving bucket")
}
bhamt, err := hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), bucket)
if err != nil {
return nil, aerrors.HandleExternalError(err, "failed to load proving bucket")
}
err = bhamt.ForEach(vmctx.Context(), func(k string, val interface{}) error {
maddr, err := address.NewFromBytes([]byte(k))
if err != nil {
return aerrors.Escalate(err, "parsing miner address")
}
ret, err := vmctx.Send(maddr, MAMethods.CheckMiner, types.NewInt(0), nil)
if err != nil {
return err
}
if len(ret) == 0 {
return nil // miner is fine
}
power := types.NewInt(0)
power.SetBytes(ret)
self.TotalStorage = types.BigSub(self.TotalStorage, power)
return nil
})
if err != nil {
return nil, aerrors.HandleExternalError(err, "iterating miners in proving bucket")
}
return nil, nil
}
func MinerSetHas(vmctx types.VMContext, rcid cid.Cid, maddr address.Address) (bool, aerrors.ActorError) {
nd, err := hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), rcid)
if err != nil {
return false, aerrors.HandleExternalError(err, "failed to load miner set")
}
err = nd.Find(vmctx.Context(), string(maddr.Bytes()), nil)
switch err {
case hamt.ErrNotFound:
return false, nil
case nil:
return true, nil
default:
return false, aerrors.HandleExternalError(err, "failed to do set lookup")
}
}
func MinerSetList(ctx context.Context, cst *hamt.CborIpldStore, rcid cid.Cid) ([]address.Address, error) {
nd, err := hamt.LoadNode(ctx, cst, rcid)
if err != nil {
return nil, xerrors.Errorf("failed to load miner set: %w", err)
}
var out []address.Address
err = nd.ForEach(ctx, func(k string, val interface{}) error {
addr, err := address.NewFromBytes([]byte(k))
if err != nil {
return err
}
out = append(out, addr)
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
func MinerSetAdd(ctx context.Context, vmctx types.VMContext, rcid cid.Cid, maddr address.Address) (cid.Cid, aerrors.ActorError) {
nd, err := hamt.LoadNode(ctx, vmctx.Ipld(), rcid)
if err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "failed to load miner set")
}
mkey := string(maddr.Bytes())
err = nd.Find(ctx, mkey, nil)
if err == nil {
return cid.Undef, aerrors.New(20, "miner already in set")
}
if !xerrors.Is(err, hamt.ErrNotFound) {
return cid.Undef, aerrors.HandleExternalError(err, "failed to do miner set check")
}
if err := nd.Set(ctx, mkey, uint64(1)); err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "adding miner address to set failed")
}
if err := nd.Flush(ctx); err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "failed to flush miner set")
}
c, err := vmctx.Ipld().Put(ctx, nd)
if err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "failed to persist miner set to storage")
}
return c, nil
}
func MinerSetRemove(ctx context.Context, vmctx types.VMContext, rcid cid.Cid, maddr address.Address) (cid.Cid, aerrors.ActorError) {
nd, err := hamt.LoadNode(ctx, vmctx.Ipld(), rcid)
if err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "failed to load miner set")
}
mkey := string(maddr.Bytes())
switch nd.Delete(ctx, mkey) {
case hamt.ErrNotFound:
return cid.Undef, aerrors.New(1, "miner not found in set on delete")
default:
return cid.Undef, aerrors.HandleExternalError(err, "failed to delete miner from set")
case nil:
}
if err := nd.Flush(ctx); err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "failed to flush miner set")
}
c, err := vmctx.Ipld().Put(ctx, nd)
if err != nil {
return cid.Undef, aerrors.HandleExternalError(err, "failed to persist miner set to storage")
}
return c, nil
}