forked from TrueCloudLab/frostfs-contract
Merge pull request #8 from nspcc-dev/fix/uniq-invocations
Limit ballot lifetime and store unique inner ring invocations
This commit is contained in:
commit
94d44953e5
2 changed files with 58 additions and 22 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue