Skip to content

Commit

Permalink
Merge pull request #2841 from 0chain/fix/rm-killed-blob-val
Browse files Browse the repository at this point in the history
Delete blobber/validator when kill/shutdown
  • Loading branch information
dabasov committed Oct 9, 2023
2 parents b329af8 + eba486b commit ee39d7d
Show file tree
Hide file tree
Showing 5 changed files with 535 additions and 16 deletions.
10 changes: 7 additions & 3 deletions code/go/0chain.net/smartcontract/provider/shutdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
"0chain.net/smartcontract/stakepool"

cstate "0chain.net/chaincore/chain/state"
"0chain.net/chaincore/smartcontractinterface"
)

func ShutDown(
input []byte,
clientId string,
clientId, ownerId string,
providerSpecific func(ProviderRequest) (AbstractProvider, stakepool.AbstractStakePool, error),
balances cstate.StateContextI,
) error {
Expand All @@ -40,8 +41,11 @@ func ShutDown(
return err
}

if clientId != sp.GetSettings().DelegateWallet {
return fmt.Errorf("access denied, allowed for delegate_wallet owner only")
var errCode = "shutdown_" + p.Type().String() + "_failed"
if err := smartcontractinterface.AuthorizeWithOwner(errCode, func() bool {
return ownerId == clientId || clientId == sp.GetSettings().DelegateWallet
}); err != nil {
return err
}

balances.EmitEvent(event.TypeStats, event.TagShutdownProvider, p.Id(), dbs.ProviderID{
Expand Down
46 changes: 42 additions & 4 deletions code/go/0chain.net/smartcontract/storagesc/kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ func (_ *StorageSmartContract) killBlobber(
return "", common.NewError("can't get config", err.Error())
}

var blobber = &StorageNode{}
var (
blobber = &StorageNode{}
sp stakepool.AbstractStakePool
)
err = provider.Kill(
input,
tx.ClientID,
Expand All @@ -45,7 +48,7 @@ func (_ *StorageSmartContract) killBlobber(
}
}

sp, err := getStakePoolAdapter(blobber.Type(), blobber.Id(), balances)
sp, err = getStakePoolAdapter(blobber.Type(), blobber.Id(), balances)
if err != nil {
return nil, nil, err
}
Expand All @@ -57,6 +60,22 @@ func (_ *StorageSmartContract) killBlobber(
if err != nil {
return "", common.NewError("kill_blobber_failed", err.Error())
}

// delete the blobber from MPT if it's empty and has no stake pools
if blobber.SavedData <= 0 && len(sp.GetPools()) == 0 {
// remove the blobber from MPT
_, err := balances.DeleteTrieNode(blobber.GetKey())
if err != nil {
return "", common.NewErrorf("kill_blobber_failed", "deleting blobber: %v", err)
}

if err = deleteStakepool(balances, blobber.ProviderType, blobber.Id()); err != nil {
return "", common.NewErrorf("kill_blobber_failed", "deleting stakepool: %v", err)
}

return "", nil
}

_, err = balances.InsertTrieNode(blobber.GetKey(), blobber)
if err != nil {
return "", common.NewError("kill_blobber_failed", "saving blobber: "+err.Error())
Expand All @@ -77,7 +96,10 @@ func (_ *StorageSmartContract) killValidator(
return "", common.NewError("can't get config", err.Error())
}

var validator = &ValidationNode{}
var (
validator = &ValidationNode{}
sp stakepool.AbstractStakePool
)
err = provider.Kill(
input,
tx.ClientID,
Expand All @@ -103,7 +125,7 @@ func (_ *StorageSmartContract) killValidator(
}
}

sp, err := getStakePoolAdapter(validator.Type(), validator.Id(), balances)
sp, err = getStakePoolAdapter(validator.Type(), validator.Id(), balances)
if err != nil {
return nil, nil, err
}
Expand All @@ -114,6 +136,22 @@ func (_ *StorageSmartContract) killValidator(
if err != nil {
return "", common.NewError("kill_validator_failed", err.Error())
}

// delete the validator from MPT if its stake pools is empty
if len(sp.GetPools()) == 0 {
// remove the validator from MPT
_, err := balances.DeleteTrieNode(validator.GetKey(""))
if err != nil {
return "", common.NewErrorf("kill_validator_failed", "deleting validator: %v", err)
}

if err = deleteStakepool(balances, validator.ProviderType, validator.Id()); err != nil {
return "", common.NewErrorf("kill_validator_failed", "deleting stakepool: %v", err)
}

return "", nil
}

_, err = balances.InsertTrieNode(validator.GetKey(""), validator)
if err != nil {
return "", common.NewError("kill_validator_failed", "saving validator: "+err.Error())
Expand Down
208 changes: 208 additions & 0 deletions code/go/0chain.net/smartcontract/storagesc/kill_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package storagesc

import (
"testing"

"github.com/0chain/common/core/util"
"github.com/stretchr/testify/require"

"0chain.net/chaincore/transaction"
"0chain.net/smartcontract/provider"
"0chain.net/smartcontract/stakepool"
"0chain.net/smartcontract/stakepool/spenum"
)

func TestStorageSmartContract_killBlobber(t *testing.T) {
t.Parallel()

tests := []struct {
name string
blobberSavedSize int64
stakePool *stakePool
input []byte
clientID string
expectedStateFunc func(t *testing.T, state *testBalances)
}{
{
name: "both blobber and stake pools are empty",
blobberSavedSize: 0,
stakePool: newStakePool(),
input: []byte(`{"provider_id":"blobber_id"}`),
clientID: "blobber_id",
expectedStateFunc: func(t *testing.T, state *testBalances) {
var b StorageNode
b.ID = "blobber_id"
err := state.GetTrieNode(b.GetKey(), &b)
require.Error(t, util.ErrValueNotPresent, err)

var s stakepool.StakePool
err = state.GetTrieNode(stakePoolKey(spenum.Blobber, "blobber_id"), &s)
require.Error(t, util.ErrValueNotPresent, err)
},
},
{
name: "blobber is not empty",
blobberSavedSize: 10,
stakePool: newStakePool(),
input: []byte(`{"provider_id":"blobber_id"}`),
clientID: "blobber_id",
expectedStateFunc: func(t *testing.T, state *testBalances) {
var b StorageNode
b.ID = "blobber_id"
err := state.GetTrieNode(b.GetKey(), &b)
require.NoError(t, err)

var s stakepool.StakePool
err = state.GetTrieNode(stakePoolKey(spenum.Blobber, "blobber_id"), &s)
require.NoError(t, err)
},
},
{
name: "stake pool is not empty",
blobberSavedSize: 0,
stakePool: &stakePool{
StakePool: &stakepool.StakePool{
Pools: map[string]*stakepool.DelegatePool{
"stake_pool_1": &stakepool.DelegatePool{},
"stake_pool_2": &stakepool.DelegatePool{},
},
},
},
input: []byte(`{"provider_id":"blobber_id"}`),
clientID: "blobber_id",
expectedStateFunc: func(t *testing.T, state *testBalances) {
var b StorageNode
b.ID = "blobber_id"
b.ProviderType = spenum.Blobber
err := state.GetTrieNode(b.GetKey(), &b)
require.NoError(t, err)

var sp stakePool
err = state.GetTrieNode(stakePoolKey(spenum.Blobber, "blobber_id"), &sp)
require.NoError(t, err)
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ssc := &StorageSmartContract{}

balances := newTestBalances(t, false)
balances.txn = &transaction.Transaction{
ClientID: tc.clientID,
}

conf := &Config{
OwnerId: tc.clientID,
StakePool: &stakePoolConfig{},
}

mustSave(t, scConfigKey(ADDRESS), conf, balances)

// Create a blobber and a stake pool
blobber := &StorageNode{
Provider: provider.Provider{
ID: "blobber_id",
ProviderType: spenum.Blobber,
},
SavedData: tc.blobberSavedSize,
}
balances.InsertTrieNode(blobber.GetKey(), blobber)
balances.InsertTrieNode(stakePoolKey(spenum.Blobber, blobber.ID), tc.stakePool)

// Call the killBlobber method
_, err := ssc.killBlobber(balances.txn, tc.input, balances)
require.NoError(t, err)

tc.expectedStateFunc(t, balances)
})
}
}

func TestStorageSmartContract_killValidator(t *testing.T) {
t.Parallel()

tests := []struct {
name string
stakePool *stakePool
input []byte
clientID string
expectedStateFunc func(t *testing.T, state *testBalances)
}{
{
name: "stake pool is empty",
stakePool: newStakePool(),
input: []byte(`{"provider_id":"validator_id"}`),
clientID: "validator_id",
expectedStateFunc: func(t *testing.T, state *testBalances) {
var b StorageNode
b.ID = "validator_id"
err := state.GetTrieNode(b.GetKey(), &b)
require.Error(t, util.ErrValueNotPresent, err)

var s stakepool.StakePool
err = state.GetTrieNode(stakePoolKey(spenum.Validator, "validator_id"), &s)
require.Error(t, util.ErrValueNotPresent, err)
},
},
{
name: "stake pool is not empty",
stakePool: &stakePool{
StakePool: &stakepool.StakePool{
Pools: map[string]*stakepool.DelegatePool{
"stake_pool_1": &stakepool.DelegatePool{},
"stake_pool_2": &stakepool.DelegatePool{},
},
},
},
input: []byte(`{"provider_id":"validator_id"}`),
clientID: "validator_id",
expectedStateFunc: func(t *testing.T, state *testBalances) {
var b StorageNode
b.ID = "validator_id"
b.ProviderType = spenum.Validator
err := state.GetTrieNode(b.GetKey(), &b)
require.NoError(t, err)

var sp stakePool
err = state.GetTrieNode(stakePoolKey(spenum.Validator, "validator_id"), &sp)
require.NoError(t, err)
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ssc := &StorageSmartContract{}

balances := newTestBalances(t, false)
balances.txn = &transaction.Transaction{
ClientID: tc.clientID,
}

conf := &Config{
OwnerId: tc.clientID,
StakePool: &stakePoolConfig{},
}

mustSave(t, scConfigKey(ADDRESS), conf, balances)

// Create a blobber and a stake pool
vn := &ValidationNode{
Provider: provider.Provider{
ID: "validator_id",
ProviderType: spenum.Validator,
},
}
balances.InsertTrieNode(vn.GetKey(""), vn)
balances.InsertTrieNode(stakePoolKey(spenum.Validator, vn.ID), tc.stakePool)

// Call the killBlobber method
_, err := ssc.killValidator(balances.txn, tc.input, balances)
require.NoError(t, err)

tc.expectedStateFunc(t, balances)
})
}
}
Loading

0 comments on commit ee39d7d

Please sign in to comment.