From 115074225ac40a1f36a69f6bf18c3a8d15da8899 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 6 Aug 2020 16:35:59 +0200 Subject: [PATCH] Remote test and other stuff --- NPoS/.gitignore | 2 + NPoS/ComplicatedPhragmén.py | 80 ++++++++++++------------- NPoS/{simplePhragmén.py => npos.py} | 41 +++++-------- NPoS/remote_test.py | 92 +++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 67 deletions(-) create mode 100644 NPoS/.gitignore rename NPoS/{simplePhragmén.py => npos.py} (93%) create mode 100644 NPoS/remote_test.py diff --git a/NPoS/.gitignore b/NPoS/.gitignore new file mode 100644 index 0000000..9f94e5d --- /dev/null +++ b/NPoS/.gitignore @@ -0,0 +1,2 @@ +.vscode +__pycache__ diff --git a/NPoS/ComplicatedPhragmén.py b/NPoS/ComplicatedPhragmén.py index 561634d..f61efea 100644 --- a/NPoS/ComplicatedPhragmén.py +++ b/NPoS/ComplicatedPhragmén.py @@ -7,7 +7,7 @@ class edge: #self.index #self.voterindex #self.canindex - + class voter: def __init__(self,votetuple): @@ -83,7 +83,7 @@ class assignment: def unelect(self,candidate): self.canelected[candidate.index]=False self.electedcandidates.remove(candidate) - + def setuplists(votelist): #Instead of Python's dict here, you can use anything with O(log n) addition and lookup. #We can also use a hashmap, by generating a random constant r and useing H(canid+r) @@ -94,7 +94,7 @@ def setuplists(votelist): numcandidates=0 numvoters=0 numedges=0 - + #Get an array of candidates that we can reference these by index for nom in voterlist: nom.index=numvoters @@ -115,13 +115,13 @@ def setuplists(votelist): edge.canindex=numcandidates numcandidates += 1 return(voterlist,candidatearray) - + def seqPhragmén(votelist,numtoelect): nomlist,candidates=setuplists(votelist) #creating an assignment now also computes the total possible stake for each candidate a=assignment(nomlist,candidates) - + for round in range(numtoelect): for canindex in range(len(candidates)): if not a.canelected[canindex]: @@ -172,8 +172,8 @@ def calculateScores(a,cutoff): #if not a.canelected[canindex]: #print(a.candidates[canindex].canid," has score ", a.canscore[canindex]," with cutoff ",cutoff) #print("Approval stake: ", a.canapproval[canindex]," support: ",a.cansupport[canindex]," denominator: ",a.canscoredenominator[canindex], " numerator: ",a.canscorenumerator[canindex]) - - + + def calculateMaxScore(a): supportList=[a.cansupport[can.index] for can in a.electedcandidates] supportList.append(0.0) @@ -249,7 +249,7 @@ def equalise(a, nom, tolerance): # Attempts to redistribute the nominators budget between elected validators # Assumes that all elected validators have backedstake set correctly # returns the max difference in stakes between sup - + electededges=[edge for edge in nom.edges if a.canelected[edge.canindex]] if len(electededges)==0: return 0.0 @@ -310,7 +310,7 @@ def seqPhragménwithpostprocessing(votelist,numtoelect, ratio=1): def factor3point15(votelist, numtoelect,tolerance=0.1): nomlist,candidates=setuplists(votelist) a=assignment(nomlist,candidates) - + for round in range(numtoelect): bestcandidate,score=calculateMaxScore(a) insertWithScore(a,bestcandidate, score) @@ -325,7 +325,7 @@ def maybecandidate(a,newcandidate,shouldremoveworst, tolerance): if shouldremoveworst: worstcanidate =min(electedcandidates, key = lambda x: b.cansupport[x.index]) b.unelect(worstcandidate) - b.elect(newcandidate) + b.elect(newcandidate) equaliseall(b,100000000,tolerance) newvalue=min([b.cansupport[candidate.index] for candidate in b.electedcandidates]) return b, newvalue @@ -395,7 +395,7 @@ def binarysearchfeasible(votelist,numtoelect,tolerance=0.1): else: targetvalue=currentvalue #print(targetvalue,lastgoodindex, maxvalue,bestknownvalue,currentvalue) - + for round in range(lastgoodindex+1,numtoelect): # First try maxscore candidate, which will help with PJR @@ -460,22 +460,22 @@ def doall(votelist, numtoelect, listvoters=True, listcans=True): if listvoters: print("Votes ",votelist) alglist=[(approvalvoting,"Approval voting"), (seqPhragmén, "Sequential Phragmén"), - (seqPhragménwithpostprocessing, "Sequential Phragmén with post processing"), + (seqPhragménwithpostprocessing, "Sequential Phragmén with post processing"), (factor3point15, "The factor 3.15 thing"), (binarysearchfeasible,"Factor 2 by binary search"), (SFFB18, "SFFB18")] for alg,name in alglist: - st=time.perf_counter() + st=time.perf_counter() a = alg(votelist,numtoelect) - et=time.perf_counter() + et=time.perf_counter() print(name, " gives") printresult(a,listvoters,listcans) print(" in ",et-st," seconds.") print() - + def example1(): votelist=[("A",10.0,["X","Y"]),("B",20.0,["X","Z"]),("C",30.0,["Y","Z"])] doall(votelist,2) - + def example2(): # Approval voting does not do so well for this kind of thing. @@ -491,7 +491,7 @@ def example3(): bluevoters = [("BlueV"+str(i),20.0,blueparty) for i in range(20)] votelist= redvoters+bluevoters doall(votelist, 20, False) - + def example4(): #Now we want an example where seq Phragmén is not so good. @@ -518,7 +518,7 @@ def example6(): ("M",50.0, ["M"])] print("Votes ",votelist) doall(votelist,5) - + def exampleLine(): votelist = [ ("a", 2000, ["A"]), @@ -530,7 +530,7 @@ def exampleLine(): ("g", 1000, ["F","G"]) ] doall(votelist,7) - + def ri(vals=20,noms=2000, votesize=10): #Let's try a random instance candidates=["Val"+str(i) for i in range(vals)] @@ -560,28 +560,28 @@ def riparty(vals=200,noms=2000, votesize=10,seed=1): - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/NPoS/simplePhragmén.py b/NPoS/npos.py similarity index 93% rename from NPoS/simplePhragmén.py rename to NPoS/npos.py index 96668c4..eec386a 100644 --- a/NPoS/simplePhragmén.py +++ b/NPoS/npos.py @@ -1,5 +1,4 @@ import unittest -import sys def print_list(ll): @@ -16,7 +15,7 @@ class edge: self.candidate = None def __str__(self): - return "Edge({}, weight = {})".format( + return "Edge({}, weight = {:,})".format( self.validator_id, self.weight, ) @@ -30,7 +29,7 @@ class nominator: self.load = 0 def __str__(self): - return "Nominator({}, budget = {}, load = {}, edges = {})".format( + return "Nominator({}, budget = {:,}, load = {}, edges = {})".format( self.nominator_id, self.budget, self.load, @@ -49,12 +48,10 @@ class candidate: self.scoredenom = 0 def __str__(self): - return "Candidate({}, approval = {}, backed = {}, score = {}, scoredenom = {})".format( + return "Candidate({}, approval = {:,}, backed_stake = {:,})".format( self.validator_id, self.approval_stake, - self.backed_stake, - self.score, - self.scoredenom, + int(self.backed_stake), ) @@ -194,7 +191,6 @@ def calculateMaxScoreNoCutoff(nomlist, candidates): for candidate in candidates: if candidate.approval_stake > 0.0: candidate.score = candidate.approval_stake / candidate.scoredenom - print("score of {} in this round is {}".format(candidate.validator_id, candidate.score)) if not candidate.elected and candidate.score > best_score: best_score = candidate.score best_candidate = candidate @@ -209,19 +205,23 @@ def electWithScore(nomlist, elected_candidate, cutoff): for new_edge in nom.edges: if new_edge.validator_id == elected_candidate.validator_id: used_budget = sum([edge.weight for edge in nom.edges]) + new_edge.weight = nom.budget - used_budget elected_candidate.backed_stake += nom.budget - used_budget + for edge in nom.edges: if edge.validator_id != elected_candidate.validator_id and edge.weight > 0.0: if edge.candidate.backed_stake > cutoff: stake_to_take = edge.weight * cutoff / edge.candidate.backed_stake + new_edge.weight += stake_to_take - edge.weight -= stake_to_take - edge.candidate.backed_stake -= stake_to_take elected_candidate.backed_stake += stake_to_take + edge.weight -= stake_to_take + edge.candidate.backed_stake -= stake_to_take -def balanced_heuristic(votelist, num_to_elect, tolerance=0.1): + +def phragmms(votelist, num_to_elect, tolerance=0.1): nomlist, candidates = setuplists(votelist) calculate_approval(nomlist) @@ -229,16 +229,12 @@ def balanced_heuristic(votelist, num_to_elect, tolerance=0.1): for round in range(num_to_elect): (elected_candidate, score) = calculateMaxScoreNoCutoff(nomlist, candidates) electWithScore(nomlist, elected_candidate, score) - print("####\nRound {} max candidate {} with score {}".format(round, elected_candidate.validator_id, score)) - print_list(nomlist) elected_candidate.elected = True elected_candidates.append(elected_candidate) elected_candidate.electedpos = round equalise_all(nomlist, 10, tolerance) - print("After balancing") - print_list(nomlist) return nomlist, elected_candidates @@ -309,7 +305,7 @@ def run_and_print_all(votelist, to_elect): printresult(nomlist, elected_candidates) print("\nBalanced Heuristic (3.15 factor) gives") - nomlist, elected_candidates = balanced_heuristic(votelist, to_elect) + nomlist, elected_candidates = phragmms(votelist, to_elect) printresult(nomlist, elected_candidates) @@ -367,7 +363,7 @@ class MaxScoreTest(unittest.TestCase): (20, 20.0, [1, 3]), (30, 30.0, [2, 3]), ] - nomlist, winners = balanced_heuristic(votelist, 2, 0) + nomlist, winners = phragmms(votelist, 2, 0) self.assertEqual(winners[0].validator_id, 3) self.assertEqual(winners[1].validator_id, 2) @@ -385,7 +381,7 @@ class MaxScoreTest(unittest.TestCase): (130, 1000, [61, 71]), ] - nomlist, winners = balanced_heuristic(votelist, 4, 0) + nomlist, winners = phragmms(votelist, 4, 0) self.assertEqual(winners[0].validator_id, 11) self.assertEqual(winners[0].backed_stake, 3000) @@ -430,12 +426,3 @@ def main(): # example1() example2() # example3() - - -if len(sys.argv) >= 2: - if sys.argv[1] == "run": - main() - else: - unittest.main() -else: - unittest.main() diff --git a/NPoS/remote_test.py b/NPoS/remote_test.py new file mode 100644 index 0000000..94a773f --- /dev/null +++ b/NPoS/remote_test.py @@ -0,0 +1,92 @@ +import npos +import pprint +from substrateinterface import SubstrateInterface +from substrateinterface.utils.ss58 import ss58_decode, ss58_encode + + +pp = pprint.PrettyPrinter(indent=4) + +substrate = SubstrateInterface( + url="ws://localhost:9944", + address_type=0, + type_registry={'types': { + "StakingLedger": { + "type": "struct", + "type_mapping": [ + ["stash", "AccountId"], + ["total", "Compact"], + ["active", "Compact"], + ["unlocking", "Vec>"], + ["claimedReward", "Vec"] + ] + }, + } + }, + type_registry_preset='polkadot', +) + +head = substrate.get_chain_finalised_head() + + +def get_candidates(): + prefix = substrate.generate_storage_hash("Staking", "Validators") + pairs = substrate.rpc_request(method="state_getPairs", params=[prefix, head])['result'] + last_32_bytes = list(map(lambda p: "0x" + p[0][-64:], pairs)) + return list(map(lambda k: ss58_encode(k), last_32_bytes)) + + +def get_nominators(): + prefix = substrate.generate_storage_hash("Staking", "Nominators") + pairs = substrate.rpc_request(method="state_getPairs", params=[prefix, head])['result'] + + nominators = list( + map(lambda p: ("0x" + p[0][-64:], substrate.decode_scale("Nominations", p[1])['targets']), pairs) + ) + + nominators = list(map(lambda x: ( + ss58_encode(x[0], substrate.address_type), + x[1], + ), nominators)) + + return list(map(lambda x: ( + x[0], + get_backing_stake_of(x[0]), + [ss58_encode(acc, substrate.address_type) for acc in x[1]], + ), nominators)) + + +def get_backing_stake_of(who): + ctrl = substrate.get_runtime_state( + module="Staking", + storage_function="Bonded", + params=[who], + block_hash=head, + )['result'] + + ctrl = ss58_encode(ctrl, substrate.address_type) + + ledger = substrate.get_runtime_state( + module="Staking", + storage_function="Ledger", + params=[ctrl], + block_hash=head, + )['result'] + + return ledger['active'] + + +def validator_count(): + return substrate.get_runtime_state("Staking", "ValidatorCount", [], head)['result'] + + +candidates = get_candidates() +nominators = get_nominators() +to_elect = validator_count() + +print("{} validators, {} nominators, electing {}".format(len(candidates), len(nominators), to_elect)) +distribution, winners = npos.phragmms(nominators, to_elect) +npos.print_list(winners) +print(sum([c.backed_stake for c in winners])) +print(min([c.backed_stake for c in winners])) +score = [min([c.backed_stake for c in winners]), sum([c.backed_stake for c in winners])] +print("score = [{:,}, {:,}]".format(int(score[0]), int(score[1])))