diff --git a/src/AntShares/Consensus/ConsensusService.cs b/src/AntShares/Consensus/ConsensusService.cs index 592b66dd3f..268f0887fa 100644 --- a/src/AntShares/Consensus/ConsensusService.cs +++ b/src/AntShares/Consensus/ConsensusService.cs @@ -1,363 +1,374 @@ -using AntShares.Core; -using AntShares.IO; -using AntShares.Network; -using AntShares.Network.Payloads; -using AntShares.Wallets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; - -namespace AntShares.Consensus -{ - public class ConsensusService : IDisposable - { - private ConsensusContext context = new ConsensusContext(); - private LocalNode localNode; - private Wallet wallet; - private Timer timer; - private uint timer_height; - private byte timer_view; - private DateTime block_received_time; - private string log_dictionary; - private bool started = false; - - public ConsensusService(LocalNode localNode, Wallet wallet, string log_dictionary = null) - { - this.localNode = localNode; - this.wallet = wallet; - this.timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite); - this.log_dictionary = log_dictionary; - } - - private bool AddTransaction(Transaction tx, bool verify) - { - if (Blockchain.Default.ContainsTransaction(tx.Hash) || - (verify && !tx.Verify(context.Transactions.Values)) || - !CheckPolicy(tx)) - { - Log($"reject tx: {tx.Hash}{Environment.NewLine}{tx.ToArray().ToHexString()}"); - RequestChangeView(); - return false; - } - context.Transactions[tx.Hash] = tx; - if (context.TransactionHashes.Length == context.Transactions.Count) - { - if (Blockchain.GetMinerAddress(Blockchain.Default.GetMiners(context.Transactions.Values).ToArray()).Equals(context.NextMiner)) - { - Log($"send perpare response"); - context.State |= ConsensusState.SignatureSent; - context.Signatures[context.MinerIndex] = context.MakeHeader().Sign(wallet.GetAccount(context.Miners[context.MinerIndex])); - SignAndRelay(context.MakePerpareResponse(context.Signatures[context.MinerIndex])); - CheckSignatures(); - } - else - { - RequestChangeView(); - return false; - } - } - return true; - } - - private void Blockchain_PersistCompleted(object sender, Block block) - { - Log($"persist block: {block.Hash}"); - block_received_time = DateTime.Now; - InitializeConsensus(0); - } - - private void CheckExpectedView(byte view_number) - { - if (context.ViewNumber == view_number) return; - if (context.ExpectedView.Count(p => p == view_number) >= context.M) - { - InitializeConsensus(view_number); - } - } - - private static bool CheckPolicy(Transaction tx) - { - switch (Policy.Default.PolicyLevel) - { - case PolicyLevel.AllowAll: - return true; - case PolicyLevel.AllowList: - return tx.Scripts.All(p => Policy.Default.List.Contains(p.RedeemScript.ToScriptHash())) || tx.Outputs.All(p => Policy.Default.List.Contains(p.ScriptHash)); - case PolicyLevel.DenyList: - return tx.Scripts.All(p => !Policy.Default.List.Contains(p.RedeemScript.ToScriptHash())) && tx.Outputs.All(p => !Policy.Default.List.Contains(p.ScriptHash)); - default: - return false; - } - } - - private void CheckSignatures() - { - if (context.Signatures.Count(p => p != null) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p))) - { - Contract contract = Contract.CreateMultiSigContract(context.Miners[context.MinerIndex].EncodePoint(true).ToScriptHash(), context.M, context.Miners); - Block block = context.MakeHeader(); - SignatureContext sc = new SignatureContext(block); - for (int i = 0, j = 0; i < context.Miners.Length && j < context.M; i++) - if (context.Signatures[i] != null) - { - sc.AddSignature(contract, context.Miners[i], context.Signatures[i]); - j++; - } - sc.Signable.Scripts = sc.GetScripts(); - block.Transactions = context.TransactionHashes.Select(p => context.Transactions[p]).ToArray(); - Log($"relay block: {block.Hash}"); - if (!localNode.Relay(block)) - Log($"reject block: {block.Hash}"); - context.State |= ConsensusState.BlockSent; - } - } - - private MinerTransaction CreateMinerTransaction(IEnumerable transactions, uint height, ulong nonce) - { - Fixed8 amount_netfee = Block.CalculateNetFee(transactions); - TransactionOutput[] outputs = amount_netfee == Fixed8.Zero ? new TransactionOutput[0] : new[] { new TransactionOutput - { - AssetId = Blockchain.AntCoin.Hash, - Value = amount_netfee, - ScriptHash = wallet.GetContracts().First().ScriptHash - } }; - return new MinerTransaction - { - Nonce = (uint)(nonce % (uint.MaxValue + 1ul)), - Attributes = new TransactionAttribute[0], - Inputs = new CoinReference[0], - Outputs = outputs, - Scripts = new Witness[0] - }; - } - - public void Dispose() - { - Log("OnStop"); - if (timer != null) timer.Dispose(); - if (started) - { - Blockchain.PersistCompleted -= Blockchain_PersistCompleted; - LocalNode.NewInventory -= LocalNode_NewInventory; - } - } - - 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) - { - lock (context) - { - if (view_number == 0) - context.Reset(wallet); - else - context.ChangeView(view_number); - if (context.MinerIndex < 0) return; - Log($"initialize: height={context.Height} view={view_number} index={context.MinerIndex} role={(context.MinerIndex == context.PrimaryIndex ? ConsensusState.Primary : ConsensusState.Backup)}"); - if (context.MinerIndex == context.PrimaryIndex) - { - context.State |= ConsensusState.Primary; - timer_height = context.Height; - timer_view = view_number; - TimeSpan span = DateTime.Now - block_received_time; - if (span >= Blockchain.TimePerBlock) - timer.Change(0, Timeout.Infinite); - else - timer.Change(Blockchain.TimePerBlock - span, Timeout.InfiniteTimeSpan); - } - else - { - context.State = ConsensusState.Backup; - timer_height = context.Height; - timer_view = view_number; - timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (view_number + 1)), Timeout.InfiniteTimeSpan); - } - } - } - - private void LocalNode_NewInventory(object sender, IInventory inventory) - { - ConsensusPayload payload = inventory as ConsensusPayload; - if (payload != null) - { - lock (context) - { - if (payload.MinerIndex == context.MinerIndex) return; - if (payload.Version != ConsensusContext.Version || payload.PrevHash != context.PrevHash || payload.Height != context.Height) - return; - if (payload.MinerIndex >= context.Miners.Length) return; - ConsensusMessage message = ConsensusMessage.DeserializeFrom(payload.Data); - if (message.ViewNumber != context.ViewNumber && message.Type != ConsensusMessageType.ChangeView) - return; - switch (message.Type) - { - case ConsensusMessageType.ChangeView: - OnChangeViewReceived(payload, (ChangeView)message); - break; - case ConsensusMessageType.PerpareRequest: - OnPerpareRequestReceived(payload, (PerpareRequest)message); - break; - case ConsensusMessageType.PerpareResponse: - OnPerpareResponseReceived(payload, (PerpareResponse)message); - break; - } - } - } - Transaction tx = inventory as Transaction; - if (tx != null) - { - lock (context) - { - if (!context.State.HasFlag(ConsensusState.Backup) || !context.State.HasFlag(ConsensusState.RequestReceived) || context.State.HasFlag(ConsensusState.SignatureSent)) - return; - if (context.Transactions.ContainsKey(tx.Hash)) return; - if (!context.TransactionHashes.Contains(tx.Hash)) return; - AddTransaction(tx, true); - } - } - } - - private void Log(string message) - { - DateTime now = DateTime.Now; - string line = $"[{now.TimeOfDay:hh\\:mm\\:ss}] {message}"; - Console.WriteLine(line); - if (string.IsNullOrEmpty(log_dictionary)) return; - lock (log_dictionary) - { - Directory.CreateDirectory(log_dictionary); - string path = Path.Combine(log_dictionary, $"{now:yyyy-MM-dd}.log"); - File.AppendAllLines(path, new[] { line }); - } - } - - private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message) - { - Log($"{nameof(OnChangeViewReceived)}: height={payload.Height} view={message.ViewNumber} index={payload.MinerIndex} nv={message.NewViewNumber}"); - if (message.NewViewNumber <= context.ExpectedView[payload.MinerIndex]) - return; - context.ExpectedView[payload.MinerIndex] = message.NewViewNumber; - CheckExpectedView(message.NewViewNumber); - } - - private void OnPerpareRequestReceived(ConsensusPayload payload, PerpareRequest message) - { - Log($"{nameof(OnPerpareRequestReceived)}: height={payload.Height} view={message.ViewNumber} index={payload.MinerIndex} tx={message.TransactionHashes.Length}"); - if (!context.State.HasFlag(ConsensusState.Backup) || context.State.HasFlag(ConsensusState.RequestReceived)) - return; - if (payload.MinerIndex != context.PrimaryIndex) return; - if (payload.Timestamp <= Blockchain.Default.GetHeader(context.PrevHash).Timestamp || payload.Timestamp > DateTime.Now.AddMinutes(10).ToTimestamp()) - { - Log($"Timestamp incorrect: {payload.Timestamp}"); - return; - } - context.State |= ConsensusState.RequestReceived; - context.Timestamp = payload.Timestamp; - context.Nonce = message.Nonce; - context.NextMiner = message.NextMiner; - context.TransactionHashes = message.TransactionHashes; - context.Transactions = new Dictionary(); - if (!context.MakeHeader().VerifySignature(context.Miners[payload.MinerIndex], message.Signature)) return; - context.Signatures = new byte[context.Miners.Length][]; - context.Signatures[payload.MinerIndex] = message.Signature; - Dictionary mempool = LocalNode.GetMemoryPool().ToDictionary(p => p.Hash); - foreach (UInt256 hash in context.TransactionHashes.Skip(1)) - if (mempool.ContainsKey(hash)) - if (!AddTransaction(mempool[hash], false)) - return; - if (!AddTransaction(message.MinerTransaction, true)) return; - LocalNode.AllowHashes(context.TransactionHashes.Except(context.Transactions.Keys)); - if (context.Transactions.Count < context.TransactionHashes.Length) - localNode.SynchronizeMemoryPool(); - } - - private void OnPerpareResponseReceived(ConsensusPayload payload, PerpareResponse message) - { - Log($"{nameof(OnPerpareResponseReceived)}: height={payload.Height} view={message.ViewNumber} index={payload.MinerIndex}"); - if (context.State.HasFlag(ConsensusState.BlockSent)) return; - if (context.Signatures[payload.MinerIndex] != null) return; - Block header = context.MakeHeader(); - if (header == null || !header.VerifySignature(context.Miners[payload.MinerIndex], message.Signature)) return; - context.Signatures[payload.MinerIndex] = message.Signature; - CheckSignatures(); - } - - private void OnTimeout(object state) - { - lock (context) - { - if (timer_height != context.Height || timer_view != context.ViewNumber) return; - Log($"timeout: height={timer_height} view={timer_view} state={context.State}"); - if (context.State.HasFlag(ConsensusState.Primary) && !context.State.HasFlag(ConsensusState.RequestSent)) - { - Log($"send perpare request: height={timer_height} view={timer_view}"); - context.State |= ConsensusState.RequestSent; - if (!context.State.HasFlag(ConsensusState.SignatureSent)) - { - context.Timestamp = Math.Max(DateTime.Now.ToTimestamp(), Blockchain.Default.GetHeader(context.PrevHash).Timestamp + 1); - context.Nonce = GetNonce(); - List transactions = LocalNode.GetMemoryPool().Where(p => CheckPolicy(p)).ToList(); - transactions.Insert(0, CreateMinerTransaction(transactions, context.Height, context.Nonce)); - context.TransactionHashes = transactions.Select(p => p.Hash).ToArray(); - context.Transactions = transactions.ToDictionary(p => p.Hash); - context.NextMiner = Blockchain.GetMinerAddress(Blockchain.Default.GetMiners(transactions).ToArray()); - context.Signatures[context.MinerIndex] = context.MakeHeader().Sign(wallet.GetAccount(context.Miners[context.MinerIndex])); - } - SignAndRelay(context.MakePerpareRequest()); - timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (timer_view + 1)), Timeout.InfiniteTimeSpan); - } - else if ((context.State.HasFlag(ConsensusState.Primary) && context.State.HasFlag(ConsensusState.RequestSent)) || context.State.HasFlag(ConsensusState.Backup)) - { - RequestChangeView(); - } - } - } - - public void RefreshPolicy() - { - Policy.Default.Refresh(); - } - - private void RequestChangeView() - { - context.ExpectedView[context.MinerIndex]++; - Log($"request change view: height={context.Height} view={context.ViewNumber} nv={context.ExpectedView[context.MinerIndex]} state={context.State}"); - timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (context.ExpectedView[context.MinerIndex] + 1)), Timeout.InfiniteTimeSpan); - SignAndRelay(context.MakeChangeView()); - CheckExpectedView(context.ExpectedView[context.MinerIndex]); - } - - private void SignAndRelay(ConsensusPayload payload) - { - SignatureContext sc; - try - { - sc = new SignatureContext(payload); - } - catch (InvalidOperationException) - { - return; - } - wallet.Sign(sc); - sc.Signable.Scripts = sc.GetScripts(); - localNode.RelayDirectly(payload); - } - - public void Start() - { - Log("OnStart"); - started = true; - Blockchain.PersistCompleted += Blockchain_PersistCompleted; - LocalNode.NewInventory += LocalNode_NewInventory; - InitializeConsensus(0); - } - } -} +using AntShares.Core; +using AntShares.IO; +using AntShares.Network; +using AntShares.Network.Payloads; +using AntShares.Wallets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace AntShares.Consensus +{ + public class ConsensusService : IDisposable + { + public const int MaxTransactionsPerBlock = 15000; + + private ConsensusContext context = new ConsensusContext(); + private LocalNode localNode; + private Wallet wallet; + private Timer timer; + private uint timer_height; + private byte timer_view; + private DateTime block_received_time; + private string log_dictionary; + private bool started = false; + + public ConsensusService(LocalNode localNode, Wallet wallet, string log_dictionary = null) + { + this.localNode = localNode; + this.wallet = wallet; + this.timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite); + this.log_dictionary = log_dictionary; + } + + private bool AddTransaction(Transaction tx, bool verify) + { + if (Blockchain.Default.ContainsTransaction(tx.Hash) || + (verify && !tx.Verify(context.Transactions.Values)) || + !CheckPolicy(tx)) + { + Log($"reject tx: {tx.Hash}{Environment.NewLine}{tx.ToArray().ToHexString()}"); + RequestChangeView(); + return false; + } + context.Transactions[tx.Hash] = tx; + if (context.TransactionHashes.Length == context.Transactions.Count) + { + if (Blockchain.GetMinerAddress(Blockchain.Default.GetMiners(context.Transactions.Values).ToArray()).Equals(context.NextMiner)) + { + Log($"send perpare response"); + context.State |= ConsensusState.SignatureSent; + context.Signatures[context.MinerIndex] = context.MakeHeader().Sign(wallet.GetAccount(context.Miners[context.MinerIndex])); + SignAndRelay(context.MakePerpareResponse(context.Signatures[context.MinerIndex])); + CheckSignatures(); + } + else + { + RequestChangeView(); + return false; + } + } + return true; + } + + private void Blockchain_PersistCompleted(object sender, Block block) + { + Log($"persist block: {block.Hash}"); + block_received_time = DateTime.Now; + InitializeConsensus(0); + } + + private void CheckExpectedView(byte view_number) + { + if (context.ViewNumber == view_number) return; + if (context.ExpectedView.Count(p => p == view_number) >= context.M) + { + InitializeConsensus(view_number); + } + } + + private static bool CheckPolicy(Transaction tx) + { + switch (Policy.Default.PolicyLevel) + { + case PolicyLevel.AllowAll: + return true; + case PolicyLevel.AllowList: + return tx.Scripts.All(p => Policy.Default.List.Contains(p.RedeemScript.ToScriptHash())) || tx.Outputs.All(p => Policy.Default.List.Contains(p.ScriptHash)); + case PolicyLevel.DenyList: + return tx.Scripts.All(p => !Policy.Default.List.Contains(p.RedeemScript.ToScriptHash())) && tx.Outputs.All(p => !Policy.Default.List.Contains(p.ScriptHash)); + default: + return false; + } + } + + private void CheckSignatures() + { + if (context.Signatures.Count(p => p != null) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p))) + { + Contract contract = Contract.CreateMultiSigContract(context.Miners[context.MinerIndex].EncodePoint(true).ToScriptHash(), context.M, context.Miners); + Block block = context.MakeHeader(); + SignatureContext sc = new SignatureContext(block); + for (int i = 0, j = 0; i < context.Miners.Length && j < context.M; i++) + if (context.Signatures[i] != null) + { + sc.AddSignature(contract, context.Miners[i], context.Signatures[i]); + j++; + } + sc.Signable.Scripts = sc.GetScripts(); + block.Transactions = context.TransactionHashes.Select(p => context.Transactions[p]).ToArray(); + Log($"relay block: {block.Hash}"); + if (!localNode.Relay(block)) + Log($"reject block: {block.Hash}"); + context.State |= ConsensusState.BlockSent; + } + } + + private MinerTransaction CreateMinerTransaction(IEnumerable transactions, uint height, ulong nonce) + { + Fixed8 amount_netfee = Block.CalculateNetFee(transactions); + TransactionOutput[] outputs = amount_netfee == Fixed8.Zero ? new TransactionOutput[0] : new[] { new TransactionOutput + { + AssetId = Blockchain.AntCoin.Hash, + Value = amount_netfee, + ScriptHash = wallet.GetContracts().First().ScriptHash + } }; + return new MinerTransaction + { + Nonce = (uint)(nonce % (uint.MaxValue + 1ul)), + Attributes = new TransactionAttribute[0], + Inputs = new CoinReference[0], + Outputs = outputs, + Scripts = new Witness[0] + }; + } + + public void Dispose() + { + Log("OnStop"); + if (timer != null) timer.Dispose(); + if (started) + { + Blockchain.PersistCompleted -= Blockchain_PersistCompleted; + LocalNode.InventoryReceiving -= LocalNode_InventoryReceiving; + LocalNode.InventoryReceived -= LocalNode_InventoryReceived; + } + } + + 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) + { + lock (context) + { + if (view_number == 0) + context.Reset(wallet); + else + context.ChangeView(view_number); + if (context.MinerIndex < 0) return; + Log($"initialize: height={context.Height} view={view_number} index={context.MinerIndex} role={(context.MinerIndex == context.PrimaryIndex ? ConsensusState.Primary : ConsensusState.Backup)}"); + if (context.MinerIndex == context.PrimaryIndex) + { + context.State |= ConsensusState.Primary; + timer_height = context.Height; + timer_view = view_number; + TimeSpan span = DateTime.Now - block_received_time; + if (span >= Blockchain.TimePerBlock) + timer.Change(0, Timeout.Infinite); + else + timer.Change(Blockchain.TimePerBlock - span, Timeout.InfiniteTimeSpan); + } + else + { + context.State = ConsensusState.Backup; + timer_height = context.Height; + timer_view = view_number; + timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (view_number + 1)), Timeout.InfiniteTimeSpan); + } + } + } + + private void LocalNode_InventoryReceived(object sender, IInventory inventory) + { + ConsensusPayload payload = inventory as ConsensusPayload; + if (payload != null) + { + lock (context) + { + if (payload.MinerIndex == context.MinerIndex) return; + if (payload.Version != ConsensusContext.Version || payload.PrevHash != context.PrevHash || payload.Height != context.Height) + return; + if (payload.MinerIndex >= context.Miners.Length) return; + ConsensusMessage message = ConsensusMessage.DeserializeFrom(payload.Data); + if (message.ViewNumber != context.ViewNumber && message.Type != ConsensusMessageType.ChangeView) + return; + switch (message.Type) + { + case ConsensusMessageType.ChangeView: + OnChangeViewReceived(payload, (ChangeView)message); + break; + case ConsensusMessageType.PerpareRequest: + OnPerpareRequestReceived(payload, (PerpareRequest)message); + break; + case ConsensusMessageType.PerpareResponse: + OnPerpareResponseReceived(payload, (PerpareResponse)message); + break; + } + } + } + } + + private void LocalNode_InventoryReceiving(object sender, InventoryReceivingEventArgs e) + { + Transaction tx = e.Inventory as Transaction; + if (tx != null) + { + lock (context) + { + if (!context.State.HasFlag(ConsensusState.Backup) || !context.State.HasFlag(ConsensusState.RequestReceived) || context.State.HasFlag(ConsensusState.SignatureSent)) + return; + if (context.Transactions.ContainsKey(tx.Hash)) return; + if (!context.TransactionHashes.Contains(tx.Hash)) return; + AddTransaction(tx, true); + e.Cancel = true; + } + } + } + + private void Log(string message) + { + DateTime now = DateTime.Now; + string line = $"[{now.TimeOfDay:hh\\:mm\\:ss}] {message}"; + Console.WriteLine(line); + if (string.IsNullOrEmpty(log_dictionary)) return; + lock (log_dictionary) + { + Directory.CreateDirectory(log_dictionary); + string path = Path.Combine(log_dictionary, $"{now:yyyy-MM-dd}.log"); + File.AppendAllLines(path, new[] { line }); + } + } + + private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message) + { + Log($"{nameof(OnChangeViewReceived)}: height={payload.Height} view={message.ViewNumber} index={payload.MinerIndex} nv={message.NewViewNumber}"); + if (message.NewViewNumber <= context.ExpectedView[payload.MinerIndex]) + return; + context.ExpectedView[payload.MinerIndex] = message.NewViewNumber; + CheckExpectedView(message.NewViewNumber); + } + + private void OnPerpareRequestReceived(ConsensusPayload payload, PerpareRequest message) + { + Log($"{nameof(OnPerpareRequestReceived)}: height={payload.Height} view={message.ViewNumber} index={payload.MinerIndex} tx={message.TransactionHashes.Length}"); + if (!context.State.HasFlag(ConsensusState.Backup) || context.State.HasFlag(ConsensusState.RequestReceived)) + return; + if (payload.MinerIndex != context.PrimaryIndex) return; + if (payload.Timestamp <= Blockchain.Default.GetHeader(context.PrevHash).Timestamp || payload.Timestamp > DateTime.Now.AddMinutes(10).ToTimestamp()) + { + Log($"Timestamp incorrect: {payload.Timestamp}"); + return; + } + context.State |= ConsensusState.RequestReceived; + context.Timestamp = payload.Timestamp; + context.Nonce = message.Nonce; + context.NextMiner = message.NextMiner; + context.TransactionHashes = message.TransactionHashes; + context.Transactions = new Dictionary(); + if (!context.MakeHeader().VerifySignature(context.Miners[payload.MinerIndex], message.Signature)) return; + context.Signatures = new byte[context.Miners.Length][]; + context.Signatures[payload.MinerIndex] = message.Signature; + Dictionary mempool = LocalNode.GetMemoryPool().ToDictionary(p => p.Hash); + foreach (UInt256 hash in context.TransactionHashes.Skip(1)) + if (mempool.ContainsKey(hash)) + if (!AddTransaction(mempool[hash], false)) + return; + if (!AddTransaction(message.MinerTransaction, true)) return; + LocalNode.AllowHashes(context.TransactionHashes.Except(context.Transactions.Keys)); + if (context.Transactions.Count < context.TransactionHashes.Length) + localNode.SynchronizeMemoryPool(); + } + + private void OnPerpareResponseReceived(ConsensusPayload payload, PerpareResponse message) + { + Log($"{nameof(OnPerpareResponseReceived)}: height={payload.Height} view={message.ViewNumber} index={payload.MinerIndex}"); + if (context.State.HasFlag(ConsensusState.BlockSent)) return; + if (context.Signatures[payload.MinerIndex] != null) return; + Block header = context.MakeHeader(); + if (header == null || !header.VerifySignature(context.Miners[payload.MinerIndex], message.Signature)) return; + context.Signatures[payload.MinerIndex] = message.Signature; + CheckSignatures(); + } + + private void OnTimeout(object state) + { + lock (context) + { + if (timer_height != context.Height || timer_view != context.ViewNumber) return; + Log($"timeout: height={timer_height} view={timer_view} state={context.State}"); + if (context.State.HasFlag(ConsensusState.Primary) && !context.State.HasFlag(ConsensusState.RequestSent)) + { + Log($"send perpare request: height={timer_height} view={timer_view}"); + context.State |= ConsensusState.RequestSent; + if (!context.State.HasFlag(ConsensusState.SignatureSent)) + { + context.Timestamp = Math.Max(DateTime.Now.ToTimestamp(), Blockchain.Default.GetHeader(context.PrevHash).Timestamp + 1); + context.Nonce = GetNonce(); + List transactions = LocalNode.GetMemoryPool().Where(p => CheckPolicy(p)).ToList(); + if (transactions.Count >= MaxTransactionsPerBlock) + transactions = transactions.OrderByDescending(p => p.NetworkFee / p.Size).Take(MaxTransactionsPerBlock - 1).ToList(); + transactions.Insert(0, CreateMinerTransaction(transactions, context.Height, context.Nonce)); + context.TransactionHashes = transactions.Select(p => p.Hash).ToArray(); + context.Transactions = transactions.ToDictionary(p => p.Hash); + context.NextMiner = Blockchain.GetMinerAddress(Blockchain.Default.GetMiners(transactions).ToArray()); + context.Signatures[context.MinerIndex] = context.MakeHeader().Sign(wallet.GetAccount(context.Miners[context.MinerIndex])); + } + SignAndRelay(context.MakePerpareRequest()); + timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (timer_view + 1)), Timeout.InfiniteTimeSpan); + } + else if ((context.State.HasFlag(ConsensusState.Primary) && context.State.HasFlag(ConsensusState.RequestSent)) || context.State.HasFlag(ConsensusState.Backup)) + { + RequestChangeView(); + } + } + } + + public void RefreshPolicy() + { + Policy.Default.Refresh(); + } + + private void RequestChangeView() + { + context.ExpectedView[context.MinerIndex]++; + Log($"request change view: height={context.Height} view={context.ViewNumber} nv={context.ExpectedView[context.MinerIndex]} state={context.State}"); + timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (context.ExpectedView[context.MinerIndex] + 1)), Timeout.InfiniteTimeSpan); + SignAndRelay(context.MakeChangeView()); + CheckExpectedView(context.ExpectedView[context.MinerIndex]); + } + + private void SignAndRelay(ConsensusPayload payload) + { + SignatureContext sc; + try + { + sc = new SignatureContext(payload); + } + catch (InvalidOperationException) + { + return; + } + wallet.Sign(sc); + sc.Signable.Scripts = sc.GetScripts(); + localNode.RelayDirectly(payload); + } + + public void Start() + { + Log("OnStart"); + started = true; + Blockchain.PersistCompleted += Blockchain_PersistCompleted; + LocalNode.InventoryReceiving += LocalNode_InventoryReceiving; + LocalNode.InventoryReceived += LocalNode_InventoryReceived; + InitializeConsensus(0); + } + } +} diff --git a/src/AntShares/Core/Transaction.cs b/src/AntShares/Core/Transaction.cs index b8c2f2c8ca..e8694e95ff 100644 --- a/src/AntShares/Core/Transaction.cs +++ b/src/AntShares/Core/Transaction.cs @@ -1,388 +1,405 @@ -using AntShares.Cryptography; -using AntShares.Cryptography.ECC; -using AntShares.IO; -using AntShares.IO.Json; -using AntShares.Network; -using AntShares.VM; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace AntShares.Core -{ - /// - /// 一切交易的基类 - /// - public abstract class Transaction : IEquatable, IInventory - { - /// - /// 交易类型 - /// - public readonly TransactionType Type; - /// - /// 版本 - /// - public byte Version; - /// - /// 该交易所具备的额外特性 - /// - public TransactionAttribute[] Attributes; - /// - /// 输入列表 - /// - public CoinReference[] Inputs; - /// - /// 输出列表 - /// - public TransactionOutput[] Outputs; - /// - /// 用于验证该交易的脚本列表 - /// - public Witness[] Scripts { get; set; } - - private UInt256 _hash = null; - public UInt256 Hash - { - get - { - if (_hash == null) - { - _hash = new UInt256(this.GetHashData().Sha256().Sha256()); - } - return _hash; - } - } - - /// - /// 清单类型 - /// - InventoryType IInventory.InventoryType => InventoryType.TX; - - private IReadOnlyDictionary _references; - /// - /// 每一个交易输入所引用的交易输出 - /// - public IReadOnlyDictionary References - { - get - { - if (_references == null) - { - Dictionary dictionary = new Dictionary(); - foreach (var group in Inputs.GroupBy(p => p.PrevHash)) - { - Transaction tx = Blockchain.Default.GetTransaction(group.Key); - if (tx == null) return null; - foreach (var reference in group.Select(p => new - { - Input = p, - Output = tx.Outputs[p.PrevIndex] - })) - { - dictionary.Add(reference.Input, reference.Output); - } - } - _references = dictionary; - } - return _references; - } - } - - public virtual int Size => sizeof(TransactionType) + sizeof(byte) + Attributes.GetVarSize() + Inputs.GetVarSize() + Outputs.GetVarSize() + Scripts.GetVarSize(); - - /// - /// 系统费用 - /// - public virtual Fixed8 SystemFee - { - get - { - if (Settings.Default.SystemFee.ContainsKey(Type)) - return Settings.Default.SystemFee[Type]; - return Fixed8.Zero; - } - } - - /// - /// 用指定的类型初始化Transaction对象 - /// - /// 交易类型 - protected Transaction(TransactionType type) - { - this.Type = type; - } - - /// - /// 反序列化 - /// - /// 数据来源 - void ISerializable.Deserialize(BinaryReader reader) - { - ((ISignable)this).DeserializeUnsigned(reader); - Scripts = reader.ReadSerializableArray(); - OnDeserialized(); - } - - /// - /// 反序列化交易中的额外数据 - /// - /// 数据来源 - protected virtual void DeserializeExclusiveData(BinaryReader reader) - { - } - - /// - /// 从指定的字节数组反序列化一笔交易 - /// - /// 字节数组 - /// 偏移量,反序列化从该偏移量处开始 - /// 返回反序列化后的结果 - public static Transaction DeserializeFrom(byte[] value, int offset = 0) - { - using (MemoryStream ms = new MemoryStream(value, offset, value.Length - offset, false)) - using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8)) - { - return DeserializeFrom(reader); - } - } - - /// - /// 反序列化 - /// - /// 数据来源 - /// 返回反序列化后的结果 - internal static Transaction DeserializeFrom(BinaryReader reader) - { - TransactionType type = (TransactionType)reader.ReadByte(); - string typeName = string.Format("{0}.{1}", typeof(Transaction).Namespace, type); - Transaction transaction = typeof(Transaction).GetTypeInfo().Assembly.CreateInstance(typeName) as Transaction; - if (transaction == null) - throw new FormatException(); - transaction.DeserializeUnsignedWithoutType(reader); - transaction.Scripts = reader.ReadSerializableArray(); - return transaction; - } - - void ISignable.DeserializeUnsigned(BinaryReader reader) - { - if ((TransactionType)reader.ReadByte() != Type) - throw new FormatException(); - DeserializeUnsignedWithoutType(reader); - } - - private void DeserializeUnsignedWithoutType(BinaryReader reader) - { - Version = reader.ReadByte(); - if (Version != 0) - throw new FormatException(); - DeserializeExclusiveData(reader); - Attributes = reader.ReadSerializableArray(); - Inputs = reader.ReadSerializableArray(); - Outputs = reader.ReadSerializableArray(ushort.MaxValue + 1); - } - - public bool Equals(Transaction other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Hash.Equals(other.Hash); - } - - public override bool Equals(object obj) - { - return Equals(obj as Transaction); - } - - public override int GetHashCode() - { - return Hash.GetHashCode(); - } - - byte[] IScriptContainer.GetMessage() - { - return this.GetHashData(); - } - - /// - /// 获取需要校验的脚本散列值 - /// - /// 返回需要校验的脚本散列值 - public virtual UInt160[] GetScriptHashesForVerifying() - { - if (References == null) throw new InvalidOperationException(); - HashSet hashes = new HashSet(Inputs.Select(p => References[p].ScriptHash)); - hashes.UnionWith(Attributes.Where(p => p.Usage == TransactionAttributeUsage.Script).Select(p => new UInt160(p.Data))); - foreach (var group in Outputs.GroupBy(p => p.AssetId)) - { - RegisterTransaction tx = Blockchain.Default.GetTransaction(group.Key) as RegisterTransaction; - if (tx == null) throw new InvalidOperationException(); - if (tx.AssetType.HasFlag(AssetType.DutyFlag)) - { - hashes.UnionWith(group.Select(p => p.ScriptHash)); - } - } - return hashes.OrderBy(p => p).ToArray(); - } - - /// - /// 获取交易后各资产的变化量 - /// - /// 返回交易后各资产的变化量 - public IEnumerable GetTransactionResults() - { - if (References == null) return null; - return References.Values.Select(p => new - { - AssetId = p.AssetId, - Value = p.Value - }).Concat(Outputs.Select(p => new - { - AssetId = p.AssetId, - Value = -p.Value - })).GroupBy(p => p.AssetId, (k, g) => new TransactionResult - { - AssetId = k, - Amount = g.Sum(p => p.Value) - }).Where(p => p.Amount != Fixed8.Zero); - } - - /// - /// 通知子类反序列化完毕 - /// - protected virtual void OnDeserialized() - { - } - - /// - /// 序列化 - /// - /// 存放序列化后的结果 - void ISerializable.Serialize(BinaryWriter writer) - { - ((ISignable)this).SerializeUnsigned(writer); - writer.Write(Scripts); - } - - /// - /// 序列化交易中的额外数据 - /// - /// 存放序列化后的结果 - protected virtual void SerializeExclusiveData(BinaryWriter writer) - { - } - - void ISignable.SerializeUnsigned(BinaryWriter writer) - { - writer.Write((byte)Type); - writer.Write(Version); - SerializeExclusiveData(writer); - writer.Write(Attributes); - writer.Write(Inputs); - writer.Write(Outputs); - } - - byte[] IApiInterface.ToArray() - { - return this.ToArray(); - } - - /// - /// 变成json对象 - /// - /// 返回json对象 - public virtual JObject ToJson() - { - JObject json = new JObject(); - json["txid"] = Hash.ToString(); - json["size"] = Size; - json["type"] = Type; - json["version"] = Version; - json["attributes"] = Attributes.Select(p => p.ToJson()).ToArray(); - json["vin"] = Inputs.Select(p => p.ToJson()).ToArray(); - json["vout"] = Outputs.Select((p, i) => p.ToJson((ushort)i)).ToArray(); - json["scripts"] = Scripts.Select(p => p.ToJson()).ToArray(); - return json; - } - - bool IInventory.Verify() - { - return Verify(Enumerable.Empty()); - } - - /// - /// 验证交易 - /// - /// 返回验证的结果 - public virtual bool Verify(IEnumerable mempool) - { - if (!Blockchain.Default.Ability.HasFlag(BlockchainAbility.UnspentIndexes) || !Blockchain.Default.Ability.HasFlag(BlockchainAbility.TransactionIndexes)) - return false; - for (int i = 1; i < Inputs.Length; i++) - for (int j = 0; j < i; j++) - if (Inputs[i].PrevHash == Inputs[j].PrevHash && Inputs[i].PrevIndex == Inputs[j].PrevIndex) - return false; - if (mempool.Where(p => p != this).SelectMany(p => p.Inputs).Intersect(Inputs).Count() > 0) - return false; - if (Blockchain.Default.IsDoubleSpend(this)) - return false; - foreach (var group in Outputs.GroupBy(p => p.AssetId)) - { - RegisterTransaction asset = Blockchain.Default.GetTransaction(group.Key) as RegisterTransaction; - if (asset == null) return false; - foreach (TransactionOutput output in group) - if (output.Value.GetData() % (long)Math.Pow(10, 8 - asset.Precision) != 0) - return false; - } - TransactionResult[] results = GetTransactionResults()?.ToArray(); - if (results == null) return false; - TransactionResult[] results_destroy = results.Where(p => p.Amount > Fixed8.Zero).ToArray(); - if (results_destroy.Length > 1) return false; - if (results_destroy.Length == 1 && results_destroy[0].AssetId != Blockchain.AntCoin.Hash) - return false; - if (SystemFee > Fixed8.Zero && (results_destroy.Length == 0 || results_destroy[0].Amount < SystemFee)) - return false; - TransactionResult[] results_issue = results.Where(p => p.Amount < Fixed8.Zero).ToArray(); - switch (Type) - { - case TransactionType.MinerTransaction: - case TransactionType.ClaimTransaction: - if (results_issue.Any(p => p.AssetId != Blockchain.AntCoin.Hash)) - return false; - break; - case TransactionType.IssueTransaction: - if (results_issue.Any(p => p.AssetId == Blockchain.AntCoin.Hash)) - return false; - break; - default: - if (results_issue.Length > 0) - return false; - break; - } - if (Attributes.Count(p => p.Usage == TransactionAttributeUsage.ECDH02 || p.Usage == TransactionAttributeUsage.ECDH03) > 1) - return false; - if (Attributes.Count(p => p.Usage == TransactionAttributeUsage.Vote) > 1024) - return false; - if (Attributes.Where(p => p.Usage == TransactionAttributeUsage.Vote).Select(p => new UInt256(p.Data)).Distinct().Count() != Attributes.Count(p => p.Usage == TransactionAttributeUsage.Vote)) - return false; - if (Attributes.Any(p => p.Usage == TransactionAttributeUsage.Vote)) - { - if (!Blockchain.Default.Ability.HasFlag(BlockchainAbility.UnspentIndexes)) - return false; - if (Outputs.All(p => !p.AssetId.Equals(Blockchain.AntShare.Hash))) - return false; - HashSet pubkeys = new HashSet(); - foreach (UInt256 vote in Attributes.Where(p => p.Usage == TransactionAttributeUsage.Vote).Select(p => new UInt256(p.Data))) - { - EnrollmentTransaction tx = Blockchain.Default.GetTransaction(vote) as EnrollmentTransaction; - if (tx == null) return false; - if (!Blockchain.Default.ContainsUnspent(vote, 0)) return false; - if (!pubkeys.Add(tx.PublicKey)) return false; - } - } - return this.VerifySignature(); - } - } -} +using AntShares.Cryptography; +using AntShares.Cryptography.ECC; +using AntShares.IO; +using AntShares.IO.Json; +using AntShares.Network; +using AntShares.VM; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace AntShares.Core +{ + /// + /// 一切交易的基类 + /// + public abstract class Transaction : IEquatable, IInventory + { + /// + /// 交易类型 + /// + public readonly TransactionType Type; + /// + /// 版本 + /// + public byte Version; + /// + /// 该交易所具备的额外特性 + /// + public TransactionAttribute[] Attributes; + /// + /// 输入列表 + /// + public CoinReference[] Inputs; + /// + /// 输出列表 + /// + public TransactionOutput[] Outputs; + /// + /// 用于验证该交易的脚本列表 + /// + public Witness[] Scripts { get; set; } + + private UInt256 _hash = null; + public UInt256 Hash + { + get + { + if (_hash == null) + { + _hash = new UInt256(this.GetHashData().Sha256().Sha256()); + } + return _hash; + } + } + + /// + /// 清单类型 + /// + InventoryType IInventory.InventoryType => InventoryType.TX; + + private Fixed8 _network_fee = -Fixed8.Satoshi; + public Fixed8 NetworkFee + { + get + { + if (_network_fee == -Fixed8.Satoshi) + { + Fixed8 input = References.Values.Where(p => p.AssetId.Equals(Blockchain.AntCoin.Hash)).Sum(p => p.Value); + Fixed8 output = Outputs.Where(p => p.AssetId.Equals(Blockchain.AntCoin.Hash)).Sum(p => p.Value); + _network_fee = input - output - SystemFee; + } + return _network_fee; + } + } + + private IReadOnlyDictionary _references; + /// + /// 每一个交易输入所引用的交易输出 + /// + public IReadOnlyDictionary References + { + get + { + if (_references == null) + { + Dictionary dictionary = new Dictionary(); + foreach (var group in Inputs.GroupBy(p => p.PrevHash)) + { + Transaction tx = Blockchain.Default.GetTransaction(group.Key); + if (tx == null) return null; + foreach (var reference in group.Select(p => new + { + Input = p, + Output = tx.Outputs[p.PrevIndex] + })) + { + dictionary.Add(reference.Input, reference.Output); + } + } + _references = dictionary; + } + return _references; + } + } + + public virtual int Size => sizeof(TransactionType) + sizeof(byte) + Attributes.GetVarSize() + Inputs.GetVarSize() + Outputs.GetVarSize() + Scripts.GetVarSize(); + + /// + /// 系统费用 + /// + public virtual Fixed8 SystemFee + { + get + { + if (Settings.Default.SystemFee.ContainsKey(Type)) + return Settings.Default.SystemFee[Type]; + return Fixed8.Zero; + } + } + + /// + /// 用指定的类型初始化Transaction对象 + /// + /// 交易类型 + protected Transaction(TransactionType type) + { + this.Type = type; + } + + /// + /// 反序列化 + /// + /// 数据来源 + void ISerializable.Deserialize(BinaryReader reader) + { + ((ISignable)this).DeserializeUnsigned(reader); + Scripts = reader.ReadSerializableArray(); + OnDeserialized(); + } + + /// + /// 反序列化交易中的额外数据 + /// + /// 数据来源 + protected virtual void DeserializeExclusiveData(BinaryReader reader) + { + } + + /// + /// 从指定的字节数组反序列化一笔交易 + /// + /// 字节数组 + /// 偏移量,反序列化从该偏移量处开始 + /// 返回反序列化后的结果 + public static Transaction DeserializeFrom(byte[] value, int offset = 0) + { + using (MemoryStream ms = new MemoryStream(value, offset, value.Length - offset, false)) + using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8)) + { + return DeserializeFrom(reader); + } + } + + /// + /// 反序列化 + /// + /// 数据来源 + /// 返回反序列化后的结果 + internal static Transaction DeserializeFrom(BinaryReader reader) + { + TransactionType type = (TransactionType)reader.ReadByte(); + string typeName = string.Format("{0}.{1}", typeof(Transaction).Namespace, type); + Transaction transaction = typeof(Transaction).GetTypeInfo().Assembly.CreateInstance(typeName) as Transaction; + if (transaction == null) + throw new FormatException(); + transaction.DeserializeUnsignedWithoutType(reader); + transaction.Scripts = reader.ReadSerializableArray(); + return transaction; + } + + void ISignable.DeserializeUnsigned(BinaryReader reader) + { + if ((TransactionType)reader.ReadByte() != Type) + throw new FormatException(); + DeserializeUnsignedWithoutType(reader); + } + + private void DeserializeUnsignedWithoutType(BinaryReader reader) + { + Version = reader.ReadByte(); + if (Version != 0) + throw new FormatException(); + DeserializeExclusiveData(reader); + Attributes = reader.ReadSerializableArray(); + Inputs = reader.ReadSerializableArray(); + Outputs = reader.ReadSerializableArray(ushort.MaxValue + 1); + } + + public bool Equals(Transaction other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Hash.Equals(other.Hash); + } + + public override bool Equals(object obj) + { + return Equals(obj as Transaction); + } + + public override int GetHashCode() + { + return Hash.GetHashCode(); + } + + byte[] IScriptContainer.GetMessage() + { + return this.GetHashData(); + } + + /// + /// 获取需要校验的脚本散列值 + /// + /// 返回需要校验的脚本散列值 + public virtual UInt160[] GetScriptHashesForVerifying() + { + if (References == null) throw new InvalidOperationException(); + HashSet hashes = new HashSet(Inputs.Select(p => References[p].ScriptHash)); + hashes.UnionWith(Attributes.Where(p => p.Usage == TransactionAttributeUsage.Script).Select(p => new UInt160(p.Data))); + foreach (var group in Outputs.GroupBy(p => p.AssetId)) + { + RegisterTransaction tx = Blockchain.Default.GetTransaction(group.Key) as RegisterTransaction; + if (tx == null) throw new InvalidOperationException(); + if (tx.AssetType.HasFlag(AssetType.DutyFlag)) + { + hashes.UnionWith(group.Select(p => p.ScriptHash)); + } + } + return hashes.OrderBy(p => p).ToArray(); + } + + /// + /// 获取交易后各资产的变化量 + /// + /// 返回交易后各资产的变化量 + public IEnumerable GetTransactionResults() + { + if (References == null) return null; + return References.Values.Select(p => new + { + AssetId = p.AssetId, + Value = p.Value + }).Concat(Outputs.Select(p => new + { + AssetId = p.AssetId, + Value = -p.Value + })).GroupBy(p => p.AssetId, (k, g) => new TransactionResult + { + AssetId = k, + Amount = g.Sum(p => p.Value) + }).Where(p => p.Amount != Fixed8.Zero); + } + + /// + /// 通知子类反序列化完毕 + /// + protected virtual void OnDeserialized() + { + } + + /// + /// 序列化 + /// + /// 存放序列化后的结果 + void ISerializable.Serialize(BinaryWriter writer) + { + ((ISignable)this).SerializeUnsigned(writer); + writer.Write(Scripts); + } + + /// + /// 序列化交易中的额外数据 + /// + /// 存放序列化后的结果 + protected virtual void SerializeExclusiveData(BinaryWriter writer) + { + } + + void ISignable.SerializeUnsigned(BinaryWriter writer) + { + writer.Write((byte)Type); + writer.Write(Version); + SerializeExclusiveData(writer); + writer.Write(Attributes); + writer.Write(Inputs); + writer.Write(Outputs); + } + + byte[] IApiInterface.ToArray() + { + return this.ToArray(); + } + + /// + /// 变成json对象 + /// + /// 返回json对象 + public virtual JObject ToJson() + { + JObject json = new JObject(); + json["txid"] = Hash.ToString(); + json["size"] = Size; + json["type"] = Type; + json["version"] = Version; + json["attributes"] = Attributes.Select(p => p.ToJson()).ToArray(); + json["vin"] = Inputs.Select(p => p.ToJson()).ToArray(); + json["vout"] = Outputs.Select((p, i) => p.ToJson((ushort)i)).ToArray(); + json["sys_fee"] = SystemFee.ToString(); + json["net_fee"] = NetworkFee.ToString(); + json["scripts"] = Scripts.Select(p => p.ToJson()).ToArray(); + return json; + } + + bool IInventory.Verify() + { + return Verify(Enumerable.Empty()); + } + + /// + /// 验证交易 + /// + /// 返回验证的结果 + public virtual bool Verify(IEnumerable mempool) + { + if (!Blockchain.Default.Ability.HasFlag(BlockchainAbility.UnspentIndexes) || !Blockchain.Default.Ability.HasFlag(BlockchainAbility.TransactionIndexes)) + return false; + for (int i = 1; i < Inputs.Length; i++) + for (int j = 0; j < i; j++) + if (Inputs[i].PrevHash == Inputs[j].PrevHash && Inputs[i].PrevIndex == Inputs[j].PrevIndex) + return false; + if (mempool.Where(p => p != this).SelectMany(p => p.Inputs).Intersect(Inputs).Count() > 0) + return false; + if (Blockchain.Default.IsDoubleSpend(this)) + return false; + foreach (var group in Outputs.GroupBy(p => p.AssetId)) + { + RegisterTransaction asset = Blockchain.Default.GetTransaction(group.Key) as RegisterTransaction; + if (asset == null) return false; + foreach (TransactionOutput output in group) + if (output.Value.GetData() % (long)Math.Pow(10, 8 - asset.Precision) != 0) + return false; + } + TransactionResult[] results = GetTransactionResults()?.ToArray(); + if (results == null) return false; + TransactionResult[] results_destroy = results.Where(p => p.Amount > Fixed8.Zero).ToArray(); + if (results_destroy.Length > 1) return false; + if (results_destroy.Length == 1 && results_destroy[0].AssetId != Blockchain.AntCoin.Hash) + return false; + if (SystemFee > Fixed8.Zero && (results_destroy.Length == 0 || results_destroy[0].Amount < SystemFee)) + return false; + TransactionResult[] results_issue = results.Where(p => p.Amount < Fixed8.Zero).ToArray(); + switch (Type) + { + case TransactionType.MinerTransaction: + case TransactionType.ClaimTransaction: + if (results_issue.Any(p => p.AssetId != Blockchain.AntCoin.Hash)) + return false; + break; + case TransactionType.IssueTransaction: + if (results_issue.Any(p => p.AssetId == Blockchain.AntCoin.Hash)) + return false; + break; + default: + if (results_issue.Length > 0) + return false; + break; + } + if (Attributes.Count(p => p.Usage == TransactionAttributeUsage.ECDH02 || p.Usage == TransactionAttributeUsage.ECDH03) > 1) + return false; + if (Attributes.Count(p => p.Usage == TransactionAttributeUsage.Vote) > 1024) + return false; + if (Attributes.Where(p => p.Usage == TransactionAttributeUsage.Vote).Select(p => new UInt256(p.Data)).Distinct().Count() != Attributes.Count(p => p.Usage == TransactionAttributeUsage.Vote)) + return false; + if (Attributes.Any(p => p.Usage == TransactionAttributeUsage.Vote)) + { + if (!Blockchain.Default.Ability.HasFlag(BlockchainAbility.UnspentIndexes)) + return false; + if (Outputs.All(p => !p.AssetId.Equals(Blockchain.AntShare.Hash))) + return false; + HashSet pubkeys = new HashSet(); + foreach (UInt256 vote in Attributes.Where(p => p.Usage == TransactionAttributeUsage.Vote).Select(p => new UInt256(p.Data))) + { + EnrollmentTransaction tx = Blockchain.Default.GetTransaction(vote) as EnrollmentTransaction; + if (tx == null) return false; + if (!Blockchain.Default.ContainsUnspent(vote, 0)) return false; + if (!pubkeys.Add(tx.PublicKey)) return false; + } + } + return this.VerifySignature(); + } + } +} diff --git a/src/AntShares/Network/InventoryReceivingEventArgs.cs b/src/AntShares/Network/InventoryReceivingEventArgs.cs new file mode 100644 index 0000000000..1ecebba2b4 --- /dev/null +++ b/src/AntShares/Network/InventoryReceivingEventArgs.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; + +namespace AntShares.Network +{ + public class InventoryReceivingEventArgs : CancelEventArgs + { + public IInventory Inventory { get; } + + public InventoryReceivingEventArgs(IInventory inventory) + { + this.Inventory = inventory; + } + } +} diff --git a/src/AntShares/Network/LocalNode.cs b/src/AntShares/Network/LocalNode.cs index f71c9b9439..b327b77f04 100644 --- a/src/AntShares/Network/LocalNode.cs +++ b/src/AntShares/Network/LocalNode.cs @@ -18,12 +18,13 @@ namespace AntShares.Network { public class LocalNode : IDisposable { - public static event EventHandler NewInventory; + public static event EventHandler InventoryReceiving; + public static event EventHandler InventoryReceived; public const uint PROTOCOL_VERSION = 0; private const int CONNECTED_MAX = 10; private const int UNCONNECTED_MAX = 1000; - public const int MemoryPoolSize = 15000; + public const int MemoryPoolSize = 30000; private static readonly Dictionary mem_pool = new Dictionary(); private readonly HashSet temp_pool = new HashSet(); @@ -120,6 +121,7 @@ private void AddTransactionLoop() Transaction[] transactions; lock (temp_pool) { + if (temp_pool.Count == 0) continue; transactions = temp_pool.ToArray(); temp_pool.Clear(); } @@ -127,16 +129,21 @@ private void AddTransactionLoop() lock (mem_pool) { transactions = transactions.Where(p => !mem_pool.ContainsKey(p.Hash) && !Blockchain.Default.ContainsTransaction(p.Hash)).ToArray(); + if (transactions.Length == 0) continue; transactions.AsParallel().ForAll(tx => { if (tx.Verify(mem_pool.Values.Concat(transactions))) verified.Add(tx); }); + if (verified.Count == 0) continue; foreach (Transaction tx in verified) mem_pool.Add(tx.Hash, tx); CheckMemPool(); } RelayDirectly(verified); + if (InventoryReceived != null) + foreach (Transaction tx in verified) + InventoryReceived(this, tx); } } @@ -169,11 +176,7 @@ private static void Blockchain_PersistCompleted(object sender, Block block) private static void CheckMemPool() { if (mem_pool.Count <= MemoryPoolSize) return; - UInt256[] hashes = mem_pool.Values.AsParallel().Select(tx => new - { - Hash = tx.Hash, - Fee = (tx.References.Values.Where(p => p.AssetId.Equals(Blockchain.AntCoin.Hash)).Sum(p => p.Value) - tx.Outputs.Where(p => p.AssetId.Equals(Blockchain.AntCoin.Hash)).Sum(p => p.Value) - tx.SystemFee) / tx.Size - }).OrderBy(p => p.Fee).Take(mem_pool.Count - MemoryPoolSize).Select(p => p.Hash).ToArray(); + UInt256[] hashes = mem_pool.Values.AsParallel().OrderBy(p => p.NetworkFee / p.Size).Take(mem_pool.Count - MemoryPoolSize).Select(p => p.Hash).ToArray(); foreach (UInt256 hash in hashes) mem_pool.Remove(hash); } @@ -370,6 +373,9 @@ public bool Relay(IInventory inventory) { if (!KnownHashes.Add(inventory.Hash)) return false; } + InventoryReceivingEventArgs args = new InventoryReceivingEventArgs(inventory); + InventoryReceiving?.Invoke(this, args); + if (args.Cancel) return false; if (inventory is Block) { if (Blockchain.Default == null) return false; @@ -386,7 +392,7 @@ public bool Relay(IInventory inventory) if (!inventory.Verify()) return false; } bool relayed = RelayDirectly(inventory); - NewInventory?.Invoke(this, inventory); + InventoryReceived?.Invoke(this, inventory); return relayed; } @@ -446,6 +452,9 @@ private void RemoteNode_InventoryReceived(object sender, IInventory inventory) { if (!KnownHashes.Add(inventory.Hash)) return; } + InventoryReceivingEventArgs args = new InventoryReceivingEventArgs(inventory); + InventoryReceiving?.Invoke(this, args); + if (args.Cancel) return; lock (temp_pool) { temp_pool.Add((Transaction)inventory); diff --git a/src/AntShares/Network/RemoteNode.cs b/src/AntShares/Network/RemoteNode.cs index a3c30f8955..88e6e680d4 100644 --- a/src/AntShares/Network/RemoteNode.cs +++ b/src/AntShares/Network/RemoteNode.cs @@ -365,7 +365,7 @@ private void RunProtocol() } lock (localNode.connectedPeers) { - if (localNode.connectedPeers.Where(p => p != this).Any(p => p.RemoteEndpoint.Address.Equals(RemoteEndpoint.Address) && p.Version.Nonce == Version.Nonce)) + if (localNode.connectedPeers.Where(p => p != this).Any(p => p.RemoteEndpoint.Address.Equals(RemoteEndpoint.Address) && p.Version?.Nonce == Version.Nonce)) { Disconnect(false); return;