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