forked from TrueCloudLab/frostfs-contract
[#18] Add sidechain contracts
Sidechain contracts include alphabet contracts for governance and audit, balance, container, neofsid, netmap, reputation contracts. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
parent
dab74aeec7
commit
bf391b57dd
22 changed files with 3279 additions and 0 deletions
54
alphabet/alphabet.go
Normal file
54
alphabet/alphabet.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
root = "alphabet"
|
||||||
|
templateName = "alphabet.tpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
glagolic := []string{
|
||||||
|
"Az", "Buky", "Vedi", "Glagoli", "Dobro", "Jest", "Zhivete",
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(path.Join(root, templateName))
|
||||||
|
die("can't read template file", err)
|
||||||
|
|
||||||
|
tmpl := template.Must(template.New("").Parse(string(data)))
|
||||||
|
|
||||||
|
for index, name := range glagolic {
|
||||||
|
lowercaseName := strings.ToLower(name)
|
||||||
|
|
||||||
|
if _, err := os.Stat(path.Join(root, lowercaseName)); os.IsNotExist(err) {
|
||||||
|
os.Mkdir(path.Join(root, lowercaseName), 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst, err := os.Create(path.Join(root, lowercaseName, lowercaseName+"_contract.go"))
|
||||||
|
die("can't create file", err)
|
||||||
|
|
||||||
|
err = tmpl.Execute(dst, map[string]interface{}{
|
||||||
|
"Name": name,
|
||||||
|
"Index": index,
|
||||||
|
})
|
||||||
|
die("can't generate code from template", err)
|
||||||
|
|
||||||
|
die("can't close generated file", dst.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func die(msg string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(msg+": %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
116
alphabet/alphabet.tpl
Normal file
116
alphabet/alphabet.tpl
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "{{ .Name }}"
|
||||||
|
index = {{ .Index }}
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
116
alphabet/az/az_contract.go
Normal file
116
alphabet/az/az_contract.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "Az"
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
116
alphabet/buky/buky_contract.go
Normal file
116
alphabet/buky/buky_contract.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "Buky"
|
||||||
|
index = 1
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
2
alphabet/config.yml
Normal file
2
alphabet/config.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
116
alphabet/dobro/dobro_contract.go
Normal file
116
alphabet/dobro/dobro_contract.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "Dobro"
|
||||||
|
index = 4
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
116
alphabet/glagoli/glagoli_contract.go
Normal file
116
alphabet/glagoli/glagoli_contract.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "Glagoli"
|
||||||
|
index = 3
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
116
alphabet/jest/jest_contract.go
Normal file
116
alphabet/jest/jest_contract.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "Jest"
|
||||||
|
index = 5
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
116
alphabet/vedi/vedi_contract.go
Normal file
116
alphabet/vedi/vedi_contract.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "Vedi"
|
||||||
|
index = 2
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
116
alphabet/zhivete/zhivete_contract.go
Normal file
116
alphabet/zhivete/zhivete_contract.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package alphabetcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// native gas token script hash
|
||||||
|
gasHash = "\xbc\xaf\x41\xd6\x84\xc7\xd4\xad\x6e\xe0\xd9\x9d\xa9\x70\x7b\x9d\x1f\x0c\x8e\x66"
|
||||||
|
|
||||||
|
// native neo token script hash
|
||||||
|
neoHash = "\x25\x05\x9e\xcb\x48\x78\xd3\xa8\x75\xf9\x1c\x51\xce\xde\xd3\x30\xd4\x57\x5f\xde"
|
||||||
|
|
||||||
|
name = "Zhivete"
|
||||||
|
index = 6
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 {
|
||||||
|
panic("incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
|
||||||
|
runtime.Log(name + " contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gas() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(gasHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neo() int {
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
return balance(neoHash, contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func balance(hash string, addr []byte) int {
|
||||||
|
balance := engine.AppCall([]byte(hash), "balanceOf", addr)
|
||||||
|
return balance.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func irList() []irNode {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
return engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPermission(ir []irNode) bool {
|
||||||
|
if len(ir) <= index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := ir[index]
|
||||||
|
return runtime.CheckWitness(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Emit() bool {
|
||||||
|
innerRingKeys := irList()
|
||||||
|
if !checkPermission(innerRingKeys) {
|
||||||
|
panic("invalid invoker")
|
||||||
|
}
|
||||||
|
|
||||||
|
contractHash := runtime.GetExecutingScriptHash()
|
||||||
|
neo := balance(neoHash, contractHash)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(neoHash), "transfer", contractHash, contractHash, neo)
|
||||||
|
|
||||||
|
gas := balance(gasHash, contractHash)
|
||||||
|
gasPerNode := gas * 7 / 8 / len(innerRingKeys)
|
||||||
|
|
||||||
|
if gasPerNode == 0 {
|
||||||
|
runtime.Log("no gas to emit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
node := innerRingKeys[i]
|
||||||
|
address := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
_ = engine.AppCall([]byte(gasHash), "transfer", contractHash, address, gasPerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("utility token has been emitted to inner ring nodes")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return name
|
||||||
|
}
|
256
audit/audit_contract.go
Normal file
256
audit/audit_contract.go
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
package auditcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckedNode struct {
|
||||||
|
Key []byte // 33 bytes
|
||||||
|
Pair int // 2 bytes
|
||||||
|
Reward int // ? up to 32 byte
|
||||||
|
}
|
||||||
|
|
||||||
|
AuditResult struct {
|
||||||
|
InnerRingNode []byte // 33 bytes
|
||||||
|
Epoch int // 8 bytes
|
||||||
|
ContainerID []byte // 32 bytes
|
||||||
|
StorageGroupID []byte // 16 bytes
|
||||||
|
PoR bool // 1 byte
|
||||||
|
PDP bool // 1 byte
|
||||||
|
// --- 91 bytes -- //
|
||||||
|
// --- 2 more bytes to size of the []CheckedNode //
|
||||||
|
Nodes []CheckedNode // <= 67 bytes per node
|
||||||
|
// about 1400 nodes may be presented in container
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
// 1E-8 GAS in precision of balance container.
|
||||||
|
// This value may be calculated in runtime based on decimal value of
|
||||||
|
// balance contract. We can also provide methods to change fee
|
||||||
|
// in runtime.
|
||||||
|
auditFee = 1 * 100_000_000
|
||||||
|
|
||||||
|
ownerIDLength = 25
|
||||||
|
|
||||||
|
journalKey = "auditJournal"
|
||||||
|
balanceContractKey = "balanceScriptHash"
|
||||||
|
containerContractKey = "containerScriptHash"
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
auditFeeTransferMsg = []byte("audit execution fee")
|
||||||
|
auditRewardTransferMsg = []byte("data audit reward")
|
||||||
|
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap, addrBalance, addrContainer []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil &&
|
||||||
|
storage.Get(ctx, balanceContractKey) != nil &&
|
||||||
|
storage.Get(ctx, containerContractKey) != nil {
|
||||||
|
panic("init: contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 || len(addrBalance) != 20 || len(addrContainer) != 20 {
|
||||||
|
panic("init: incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
storage.Put(ctx, balanceContractKey, addrBalance)
|
||||||
|
storage.Put(ctx, containerContractKey, addrContainer)
|
||||||
|
|
||||||
|
setSerialized(ctx, journalKey, []AuditResult{})
|
||||||
|
|
||||||
|
runtime.Log("audit contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Put(rawAuditResult []byte) bool {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
|
||||||
|
auditResult, err := newAuditResult(rawAuditResult)
|
||||||
|
if err {
|
||||||
|
panic("put: can't parse audit result")
|
||||||
|
}
|
||||||
|
|
||||||
|
var presented = false
|
||||||
|
|
||||||
|
for i := range innerRing {
|
||||||
|
ir := innerRing[i]
|
||||||
|
if bytesEqual(ir.key, auditResult.InnerRingNode) {
|
||||||
|
presented = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runtime.CheckWitness(auditResult.InnerRingNode) || !presented {
|
||||||
|
panic("put: access denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: limit size of the audit journal:
|
||||||
|
// history will be stored in chain (args or notifies)
|
||||||
|
// contract storage will be used as a cache if needed
|
||||||
|
journal := getAuditResult(ctx)
|
||||||
|
journal = append(journal, auditResult)
|
||||||
|
|
||||||
|
setSerialized(ctx, journalKey, journal)
|
||||||
|
|
||||||
|
if auditResult.PDP && auditResult.PoR {
|
||||||
|
// find who is the ownerID
|
||||||
|
containerContract := storage.Get(ctx, containerContractKey).([]byte)
|
||||||
|
|
||||||
|
// todo: implement easy way to get owner from the container id
|
||||||
|
ownerID := engine.AppCall(containerContract, "owner", auditResult.ContainerID).([]byte)
|
||||||
|
if len(ownerID) != ownerIDLength {
|
||||||
|
runtime.Log("put: can't get owner id of the container")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerScriptHash := walletToScripHash(ownerID)
|
||||||
|
|
||||||
|
// transfer fee to the inner ring node
|
||||||
|
balanceContract := storage.Get(ctx, balanceContractKey).([]byte)
|
||||||
|
irScriptHash := contract.CreateStandardAccount(auditResult.InnerRingNode)
|
||||||
|
|
||||||
|
tx := engine.AppCall(balanceContract, "transferX",
|
||||||
|
ownerScriptHash,
|
||||||
|
irScriptHash,
|
||||||
|
auditFee,
|
||||||
|
auditFeeTransferMsg, // todo: add epoch, container and storage group info
|
||||||
|
)
|
||||||
|
if !tx.(bool) {
|
||||||
|
panic("put: can't transfer inner ring fee")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(auditResult.Nodes); i++ {
|
||||||
|
node := auditResult.Nodes[i]
|
||||||
|
nodeScriptHash := contract.CreateStandardAccount(node.Key)
|
||||||
|
|
||||||
|
tx := engine.AppCall(balanceContract, "transferX",
|
||||||
|
ownerScriptHash,
|
||||||
|
nodeScriptHash,
|
||||||
|
node.Reward,
|
||||||
|
auditRewardTransferMsg, // todo: add epoch, container and storage group info
|
||||||
|
)
|
||||||
|
if !tx.(bool) {
|
||||||
|
runtime.Log("put: can't transfer storage payment")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version() int {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAuditResult(data []byte) (AuditResult, bool) {
|
||||||
|
var (
|
||||||
|
tmp interface{}
|
||||||
|
ln = len(data)
|
||||||
|
result = AuditResult{
|
||||||
|
InnerRingNode: nil, // neo-go#949
|
||||||
|
ContainerID: nil,
|
||||||
|
StorageGroupID: nil,
|
||||||
|
Nodes: []CheckedNode{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(data) < 91 { // all required headers
|
||||||
|
runtime.Log("newAuditResult: can't parse audit result header")
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
|
||||||
|
result.InnerRingNode = data[0:33]
|
||||||
|
|
||||||
|
epoch := data[33:41]
|
||||||
|
tmp = epoch
|
||||||
|
result.Epoch = tmp.(int)
|
||||||
|
|
||||||
|
result.ContainerID = data[41:73]
|
||||||
|
result.StorageGroupID = data[73:89]
|
||||||
|
result.PoR = util.Equals(data[90], 0x01)
|
||||||
|
result.PDP = util.Equals(data[91], 0x01)
|
||||||
|
|
||||||
|
// if there are nodes, that were checked
|
||||||
|
if len(data) > 93 {
|
||||||
|
rawCounter := data[91:93]
|
||||||
|
tmp = rawCounter
|
||||||
|
counter := tmp.(int)
|
||||||
|
|
||||||
|
ptr := 93
|
||||||
|
|
||||||
|
for i := 0; i < counter; i++ {
|
||||||
|
if ptr+33+2+32 > ln {
|
||||||
|
runtime.Log("newAuditResult: broken node")
|
||||||
|
return result, false
|
||||||
|
}
|
||||||
|
|
||||||
|
node := CheckedNode{
|
||||||
|
Key: nil, // neo-go#949
|
||||||
|
}
|
||||||
|
node.Key = data[ptr : ptr+33]
|
||||||
|
|
||||||
|
pair := data[ptr+33 : ptr+35]
|
||||||
|
tmp = pair
|
||||||
|
node.Pair = tmp.(int)
|
||||||
|
|
||||||
|
reward := data[ptr+35 : ptr+67]
|
||||||
|
tmp = reward
|
||||||
|
node.Reward = tmp.(int)
|
||||||
|
|
||||||
|
result.Nodes = append(result.Nodes, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuditResult(ctx storage.Context) []AuditResult {
|
||||||
|
data := storage.Get(ctx, journalKey)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]AuditResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []AuditResult{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
||||||
|
data := binary.Serialize(value)
|
||||||
|
storage.Put(ctx, key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func walletToScripHash(wallet []byte) []byte {
|
||||||
|
return wallet[1 : len(wallet)-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
// neo-go#1176
|
||||||
|
func bytesEqual(a []byte, b []byte) bool {
|
||||||
|
return util.Equals(string(a), string(b))
|
||||||
|
}
|
2
audit/config.yml
Normal file
2
audit/config.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
537
balance/balance_contract.go
Normal file
537
balance/balance_contract.go
Normal file
|
@ -0,0 +1,537 @@
|
||||||
|
package balancecontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/crypto"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
ballot struct {
|
||||||
|
id []byte // id of the voting decision
|
||||||
|
n [][]byte // already voted inner ring nodes
|
||||||
|
block int // block with the last vote
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token holds all token info.
|
||||||
|
Token struct {
|
||||||
|
// Token name
|
||||||
|
Name string
|
||||||
|
// Ticker symbol
|
||||||
|
Symbol string
|
||||||
|
// Amount of decimals
|
||||||
|
Decimals int
|
||||||
|
// Storage key for circulation value
|
||||||
|
CirculationKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
Account struct {
|
||||||
|
// Active balance
|
||||||
|
Balance int
|
||||||
|
// Until valid for lock accounts
|
||||||
|
Until int
|
||||||
|
// Parent field used in lock accounts, used to return assets back if
|
||||||
|
// account wasn't burnt.
|
||||||
|
Parent []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
name = "NeoFS Balance"
|
||||||
|
symbol = "NEOFS"
|
||||||
|
decimals = 12
|
||||||
|
circulation = "MainnetGAS"
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
voteKey = "ballots"
|
||||||
|
blockDiff = 20 // change base on performance evaluation
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
containerContractKey = "containerScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lockTransferMsg = []byte("lock assets to withdraw")
|
||||||
|
unlockTransferMsg = []byte("asset lock expired")
|
||||||
|
|
||||||
|
ctx storage.Context
|
||||||
|
token Token
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateToken initializes the Token Interface for the Smart Contract to operate with.
|
||||||
|
func CreateToken() Token {
|
||||||
|
return Token{
|
||||||
|
Name: name,
|
||||||
|
Symbol: symbol,
|
||||||
|
Decimals: decimals,
|
||||||
|
CirculationKey: circulation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
token = CreateToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap, addrContainer []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("init: contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 || len(addrContainer) != 20 {
|
||||||
|
panic("init: incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
storage.Put(ctx, containerContractKey, addrContainer)
|
||||||
|
|
||||||
|
runtime.Log("balance contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name() string {
|
||||||
|
return token.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func Symbol() string {
|
||||||
|
return token.Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decimals() int {
|
||||||
|
return token.Decimals
|
||||||
|
}
|
||||||
|
|
||||||
|
func TotalSupply() int {
|
||||||
|
return token.getSupply(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BalanceOf(holder []byte) interface{} {
|
||||||
|
return token.balanceOf(ctx, holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Transfer(from, to []byte, amount int) bool {
|
||||||
|
return token.transfer(ctx, from, to, amount, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TransferX(from, to []byte, amount int, details []byte) bool {
|
||||||
|
var (
|
||||||
|
n int // number of votes for inner ring invoke
|
||||||
|
hashTxID []byte // ballot key of the inner ring invocation
|
||||||
|
)
|
||||||
|
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("transferX: this method must be invoked from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
fromKnownContract := fromKnownContract(runtime.GetCallingScriptHash())
|
||||||
|
if fromKnownContract {
|
||||||
|
n = threshold
|
||||||
|
runtime.Log("transferX: processed indirect invoke")
|
||||||
|
} else {
|
||||||
|
hashTxID = invokeID([]interface{}{from, to, amount}, []byte("transfer"))
|
||||||
|
n = vote(ctx, hashTxID, irKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, hashTxID)
|
||||||
|
|
||||||
|
result := token.transfer(ctx, from, to, amount, true, details)
|
||||||
|
if result {
|
||||||
|
runtime.Log("transferX: success")
|
||||||
|
} else {
|
||||||
|
// consider panic there
|
||||||
|
runtime.Log("transferX: fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lock(txID, from, to []byte, amount, until int) bool {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("lock: this method must be invoked from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashTxID := invokeID([]interface{}{txID, from, to, amount, until}, []byte("lock"))
|
||||||
|
|
||||||
|
n := vote(ctx, hashTxID, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, hashTxID)
|
||||||
|
|
||||||
|
lockAccount := Account{
|
||||||
|
Balance: 0,
|
||||||
|
Until: until,
|
||||||
|
Parent: from,
|
||||||
|
}
|
||||||
|
setSerialized(ctx, to, lockAccount)
|
||||||
|
|
||||||
|
result := token.transfer(ctx, from, to, amount, true, lockTransferMsg)
|
||||||
|
if !result {
|
||||||
|
// consider using `return false` to remove votes
|
||||||
|
panic("lock: can't lock funds")
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("lock: created lock account")
|
||||||
|
runtime.Notify("Lock", txID, from, to, amount, until)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("lock: processed invoke from inner ring")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEpoch(epochNum int) bool {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("epochNum: this method must be invoked from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
epochID := invokeID([]interface{}{epochNum}, []byte("epoch"))
|
||||||
|
|
||||||
|
n := vote(ctx, epochID, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, epochID)
|
||||||
|
it := storage.Find(ctx, []byte{})
|
||||||
|
for iterator.Next(it) {
|
||||||
|
addr := iterator.Key(it).([]byte)
|
||||||
|
if len(addr) != 20 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := getAccount(ctx, addr)
|
||||||
|
if acc.Until == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if epochNum >= acc.Until {
|
||||||
|
// return assets back to the parent
|
||||||
|
token.transfer(ctx, addr, acc.Parent, acc.Balance, true, unlockTransferMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log("newEpoch: processed invoke from inner ring")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mint(to []byte, amount int, details []byte) bool {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("burn: this method must be invoked from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
mintID := invokeID([]interface{}{to, amount, details}, []byte("mint"))
|
||||||
|
|
||||||
|
n := vote(ctx, mintID, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, mintID)
|
||||||
|
|
||||||
|
ok := token.transfer(ctx, nil, to, amount, true, details)
|
||||||
|
if !ok {
|
||||||
|
panic("mint: can't transfer assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
supply := token.getSupply(ctx)
|
||||||
|
supply = supply + amount
|
||||||
|
storage.Put(ctx, token.CirculationKey, supply)
|
||||||
|
runtime.Log("mint: assets were minted")
|
||||||
|
runtime.Notify("Mint", to, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Burn(from []byte, amount int, details []byte) bool {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("burn: this method must be invoked from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
burnID := invokeID([]interface{}{from, amount, details}, []byte("burn"))
|
||||||
|
|
||||||
|
n := vote(ctx, burnID, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, burnID)
|
||||||
|
|
||||||
|
ok := token.transfer(ctx, from, nil, amount, true, details)
|
||||||
|
if !ok {
|
||||||
|
panic("burn: can't transfer assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
supply := token.getSupply(ctx)
|
||||||
|
if supply < amount {
|
||||||
|
panic("panic, negative supply after burn")
|
||||||
|
}
|
||||||
|
|
||||||
|
supply = supply - amount
|
||||||
|
storage.Put(ctx, token.CirculationKey, supply)
|
||||||
|
runtime.Log("burn: assets were burned")
|
||||||
|
runtime.Notify("Burn", from, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version() int {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSupply gets the token totalSupply value from VM storage.
|
||||||
|
func (t Token) getSupply(ctx storage.Context) int {
|
||||||
|
supply := storage.Get(ctx, t.CirculationKey)
|
||||||
|
if supply != nil {
|
||||||
|
return supply.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceOf gets the token balance of a specific address.
|
||||||
|
func (t Token) balanceOf(ctx storage.Context, holder []byte) interface{} {
|
||||||
|
acc := getAccount(ctx, holder)
|
||||||
|
|
||||||
|
return acc.Balance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) transfer(ctx storage.Context, from []byte, to []byte, amount int, innerRing bool, details []byte) bool {
|
||||||
|
amountFrom, ok := t.canTransfer(ctx, from, to, amount, innerRing)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(from) == 20 {
|
||||||
|
if amountFrom.Balance == amount {
|
||||||
|
storage.Delete(ctx, from)
|
||||||
|
} else {
|
||||||
|
amountFrom.Balance = amountFrom.Balance - amount // neo-go#953
|
||||||
|
setSerialized(ctx, from, amountFrom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(to) == 20 {
|
||||||
|
amountTo := getAccount(ctx, to)
|
||||||
|
amountTo.Balance = amountTo.Balance + amount // neo-go#953
|
||||||
|
setSerialized(ctx, to, amountTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Notify("transfer", from, to, amount)
|
||||||
|
runtime.Notify("transferX", from, to, amount, details)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// canTransfer returns the amount it can transfer.
|
||||||
|
func (t Token) canTransfer(ctx storage.Context, from []byte, to []byte, amount int, innerRing bool) (Account, bool) {
|
||||||
|
var (
|
||||||
|
emptyAcc = Account{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if !innerRing {
|
||||||
|
if len(to) != 20 || !isUsableAddress(from) {
|
||||||
|
runtime.Log("transfer: bad script hashes")
|
||||||
|
return emptyAcc, false
|
||||||
|
}
|
||||||
|
} else if len(from) == 0 {
|
||||||
|
return emptyAcc, true
|
||||||
|
}
|
||||||
|
|
||||||
|
amountFrom := getAccount(ctx, from)
|
||||||
|
if amountFrom.Balance < amount {
|
||||||
|
runtime.Log("transfer: not enough assets")
|
||||||
|
return emptyAcc, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// return amountFrom value back to transfer, reduces extra Get
|
||||||
|
return amountFrom, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUsableAddress checks if the sender is either the correct NEO address or SC address.
|
||||||
|
func isUsableAddress(addr []byte) bool {
|
||||||
|
if len(addr) == 20 {
|
||||||
|
if runtime.CheckWitness(addr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a smart contract is calling script hash
|
||||||
|
callingScriptHash := runtime.GetCallingScriptHash()
|
||||||
|
if bytesEqual(callingScriptHash, addr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerRingInvoker(ir []irNode) []byte {
|
||||||
|
for i := 0; i < len(ir); i++ {
|
||||||
|
node := ir[i]
|
||||||
|
if runtime.CheckWitness(node.key) {
|
||||||
|
return node.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vote(ctx storage.Context, id, from []byte) int {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
found = -1
|
||||||
|
blockHeight = blockchain.GetHeight()
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if bytesEqual(cnd.id, id) {
|
||||||
|
voters := cnd.n
|
||||||
|
|
||||||
|
for j := range voters {
|
||||||
|
if bytesEqual(voters[j], from) {
|
||||||
|
return len(voters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voters = append(voters, from)
|
||||||
|
cnd = ballot{id: id, n: voters, block: blockHeight}
|
||||||
|
found = len(voters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not add old ballots, they are invalid
|
||||||
|
if blockHeight-cnd.block <= blockDiff {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found < 0 {
|
||||||
|
voters := [][]byte{from}
|
||||||
|
newCandidates = append(newCandidates, ballot{
|
||||||
|
id: id,
|
||||||
|
n: voters,
|
||||||
|
block: blockHeight})
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeVotes(ctx storage.Context, id []byte) {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if !bytesEqual(cnd.id, id) {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBallots(ctx storage.Context) []ballot {
|
||||||
|
data := storage.Get(ctx, voteKey)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]ballot)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ballot{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
||||||
|
data := binary.Serialize(value)
|
||||||
|
storage.Put(ctx, key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccount(ctx storage.Context, key interface{}) Account {
|
||||||
|
data := storage.Get(ctx, key)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).(Account)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Account{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeID(args []interface{}, prefix []byte) []byte {
|
||||||
|
for i := range args {
|
||||||
|
arg := args[i].([]byte)
|
||||||
|
prefix = append(prefix, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.SHA256(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// neo-go#1176
|
||||||
|
func bytesEqual(a []byte, b []byte) bool {
|
||||||
|
return util.Equals(string(a), string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if invocation made from known container or audit contracts.
|
||||||
|
This is necessary because calls from these contracts require to do transfer
|
||||||
|
without signature collection (1 invoke transfer).
|
||||||
|
|
||||||
|
IR1, IR2, IR3, IR4 -(4 invokes)-> [ Container Contract ] -(1 invoke)-> [ Balance Contract ]
|
||||||
|
|
||||||
|
We can do 1 invoke transfer if:
|
||||||
|
- invoke happened from inner ring node,
|
||||||
|
- it is indirect invocation from other smart-contract.
|
||||||
|
|
||||||
|
However there is a possible attack, when malicious inner ring node creates
|
||||||
|
malicious smart-contract in morph chain to do inderect call.
|
||||||
|
|
||||||
|
MaliciousIR -(1 invoke)-> [ Malicious Contract ] -(1 invoke) -> [ Balance Contract ]
|
||||||
|
|
||||||
|
To prevent that, we have to allow 1 invoke transfer from authorised well known
|
||||||
|
smart-contracts, that will be set up at `Init` method.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func fromKnownContract(caller []byte) bool {
|
||||||
|
containerContractAddr := storage.Get(ctx, containerContractKey).([]byte)
|
||||||
|
if bytesEqual(caller, containerContractAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
45
balance/config.yml
Normal file
45
balance/config.yml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
events:
|
||||||
|
- name: Lock
|
||||||
|
parameters:
|
||||||
|
- name: txID
|
||||||
|
type: ByteString
|
||||||
|
- name: from
|
||||||
|
type: ByteString
|
||||||
|
- name: to
|
||||||
|
type: ByteString
|
||||||
|
- name: amount
|
||||||
|
type: Integer
|
||||||
|
- name: until
|
||||||
|
type: Integer
|
||||||
|
- name: transfer
|
||||||
|
parameters:
|
||||||
|
- name: from
|
||||||
|
type: ByteString
|
||||||
|
- name: to
|
||||||
|
type: ByteString
|
||||||
|
- name: amount
|
||||||
|
type: Integer
|
||||||
|
- name: transferX
|
||||||
|
parameters:
|
||||||
|
- name: from
|
||||||
|
type: ByteString
|
||||||
|
- name: to
|
||||||
|
type: ByteString
|
||||||
|
- name: amount
|
||||||
|
type: Integer
|
||||||
|
- name: details
|
||||||
|
type: ByteString
|
||||||
|
- name: Mint
|
||||||
|
parameters:
|
||||||
|
- name: from
|
||||||
|
type: ByteString
|
||||||
|
- name: amount
|
||||||
|
type: Integer
|
||||||
|
- name: Burn
|
||||||
|
parameters:
|
||||||
|
- name: to
|
||||||
|
type: ByteString
|
||||||
|
- name: amount
|
||||||
|
type: Integer
|
17
container/config.yml
Normal file
17
container/config.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
events:
|
||||||
|
- name: containerPut
|
||||||
|
parameters:
|
||||||
|
- name: container
|
||||||
|
type: ByteString
|
||||||
|
- name: signature
|
||||||
|
type: ByteString
|
||||||
|
- name: publicKey
|
||||||
|
type: ByteString
|
||||||
|
- name: containerDelete
|
||||||
|
parameters:
|
||||||
|
- name: containerID
|
||||||
|
type: ByteString
|
||||||
|
- name: signature
|
||||||
|
type: ByteString
|
471
container/container_contract.go
Normal file
471
container/container_contract.go
Normal file
|
@ -0,0 +1,471 @@
|
||||||
|
package containercontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
|
||||||
|
"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/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
ballot struct {
|
||||||
|
id []byte // id of the voting decision
|
||||||
|
n [][]byte // already voted inner ring nodes
|
||||||
|
block int // block with the last vote
|
||||||
|
}
|
||||||
|
|
||||||
|
extendedACL struct {
|
||||||
|
val []byte
|
||||||
|
sig []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = 1
|
||||||
|
voteKey = "ballots"
|
||||||
|
ownersKey = "ownersList"
|
||||||
|
blockDiff = 20 // change base on performance evaluation
|
||||||
|
|
||||||
|
neofsIDContractKey = "identityScriptHash"
|
||||||
|
balanceContractKey = "balanceScriptHash"
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
containerFeeKey = "ContainerFee"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
containerFeeTransferMsg = []byte("container creation fee")
|
||||||
|
eACLPrefix = []byte("eACL")
|
||||||
|
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 || len(addrBalance) != 20 || len(addrID) != 20 {
|
||||||
|
panic("init: incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
storage.Put(ctx, balanceContractKey, addrBalance)
|
||||||
|
storage.Put(ctx, neofsIDContractKey, addrID)
|
||||||
|
|
||||||
|
runtime.Log("container contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Put(container, signature, publicKey []byte) bool {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
offset := int(container[1])
|
||||||
|
offset = 2 + offset + 4 // version prefix + version size + owner prefix
|
||||||
|
ownerID := container[offset : offset+25] // offset + size of owner
|
||||||
|
containerID := crypto.SHA256(container)
|
||||||
|
neofsIDContractAddr := storage.Get(ctx, neofsIDContractKey).([]byte)
|
||||||
|
|
||||||
|
// If invoked from storage node, ignore it.
|
||||||
|
// Inner ring will find tx, validate it and send it again.
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
// check provided key
|
||||||
|
if !isSignedByOwnerKey(container, signature, ownerID, publicKey) {
|
||||||
|
// check keys from NeoFSID
|
||||||
|
keys := engine.AppCall(neofsIDContractAddr, "key", ownerID).([][]byte)
|
||||||
|
if !verifySignature(container, signature, keys) {
|
||||||
|
panic("put: invalid owner signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Notify("containerPut", container, signature, publicKey)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
from := walletToScripHash(ownerID)
|
||||||
|
balanceContractAddr := storage.Get(ctx, balanceContractKey).([]byte)
|
||||||
|
containerFee := engine.AppCall(netmapContractAddr, "config", containerFeeKey).(int)
|
||||||
|
hashCandidate := invokeID([]interface{}{container, signature, publicKey}, []byte("put"))
|
||||||
|
|
||||||
|
n := vote(ctx, hashCandidate, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, hashCandidate)
|
||||||
|
// todo: check if new container with unique container id
|
||||||
|
|
||||||
|
for i := 0; i < len(innerRing); i++ {
|
||||||
|
node := innerRing[i]
|
||||||
|
to := contract.CreateStandardAccount(node.key)
|
||||||
|
|
||||||
|
tx := engine.AppCall(balanceContractAddr, "transferX",
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
containerFee,
|
||||||
|
containerFeeTransferMsg, // consider add container id to the message
|
||||||
|
)
|
||||||
|
if !tx.(bool) {
|
||||||
|
// todo: consider using `return false` to remove votes
|
||||||
|
panic("put: can't transfer assets for container creation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addContainer(ctx, containerID, ownerID, container)
|
||||||
|
// try to remove underscore at v0.92.0
|
||||||
|
_ = engine.AppCall(neofsIDContractAddr, "addKey", ownerID, [][]byte{publicKey})
|
||||||
|
|
||||||
|
runtime.Log("put: added new container")
|
||||||
|
} else {
|
||||||
|
runtime.Log("put: processed invoke from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(containerID, signature []byte) bool {
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
ownerID := getOwnerByID(ctx, containerID)
|
||||||
|
if len(ownerID) == 0 {
|
||||||
|
panic("delete: container does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If invoked from storage node, ignore it.
|
||||||
|
// Inner ring will find tx, validate it and send it again.
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
// check provided key
|
||||||
|
neofsIDContractAddr := storage.Get(ctx, neofsIDContractKey).([]byte)
|
||||||
|
keys := engine.AppCall(neofsIDContractAddr, "key", ownerID).([][]byte)
|
||||||
|
|
||||||
|
if !verifySignature(containerID, signature, keys) {
|
||||||
|
panic("delete: invalid owner signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Notify("containerDelete", containerID, signature)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
hashCandidate := invokeID([]interface{}{containerID, signature}, []byte("delete"))
|
||||||
|
|
||||||
|
n := vote(ctx, hashCandidate, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, hashCandidate)
|
||||||
|
removeContainer(ctx, containerID, ownerID)
|
||||||
|
runtime.Log("delete: remove container")
|
||||||
|
} else {
|
||||||
|
runtime.Log("delete: processed invoke from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(containerID []byte) []byte {
|
||||||
|
return storage.Get(ctx, containerID).([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Owner(containerID []byte) []byte {
|
||||||
|
return getOwnerByID(ctx, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func List(owner []byte) [][]byte {
|
||||||
|
var list [][]byte
|
||||||
|
|
||||||
|
owners := getList(ctx, ownersKey)
|
||||||
|
for i := 0; i < len(owners); i++ {
|
||||||
|
ownerID := owners[i]
|
||||||
|
if len(owner) != 0 && !bytesEqual(owner, ownerID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
containers := getList(ctx, ownerID)
|
||||||
|
|
||||||
|
for j := 0; j < len(containers); j++ {
|
||||||
|
container := containers[j]
|
||||||
|
list = append(list, container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetEACL(eACL, signature []byte) bool {
|
||||||
|
// get container ID
|
||||||
|
offset := int(eACL[1])
|
||||||
|
offset = 2 + offset + 4
|
||||||
|
containerID := eACL[offset : offset+32]
|
||||||
|
|
||||||
|
ownerID := getOwnerByID(ctx, containerID)
|
||||||
|
if len(ownerID) == 0 {
|
||||||
|
panic("setEACL: container does not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
neofsIDContractAddr := storage.Get(ctx, neofsIDContractKey).([]byte)
|
||||||
|
keys := engine.AppCall(neofsIDContractAddr, "key", ownerID).([][]byte)
|
||||||
|
|
||||||
|
if !verifySignature(eACL, signature, keys) {
|
||||||
|
panic("setEACL: invalid eACL signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := extendedACL{
|
||||||
|
val: eACL,
|
||||||
|
sig: signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
key := append(eACLPrefix, containerID...)
|
||||||
|
setSerialized(ctx, key, rule)
|
||||||
|
|
||||||
|
runtime.Log("setEACL: success")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func EACL(containerID []byte) extendedACL {
|
||||||
|
return getEACL(ctx, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version() int {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func addContainer(ctx storage.Context, id []byte, owner []byte, container []byte) {
|
||||||
|
addOrAppend(ctx, ownersKey, owner)
|
||||||
|
addOrAppend(ctx, owner, id)
|
||||||
|
storage.Put(ctx, id, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeContainer(ctx storage.Context, id []byte, owner []byte) {
|
||||||
|
n := remove(ctx, owner, id)
|
||||||
|
|
||||||
|
// if it was last container, remove owner from the list of owners
|
||||||
|
if n == 0 {
|
||||||
|
_ = remove(ctx, ownersKey, owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addOrAppend(ctx storage.Context, key interface{}, value []byte) {
|
||||||
|
list := getList(ctx, key)
|
||||||
|
for i := 0; i < len(list); i++ {
|
||||||
|
if bytesEqual(list[i], value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(list) == 0 {
|
||||||
|
list = [][]byte{value}
|
||||||
|
} else {
|
||||||
|
list = append(list, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, key, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove returns amount of left elements in the list
|
||||||
|
func remove(ctx storage.Context, key interface{}, value []byte) int {
|
||||||
|
var (
|
||||||
|
list = getList(ctx, key)
|
||||||
|
newList = [][]byte{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(list); i++ {
|
||||||
|
if !bytesEqual(list[i], value) {
|
||||||
|
newList = append(newList, list[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ln := len(newList)
|
||||||
|
if ln == 0 {
|
||||||
|
storage.Delete(ctx, key)
|
||||||
|
} else {
|
||||||
|
setSerialized(ctx, key, newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ln
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerRingInvoker(ir []irNode) []byte {
|
||||||
|
for i := 0; i < len(ir); i++ {
|
||||||
|
node := ir[i]
|
||||||
|
if runtime.CheckWitness(node.key) {
|
||||||
|
return node.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vote(ctx storage.Context, id, from []byte) int {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
found = -1
|
||||||
|
blockHeight = blockchain.GetHeight()
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if bytesEqual(cnd.id, id) {
|
||||||
|
voters := cnd.n
|
||||||
|
|
||||||
|
for j := range voters {
|
||||||
|
if bytesEqual(voters[j], from) {
|
||||||
|
return len(voters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voters = append(voters, from)
|
||||||
|
cnd = ballot{id: id, n: voters, block: blockHeight}
|
||||||
|
found = len(voters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not add old ballots, they are invalid
|
||||||
|
if blockHeight-cnd.block <= blockDiff {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found < 0 {
|
||||||
|
voters := [][]byte{from}
|
||||||
|
newCandidates = append(newCandidates, ballot{
|
||||||
|
id: id,
|
||||||
|
n: voters,
|
||||||
|
block: blockHeight})
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeVotes(ctx storage.Context, id []byte) {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if !bytesEqual(cnd.id, id) {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getList(ctx storage.Context, key interface{}) [][]byte {
|
||||||
|
data := storage.Get(ctx, key)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([][]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [][]byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBallots(ctx storage.Context) []ballot {
|
||||||
|
data := storage.Get(ctx, voteKey)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]ballot)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ballot{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEACL(ctx storage.Context, cid []byte) extendedACL {
|
||||||
|
key := append(eACLPrefix, cid...)
|
||||||
|
data := storage.Get(ctx, key)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).(extendedACL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extendedACL{val: []byte{}, sig: []byte{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSerialized(ctx storage.Context, key, value interface{}) {
|
||||||
|
data := binary.Serialize(value)
|
||||||
|
storage.Put(ctx, key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func walletToScripHash(wallet []byte) []byte {
|
||||||
|
return wallet[1 : len(wallet)-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifySignature(msg, sig []byte, keys [][]byte) bool {
|
||||||
|
for i := range keys {
|
||||||
|
key := keys[i]
|
||||||
|
if crypto.ECDsaSecp256r1Verify(msg, key, sig) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeID(args []interface{}, prefix []byte) []byte {
|
||||||
|
for i := range args {
|
||||||
|
arg := args[i].([]byte)
|
||||||
|
prefix = append(prefix, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.SHA256(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOwnerByID(ctx storage.Context, id []byte) []byte {
|
||||||
|
owners := getList(ctx, ownersKey)
|
||||||
|
for i := 0; i < len(owners); i++ {
|
||||||
|
ownerID := owners[i]
|
||||||
|
containers := getList(ctx, ownerID)
|
||||||
|
|
||||||
|
for j := 0; j < len(containers); j++ {
|
||||||
|
container := containers[j]
|
||||||
|
if bytesEqual(container, id) {
|
||||||
|
return ownerID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// neo-go#1176
|
||||||
|
func bytesEqual(a []byte, b []byte) bool {
|
||||||
|
return util.Equals(string(a), string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSignedByOwnerKey(msg, sig, owner, key []byte) bool {
|
||||||
|
if !isOwnerFromKey(owner, key) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.ECDsaSecp256r1Verify(msg, key, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOwnerFromKey(owner []byte, key []byte) bool {
|
||||||
|
ownerSH := walletToScripHash(owner)
|
||||||
|
keySH := contract.CreateStandardAccount(key)
|
||||||
|
|
||||||
|
return bytesEqual(ownerSH, keySH)
|
||||||
|
}
|
2
neofsid/config.yml
Normal file
2
neofsid/config.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
314
neofsid/neofsid_contract.go
Normal file
314
neofsid/neofsid_contract.go
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
package neofsidcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/crypto"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
ballot struct {
|
||||||
|
id []byte // id of the voting decision
|
||||||
|
n [][]byte // already voted inner ring nodes
|
||||||
|
block int // block with the last vote
|
||||||
|
}
|
||||||
|
|
||||||
|
UserInfo struct {
|
||||||
|
Keys [][]byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = 1
|
||||||
|
blockDiff = 20 // change base on performance evaluation
|
||||||
|
|
||||||
|
voteKey = "ballots"
|
||||||
|
|
||||||
|
netmapContractKey = "netmapScriptHash"
|
||||||
|
containerContractKey = "containerScriptHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(addrNetmap, addrContainer []byte) {
|
||||||
|
if storage.Get(ctx, netmapContractKey) != nil {
|
||||||
|
panic("init: contract already deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrNetmap) != 20 || len(addrContainer) != 20 {
|
||||||
|
panic("init: incorrect length of contract script hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
||||||
|
storage.Put(ctx, containerContractKey, addrContainer)
|
||||||
|
|
||||||
|
runtime.Log("neofsid contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddKey(owner []byte, keys [][]byte) bool {
|
||||||
|
var (
|
||||||
|
n int // number of votes for inner ring invoke
|
||||||
|
id []byte // ballot key of the inner ring invocation
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(owner) != 25 {
|
||||||
|
panic("addKey: incorrect owner")
|
||||||
|
}
|
||||||
|
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("addKey: invocation from non inner ring node")
|
||||||
|
}
|
||||||
|
|
||||||
|
info := getUserInfo(ctx, owner)
|
||||||
|
|
||||||
|
addLoop:
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
pubKey := keys[i]
|
||||||
|
if len(pubKey) != 33 {
|
||||||
|
panic("addKey: incorrect public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := range info.Keys {
|
||||||
|
key := info.Keys[j]
|
||||||
|
if bytesEqual(key, pubKey) {
|
||||||
|
continue addLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Keys = append(info.Keys, pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromKnownContract := fromKnownContract(runtime.GetCallingScriptHash())
|
||||||
|
if fromKnownContract {
|
||||||
|
n = threshold
|
||||||
|
runtime.Log("addKey: processed indirect invoke")
|
||||||
|
} else {
|
||||||
|
id := invokeIDKeys(owner, keys, []byte("add"))
|
||||||
|
n = vote(ctx, id, irKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < threshold {
|
||||||
|
runtime.Log("addKey: processed invoke from inner ring")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
removeVotes(ctx, id)
|
||||||
|
setSerialized(ctx, owner, info)
|
||||||
|
runtime.Log("addKey: key bound to the owner")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveKey(owner []byte, keys [][]byte) bool {
|
||||||
|
if len(owner) != 25 {
|
||||||
|
panic("removeKey: incorrect owner")
|
||||||
|
}
|
||||||
|
|
||||||
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
||||||
|
innerRing := engine.AppCall(netmapContractAddr, "innerRingList").([]irNode)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("removeKey: invocation from non inner ring node")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := invokeIDKeys(owner, keys, []byte("remove"))
|
||||||
|
|
||||||
|
n := vote(ctx, id, irKey)
|
||||||
|
if n < threshold {
|
||||||
|
runtime.Log("removeKey: processed invoke from inner ring")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
removeVotes(ctx, id)
|
||||||
|
|
||||||
|
info := getUserInfo(ctx, owner)
|
||||||
|
var leftKeys [][]byte
|
||||||
|
|
||||||
|
rmLoop:
|
||||||
|
for i := range info.Keys {
|
||||||
|
key := info.Keys[i]
|
||||||
|
|
||||||
|
for j := 0; j < len(keys); j++ {
|
||||||
|
pubKey := keys[j]
|
||||||
|
if len(pubKey) != 33 {
|
||||||
|
panic("removeKey: incorrect public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytesEqual(key, pubKey) {
|
||||||
|
continue rmLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leftKeys = append(leftKeys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Keys = leftKeys
|
||||||
|
setSerialized(ctx, owner, info)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Key(owner []byte) [][]byte {
|
||||||
|
if len(owner) != 25 {
|
||||||
|
panic("key: incorrect owner")
|
||||||
|
}
|
||||||
|
|
||||||
|
info := getUserInfo(ctx, owner)
|
||||||
|
|
||||||
|
return info.Keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version() int {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserInfo(ctx storage.Context, key interface{}) UserInfo {
|
||||||
|
data := storage.Get(ctx, key)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).(UserInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserInfo{Keys: [][]byte{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBallots(ctx storage.Context) []ballot {
|
||||||
|
data := storage.Get(ctx, voteKey)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]ballot)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ballot{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
||||||
|
data := binary.Serialize(value)
|
||||||
|
storage.Put(ctx, key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerRingInvoker(ir []irNode) []byte {
|
||||||
|
for i := 0; i < len(ir); i++ {
|
||||||
|
node := ir[i]
|
||||||
|
if runtime.CheckWitness(node.key) {
|
||||||
|
return node.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vote(ctx storage.Context, id, from []byte) int {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
found = -1
|
||||||
|
blockHeight = blockchain.GetHeight()
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if bytesEqual(cnd.id, id) {
|
||||||
|
voters := cnd.n
|
||||||
|
|
||||||
|
for j := range voters {
|
||||||
|
if bytesEqual(voters[j], from) {
|
||||||
|
return len(voters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voters = append(voters, from)
|
||||||
|
cnd = ballot{id: id, n: voters, block: blockHeight}
|
||||||
|
found = len(voters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not add old ballots, they are invalid
|
||||||
|
if blockHeight-cnd.block <= blockDiff {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found < 0 {
|
||||||
|
voters := [][]byte{from}
|
||||||
|
newCandidates = append(newCandidates, ballot{
|
||||||
|
id: id,
|
||||||
|
n: voters,
|
||||||
|
block: blockHeight})
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeVotes(ctx storage.Context, id []byte) {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if !bytesEqual(cnd.id, id) {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeID(args []interface{}, prefix []byte) []byte {
|
||||||
|
for i := range args {
|
||||||
|
arg := args[i].([]byte)
|
||||||
|
prefix = append(prefix, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.SHA256(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeIDKeys(owner []byte, keys [][]byte, prefix []byte) []byte {
|
||||||
|
prefix = append(prefix, owner...)
|
||||||
|
for i := range keys {
|
||||||
|
prefix = append(prefix, keys[i]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.SHA256(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// neo-go#1176
|
||||||
|
func bytesEqual(a []byte, b []byte) bool {
|
||||||
|
return util.Equals(string(a), string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromKnownContract(caller []byte) bool {
|
||||||
|
containerContractAddr := storage.Get(ctx, containerContractKey).([]byte)
|
||||||
|
if bytesEqual(caller, containerContractAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
17
netmap/config.yml
Normal file
17
netmap/config.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
events:
|
||||||
|
- name: AddPeer
|
||||||
|
parameters:
|
||||||
|
- name: nodeInfo
|
||||||
|
type: ByteString
|
||||||
|
- name: UpdateState
|
||||||
|
parameters:
|
||||||
|
- name: state
|
||||||
|
type: Integer
|
||||||
|
- name: publicKey
|
||||||
|
type: ByteString
|
||||||
|
- name: NewEpoch
|
||||||
|
parameters:
|
||||||
|
- name: epoch
|
||||||
|
type: Integer
|
540
netmap/netmap_contract.go
Normal file
540
netmap/netmap_contract.go
Normal file
|
@ -0,0 +1,540 @@
|
||||||
|
package netmapcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
|
||||||
|
"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/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
irNode struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
storageNode struct {
|
||||||
|
info []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
ballot struct {
|
||||||
|
id []byte // id of the voting decision
|
||||||
|
n [][]byte // already voted inner ring nodes
|
||||||
|
block int // block with the last vote
|
||||||
|
}
|
||||||
|
|
||||||
|
netmapNode struct {
|
||||||
|
node storageNode
|
||||||
|
state nodeState
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeState int
|
||||||
|
|
||||||
|
record struct {
|
||||||
|
key []byte
|
||||||
|
val []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = 1
|
||||||
|
blockDiff = 20 // change base on performance evaluation
|
||||||
|
|
||||||
|
voteKey = "ballots"
|
||||||
|
netmapKey = "netmap"
|
||||||
|
innerRingKey = "innerring"
|
||||||
|
configuredKey = "initconfig"
|
||||||
|
|
||||||
|
snapshot0Key = "snapshotCurrent"
|
||||||
|
snapshot1Key = "snapshotPrevious"
|
||||||
|
snapshotEpoch = "snapshotEpoch"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ nodeState = iota
|
||||||
|
onlineState
|
||||||
|
offlineState
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configPrefix = []byte("config")
|
||||||
|
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
var irList []irNode
|
||||||
|
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
key := keys[i]
|
||||||
|
irList = append(irList, irNode{key: key})
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, innerRingKey, irList)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
setSerialized(ctx, netmapKey, []netmapNode{})
|
||||||
|
setSerialized(ctx, snapshot0Key, []netmapNode{})
|
||||||
|
setSerialized(ctx, snapshot1Key, []netmapNode{})
|
||||||
|
setSerialized(ctx, voteKey, []ballot{})
|
||||||
|
|
||||||
|
runtime.Log("netmap contract initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
func InnerRingList() []irNode {
|
||||||
|
return getIRNodes(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateInnerRing(keys [][]byte) bool {
|
||||||
|
innerRing := getIRNodes(ctx)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("updateInnerRing: this method must be invoked by inner ring nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
var irList []irNode
|
||||||
|
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
key := keys[i]
|
||||||
|
irList = append(irList, irNode{key: key})
|
||||||
|
}
|
||||||
|
|
||||||
|
rawIRList := binary.Serialize(irList)
|
||||||
|
hashIRList := crypto.SHA256(rawIRList)
|
||||||
|
|
||||||
|
n := vote(ctx, hashIRList, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
runtime.Log("updateInnerRing: inner ring list updated")
|
||||||
|
setSerialized(ctx, innerRingKey, irList)
|
||||||
|
removeVotes(ctx, hashIRList)
|
||||||
|
} else {
|
||||||
|
runtime.Log("updateInnerRing: processed invoke from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPeer(nodeInfo []byte) bool {
|
||||||
|
innerRing := getIRNodes(ctx)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
publicKey := nodeInfo[2:35] // offset:2, len:33
|
||||||
|
if !runtime.CheckWitness(publicKey) {
|
||||||
|
panic("addPeer: witness check failed")
|
||||||
|
}
|
||||||
|
runtime.Notify("AddPeer", nodeInfo)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate := storageNode{
|
||||||
|
info: nodeInfo,
|
||||||
|
}
|
||||||
|
rawCandidate := binary.Serialize(candidate)
|
||||||
|
hashCandidate := crypto.SHA256(rawCandidate)
|
||||||
|
|
||||||
|
nm := addToNetmap(ctx, candidate)
|
||||||
|
|
||||||
|
n := vote(ctx, hashCandidate, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
if nm == nil {
|
||||||
|
runtime.Log("addPeer: storage node already in the netmap")
|
||||||
|
} else {
|
||||||
|
setSerialized(ctx, netmapKey, nm)
|
||||||
|
runtime.Log("addPeer: add storage node to the network map")
|
||||||
|
}
|
||||||
|
removeVotes(ctx, hashCandidate)
|
||||||
|
} else {
|
||||||
|
runtime.Log("addPeer: processed invoke from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateState(state int, publicKey []byte) bool {
|
||||||
|
if len(publicKey) != 33 {
|
||||||
|
panic("updateState: incorrect public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
innerRing := getIRNodes(ctx)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
if !runtime.CheckWitness(publicKey) {
|
||||||
|
panic("updateState: witness check failed")
|
||||||
|
}
|
||||||
|
runtime.Notify("UpdateState", state, publicKey)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch nodeState(state) {
|
||||||
|
case offlineState:
|
||||||
|
newNetmap := removeFromNetmap(ctx, publicKey)
|
||||||
|
|
||||||
|
hashID := invokeID([]interface{}{publicKey}, []byte("delete"))
|
||||||
|
n := vote(ctx, hashID, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
runtime.Log("updateState: remove storage node from the network map")
|
||||||
|
setSerialized(ctx, netmapKey, newNetmap)
|
||||||
|
removeVotes(ctx, hashID)
|
||||||
|
} else {
|
||||||
|
runtime.Log("updateState: processed invoke from inner ring")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("updateState: unsupported state")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEpoch(epochNum int) bool {
|
||||||
|
innerRing := getIRNodes(ctx)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("newEpoch: this method must be invoked by inner ring nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
data0snapshot := getSnapshot(ctx, snapshot0Key)
|
||||||
|
dataOnlineState := filterNetmap(ctx, onlineState)
|
||||||
|
|
||||||
|
hashID := invokeID([]interface{}{epochNum}, []byte("epoch"))
|
||||||
|
|
||||||
|
n := vote(ctx, hashID, irKey)
|
||||||
|
if n >= threshold {
|
||||||
|
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
|
||||||
|
setSerialized(ctx, snapshot1Key, data0snapshot)
|
||||||
|
|
||||||
|
// put netmap into actual snapshot
|
||||||
|
setSerialized(ctx, snapshot0Key, dataOnlineState)
|
||||||
|
|
||||||
|
removeVotes(ctx, hashID)
|
||||||
|
runtime.Notify("NewEpoch", epochNum)
|
||||||
|
} else {
|
||||||
|
runtime.Log("newEpoch: processed invoke from inner ring")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Epoch() int {
|
||||||
|
return storage.Get(ctx, snapshotEpoch).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Netmap() []storageNode {
|
||||||
|
return getSnapshot(ctx, snapshot0Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Snapshot(diff int) []storageNode {
|
||||||
|
var key string
|
||||||
|
switch diff {
|
||||||
|
case 0:
|
||||||
|
key = snapshot0Key
|
||||||
|
case 1:
|
||||||
|
key = snapshot1Key
|
||||||
|
default:
|
||||||
|
panic("snapshot: incorrect diff")
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSnapshot(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Config(key []byte) interface{} {
|
||||||
|
return getConfig(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetConfig(id, key, val []byte) bool {
|
||||||
|
// check if it is inner ring invocation
|
||||||
|
innerRing := getIRNodes(ctx)
|
||||||
|
threshold := len(innerRing)/3*2 + 1
|
||||||
|
|
||||||
|
irKey := innerRingInvoker(innerRing)
|
||||||
|
if len(irKey) == 0 {
|
||||||
|
panic("setConfig: invoked by non inner ring node")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check unique id of the operation
|
||||||
|
hashID := invokeID([]interface{}{id, key, val}, []byte("config"))
|
||||||
|
n := vote(ctx, hashID, irKey)
|
||||||
|
|
||||||
|
if n >= threshold {
|
||||||
|
removeVotes(ctx, hashID)
|
||||||
|
setConfig(ctx, key, val)
|
||||||
|
|
||||||
|
runtime.Log("setConfig: configuration has been updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitConfig(args [][]byte) bool {
|
||||||
|
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")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListConfig() []record {
|
||||||
|
var config []record
|
||||||
|
|
||||||
|
it := storage.Find(ctx, configPrefix)
|
||||||
|
for iterator.Next(it) {
|
||||||
|
key := iterator.Key(it).([]byte)
|
||||||
|
val := iterator.Value(it).([]byte)
|
||||||
|
r := record{key: key[len(configPrefix):], val: val}
|
||||||
|
|
||||||
|
config = append(config, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version() int {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerRingInvoker(ir []irNode) []byte {
|
||||||
|
for i := 0; i < len(ir); i++ {
|
||||||
|
node := ir[i]
|
||||||
|
if runtime.CheckWitness(node.key) {
|
||||||
|
return node.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bytesEqual(newNodeKey, netmapNodeKey) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
netmap = append(netmap, node)
|
||||||
|
|
||||||
|
return netmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromNetmap(ctx storage.Context, key []byte) []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 !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 vote(ctx storage.Context, id, from []byte) int {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
found = -1
|
||||||
|
blockHeight = blockchain.GetHeight()
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if bytesEqual(cnd.id, id) {
|
||||||
|
voters := cnd.n
|
||||||
|
|
||||||
|
for j := range voters {
|
||||||
|
if bytesEqual(voters[j], from) {
|
||||||
|
return len(voters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voters = append(voters, from)
|
||||||
|
cnd = ballot{id: id, n: voters, block: blockHeight}
|
||||||
|
found = len(voters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not add old ballots, they are invalid
|
||||||
|
if blockHeight-cnd.block <= blockDiff {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found < 0 {
|
||||||
|
voters := [][]byte{from}
|
||||||
|
newCandidates = append(newCandidates, ballot{
|
||||||
|
id: id,
|
||||||
|
n: voters,
|
||||||
|
block: blockHeight})
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeVotes(ctx storage.Context, id []byte) {
|
||||||
|
var (
|
||||||
|
newCandidates []ballot
|
||||||
|
candidates = getBallots(ctx)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(candidates); i++ {
|
||||||
|
cnd := candidates[i]
|
||||||
|
if !bytesEqual(cnd.id, id) {
|
||||||
|
newCandidates = append(newCandidates, cnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, voteKey, newCandidates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIRNodes(ctx storage.Context) []irNode {
|
||||||
|
data := storage.Get(ctx, innerRingKey)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]irNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []irNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNetmapNodes(ctx storage.Context) []netmapNode {
|
||||||
|
data := storage.Get(ctx, netmapKey)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]netmapNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []netmapNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(ctx storage.Context, key string) []storageNode {
|
||||||
|
data := storage.Get(ctx, key)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]storageNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []storageNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBallots(ctx storage.Context) []ballot {
|
||||||
|
data := storage.Get(ctx, voteKey)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([]ballot)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ballot{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
||||||
|
data := binary.Serialize(value)
|
||||||
|
storage.Put(ctx, key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 invokeID(args []interface{}, prefix []byte) []byte {
|
||||||
|
for i := range args {
|
||||||
|
arg := args[i].([]byte)
|
||||||
|
prefix = append(prefix, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.SHA256(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// neo-go#1176
|
||||||
|
func bytesEqual(a []byte, b []byte) bool {
|
||||||
|
return util.Equals(string(a), string(b))
|
||||||
|
}
|
2
reputation/config.yml
Normal file
2
reputation/config.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
92
reputation/reputation_contract.go
Normal file
92
reputation/reputation_contract.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package reputationcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
const version = 1
|
||||||
|
|
||||||
|
const (
|
||||||
|
peerIDSize = 46 // NeoFS PeerIDSize
|
||||||
|
trustValSize = 8 // float64
|
||||||
|
|
||||||
|
trustStructSize = 0 +
|
||||||
|
peerIDSize + // manager ID
|
||||||
|
peerIDSize + // trusted ID
|
||||||
|
trustValSize // trust value
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
trustJournalPrefix = []byte("trustJournal")
|
||||||
|
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GetTrigger() != runtime.Application {
|
||||||
|
panic("contract has not been called in application node")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Put(manager, epoch, typ []byte, newTrustList [][]byte) bool {
|
||||||
|
if !runtime.CheckWitness(manager) {
|
||||||
|
panic("put: incorrect manager key")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(newTrustList); i++ {
|
||||||
|
trustData := newTrustList[i]
|
||||||
|
|
||||||
|
if len(trustData) != trustStructSize {
|
||||||
|
panic("list: invalid trust value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: consider using notification for inner ring node
|
||||||
|
|
||||||
|
// todo: limit size of the trust journal:
|
||||||
|
// history will be stored in chain (args or notifies)
|
||||||
|
// contract storage will be used as a cache if needed
|
||||||
|
key := append(trustJournalPrefix, append(epoch, typ...)...)
|
||||||
|
|
||||||
|
trustList := getList(ctx, key)
|
||||||
|
|
||||||
|
// fixme: with neo3.0 it is kinda unnecessary
|
||||||
|
if len(trustList) == 0 {
|
||||||
|
// if journal slice is not initialized, then `append` will throw
|
||||||
|
trustList = newTrustList
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(newTrustList); i++ {
|
||||||
|
trustList = append(trustList, newTrustList[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSerialized(ctx, key, trustList)
|
||||||
|
|
||||||
|
runtime.Log("trust list was successfully updated")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func List(epoch, typ []byte) [][]byte {
|
||||||
|
key := append(trustJournalPrefix, append(epoch, typ...)...)
|
||||||
|
|
||||||
|
return getList(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getList(ctx storage.Context, key interface{}) [][]byte {
|
||||||
|
data := storage.Get(ctx, key)
|
||||||
|
if data != nil {
|
||||||
|
return binary.Deserialize(data.([]byte)).([][]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [][]byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
||||||
|
data := binary.Serialize(value)
|
||||||
|
storage.Put(ctx, key, data)
|
||||||
|
}
|
Loading…
Reference in a new issue