Skip to content

Commit

Permalink
PoW Spam protection (TX V2)
Browse files Browse the repository at this point in the history
First draft of  PoW spam protection

- neo-project#414 (comment)
- neo-project#408
  • Loading branch information
shargon committed Nov 1, 2018
1 parent fd5e3c6 commit b6f33df
Show file tree
Hide file tree
Showing 12 changed files with 69 additions and 34 deletions.
39 changes: 13 additions & 26 deletions neo/Consensus/ConsensusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion neo/Network/P2P/Payloads/ClaimTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CoinReference>();
if (Claims.Length == 0) throw new FormatException();
}
Expand Down
2 changes: 1 addition & 1 deletion neo/Network/P2P/Payloads/ContractTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public ContractTransaction()

protected override void DeserializeExclusiveData(BinaryReader reader)
{
if (Version != 0) throw new FormatException();
if (Version > 2) throw new FormatException();
}
}
}
2 changes: 1 addition & 1 deletion neo/Network/P2P/Payloads/InvocationTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion neo/Network/P2P/Payloads/IssueTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions neo/Network/P2P/Payloads/MinerTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public abstract class Transaction : IEquatable<Transaction>, IInventory
public TransactionOutput[] Outputs;
public Witness[] Witnesses { get; set; }

public uint CreationHeight;
public uint Nonce;

private UInt256 _hash = null;
public UInt256 Hash
{
Expand Down Expand Up @@ -97,6 +100,8 @@ public IReadOnlyDictionary<CoinReference, TransactionOutput> 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;
Expand All @@ -109,6 +114,20 @@ void ISerializable.Deserialize(BinaryReader reader)
OnDeserialized();
}

public void ComputeNonce(uint difficult) => ComputeNonce(tx => tx.Difficult <= difficult);

public void ComputeNonce(Func<Transaction, bool> condition)
{
var rand = new Random(Environment.TickCount);

do
{
_hash = null;
Nonce = (uint)rand.Next();
}
while (!condition(this));
}

protected virtual void DeserializeExclusiveData(BinaryReader reader)
{
}
Expand Down Expand Up @@ -148,6 +167,12 @@ private void DeserializeUnsignedWithoutType(BinaryReader reader)
Attributes = reader.ReadSerializableArray<TransactionAttribute>(MaxTransactionAttributes);
Inputs = reader.ReadSerializableArray<CoinReference>();
Outputs = reader.ReadSerializableArray<TransactionOutput>(ushort.MaxValue + 1);

if (Version >= 2)
{
CreationHeight = reader.ReadUInt32();
Nonce = reader.ReadUInt32();
}
}

public bool Equals(Transaction other)
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 10 additions & 0 deletions neo/Network/P2P/RemoteNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion neo/Network/RPC/RpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
};
Expand Down
2 changes: 2 additions & 0 deletions neo/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal class Settings
public IReadOnlyDictionary<TransactionType, Fixed8> 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; }

Expand All @@ -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));
}

Expand Down
7 changes: 7 additions & 0 deletions neo/Wallets/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down
2 changes: 1 addition & 1 deletion neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};
}
Expand Down

0 comments on commit b6f33df

Please sign in to comment.