Skip to content
Open
Changes from 1 commit
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
80 changes: 72 additions & 8 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 @@ -399,6 +401,46 @@ 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.ToString().StartsWith("0x0000")) // TODO: Decide the proper value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a proof of work at all here? What does it prove?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it is for all committee , not only CN

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cn could be set every time he is the primary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a proof of work at all here? What does it prove?

Proof that the committee has a node

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this doesn't prove there is a node. I can separate this code from the node. My point is that a transaction is sufficient. Adding any calculations would be a serious regression for Neo and it wouldn't add any benefit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think we don't need to introduce proof of work.

What's your proposal? Regular tx is easy to fake. I thought an idea but it's incompatible with huge amount of committee (outside 21).

If committee produce a signature of the previous block, the primary can choose this rpc signatures and use them as a proof when they persist the block.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shargon @erikzhang , do you know? Double speakers would be perfect for that because we could, every round, make a committee to be a speaker and do round robin on that.

So, every member would need to be ready. Double speakers do that without any problems because they would be fallback. Based on node's statistics we would know the truth.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's only work for 21, no for outside 21

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that if a node deliberately pretends to be online while refusing to participate in the consensus process, there is essentially no reliable way for us to detect such behavior. Therefore, the primary purpose of “proof of node” should not be to identify malicious non-participation. Instead, it should focus on preventing accidental misconfigurations by candidates—such as forgetting to run the node, failing to start the consensus module, or unintentionally launching multiple consensus instances.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then only a tx is good for you, and maybe a counter of how many view changes?

throw new Exception("Proof of work is too easy");

if (ComputeProofOfLife(engine.SnapshotCache, blockIndex, (engine.ScriptContainer as Transaction)?.Nonce) != proofOfWork)
throw new Exception("Invalid proof of work");

state.LastProofOfLife = blockIndex;
}

private static UInt256 ComputeProofOfLife(DataCache snapshotCache, uint blockIndex, long? nonce = 0)
{
var blockHash = Ledger.GetBlockHash(snapshotCache, blockIndex);

return blockHash == null
? throw new Exception("Invalid blockIndex")
: (UInt256)Cryptography.Helper.Blake2b_256(blockHash.ToArray(), BitConverter.GetBytes(nonce ?? 0));
}

private bool RegisterInternal(ApplicationEngine engine, ECPoint pubkey)
{
if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()))
Expand Down Expand Up @@ -509,7 +551,7 @@ private async ContractTask<bool> Vote(ApplicationEngine engine, UInt160 account,
[ContractMethod(CpuFee = 1 << 22, RequiredCallFlags = CallFlags.ReadStates)]
internal (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot)
{
return GetCandidatesInternal(snapshot)
return GetCandidatesInternal(snapshot, true)
.Select(p => (p.PublicKey, p.State.Votes))
.Take(256)
.ToArray();
Expand All @@ -524,18 +566,20 @@ private async ContractTask<bool> Vote(ApplicationEngine engine, UInt160 account,
private IIterator GetAllCandidates(IReadOnlyStore snapshot)
{
const FindOptions options = FindOptions.RemovePrefix | FindOptions.DeserializeValues | FindOptions.PickField1;
var enumerator = GetCandidatesInternal(snapshot)
var enumerator = GetCandidatesInternal(snapshot, true)
.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 requiredProofOfLife = Ledger.CurrentIndex(snapshot) - 10_000; // TODO: Decide the proper value
var prefixKey = CreateStorageKey(Prefix_Candidate);

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, true)
.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
Loading