diff --git a/neo/Consensus/ConsensusService.cs b/neo/Consensus/ConsensusService.cs index fe533ddac6..e7ada014c1 100644 --- a/neo/Consensus/ConsensusService.cs +++ b/neo/Consensus/ConsensusService.cs @@ -34,7 +34,8 @@ public ConsensusService(NeoSystem system, Wallet wallet) private bool AddTransaction(Transaction tx, bool verify) { - if (context.Snapshot.ContainsTransaction(tx.Hash) || + if (tx.Version < 2 || // Don't accept TX without PoW spam protection + context.Snapshot.ContainsTransaction(tx.Hash) || (verify && !tx.Verify(context.Snapshot, context.Transactions.Values)) || !Plugin.CheckPolicy(tx)) { @@ -114,37 +115,23 @@ private void FillContext() Value = amount_netfee, ScriptHash = wallet.GetChangeAddress() } }; - while (true) + + MinerTransaction tx = new MinerTransaction { - ulong nonce = GetNonce(); - MinerTransaction tx = new MinerTransaction - { - Nonce = (uint)(nonce % (uint.MaxValue + 1ul)), - Attributes = new TransactionAttribute[0], - Inputs = new CoinReference[0], - Outputs = outputs, - Witnesses = new Witness[0] - }; - if (!context.Snapshot.ContainsTransaction(tx.Hash)) - { - context.Nonce = nonce; - transactions.Insert(0, tx); - break; - } - } + Attributes = new TransactionAttribute[0], + Inputs = new CoinReference[0], + Outputs = outputs, + Witnesses = new Witness[0] + }; + + tx.ComputeNonce(ntx => !context.Snapshot.ContainsTransaction(ntx.Hash)); + transactions.Insert(0, tx); + context.TransactionHashes = transactions.Select(p => p.Hash).ToArray(); context.Transactions = transactions.ToDictionary(p => p.Hash); context.NextConsensus = Blockchain.GetConsensusAddress(context.Snapshot.GetValidators(transactions).ToArray()); } - private static ulong GetNonce() - { - byte[] nonce = new byte[sizeof(ulong)]; - Random rand = new Random(); - rand.NextBytes(nonce); - return nonce.ToUInt64(0); - } - private void InitializeConsensus(byte view_number) { if (view_number == 0) diff --git a/neo/Network/P2P/Payloads/ClaimTransaction.cs b/neo/Network/P2P/Payloads/ClaimTransaction.cs index e87cba06b8..593b2a2069 100644 --- a/neo/Network/P2P/Payloads/ClaimTransaction.cs +++ b/neo/Network/P2P/Payloads/ClaimTransaction.cs @@ -24,7 +24,7 @@ public ClaimTransaction() protected override void DeserializeExclusiveData(BinaryReader reader) { - if (Version != 0) throw new FormatException(); + if (Version > 2) throw new FormatException(); Claims = reader.ReadSerializableArray(); if (Claims.Length == 0) throw new FormatException(); } diff --git a/neo/Network/P2P/Payloads/ContractTransaction.cs b/neo/Network/P2P/Payloads/ContractTransaction.cs index 241795acb6..c34078f13a 100644 --- a/neo/Network/P2P/Payloads/ContractTransaction.cs +++ b/neo/Network/P2P/Payloads/ContractTransaction.cs @@ -12,7 +12,7 @@ public ContractTransaction() protected override void DeserializeExclusiveData(BinaryReader reader) { - if (Version != 0) throw new FormatException(); + if (Version > 2) throw new FormatException(); } } } diff --git a/neo/Network/P2P/Payloads/InvocationTransaction.cs b/neo/Network/P2P/Payloads/InvocationTransaction.cs index 38880a4a58..ffdeedafb4 100644 --- a/neo/Network/P2P/Payloads/InvocationTransaction.cs +++ b/neo/Network/P2P/Payloads/InvocationTransaction.cs @@ -23,7 +23,7 @@ public InvocationTransaction() protected override void DeserializeExclusiveData(BinaryReader reader) { - if (Version > 1) throw new FormatException(); + if (Version > 2) throw new FormatException(); Script = reader.ReadVarBytes(65536); if (Script.Length == 0) throw new FormatException(); if (Version >= 1) diff --git a/neo/Network/P2P/Payloads/IssueTransaction.cs b/neo/Network/P2P/Payloads/IssueTransaction.cs index 841a50c5e5..3ee4053d84 100644 --- a/neo/Network/P2P/Payloads/IssueTransaction.cs +++ b/neo/Network/P2P/Payloads/IssueTransaction.cs @@ -27,7 +27,7 @@ public IssueTransaction() protected override void DeserializeExclusiveData(BinaryReader reader) { - if (Version > 1) throw new FormatException(); + if (Version > 2) throw new FormatException(); } public override UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) diff --git a/neo/Network/P2P/Payloads/MinerTransaction.cs b/neo/Network/P2P/Payloads/MinerTransaction.cs index e4fcc8a51e..896429389a 100644 --- a/neo/Network/P2P/Payloads/MinerTransaction.cs +++ b/neo/Network/P2P/Payloads/MinerTransaction.cs @@ -8,8 +8,6 @@ namespace Neo.Network.P2P.Payloads { public class MinerTransaction : Transaction { - public uint Nonce; - public override Fixed8 NetworkFee => Fixed8.Zero; public override int Size => base.Size + sizeof(uint); diff --git a/neo/Network/P2P/Payloads/Transaction.cs b/neo/Network/P2P/Payloads/Transaction.cs index 4d821706e6..4e68c505a7 100644 --- a/neo/Network/P2P/Payloads/Transaction.cs +++ b/neo/Network/P2P/Payloads/Transaction.cs @@ -34,6 +34,9 @@ public abstract class Transaction : IEquatable, IInventory public TransactionOutput[] Outputs; public Witness[] Witnesses { get; set; } + public uint CreationHeight; + public uint Nonce; + private UInt256 _hash = null; public UInt256 Hash { @@ -97,6 +100,8 @@ public IReadOnlyDictionary References public virtual Fixed8 SystemFee => Settings.Default.SystemFee.TryGetValue(Type, out Fixed8 fee) ? fee : Fixed8.Zero; + public uint Difficult => (uint)BitConverter.ToInt32(Hash.ToArray().Take(4).Reverse().ToArray(), 0); + protected Transaction(TransactionType type) { this.Type = type; @@ -109,6 +114,20 @@ void ISerializable.Deserialize(BinaryReader reader) OnDeserialized(); } + public void ComputeNonce(uint difficult) => ComputeNonce(tx => tx.Difficult <= difficult); + + public void ComputeNonce(Func condition) + { + var rand = new Random(Environment.TickCount); + + do + { + _hash = null; + Nonce = (uint)rand.Next(); + } + while (!condition(this)); + } + protected virtual void DeserializeExclusiveData(BinaryReader reader) { } @@ -148,6 +167,12 @@ private void DeserializeUnsignedWithoutType(BinaryReader reader) Attributes = reader.ReadSerializableArray(MaxTransactionAttributes); Inputs = reader.ReadSerializableArray(); Outputs = reader.ReadSerializableArray(ushort.MaxValue + 1); + + if (Version >= 2) + { + CreationHeight = reader.ReadUInt32(); + Nonce = reader.ReadUInt32(); + } } public bool Equals(Transaction other) @@ -229,6 +254,12 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer) writer.Write(Attributes); writer.Write(Inputs); writer.Write(Outputs); + + if (Version >= 2) + { + writer.Write(CreationHeight); + writer.Write(Nonce); + } } public virtual JObject ToJson() diff --git a/neo/Network/P2P/RemoteNode.cs b/neo/Network/P2P/RemoteNode.cs index 9880339f73..ee38818a22 100644 --- a/neo/Network/P2P/RemoteNode.cs +++ b/neo/Network/P2P/RemoteNode.cs @@ -142,6 +142,16 @@ private void OnSend(IInventory inventory) if (Version?.Relay != true) return; if (inventory.InventoryType == InventoryType.TX) { + if (inventory is Transaction tx && tx.Version >= 2) + { + // Check PoW spam protection for drop TX + + if (tx.Difficult > Settings.Default.TransactionDifficult) + { + return; + } + } + if (bloom_filter != null && !bloom_filter.Test((Transaction)inventory)) return; } diff --git a/neo/Network/RPC/RpcServer.cs b/neo/Network/RPC/RpcServer.cs index 5a242e258c..e94392a8be 100644 --- a/neo/Network/RPC/RpcServer.cs +++ b/neo/Network/RPC/RpcServer.cs @@ -90,7 +90,7 @@ private JObject GetInvokeResult(byte[] script) { InvocationTransaction tx = new InvocationTransaction { - Version = 1, + Version = 2, Script = json["script"].AsString().HexToBytes(), Gas = Fixed8.Parse(json["gas_consumed"].AsString()) }; diff --git a/neo/Settings.cs b/neo/Settings.cs index 41c63aa02e..f9fd495248 100644 --- a/neo/Settings.cs +++ b/neo/Settings.cs @@ -15,6 +15,7 @@ internal class Settings public IReadOnlyDictionary SystemFee { get; private set; } public Fixed8 LowPriorityThreshold { get; private set; } public uint SecondsPerBlock { get; private set; } + public uint TransactionDifficult { get; private set; } public static Settings Default { get; private set; } @@ -32,6 +33,7 @@ public Settings(IConfigurationSection section) this.SeedList = section.GetSection("SeedList").GetChildren().Select(p => p.Value).ToArray(); this.SystemFee = section.GetSection("SystemFee").GetChildren().ToDictionary(p => (TransactionType)Enum.Parse(typeof(TransactionType), p.Key, true), p => Fixed8.Parse(p.Value)); this.SecondsPerBlock = GetValueOrDefault(section.GetSection("SecondsPerBlock"), 15u, p => uint.Parse(p)); + this.TransactionDifficult = GetValueOrDefault(section.GetSection("TransactionDifficult"), (uint)ushort.MaxValue, p => uint.Parse(p)); this.LowPriorityThreshold = GetValueOrDefault(section.GetSection("LowPriorityThreshold"), Fixed8.FromDecimal(0.001m), p => Fixed8.Parse(p)); } diff --git a/neo/Wallets/Helper.cs b/neo/Wallets/Helper.cs index 78903b4637..d0821c1143 100644 --- a/neo/Wallets/Helper.cs +++ b/neo/Wallets/Helper.cs @@ -10,6 +10,13 @@ public static class Helper { public static byte[] Sign(this IVerifiable verifiable, KeyPair key) { + if (verifiable is Transaction tx) + { + // Compute a small PoW spam protection => hash should start with 0x0000 + + tx.ComputeNonce(Settings.Default.TransactionDifficult); + } + return Crypto.Default.Sign(verifiable.GetHashData(), key.PrivateKey, key.PublicKey.EncodePoint(false).Skip(1).ToArray()); } diff --git a/neo/Wallets/Wallet.cs b/neo/Wallets/Wallet.cs index 27a1040f3d..e4267de0d2 100644 --- a/neo/Wallets/Wallet.cs +++ b/neo/Wallets/Wallet.cs @@ -350,7 +350,7 @@ public virtual WalletAccount Import(string nep2, string passphrase) sb.Emit(OpCode.RET, nonce); tx = new InvocationTransaction { - Version = 1, + Version = 2, Script = sb.ToArray() }; }