Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
68 changes: 68 additions & 0 deletions src/Neo/Cryptography/ProofOfWork.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// ProofOfWork.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Extensions;
using Neo.Extensions.Factories;
using System;
using System.Buffers.Binary;

namespace Neo.Cryptography
{
public class ProofOfWork
{
/// <summary>
/// Verify if the proof of work match with the difficulty.
/// </summary>
/// <param name="proofOfWork">Proof of Work</param>
/// <param name="difficulty">Difficulty</param>
/// <returns></returns>
public static bool VerifyDifficulty(UInt256 proofOfWork, uint difficulty)
{
// Take the first 8 bytes in order to check the proof of work difficulty

var bytes = proofOfWork.ToArray();
var value = BinaryPrimitives.ReadUInt32BigEndian(bytes.AsSpan(0, 4));
return value < difficulty;
}

/// <summary>
/// Compute proof of work
/// </summary>
/// <param name="blockHash">BlockHash</param>
/// <param name="nonce">Nonce</param>
/// <returns>Proof of Work</returns>
public static UInt256 Compute(UInt256 blockHash, long nonce)
{
var salt = new byte[16];
BinaryPrimitives.WriteInt64BigEndian(salt, nonce);

return (UInt256)Helper.Blake2b_256(blockHash.ToArray(), salt);
}

/// <summary>
/// Compute proof of work with difficulty
/// </summary>
/// <param name="blockHash">Block hash</param>
/// <param name="difficulty">Difficulty</param>
/// <returns>Nonce</returns>
public static long ComputeNonce(UInt256 blockHash, uint difficulty)
{
while (true)
{
var nonce = RandomNumberFactory.NextInt64();
var pow = Compute(blockHash, nonce);

if (VerifyDifficulty(pow, difficulty))
return nonce;
}
}
}
}
88 changes: 76 additions & 12 deletions src/Neo/SmartContract/Native/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

#pragma warning disable IDE0051

using Neo.Cryptography;
using Neo.Cryptography.ECC;
using Neo.Extensions;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract.Iterators;
using Neo.SmartContract.Manifest;
Expand Down Expand Up @@ -274,6 +276,13 @@ internal override async ContractTask PostPersistAsync(ApplicationEngine engine)
}
}
}

// Set primary as alive

var key = CreateStorageKey(Prefix_Candidate, pubkey);
var item = engine.SnapshotCache.GetAndChange(key, () => new StorageItem(new CandidateState()));
var state = item.GetInteroperable<CandidateState>();
state.LastProofOfLife = engine.PersistingBlock.Index;
}

/// <summary>
Expand Down Expand Up @@ -399,6 +408,39 @@ private bool RegisterCandidate(ApplicationEngine engine, ECPoint pubkey)
return RegisterInternal(engine, pubkey);
}

/// <summary>
/// Proof of life of a candidate.
/// </summary>
/// <param name="engine">The engine used to check witness and read data.</param>
/// <param name="pubkey">The public key of the candidate.</param>
/// <param name="proofOfWork">Proof of Work</param>
/// <param name="blockIndex">Block Index</param>
[ContractMethod(Hardfork.HF_Faun, RequiredCallFlags = CallFlags.States)]
private void ProofOfLife(ApplicationEngine engine, ECPoint pubkey, UInt256 proofOfWork, uint blockIndex)
{
if (engine.PersistingBlock == null || blockIndex >= engine.PersistingBlock.Index)
throw new Exception("Invalid proof of work");

if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()))
throw new Exception("Invalid witness");

var key = CreateStorageKey(Prefix_Candidate, pubkey);
var item = engine.SnapshotCache.GetAndChange(key, () => new StorageItem(new CandidateState()));
var state = item.GetInteroperable<CandidateState>();
if (!state.Registered)
throw new Exception("Only registered candidates are availables");

if (!ProofOfWork.VerifyDifficulty(proofOfWork, Policy.GetProofOfLifeDifficulty(engine.SnapshotCache)))
throw new Exception("Proof of work is too easy");

var blockHash = Ledger.GetBlockHash(engine.SnapshotCache, blockIndex);

if (blockHash == null || ProofOfWork.Compute(blockHash, (engine.ScriptContainer as Transaction)?.Nonce ?? 0) != proofOfWork)
throw new Exception("Invalid proof of work");

state.LastProofOfLife = blockIndex;
}

private bool RegisterInternal(ApplicationEngine engine, ECPoint pubkey)
{
if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()))
Expand Down Expand Up @@ -504,12 +546,12 @@ private async ContractTask<bool> Vote(ApplicationEngine engine, UInt160 account,
/// <summary>
/// Gets the first 256 registered candidates.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="engine">The ApplicationEngine used.</param>
/// <returns>All the registered candidates.</returns>
[ContractMethod(CpuFee = 1 << 22, RequiredCallFlags = CallFlags.ReadStates)]
internal (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot)
internal (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(ApplicationEngine engine)
{
return GetCandidatesInternal(snapshot)
return GetCandidatesInternal(engine.SnapshotCache, engine.IsHardforkEnabled(Hardfork.HF_Faun))
.Select(p => (p.PublicKey, p.State.Votes))
.Take(256)
.ToArray();
Expand All @@ -518,24 +560,26 @@ private async ContractTask<bool> Vote(ApplicationEngine engine, UInt160 account,
/// <summary>
/// Gets the registered candidates iterator.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="engine">The ApplicationEngine used.</param>
/// <returns>All the registered candidates.</returns>
[ContractMethod(CpuFee = 1 << 22, RequiredCallFlags = CallFlags.ReadStates)]
private IIterator GetAllCandidates(IReadOnlyStore snapshot)
private IIterator GetAllCandidates(ApplicationEngine engine)
{
const FindOptions options = FindOptions.RemovePrefix | FindOptions.DeserializeValues | FindOptions.PickField1;
var enumerator = GetCandidatesInternal(snapshot)
var enumerator = GetCandidatesInternal(engine.SnapshotCache, engine.IsHardforkEnabled(Hardfork.HF_Faun))
.Select(p => (p.Key, p.Value))
.GetEnumerator();
return new StorageIterator(enumerator, 1, options);
}

internal IEnumerable<(StorageKey Key, StorageItem Value, ECPoint PublicKey, CandidateState State)> GetCandidatesInternal(IReadOnlyStore snapshot)
internal IEnumerable<(StorageKey Key, StorageItem Value, ECPoint PublicKey, CandidateState State)> GetCandidatesInternal(IReadOnlyStore snapshot, bool withProofOfLife)
{
var prefixKey = CreateStorageKey(Prefix_Candidate);
var requiredProofOfLife = Ledger.CurrentIndex(snapshot) - Policy.GetMaxProofOfNodeHeight(snapshot);

return snapshot.Find(prefixKey)
.Select(p => (p.Key, p.Value, PublicKey: p.Key.Key[1..].AsSerializable<ECPoint>(), State: p.Value.GetInteroperable<CandidateState>()))
.Where(p => p.State.Registered)
.Where(p => p.State.Registered && (!withProofOfLife || (p.State.LastProofOfLife != null && p.State.LastProofOfLife >= requiredProofOfLife)))
.Where(p => !Policy.IsBlocked(snapshot, Contract.CreateSignatureRedeemScript(p.PublicKey).ToScriptHash()));
}

Expand Down Expand Up @@ -607,11 +651,20 @@ public ECPoint[] ComputeNextBlockValidators(DataCache snapshot, ProtocolSettings

private IEnumerable<(ECPoint PublicKey, BigInteger Votes)> ComputeCommitteeMembers(DataCache snapshot, ProtocolSettings settings)
{
decimal votersCount = (decimal)(BigInteger)snapshot[_votersCount];
decimal voterTurnout = votersCount / (decimal)TotalAmount;
var candidates = GetCandidatesInternal(snapshot)
var votersCount = (decimal)(BigInteger)snapshot[_votersCount];
var voterTurnout = votersCount / (decimal)TotalAmount;
var candidates = GetCandidatesInternal(snapshot, settings.IsHardforkEnabled(Hardfork.HF_Faun, Ledger.CurrentIndex(snapshot)))
.Select(p => (p.PublicKey, p.State.Votes))
.ToArray();

if (candidates.Length < settings.CommitteeMembersCount)
{
// If there are not enough candidates, include those without proof of life
candidates = GetCandidatesInternal(snapshot, false)
.Select(p => (p.PublicKey, p.State.Votes))
.ToArray();
}

if (voterTurnout < EffectiveVoterTurnout || candidates.Length < settings.CommitteeMembersCount)
return settings.StandbyCommittee.Select(p => (p, candidates.FirstOrDefault(k => k.PublicKey.Equals(p)).Votes));
return candidates
Expand Down Expand Up @@ -686,16 +739,27 @@ internal class CandidateState : IInteroperable
{
public bool Registered;
public BigInteger Votes;
public uint? LastProofOfLife;

public void FromStackItem(StackItem stackItem)
{
Struct @struct = (Struct)stackItem;
var @struct = (Struct)stackItem;
Registered = @struct[0].GetBoolean();
Votes = @struct[1].GetInteger();

if (@struct.Count > 2)
{
LastProofOfLife = (uint)@struct[2].GetInteger();
}
}

public StackItem ToStackItem(IReferenceCounter? referenceCounter)
{
if (LastProofOfLife.HasValue)
{
return new Struct(referenceCounter) { Registered, Votes, LastProofOfLife.Value };
}

return new Struct(referenceCounter) { Registered, Votes };
}
}
Expand Down
65 changes: 64 additions & 1 deletion src/Neo/SmartContract/Native/PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ public sealed class PolicyContract : NativeContract
/// </summary>
public const uint DefaultNotaryAssistedAttributeFee = 1000_0000;

/// <summary>
/// The default max proof of node height.
/// </summary>
public const uint DefaultMaxProofOfNodeHeight = 10_000;

/// <summary>
/// The default proof of node difficulty.
/// </summary>
public const uint DefaultProofOfNodeDifficulty = 0x00FFFFFF;

/// <summary>
/// The maximum execution fee factor that the committee can set.
/// </summary>
Expand Down Expand Up @@ -90,13 +100,17 @@ public sealed class PolicyContract : NativeContract
private const byte Prefix_MillisecondsPerBlock = 21;
private const byte Prefix_MaxValidUntilBlockIncrement = 22;
private const byte Prefix_MaxTraceableBlocks = 23;
private const byte Prefix_MaxProofOfNodeHeight = 24;
private const byte Prefix_ProofOfNodeDifficulty = 25;

private readonly StorageKey _feePerByte;
private readonly StorageKey _execFeeFactor;
private readonly StorageKey _storagePrice;
private readonly StorageKey _millisecondsPerBlock;
private readonly StorageKey _maxValidUntilBlockIncrement;
private readonly StorageKey _maxTraceableBlocks;
private readonly StorageKey _maxProofOfNodeHeight;
private readonly StorageKey _proofOfNodeDifficulty;

/// <summary>
/// The event name for the block generation time changed.
Expand All @@ -115,6 +129,8 @@ internal PolicyContract() : base()
_millisecondsPerBlock = CreateStorageKey(Prefix_MillisecondsPerBlock);
_maxValidUntilBlockIncrement = CreateStorageKey(Prefix_MaxValidUntilBlockIncrement);
_maxTraceableBlocks = CreateStorageKey(Prefix_MaxTraceableBlocks);
_maxProofOfNodeHeight = CreateStorageKey(Prefix_MaxProofOfNodeHeight);
_proofOfNodeDifficulty = CreateStorageKey(Prefix_ProofOfNodeDifficulty);
}

internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardfork)
Expand All @@ -130,7 +146,12 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor
engine.SnapshotCache.Add(CreateStorageKey(Prefix_AttributeFee, (byte)TransactionAttributeType.NotaryAssisted), new StorageItem(DefaultNotaryAssistedAttributeFee));
engine.SnapshotCache.Add(_millisecondsPerBlock, new StorageItem(engine.ProtocolSettings.MillisecondsPerBlock));
engine.SnapshotCache.Add(_maxValidUntilBlockIncrement, new StorageItem(engine.ProtocolSettings.MaxValidUntilBlockIncrement));
engine.SnapshotCache.Add(_maxTraceableBlocks, new StorageItem(engine.ProtocolSettings.MaxTraceableBlocks));
engine.SnapshotCache.Add(_maxProofOfNodeHeight, new StorageItem(engine.ProtocolSettings.MaxTraceableBlocks));
}
if (hardfork == Hardfork.HF_Faun)
{
engine.SnapshotCache.Add(_maxTraceableBlocks, new StorageItem(DefaultMaxProofOfNodeHeight));
engine.SnapshotCache.Add(_proofOfNodeDifficulty, new StorageItem(DefaultProofOfNodeDifficulty));
}
return ContractTask.CompletedTask;
}
Expand Down Expand Up @@ -226,6 +247,28 @@ public uint GetAttributeFeeV1(IReadOnlyStore snapshot, byte attributeType)
return GetAttributeFee(snapshot, attributeType, true);
}

/// <summary>
/// Gets the max proof of node height.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <returns>The proof of node height.</returns>
[ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public uint GetMaxProofOfNodeHeight(IReadOnlyStore snapshot)
{
return (uint)(BigInteger)snapshot[_maxProofOfNodeHeight];
}

/// <summary>
/// Gets the max proof of node difficulty.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <returns>The proof of node height.</returns>
[ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public uint GetProofOfLifeDifficulty(IReadOnlyStore snapshot)
{
return (uint)(BigInteger)snapshot[_proofOfNodeDifficulty];
}

/// <summary>
/// Generic handler for GetAttributeFeeV0 and GetAttributeFee that
/// gets the fee for attribute.
Expand Down Expand Up @@ -330,6 +373,26 @@ private void SetAttributeFee(ApplicationEngine engine, byte attributeType, uint
engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_AttributeFee, attributeType), () => new StorageItem(DefaultAttributeFee)).Set(value);
}

[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)]
private void SetMaxProofOfNodeHeight(ApplicationEngine engine, uint value)
{
var maxValue = GetMaxTraceableBlocks(engine.SnapshotCache);

if (value < 100 || value > maxValue)
throw new ArgumentOutOfRangeException(nameof(value), $"MaxProofOfNodeHeight must be between [100, {maxValue}], got {value}");
AssertCommittee(engine);

engine.SnapshotCache.GetAndChange(_maxProofOfNodeHeight)!.Set(value);
}

[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)]
private void SetProofOfNodeDifficulty(ApplicationEngine engine, uint value)
{
AssertCommittee(engine);

engine.SnapshotCache.GetAndChange(_proofOfNodeDifficulty)!.Set(value);
}

[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)]
private void SetFeePerByte(ApplicationEngine engine, long value)
{
Expand Down
Loading
Loading