Merge pull request #8 from nspcc-dev/fix/uniq-invocations

Limit ballot lifetime and store unique inner ring invocations
This commit is contained in:
Alex Vanin 2020-06-19 13:44:08 +03:00 committed by GitHub
commit 94d44953e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 22 deletions

View file

@ -1,6 +1,7 @@
package smart_contract package smart_contract
import ( import (
"github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
"github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/engine"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
@ -11,8 +12,9 @@ import (
type ( type (
ballot struct { ballot struct {
id []byte id []byte // id of the voting decision
n int n [][]byte // already voted inner ring nodes
block int // block with the last vote
} }
node struct { node struct {
@ -30,6 +32,7 @@ const (
innerRingCandidateFee = 100 * 1000 * 1000 // 10^8 innerRingCandidateFee = 100 * 1000 * 1000 // 10^8
version = 1 version = 1
voteKey = "ballots" voteKey = "ballots"
blockDiff = 20 // change base on performance evaluation
) )
func Main(op string, args []interface{}) interface{} { func Main(op string, args []interface{}) interface{} {
@ -198,7 +201,8 @@ func Main(op string, args []interface{}) interface{} {
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check) usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
threshold := len(irList)/3*2 + 1 threshold := len(irList)/3*2 + 1
if !isInnerRingRequest(irList) { irKey := innerRingInvoker(irList)
if len(irKey) == 0 {
panic("cheque: invoked by non inner ring node") panic("cheque: invoked by non inner ring node")
} }
@ -207,7 +211,7 @@ func Main(op string, args []interface{}) interface{} {
panic("cheque: non unique id") panic("cheque: non unique id")
} }
n := vote(ctx, hashID) n := vote(ctx, hashID, irKey)
if n >= threshold { if n >= threshold {
removeVotes(ctx, hashID) removeVotes(ctx, hashID)
@ -238,7 +242,8 @@ func Main(op string, args []interface{}) interface{} {
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check) usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
threshold := len(irList)/3*2 + 1 threshold := len(irList)/3*2 + 1
if !isInnerRingRequest(irList) { irKey := innerRingInvoker(irList)
if len(irKey) == 0 {
panic("innerRingUpdate: invoked by non inner ring node") panic("innerRingUpdate: invoked by non inner ring node")
} }
@ -249,7 +254,7 @@ func Main(op string, args []interface{}) interface{} {
chequeHash := crypto.Hash256(data) chequeHash := crypto.Hash256(data)
n := vote(ctx, chequeHash) n := vote(ctx, chequeHash, irKey)
if n >= threshold { if n >= threshold {
removeVotes(ctx, chequeHash) removeVotes(ctx, chequeHash)
@ -429,38 +434,54 @@ func delSerializedIR(ctx storage.Context, key string, value []byte) bool {
return false return false
} }
// isInnerRingRequest returns true if contract was invoked by inner ring node. // innerRingInvoker returns public key of inner ring node that invoked contract.
func isInnerRingRequest(irList []node) bool { func innerRingInvoker(ir []node) []byte {
for i := 0; i < len(irList); i++ { for i := 0; i < len(ir); i++ {
irNode := irList[i] node := ir[i]
if runtime.CheckWitness(node.pub) {
if runtime.CheckWitness(irNode.pub) { return node.pub
return true
} }
} }
return false return nil
} }
// todo: votes must be from unique inner ring nods func vote(ctx storage.Context, id, from []byte) int {
func vote(ctx storage.Context, id []byte) int {
var ( var (
newCandidates = []ballot{} newCandidates []ballot
candidates = getSerialized(ctx, voteKey).([]ballot) candidates = getSerialized(ctx, voteKey).([]ballot)
found = -1 found = -1
blockHeight = blockchain.GetHeight()
) )
for i := 0; i < len(candidates); i++ { for i := 0; i < len(candidates); i++ {
cnd := candidates[i] cnd := candidates[i]
if util.Equals(cnd.id, id) { if util.Equals(cnd.id, id) {
cnd = ballot{id: id, n: cnd.n + 1} voters := cnd.n
found = cnd.n
for j := range voters {
if util.Equals(voters[j], from) {
return len(voters)
} }
}
voters = append(voters, from)
cnd = ballot{id: id, n: voters, block: blockHeight}
found = len(voters)
}
// do not add old ballots, they are invalid
if blockHeight-cnd.block <= blockDiff {
newCandidates = append(newCandidates, cnd) newCandidates = append(newCandidates, cnd)
} }
}
if found < 0 { if found < 0 {
newCandidates = append(newCandidates, ballot{id: id, n: 1}) voters := [][]byte{from}
newCandidates = append(newCandidates, ballot{
id: id,
n: voters,
block: blockHeight})
found = 1 found = 1
} }

View file

@ -44,6 +44,7 @@ func TestContract(t *testing.T) {
contract := initGoContract(t, contractTemplate, nodeCount) contract := initGoContract(t, contractTemplate, nodeCount)
plug.cgas[contractStr] = util.Fixed8FromInt64(1000) plug.cgas[contractStr] = util.Fixed8FromInt64(1000)
plug.invokeKey = crypto.MarshalPublicKey(&contract.privs[0].PublicKey)
var args []interface{} var args []interface{}
for i := range contract.privs { for i := range contract.privs {
@ -107,6 +108,7 @@ func TestContract(t *testing.T) {
// call it threshold amount of times // call it threshold amount of times
for i := 0; i < 2*nodeCount/3+1; i++ { for i := 0; i < 2*nodeCount/3+1; i++ {
plug.invokeKey = crypto.MarshalPublicKey(&contract.privs[i].PublicKey)
v := initVM(contract, plug) v := initVM(contract, plug)
loadArg(t, v, "Cheque", []interface{}{id, user, int(gas), lockAcc}) loadArg(t, v, "Cheque", []interface{}{id, user, int(gas), lockAcc})
@ -260,6 +262,7 @@ type storagePlugin struct {
interops map[uint32]vm.InteropFunc interops map[uint32]vm.InteropFunc
storageOps []kv storageOps []kv
notify []interface{} notify []interface{}
invokeKey []byte
} }
func newStoragePlugin(t *testing.T) *storagePlugin { func newStoragePlugin(t *testing.T) *storagePlugin {
@ -290,6 +293,7 @@ func newStoragePlugin(t *testing.T) *storagePlugin {
} }
s.interops[getID("System.ExecutionEngine.GetScriptContainer")] = s.GetScriptContainer s.interops[getID("System.ExecutionEngine.GetScriptContainer")] = s.GetScriptContainer
s.interops[getID("Neo.Transaction.GetHash")] = s.GetHash s.interops[getID("Neo.Transaction.GetHash")] = s.GetHash
s.interops[getID("Neo.Blockchain.GetHeight")] = s.GetHeight
return s return s
} }
@ -402,7 +406,13 @@ func (s *storagePlugin) GetTrigger(v *vm.VM) error {
} }
func (s *storagePlugin) CheckWitness(v *vm.VM) error { func (s *storagePlugin) CheckWitness(v *vm.VM) error {
key := v.Estack().Pop().Value().([]byte)
if bytes.Equal(key, s.invokeKey) {
v.Estack().PushVal(true) v.Estack().PushVal(true)
} else {
v.Estack().PushVal(false)
}
return nil return nil
} }
@ -423,6 +433,11 @@ func (s *storagePlugin) GetHash(v *vm.VM) error {
return nil return nil
} }
func (s *storagePlugin) GetHeight(v *vm.VM) error {
v.Estack().PushVal(42)
return nil
}
func (s *storagePlugin) logStorage(op string, key, value []byte) { func (s *storagePlugin) logStorage(op string, key, value []byte) {
s.storageOps = append(s.storageOps, kv{ s.storageOps = append(s.storageOps, kv{
Operation: op, Operation: op,