diff --git a/common/ir.go b/common/ir.go index cdb9a14..86f50b5 100644 --- a/common/ir.go +++ b/common/ir.go @@ -6,12 +6,28 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" "github.com/nspcc-dev/neo-go/pkg/interop/native/neo" "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" ) type IRNode struct { PublicKey interop.PublicKey } +const irListMethod = "innerRingList" + +// InnerRingInvoker returns public key of inner ring node that invoked contract. +// Work around for environments without notary support. +func InnerRingInvoker(ir []IRNode) interop.PublicKey { + for i := 0; i < len(ir); i++ { + node := ir[i] + if runtime.CheckWitness(node.PublicKey) { + return node.PublicKey + } + } + + return nil +} + // InnerRingNodes return list of inner ring nodes from state validator role // in side chain. func InnerRingNodes() []IRNode { @@ -20,6 +36,13 @@ func InnerRingNodes() []IRNode { return keysToNodes(list) } +// InnerRingNodesFromNetmap gets list of inner ring through +// calling "innerRingList" method of smart contract. +// Work around for environments without notary support. +func InnerRingNodesFromNetmap(sc interop.Hash160) []IRNode { + return contract.Call(sc, irListMethod, contract.ReadOnly).([]IRNode) +} + // AlphabetNodes return list of alphabet nodes from committee in side chain. func AlphabetNodes() []IRNode { list := neo.GetCommittee() diff --git a/common/vote.go b/common/vote.go index 4dad794..a29ea4d 100644 --- a/common/vote.go +++ b/common/vote.go @@ -1,11 +1,122 @@ package common import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" + "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" + "github.com/nspcc-dev/neo-go/pkg/interop/native/std" + "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/util" ) +type Ballot struct { + // ID of the voting decision. + ID []byte + + // Public keys of already voted inner ring nodes. + Voters []interop.PublicKey + + // Height of block with the last vote. + Height int +} + +const voteKey = "ballots" + +const blockDiff = 20 // change base on performance evaluation + +func InitVote(ctx storage.Context) { + SetSerialized(ctx, voteKey, []Ballot{}) +} + +// Vote adds ballot for the decision with specific 'id' and returns amount +// on unique voters for that decision. +func Vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []Ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = ledger.CurrentIndex() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + + if blockHeight-cnd.Height > blockDiff { + continue + } + + if BytesEqual(cnd.ID, id) { + voters := cnd.Voters + + for j := range voters { + if BytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = Ballot{ID: id, Voters: voters, Height: blockHeight} + found = len(voters) + } + + newCandidates = append(newCandidates, cnd) + } + + if found < 0 { + voters := []interop.PublicKey{from} + newCandidates = append(newCandidates, Ballot{ + ID: id, + Voters: voters, + Height: blockHeight}) + found = 1 + } + + SetSerialized(ctx, voteKey, newCandidates) + + return found +} + +// RemoveVotes clears ballots of the decision that has been accepted by +// inner ring nodes. +func RemoveVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []Ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !BytesEqual(cnd.ID, id) { + newCandidates = append(newCandidates, cnd) + } + } + + SetSerialized(ctx, voteKey, newCandidates) +} + +// getBallots returns deserialized slice of vote ballots. +func getBallots(ctx storage.Context) []Ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return std.Deserialize(data.([]byte)).([]Ballot) + } + + return []Ballot{} +} + // BytesEqual compares two slice of bytes by wrapping them into strings, // which is necessary with new util.Equal interop behaviour, see neo-go#1176. func BytesEqual(a []byte, b []byte) bool { return util.Equals(string(a), string(b)) } + +// InvokeID returns hashed value of prefix and args concatenation. Used to +// identify different ballots. +func InvokeID(args []interface{}, prefix []byte) []byte { + for i := range args { + arg := args[i].([]byte) + prefix = append(prefix, arg...) + } + + return crypto.Sha256(prefix) +}