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"
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type node struct {
|
type (
|
||||||
pub []byte
|
ballot struct {
|
||||||
}
|
id []byte
|
||||||
|
n int
|
||||||
|
}
|
||||||
|
|
||||||
type check struct {
|
node struct {
|
||||||
id []byte
|
pub []byte
|
||||||
height []byte
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// GAS NEP-5 HASH
|
check struct {
|
||||||
const tokenHash = "\x77\xea\x59\x6b\x7a\xdf\x7e\x4d\xd1\x40\x76\x97\x31\xb7\xd2\xf0\xe0\x6b\xcd\x9b"
|
id []byte
|
||||||
|
height []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const innerRingCandidateFee = 100 * 1000 * 1000 // 10^8
|
const (
|
||||||
|
// GAS NEP-5 HASH
|
||||||
const version = 1
|
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{} {
|
func Main(op string, args []interface{}) interface{} {
|
||||||
// The trigger determines whether this smart-contract is being
|
// 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, "UsedVerifCheckList", data)
|
||||||
storage.Put(ctx, "InnerRingCandidates", data)
|
storage.Put(ctx, "InnerRingCandidates", data)
|
||||||
|
|
||||||
|
data = runtime.Serialize([]ballot{})
|
||||||
|
storage.Put(ctx, voteKey, data)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
case "InnerRingList":
|
case "InnerRingList":
|
||||||
irList := getSerialized(ctx, "InnerRingList").([]node)
|
irList := getSerialized(ctx, "InnerRingList").([]node)
|
||||||
|
@ -181,60 +192,66 @@ func Main(op string, args []interface{}) interface{} {
|
||||||
listSize := listItemCount * 33
|
listSize := listItemCount * 33
|
||||||
|
|
||||||
offset := 8 + 2 + listSize
|
offset := 8 + 2 + listSize
|
||||||
msg := data[:offset]
|
|
||||||
message := crypto.SHA256(msg)
|
|
||||||
|
|
||||||
irList := getSerialized(ctx, "InnerRingList").([]node)
|
irList := getSerialized(ctx, "InnerRingList").([]node)
|
||||||
if !verifySignatures(irList, data, message, offset) {
|
|
||||||
panic("can't verify signatures")
|
|
||||||
}
|
|
||||||
|
|
||||||
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
|
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
|
||||||
c := check{
|
threshold := len(irList)/3*2 + 1
|
||||||
id: id,
|
|
||||||
height: []byte{1}, // ir update cheques use height as id
|
if !isInnerRingRequest(irList) {
|
||||||
|
panic("innerRingUpdate: invoked by non inner ring node")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c := check{id: id}
|
||||||
if containsCheck(usedList, c) {
|
if containsCheck(usedList, c) {
|
||||||
panic("check has already been used")
|
panic("innerRingUpdate: cheque has non unique id")
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates := getSerialized(ctx, "InnerRingCandidates").([]node)
|
chequeHash := crypto.Hash256(data)
|
||||||
offset = 10
|
|
||||||
newIR := []node{}
|
|
||||||
|
|
||||||
loop:
|
n := vote(ctx, chequeHash)
|
||||||
for i := 0; i < listItemCount; i, offset = i+1, offset+33 {
|
if n >= threshold {
|
||||||
pub := data[offset : offset+33]
|
removeVotes(ctx, chequeHash)
|
||||||
|
|
||||||
for j := 0; j < len(irList); j++ {
|
candidates := getSerialized(ctx, "InnerRingCandidates").([]node)
|
||||||
n := irList[j]
|
offset = 10
|
||||||
if util.Equals(n.pub, pub) {
|
newIR := []node{}
|
||||||
newIR = append(newIR, n)
|
|
||||||
continue loop
|
loop:
|
||||||
|
for i := 0; i < listItemCount; i, offset = i+1, offset+33 {
|
||||||
|
pub := data[offset : offset+33]
|
||||||
|
|
||||||
|
for j := 0; j < len(irList); j++ {
|
||||||
|
n := irList[j]
|
||||||
|
if util.Equals(n.pub, pub) {
|
||||||
|
newIR = append(newIR, n)
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < len(candidates); j++ {
|
||||||
|
n := candidates[j]
|
||||||
|
if util.Equals(n.pub, pub) {
|
||||||
|
newIR = append(newIR, n)
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for j := 0; j < len(candidates); j++ {
|
if len(newIR) != listItemCount {
|
||||||
n := candidates[j]
|
panic("new inner ring wasn't processed correctly")
|
||||||
if util.Equals(n.pub, pub) {
|
|
||||||
newIR = append(newIR, n)
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(newIR) != listItemCount {
|
for i := 0; i < len(newIR); i++ {
|
||||||
panic("new inner ring wasn't processed correctly")
|
n := newIR[i]
|
||||||
}
|
delSerializedIR(ctx, "InnerRingCandidates", n.pub)
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < len(newIR); i++ {
|
newIRData := runtime.Serialize(newIR)
|
||||||
n := newIR[i]
|
storage.Put(ctx, "InnerRingList", newIRData)
|
||||||
delSerializedIR(ctx, "InnerRingCandidates", n.pub)
|
putSerialized(ctx, "UsedVerifCheckList", c)
|
||||||
}
|
|
||||||
|
|
||||||
newIRData := runtime.Serialize(newIR)
|
runtime.Notify("InnerRingUpdate", c.id, newIRData)
|
||||||
storage.Put(ctx, "InnerRingList", newIRData)
|
}
|
||||||
putSerialized(ctx, "UsedVerifCheckList", c)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
case "IsInnerRing":
|
case "IsInnerRing":
|
||||||
|
@ -405,3 +422,61 @@ func verifySignatures(irList []node, data []byte, message []byte, offset int) bo
|
||||||
runtime.Log("not enough verified signatures")
|
runtime.Log("not enough verified signatures")
|
||||||
return false
|
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