Compare commits
2 commits
master
...
acid-ant/f
Author | SHA1 | Date | |
---|---|---|---|
3aa936df5c | |||
7acc5ecaf1 |
20 changed files with 154 additions and 1198 deletions
|
@ -5,6 +5,9 @@ Changelog for FrostFS Contract
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
### Changed
|
### Changed
|
||||||
|
### Removed
|
||||||
|
- Notary disabled code from all contracts (#7)
|
||||||
|
|
||||||
### Updated
|
### Updated
|
||||||
- `neo-go` to `v0.99.4`
|
- `neo-go` to `v0.99.4`
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"github.com/TrueCloudLab/frostfs-contract/common"
|
"github.com/TrueCloudLab/frostfs-contract/common"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
|
||||||
|
@ -34,13 +33,17 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
|
||||||
|
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
storage.Delete(ctx, notaryDisabledKey)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
args := data.(struct {
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled in future version
|
||||||
notaryDisabled bool
|
notaryDisabled bool
|
||||||
addrNetmap interop.Hash160
|
addrNetmap interop.Hash160
|
||||||
addrProxy interop.Hash160
|
addrProxy interop.Hash160
|
||||||
|
@ -49,7 +52,11 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
total int
|
total int
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(args.addrNetmap) != interop.Hash160Len || !args.notaryDisabled && len(args.addrProxy) != interop.Hash160Len {
|
if args.notaryDisabled {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args.addrNetmap) != interop.Hash160Len || len(args.addrProxy) != interop.Hash160Len {
|
||||||
panic("incorrect length of contract script hash")
|
panic("incorrect length of contract script hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,13 +66,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
storage.Put(ctx, indexKey, args.index)
|
storage.Put(ctx, indexKey, args.index)
|
||||||
storage.Put(ctx, totalKey, args.total)
|
storage.Put(ctx, totalKey, args.total)
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
common.InitVote(ctx)
|
|
||||||
runtime.Log(args.name + " notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log(args.name + " contract initialized")
|
runtime.Log(args.name + " contract initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,15 +120,11 @@ func checkPermission(ir []interop.PublicKey) bool {
|
||||||
// and proxy contract. It can be invoked only by an Alphabet node of the Inner Ring.
|
// and proxy contract. It can be invoked only by an Alphabet node of the Inner Ring.
|
||||||
//
|
//
|
||||||
// To produce GAS, an alphabet contract transfers all available NEO from the contract
|
// To produce GAS, an alphabet contract transfers all available NEO from the contract
|
||||||
// account to itself. If notary is enabled, 50% of the GAS in the contract account
|
// account to itself. 50% of the GAS in the contract account
|
||||||
// are transferred to proxy contract. 43.75% of the GAS are equally distributed
|
// are transferred to proxy contract. 43.75% of the GAS are equally distributed
|
||||||
// among all Inner Ring nodes. Remaining 6.25% of the GAS stay in the contract.
|
// among all Inner Ring nodes. Remaining 6.25% of the GAS stay in the contract.
|
||||||
//
|
|
||||||
// If notary is disabled, 87.5% of the GAS are equally distributed among all
|
|
||||||
// Inner Ring nodes. Remaining 12.5% of the GAS stay in the contract.
|
|
||||||
func Emit() {
|
func Emit() {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
alphabet := common.AlphabetNodes()
|
alphabet := common.AlphabetNodes()
|
||||||
if !checkPermission(alphabet) {
|
if !checkPermission(alphabet) {
|
||||||
|
@ -143,7 +139,6 @@ func Emit() {
|
||||||
|
|
||||||
gasBalance := gas.BalanceOf(contractHash)
|
gasBalance := gas.BalanceOf(contractHash)
|
||||||
|
|
||||||
if !notaryDisabled {
|
|
||||||
proxyAddr := storage.Get(ctx, proxyKey).(interop.Hash160)
|
proxyAddr := storage.Get(ctx, proxyKey).(interop.Hash160)
|
||||||
|
|
||||||
proxyGas := gasBalance / 2
|
proxyGas := gasBalance / 2
|
||||||
|
@ -158,16 +153,8 @@ func Emit() {
|
||||||
gasBalance -= proxyGas
|
gasBalance -= proxyGas
|
||||||
|
|
||||||
runtime.Log("utility token has been emitted to proxy contract")
|
runtime.Log("utility token has been emitted to proxy contract")
|
||||||
}
|
|
||||||
|
|
||||||
var innerRing []interop.PublicKey
|
innerRing := common.InnerRingNodes()
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
netmapContract := storage.Get(ctx, netmapKey).(interop.Hash160)
|
|
||||||
innerRing = common.InnerRingNodesFromNetmap(netmapContract)
|
|
||||||
} else {
|
|
||||||
innerRing = common.InnerRingNodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
gasPerNode := gasBalance * 7 / 8 / len(innerRing)
|
gasPerNode := gasBalance * 7 / 8 / len(innerRing)
|
||||||
|
|
||||||
|
@ -192,25 +179,10 @@ func Emit() {
|
||||||
// alphabet contracts) should vote for a new committee.
|
// alphabet contracts) should vote for a new committee.
|
||||||
func Vote(epoch int, candidates []interop.PublicKey) {
|
func Vote(epoch int, candidates []interop.PublicKey) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
index := index(ctx)
|
index := index(ctx)
|
||||||
name := name(ctx)
|
name := name(ctx)
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("invalid invoker")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
curEpoch := currentEpoch(ctx)
|
curEpoch := currentEpoch(ctx)
|
||||||
if epoch != curEpoch {
|
if epoch != curEpoch {
|
||||||
|
@ -220,18 +192,6 @@ func Vote(epoch int, candidates []interop.PublicKey) {
|
||||||
candidate := candidates[index%len(candidates)]
|
candidate := candidates[index%len(candidates)]
|
||||||
address := runtime.GetExecutingScriptHash()
|
address := runtime.GetExecutingScriptHash()
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := voteID(epoch, candidates)
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := neo.Vote(address, candidate)
|
ok := neo.Vote(address, candidate)
|
||||||
if ok {
|
if ok {
|
||||||
runtime.Log(name + ": successfully voted for validator")
|
runtime.Log(name + ": successfully voted for validator")
|
||||||
|
@ -240,21 +200,6 @@ func Vote(epoch int, candidates []interop.PublicKey) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func voteID(epoch interface{}, args []interop.PublicKey) []byte {
|
|
||||||
var (
|
|
||||||
result []byte
|
|
||||||
epochBytes = epoch.([]byte)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = append(result, epochBytes...)
|
|
||||||
|
|
||||||
for i := range args {
|
|
||||||
result = append(result, args[i]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return crypto.Sha256(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the Glagolitic name of the contract.
|
// Name returns the Glagolitic name of the contract.
|
||||||
func Name() string {
|
func Name() string {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
|
|
@ -44,6 +44,13 @@ const (
|
||||||
|
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
if data.([]interface{})[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(ctx, notaryDisabledKey)
|
||||||
|
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
|
@ -51,6 +58,7 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
args := data.(struct {
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled in future version
|
||||||
notaryDisabled bool
|
notaryDisabled bool
|
||||||
addrNetmap interop.Hash160
|
addrNetmap interop.Hash160
|
||||||
})
|
})
|
||||||
|
@ -61,12 +69,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
|
|
||||||
storage.Put(ctx, netmapContractKey, args.addrNetmap)
|
storage.Put(ctx, netmapContractKey, args.addrNetmap)
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
runtime.Log("audit contract notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log("audit contract initialized")
|
runtime.Log("audit contract initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,16 +92,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
|
||||||
// in later epochs.
|
// in later epochs.
|
||||||
func Put(rawAuditResult []byte) {
|
func Put(rawAuditResult []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var innerRing []interop.PublicKey
|
innerRing := common.InnerRingNodes()
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
netmapContract := storage.Get(ctx, netmapContractKey).(interop.Hash160)
|
|
||||||
innerRing = common.InnerRingNodesFromNetmap(netmapContract)
|
|
||||||
} else {
|
|
||||||
innerRing = common.InnerRingNodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr := newAuditHeader(rawAuditResult)
|
hdr := newAuditHeader(rawAuditResult)
|
||||||
presented := false
|
presented := false
|
||||||
|
@ -182,7 +176,6 @@ func list(it iterator.Iterator) [][]byte {
|
||||||
|
|
||||||
ignore := [][]byte{
|
ignore := [][]byte{
|
||||||
[]byte(netmapContractKey),
|
[]byte(netmapContractKey),
|
||||||
[]byte(notaryDisabledKey),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
|
|
|
@ -60,6 +60,13 @@ func init() {
|
||||||
|
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
if data.([]interface{})[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(ctx, notaryDisabledKey)
|
||||||
|
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
|
@ -67,6 +74,7 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
args := data.(struct {
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled in future version
|
||||||
notaryDisabled bool
|
notaryDisabled bool
|
||||||
addrNetmap interop.Hash160
|
addrNetmap interop.Hash160
|
||||||
addrContainer interop.Hash160
|
addrContainer interop.Hash160
|
||||||
|
@ -79,13 +87,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
storage.Put(ctx, netmapContractKey, args.addrNetmap)
|
storage.Put(ctx, netmapContractKey, args.addrNetmap)
|
||||||
storage.Put(ctx, containerContractKey, args.addrContainer)
|
storage.Put(ctx, containerContractKey, args.addrContainer)
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
common.InitVote(ctx)
|
|
||||||
runtime.Log("balance contract notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log("balance contract initialized")
|
runtime.Log("balance contract initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,42 +147,8 @@ func Transfer(from, to interop.Hash160, amount int, data interface{}) bool {
|
||||||
// Inner Ring with multisignature.
|
// Inner Ring with multisignature.
|
||||||
func TransferX(from, to interop.Hash160, amount int, details []byte) {
|
func TransferX(from, to interop.Hash160, amount int, details []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
indirectCall bool
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked from inner ring")
|
|
||||||
}
|
|
||||||
|
|
||||||
indirectCall = common.FromKnownContract(
|
|
||||||
ctx,
|
|
||||||
runtime.GetCallingScriptHash(),
|
|
||||||
containerContractKey,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled && !indirectCall {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{from, to, amount}, []byte("transfer"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := token.transfer(ctx, from, to, amount, true, details)
|
result := token.transfer(ctx, from, to, amount, true, details)
|
||||||
if !result {
|
if !result {
|
||||||
|
@ -201,23 +168,8 @@ func TransferX(from, to interop.Hash160, amount int, details []byte) {
|
||||||
// to a new lock account that won't be used for anything beside Unlock and Burn.
|
// to a new lock account that won't be used for anything beside Unlock and Burn.
|
||||||
func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
|
func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked from inner ring")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
details := common.LockTransferDetails(txDetails)
|
details := common.LockTransferDetails(txDetails)
|
||||||
|
|
||||||
|
@ -227,18 +179,6 @@ func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
|
||||||
Parent: from,
|
Parent: from,
|
||||||
}
|
}
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{txDetails}, []byte("lock"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
common.SetSerialized(ctx, to, lockAccount)
|
common.SetSerialized(ctx, to, lockAccount)
|
||||||
|
|
||||||
result := token.transfer(ctx, from, to, amount, true, details)
|
result := token.transfer(ctx, from, to, amount, true, details)
|
||||||
|
@ -258,21 +198,8 @@ func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
|
||||||
// It produces Transfer and TransferX notifications.
|
// It produces Transfer and TransferX notifications.
|
||||||
func NewEpoch(epochNum int) {
|
func NewEpoch(epochNum int) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
common.CheckAlphabetWitness()
|
||||||
indirectCall := common.FromKnownContract(
|
|
||||||
ctx,
|
|
||||||
runtime.GetCallingScriptHash(),
|
|
||||||
netmapContractKey,
|
|
||||||
)
|
|
||||||
if !indirectCall {
|
|
||||||
panic("this method must be invoked from inner ring")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
it := storage.Find(ctx, []byte{}, storage.KeysOnly)
|
it := storage.Find(ctx, []byte{}, storage.KeysOnly)
|
||||||
for iterator.Next(it) {
|
for iterator.Next(it) {
|
||||||
|
@ -305,38 +232,11 @@ func NewEpoch(epochNum int) {
|
||||||
// Mint increases total supply of NEP-17 compatible FrostFS token.
|
// Mint increases total supply of NEP-17 compatible FrostFS token.
|
||||||
func Mint(to interop.Hash160, amount int, txDetails []byte) {
|
func Mint(to interop.Hash160, amount int, txDetails []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked from inner ring")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
details := common.MintTransferDetails(txDetails)
|
details := common.MintTransferDetails(txDetails)
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{txDetails}, []byte("mint"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := token.transfer(ctx, nil, to, amount, true, details)
|
ok := token.transfer(ctx, nil, to, amount, true, details)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("can't transfer assets")
|
panic("can't transfer assets")
|
||||||
|
@ -362,38 +262,11 @@ func Mint(to interop.Hash160, amount int, txDetails []byte) {
|
||||||
// compatible FrostFS token.
|
// compatible FrostFS token.
|
||||||
func Burn(from interop.Hash160, amount int, txDetails []byte) {
|
func Burn(from interop.Hash160, amount int, txDetails []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked from inner ring")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
details := common.BurnTransferDetails(txDetails)
|
details := common.BurnTransferDetails(txDetails)
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{txDetails}, []byte("burn"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := token.transfer(ctx, from, nil, amount, true, details)
|
ok := token.transfer(ctx, from, nil, amount, true, details)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("can't transfer assets")
|
panic("can't transfer assets")
|
||||||
|
|
15
common/common.go
Normal file
15
common/common.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PanicMsgForNotaryDisabledEnv = "contract not applicable for notary-disabled environment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BytesEqual compares two slices of bytes by wrapping them into strings,
|
||||||
|
// which is necessary with new util.Equals interop behaviour, see neo-go#1176.
|
||||||
|
func BytesEqual(a []byte, b []byte) bool {
|
||||||
|
return util.Equals(string(a), string(b))
|
||||||
|
}
|
28
common/ir.go
28
common/ir.go
|
@ -6,28 +6,12 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IRNode struct {
|
type IRNode struct {
|
||||||
PublicKey interop.PublicKey
|
PublicKey interop.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
const irListMethod = "innerRingList"
|
|
||||||
|
|
||||||
// InnerRingInvoker returns the public key of the inner ring node that has invoked the contract.
|
|
||||||
// Work around for environments without notary support.
|
|
||||||
func InnerRingInvoker(ir []interop.PublicKey) interop.PublicKey {
|
|
||||||
for i := 0; i < len(ir); i++ {
|
|
||||||
node := ir[i]
|
|
||||||
if runtime.CheckWitness(node) {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InnerRingNodes return a list of inner ring nodes from state validator role
|
// InnerRingNodes return a list of inner ring nodes from state validator role
|
||||||
// in the sidechain.
|
// in the sidechain.
|
||||||
func InnerRingNodes() []interop.PublicKey {
|
func InnerRingNodes() []interop.PublicKey {
|
||||||
|
@ -35,18 +19,6 @@ func InnerRingNodes() []interop.PublicKey {
|
||||||
return roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
|
return roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// InnerRingNodesFromNetmap gets a list of inner ring nodes through
|
|
||||||
// calling "innerRingList" method of smart contract.
|
|
||||||
// Work around for environments without notary support.
|
|
||||||
func InnerRingNodesFromNetmap(sc interop.Hash160) []interop.PublicKey {
|
|
||||||
nodes := contract.Call(sc, irListMethod, contract.ReadOnly).([]IRNode)
|
|
||||||
pubs := []interop.PublicKey{}
|
|
||||||
for i := range nodes {
|
|
||||||
pubs = append(pubs, nodes[i].PublicKey)
|
|
||||||
}
|
|
||||||
return pubs
|
|
||||||
}
|
|
||||||
|
|
||||||
// AlphabetNodes returns a list of alphabet nodes from committee in the sidechain.
|
// AlphabetNodes returns a list of alphabet nodes from committee in the sidechain.
|
||||||
func AlphabetNodes() []interop.PublicKey {
|
func AlphabetNodes() []interop.PublicKey {
|
||||||
return neo.GetCommittee()
|
return neo.GetCommittee()
|
||||||
|
|
|
@ -5,15 +5,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetList(ctx storage.Context, key interface{}) [][]byte {
|
|
||||||
data := storage.Get(ctx, key)
|
|
||||||
if data != nil {
|
|
||||||
return std.Deserialize(data.([]byte)).([][]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
return [][]byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSerialized serializes data and puts it into contract storage.
|
// SetSerialized serializes data and puts it into contract storage.
|
||||||
func SetSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
func SetSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
||||||
data := std.Serialize(value)
|
data := std.Serialize(value)
|
||||||
|
|
149
common/vote.go
149
common/vote.go
|
@ -1,149 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Ballot struct {
|
|
||||||
// ID of the voting decision.
|
|
||||||
ID []byte
|
|
||||||
|
|
||||||
// Public keys of the already voted inner ring nodes.
|
|
||||||
Voters []interop.PublicKey
|
|
||||||
|
|
||||||
// Height of block with the last vote.
|
|
||||||
Height int
|
|
||||||
}
|
|
||||||
|
|
||||||
const voteKey = "ballots"
|
|
||||||
|
|
||||||
const blockDiff = 20 // change base on performance evaluation
|
|
||||||
|
|
||||||
func InitVote(ctx storage.Context) {
|
|
||||||
SetSerialized(ctx, voteKey, []Ballot{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vote adds ballot for the decision with a specific 'id' and returns the amount
|
|
||||||
// of unique voters for that decision.
|
|
||||||
func Vote(ctx storage.Context, id, from []byte) int {
|
|
||||||
var (
|
|
||||||
newCandidates []Ballot
|
|
||||||
candidates = getBallots(ctx)
|
|
||||||
found = -1
|
|
||||||
blockHeight = ledger.CurrentIndex()
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := 0; i < len(candidates); i++ {
|
|
||||||
cnd := candidates[i]
|
|
||||||
|
|
||||||
if blockHeight-cnd.Height > blockDiff {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if BytesEqual(cnd.ID, id) {
|
|
||||||
voters := cnd.Voters
|
|
||||||
|
|
||||||
for j := range voters {
|
|
||||||
if BytesEqual(voters[j], from) {
|
|
||||||
return len(voters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
voters = append(voters, from)
|
|
||||||
cnd = Ballot{ID: id, Voters: voters, Height: blockHeight}
|
|
||||||
found = len(voters)
|
|
||||||
}
|
|
||||||
|
|
||||||
newCandidates = append(newCandidates, cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if found < 0 {
|
|
||||||
voters := []interop.PublicKey{from}
|
|
||||||
newCandidates = append(newCandidates, Ballot{
|
|
||||||
ID: id,
|
|
||||||
Voters: voters,
|
|
||||||
Height: blockHeight})
|
|
||||||
found = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
SetSerialized(ctx, voteKey, newCandidates)
|
|
||||||
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveVotes clears ballots of the decision that has been accepted by
|
|
||||||
// inner ring nodes.
|
|
||||||
func RemoveVotes(ctx storage.Context, id []byte) {
|
|
||||||
var (
|
|
||||||
candidates = getBallots(ctx)
|
|
||||||
index int
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := 0; i < len(candidates); i++ {
|
|
||||||
cnd := candidates[i]
|
|
||||||
if BytesEqual(cnd.ID, id) {
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
util.Remove(candidates, index)
|
|
||||||
SetSerialized(ctx, voteKey, candidates)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBallots returns a deserialized slice of vote ballots.
|
|
||||||
func getBallots(ctx storage.Context) []Ballot {
|
|
||||||
data := storage.Get(ctx, voteKey)
|
|
||||||
if data != nil {
|
|
||||||
return std.Deserialize(data.([]byte)).([]Ballot)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []Ballot{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesEqual compares two slices of bytes by wrapping them into strings,
|
|
||||||
// which is necessary with new util.Equals interop behaviour, see neo-go#1176.
|
|
||||||
func BytesEqual(a []byte, b []byte) bool {
|
|
||||||
return util.Equals(string(a), string(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvokeID returns hashed value of prefix and args concatenation. Iy is used to
|
|
||||||
// identify different ballots.
|
|
||||||
func InvokeID(args []interface{}, prefix []byte) []byte {
|
|
||||||
for i := range args {
|
|
||||||
arg := args[i].([]byte)
|
|
||||||
prefix = append(prefix, arg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return crypto.Sha256(prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Check if the invocation is 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:
|
|
||||||
- invokation has happened from inner ring node,
|
|
||||||
- it is indirect invocation from another smart-contract.
|
|
||||||
|
|
||||||
However, there is a possible attack, when a malicious inner ring node creates
|
|
||||||
a malicious smart-contract in the morph chain to do indirect 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(ctx storage.Context, caller interop.Hash160, key string) bool {
|
|
||||||
addr := storage.Get(ctx, key).(interop.Hash160)
|
|
||||||
return BytesEqual(caller, addr)
|
|
||||||
}
|
|
|
@ -16,8 +16,8 @@ var (
|
||||||
|
|
||||||
// CheckAlphabetWitness checks witness of the passed caller.
|
// CheckAlphabetWitness checks witness of the passed caller.
|
||||||
// It panics with ErrAlphabetWitnessFailed message on fail.
|
// It panics with ErrAlphabetWitnessFailed message on fail.
|
||||||
func CheckAlphabetWitness(caller []byte) {
|
func CheckAlphabetWitness() {
|
||||||
checkWitnessWithPanic(caller, ErrAlphabetWitnessFailed)
|
checkWitnessWithPanic(AlphabetAddress(), ErrAlphabetWitnessFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckOwnerWitness checks witness of the passed caller.
|
// CheckOwnerWitness checks witness of the passed caller.
|
||||||
|
|
|
@ -92,6 +92,13 @@ func OnNEP11Payment(a interop.Hash160, b int, c []byte, d interface{}) {
|
||||||
|
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
if data.([]interface{})[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(ctx, notaryDisabledKey)
|
||||||
|
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
|
@ -119,6 +126,7 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
args := data.(struct {
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled in future version
|
||||||
notaryDisabled bool
|
notaryDisabled bool
|
||||||
addrNetmap interop.Hash160
|
addrNetmap interop.Hash160
|
||||||
addrBalance interop.Hash160
|
addrBalance interop.Hash160
|
||||||
|
@ -139,13 +147,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
storage.Put(ctx, nnsContractKey, args.addrNNS)
|
storage.Put(ctx, nnsContractKey, args.addrNNS)
|
||||||
storage.Put(ctx, nnsRootKey, args.nnsRoot)
|
storage.Put(ctx, nnsRootKey, args.nnsRoot)
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
common.InitVote(ctx)
|
|
||||||
runtime.Log("container contract notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// add NNS root for container alias domains
|
// add NNS root for container alias domains
|
||||||
registerNiceNameTLD(args.addrNNS, args.nnsRoot)
|
registerNiceNameTLD(args.addrNNS, args.nnsRoot)
|
||||||
|
|
||||||
|
@ -197,7 +198,6 @@ func PutNamed(container []byte, signature interop.Signature,
|
||||||
publicKey interop.PublicKey, token []byte,
|
publicKey interop.PublicKey, token []byte,
|
||||||
name, zone string) {
|
name, zone string) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
ownerID := ownerFromBinaryContainer(container)
|
ownerID := ownerFromBinaryContainer(container)
|
||||||
containerID := crypto.Sha256(container)
|
containerID := crypto.Sha256(container)
|
||||||
|
@ -238,26 +238,7 @@ func PutNamed(container []byte, signature interop.Signature,
|
||||||
panic("insufficient balance to create container")
|
panic("insufficient balance to create container")
|
||||||
}
|
}
|
||||||
|
|
||||||
if notaryDisabled {
|
common.CheckAlphabetWitness()
|
||||||
nodeKey := common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
runtime.Notify("containerPut", container, signature, publicKey, token)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{container, signature, publicKey}, []byte("put"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
// todo: check if new container with unique container id
|
// todo: check if new container with unique container id
|
||||||
|
|
||||||
details := common.ContainerFeeTransferDetails(containerID)
|
details := common.ContainerFeeTransferDetails(containerID)
|
||||||
|
@ -336,34 +317,13 @@ func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool
|
||||||
// If the container doesn't exist, it panics with NotFoundError.
|
// If the container doesn't exist, it panics with NotFoundError.
|
||||||
func Delete(containerID []byte, signature interop.Signature, token []byte) {
|
func Delete(containerID []byte, signature interop.Signature, token []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
ownerID := getOwnerByID(ctx, containerID)
|
ownerID := getOwnerByID(ctx, containerID)
|
||||||
if ownerID == nil {
|
if ownerID == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if notaryDisabled {
|
common.CheckAlphabetWitness()
|
||||||
alphabet := common.AlphabetNodes()
|
|
||||||
nodeKey := common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
runtime.Notify("containerDelete", containerID, signature, token)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{containerID, signature}, []byte("delete"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := append([]byte(nnsHasAliasKey), containerID...)
|
key := append([]byte(nnsHasAliasKey), containerID...)
|
||||||
domain := storage.Get(ctx, key).(string)
|
domain := storage.Get(ctx, key).(string)
|
||||||
|
@ -463,7 +423,6 @@ func List(owner []byte) [][]byte {
|
||||||
// If the container doesn't exist, it panics with NotFoundError.
|
// If the container doesn't exist, it panics with NotFoundError.
|
||||||
func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
|
func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
// V2 format
|
// V2 format
|
||||||
// get container ID
|
// get container ID
|
||||||
|
@ -476,27 +435,7 @@ func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicK
|
||||||
panic(NotFoundError)
|
panic(NotFoundError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if notaryDisabled {
|
common.CheckAlphabetWitness()
|
||||||
alphabet := common.AlphabetNodes()
|
|
||||||
nodeKey := common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
runtime.Notify("setEACL", eACL, signature, publicKey, token)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{eACL}, []byte("setEACL"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
rule := ExtendedACL{
|
rule := ExtendedACL{
|
||||||
value: eACL,
|
value: eACL,
|
||||||
|
@ -628,21 +567,8 @@ func IterateContainerSizes(epoch int) iterator.Iterator {
|
||||||
// epochNum + 3. It can be invoked only by NewEpoch method of the Netmap contract.
|
// epochNum + 3. It can be invoked only by NewEpoch method of the Netmap contract.
|
||||||
func NewEpoch(epochNum int) {
|
func NewEpoch(epochNum int) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
common.CheckAlphabetWitness()
|
||||||
indirectCall := common.FromKnownContract(
|
|
||||||
ctx,
|
|
||||||
runtime.GetCallingScriptHash(),
|
|
||||||
netmapContractKey,
|
|
||||||
)
|
|
||||||
if !indirectCall {
|
|
||||||
panic("method must be invoked by inner ring")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupContainers(ctx, epochNum)
|
cleanupContainers(ctx, epochNum)
|
||||||
}
|
}
|
||||||
|
@ -650,36 +576,7 @@ func NewEpoch(epochNum int) {
|
||||||
// StartContainerEstimation method produces StartEstimation notification.
|
// StartContainerEstimation method produces StartEstimation notification.
|
||||||
// It can be invoked only by Alphabet nodes of the Inner Ring.
|
// It can be invoked only by Alphabet nodes of the Inner Ring.
|
||||||
func StartContainerEstimation(epoch int) {
|
func StartContainerEstimation(epoch int) {
|
||||||
ctx := storage.GetContext()
|
common.CheckAlphabetWitness()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("method must be invoked by inner ring")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{epoch}, []byte("startEstimation"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Notify("StartEstimation", epoch)
|
runtime.Notify("StartEstimation", epoch)
|
||||||
runtime.Log("notification has been produced")
|
runtime.Log("notification has been produced")
|
||||||
|
@ -688,36 +585,7 @@ func StartContainerEstimation(epoch int) {
|
||||||
// StopContainerEstimation method produces StopEstimation notification.
|
// StopContainerEstimation method produces StopEstimation notification.
|
||||||
// It can be invoked only by Alphabet nodes of the Inner Ring.
|
// It can be invoked only by Alphabet nodes of the Inner Ring.
|
||||||
func StopContainerEstimation(epoch int) {
|
func StopContainerEstimation(epoch int) {
|
||||||
ctx := storage.GetContext()
|
common.CheckAlphabetWitness()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("method must be invoked by inner ring")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{epoch}, []byte("stopEstimation"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Notify("StopEstimation", epoch)
|
runtime.Notify("StopEstimation", epoch)
|
||||||
runtime.Log("notification has been produced")
|
runtime.Log("notification has been produced")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: "FrostFS"
|
name: "FrostFS"
|
||||||
safemethods: ["alphabetList", "alphabetAddress", "innerRingCandidates", "config", "listConfig", "version"]
|
safemethods: ["alphabetAddress", "innerRingCandidates", "config", "listConfig", "version"]
|
||||||
permissions:
|
permissions:
|
||||||
- methods: ["update", "transfer"]
|
- methods: ["update", "transfer"]
|
||||||
events:
|
events:
|
||||||
|
|
|
@ -70,16 +70,6 @@ public keys with the user account (OwnerID). Keys argument is an array of ByteAr
|
||||||
- name: keys
|
- name: keys
|
||||||
type: Array
|
type: Array
|
||||||
|
|
||||||
AlphabetUpdate notification. This notification is produced when Alphabet nodes
|
|
||||||
have updated their lists in the contract. Alphabet argument is an array of ByteArray. It
|
|
||||||
contains public keys of new alphabet nodes.
|
|
||||||
|
|
||||||
AlphabetUpdate:
|
|
||||||
- name: id
|
|
||||||
type: ByteArray
|
|
||||||
- name: alphabet
|
|
||||||
type: Array
|
|
||||||
|
|
||||||
SetConfig notification. This notification is produced when Alphabet nodes update
|
SetConfig notification. This notification is produced when Alphabet nodes update
|
||||||
FrostFS network configuration value.
|
FrostFS network configuration value.
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||||
|
@ -48,6 +47,12 @@ var (
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
if data.([]interface{})[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(ctx, notaryDisabledKey)
|
||||||
|
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
|
@ -55,6 +60,7 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
args := data.(struct {
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled in future version
|
||||||
notaryDisabled bool
|
notaryDisabled bool
|
||||||
addrProc interop.Hash160
|
addrProc interop.Hash160
|
||||||
keys []interop.PublicKey
|
keys []interop.PublicKey
|
||||||
|
@ -81,13 +87,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
|
|
||||||
storage.Put(ctx, processingContractKey, args.addrProc)
|
storage.Put(ctx, processingContractKey, args.addrProc)
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
common.InitVote(ctx)
|
|
||||||
runtime.Log("frostfs contract notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
ln := len(args.config)
|
ln := len(args.config)
|
||||||
if ln%2 != 0 {
|
if ln%2 != 0 {
|
||||||
panic("bad configuration")
|
panic("bad configuration")
|
||||||
|
@ -110,25 +109,15 @@ func Update(script []byte, manifest []byte, data interface{}) {
|
||||||
alphabetKeys := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
|
alphabetKeys := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
|
||||||
alphabetCommittee := common.Multiaddress(alphabetKeys, true)
|
alphabetCommittee := common.Multiaddress(alphabetKeys, true)
|
||||||
|
|
||||||
common.CheckAlphabetWitness(alphabetCommittee)
|
if !runtime.CheckWitness(alphabetCommittee) {
|
||||||
|
panic(common.ErrAlphabetWitnessFailed)
|
||||||
|
}
|
||||||
|
|
||||||
contract.Call(interop.Hash160(management.Hash), "update",
|
contract.Call(interop.Hash160(management.Hash), "update",
|
||||||
contract.All, script, manifest, common.AppendVersion(data))
|
contract.All, script, manifest, common.AppendVersion(data))
|
||||||
runtime.Log("frostfs contract updated")
|
runtime.Log("frostfs contract updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlphabetList returns an array of alphabet node keys. It is used in sidechain notary
|
|
||||||
// disabled environment.
|
|
||||||
func AlphabetList() []common.IRNode {
|
|
||||||
ctx := storage.GetReadOnlyContext()
|
|
||||||
pubs := getAlphabetNodes(ctx)
|
|
||||||
nodes := []common.IRNode{}
|
|
||||||
for i := range pubs {
|
|
||||||
nodes = append(nodes, common.IRNode{PublicKey: pubs[i]})
|
|
||||||
}
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// AlphabetAddress returns 2\3n+1 multisignature address of alphabet nodes.
|
// AlphabetAddress returns 2\3n+1 multisignature address of alphabet nodes.
|
||||||
// It is used in sidechain notary disabled environment.
|
// It is used in sidechain notary disabled environment.
|
||||||
func AlphabetAddress() interop.Hash160 {
|
func AlphabetAddress() interop.Hash160 {
|
||||||
|
@ -156,42 +145,15 @@ func InnerRingCandidates() []common.IRNode {
|
||||||
// This method does not return fee back to the candidate.
|
// This method does not return fee back to the candidate.
|
||||||
func InnerRingCandidateRemove(key interop.PublicKey) {
|
func InnerRingCandidateRemove(key interop.PublicKey) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
keyOwner := runtime.CheckWitness(key)
|
keyOwner := runtime.CheckWitness(key)
|
||||||
|
|
||||||
if !keyOwner {
|
if !keyOwner {
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = getAlphabetNodes(ctx)
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked by candidate or alphabet")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := AlphabetAddress()
|
multiaddr := AlphabetAddress()
|
||||||
if !runtime.CheckWitness(multiaddr) {
|
if !runtime.CheckWitness(multiaddr) {
|
||||||
panic("this method must be invoked by candidate or alphabet")
|
panic("this method must be invoked by candidate or alphabet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled && !keyOwner {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := append(key, []byte("delete")...)
|
|
||||||
hashID := crypto.Sha256(id)
|
|
||||||
|
|
||||||
n := common.Vote(ctx, hashID, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, hashID)
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := []byte(candidatesKey)
|
prefix := []byte(candidatesKey)
|
||||||
stKey := append(prefix, key...)
|
stKey := append(prefix, key...)
|
||||||
|
@ -285,29 +247,16 @@ func Withdraw(user interop.Hash160, amount int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
// transfer fee to proxy contract to pay cheque invocation
|
// transfer fee to proxy contract to pay cheque invocation
|
||||||
fee := getConfig(ctx, withdrawFeeConfigKey).(int)
|
fee := getConfig(ctx, withdrawFeeConfigKey).(int)
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet := getAlphabetNodes(ctx)
|
|
||||||
for _, node := range alphabet {
|
|
||||||
processingAddr := contract.CreateStandardAccount(node)
|
|
||||||
|
|
||||||
transferred := gas.Transfer(user, processingAddr, fee, []byte{})
|
|
||||||
if !transferred {
|
|
||||||
panic("failed to transfer withdraw fee, aborting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
processingAddr := storage.Get(ctx, processingContractKey).(interop.Hash160)
|
processingAddr := storage.Get(ctx, processingContractKey).(interop.Hash160)
|
||||||
|
|
||||||
transferred := gas.Transfer(user, processingAddr, fee, []byte{})
|
transferred := gas.Transfer(user, processingAddr, fee, []byte{})
|
||||||
if !transferred {
|
if !transferred {
|
||||||
panic("failed to transfer withdraw fee, aborting")
|
panic("failed to transfer withdraw fee, aborting")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// notify alphabet nodes
|
// notify alphabet nodes
|
||||||
amount = amount * 100000000
|
amount = amount * 100000000
|
||||||
|
@ -322,38 +271,10 @@ func Withdraw(user interop.Hash160, amount int) {
|
||||||
//
|
//
|
||||||
// This method produces Cheque notification to burn assets in sidechain.
|
// This method produces Cheque notification to burn assets in sidechain.
|
||||||
func Cheque(id []byte, user interop.Hash160, amount int, lockAcc []byte) {
|
func Cheque(id []byte, user interop.Hash160, amount int, lockAcc []byte) {
|
||||||
ctx := storage.GetContext()
|
common.CheckAlphabetWitness()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = getAlphabetNodes(ctx)
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked by alphabet")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
from := runtime.GetExecutingScriptHash()
|
from := runtime.GetExecutingScriptHash()
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
transferred := gas.Transfer(from, user, amount, nil)
|
transferred := gas.Transfer(from, user, amount, nil)
|
||||||
if !transferred {
|
if !transferred {
|
||||||
panic("failed to transfer funds, aborting")
|
panic("failed to transfer funds, aborting")
|
||||||
|
@ -403,63 +324,6 @@ func Unbind(user []byte, keys []interop.PublicKey) {
|
||||||
runtime.Notify("Unbind", user, keys)
|
runtime.Notify("Unbind", user, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlphabetUpdate updates a list of alphabet nodes with the provided list of
|
|
||||||
// public keys. It can be invoked only by alphabet nodes.
|
|
||||||
//
|
|
||||||
// This method is used in notary disabled sidechain environment. In this case,
|
|
||||||
// the actual alphabet list should be stored in the FrostFS contract.
|
|
||||||
func AlphabetUpdate(id []byte, args []interop.PublicKey) {
|
|
||||||
ctx := storage.GetContext()
|
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
if len(args) == 0 {
|
|
||||||
panic("bad arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = getAlphabetNodes(ctx)
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked by alphabet")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
newAlphabet := []interop.PublicKey{}
|
|
||||||
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
pubKey := args[i]
|
|
||||||
if len(pubKey) != interop.PublicKeyCompressedLen {
|
|
||||||
panic("invalid public key in alphabet list")
|
|
||||||
}
|
|
||||||
|
|
||||||
newAlphabet = append(newAlphabet, pubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
common.SetSerialized(ctx, alphabetKey, newAlphabet)
|
|
||||||
|
|
||||||
runtime.Notify("AlphabetUpdate", id, newAlphabet)
|
|
||||||
runtime.Log("alphabet list has been updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config returns configuration value of FrostFS configuration. If the key does
|
// Config returns configuration value of FrostFS configuration. If the key does
|
||||||
// not exists, returns nil.
|
// not exists, returns nil.
|
||||||
func Config(key []byte) interface{} {
|
func Config(key []byte) interface{} {
|
||||||
|
@ -471,34 +335,8 @@ func Config(key []byte) interface{} {
|
||||||
// only by Alphabet nodes.
|
// only by Alphabet nodes.
|
||||||
func SetConfig(id, key, val []byte) {
|
func SetConfig(id, key, val []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = getAlphabetNodes(ctx)
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(key) == 0 {
|
|
||||||
panic("this method must be invoked by alphabet")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig(ctx, key, val)
|
setConfig(ctx, key, val)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
"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/storage"
|
||||||
|
@ -31,6 +30,12 @@ const (
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
if data.([]interface{})[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(ctx, notaryDisabledKey)
|
||||||
|
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
|
@ -38,6 +43,7 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
args := data.(struct {
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled in future version
|
||||||
notaryDisabled bool
|
notaryDisabled bool
|
||||||
addrNetmap interop.Hash160
|
addrNetmap interop.Hash160
|
||||||
addrContainer interop.Hash160
|
addrContainer interop.Hash160
|
||||||
|
@ -50,13 +56,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
storage.Put(ctx, netmapContractKey, args.addrNetmap)
|
storage.Put(ctx, netmapContractKey, args.addrNetmap)
|
||||||
storage.Put(ctx, containerContractKey, args.addrContainer)
|
storage.Put(ctx, containerContractKey, args.addrContainer)
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
common.InitVote(ctx)
|
|
||||||
runtime.Log("frostfsid contract notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log("frostfsid contract initialized")
|
runtime.Log("frostfsid contract initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,41 +89,8 @@ func AddKey(owner []byte, keys []interop.PublicKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
indirectCall bool
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("invocation from non inner ring node")
|
|
||||||
}
|
|
||||||
|
|
||||||
indirectCall = common.FromKnownContract(
|
|
||||||
ctx,
|
|
||||||
runtime.GetCallingScriptHash(),
|
|
||||||
containerContractKey)
|
|
||||||
|
|
||||||
if indirectCall {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := invokeIDKeys(owner, keys, []byte("add"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
ownerKey := append([]byte{ownerKeysPrefix}, owner...)
|
ownerKey := append([]byte{ownerKeysPrefix}, owner...)
|
||||||
for i := range keys {
|
for i := range keys {
|
||||||
|
@ -153,35 +119,11 @@ func RemoveKey(owner []byte, keys []interop.PublicKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("invocation from non inner ring node")
|
|
||||||
}
|
|
||||||
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := invokeIDKeys(owner, keys, []byte("remove"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
multiaddr := common.AlphabetAddress()
|
||||||
if !runtime.CheckWitness(multiaddr) {
|
if !runtime.CheckWitness(multiaddr) {
|
||||||
panic("invocation from non inner ring node")
|
panic("invocation from non inner ring node")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ownerKey := append([]byte{ownerKeysPrefix}, owner...)
|
ownerKey := append([]byte{ownerKeysPrefix}, owner...)
|
||||||
for i := range keys {
|
for i := range keys {
|
||||||
|
@ -222,12 +164,3 @@ func getUserInfo(ctx storage.Context, key interface{}) UserInfo {
|
||||||
|
|
||||||
return UserInfo{Keys: pubs}
|
return UserInfo{Keys: pubs}
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeIDKeys(owner []byte, keys []interop.PublicKey, prefix []byte) []byte {
|
|
||||||
prefix = append(prefix, owner...)
|
|
||||||
for i := range keys {
|
|
||||||
prefix = append(prefix, keys[i]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return crypto.Sha256(prefix)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: "FrostFS Netmap"
|
name: "FrostFS Netmap"
|
||||||
safemethods: ["innerRingList", "epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version"]
|
safemethods: ["epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version"]
|
||||||
permissions:
|
permissions:
|
||||||
- methods: ["update", "newEpoch"]
|
- methods: ["update", "newEpoch"]
|
||||||
events:
|
events:
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
Netmap contract is a contract deployed in FrostFS sidechain.
|
Netmap contract is a contract deployed in FrostFS sidechain.
|
||||||
|
|
||||||
Netmap contract stores and manages FrostFS network map, Storage node candidates
|
Netmap contract stores and manages FrostFS network map, Storage node candidates
|
||||||
and epoch number counter. In notary disabled environment, contract also stores
|
and epoch number counter.
|
||||||
a list of Inner Ring node keys.
|
|
||||||
|
|
||||||
# Contract notifications
|
# Contract notifications
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||||
|
@ -71,7 +70,14 @@ var (
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
if data.([]interface{})[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(ctx, notaryDisabledKey)
|
||||||
|
|
||||||
var args = data.(struct {
|
var args = data.(struct {
|
||||||
|
//TODO(@acid-ant): #9 remove notaryDisabled in future version
|
||||||
notaryDisabled bool
|
notaryDisabled bool
|
||||||
addrBalance interop.Hash160
|
addrBalance interop.Hash160
|
||||||
addrContainer interop.Hash160
|
addrContainer interop.Hash160
|
||||||
|
@ -115,14 +121,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
storage.Put(ctx, balanceContractKey, args.addrBalance)
|
storage.Put(ctx, balanceContractKey, args.addrBalance)
|
||||||
storage.Put(ctx, containerContractKey, args.addrContainer)
|
storage.Put(ctx, containerContractKey, args.addrContainer)
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
common.SetSerialized(ctx, innerRingKey, args.keys)
|
|
||||||
common.InitVote(ctx)
|
|
||||||
runtime.Log("netmap contract notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log("netmap contract initialized")
|
runtime.Log("netmap contract initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,62 +136,6 @@ func Update(script []byte, manifest []byte, data interface{}) {
|
||||||
runtime.Log("netmap contract updated")
|
runtime.Log("netmap contract updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// InnerRingList method returns a slice of structures that contains the public key of
|
|
||||||
// an Inner Ring node. It should be used in notary disabled environment only.
|
|
||||||
//
|
|
||||||
// If notary is enabled, look to NeoFSAlphabet role in native RoleManagement
|
|
||||||
// contract of the sidechain.
|
|
||||||
func InnerRingList() []common.IRNode {
|
|
||||||
ctx := storage.GetReadOnlyContext()
|
|
||||||
pubs := getIRNodes(ctx)
|
|
||||||
nodes := []common.IRNode{}
|
|
||||||
for i := range pubs {
|
|
||||||
nodes = append(nodes, common.IRNode{PublicKey: pubs[i]})
|
|
||||||
}
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateInnerRing method updates a list of Inner Ring node keys. It should be used
|
|
||||||
// only in notary disabled environment. It can be invoked only by Alphabet nodes.
|
|
||||||
//
|
|
||||||
// If notary is enabled, update NeoFSAlphabet role in native RoleManagement
|
|
||||||
// contract of the sidechain. Use notary service to collect multisignature.
|
|
||||||
func UpdateInnerRing(keys []interop.PublicKey) {
|
|
||||||
ctx := storage.GetContext()
|
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked by alphabet nodes")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := keysID(keys, []byte("updateIR"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log("inner ring list updated")
|
|
||||||
common.SetSerialized(ctx, innerRingKey, keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPeerIR accepts Alphabet calls in the notary-enabled contract setting and
|
// AddPeerIR accepts Alphabet calls in the notary-enabled contract setting and
|
||||||
// behaves similar to AddPeer in the notary-disabled one.
|
// behaves similar to AddPeer in the notary-disabled one.
|
||||||
//
|
//
|
||||||
|
@ -201,12 +143,8 @@ func UpdateInnerRing(keys []interop.PublicKey) {
|
||||||
// AddPeerIR MUST be called by the Alphabet member only.
|
// AddPeerIR MUST be called by the Alphabet member only.
|
||||||
func AddPeerIR(nodeInfo []byte) {
|
func AddPeerIR(nodeInfo []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
if notaryDisabled {
|
|
||||||
panic("AddPeerIR should only be called in notary-enabled environment")
|
|
||||||
}
|
|
||||||
|
|
||||||
common.CheckAlphabetWitness(common.AlphabetAddress())
|
common.CheckAlphabetWitness()
|
||||||
|
|
||||||
publicKey := nodeInfo[2:35] // V2 format: offset:2, len:33
|
publicKey := nodeInfo[2:35] // V2 format: offset:2, len:33
|
||||||
|
|
||||||
|
@ -217,77 +155,13 @@ func AddPeerIR(nodeInfo []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPeer accepts information about the network map candidate in the FrostFS
|
// AddPeer accepts information about the network map candidate in the FrostFS
|
||||||
// binary protocol format, identifies the caller and behaves depending on different
|
// binary protocol format and does nothing. Keep method because storage node
|
||||||
// conditions listed below.
|
// creates a notary transaction with this method, which produces a notary
|
||||||
//
|
// notification (implicit here).
|
||||||
// Contract settings:
|
|
||||||
//
|
|
||||||
// (1) notary-enabled
|
|
||||||
// (2) notary-disabled
|
|
||||||
//
|
|
||||||
// Callers:
|
|
||||||
//
|
|
||||||
// (a) candidate himself, if node's public key corresponds to the signer
|
|
||||||
// (b) Alphabet member
|
|
||||||
// (c) others
|
|
||||||
//
|
|
||||||
// AddPeer case-by-case behavior:
|
|
||||||
//
|
|
||||||
// (1a) does nothing
|
|
||||||
// (1b) panics. Notice that AddPeerIR MUST be used for this purpose.
|
|
||||||
// (2a) throws AddPeer notification with the provided BLOB
|
|
||||||
// (2b) accepts Alphabet vote. If the threshold of votes is reached, adds
|
|
||||||
// new element to the candidate set, and throws AddPeerSuccess notification.
|
|
||||||
// (c) panics
|
|
||||||
//
|
|
||||||
// Candidate MUST call AddPeer with "online" state in its descriptor. Alphabet
|
|
||||||
// members MUST NOT call AddPeer with any other states.
|
|
||||||
func AddPeer(nodeInfo []byte) {
|
func AddPeer(nodeInfo []byte) {
|
||||||
ctx := storage.GetContext()
|
// V2 format - offset:2, len:33
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
common.CheckWitness(nodeInfo[2:35])
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// V2 format
|
|
||||||
publicKey := nodeInfo[2:35] // offset:2, len:33
|
|
||||||
|
|
||||||
// If notary is enabled or caller is not an alphabet node,
|
|
||||||
// just emit the notification for alphabet.
|
|
||||||
if !notaryDisabled || len(nodeKey) == 0 {
|
|
||||||
common.CheckWitness(publicKey)
|
|
||||||
if notaryDisabled {
|
|
||||||
runtime.Notify("AddPeer", nodeInfo)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
candidate := Node{
|
|
||||||
BLOB: nodeInfo,
|
|
||||||
State: NodeStateOnline,
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
rawCandidate := std.Serialize(candidate)
|
|
||||||
id := crypto.Sha256(rawCandidate)
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
addToNetmap(ctx, publicKey, candidate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updates state of the network map candidate by its public key in the contract
|
// updates state of the network map candidate by its public key in the contract
|
||||||
|
@ -310,13 +184,8 @@ func updateCandidateState(ctx storage.Context, publicKey interop.PublicKey, stat
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateState accepts new state to be assigned to network map candidate
|
// UpdateState accepts new state to be assigned to network map candidate
|
||||||
// identified by the given public key, identifies the signer and behaves
|
// identified by the given public key, identifies the signer.
|
||||||
// depending on different conditions listed below.
|
// Applicable only for notary-enabled environment.
|
||||||
//
|
|
||||||
// Contract settings:
|
|
||||||
//
|
|
||||||
// (1) notary-enabled
|
|
||||||
// (2) notary-disabled
|
|
||||||
//
|
//
|
||||||
// Signers:
|
// Signers:
|
||||||
//
|
//
|
||||||
|
@ -327,13 +196,10 @@ func updateCandidateState(ctx storage.Context, publicKey interop.PublicKey, stat
|
||||||
//
|
//
|
||||||
// UpdateState case-by-case behavior:
|
// UpdateState case-by-case behavior:
|
||||||
//
|
//
|
||||||
// (1a) panics
|
// (a) panics
|
||||||
// (1b) like (1a)
|
// (b) panics
|
||||||
// (1ab) updates candidate's state in the contract storage (*), and throws
|
// (ab) updates candidate's state in the contract storage (*), and throws
|
||||||
// UpdateStateSuccess with the provided key and new state
|
// UpdateStateSuccess with the provided key and new state
|
||||||
// (2a) throws UpdateState notification with the provided key and new state
|
|
||||||
// (2b) accepts Alphabet vote. If the threshold of votes is reached, behaves
|
|
||||||
// like (1ab).
|
|
||||||
// (c) panics
|
// (c) panics
|
||||||
//
|
//
|
||||||
// (*) Candidate is removed from the candidate set if state is NodeStateOffline.
|
// (*) Candidate is removed from the candidate set if state is NodeStateOffline.
|
||||||
|
@ -349,33 +215,9 @@ func UpdateState(state NodeState, publicKey interop.PublicKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet := common.AlphabetNodes()
|
|
||||||
nodeKey := common.InnerRingInvoker(alphabet)
|
|
||||||
|
|
||||||
// If caller is not an alphabet node,
|
|
||||||
// just emit the notification for alphabet.
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
common.CheckWitness(publicKey)
|
common.CheckWitness(publicKey)
|
||||||
runtime.Notify("UpdateState", state, publicKey)
|
common.CheckAlphabetWitness()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{state, publicKey}, []byte("update"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
} else {
|
|
||||||
common.CheckWitness(publicKey)
|
|
||||||
common.CheckAlphabetWitness(common.AlphabetAddress())
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCandidateState(ctx, publicKey, state)
|
updateCandidateState(ctx, publicKey, state)
|
||||||
}
|
}
|
||||||
|
@ -388,12 +230,8 @@ func UpdateState(state NodeState, publicKey interop.PublicKey) {
|
||||||
// UpdateStateIR MUST be called by the Alphabet member only.
|
// UpdateStateIR MUST be called by the Alphabet member only.
|
||||||
func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
|
func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
if notaryDisabled {
|
|
||||||
panic("UpdateStateIR should only be called in notary-enabled environment")
|
|
||||||
}
|
|
||||||
|
|
||||||
common.CheckAlphabetWitness(common.AlphabetAddress())
|
common.CheckAlphabetWitness()
|
||||||
|
|
||||||
updateCandidateState(ctx, publicKey, state)
|
updateCandidateState(ctx, publicKey, state)
|
||||||
}
|
}
|
||||||
|
@ -409,35 +247,8 @@ func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
|
||||||
// It produces NewEpoch notification.
|
// It produces NewEpoch notification.
|
||||||
func NewEpoch(epochNum int) {
|
func NewEpoch(epochNum int) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("this method must be invoked by inner ring nodes")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{epochNum}, []byte("epoch"))
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentEpoch := storage.Get(ctx, snapshotEpoch).(int)
|
currentEpoch := storage.Get(ctx, snapshotEpoch).(int)
|
||||||
if epochNum <= currentEpoch {
|
if epochNum <= currentEpoch {
|
||||||
|
@ -535,7 +346,7 @@ func getSnapshotCount(ctx storage.Context) int {
|
||||||
//
|
//
|
||||||
// Count MUST NOT be negative.
|
// Count MUST NOT be negative.
|
||||||
func UpdateSnapshotCount(count int) {
|
func UpdateSnapshotCount(count int) {
|
||||||
common.CheckAlphabetWitness(common.AlphabetAddress())
|
common.CheckAlphabetWitness()
|
||||||
if count < 0 {
|
if count < 0 {
|
||||||
panic("count must be positive")
|
panic("count must be positive")
|
||||||
}
|
}
|
||||||
|
@ -634,34 +445,8 @@ func Config(key []byte) interface{} {
|
||||||
// only by Alphabet nodes.
|
// only by Alphabet nodes.
|
||||||
func SetConfig(id, key, val []byte) {
|
func SetConfig(id, key, val []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
common.CheckAlphabetWitness()
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
panic("invoked by non inner ring node")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig(ctx, key, val)
|
setConfig(ctx, key, val)
|
||||||
|
|
||||||
|
@ -784,26 +569,3 @@ func cleanup(ctx storage.Context, epoch int) {
|
||||||
containerContractAddr := storage.Get(ctx, containerContractKey).(interop.Hash160)
|
containerContractAddr := storage.Get(ctx, containerContractKey).(interop.Hash160)
|
||||||
contract.Call(containerContractAddr, cleanupEpochMethod, contract.All, epoch)
|
contract.Call(containerContractAddr, cleanupEpochMethod, contract.All, epoch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIRNodes(ctx storage.Context) []interop.PublicKey {
|
|
||||||
data := storage.Get(ctx, innerRingKey)
|
|
||||||
if data != nil {
|
|
||||||
return std.Deserialize(data.([]byte)).([]interop.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []interop.PublicKey{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func keysID(args []interop.PublicKey, prefix []byte) []byte {
|
|
||||||
var (
|
|
||||||
result []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
result = append(result, prefix...)
|
|
||||||
|
|
||||||
for i := range args {
|
|
||||||
result = append(result, args[i]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return crypto.Sha256(result)
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,25 +18,18 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
|
args := data.([]interface{})
|
||||||
|
if args[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(storage.GetContext(), notaryDisabledKey)
|
||||||
|
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
args := data.([]interface{})
|
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
|
||||||
notaryDisabled bool
|
|
||||||
})
|
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
|
||||||
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
|
|
||||||
if args.notaryDisabled {
|
|
||||||
common.InitVote(ctx)
|
|
||||||
runtime.Log("reputation contract notary disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log("reputation contract initialized")
|
runtime.Log("reputation contract initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,40 +53,14 @@ func Update(script []byte, manifest []byte, data interface{}) {
|
||||||
// Value contains a stable marshaled structure of DataAuditResult.
|
// Value contains a stable marshaled structure of DataAuditResult.
|
||||||
func Put(epoch int, peerID []byte, value []byte) {
|
func Put(epoch int, peerID []byte, value []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
|
|
||||||
var ( // for invocation collection without notary
|
|
||||||
alphabet []interop.PublicKey
|
|
||||||
nodeKey []byte
|
|
||||||
alphabetCall bool
|
|
||||||
)
|
|
||||||
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet = common.AlphabetNodes()
|
|
||||||
nodeKey = common.InnerRingInvoker(alphabet)
|
|
||||||
alphabetCall = len(nodeKey) != 0
|
|
||||||
} else {
|
|
||||||
multiaddr := common.AlphabetAddress()
|
multiaddr := common.AlphabetAddress()
|
||||||
alphabetCall = runtime.CheckWitness(multiaddr)
|
if !runtime.CheckWitness(multiaddr) {
|
||||||
}
|
|
||||||
|
|
||||||
if !alphabetCall {
|
|
||||||
runtime.Notify("reputationPut", epoch, peerID, value)
|
runtime.Notify("reputationPut", epoch, peerID, value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id := storageID(epoch, peerID)
|
id := storageID(epoch, peerID)
|
||||||
if notaryDisabled {
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := getReputationKey(reputationCountPrefix, id)
|
key := getReputationKey(reputationCountPrefix, id)
|
||||||
rawCnt := storage.Get(ctx, key)
|
rawCnt := storage.Get(ctx, key)
|
||||||
cnt := 0
|
cnt := 0
|
||||||
|
|
|
@ -27,14 +27,6 @@ const (
|
||||||
ErrInvalidUser = "invalid user"
|
ErrInvalidUser = "invalid user"
|
||||||
// ErrInvalidNode is thrown when node has invalid format.
|
// ErrInvalidNode is thrown when node has invalid format.
|
||||||
ErrInvalidNode = "invalid node key"
|
ErrInvalidNode = "invalid node key"
|
||||||
// ErrNodeAdmNotExist is thrown when node admin is not found.
|
|
||||||
ErrNodeAdmNotExist = "node admin not found"
|
|
||||||
// ErrClientAdmNotExist is thrown when client admin is not found.
|
|
||||||
ErrClientAdmNotExist = "client admin not found"
|
|
||||||
// ErrNodeNotExist is thrown when node is not found.
|
|
||||||
ErrNodeNotExist = "node not found"
|
|
||||||
// ErrUserNotExist is thrown when user is not found.
|
|
||||||
ErrUserNotExist = "user not found"
|
|
||||||
// ErrAccessDenied is thrown when operation is denied for caller.
|
// ErrAccessDenied is thrown when operation is denied for caller.
|
||||||
ErrAccessDenied = "access denied"
|
ErrAccessDenied = "access denied"
|
||||||
)
|
)
|
||||||
|
@ -57,18 +49,17 @@ const (
|
||||||
|
|
||||||
// _deploy function sets up initial list of inner ring public keys.
|
// _deploy function sets up initial list of inner ring public keys.
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
if isUpdate {
|
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
|
if args[0].(bool) {
|
||||||
|
panic(common.PanicMsgForNotaryDisabledEnv)
|
||||||
|
}
|
||||||
|
storage.Delete(storage.GetContext(), notaryDisabledKey)
|
||||||
|
|
||||||
|
if isUpdate {
|
||||||
common.CheckVersion(args[len(args)-1].(int))
|
common.CheckVersion(args[len(args)-1].(int))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.(struct {
|
|
||||||
notaryDisabled bool
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := storage.GetContext()
|
|
||||||
storage.Put(ctx, []byte{notaryDisabledKey}, args.notaryDisabled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update method updates contract source code and manifest. It can be invoked
|
// Update method updates contract source code and manifest. It can be invoked
|
||||||
|
@ -99,30 +90,9 @@ func Put(id []byte, ownerKey interop.PublicKey, info []byte) {
|
||||||
panic(ErrAlreadyExists)
|
panic(ErrAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
|
|
||||||
if notaryDisabled {
|
|
||||||
alphabet := common.AlphabetNodes()
|
|
||||||
nodeKey := common.InnerRingInvoker(alphabet)
|
|
||||||
if len(nodeKey) == 0 {
|
|
||||||
common.CheckWitness(ownerKey)
|
|
||||||
runtime.Notify("Put", id, ownerKey, info)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
threshold := len(alphabet)*2/3 + 1
|
|
||||||
id := common.InvokeID([]interface{}{ownerKey, info}, []byte("put"))
|
|
||||||
n := common.Vote(ctx, id, nodeKey)
|
|
||||||
if n < threshold {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
|
||||||
} else {
|
|
||||||
common.CheckOwnerWitness(ownerKey)
|
common.CheckOwnerWitness(ownerKey)
|
||||||
|
|
||||||
multiaddr := common.AlphabetAddress()
|
common.CheckAlphabetWitness()
|
||||||
common.CheckAlphabetWitness(multiaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
storage.Put(ctx, stKey, ownerKey)
|
storage.Put(ctx, stKey, ownerKey)
|
||||||
stKey[0] = infoPrefix
|
stKey[0] = infoPrefix
|
||||||
|
|
|
@ -79,20 +79,6 @@ func newFrostFSInvoker(t *testing.T, n int, config ...interface{}) (*neotest.Con
|
||||||
return e.CommitteeInvoker(h).WithSigners(alphabet), alphabet, pubs
|
return e.CommitteeInvoker(h).WithSigners(alphabet), alphabet, pubs
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFrostFS_AlphabetList(t *testing.T) {
|
|
||||||
const alphabetSize = 4
|
|
||||||
|
|
||||||
e, _, pubs := newFrostFSInvoker(t, alphabetSize)
|
|
||||||
arr := make([]stackitem.Item, len(pubs))
|
|
||||||
for i := range arr {
|
|
||||||
arr[i] = stackitem.NewStruct([]stackitem.Item{
|
|
||||||
stackitem.NewByteArray(pubs[i].Bytes()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Invoke(t, stackitem.NewArray(arr), "alphabetList")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFrostFS_InnerRingCandidate(t *testing.T) {
|
func TestFrostFS_InnerRingCandidate(t *testing.T) {
|
||||||
e, _, _ := newFrostFSInvoker(t, 4, frostfs.CandidateFeeConfigKey, int64(10))
|
e, _, _ := newFrostFSInvoker(t, 4, frostfs.CandidateFeeConfigKey, int64(10))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue