From 4adc81920d87d65f0a266262d08ef801c06aefa1 Mon Sep 17 00:00:00 2001 From: ShawnYun Date: Mon, 2 Nov 2020 23:18:46 +0800 Subject: [PATCH 1/2] fix sync (#2031) Co-authored-by: Shargon --- src/neo/Network/P2P/TaskManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/neo/Network/P2P/TaskManager.cs b/src/neo/Network/P2P/TaskManager.cs index 4f4397aedb..fb1ade45bd 100644 --- a/src/neo/Network/P2P/TaskManager.cs +++ b/src/neo/Network/P2P/TaskManager.cs @@ -120,6 +120,7 @@ private void OnNewTasks(InvPayload payload) private void OnPersistCompleted(Block block) { receivedBlockIndex.Remove(block.Index); + RequestTasks(false); } protected override void OnReceive(object message) @@ -293,7 +294,7 @@ private void RequestTasks(bool sendPing) var highestBlockIndex = sessions.Values.Max(p => p.LastBlockIndex); for (; taskCounts < MaxSyncTasksCount; taskCounts++) { - if (lastTaskIndex >= highestBlockIndex) break; + if (lastTaskIndex >= highestBlockIndex || lastTaskIndex >= Blockchain.Singleton.Height + InvPayload.MaxHashesCount) break; if (!AssignSyncTask(++lastTaskIndex)) break; } } From 5800cf25e8155c7167d725519d78d1ea742cdf3f Mon Sep 17 00:00:00 2001 From: Luchuan Date: Tue, 3 Nov 2020 03:35:03 +0800 Subject: [PATCH 2/2] Add incentive for voter (#1848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add reward key * incentive for voter * optimize * optimize * optimize * fix CalculateBonus and add ut * format * Item2 -> Votes * Move fields * Fix GetCommitteeVotes() * specify variable name * Remove double ToArray * fix ut * remove * fix * fix * remove useless code * optimize * fix * format * Add using to dispose enumerators * Update NeoToken.cs * Update NeoToken.cs * fix * fix CalculateBonus * Usng asending order * Update NeoToken.cs * fix * optimize findrange * format * optimize * Update NeoToken.cs * fix * format * Use GetCommitteeFromCache * Ensure registered * fix PostPersist * Update NeoToken.cs * cache committee votes * cache committee * Update src/neo/Ledger/StorageItem.cs Co-authored-by: Erik Zhang * fix PostPersist * fix ShouldRefreshCommittee & optimize PostPersist * add CommitteeEpoch * fix CalculateBonus * fix PostPersist * optimize CalculateBonus * optimize PostPersist * Update NeoToken.cs * add comments for trigger github action * Allow standby can be voted * optimize * rename local variable * Optimize Unregister Candidate * Revert * add ut * fix ut * format * Conflicts * Update src/neo/SmartContract/Native/Tokens/NeoToken.cs Co-authored-by: Erik Zhang Co-authored-by: Tommo-L Co-authored-by: Erik Zhang Co-authored-by: Vitor Nazário Coelho Co-authored-by: Shargon --- .../SmartContract/Native/Tokens/NeoToken.cs | 124 +++++++++--- tests/neo.UnitTests/Consensus/UT_Consensus.cs | 13 +- .../Native/Tokens/UT_NeoToken.cs | 188 +++++++++++++++++- 3 files changed, 297 insertions(+), 28 deletions(-) diff --git a/src/neo/SmartContract/Native/Tokens/NeoToken.cs b/src/neo/SmartContract/Native/Tokens/NeoToken.cs index 3ba7408c17..eb5b0f14e4 100644 --- a/src/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/src/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -29,6 +29,7 @@ public sealed class NeoToken : Nep5Token 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; @@ -53,7 +54,7 @@ protected override void OnBalanceChanging(ApplicationEngine engine, UInt160 acco StorageKey key = CreateStorageKey(Prefix_Candidate).Add(state.VoteTo); CandidateState candidate = engine.Snapshot.Storages.GetAndChange(key).GetInteroperable(); candidate.Votes += amount; - CheckCandidate(engine.Snapshot, key, candidate); + CheckCandidate(engine.Snapshot, state.VoteTo, candidate); } private void DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccountState state) @@ -61,16 +62,33 @@ private void DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccount // 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)) { @@ -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()).ToArray()) + 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 @@ -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.Clear(); + cachedCommittee.AddRange(ComputeCommitteeMembers(engine.Snapshot)); } } @@ -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++) + { + 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)] @@ -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(); - return CalculateBonus(snapshot, state.Balance, state.BalanceHeight, end); + return CalculateBonus(snapshot, state.VoteTo, state.Balance, state.BalanceHeight, end); } [ContractMethod(0_05000000, CallFlags.AllowModifyStates)] @@ -190,7 +239,7 @@ private bool UnregisterCandidate(ApplicationEngine engine, ECPoint pubkey) StorageItem item = engine.Snapshot.Storages.GetAndChange(key); CandidateState state = item.GetInteroperable(); state.Registered = false; - CheckCandidate(engine.Snapshot, key, state); + CheckCandidate(engine.Snapshot, pubkey, state); return true; } @@ -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(); state_validator.Votes -= state_account.Balance; - CheckCandidate(engine.Snapshot, key, state_validator); + CheckCandidate(engine.Snapshot, state_account.VoteTo, state_validator); } state_account.VoteTo = voteTo; if (validator_new != null) @@ -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(); } public UInt160 GetCommitteeAddress(StoreView snapshot) @@ -254,26 +304,24 @@ public UInt160 GetCommitteeAddress(StoreView snapshot) return Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash(); } - private IEnumerable GetCommitteeFromCache(StoreView snapshot) + private CachedCommittee GetCommitteeFromCache(StoreView snapshot) { - return snapshot.Storages[CreateStorageKey(Prefix_Committee)].GetSerializableList(); + return snapshot.Storages[CreateStorageKey(Prefix_Committee)].GetInteroperable(); } 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 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)] @@ -281,6 +329,7 @@ public ECPoint[] GetNextBlockValidators(StoreView snapshot) { return GetCommitteeFromCache(snapshot) .Take(ProtocolSettings.Default.ValidatorsCount) + .Select(p => p.PublicKey) .OrderBy(p => p) .ToArray(); } @@ -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(), @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 }))); + } + } } } diff --git a/tests/neo.UnitTests/Consensus/UT_Consensus.cs b/tests/neo.UnitTests/Consensus/UT_Consensus.cs index e7ce88be40..038812b072 100644 --- a/tests/neo.UnitTests/Consensus/UT_Consensus.cs +++ b/tests/neo.UnitTests/Consensus/UT_Consensus.cs @@ -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 @@ -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(); // =============================================================== @@ -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(); @@ -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."); diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs index cfb530deec..77b6805622 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Consensus; using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; @@ -11,7 +12,9 @@ using Neo.SmartContract.Native; using Neo.UnitTests.Extensions; using Neo.VM; +using Neo.Wallets; using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; using static Neo.SmartContract.Native.Tokens.NeoToken; @@ -309,6 +312,7 @@ public void Check_GetCommittee() } //register more candidates,committee member change + snapshot.PersistingBlock = new Block { Index = 0 }; for (int i = 0; i < ProtocolSettings.Default.CommitteeMembersCount - 1; i++) { Check_RegisterValidator(snapshot, Blockchain.StandbyCommittee[i].ToArray()); @@ -415,7 +419,7 @@ public void Check_CommitteeBonus() engine.Execute(); engine.State.Should().Be(VM.VMState.HALT); - var committee = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray(); + var committee = Blockchain.StandbyCommittee; NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[0]).ScriptHash.ToArray()).Should().Be(25000000); NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash.ToArray()).Should().Be(25000000); NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash.ToArray()).Should().Be(0); @@ -472,6 +476,16 @@ public void TestCalculateBonus() action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); snapshot.Storages.Delete(key); + // Fault range: start >= end + + snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState + { + Balance = 100, + BalanceHeight = 100 + })); + action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); + snapshot.Storages.Delete(key); + // Normal 1) votee is non exist snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState @@ -480,6 +494,27 @@ public void TestCalculateBonus() })); NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); snapshot.Storages.Delete(key); + + // Normal 2) votee is not committee + + snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState + { + Balance = 100, + VoteTo = ECCurve.Secp256r1.G + })); + NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); + snapshot.Storages.Delete(key); + + // Normal 3) votee is committee + + snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState + { + Balance = 100, + VoteTo = Blockchain.StandbyCommittee[0] + })); + snapshot.Storages.Add(new KeyBuilder(-1, 23).Add(Blockchain.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() }); + NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(50 * 100)); + snapshot.Storages.Delete(key); } [TestMethod] @@ -535,6 +570,47 @@ public void TestGetCandidates2() NativeContract.NEO.GetCandidates(snapshot).Length.Should().Be(1); } + [TestMethod] + public void TestCheckCandidate() + { + var snapshot = Blockchain.Singleton.GetSnapshot(); + var committee = NativeContract.NEO.GetCommittee(snapshot); + var point = committee[0].EncodePoint(true); + + // Prepare Prefix_VoterRewardPerCommittee + var storageKey = new KeyBuilder(-1, 23).Add(committee[0]).AddBigEndian(20); + snapshot.Storages.Add(storageKey, new StorageItem(new BigInteger(1000))); + + // Prepare Candidate + storageKey = new KeyBuilder(-1, 33).Add(committee[0]); + snapshot.Storages.Add(storageKey, new StorageItem(new CandidateState { Registered = true, Votes = BigInteger.One })); + + storageKey = new KeyBuilder(-1, 23).Add(committee[0]); + snapshot.Storages.Find(storageKey.ToArray()).ToArray().Length.Should().Be(1); + + // Pre-persist + snapshot.PersistingBlock = new Block { Index = 21 }; + Check_OnPersist(snapshot); + + // Clear votes + storageKey = new KeyBuilder(-1, 33).Add(committee[0]); + snapshot.Storages.GetAndChange(storageKey).GetInteroperable().Votes = BigInteger.Zero; + + // Unregister candidate, remove + var ret = Check_UnregisterCandidate(snapshot, point); + ret.State.Should().BeTrue(); + ret.Result.Should().BeTrue(); + + storageKey = new KeyBuilder(-1, 23).Add(committee[0]); + snapshot.Storages.Find(storageKey.ToArray()).ToArray().Length.Should().Be(0); + + // Post-persist + Check_PostPersist(snapshot).Should().BeTrue(); + + storageKey = new KeyBuilder(-1, 23).Add(committee[0]); + snapshot.Storages.Find(storageKey.ToArray()).ToArray().Length.Should().Be(1); + } + [TestMethod] public void TestGetCommittee() { @@ -632,6 +708,80 @@ public void TestEconomicParameter() NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, snapshot.PersistingBlock.Index + 1).Should().Be(6500); } + [TestMethod] + public void TestClaimGas() + { + var snapshot = Blockchain.Singleton.GetSnapshot(); + + // Initialize block + snapshot.Storages.Add(CreateStorageKey(1), new StorageItem(new BigInteger(30000000))); + + ECPoint[] standbyCommittee = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray(); + CachedCommittee cachedCommittee = new CachedCommittee(); + for (var i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++) + { + ECPoint member = standbyCommittee[i]; + snapshot.Storages.Add(new KeyBuilder(-1, 33).Add(member), new StorageItem(new CandidateState() + { + Registered = true, + Votes = 200 * 10000 + })); + cachedCommittee.Add((member, 200 * 10000)); + } + snapshot.Storages[new KeyBuilder(-1, 14)].Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), 4096); + + var item = snapshot.Storages.GetAndChange(new KeyBuilder(-1, 1), () => new StorageItem()); + item.Value = ((BigInteger)2100 * 10000L).ToByteArray(); + + snapshot.PersistingBlock = new Block { Index = 0 }; + Check_PostPersist(snapshot).Should().BeTrue(); + + var committee = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray(); + var accountA = committee[0]; + var accountB = committee[ProtocolSettings.Default.CommitteeMembersCount - 1]; + NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(accountA).ScriptHash).Should().Be(0); + + StorageItem storageItem = snapshot.Storages.TryGet(new KeyBuilder(-1, 23).Add(accountA).AddBigEndian(1)); + new BigInteger(storageItem.Value).Should().Be(31875000000); + + snapshot.Storages.TryGet(new KeyBuilder(-1, 23).Add(accountB).AddBigEndian(uint.MaxValue - 1)).Should().BeNull(); + + // Next block + + snapshot.PersistingBlock = new Block { Index = 1 }; + Check_PostPersist(snapshot).Should().BeTrue(); + + NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash).Should().Be(0); + + storageItem = snapshot.Storages.TryGet(new KeyBuilder(-1, 23).Add(committee[1]).AddBigEndian(1)); + new BigInteger(storageItem.Value).Should().Be(31875000000); + + // Next block + + snapshot.PersistingBlock = new Block { Index = 21 }; + Check_PostPersist(snapshot).Should().BeTrue(); + + accountA = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray()[2]; + NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash).Should().Be(0); + + storageItem = snapshot.Storages.TryGet(new KeyBuilder(-1, 23).Add(committee[2]).AddBigEndian(22)); + new BigInteger(storageItem.Value).Should().Be(31875000000 * 2); + + + // Claim GAS + + var account = Contract.CreateSignatureContract(committee[2]).ScriptHash; + snapshot.Storages.Add(new KeyBuilder(-1, 20).Add(account), new StorageItem(new NeoAccountState + { + BalanceHeight = 3, + Balance = 200 * 10000 - 2 * 100, + VoteTo = committee[2] + })); + NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(1999800); + BigInteger value = NativeContract.NEO.UnclaimedGas(snapshot, account, 29 + 3); + value.Should().Be(1999800 * 31875000000 / 100000000L + (1999800L * 10 * 5 * 29 / 100)); + } + [TestMethod] public void TestUnclaimedGas() { @@ -705,6 +855,42 @@ public void TestVote() return (true, result.GetBoolean()); } + internal static bool Check_OnPersist(StoreView snapshot) + { + ECPoint[] committees = NativeContract.NEO.GetCommittee(snapshot); + UInt160 committeesMultisign = Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash(); + var engine = ApplicationEngine.Create(TriggerType.OnPersist, + new Nep5NativeContractExtensions.ManualWitness(committeesMultisign), snapshot); + + engine.LoadScript(NativeContract.NEO.Script); + + var script = new ScriptBuilder(); + script.EmitPush(0); + script.Emit(OpCode.PACK); + script.EmitPush("onPersist"); + engine.LoadScript(script.ToArray()); + + return engine.Execute() == VMState.HALT; + } + + internal static bool Check_PostPersist(StoreView snapshot) + { + ECPoint[] committees = NativeContract.NEO.GetCommittee(snapshot); + UInt160 committeesMultisign = Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash(); + var engine = ApplicationEngine.Create(TriggerType.PostPersist, + new Nep5NativeContractExtensions.ManualWitness(committeesMultisign), snapshot); + + engine.LoadScript(NativeContract.NEO.Script); + + var script = new ScriptBuilder(); + script.EmitPush(0); + script.Emit(OpCode.PACK); + script.EmitPush("postPersist"); + engine.LoadScript(script.ToArray()); + + return engine.Execute() == VMState.HALT; + } + internal static (BigInteger Value, bool State) Check_GetGasPerBlock(StoreView snapshot) { var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot);