[#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>
enable-notary-in-public-chains
Alex Vanin 2020-10-27 15:14:06 +03:00 committed by Alex Vanin
parent dab74aeec7
commit bf391b57dd
22 changed files with 3279 additions and 0 deletions

View 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)
}
}

View 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
}

View 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
}

View 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
}

View File

@ -0,0 +1,2 @@
hasstorage: true
ispayable: false

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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 100644
View File

@ -0,0 +1,2 @@
hasstorage: true
ispayable: false

View 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 100644
View 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

View 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

View 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)
}

View File

@ -0,0 +1,2 @@
hasstorage: true
ispayable: false

View 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 100644
View 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

View 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))
}

View File

@ -0,0 +1,2 @@
hasstorage: true
ispayable: false

View 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)
}