Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cache for recent on chain transaction hashes #1886

Closed
Show file tree
Hide file tree
Changes from 3 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
64 changes: 64 additions & 0 deletions src/neo/IO/Caching/HashCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;

namespace Neo.IO.Caching
{
public class HashCache<T> where T : IEquatable<T>
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The logic is somehow different, i.e. don't need a bucketCapacity, don't want to check Contains upon adding as has been checked before in Blockchain.OnTransaction, etc.

{
/// <summary>
/// Sets where the Hashes are stored
/// </summary>
private readonly LinkedList<HashSet<T>> sets = new LinkedList<HashSet<T>>();

/// <summary>
/// Maximum number of buckets for the LinkedList, meaning its maximum cardinality.
/// </summary>
private readonly int maxBucketCount;

public int Depth => sets.Count;

public HashCache(int maxBucketCount = 10)
{
if (maxBucketCount <= 0) throw new ArgumentOutOfRangeException($"{nameof(maxBucketCount)} should be greater than 0");

this.maxBucketCount = maxBucketCount;
sets.AddFirst(new HashSet<T>());
}

public bool Add(T item)
{
//if (Contains(item)) return false;
return sets.First.Value.Add(item);
}

public bool Contains(T item)
{
foreach (var set in sets)
{
if (set.Contains(item)) return true;
}
return false;
}

public void Refresh()
{
var newSet = new HashSet<T>();
sets.AddFirst(newSet);
if (sets.Count > maxBucketCount)
{
sets.RemoveLast();
}
}

public IEnumerator<T> GetEnumerator()
{
foreach (var set in sets)
{
foreach (var item in set)
{
yield return item;
}
}
}
}
}
20 changes: 16 additions & 4 deletions src/neo/Ledger/Blockchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public class RelayResult { public IInventory Inventory; public VerifyResult Resu
public uint HeaderHeight => currentSnapshot.HeaderHeight;
public UInt256 CurrentBlockHash => currentSnapshot.CurrentBlockHash;
public UInt256 CurrentHeaderHash => currentSnapshot.CurrentHeaderHash;
private static readonly int cachedHeight = 10;
private HashCache<UInt256> recentOnchainTxCache = new HashCache<UInt256>(cachedHeight);

private static Blockchain singleton;
public static Blockchain Singleton
Expand Down Expand Up @@ -149,10 +151,18 @@ public bool ContainsBlock(UInt256 hash)
return View.ContainsBlock(hash);
}

public bool ContainsTransaction(UInt256 hash)
public bool ContainsTransaction(UInt256 hash, uint validUntilBlock)
{
if (MemPool.ContainsKey(hash)) return true;
return View.ContainsTransaction(hash);
return TransactionExist(hash, validUntilBlock);
}

public bool TransactionExist(UInt256 hash, uint validUntilBlock)
{
if (recentOnchainTxCache.Contains(hash)) return true;
if (validUntilBlock - Transaction.MaxValidUntilBlockIncrement < currentSnapshot.Height - Math.Min(cachedHeight, recentOnchainTxCache.Depth))
return View.ContainsTransaction(hash);
else return false;
}

private static Transaction DeployNativeContracts()
Expand Down Expand Up @@ -352,7 +362,7 @@ private VerifyResult OnNewInventory(IInventory inventory)

private VerifyResult OnNewTransaction(Transaction transaction)
{
if (ContainsTransaction(transaction.Hash)) return VerifyResult.AlreadyExists;
if (ContainsTransaction(transaction.Hash, transaction.ValidUntilBlock)) return VerifyResult.AlreadyExists;
return MemPool.TryAdd(transaction, currentSnapshot);
}

Expand Down Expand Up @@ -399,7 +409,7 @@ protected override void OnReceive(object message)

private void OnTransaction(Transaction tx, bool relay)
{
if (ContainsTransaction(tx.Hash))
if (ContainsTransaction(tx.Hash, tx.ValidUntilBlock))
SendRelayResult(tx, VerifyResult.AlreadyExists);
else
txrouter.Tell(new TransactionRouter.Task { Transaction = tx, Relay = relay }, Sender);
Expand Down Expand Up @@ -440,6 +450,7 @@ private void Persist(Block block)

clonedSnapshot.Transactions.Add(tx.Hash, state);
clonedSnapshot.Transactions.Commit();
recentOnchainTxCache.Add(tx.Hash);

using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, clonedSnapshot, tx.SystemFee))
{
Expand Down Expand Up @@ -484,6 +495,7 @@ private void Persist(Block block)
}
UpdateCurrentSnapshot();
block_cache.Remove(block.PrevHash);
recentOnchainTxCache.Refresh();
MemPool.UpdatePoolForBlockPersisted(block, currentSnapshot);
Context.System.EventStream.Publish(new PersistCompleted { Block = block });
}
Expand Down
15 changes: 14 additions & 1 deletion src/neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class Transaction : IEquatable<Transaction>, IInventory, IInteroperable
private long sysfee;
private long netfee;
private uint validUntilBlock;
private UInt256 lastBlockHash = UInt256.Zero;
Copy link
Contributor

@Tommo-L Tommo-L Aug 29, 2020

Choose a reason for hiding this comment

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

If so, why not use Height or SendHeight, HeightAt, ValidAfter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Such value can be forged by sender to make duplicate transactions onchain.

private Signer[] _signers;
private TransactionAttribute[] attributes;
private byte[] script;
Expand All @@ -40,7 +41,8 @@ public class Transaction : IEquatable<Transaction>, IInventory, IInteroperable
sizeof(uint) + //Nonce
sizeof(long) + //SystemFee
sizeof(long) + //NetworkFee
sizeof(uint); //ValidUntilBlock
sizeof(uint) + //ValidUntilBlock
UInt256.Length; //LastBlockHash

private Dictionary<Type, TransactionAttribute[]> _attributesCache;
public TransactionAttribute[] Attributes
Expand Down Expand Up @@ -135,6 +137,12 @@ public uint ValidUntilBlock
set { validUntilBlock = value; _hash = null; }
}

public UInt256 LastBlockHash
{
get => lastBlockHash;
set { lastBlockHash = value; _hash = null; }
}

public byte Version
{
get => version;
Expand Down Expand Up @@ -195,6 +203,7 @@ public void DeserializeUnsigned(BinaryReader reader)
if (NetworkFee < 0) throw new FormatException();
if (SystemFee + NetworkFee < SystemFee) throw new FormatException();
ValidUntilBlock = reader.ReadUInt32();
LastBlockHash = reader.ReadSerializable<UInt256>();
Signers = DeserializeSigners(reader, MaxTransactionAttributes).ToArray();
Attributes = DeserializeAttributes(reader, MaxTransactionAttributes - Signers.Length).ToArray();
Script = reader.ReadVarBytes(ushort.MaxValue);
Expand Down Expand Up @@ -253,6 +262,7 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer)
writer.Write(SystemFee);
writer.Write(NetworkFee);
writer.Write(ValidUntilBlock);
writer.Write(LastBlockHash);
writer.Write(Signers);
writer.Write(Attributes);
writer.WriteVarBytes(Script);
Expand All @@ -269,6 +279,7 @@ public JObject ToJson()
json["sysfee"] = SystemFee.ToString();
json["netfee"] = NetworkFee.ToString();
json["validuntilblock"] = ValidUntilBlock;
json["lastblockhash"] = LastBlockHash.ToString();
json["signers"] = Signers.Select(p => p.ToJson()).ToArray();
json["attributes"] = Attributes.Select(p => p.ToJson()).ToArray();
json["script"] = Convert.ToBase64String(Script);
Expand All @@ -285,6 +296,8 @@ public virtual VerifyResult VerifyStateDependent(StoreView snapshot, Transaction
{
if (ValidUntilBlock <= snapshot.Height || ValidUntilBlock > snapshot.Height + MaxValidUntilBlockIncrement)
return VerifyResult.Expired;
if (snapshot.GetBlock(LastBlockHash)?.Index != ValidUntilBlock - MaxValidUntilBlockIncrement)
Copy link
Member

Choose a reason for hiding this comment

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

@Qiao-Jin, shouldn't it be <= instead of !=? If not, could you, please, explain the meaning of this line?

Copy link
Contributor Author

@Qiao-Jin Qiao-Jin Sep 4, 2020

Choose a reason for hiding this comment

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

Why? This LastBlockHash is used to judge whether it's a valid ValidUntilBlock got from Height + MaxValidUntilBlockIncrement.

return VerifyResult.Invalid;
UInt160[] hashes = GetScriptHashesForVerifying(snapshot);
if (NativeContract.Policy.IsAnyAccountBlocked(snapshot, hashes))
return VerifyResult.PolicyFail;
Expand Down
1 change: 1 addition & 0 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ private Transaction MakeTransaction(StoreView snapshot, byte[] script, Signer[]
Nonce = (uint)rand.Next(),
Script = script,
ValidUntilBlock = snapshot.Height + Transaction.MaxValidUntilBlockIncrement,
LastBlockHash = snapshot.CurrentBlockHash,
Signers = GetSigners(account, cosigners),
Attributes = attributes,
};
Expand Down
10 changes: 5 additions & 5 deletions tests/neo.UnitTests/Ledger/UT_Blockchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,20 @@ public void TestContainsBlock()
[TestMethod]
public void TestContainsTransaction()
{
Blockchain.Singleton.ContainsTransaction(UInt256.Zero).Should().BeFalse();
Blockchain.Singleton.ContainsTransaction(txSample.Hash).Should().BeTrue();
Blockchain.Singleton.ContainsTransaction(UInt256.Zero, 0).Should().BeFalse();
Blockchain.Singleton.ContainsTransaction(txSample.Hash, 0).Should().BeTrue();
}

[TestMethod]
public void TestGetCurrentBlockHash()
{
Blockchain.Singleton.CurrentBlockHash.Should().Be(UInt256.Parse("0xecaee33262f1bc7c7c28f2b25b54a5d61d50670871f45c0c6fe755a40cbde4a8"));
Blockchain.Singleton.CurrentBlockHash.Should().Be(UInt256.Parse("0x04656ca14f0f80cb936012246d7f12e4b8b5366feccab356533564b004c7c592"));
}

[TestMethod]
public void TestGetCurrentHeaderHash()
{
Blockchain.Singleton.CurrentHeaderHash.Should().Be(UInt256.Parse("0xecaee33262f1bc7c7c28f2b25b54a5d61d50670871f45c0c6fe755a40cbde4a8"));
Blockchain.Singleton.CurrentHeaderHash.Should().Be(UInt256.Parse("0x04656ca14f0f80cb936012246d7f12e4b8b5366feccab356533564b004c7c592"));
}

[TestMethod]
Expand All @@ -91,7 +91,7 @@ public void TestGetBlock()
[TestMethod]
public void TestGetBlockHash()
{
Blockchain.Singleton.GetBlockHash(0).Should().Be(UInt256.Parse("0xecaee33262f1bc7c7c28f2b25b54a5d61d50670871f45c0c6fe755a40cbde4a8"));
Blockchain.Singleton.GetBlockHash(0).Should().Be(UInt256.Parse("0x04656ca14f0f80cb936012246d7f12e4b8b5366feccab356533564b004c7c592"));
Blockchain.Singleton.GetBlockHash(10).Should().BeNull();
}

Expand Down
6 changes: 3 additions & 3 deletions tests/neo.UnitTests/Ledger/UT_PoolItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ public void TestCleanup()
[TestMethod]
public void PoolItem_CompareTo_Fee()
{
int size1 = 51;
int size1 = 83;
int netFeeSatoshi1 = 1;
var tx1 = GenerateTx(netFeeSatoshi1, size1);
int size2 = 51;
int size2 = 83;
int netFeeSatoshi2 = 2;
var tx2 = GenerateTx(netFeeSatoshi2, size2);

Expand All @@ -55,7 +55,7 @@ public void PoolItem_CompareTo_Fee()
[TestMethod]
public void PoolItem_CompareTo_Hash()
{
int sizeFixed = 51;
int sizeFixed = 83;
int netFeeSatoshiFixed = 1;

var tx1 = GenerateTxWithFirstByteOfHashGreaterThanOrEqualTo(0x80, netFeeSatoshiFixed, sizeFixed);
Expand Down
2 changes: 1 addition & 1 deletion tests/neo.UnitTests/Ledger/UT_TransactionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void TestDeserialize()
[TestMethod]
public void TestGetSize()
{
((ISerializable)origin).Size.Should().Be(63);
((ISerializable)origin).Size.Should().Be(95);
}
}
}
18 changes: 9 additions & 9 deletions tests/neo.UnitTests/Network/P2P/Payloads/UT_Block.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void Size_Get_1_Transaction()
TestUtils.GetTransaction(UInt160.Zero)
};

uut.Size.Should().Be(167);
uut.Size.Should().Be(199);
}

[TestMethod]
Expand All @@ -75,7 +75,7 @@ public void Size_Get_3_Transaction()
TestUtils.GetTransaction(UInt160.Zero)
};

uut.Size.Should().Be(273);
uut.Size.Should().Be(369);
}

[TestMethod]
Expand All @@ -84,7 +84,7 @@ public void Serialize()
UInt256 val256 = UInt256.Zero;
TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 1);

var hex = "000000000000000000000000000000000000000000000000000000000000000000000000add6632f6f3d29cdf94555bb191fb5296683e5446f9937c56bb94c8608023044e913ff854c00000000000000000000000000000000000000000000000000000001000111020000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000100010000";
var hex = "00000000000000000000000000000000000000000000000000000000000000000000000092cc2ac42bc71e3f1a53f8edeb6a7b001499f2161416ce6b91aac88466b1a249e913ff854c000000000000000000000000000000000000000000000000000000010001110200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000100010000";
uut.ToArray().ToHexString().Should().Be(hex);
}

Expand All @@ -94,7 +94,7 @@ public void Deserialize()
UInt256 val256 = UInt256.Zero;
TestUtils.SetupBlockWithValues(new Block(), val256, out var merkRoot, out var val160, out var timestampVal, out var indexVal, out var scriptVal, out var transactionsVal, 1);

var hex = "000000000000000000000000000000000000000000000000000000000000000000000000add6632f6f3d29cdf94555bb191fb5296683e5446f9937c56bb94c8608023044e913ff854c00000000000000000000000000000000000000000000000000000001000111020000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000100010000";
var hex = "00000000000000000000000000000000000000000000000000000000000000000000000092cc2ac42bc71e3f1a53f8edeb6a7b001499f2161416ce6b91aac88466b1a249e913ff854c000000000000000000000000000000000000000000000000000000010001110200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000100010000";

using (MemoryStream ms = new MemoryStream(hex.HexToBytes(), false))
using (BinaryReader reader = new BinaryReader(ms))
Expand Down Expand Up @@ -199,11 +199,11 @@ public void ToJson()

JObject jObj = uut.ToJson();
jObj.Should().NotBeNull();
jObj["hash"].AsString().Should().Be("0x9a164d5b9a1ab8745c97dbaaaef8eb30b0d80a00205acdc82daf502bee69bc20");
jObj["size"].AsNumber().Should().Be(167);
jObj["hash"].AsString().Should().Be("0x70b9f744c43ec6db3b048fad4f433e4baa8b4daa48f17fd5faa1a9396aaf07d5");
jObj["size"].AsNumber().Should().Be(199);
jObj["version"].AsNumber().Should().Be(0);
jObj["previousblockhash"].AsString().Should().Be("0x0000000000000000000000000000000000000000000000000000000000000000");
jObj["merkleroot"].AsString().Should().Be("0x44300208864cb96bc537996f44e5836629b51f19bb5545f9cd293d6f2f63d6ad");
jObj["merkleroot"].AsString().Should().Be("0x49a2b16684c8aa916bce161416f29914007b6aebedf8531a3f1ec72bc42acc92");
jObj["time"].AsNumber().Should().Be(328665601001);
jObj["index"].AsNumber().Should().Be(0);
jObj["nextconsensus"].AsString().Should().Be("NKuyBkoGdZZSLyPbJEetheRhMjeznFZszf");
Expand All @@ -214,8 +214,8 @@ public void ToJson()

jObj["tx"].Should().NotBeNull();
JArray txObj = (JArray)jObj["tx"];
txObj[0]["hash"].AsString().Should().Be("0x995ce8ff19c30f6b0d6b03e5ed8bd30b08027c92177923782d3a64f573421931");
txObj[0]["size"].AsNumber().Should().Be(53);
txObj[0]["hash"].AsString().Should().Be("0xafce5f1d3a752e13bf166fe66db0131fa5e0138c2e1327febdf87af0760dee1f");
txObj[0]["size"].AsNumber().Should().Be(85);
txObj[0]["version"].AsNumber().Should().Be(0);
((JArray)txObj[0]["attributes"]).Count.Should().Be(0);
txObj[0]["netfee"].AsString().Should().Be("0");
Expand Down
Loading