Merge pull request #2462 from nspcc-dev/block-destroyed

core: block destroyed contracts and restrict maximum contract updates
This commit is contained in:
Roman Khimov 2022-05-04 14:43:51 +03:00 committed by GitHub
commit 4adb2b95c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 24 additions and 6 deletions

View file

@ -82,6 +82,7 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts {
neo.Policy = policy neo.Policy = policy
gas.NEO = neo gas.NEO = neo
mgmt.NEO = neo mgmt.NEO = neo
mgmt.Policy = policy
policy.NEO = neo policy.NEO = neo
cs.GAS = gas cs.GAS = gas

View file

@ -28,7 +28,8 @@ import (
// Management is contract-managing native contract. // Management is contract-managing native contract.
type Management struct { type Management struct {
interop.ContractMD interop.ContractMD
NEO *NEO NEO *NEO
Policy *Policy
} }
type ManagementCache struct { type ManagementCache struct {
@ -286,6 +287,9 @@ func (m *Management) markUpdated(d *dao.Simple, hash util.Uint160, cs *state.Con
// It doesn't run _deploy method and doesn't emit notification. // It doesn't run _deploy method and doesn't emit notification.
func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) { func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) {
h := state.CreateContractHash(sender, neff.Checksum, manif.Name) h := state.CreateContractHash(sender, neff.Checksum, manif.Name)
if m.Policy.IsBlocked(d, h) {
return nil, fmt.Errorf("the contract %s has been blocked", h.StringLE())
}
_, err := m.GetContract(d, h) _, err := m.GetContract(d, h)
if err == nil { if err == nil {
return nil, errors.New("contract already exists") return nil, errors.New("contract already exists")
@ -349,6 +353,9 @@ func (m *Management) Update(d *dao.Simple, hash util.Uint160, neff *nef.File, ma
if err != nil { if err != nil {
return nil, errors.New("contract doesn't exist") return nil, errors.New("contract doesn't exist")
} }
if oldcontract.UpdateCounter == math.MaxUint16 {
return nil, errors.New("the contract reached the maximum number of updates")
}
contract = *oldcontract // Make a copy, don't ruin (potentially) cached contract. contract = *oldcontract // Make a copy, don't ruin (potentially) cached contract.
// if NEF was provided, update the contract script // if NEF was provided, update the contract script
@ -404,6 +411,7 @@ func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error {
d.DeleteStorageItem(contract.ID, k) d.DeleteStorageItem(contract.ID, k)
return true return true
}) })
m.Policy.blockAccountInternal(d, hash)
m.markUpdated(d, hash, nil) m.markUpdated(d, hash, nil)
return nil return nil
} }

View file

@ -17,9 +17,11 @@ import (
func TestDeployGetUpdateDestroyContract(t *testing.T) { func TestDeployGetUpdateDestroyContract(t *testing.T) {
mgmt := newManagement() mgmt := newManagement()
mgmt.Policy = newPolicy()
d := dao.NewSimple(storage.NewMemoryStore(), false, false) d := dao.NewSimple(storage.NewMemoryStore(), false, false)
err := mgmt.Initialize(&interop.Context{DAO: d}) err := mgmt.Initialize(&interop.Context{DAO: d})
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, mgmt.Policy.Initialize(&interop.Context{DAO: d}))
script := []byte{byte(opcode.RET)} script := []byte{byte(opcode.RET)}
sender := util.Uint160{1, 2, 3} sender := util.Uint160{1, 2, 3}
ne, err := nef.NewFile(script) ne, err := nef.NewFile(script)
@ -86,9 +88,11 @@ func TestManagement_Initialize(t *testing.T) {
func TestManagement_GetNEP17Contracts(t *testing.T) { func TestManagement_GetNEP17Contracts(t *testing.T) {
mgmt := newManagement() mgmt := newManagement()
mgmt.Policy = newPolicy()
d := dao.NewSimple(storage.NewMemoryStore(), false, false) d := dao.NewSimple(storage.NewMemoryStore(), false, false)
err := mgmt.Initialize(&interop.Context{DAO: d}) err := mgmt.Initialize(&interop.Context{DAO: d})
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, mgmt.Policy.Initialize(&interop.Context{DAO: d}))
err = mgmt.InitializeCache(d) err = mgmt.InitializeCache(d)
require.NoError(t, err) require.NoError(t, err)

View file

@ -563,5 +563,7 @@ func TestManagement_ContractDestroy(t *testing.T) {
t.Run("check contract", func(t *testing.T) { t.Run("check contract", func(t *testing.T) {
managementInvoker.Invoke(t, stackitem.Null{}, "getContract", cs1.Hash.BytesBE()) managementInvoker.Invoke(t, stackitem.Null{}, "getContract", cs1.Hash.BytesBE())
}) })
// deploy after destroy should fail
managementInvoker.InvokeFail(t, fmt.Sprintf("the contract %s has been blocked", cs1.Hash.StringLE()), "deploy", nefBytes, manifestBytes)
}) })
} }

View file

@ -317,20 +317,23 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki
panic("cannot block native contract") panic("cannot block native contract")
} }
} }
i, blocked := p.isBlockedInternal(ic.DAO, hash) return stackitem.NewBool(p.blockAccountInternal(ic.DAO, hash))
}
func (p *Policy) blockAccountInternal(d *dao.Simple, hash util.Uint160) bool {
i, blocked := p.isBlockedInternal(d, hash)
if blocked { if blocked {
return stackitem.NewBool(false) return false
} }
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{}) d.PutStorageItem(p.ID, key, state.StorageItem{})
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache) cache := d.GetRWCache(p.ID).(*PolicyCache)
if len(cache.blockedAccounts) == i { if len(cache.blockedAccounts) == i {
cache.blockedAccounts = append(cache.blockedAccounts, hash) cache.blockedAccounts = append(cache.blockedAccounts, hash)
} else { } else {
cache.blockedAccounts = append(cache.blockedAccounts[:i+1], cache.blockedAccounts[i:]...) cache.blockedAccounts = append(cache.blockedAccounts[:i+1], cache.blockedAccounts[i:]...)
cache.blockedAccounts[i] = hash cache.blockedAccounts[i] = hash
} }
return stackitem.NewBool(true) return true
} }
// unblockAccount is Policy contract method and removes given account hash from // unblockAccount is Policy contract method and removes given account hash from