package netmapcontract import ( "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/std" "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" ) type ( storageNode struct { info []byte } netmapNode struct { node storageNode state nodeState } nodeState int record struct { key []byte val []byte } ) const ( version = 1 netmapKey = "netmap" configuredKey = "initconfig" notaryDisabledKey = "notary" innerRingKey = "innerring" snapshot0Key = "snapshotCurrent" snapshot1Key = "snapshotPrevious" snapshotEpoch = "snapshotEpoch" containerContractKey = "containerScriptHash" balanceContractKey = "balanceScriptHash" cleanupEpochMethod = "newEpoch" ) const ( _ nodeState = iota onlineState offlineState ) var ( configPrefix = []byte("config") ) // _deploy function sets up initial list of inner ring public keys. func _deploy(data interface{}, isUpdate bool) { if isUpdate { return } args := data.([]interface{}) notaryDisabled := args[0].(bool) owner := args[1].(interop.Hash160) addrBalance := args[2].(interop.Hash160) addrContainer := args[3].(interop.Hash160) keys := args[4].([]interop.PublicKey) ctx := storage.GetContext() if !common.HasUpdateAccess(ctx) { panic("only owner can reinitialize contract") } if len(addrBalance) != 20 || len(addrContainer) != 20 { panic("init: incorrect length of contract script hash") } storage.Put(ctx, common.OwnerKey, owner) // epoch number is a little endian int, it doesn't need to be serialized storage.Put(ctx, snapshotEpoch, 0) // simplified: this used for const sysfee in AddPeer method common.SetSerialized(ctx, netmapKey, []netmapNode{}) common.SetSerialized(ctx, snapshot0Key, []netmapNode{}) common.SetSerialized(ctx, snapshot1Key, []netmapNode{}) storage.Put(ctx, balanceContractKey, addrBalance) storage.Put(ctx, containerContractKey, addrContainer) // initialize the way to collect signatures storage.Put(ctx, notaryDisabledKey, notaryDisabled) if notaryDisabled { var irList []common.IRNode for i := 0; i < len(keys); i++ { key := keys[i] irList = append(irList, common.IRNode{PublicKey: key}) } common.SetSerialized(ctx, innerRingKey, irList) common.InitVote(ctx) runtime.Log("netmap contract notary disabled") } runtime.Log("netmap contract initialized") } func Migrate(script []byte, manifest []byte, data interface{}) bool { ctx := storage.GetReadOnlyContext() if !common.HasUpdateAccess(ctx) { runtime.Log("only owner can update contract") return false } contract.Call(interop.Hash160(management.Hash), "update", contract.All, script, manifest, data) runtime.Log("netmap contract updated") return true } func InnerRingList() []common.IRNode { ctx := storage.GetReadOnlyContext() return getIRNodes(ctx) } func UpdateInnerRing(keys []interop.PublicKey) { ctx := storage.GetContext() notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) var ( // for invocation collection without notary alphabet []common.IRNode nodeKey []byte ) if notaryDisabled { alphabet = common.AlphabetNodes() nodeKey = common.InnerRingInvoker(alphabet) if len(nodeKey) == 0 { panic("updateInnerRing: this method must be invoked by alphabet nodes") } } else { multiaddr := common.AlphabetAddress() if !runtime.CheckWitness(multiaddr) { panic("updateInnerRing: this method must be invoked by alphabet nodes") } } var irList []common.IRNode for i := 0; i < len(keys); i++ { key := keys[i] irList = append(irList, common.IRNode{PublicKey: key}) } if notaryDisabled { threshold := len(alphabet)*2/3 + 1 id := keysID(keys, []byte("updateIR")) n := common.Vote(ctx, id, nodeKey) if n < threshold { return } common.RemoveVotes(ctx, id) } runtime.Log("updateInnerRing: inner ring list updated") common.SetSerialized(ctx, innerRingKey, irList) } func AddPeer(nodeInfo []byte) { ctx := storage.GetContext() notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) var ( // for invocation collection without notary alphabet []common.IRNode nodeKey []byte alphabetCall bool ) if notaryDisabled { alphabet = common.AlphabetNodes() nodeKey = common.InnerRingInvoker(alphabet) alphabetCall = len(nodeKey) != 0 } else { multiaddr := common.AlphabetAddress() alphabetCall = runtime.CheckWitness(multiaddr) } if !alphabetCall { publicKey := nodeInfo[2:35] // offset:2, len:33 if !runtime.CheckWitness(publicKey) { panic("addPeer: witness check failed") } runtime.Notify("AddPeer", nodeInfo) return } candidate := storageNode{ info: nodeInfo, } nm := addToNetmap(ctx, candidate) if notaryDisabled { threshold := len(alphabet)*2/3 + 1 rawCandidate := std.Serialize(candidate) id := crypto.Sha256(rawCandidate) n := common.Vote(ctx, id, nodeKey) if n < threshold { return } common.RemoveVotes(ctx, id) } if nm == nil { runtime.Log("addPeer: storage node already in the netmap") } else { common.SetSerialized(ctx, netmapKey, nm) runtime.Log("addPeer: add storage node to the network map") } } func UpdateState(state int, publicKey interop.PublicKey) { if len(publicKey) != 33 { panic("updateState: incorrect public key") } ctx := storage.GetContext() notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) var ( // for invocation collection without notary alphabet []common.IRNode nodeKey []byte alphabetCall bool ) if notaryDisabled { alphabet = common.AlphabetNodes() nodeKey = common.InnerRingInvoker(alphabet) alphabetCall = len(nodeKey) != 0 } else { multiaddr := common.AlphabetAddress() alphabetCall = runtime.CheckWitness(multiaddr) } if !alphabetCall { if !runtime.CheckWitness(publicKey) { panic("updateState: witness check failed") } runtime.Notify("UpdateState", state, publicKey) return } if notaryDisabled { threshold := len(alphabet)*2/3 + 1 id := common.InvokeID([]interface{}{state, publicKey}, []byte("update")) n := common.Vote(ctx, id, nodeKey) if n < threshold { return } common.RemoveVotes(ctx, id) } switch nodeState(state) { case offlineState: newNetmap := removeFromNetmap(ctx, publicKey) runtime.Log("updateState: remove storage node from the network map") common.SetSerialized(ctx, netmapKey, newNetmap) default: panic("updateState: unsupported state") } } func NewEpoch(epochNum int) { ctx := storage.GetContext() notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) var ( // for invocation collection without notary alphabet []common.IRNode nodeKey []byte ) if notaryDisabled { alphabet = common.AlphabetNodes() nodeKey = common.InnerRingInvoker(alphabet) if len(nodeKey) == 0 { panic("newEpoch: this method must be invoked by inner ring nodes") } } else { multiaddr := common.AlphabetAddress() if !runtime.CheckWitness(multiaddr) { panic("newEpoch: this method must be invoked by inner ring nodes") } } if notaryDisabled { threshold := len(alphabet)*2/3 + 1 id := common.InvokeID([]interface{}{epochNum}, []byte("epoch")) n := common.Vote(ctx, id, nodeKey) if n < threshold { return } common.RemoveVotes(ctx, id) } currentEpoch := storage.Get(ctx, snapshotEpoch).(int) if epochNum <= currentEpoch { panic("invalid epoch") // ignore invocations with invalid epoch } data0snapshot := getSnapshot(ctx, snapshot0Key) dataOnlineState := filterNetmap(ctx, onlineState) runtime.Log("newEpoch: process new epoch") // todo: check if provided epoch number is bigger than current storage.Put(ctx, snapshotEpoch, epochNum) // put actual snapshot into previous snapshot common.SetSerialized(ctx, snapshot1Key, data0snapshot) // put netmap into actual snapshot common.SetSerialized(ctx, snapshot0Key, dataOnlineState) // make clean up routines in other contracts cleanup(ctx, epochNum) runtime.Notify("NewEpoch", epochNum) } func Epoch() int { ctx := storage.GetReadOnlyContext() return storage.Get(ctx, snapshotEpoch).(int) } func Netmap() []storageNode { ctx := storage.GetReadOnlyContext() return getSnapshot(ctx, snapshot0Key) } // NetmapCandidates returns all node candidates for next epoch and their // status codes. func NetmapCandidates() []netmapNode { ctx := storage.GetReadOnlyContext() return getNetmapNodes(ctx) } func Snapshot(diff int) []storageNode { var key string switch diff { case 0: key = snapshot0Key case 1: key = snapshot1Key default: panic("snapshot: incorrect diff") } ctx := storage.GetReadOnlyContext() return getSnapshot(ctx, key) } func SnapshotByEpoch(epoch int) []storageNode { ctx := storage.GetReadOnlyContext() currentEpoch := storage.Get(ctx, snapshotEpoch).(int) return Snapshot(currentEpoch - epoch) } func Config(key []byte) interface{} { ctx := storage.GetReadOnlyContext() return getConfig(ctx, key) } func SetConfig(id, key, val []byte) { ctx := storage.GetContext() notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) var ( // for invocation collection without notary alphabet []common.IRNode nodeKey []byte ) if notaryDisabled { alphabet = common.AlphabetNodes() nodeKey = common.InnerRingInvoker(alphabet) if len(nodeKey) == 0 { panic("setConfig: invoked by non inner ring node") } } else { multiaddr := common.AlphabetAddress() if !runtime.CheckWitness(multiaddr) { panic("setConfig: invoked by non inner ring node") } } if notaryDisabled { threshold := len(alphabet)*2/3 + 1 n := common.Vote(ctx, id, nodeKey) if n < threshold { return } common.RemoveVotes(ctx, id) } setConfig(ctx, key, val) runtime.Log("setConfig: configuration has been updated") } func InitConfig(args [][]byte) { ctx := storage.GetContext() if storage.Get(ctx, configuredKey) != nil { panic("netmap: configuration already installed") } ln := len(args) if ln%2 != 0 { panic("initConfig: bad arguments") } for i := 0; i < ln/2; i++ { key := args[i*2] val := args[i*2+1] setConfig(ctx, key, val) } storage.Put(ctx, configuredKey, true) runtime.Log("netmap: config has been installed") } func ListConfig() []record { ctx := storage.GetReadOnlyContext() var config []record it := storage.Find(ctx, configPrefix, storage.None) for iterator.Next(it) { pair := iterator.Value(it).([]interface{}) key := pair[0].([]byte) val := pair[1].([]byte) r := record{key: key[len(configPrefix):], val: val} config = append(config, r) } return config } func Version() int { return version } func addToNetmap(ctx storage.Context, n storageNode) []netmapNode { var ( newNode = n.info newNodeKey = newNode[2:35] netmap = getNetmapNodes(ctx) node = netmapNode{ node: n, state: onlineState, } ) for i := range netmap { netmapNode := netmap[i].node.info netmapNodeKey := netmapNode[2:35] if common.BytesEqual(newNodeKey, netmapNodeKey) { return nil } } netmap = append(netmap, node) return netmap } func removeFromNetmap(ctx storage.Context, key interop.PublicKey) []netmapNode { var ( netmap = getNetmapNodes(ctx) newNetmap = []netmapNode{} ) for i := 0; i < len(netmap); i++ { item := netmap[i] node := item.node.info publicKey := node[2:35] // offset:2, len:33 if !common.BytesEqual(publicKey, key) { newNetmap = append(newNetmap, item) } } return newNetmap } func filterNetmap(ctx storage.Context, st nodeState) []storageNode { var ( netmap = getNetmapNodes(ctx) result = []storageNode{} ) for i := 0; i < len(netmap); i++ { item := netmap[i] if item.state == st { result = append(result, item.node) } } return result } func getNetmapNodes(ctx storage.Context) []netmapNode { data := storage.Get(ctx, netmapKey) if data != nil { return std.Deserialize(data.([]byte)).([]netmapNode) } return []netmapNode{} } func getSnapshot(ctx storage.Context, key string) []storageNode { data := storage.Get(ctx, key) if data != nil { return std.Deserialize(data.([]byte)).([]storageNode) } return []storageNode{} } func getConfig(ctx storage.Context, key interface{}) interface{} { postfix := key.([]byte) storageKey := append(configPrefix, postfix...) return storage.Get(ctx, storageKey) } func setConfig(ctx storage.Context, key, val interface{}) { postfix := key.([]byte) storageKey := append(configPrefix, postfix...) storage.Put(ctx, storageKey, val) } func cleanup(ctx storage.Context, epoch int) { balanceContractAddr := storage.Get(ctx, balanceContractKey).(interop.Hash160) contract.Call(balanceContractAddr, cleanupEpochMethod, contract.All, epoch) containerContractAddr := storage.Get(ctx, containerContractKey).(interop.Hash160) contract.Call(containerContractAddr, cleanupEpochMethod, contract.All, epoch) } func getIRNodes(ctx storage.Context) []common.IRNode { data := storage.Get(ctx, innerRingKey) if data != nil { return std.Deserialize(data.([]byte)).([]common.IRNode) } return []common.IRNode{} } func keysID(args []interop.PublicKey, prefix []byte) []byte { var ( result []byte ) result = append(result, prefix...) for i := range args { result = append(result, args[i]...) } return crypto.Sha256(result) }