Skip to content
This repository has been archived by the owner on Jun 6, 2023. It is now read-only.

Commit

Permalink
Miner: Handle a pending key change that has been replaced (#902)
Browse files Browse the repository at this point in the history
* fix worker key change problem
  • Loading branch information
aarshkshah1992 committed Aug 6, 2020
1 parent 0f9f59a commit 8735710
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 8 deletions.
13 changes: 5 additions & 8 deletions actors/builtin/miner/miner_actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,13 @@ type ChangeWorkerAddressParams struct {

func (a Actor) ChangeWorkerAddress(rt Runtime, params *ChangeWorkerAddressParams) *adt.EmptyValue {
var effectiveEpoch abi.ChainEpoch
worker := resolveWorkerAddress(rt, params.NewWorker)
var st State
rt.State().Transaction(&st, func() {
info := getMinerInfo(rt, &st)

rt.ValidateImmediateCallerIs(info.Owner)

worker := resolveWorkerAddress(rt, params.NewWorker)

effectiveEpoch = rt.CurrEpoch() + WorkerKeyChangeDelay

// This may replace another pending key change.
Expand Down Expand Up @@ -1952,12 +1951,10 @@ func commitWorkerKeyChange(rt Runtime) *adt.EmptyValue {
var st State
rt.State().Transaction(&st, func() {
info := getMinerInfo(rt, &st)
if info.PendingWorkerKey == nil {
rt.Abortf(exitcode.ErrIllegalState, "No pending key change.")
}

if info.PendingWorkerKey.EffectiveAt > rt.CurrEpoch() {
rt.Abortf(exitcode.ErrIllegalState, "Too early for key change. Current: %v, Change: %v)", rt.CurrEpoch(), info.PendingWorkerKey.EffectiveAt)
// A previously scheduled key change could have been replaced with a new key change request
// scheduled in the future. This case should be treated as a no-op.
if info.PendingWorkerKey == nil || info.PendingWorkerKey.EffectiveAt > rt.CurrEpoch() {
return
}

info.Worker = info.PendingWorkerKey.NewWorker
Expand Down
166 changes: 166 additions & 0 deletions actors/builtin/miner/miner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,123 @@ func TestWithdrawBalance(t *testing.T) {
})
}

func TestChangeWorkerAddress(t *testing.T) {
periodOffset := abi.ChainEpoch(100)

setupFunc := func() (*mock.Runtime, *actorHarness) {
actor := newHarness(t, periodOffset)
builder := builderForHarness(actor).
WithBalance(bigBalance, big.Zero())
rt := builder.Build(t)

return rt, actor
}

t.Run("successfully change a worker address", func(t *testing.T) {
rt, actor := setupFunc()
actor.constructAndVerify(rt)
newWorker := tutil.NewIDAddr(t, 999)

currentEpoch := abi.ChainEpoch(5)
rt.SetEpoch(currentEpoch)

effectiveEpoch := currentEpoch + miner.WorkerKeyChangeDelay
actor.changeWorkerAddress(rt, newWorker, effectiveEpoch)

// no change if current epoch is less than effective epoch
rt.SetEpoch(effectiveEpoch - 1)
rt.SetCaller(builtin.StoragePowerActorAddr, builtin.StoragePowerActorCodeID)
rt.ExpectValidateCallerAddr(builtin.StoragePowerActorAddr)
rt.Call(actor.a.OnDeferredCronEvent, &miner.CronEventPayload{
EventType: miner.CronEventWorkerKeyChange,
})
rt.Verify()
st := getState(rt)
info, err := st.GetInfo(adt.AsStore(rt))
require.NoError(t, err)
require.NotNil(t, info.PendingWorkerKey)
require.EqualValues(t, actor.worker, info.Worker)

// set current epoch to effective epoch and ask to change the address
actor.cronWorkerAddrChange(rt, effectiveEpoch, newWorker)
})

t.Run("fails if unable to resolve worker address", func(t *testing.T) {
rt, actor := setupFunc()
actor.constructAndVerify(rt)
newWorker := tutil.NewBLSAddr(t, 999)
rt.SetAddressActorType(newWorker, builtin.AccountActorCodeID)

rt.SetCaller(actor.owner, builtin.AccountActorCodeID)
param := &miner.ChangeWorkerAddressParams{newWorker}
rt.ExpectAbort(exitcode.ErrIllegalArgument, func() {
rt.Call(actor.a.ChangeWorkerAddress, param)
})
rt.Verify()
})

t.Run("fails if worker public key is not BLS", func(t *testing.T) {
rt, actor := setupFunc()
actor.constructAndVerify(rt)
newWorker := tutil.NewIDAddr(t, 999)
rt.SetAddressActorType(newWorker, builtin.AccountActorCodeID)
key := tutil.NewIDAddr(t, 505)

rt.ExpectSend(newWorker, builtin.MethodsAccount.PubkeyAddress, nil, big.Zero(), &key, exitcode.Ok)

rt.SetCaller(actor.owner, builtin.AccountActorCodeID)
param := &miner.ChangeWorkerAddressParams{newWorker}
rt.ExpectAbort(exitcode.ErrIllegalArgument, func() {
rt.Call(actor.a.ChangeWorkerAddress, param)
})
rt.Verify()
})

t.Run("fails if new worker address does not have a code", func(t *testing.T) {
rt, actor := setupFunc()
actor.constructAndVerify(rt)
newWorker := tutil.NewIDAddr(t, 5001)

rt.SetCaller(actor.owner, builtin.AccountActorCodeID)
param := &miner.ChangeWorkerAddressParams{newWorker}
rt.ExpectAbort(exitcode.ErrIllegalArgument, func() {
rt.Call(actor.a.ChangeWorkerAddress, param)
})
rt.Verify()
})

t.Run("fails if new worker is not an account actor", func(t *testing.T) {
rt, actor := setupFunc()
actor.constructAndVerify(rt)
newWorker := tutil.NewIDAddr(t, 999)
rt.SetAddressActorType(newWorker, builtin.StorageMinerActorCodeID)

rt.SetCaller(actor.owner, builtin.AccountActorCodeID)
param := &miner.ChangeWorkerAddressParams{newWorker}
rt.ExpectAbort(exitcode.ErrIllegalArgument, func() {
rt.Call(actor.a.ChangeWorkerAddress, param)
})
rt.Verify()
})

t.Run("fails when caller is not the owner", func(t *testing.T) {
rt, actor := setupFunc()
actor.constructAndVerify(rt)
newWorker := tutil.NewIDAddr(t, 999)
rt.SetAddressActorType(newWorker, builtin.AccountActorCodeID)

rt.ExpectValidateCallerAddr(actor.owner)
rt.ExpectSend(newWorker, builtin.MethodsAccount.PubkeyAddress, nil, big.Zero(), &actor.key, exitcode.Ok)

rt.SetCaller(actor.worker, builtin.AccountActorCodeID)
param := &miner.ChangeWorkerAddressParams{newWorker}
rt.ExpectAbort(exitcode.ErrForbidden, func() {
rt.Call(actor.a.ChangeWorkerAddress, param)
})
rt.Verify()
})
}

func TestReportConsensusFault(t *testing.T) {
periodOffset := abi.ChainEpoch(100)
actor := newHarness(t, periodOffset)
Expand Down Expand Up @@ -2048,6 +2165,55 @@ func (h *actorHarness) getLockedFunds(rt *mock.Runtime) abi.TokenAmount {
// Actor method calls
//

func (h *actorHarness) changeWorkerAddress(rt *mock.Runtime, newWorker addr.Address, effectiveEpoch abi.ChainEpoch) {
rt.SetAddressActorType(newWorker, builtin.AccountActorCodeID)

param := &miner.ChangeWorkerAddressParams{newWorker}

cronPayload := miner.CronEventPayload{
EventType: miner.CronEventWorkerKeyChange,
}
payload := new(bytes.Buffer)
err := cronPayload.MarshalCBOR(payload)
require.NoError(h.t, err)
cronEvt := &power.EnrollCronEventParams{
EventEpoch: effectiveEpoch,
Payload: payload.Bytes(),
}

rt.ExpectValidateCallerAddr(h.owner)
rt.ExpectSend(newWorker, builtin.MethodsAccount.PubkeyAddress, nil, big.Zero(), &h.key, exitcode.Ok)

rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.EnrollCronEvent,
cronEvt, big.Zero(), nil, exitcode.Ok)

rt.SetCaller(h.owner, builtin.AccountActorCodeID)
rt.Call(h.a.ChangeWorkerAddress, param)
rt.Verify()

st := getState(rt)
info, err := st.GetInfo(adt.AsStore(rt))
require.NoError(h.t, err)
require.EqualValues(h.t, effectiveEpoch, info.PendingWorkerKey.EffectiveAt)
require.EqualValues(h.t, newWorker, info.PendingWorkerKey.NewWorker)
}

func (h *actorHarness) cronWorkerAddrChange(rt *mock.Runtime, effectiveEpoch abi.ChainEpoch, newWorker addr.Address) {
rt.SetEpoch(effectiveEpoch)
rt.SetCaller(builtin.StoragePowerActorAddr, builtin.StoragePowerActorCodeID)
rt.ExpectValidateCallerAddr(builtin.StoragePowerActorAddr)
rt.Call(h.a.OnDeferredCronEvent, &miner.CronEventPayload{
EventType: miner.CronEventWorkerKeyChange,
})
rt.Verify()

st := getState(rt)
info, err := st.GetInfo(adt.AsStore(rt))
require.NoError(h.t, err)
require.Nil(h.t, info.PendingWorkerKey)
require.EqualValues(h.t, newWorker, info.Worker)
}

func (h *actorHarness) controlAddresses(rt *mock.Runtime) (owner, worker addr.Address) {
rt.ExpectValidateCallerAny()
ret := rt.Call(h.a.ControlAddresses, nil).(*miner.GetControlAddressesReturn)
Expand Down

0 comments on commit 8735710

Please sign in to comment.