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:
alexvanin 2020-05-27 15:23:44 +03:00
parent 78b8af8f83
commit 4fbfa1bc98

View file

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