slash consensus fault needs to also be a method on the miner

This commit is contained in:
whyrusleeping 2019-09-12 16:48:03 -07:00
parent fe020d9fd1
commit 197a2a3025
10 changed files with 320 additions and 61 deletions

View File

@ -17,7 +17,11 @@ const RandomnessLookback = 20
const ProvingPeriodDuration = 2 * 60 // an hour, for now
const PoSTChallangeTime = 1 * 60
const PowerCollateralProportion = 0.2
const PerCapitaCollateralProportion = 0.05
const PowerCollateralProportion = 20
const PerCapitaCollateralProportion = 5
const CollateralPrecision = 100
const TotalFilecoin = 2000000000
const FilecoinPrecision = 1000000000000000000
// TODO: Move other important consts here

View File

@ -3,6 +3,7 @@ package actors
import (
"context"
"fmt"
"math"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
@ -121,9 +122,10 @@ type maMethods struct {
PaymentVerifyInclusion uint64
PaymentVerifySector 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}
var MAMethods = maMethods{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
func (sma StorageMinerActor) Exports() []interface{} {
return []interface{}{
@ -146,6 +148,7 @@ func (sma StorageMinerActor) Exports() []interface{} {
17: sma.PaymentVerifyInclusion,
18: sma.PaymentVerifySector,
19: nil,
20: sma.SlashConsensusFault,
}
}
@ -702,3 +705,57 @@ func (sma StorageMinerActor) PaymentVerifySector(act *types.Actor, vmctx types.V
return nil, nil
}
type MinerSlashConsensusFault struct {
Slasher address.Address
AtHeight uint64
SlashedCollateral types.BigInt
}
func (sma StorageMinerActor) SlashConsensusFault(act *types.Actor, vmctx types.VMContext, params *MinerSlashConsensusFault) ([]byte, ActorError) {
if vmctx.Message().From != StorageMarketAddress {
return nil, aerrors.New(1, "SlashConsensusFault may only be called by the storage market actor")
}
slashedCollateral := params.SlashedCollateral
if types.BigCmp(slashedCollateral, act.Balance) < 0 {
slashedCollateral = act.Balance
}
// Some of the slashed collateral should be paid to the slasher
// GROWTH_RATE determines how fast the slasher share of slashed collateral will increase as block elapses
// current GROWTH_RATE results in SLASHER_SHARE reaches 1 after 30 blocks
// TODO: define arithmetic precision and rounding for this operation
blockElapsed := vmctx.BlockHeight() - params.AtHeight
growthRate := 1.26
initialShare := 0.001
// REVIEW: floating point precision loss anyone?
slasherPortion := initialShare * math.Pow(growthRate, float64(blockElapsed))
if slasherPortion > 1 {
slasherPortion = 1
}
const precision = 1000000
slasherShare := types.BigDiv(types.BigMul(types.NewInt(uint64(precision*slasherPortion)), slashedCollateral), types.NewInt(precision))
burnPortion := types.BigSub(slashedCollateral, slasherShare)
_, err := vmctx.Send(vmctx.Message().From, 0, slasherShare, nil)
if err != nil {
return nil, aerrors.Wrap(err, "failed to pay slasher")
}
_, err = vmctx.Send(BurntFundsAddress, 0, burnPortion, nil)
if err != nil {
return nil, aerrors.Wrap(err, "failed to burn funds")
}
// TODO: this still allows the miner to commit sectors and submit posts,
// their users could potentially be unaffected, but the miner will never be
// able to mine a block again
// One potential issue: the miner will have to pay back the slashed
// collateral to continue submitting PoSts, which includes pledge
// collateral that they no longer really 'need'
return nil, nil
}

View File

@ -3,18 +3,17 @@ package actors
import (
"context"
"fmt"
"math"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
xerrors "golang.org/x/xerrors"
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 StorageMarketActor struct{}
@ -64,6 +63,21 @@ func (sma StorageMarketActor) CreateStorageMiner(act *types.Actor, vmctx types.V
return nil, aerrors.New(1, "Unsupported sector size")
}
var self StorageMarketState
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 types.BigCmp(vmctx.Message().Value, reqColl) < 0 {
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,
@ -81,13 +95,7 @@ func (sma StorageMarketActor) CreateStorageMiner(act *types.Actor, vmctx types.V
naddr, nerr := address.NewFromBytes(ret)
if nerr != nil {
return nil, aerrors.Absorb(nerr, 1, "could not read address of new actor")
}
var self StorageMarketState
old := vmctx.Storage().GetHead()
if err := vmctx.Storage().Get(old, &self); err != nil {
return nil, err
return nil, aerrors.Absorb(nerr, 2, "could not read address of new actor")
}
ncid, err := MinerSetAdd(context.TODO(), vmctx, self.Miners, naddr)
@ -178,21 +186,21 @@ func (sma StorageMarketActor) SlashConsensusFault(act *types.Actor, vmctx types.
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.Escalate(oerr, "response from 'GetWorkerAddr' was not a valid address")
return nil, aerrors.Absorb(oerr, 3, "response from 'GetWorkerAddr' was not a valid address")
}
if err := params.Block1.CheckBlockSignature(worker); err != nil {
return nil, aerrors.Absorb(err, 3, "block1 did not have valid signature")
return nil, aerrors.Absorb(err, 4, "block1 did not have valid signature")
}
if err := params.Block2.CheckBlockSignature(worker); err != nil {
return nil, aerrors.Absorb(err, 4, "block2 did not have valid signature")
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(5, "blocks do not prove a slashable offense")
return nil, aerrors.New(6, "blocks do not prove a slashable offense")
}
var self StorageMarketState
@ -209,12 +217,7 @@ func (sma StorageMarketActor) SlashConsensusFault(act *types.Actor, vmctx types.
if has, err := MinerSetHas(context.TODO(), vmctx, self.Miners, miner); err != nil {
return nil, aerrors.Wrapf(err, "failed to check miner in set")
} else if !has {
return nil, aerrors.New(6, "either already slashed or not a miner")
}
minerBalance, err := vmctx.GetBalance(miner)
if err != nil {
return nil, err
return nil, aerrors.New(7, "either already slashed or not a miner")
}
minerPower, err := powerLookup(context.TODO(), vmctx, &self, miner)
@ -222,36 +225,23 @@ func (sma StorageMarketActor) SlashConsensusFault(act *types.Actor, vmctx types.
return nil, err
}
slashedCollateral := pledgeCollateralForSize(minerPower, self.TotalStorage, self.MinerCount)
if types.BigCmp(slashedCollateral, minerBalance) < 0 {
slashedCollateral = minerBalance
}
// Some of the slashed collateral should be paid to the slasher
// GROWTH_RATE determines how fast the slasher share of slashed collateral will increase as block elapses
// current GROWTH_RATE results in SLASHER_SHARE reaches 1 after 30 blocks
// TODO: define arithmetic precision and rounding for this operation
blockElapsed := vmctx.BlockHeight() - params.Block1.Height
growthRate := 1.26
initialShare := 0.001
// REVIEW: floating point precision loss anyone?
slasherPortion := initialShare * math.Pow(growthRate, float64(blockElapsed))
if slasherPortion > 1 {
slasherPortion = 1
}
slasherShare := types.BigDiv(types.BigMul(types.NewInt(uint64(1000000*slasherPortion)), slashedCollateral), types.NewInt(1000000))
burnPortion := types.BigSub(slashedCollateral, slasherShare)
_, err = vmctx.Send(vmctx.Message().From, 0, slasherShare, nil)
slashedCollateral, err := pledgeCollateralForSize(vmctx, minerPower, self.TotalStorage, self.MinerCount)
if err != nil {
return nil, aerrors.Wrap(err, "failed to pay slasher")
return nil, err
}
_, err = vmctx.Send(BurntFundsAddress, 0, burnPortion, nil)
enc, err := SerializeParams(&MinerSlashConsensusFault{
Slasher: vmctx.Message().From,
AtHeight: params.Block1.Height,
SlashedCollateral: slashedCollateral,
})
if err != nil {
return nil, aerrors.Wrap(err, "failed to burn funds")
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
@ -264,6 +254,15 @@ func (sma StorageMarketActor) SlashConsensusFault(act *types.Actor, vmctx types.
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
}
@ -374,22 +373,59 @@ func (sma StorageMarketActor) PledgeCollateralForSize(act *types.Actor, vmctx ty
return nil, err
}
totalCollateral := pledgeCollateralForSize(param.Size, self.TotalStorage, self.MinerCount)
totalCollateral, err := pledgeCollateralForSize(vmctx, param.Size, self.TotalStorage, self.MinerCount)
if err != nil {
return nil, err
}
return totalCollateral.Bytes(), nil
}
func pledgeCollateralForSize(size, totalStorage types.BigInt, minerCount uint64) types.BigInt {
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
}
availableFilecoin := types.NewInt(5000000) // TODO: get actual available filecoin amount
// 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
totalPowerCollateral := types.BigDiv(types.BigMul(availableFilecoin, types.NewInt(uint64(float64(100*build.PowerCollateralProportion)))), types.NewInt(100))
totalPerCapitaCollateral := types.BigDiv(types.BigMul(availableFilecoin, types.NewInt(uint64(float64(100*build.PowerCollateralProportion)))), types.NewInt(100))
availableFilecoin := types.BigSub(
types.BigMul(types.NewInt(build.TotalFilecoin), types.NewInt(build.FilecoinPrecision)),
netBalance,
)
powerCollateral := types.BigDiv(types.BigMul(totalPowerCollateral, size), totalStorage)
perCapCollateral := types.BigDiv(totalPerCapitaCollateral, types.NewInt(minerCount))
totalPowerCollateral := types.BigDiv(
types.BigMul(
availableFilecoin,
types.NewInt(build.PowerCollateralProportion),
),
types.NewInt(build.CollateralPrecision),
)
return types.BigAdd(powerCollateral, perCapCollateral)
totalPerCapitaCollateral := types.BigDiv(
types.BigMul(
availableFilecoin,
types.NewInt(build.PerCapitaCollateralProportion),
),
types.NewInt(build.CollateralPrecision),
)
powerCollateral := types.BigDiv(
types.BigMul(
totalPowerCollateral,
size,
),
totalStorage,
)
perCapCollateral := types.BigDiv(
totalPerCapitaCollateral,
types.NewInt(minerCount),
)
return types.BigAdd(powerCollateral, perCapCollateral), nil
}
func MinerSetHas(ctx context.Context, vmctx types.VMContext, rcid cid.Cid, maddr address.Address) (bool, aerrors.ActorError) {

View File

@ -6,6 +6,7 @@ import (
"testing"
"github.com/filecoin-project/go-lotus/build"
cbg "github.com/whyrusleeping/cbor-gen"
. "github.com/filecoin-project/go-lotus/chain/actors"
"github.com/filecoin-project/go-lotus/chain/address"
@ -13,6 +14,7 @@ import (
"github.com/filecoin-project/go-lotus/chain/wallet"
cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
mh "github.com/multiformats/go-multihash"
"github.com/stretchr/testify/assert"
@ -22,7 +24,7 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
var ownerAddr, workerAddr address.Address
opts := []HarnessOpt{
HarnessAddr(&ownerAddr, 100000),
HarnessAddr(&ownerAddr, 1000000),
HarnessAddr(&workerAddr, 100000),
}
@ -30,7 +32,11 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
var minerAddr address.Address
{
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SMAMethods.CreateStorageMiner,
// cheating the bootstrapping problem
cheatStorageMarketTotal(t, h)
ret, _ := h.InvokeWithValue(t, ownerAddr, StorageMarketAddress, SMAMethods.CreateStorageMiner,
types.NewInt(500000),
&CreateStorageMinerParams{
Owner: ownerAddr,
Worker: workerAddr,
@ -92,6 +98,47 @@ func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
})
ApplyOK(t, ret)
}
{
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SMAMethods.PowerLookup,
&PowerLookupParams{Miner: minerAddr})
assert.Equal(t, ret.ExitCode, byte(1))
}
{
ret, _ := h.Invoke(t, ownerAddr, StorageMarketAddress, SMAMethods.IsMiner, &IsMinerParam{minerAddr})
ApplyOK(t, ret)
assert.Equal(t, ret.Return, cbg.CborBoolFalse)
}
}
func cheatStorageMarketTotal(t *testing.T, h *Harness) {
t.Helper()
sma, err := h.vm.StateTree().GetActor(StorageMarketAddress)
if err != nil {
t.Fatal(err)
}
cst := hamt.CSTFromBstore(h.cs.Blockstore())
var smastate StorageMarketState
if err := cst.Get(context.TODO(), sma.Head, &smastate); err != nil {
t.Fatal(err)
}
smastate.TotalStorage = types.NewInt(10000)
c, err := cst.Put(context.TODO(), &smastate)
if err != nil {
t.Fatal(err)
}
sma.Head = c
if err := h.vm.StateTree().SetActor(StorageMarketAddress, sma); err != nil {
t.Fatal(err)
}
}
func fakeBlock(t *testing.T, minerAddr address.Address, ts uint64) *types.BlockHeader {

View File

@ -47,6 +47,22 @@ func Newf(retCode uint8, format string, args ...interface{}) ActorError {
}
}
func Fatal(message string, args ...interface{}) ActorError {
return &actorError{
fatal: true,
msg: message,
frame: xerrors.Caller(1),
}
}
func Fatalf(format string, args ...interface{}) ActorError {
return &actorError{
fatal: true,
msg: fmt.Sprintf(format, args...),
frame: xerrors.Caller(1),
}
}
// Wrap extens chain of errors with a message
func Wrap(err ActorError, message string) ActorError {
if err == nil {

View File

@ -2697,7 +2697,7 @@ func (t *SlashConsensusFaultParams) UnmarshalCBOR(r io.Reader) error {
}
if extra != 2 {
return fmt.Errorf("cbor input had wrong number of fields (got %d)", extra)
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Block1 (types.BlockHeader)
@ -2787,3 +2787,75 @@ func (t *PledgeCollateralParams) UnmarshalCBOR(r io.Reader) error {
}
return nil
}
func (t *MinerSlashConsensusFault) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{131}); err != nil {
return err
}
// t.t.Slasher (address.Address)
if err := t.Slasher.MarshalCBOR(w); err != nil {
return err
}
// t.t.AtHeight (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.AtHeight)); err != nil {
return err
}
// t.t.SlashedCollateral (types.BigInt)
if err := t.SlashedCollateral.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *MinerSlashConsensusFault) 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 != 3 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.t.Slasher (address.Address)
{
if err := t.Slasher.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.t.AtHeight (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.AtHeight = extra
// t.t.SlashedCollateral (types.BigInt)
{
if err := t.SlashedCollateral.UnmarshalCBOR(br); err != nil {
return err
}
}
return nil
}

View File

@ -240,11 +240,17 @@ func (h *Harness) SendFunds(t testing.TB, from address.Address, to address.Addre
func (h *Harness) Invoke(t testing.TB, from address.Address, to address.Address,
method uint64, params cbg.CBORMarshaler) (*vm.ApplyRet, *state.StateTree) {
t.Helper()
return h.InvokeWithValue(t, from, to, method, types.NewInt(0), params)
}
func (h *Harness) InvokeWithValue(t testing.TB, from address.Address, to address.Address,
method uint64, value types.BigInt, params cbg.CBORMarshaler) (*vm.ApplyRet, *state.StateTree) {
t.Helper()
return h.Apply(t, types.Message{
To: to,
From: from,
Method: method,
Value: types.NewInt(0),
Value: value,
Params: DumpObject(t, params),
GasPrice: types.NewInt(1),
GasLimit: types.NewInt(testGasLimit),

View File

@ -98,15 +98,29 @@ func MakeInitialStateTree(bs bstore.Blockstore, actmap map[address.Address]types
return nil, xerrors.Errorf("set storage market actor: %w", err)
}
netAmt := types.Fil(types.NewInt(build.TotalFilecoin))
for _, amt := range actmap {
netAmt = types.BigSub(netAmt, amt)
}
err = state.SetActor(actors.NetworkAddress, &types.Actor{
Code: actors.AccountActorCodeCid,
Balance: types.NewInt(100000000000),
Balance: netAmt,
Head: emptyobject,
})
if err != nil {
return nil, xerrors.Errorf("set network account actor: %w", err)
}
err = state.SetActor(actors.BurntFundsAddress, &types.Actor{
Code: actors.AccountActorCodeCid,
Balance: types.NewInt(0),
Head: emptyobject,
})
if err != nil {
return nil, xerrors.Errorf("set burnt funds account actor: %w", err)
}
for a, v := range actmap {
err = state.SetActor(a, &types.Actor{
Code: actors.AccountActorCodeCid,

View File

@ -6,6 +6,8 @@ import (
"io"
"math/big"
"github.com/filecoin-project/go-lotus/build"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/polydawn/refmt/obj/atlas"
cbg "github.com/whyrusleeping/cbor-gen"
@ -41,6 +43,10 @@ func NewInt(i uint64) BigInt {
return BigInt{big.NewInt(0).SetUint64(i)}
}
func Fil(i BigInt) BigInt {
return BigMul(i, NewInt(build.FilecoinPrecision))
}
func BigFromBytes(b []byte) BigInt {
i := big.NewInt(0).SetBytes(b)
return BigInt{i}

View File

@ -73,6 +73,7 @@ func main() {
actors.UpdateStorageParams{},
actors.SlashConsensusFaultParams{},
actors.PledgeCollateralParams{},
actors.MinerSlashConsensusFault{},
)
if err != nil {
fmt.Println(err)