forked from TrueCloudLab/frostfs-contract
Collect "InnerRingUpdate" calls from inner ring nodes
Inner ring nodes do not collect signatures for the cheque now. Instead they invoke "InnerRingUpdate" method and smart-contract checks if method was called from inner ring node. Then it accepts cheque if there were 2/3n+1 invokes.
This commit is contained in:
parent
78b8af8f83
commit
4fbfa1bc98
1 changed files with 125 additions and 50 deletions
|
@ -8,21 +8,29 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
pub []byte
|
||||
}
|
||||
type (
|
||||
ballot struct {
|
||||
id []byte
|
||||
n int
|
||||
}
|
||||
|
||||
type check struct {
|
||||
node struct {
|
||||
pub []byte
|
||||
}
|
||||
|
||||
check struct {
|
||||
id []byte
|
||||
height []byte
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// GAS NEP-5 HASH
|
||||
const tokenHash = "\x77\xea\x59\x6b\x7a\xdf\x7e\x4d\xd1\x40\x76\x97\x31\xb7\xd2\xf0\xe0\x6b\xcd\x9b"
|
||||
|
||||
const innerRingCandidateFee = 100 * 1000 * 1000 // 10^8
|
||||
|
||||
const version = 1
|
||||
const (
|
||||
// GAS NEP-5 HASH
|
||||
tokenHash = "\x77\xea\x59\x6b\x7a\xdf\x7e\x4d\xd1\x40\x76\x97\x31\xb7\xd2\xf0\xe0\x6b\xcd\x9b"
|
||||
innerRingCandidateFee = 100 * 1000 * 1000 // 10^8
|
||||
version = 1
|
||||
voteKey = "ballots"
|
||||
)
|
||||
|
||||
func Main(op string, args []interface{}) interface{} {
|
||||
// The trigger determines whether this smart-contract is being
|
||||
|
@ -75,6 +83,9 @@ func Main(op string, args []interface{}) interface{} {
|
|||
storage.Put(ctx, "UsedVerifCheckList", data)
|
||||
storage.Put(ctx, "InnerRingCandidates", data)
|
||||
|
||||
data = runtime.Serialize([]ballot{})
|
||||
storage.Put(ctx, voteKey, data)
|
||||
|
||||
return true
|
||||
case "InnerRingList":
|
||||
irList := getSerialized(ctx, "InnerRingList").([]node)
|
||||
|
@ -181,23 +192,26 @@ func Main(op string, args []interface{}) interface{} {
|
|||
listSize := listItemCount * 33
|
||||
|
||||
offset := 8 + 2 + listSize
|
||||
msg := data[:offset]
|
||||
message := crypto.SHA256(msg)
|
||||
|
||||
irList := getSerialized(ctx, "InnerRingList").([]node)
|
||||
if !verifySignatures(irList, data, message, offset) {
|
||||
panic("can't verify signatures")
|
||||
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
|
||||
threshold := len(irList)/3*2 + 1
|
||||
|
||||
if !isInnerRingRequest(irList) {
|
||||
panic("innerRingUpdate: invoked by non inner ring node")
|
||||
}
|
||||
|
||||
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
|
||||
c := check{
|
||||
id: id,
|
||||
height: []byte{1}, // ir update cheques use height as id
|
||||
}
|
||||
c := check{id: id}
|
||||
if containsCheck(usedList, c) {
|
||||
panic("check has already been used")
|
||||
panic("innerRingUpdate: cheque has non unique id")
|
||||
}
|
||||
|
||||
chequeHash := crypto.Hash256(data)
|
||||
|
||||
n := vote(ctx, chequeHash)
|
||||
if n >= threshold {
|
||||
removeVotes(ctx, chequeHash)
|
||||
|
||||
candidates := getSerialized(ctx, "InnerRingCandidates").([]node)
|
||||
offset = 10
|
||||
newIR := []node{}
|
||||
|
@ -236,6 +250,9 @@ func Main(op string, args []interface{}) interface{} {
|
|||
storage.Put(ctx, "InnerRingList", newIRData)
|
||||
putSerialized(ctx, "UsedVerifCheckList", c)
|
||||
|
||||
runtime.Notify("InnerRingUpdate", c.id, newIRData)
|
||||
}
|
||||
|
||||
return true
|
||||
case "IsInnerRing":
|
||||
if len(args) != 1 {
|
||||
|
@ -405,3 +422,61 @@ func verifySignatures(irList []node, data []byte, message []byte, offset int) bo
|
|||
runtime.Log("not enough verified signatures")
|
||||
return false
|
||||
}
|
||||
|
||||
// isInnerRingRequest returns true if contract was invoked by inner ring node.
|
||||
func isInnerRingRequest(irList []node) bool {
|
||||
for i := 0; i < len(irList); i++ {
|
||||
irNode := irList[i]
|
||||
|
||||
if runtime.CheckWitness(irNode.pub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// todo: votes must be from unique inner ring nods
|
||||
func vote(ctx storage.Context, id []byte) int {
|
||||
var (
|
||||
newCandidates = []ballot{}
|
||||
candidates = getSerialized(ctx, voteKey).([]ballot)
|
||||
found = -1
|
||||
)
|
||||
|
||||
for i := 0; i < len(candidates); i++ {
|
||||
cnd := candidates[i]
|
||||
if util.Equals(cnd.id, id) {
|
||||
cnd = ballot{id: id, n: cnd.n + 1}
|
||||
found = cnd.n
|
||||
}
|
||||
newCandidates = append(newCandidates, cnd)
|
||||
}
|
||||
|
||||
if found < 0 {
|
||||
newCandidates = append(newCandidates, ballot{id: id, n: 1})
|
||||
found = 1
|
||||
}
|
||||
|
||||
data := runtime.Serialize(newCandidates)
|
||||
storage.Put(ctx, voteKey, data)
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func removeVotes(ctx storage.Context, id []byte) {
|
||||
var (
|
||||
newCandidates = []ballot{}
|
||||
candidates = getSerialized(ctx, voteKey).([]ballot)
|
||||
)
|
||||
|
||||
for i := 0; i < len(candidates); i++ {
|
||||
cnd := candidates[i]
|
||||
if !util.Equals(cnd.id, id) {
|
||||
newCandidates = append(newCandidates, cnd)
|
||||
}
|
||||
}
|
||||
|
||||
data := runtime.Serialize(newCandidates)
|
||||
storage.Put(ctx, voteKey, data)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue