Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add incentive for voter #1848

Merged
merged 88 commits into from
Nov 2, 2020
Merged
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
bc785f3
add reward key
Aug 18, 2020
1e8966a
Merge branch 'master' into incentive_for_voter
erikzhang Aug 21, 2020
25908bc
Merge branch 'master' into incentive_for_voter
erikzhang Aug 22, 2020
a6e438e
incentive for voter
Aug 22, 2020
4249d07
optimize
Aug 22, 2020
422f20a
optimize
Aug 23, 2020
df0062b
optimize
Aug 23, 2020
7c72d41
fix CalculateBonus and add ut
Aug 23, 2020
0a3d09e
format
Aug 23, 2020
723f60c
Item2 -> Votes
vncoelho Aug 23, 2020
e99a806
Merge branch 'master' into incentive_for_voter
erikzhang Aug 25, 2020
5b92e05
Move fields
erikzhang Aug 25, 2020
addfebf
Fix GetCommitteeVotes()
erikzhang Aug 25, 2020
4b753d2
specify variable name
Aug 27, 2020
eb9b728
Remove double ToArray
shargon Aug 28, 2020
bf1ec79
fix
Sep 7, 2020
ba05800
fix ut
Sep 7, 2020
5e4abe7
remove
Sep 8, 2020
03859d8
Merge branch 'master' into incentive_for_voter
erikzhang Sep 11, 2020
77fe460
fix
Sep 11, 2020
2a806ef
fix
Sep 11, 2020
67aafdc
remove useless code
Sep 11, 2020
675fe7a
optimize
Sep 11, 2020
e062344
Merge branch 'master' into incentive_for_voter
shargon Sep 12, 2020
ccd4151
Merge branch 'master' into incentive_for_voter
erikzhang Sep 13, 2020
6ffc601
fix
Sep 14, 2020
b0db8f4
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 14, 2020
5d19387
Merge branch 'master' into incentive_for_voter
erikzhang Sep 15, 2020
ed13ad5
resolve
Sep 15, 2020
8b06824
format
Sep 15, 2020
62baa9f
Merge branch 'master' into incentive_for_voter
shargon Sep 15, 2020
c49a809
Add using to dispose enumerators
erikzhang Sep 16, 2020
bf00ca1
Update NeoToken.cs
erikzhang Sep 17, 2020
fed12df
Update NeoToken.cs
erikzhang Sep 17, 2020
87e1d9d
fix
Sep 17, 2020
0570997
fix CalculateBonus
Sep 18, 2020
4a12748
Usng asending order
Sep 18, 2020
e656049
Merge branch 'master' into incentive_for_voter
erikzhang Sep 18, 2020
1dd9ad4
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 21, 2020
3d9b2ff
Merge branch 'master' into incentive_for_voter
shargon Sep 21, 2020
321f5f8
Update NeoToken.cs
erikzhang Sep 22, 2020
57b6e32
fix
Sep 22, 2020
3124024
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 24, 2020
9af4bb3
optimize findrange
Sep 24, 2020
40e73de
format
Sep 24, 2020
c5f1458
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 25, 2020
53950b4
optimize
Sep 25, 2020
849be7c
Update NeoToken.cs
erikzhang Sep 25, 2020
94caa53
fix
Sep 27, 2020
c95ff95
format
Sep 27, 2020
6693760
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 27, 2020
d811ce8
Use GetCommitteeFromCache
erikzhang Sep 27, 2020
f5f9259
Ensure registered
erikzhang Sep 27, 2020
36ec672
fix PostPersist
Sep 27, 2020
251d633
Update NeoToken.cs
erikzhang Sep 28, 2020
268bc86
cache committee votes
Sep 28, 2020
811292b
Merge branch 'incentive_for_voter' of https://github.com/Tommo-L/neo …
Sep 28, 2020
f5c17fd
cache committee
Sep 29, 2020
29e83ad
Update src/neo/Ledger/StorageItem.cs
shargon Sep 30, 2020
81cd44a
Merge branch 'master' into incentive_for_voter
Tommo-L Oct 9, 2020
e3b2ee6
fix PostPersist
Oct 9, 2020
22f1af9
fix ShouldRefreshCommittee & optimize PostPersist
Oct 9, 2020
3d5065d
add CommitteeEpoch
Oct 9, 2020
50e1663
fix CalculateBonus
Oct 9, 2020
aa688ca
fix PostPersist
Oct 9, 2020
6218108
optimize CalculateBonus
Oct 9, 2020
4253502
optimize PostPersist
Oct 9, 2020
d06c1cb
Update NeoToken.cs
erikzhang Oct 10, 2020
cd37075
Merge branch 'master' into incentive_for_voter
shargon Oct 10, 2020
083412f
fix conflicts
Oct 19, 2020
875dc76
Merge branch 'incentive_for_voter' of https://github.com/Tommo-L/neo …
Oct 19, 2020
7ba5e24
add comments for trigger github action
Oct 19, 2020
e8c8044
Allow standby can be voted
Oct 20, 2020
95acd30
optimize
Oct 27, 2020
5d5837e
rename local variable
shargon Oct 27, 2020
990d8d5
Optimize Unregister Candidate
shargon Oct 27, 2020
898f21c
Revert
shargon Oct 27, 2020
5173eeb
Merge branch 'master' into incentive_for_voter
shargon Oct 27, 2020
15792c2
Merge branch 'master' into incentive_for_voter
shargon Oct 28, 2020
44bdd5c
add ut
Oct 29, 2020
4d4986d
fix ut
Oct 29, 2020
ca174f0
format
Oct 29, 2020
a96ca80
Merge branch 'master' into incentive_for_voter
shargon Oct 29, 2020
c23c3ba
Conflicts
shargon Oct 29, 2020
387565f
Merge branch 'master' into incentive_for_voter
Tommo-L Oct 31, 2020
5e18673
Update src/neo/SmartContract/Native/Tokens/NeoToken.cs
Tommo-L Nov 1, 2020
947882c
Merge branch 'master' into incentive_for_voter
shargon Nov 1, 2020
99522a1
Merge branch 'master' into incentive_for_voter
shargon Nov 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 99 additions & 25 deletions src/neo/SmartContract/Native/Tokens/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public sealed class NeoToken : Nep5Token<NeoToken.NeoAccountState>
private const byte Prefix_Candidate = 33;
private const byte Prefix_Committee = 14;
private const byte Prefix_GasPerBlock = 29;
private const byte Prefix_VoterRewardPerCommittee = 23;

private const byte NeoHolderRewardRatio = 10;
private const byte CommitteeRewardRatio = 5;
Expand All @@ -53,24 +54,41 @@ protected override void OnBalanceChanging(ApplicationEngine engine, UInt160 acco
StorageKey key = CreateStorageKey(Prefix_Candidate).Add(state.VoteTo);
CandidateState candidate = engine.Snapshot.Storages.GetAndChange(key).GetInteroperable<CandidateState>();
candidate.Votes += amount;
CheckCandidate(engine.Snapshot, key, candidate);
CheckCandidate(engine.Snapshot, state.VoteTo, candidate);
}

private void DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccountState state)
{
// PersistingBlock is null when running under the debugger
if (engine.Snapshot.PersistingBlock == null) return;

BigInteger gas = CalculateBonus(engine.Snapshot, state.Balance, state.BalanceHeight, engine.Snapshot.PersistingBlock.Index);
BigInteger gas = CalculateBonus(engine.Snapshot, state.VoteTo, state.Balance, state.BalanceHeight, engine.Snapshot.PersistingBlock.Index);
state.BalanceHeight = engine.Snapshot.PersistingBlock.Index;
GAS.Mint(engine, account, gas);
}

private BigInteger CalculateBonus(StoreView snapshot, BigInteger value, uint start, uint end)
private BigInteger CalculateBonus(StoreView snapshot, ECPoint vote, BigInteger value, uint start, uint end)
{
if (value.IsZero || start >= end) return BigInteger.Zero;
if (value.Sign < 0) throw new ArgumentOutOfRangeException(nameof(value));

BigInteger neoHolderReward = CalculateNeoHolderReward(snapshot, value, start, end);
if (vote is null) return neoHolderReward;

byte[] border = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).ToArray();
byte[] keyStart = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(start).ToArray();
(_, var item) = snapshot.Storages.FindRange(keyStart, border, SeekDirection.Backward).FirstOrDefault();
BigInteger startRewardPerNeo = item ?? BigInteger.Zero;

byte[] keyEnd = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(end).ToArray();
(_, item) = snapshot.Storages.FindRange(keyEnd, border, SeekDirection.Backward).FirstOrDefault();
BigInteger endRewardPerNeo = item ?? BigInteger.Zero;

return neoHolderReward + value * (endRewardPerNeo - startRewardPerNeo) / 100000000L;
}

private BigInteger CalculateNeoHolderReward(StoreView snapshot, BigInteger value, uint start, uint end)
{
BigInteger sum = 0;
foreach (var (index, gasPerBlock) in GetSortedGasRecords(snapshot, end - 1))
{
Expand All @@ -88,17 +106,22 @@ private BigInteger CalculateBonus(StoreView snapshot, BigInteger value, uint sta
return value * sum * NeoHolderRewardRatio / 100 / TotalAmount;
}

private static void CheckCandidate(StoreView snapshot, StorageKey key, CandidateState candidate)
private void CheckCandidate(StoreView snapshot, ECPoint pubkey, CandidateState candidate)
{
if (!candidate.Registered && candidate.Votes.IsZero)
snapshot.Storages.Delete(key);
{
foreach (var (rewardKey, _) in snapshot.Storages.Find(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(pubkey).ToArray()))
Tommo-L marked this conversation as resolved.
Show resolved Hide resolved
snapshot.Storages.Delete(rewardKey);
snapshot.Storages.Delete(CreateStorageKey(Prefix_Candidate).Add(pubkey));
}
}

public bool ShouldRefreshCommittee(uint height) => height % (ProtocolSettings.Default.CommitteeMembersCount + ProtocolSettings.Default.ValidatorsCount) == 0;
public bool ShouldRefreshCommittee(uint height) => height % ProtocolSettings.Default.CommitteeMembersCount == 0;

internal override void Initialize(ApplicationEngine engine)
{
engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_Committee), new StorageItem(Blockchain.StandbyCommittee.ToByteArray()));
var cachedCommittee = new CachedCommittee(Blockchain.StandbyCommittee.Select(p => (p, BigInteger.Zero)));
engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee));
engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(new byte[0]));

// Initialize economic parameters
Expand All @@ -112,10 +135,12 @@ protected override void OnPersist(ApplicationEngine engine)
base.OnPersist(engine);

// Set next committee
if (ShouldRefreshCommittee(engine.Snapshot.Height))
if (ShouldRefreshCommittee(engine.Snapshot.PersistingBlock.Index))
{
StorageItem storageItem = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Committee));
storageItem.Value = ComputeCommitteeMembers(engine.Snapshot).ToArray().ToByteArray();
var cachedCommittee = storageItem.GetInteroperable<CachedCommittee>();
cachedCommittee.Clear();
cachedCommittee.AddRange(ComputeCommitteeMembers(engine.Snapshot));
}
}

Expand All @@ -125,11 +150,35 @@ protected override void PostPersist(ApplicationEngine engine)

// Distribute GAS for committee

int index = (int)(engine.Snapshot.PersistingBlock.Index % (uint)ProtocolSettings.Default.CommitteeMembersCount);
int m = ProtocolSettings.Default.CommitteeMembersCount;
int n = ProtocolSettings.Default.ValidatorsCount;
int index = (int)(engine.Snapshot.PersistingBlock.Index % (uint)m);
var gasPerBlock = GetGasPerBlock(engine.Snapshot);
var pubkey = GetCommittee(engine.Snapshot)[index];
var committee = GetCommitteeFromCache(engine.Snapshot);
var pubkey = committee.ElementAt(index).PublicKey;
var account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash();
GAS.Mint(engine, account, gasPerBlock * CommitteeRewardRatio / 100);

// Record the cumulative reward of the voters of committee

if (ShouldRefreshCommittee(engine.Snapshot.PersistingBlock.Index))
{
BigInteger voterRewardOfEachCommittee = gasPerBlock * VoterRewardRatio * 100000000L * m / (m + n) / 100; // Zoom in 100000000 times, and the final calculation should be divided 100000000L
for (index = 0; index < committee.Count; index++)
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
{
var member = committee.ElementAt(index);
var factor = index < n ? 2 : 1; // The `voter` rewards of validator will double than other committee's
if (member.Votes > 0)
{
BigInteger voterSumRewardPerNEO = factor * voterRewardOfEachCommittee / member.Votes;
StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(member.PublicKey).AddBigEndian(engine.Snapshot.PersistingBlock.Index + 1);
byte[] border = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(member.PublicKey).ToArray();
(_, var item) = engine.Snapshot.Storages.FindRange(voterRewardKey.ToArray(), border, SeekDirection.Backward).FirstOrDefault();
voterSumRewardPerNEO += (item ?? BigInteger.Zero);
engine.Snapshot.Storages.Add(voterRewardKey, new StorageItem(voterSumRewardPerNEO));
}
}
}
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
Expand Down Expand Up @@ -165,7 +214,7 @@ public BigInteger UnclaimedGas(StoreView snapshot, UInt160 account, uint end)
StorageItem storage = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Account).Add(account));
if (storage is null) return BigInteger.Zero;
NeoAccountState state = storage.GetInteroperable<NeoAccountState>();
return CalculateBonus(snapshot, state.Balance, state.BalanceHeight, end);
return CalculateBonus(snapshot, state.VoteTo, state.Balance, state.BalanceHeight, end);
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
Expand All @@ -190,7 +239,7 @@ private bool UnregisterCandidate(ApplicationEngine engine, ECPoint pubkey)
StorageItem item = engine.Snapshot.Storages.GetAndChange(key);
CandidateState state = item.GetInteroperable<CandidateState>();
state.Registered = false;
CheckCandidate(engine.Snapshot, key, state);
CheckCandidate(engine.Snapshot, pubkey, state);
return true;
}

Expand All @@ -215,13 +264,14 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo)
else
item.Add(-state_account.Balance);
}
DistributeGas(engine, account, state_account);
if (state_account.VoteTo != null)
{
StorageKey key = CreateStorageKey(Prefix_Candidate).Add(state_account.VoteTo);
StorageItem storage_validator = engine.Snapshot.Storages.GetAndChange(key);
CandidateState state_validator = storage_validator.GetInteroperable<CandidateState>();
state_validator.Votes -= state_account.Balance;
CheckCandidate(engine.Snapshot, key, state_validator);
CheckCandidate(engine.Snapshot, state_account.VoteTo, state_validator);
}
state_account.VoteTo = voteTo;
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
if (validator_new != null)
Expand All @@ -245,7 +295,7 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo)
[ContractMethod(1_00000000, CallFlags.AllowStates)]
public ECPoint[] GetCommittee(StoreView snapshot)
{
return GetCommitteeFromCache(snapshot).OrderBy(p => p).ToArray();
return GetCommitteeFromCache(snapshot).Select(p => p.PublicKey).OrderBy(p => p).ToArray();
shargon marked this conversation as resolved.
Show resolved Hide resolved
}

public UInt160 GetCommitteeAddress(StoreView snapshot)
Expand All @@ -254,33 +304,32 @@ public UInt160 GetCommitteeAddress(StoreView snapshot)
return Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash();
}

private IEnumerable<ECPoint> GetCommitteeFromCache(StoreView snapshot)
private CachedCommittee GetCommitteeFromCache(StoreView snapshot)
{
return snapshot.Storages[CreateStorageKey(Prefix_Committee)].GetSerializableList<ECPoint>();
return snapshot.Storages[CreateStorageKey(Prefix_Committee)].GetInteroperable<CachedCommittee>();
}

internal ECPoint[] ComputeNextBlockValidators(StoreView snapshot)
{
return ComputeCommitteeMembers(snapshot).Take(ProtocolSettings.Default.ValidatorsCount).OrderBy(p => p).ToArray();
return ComputeCommitteeMembers(snapshot).Select(p => p.PublicKey).Take(ProtocolSettings.Default.ValidatorsCount).OrderBy(p => p).ToArray();
}

private IEnumerable<ECPoint> ComputeCommitteeMembers(StoreView snapshot)
private IEnumerable<(ECPoint PublicKey, BigInteger Votes)> ComputeCommitteeMembers(StoreView snapshot)
{
decimal votersCount = (decimal)(BigInteger)snapshot.Storages[CreateStorageKey(Prefix_VotersCount)];
decimal VoterTurnout = votersCount / (decimal)TotalAmount;
if (VoterTurnout < EffectiveVoterTurnout)
return Blockchain.StandbyCommittee;
decimal voterTurnout = votersCount / (decimal)TotalAmount;
var candidates = GetCandidates(snapshot);
if (candidates.Length < ProtocolSettings.Default.CommitteeMembersCount)
return Blockchain.StandbyCommittee;
return candidates.OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(ProtocolSettings.Default.CommitteeMembersCount);
if (voterTurnout < EffectiveVoterTurnout || candidates.Length < ProtocolSettings.Default.CommitteeMembersCount)
return Blockchain.StandbyCommittee.Select(p => (p, candidates.FirstOrDefault(k => k.PublicKey.Equals(p)).Votes));
return candidates.OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Take(ProtocolSettings.Default.CommitteeMembersCount);
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
public ECPoint[] GetNextBlockValidators(StoreView snapshot)
{
return GetCommitteeFromCache(snapshot)
.Take(ProtocolSettings.Default.ValidatorsCount)
.Select(p => p.PublicKey)
.OrderBy(p => p)
.ToArray();
}
Expand Down Expand Up @@ -324,5 +373,30 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter)
return new Struct(referenceCounter) { Registered, Votes };
}
}

public class CachedCommittee : List<(ECPoint PublicKey, BigInteger Votes)>, IInteroperable
{
public CachedCommittee()
{
}

public CachedCommittee(IEnumerable<(ECPoint PublicKey, BigInteger Votes)> collection) : base(collection)
{
}

public void FromStackItem(StackItem stackItem)
{
foreach (StackItem item in (VM.Types.Array)stackItem)
{
Struct @struct = (Struct)item;
Add((@struct[0].GetSpan().AsSerializable<ECPoint>(), @struct[1].GetInteger()));
}
}

public StackItem ToStackItem(ReferenceCounter referenceCounter)
{
return new VM.Types.Array(referenceCounter, this.Select(p => new Struct(referenceCounter, new StackItem[] { p.PublicKey.ToArray(), p.Votes })));
}
}
}
}
13 changes: 11 additions & 2 deletions tests/neo.UnitTests/Consensus/UT_Consensus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Security.Cryptography;
using static Neo.SmartContract.Native.Tokens.NeoToken;
using ECPoint = Neo.Cryptography.ECC.ECPoint;

namespace Neo.UnitTests.Consensus
Expand Down Expand Up @@ -277,10 +279,11 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm
Console.WriteLine($"\nContract updated: {updatedContract.ScriptHash}");

// ===============================================================
CachedCommittee cachedCommittee = new CachedCommittee(mockContext.Object.Validators.Select(p => (p, BigInteger.Zero)));
mockContext.Object.Snapshot.Storages.Delete(CreateStorageKeyForNativeNeo(14));
mockContext.Object.Snapshot.Storages.Add(CreateStorageKeyForNativeNeo(14), new StorageItem()
{
Value = mockContext.Object.Validators.ToByteArray()
Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), 4096)
});
mockContext.Object.Snapshot.Commit();
// ===============================================================
Expand All @@ -298,6 +301,8 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm
Console.WriteLine("\n\n==========================");
Console.WriteLine("\nBasic commits Signatures verification");
// Basic tests for understanding signatures and ensuring signatures of commits are correct on tests


var cmPayloadTemp = GetCommitPayloadModifiedAndSignedCopy(commitPayload, 6, kp_array[6], updatedBlockHashData);
Crypto.VerifySignature(originalBlockHashData, cm.Signature, mockContext.Object.Validators[0]).Should().BeFalse();
Crypto.VerifySignature(updatedBlockHashData, cm.Signature, mockContext.Object.Validators[0]).Should().BeFalse();
Expand Down Expand Up @@ -414,7 +419,11 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm
Console.WriteLine("mockContext Reset for returning Blockchain.Singleton snapshot to original state.");
mockContext.Object.Reset(0);
mockContext.Object.Snapshot.Storages.Delete(CreateStorageKeyForNativeNeo(14));
mockContext.Object.Snapshot.Storages.Add(CreateStorageKeyForNativeNeo(14), new StorageItem(Blockchain.StandbyCommittee.ToByteArray()));
cachedCommittee = new CachedCommittee(Blockchain.StandbyCommittee.Select(p => (p, BigInteger.Zero)));
mockContext.Object.Snapshot.Storages.Add(CreateStorageKeyForNativeNeo(14), new StorageItem
{
Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), 4096)
});
mockContext.Object.Snapshot.Commit();

Console.WriteLine("mockContext Reset.");
Expand Down
Loading