From dc84dde87b74339885604eb5c7226f7e4194a1c3 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Mon, 15 Jun 2020 18:54:24 +0300 Subject: [PATCH 1/2] Limit ballot lifetime and store unique inner ring invocations --- neofs_contract.go | 63 +++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/neofs_contract.go b/neofs_contract.go index 6d70638..f3cd9cd 100644 --- a/neofs_contract.go +++ b/neofs_contract.go @@ -1,6 +1,7 @@ package smart_contract 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/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" @@ -11,8 +12,9 @@ import ( type ( ballot struct { - id []byte - n int + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote } node struct { @@ -30,6 +32,7 @@ const ( innerRingCandidateFee = 100 * 1000 * 1000 // 10^8 version = 1 voteKey = "ballots" + blockDiff = 20 // change base on performance evaluation ) func Main(op string, args []interface{}) interface{} { @@ -198,7 +201,8 @@ func Main(op string, args []interface{}) interface{} { usedList := getSerialized(ctx, "UsedVerifCheckList").([]check) threshold := len(irList)/3*2 + 1 - if !isInnerRingRequest(irList) { + irKey := innerRingInvoker(irList) + if len(irKey) == 0 { panic("cheque: invoked by non inner ring node") } @@ -207,7 +211,7 @@ func Main(op string, args []interface{}) interface{} { panic("cheque: non unique id") } - n := vote(ctx, hashID) + n := vote(ctx, hashID, irKey) if n >= threshold { removeVotes(ctx, hashID) @@ -238,7 +242,8 @@ func Main(op string, args []interface{}) interface{} { usedList := getSerialized(ctx, "UsedVerifCheckList").([]check) threshold := len(irList)/3*2 + 1 - if !isInnerRingRequest(irList) { + irKey := innerRingInvoker(irList) + if len(irKey) == 0 { panic("innerRingUpdate: invoked by non inner ring node") } @@ -249,7 +254,7 @@ func Main(op string, args []interface{}) interface{} { chequeHash := crypto.Hash256(data) - n := vote(ctx, chequeHash) + n := vote(ctx, chequeHash, irKey) if n >= threshold { removeVotes(ctx, chequeHash) @@ -429,38 +434,54 @@ func delSerializedIR(ctx storage.Context, key string, value []byte) bool { 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 +// isInnerRingInvoker returns public key of inner ring node that invoked contract. +func innerRingInvoker(ir []node) []byte { + for i := 0; i < len(ir); i++ { + node := ir[i] + if runtime.CheckWitness(node.pub) { + return node.pub } } - return false + return nil } -// todo: votes must be from unique inner ring nods -func vote(ctx storage.Context, id []byte) int { +func vote(ctx storage.Context, id, from []byte) int { var ( - newCandidates = []ballot{} + newCandidates []ballot candidates = getSerialized(ctx, voteKey).([]ballot) found = -1 + blockHeight = blockchain.GetHeight() ) 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 + voters := 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 { - newCandidates = append(newCandidates, ballot{id: id, n: 1}) + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) found = 1 } From f10d346925de9aa40e614ab3e2743cd4df923d1d Mon Sep 17 00:00:00 2001 From: alexvanin Date: Wed, 17 Jun 2020 12:54:41 +0300 Subject: [PATCH 2/2] Fix tests for unique inner ring invoke checker --- neofs_contract.go | 2 +- neofs_contract_test.go | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/neofs_contract.go b/neofs_contract.go index f3cd9cd..56d426a 100644 --- a/neofs_contract.go +++ b/neofs_contract.go @@ -434,7 +434,7 @@ func delSerializedIR(ctx storage.Context, key string, value []byte) bool { return false } -// isInnerRingInvoker returns public key of inner ring node that invoked contract. +// innerRingInvoker returns public key of inner ring node that invoked contract. func innerRingInvoker(ir []node) []byte { for i := 0; i < len(ir); i++ { node := ir[i] diff --git a/neofs_contract_test.go b/neofs_contract_test.go index 75719f5..e9a2736 100644 --- a/neofs_contract_test.go +++ b/neofs_contract_test.go @@ -44,6 +44,7 @@ func TestContract(t *testing.T) { contract := initGoContract(t, contractTemplate, nodeCount) plug.cgas[contractStr] = util.Fixed8FromInt64(1000) + plug.invokeKey = crypto.MarshalPublicKey(&contract.privs[0].PublicKey) var args []interface{} for i := range contract.privs { @@ -107,6 +108,7 @@ func TestContract(t *testing.T) { // call it threshold amount of times for i := 0; i < 2*nodeCount/3+1; i++ { + plug.invokeKey = crypto.MarshalPublicKey(&contract.privs[i].PublicKey) v := initVM(contract, plug) loadArg(t, v, "Cheque", []interface{}{id, user, int(gas), lockAcc}) @@ -260,6 +262,7 @@ type storagePlugin struct { interops map[uint32]vm.InteropFunc storageOps []kv notify []interface{} + invokeKey []byte } 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("Neo.Transaction.GetHash")] = s.GetHash + s.interops[getID("Neo.Blockchain.GetHeight")] = s.GetHeight return s } @@ -402,7 +406,13 @@ func (s *storagePlugin) GetTrigger(v *vm.VM) error { } func (s *storagePlugin) CheckWitness(v *vm.VM) error { - v.Estack().PushVal(true) + key := v.Estack().Pop().Value().([]byte) + if bytes.Equal(key, s.invokeKey) { + v.Estack().PushVal(true) + } else { + v.Estack().PushVal(false) + } + return nil } @@ -423,6 +433,11 @@ func (s *storagePlugin) GetHash(v *vm.VM) error { 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) { s.storageOps = append(s.storageOps, kv{ Operation: op,