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 {
pub []byte
}
check struct {
id []byte id []byte
height []byte height []byte
} }
)
// GAS NEP-5 HASH const (
const tokenHash = "\x77\xea\x59\x6b\x7a\xdf\x7e\x4d\xd1\x40\x76\x97\x31\xb7\xd2\xf0\xe0\x6b\xcd\x9b" // 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"
const innerRingCandidateFee = 100 * 1000 * 1000 // 10^8 innerRingCandidateFee = 100 * 1000 * 1000 // 10^8
version = 1
const 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,23 +192,26 @@ 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) { usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
panic("can't verify signatures") 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}
c := check{
id: id,
height: []byte{1}, // ir update cheques use height as id
}
if containsCheck(usedList, c) { 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) candidates := getSerialized(ctx, "InnerRingCandidates").([]node)
offset = 10 offset = 10
newIR := []node{} newIR := []node{}
@ -236,6 +250,9 @@ func Main(op string, args []interface{}) interface{} {
storage.Put(ctx, "InnerRingList", newIRData) storage.Put(ctx, "InnerRingList", newIRData)
putSerialized(ctx, "UsedVerifCheckList", c) putSerialized(ctx, "UsedVerifCheckList", c)
runtime.Notify("InnerRingUpdate", c.id, newIRData)
}
return true return true
case "IsInnerRing": case "IsInnerRing":
if len(args) != 1 { 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") 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)
}