From 88c738b736aa5e8deb26642296c473342fd995d7 Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Thu, 11 Feb 2021 18:55:32 +0300 Subject: [PATCH] [#49] Support contract migration At initialization contract saves master script hash that allows to re-initialize or migrate contract. Signed-off-by: Alex Vanin --- alphabet/alphabet_contract.go | 20 +++++++++++++++--- audit/audit_contract.go | 35 ++++++++++++++++++++++++------- balance/balance_contract.go | 20 +++++++++++++++--- common/update.go | 22 +++++++++++++++++++ container/container_contract.go | 22 ++++++++++++++----- go.mod | 2 +- go.sum | 4 ++++ neofs/neofs_contract.go | 22 ++++++++++++++++--- neofsid/neofsid_contract.go | 21 ++++++++++++++++--- netmap/netmap_contract.go | 22 ++++++++++++++++--- reputation/reputation_contract.go | 24 +++++++++++++++++++++ 11 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 common/update.go diff --git a/alphabet/alphabet_contract.go b/alphabet/alphabet_contract.go index 2cbd9b7..f1a8971 100644 --- a/alphabet/alphabet_contract.go +++ b/alphabet/alphabet_contract.go @@ -6,6 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/neo" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" @@ -34,15 +35,16 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { } } -func Init(addrNetmap []byte, name string, index, total int) { - if storage.Get(ctx, netmapKey) != nil { - panic("contract already deployed") +func Init(owner interop.Hash160, addrNetmap []byte, name string, index, total int) { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") } if len(addrNetmap) != 20 { panic("incorrect length of contract script hash") } + storage.Put(ctx, common.OwnerKey, owner) storage.Put(ctx, netmapKey, addrNetmap) storage.Put(ctx, nameKey, name) storage.Put(ctx, indexKey, index) @@ -53,6 +55,18 @@ func Init(addrNetmap []byte, name string, index, total int) { runtime.Log(name + " contract initialized") } +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("alphabet contract updated") + + return true +} + func Gas() int { return gas.BalanceOf(runtime.GetExecutingScriptHash()) } diff --git a/audit/audit_contract.go b/audit/audit_contract.go index 36f2599..1140ba1 100644 --- a/audit/audit_contract.go +++ b/audit/audit_contract.go @@ -4,6 +4,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neofs-contract/common" @@ -36,8 +37,7 @@ func (a auditHeader) ID() []byte { const ( version = 1 - netmapContractKey = "netmapScriptHash" - netmapContractKeyLn = len(netmapContractKey) + netmapContractKey = "netmapScriptHash" ) var ctx storage.Context @@ -46,20 +46,33 @@ func init() { ctx = storage.GetContext() } -func Init(addrNetmap interop.Hash160) { - if storage.Get(ctx, netmapContractKey) != nil { - panic("init: contract already deployed") +func Init(owner interop.Hash160, addrNetmap interop.Hash160) { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") } if len(addrNetmap) != 20 { panic("init: incorrect length of contract script hash") } + storage.Put(ctx, common.OwnerKey, owner) storage.Put(ctx, netmapContractKey, addrNetmap) runtime.Log("audit contract initialized") } +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("audit contract updated") + + return true +} + func Put(rawAuditResult []byte) bool { innerRing := common.InnerRingListViaStorage(ctx, netmapContractKey) @@ -126,10 +139,18 @@ func ListByNode(epoch int, cid []byte, key interop.PublicKey) [][]byte { func list(it iterator.Iterator) [][]byte { var result [][]byte + ignore := [][]byte{ + []byte(netmapContractKey), + []byte(common.OwnerKey), + } + +loop: for iterator.Next(it) { key := iterator.Value(it).([]byte) // iterator MUST BE `storage.KeysOnly` - if len(key) == netmapContractKeyLn { - continue + for _, ignoreKey := range ignore { + if common.BytesEqual(key, ignoreKey) { + continue loop + } } result = append(result, key) diff --git a/balance/balance_contract.go b/balance/balance_contract.go index 2ea2178..4d00e7d 100644 --- a/balance/balance_contract.go +++ b/balance/balance_contract.go @@ -4,6 +4,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/binary" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neofs-contract/common" @@ -63,21 +64,34 @@ func init() { token = CreateToken() } -func Init(addrNetmap, addrContainer []byte) { - if storage.Get(ctx, netmapContractKey) != nil { - panic("init: contract already deployed") +func Init(owner interop.Hash160, addrNetmap, addrContainer []byte) { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") } if len(addrNetmap) != 20 || len(addrContainer) != 20 { panic("init: incorrect length of contract script hash") } + storage.Put(ctx, common.OwnerKey, owner) storage.Put(ctx, netmapContractKey, addrNetmap) storage.Put(ctx, containerContractKey, addrContainer) runtime.Log("balance contract initialized") } +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("balance contract updated") + + return true +} + func Symbol() string { return token.Symbol } diff --git a/common/update.go b/common/update.go new file mode 100644 index 0000000..7b9a054 --- /dev/null +++ b/common/update.go @@ -0,0 +1,22 @@ +package common + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + "github.com/nspcc-dev/neo-go/pkg/interop/storage" +) + +const OwnerKey = "contractOwner" + +// HasUpdateAccess returns true if contract can be initialized, re-initialized +// or migrated. +func HasUpdateAccess(ctx storage.Context) bool { + data := storage.Get(ctx, OwnerKey) + if data == nil { // contract has not been initialized yet, return true + return true + } + + owner := data.(interop.Hash160) + + return runtime.CheckWitness(owner) +} diff --git a/container/container_contract.go b/container/container_contract.go index 733d4d3..b8c2201 100644 --- a/container/container_contract.go +++ b/container/container_contract.go @@ -6,6 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neofs-contract/common" @@ -59,17 +60,16 @@ func init() { ctx = storage.GetContext() } -func Init(addrNetmap, addrBalance, addrID []byte) { - if storage.Get(ctx, netmapContractKey) != nil && - storage.Get(ctx, balanceContractKey) != nil && - storage.Get(ctx, neofsIDContractKey) != nil { - panic("init: contract already deployed") +func Init(owner interop.Hash160, addrNetmap, addrBalance, addrID []byte) { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") } if len(addrNetmap) != 20 || len(addrBalance) != 20 || len(addrID) != 20 { panic("init: incorrect length of contract script hash") } + storage.Put(ctx, common.OwnerKey, owner) storage.Put(ctx, netmapContractKey, addrNetmap) storage.Put(ctx, balanceContractKey, addrBalance) storage.Put(ctx, neofsIDContractKey, addrID) @@ -77,6 +77,18 @@ func Init(addrNetmap, addrBalance, addrID []byte) { runtime.Log("container contract initialized") } +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("container contract updated") + + return true +} + func Put(container, signature, publicKey []byte) bool { netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte) innerRing := common.InnerRingList(netmapContractAddr) diff --git a/go.mod b/go.mod index 35233b8..3da384e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/nspcc-dev/neofs-contract go 1.13 -require github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210211114309-2ee755e09fcd +require github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210212114643-94672d4d83b7 diff --git a/go.sum b/go.sum index 73b8048..b34812d 100644 --- a/go.sum +++ b/go.sum @@ -139,6 +139,8 @@ github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210208141009-71494e6ae449 h1:gfsr6+g github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210208141009-71494e6ae449/go.mod h1:6D4NY4Bs1OTan3VdLvAuy9kGt460/2dsfHcthQGS+QU= github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210211114309-2ee755e09fcd h1:4zMEQ+xVaa6oEnzzHOlRwwRQMdze/so7YpjjE8bfkPI= github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210211114309-2ee755e09fcd/go.mod h1:6D4NY4Bs1OTan3VdLvAuy9kGt460/2dsfHcthQGS+QU= +github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210212114643-94672d4d83b7 h1:4DPdZM6M1idOhkpMlZsoNjNTuj0Y4I5EyVxe5FA5jmY= +github.com/nspcc-dev/neo-go v0.93.0-pre.0.20210212114643-94672d4d83b7/go.mod h1:6tixfAd+d8TIm05DA874j6t898G/fyqA2fHVJxkJCXQ= github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= github.com/nspcc-dev/neofs-crypto v0.2.3/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw= github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= @@ -239,6 +241,8 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180318012157-96caea41033d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/neofs/neofs_contract.go b/neofs/neofs_contract.go index 557adcc..1e8df89 100644 --- a/neofs/neofs_contract.go +++ b/neofs/neofs_contract.go @@ -38,6 +38,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neofs-contract/common" @@ -84,9 +85,9 @@ func init() { } // Init set up initial inner ring node keys. -func Init(args [][]byte) bool { - if storage.Get(ctx, innerRingKey) != nil { - panic("neofs: contract already deployed") +func Init(owner interop.PublicKey, args [][]byte) bool { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") } var irList []common.IRNode @@ -109,11 +110,26 @@ func Init(args [][]byte) bool { common.SetSerialized(ctx, candidatesKey, []common.IRNode{}) common.SetSerialized(ctx, cashedChequesKey, []cheque{}) + storage.Put(ctx, common.OwnerKey, owner) + runtime.Log("neofs: contract initialized") return true } +// Migrate updates smart contract execution script and manifest. +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("neofs contract updated") + + return true +} + // InnerRingList returns array of inner ring node keys. func InnerRingList() []common.IRNode { return getInnerRingNodes(ctx, innerRingKey) diff --git a/neofsid/neofsid_contract.go b/neofsid/neofsid_contract.go index 344e3ea..aca67c7 100644 --- a/neofsid/neofsid_contract.go +++ b/neofsid/neofsid_contract.go @@ -1,8 +1,10 @@ package neofsidcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/binary" "github.com/nspcc-dev/neo-go/pkg/interop/crypto" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neofs-contract/common" @@ -27,21 +29,34 @@ func init() { ctx = storage.GetContext() } -func Init(addrNetmap, addrContainer []byte) { - if storage.Get(ctx, netmapContractKey) != nil { - panic("init: contract already deployed") +func Init(owner interop.Hash160, addrNetmap, addrContainer []byte) { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") } if len(addrNetmap) != 20 || len(addrContainer) != 20 { panic("init: incorrect length of contract script hash") } + storage.Put(ctx, common.OwnerKey, owner) storage.Put(ctx, netmapContractKey, addrNetmap) storage.Put(ctx, containerContractKey, addrContainer) runtime.Log("neofsid contract initialized") } +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("neofsid contract updated") + + return true +} + func AddKey(owner []byte, keys [][]byte) bool { var ( n int // number of votes for inner ring invoke diff --git a/netmap/netmap_contract.go b/netmap/netmap_contract.go index 974591d..5af7f93 100644 --- a/netmap/netmap_contract.go +++ b/netmap/netmap_contract.go @@ -1,9 +1,11 @@ package netmapcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/binary" "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neofs-contract/common" @@ -57,9 +59,9 @@ func init() { // Init function sets up initial list of inner ring public keys and should // be invoked once at neofs infrastructure setup. -func Init(keys [][]byte) { - if storage.Get(ctx, innerRingKey) != nil { - panic("netmap: contract already initialized") +func Init(owner interop.Hash160, keys [][]byte) { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") } var irList []common.IRNode @@ -69,6 +71,8 @@ func Init(keys [][]byte) { irList = append(irList, common.IRNode{PublicKey: key}) } + storage.Put(ctx, common.OwnerKey, owner) + common.SetSerialized(ctx, innerRingKey, irList) // epoch number is a little endian int, it doesn't need to be serialized @@ -83,6 +87,18 @@ func Init(keys [][]byte) { runtime.Log("netmap contract initialized") } +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("netmap contract updated") + + return true +} + func InnerRingList() []common.IRNode { return getIRNodes(ctx) } diff --git a/reputation/reputation_contract.go b/reputation/reputation_contract.go index dbfea7a..14c1bc2 100644 --- a/reputation/reputation_contract.go +++ b/reputation/reputation_contract.go @@ -1,6 +1,8 @@ package reputationcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neofs-contract/common" @@ -28,6 +30,28 @@ func init() { ctx = storage.GetContext() } +func Init(owner interop.Hash160) { + if !common.HasUpdateAccess(ctx) { + panic("only owner can reinitialize contract") + } + + storage.Put(ctx, common.OwnerKey, owner) + + runtime.Log("reputation contract initialized") +} + +func Migrate(script []byte, manifest []byte) bool { + if !common.HasUpdateAccess(ctx) { + runtime.Log("only owner can update contract") + return false + } + + management.Update(script, manifest) + runtime.Log("reputation contract updated") + + return true +} + func Put(manager, epoch, typ []byte, newTrustList [][]byte) bool { if !runtime.CheckWitness(manager) { panic("put: incorrect manager key")