diff --git a/src/AntShares/Core/AgencyTransaction.cs b/src/AntShares/Core/AgencyTransaction.cs deleted file mode 100644 index e0aa061587..0000000000 --- a/src/AntShares/Core/AgencyTransaction.cs +++ /dev/null @@ -1,242 +0,0 @@ -using AntShares.IO; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace AntShares.Core -{ - /// - /// 委托交易 - /// 交易规则: - /// 1. 单个交易中,所有订单的代理人必须是同一人; - /// 2. 单个交易中,所有订单的交易商品必须完全相同,交易货币也必须完全相同; - /// 3. 交易商品不能和交易货币相同; - /// 4. 买盘和卖盘两者都至少需要包含一笔订单; - /// 5. 交易中不能包含完全未成交的订单,且至多只能包含一笔部分成交的订单; - /// 6. 如果存在部分成交的订单,则该订单的价格必须是最差的,即:对于买单,它的价格是最低价格;对于卖单,它的价格是最高价格; - /// 7. 对于买单,需以不高于委托方所指定的价格成交; - /// 8. 对于卖单,需以不低于委托方所指定的价格成交; - /// 9. 交易数量精确到10^-4,交易价格精确到10^-4; - /// - public class AgencyTransaction : Transaction - { - /// - /// 资产编号 - /// - public UInt256 AssetId; - /// - /// 货币编号 - /// - public UInt256 ValueAssetId; - /// - /// 代理人的合约散列 - /// - public UInt160 Agent; - /// - /// 订单列表 - /// - public Order[] Orders; - /// - /// 部分成交的订单 - /// - public SplitOrder SplitOrder; - - public override int Size => base.Size + AssetId.Size + ValueAssetId.Size + Agent.Size + Orders.Length.GetVarSize() + Orders.Sum(p => p.SizeInTransaction) + SplitOrder.Size; - - public AgencyTransaction() - : base(TransactionType.AgencyTransaction) - { - } - - /// - /// 反序列化交易中的额外数据 - /// - /// 数据来源 - protected override void DeserializeExclusiveData(BinaryReader reader) - { - this.AssetId = reader.ReadSerializable(); - this.ValueAssetId = reader.ReadSerializable(); - this.Agent = reader.ReadSerializable(); - this.Orders = new Order[reader.ReadVarInt(0x1000)]; - for (int i = 0; i < Orders.Length; i++) - { - Orders[i] = new Order(); - Orders[i].DeserializeInTransaction(reader, this); - } - if (reader.ReadVarInt(1) == 0) - { - this.SplitOrder = null; - } - else - { - this.SplitOrder = reader.ReadSerializable(); - } - } - - /// - /// 获取交易中所有的输入 - /// - /// 返回交易中所有的输入以及订单中的所有输入 - public override IEnumerable GetAllInputs() - { - return Orders.SelectMany(p => p.Inputs).Concat(base.GetAllInputs()); - } - - /// - /// 获得需要校验的脚本Hash - /// - /// 返回需要校验的脚本Hash - public override UInt160[] GetScriptHashesForVerifying() - { - HashSet hashes = new HashSet(); - foreach (var group in Inputs.GroupBy(p => p.PrevHash)) - { - Transaction tx = Blockchain.Default.GetTransaction(group.Key); - if (tx == null) throw new InvalidOperationException(); - AgencyTransaction tx_agency = tx as AgencyTransaction; - if (tx_agency?.SplitOrder == null || tx_agency.AssetId != AssetId || tx_agency.ValueAssetId != ValueAssetId || tx_agency.Agent != Agent) - { - hashes.UnionWith(group.Select(p => tx.Outputs[p.PrevIndex].ScriptHash)); - } - else - { - hashes.UnionWith(group.Select(p => tx.Outputs[p.PrevIndex].ScriptHash).Where(p => p != tx_agency.SplitOrder.Client)); - } - } - hashes.Add(Agent); - return hashes.OrderBy(p => p).ToArray(); - } - - /// - /// 序列化交易中的额外数据 - /// - /// 存放序列化后的结果 - protected override void SerializeExclusiveData(BinaryWriter writer) - { - writer.Write(AssetId); - writer.Write(ValueAssetId); - writer.Write(Agent); - writer.WriteVarInt(Orders.Length); - for (int i = 0; i < Orders.Length; i++) - { - Orders[i].SerializeInTransaction(writer); - } - if (SplitOrder == null) - { - writer.WriteVarInt(0); - } - else - { - writer.WriteVarInt(1); - writer.Write(SplitOrder); - } - } - - //TODO: 此处需要较多的测试来证明它的正确性 - //因为委托交易的验证算法有点太复杂了, - //考虑未来是否可以优化这个算法 - /// - /// 验证交易 - /// - /// 返回验证的结果 - public override bool Verify(IEnumerable mempool) - { - if (!base.Verify(mempool)) return false; - foreach (Order order in Orders) - if (!order.VerifySignature()) - return false; - if (AssetId == ValueAssetId) return false; - RegisterTransaction asset_value = Blockchain.Default.GetTransaction(ValueAssetId) as RegisterTransaction; - if (asset_value?.AssetType != AssetType.Currency) - return false; - List orders = new List(Orders); - foreach (var group in Inputs.GroupBy(p => p.PrevHash)) - { - Transaction tx = Blockchain.Default.GetTransaction(group.Key); - if (tx == null) return false; - AgencyTransaction tx_agency = tx as AgencyTransaction; - if (tx_agency?.SplitOrder == null || tx_agency.AssetId != AssetId || tx_agency.ValueAssetId != ValueAssetId || tx_agency.Agent != Agent) - continue; - var outputs = group.Select(p => new - { - Input = p, - Output = tx_agency.Outputs[p.PrevIndex] - }).Where(p => p.Output.ScriptHash == tx_agency.SplitOrder.Client).ToDictionary(p => p.Input, p => p.Output); - if (outputs.Count == 0) continue; - if (outputs.Count != tx_agency.Outputs.Count(p => p.ScriptHash == tx_agency.SplitOrder.Client)) - return false; - orders.Add(new Order - { - AssetId = this.AssetId, - ValueAssetId = this.ValueAssetId, - Agent = this.Agent, - Amount = tx_agency.SplitOrder.Amount, - Price = tx_agency.SplitOrder.Price, - Client = tx_agency.SplitOrder.Client, - Inputs = outputs.Keys.ToArray() - }); - } - if (orders.Count < 2) return false; - if (orders.Count(p => p.Amount > Fixed8.Zero) == 0 || orders.Count(p => p.Amount < Fixed8.Zero) == 0) - return false; - Fixed8 amount_unmatched = orders.Sum(p => p.Amount); - if (amount_unmatched == Fixed8.Zero) - { - if (SplitOrder != null) return false; - } - else - { - if (SplitOrder?.Amount != amount_unmatched) return false; - } - foreach (Order order in orders) - { - TransactionOutput[] inputs = order.Inputs.Select(p => References[p]).ToArray(); - if (order.Amount > Fixed8.Zero) - { - if (inputs.Any(p => p.AssetId != order.ValueAssetId)) return false; - if (inputs.Sum(p => p.Value) < order.Amount * order.Price) return false; - } - else - { - if (inputs.Any(p => p.AssetId != order.AssetId)) return false; - if (inputs.Sum(p => p.Value) < order.Amount) return false; - } - } - if (SplitOrder != null) - { - Fixed8 price_worst = amount_unmatched > Fixed8.Zero ? orders.Min(p => p.Price) : orders.Max(p => p.Price); - if (SplitOrder.Price != price_worst) return false; - Order[] orders_worst = orders.Where(p => p.Price == price_worst && p.Client == SplitOrder.Client).ToArray(); - if (orders_worst.Length == 0) return false; - Fixed8 amount_worst = orders_worst.Sum(p => p.Amount); - if (amount_worst.Abs() < amount_unmatched.Abs()) return false; - Order order_combine = new Order - { - AssetId = this.AssetId, - ValueAssetId = this.ValueAssetId, - Agent = this.Agent, - Amount = amount_worst - amount_unmatched, - Price = price_worst, - Client = SplitOrder.Client, - Inputs = orders_worst.SelectMany(p => p.Inputs).ToArray() - }; - foreach (Order order_worst in orders_worst) - { - orders.Remove(order_worst); - } - orders.Add(order_combine); - } - foreach (var group in orders.GroupBy(p => p.Client)) - { - TransactionOutput[] inputs = group.SelectMany(p => p.Inputs).Select(p => References[p]).ToArray(); - TransactionOutput[] outputs = Outputs.Where(p => p.ScriptHash == group.Key).ToArray(); - Fixed8 money_spent = inputs.Where(p => p.AssetId == ValueAssetId).Sum(p => p.Value) - outputs.Where(p => p.AssetId == ValueAssetId).Sum(p => p.Value); - Fixed8 amount_changed = outputs.Where(p => p.AssetId == AssetId).Sum(p => p.Value) - inputs.Where(p => p.AssetId == AssetId).Sum(p => p.Value); - if (amount_changed != group.Sum(p => p.Amount)) return false; - if (money_spent > group.Sum(p => p.Amount * p.Price)) return false; - } - return true; - } - } -} diff --git a/src/AntShares/Core/Order.cs b/src/AntShares/Core/Order.cs deleted file mode 100644 index 870d422788..0000000000 --- a/src/AntShares/Core/Order.cs +++ /dev/null @@ -1,139 +0,0 @@ -using AntShares.IO; -using AntShares.VM; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace AntShares.Core -{ - /// - /// 订单 - /// - public class Order : ISignable - { - /// - /// 资产编号 - /// - public UInt256 AssetId; - /// - /// 货币编号 - /// - public UInt256 ValueAssetId; - /// - /// 代理人的合约散列 - /// - public UInt160 Agent; - /// - /// 买入或卖出的数量,正数表示买入,负数表示卖出 - /// - public Fixed8 Amount; - /// - /// 价格 - /// - public Fixed8 Price; - /// - /// 委托人的合约散列 - /// - public UInt160 Client; - /// - /// 输入列表 - /// - public CoinReference[] Inputs; - /// - /// 用于验证该订单的脚本列表 - /// - public Witness[] Scripts { get; set; } - - public int Size => AssetId.Size + ValueAssetId.Size + Agent.Size + SizeInTransaction; - - public int SizeInTransaction => Amount.Size + Price.Size + Client.Size + Inputs.Length.GetVarSize() + Inputs.Sum(p => p.Size) + Scripts.Length.GetVarSize() + Scripts.Sum(p => p.Size); - - void ISerializable.Deserialize(BinaryReader reader) - { - ((ISignable)this).DeserializeUnsigned(reader); - Scripts = reader.ReadSerializableArray(); - } - - internal void DeserializeInTransaction(BinaryReader reader, AgencyTransaction tx) - { - DeserializeUnsignedInternal(reader, tx.AssetId, tx.ValueAssetId, tx.Agent); - Scripts = reader.ReadSerializableArray(); - } - - void ISignable.DeserializeUnsigned(BinaryReader reader) - { - UInt256 asset_id = reader.ReadSerializable(); - UInt256 value_asset_id = reader.ReadSerializable(); - if (asset_id == value_asset_id) throw new FormatException(); - UInt160 agent = reader.ReadSerializable(); - DeserializeUnsignedInternal(reader, asset_id, value_asset_id, agent); - } - - private void DeserializeUnsignedInternal(BinaryReader reader, UInt256 asset_id, UInt256 value_asset_id, UInt160 agent) - { - AssetId = asset_id; - ValueAssetId = value_asset_id; - Agent = agent; - Amount = reader.ReadSerializable(); - if (Amount == Fixed8.Zero) throw new FormatException(); - if (Amount.GetData() % 10000 != 0) throw new FormatException(); - Price = reader.ReadSerializable(); - if (Price <= Fixed8.Zero) throw new FormatException(); - if (Price.GetData() % 10000 != 0) throw new FormatException(); - Client = reader.ReadSerializable(); - Inputs = reader.ReadSerializableArray(); - if (Inputs.Distinct().Count() != Inputs.Length) - throw new FormatException(); - } - - byte[] IScriptContainer.GetMessage() - { - return this.GetHashData(); - } - - UInt160[] ISignable.GetScriptHashesForVerifying() - { - HashSet hashes = new HashSet(); - RegisterTransaction asset = Blockchain.Default.GetTransaction(AssetId) as RegisterTransaction; - if (asset == null) throw new InvalidOperationException(); - if (asset.AssetType == AssetType.Share) - { - hashes.Add(Client); - } - foreach (var group in Inputs.GroupBy(p => p.PrevHash)) - { - Transaction tx = Blockchain.Default.GetTransaction(group.Key); - if (tx == null) throw new InvalidOperationException(); - hashes.UnionWith(group.Select(p => tx.Outputs[p.PrevIndex].ScriptHash)); - } - return hashes.OrderBy(p => p).ToArray(); - } - - void ISerializable.Serialize(BinaryWriter writer) - { - ((ISignable)this).SerializeUnsigned(writer); - writer.Write(Scripts); - } - - internal void SerializeInTransaction(BinaryWriter writer) - { - writer.Write(Amount); - writer.Write(Price); - writer.Write(Client); - writer.Write(Inputs); - writer.Write(Scripts); - } - - void ISignable.SerializeUnsigned(BinaryWriter writer) - { - writer.Write(AssetId); - writer.Write(ValueAssetId); - writer.Write(Agent); - writer.Write(Amount); - writer.Write(Price); - writer.Write(Client); - writer.Write(Inputs); - } - } -} diff --git a/src/AntShares/Core/SplitOrder.cs b/src/AntShares/Core/SplitOrder.cs deleted file mode 100644 index bc9fabe007..0000000000 --- a/src/AntShares/Core/SplitOrder.cs +++ /dev/null @@ -1,45 +0,0 @@ -using AntShares.IO; -using System; -using System.IO; - -namespace AntShares.Core -{ - /// - /// 部分成交的订单 - /// - public class SplitOrder : ISerializable - { - /// - /// 买入或卖出的数量 - /// - public Fixed8 Amount; - /// - /// 价格 - /// - public Fixed8 Price; - /// - /// 委托人的合约散列 - /// - public UInt160 Client; - - public int Size => Amount.Size + Price.Size + Client.Size; - - void ISerializable.Deserialize(BinaryReader reader) - { - this.Amount = reader.ReadSerializable(); - if (Amount == Fixed8.Zero) throw new FormatException(); - if (Amount.GetData() % 10000 != 0) throw new FormatException(); - this.Price = reader.ReadSerializable(); - if (Price <= Fixed8.Zero) throw new FormatException(); - if (Price.GetData() % 10000 != 0) throw new FormatException(); - this.Client = reader.ReadSerializable(); - } - - void ISerializable.Serialize(BinaryWriter writer) - { - writer.Write(Amount); - writer.Write(Price); - writer.Write(Client); - } - } -} diff --git a/src/AntShares/Core/Transaction.cs b/src/AntShares/Core/Transaction.cs index d8b1d9c5a3..1ebc05395c 100644 --- a/src/AntShares/Core/Transaction.cs +++ b/src/AntShares/Core/Transaction.cs @@ -72,7 +72,7 @@ public IReadOnlyDictionary References if (_references == null) { Dictionary dictionary = new Dictionary(); - foreach (var group in GetAllInputs().GroupBy(p => p.PrevHash)) + foreach (var group in Inputs.GroupBy(p => p.PrevHash)) { Transaction tx = Blockchain.Default.GetTransaction(group.Key); if (tx == null) return null; @@ -188,15 +188,6 @@ public override bool Equals(object obj) return Equals(obj as Transaction); } - /// - /// 获取交易的所有输入 - /// - /// 返回交易的所有输入 - public virtual IEnumerable GetAllInputs() - { - return Inputs; - } - public override int GetHashCode() { return Hash.GetHashCode(); @@ -322,12 +313,11 @@ public virtual bool Verify(IEnumerable mempool) if (Blockchain.Default.ContainsTransaction(Hash)) return true; if (!Blockchain.Default.Ability.HasFlag(BlockchainAbility.UnspentIndexes) || !Blockchain.Default.Ability.HasFlag(BlockchainAbility.TransactionIndexes)) return false; - CoinReference[] inputs = GetAllInputs().ToArray(); - for (int i = 1; i < inputs.Length; i++) + 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) + if (Inputs[i].PrevHash == Inputs[j].PrevHash && Inputs[i].PrevIndex == Inputs[j].PrevIndex) return false; - if (mempool.SelectMany(p => p.GetAllInputs()).Intersect(GetAllInputs()).Count() > 0) + if (mempool.SelectMany(p => p.Inputs).Intersect(Inputs).Count() > 0) return false; if (Blockchain.Default.IsDoubleSpend(this)) return false; diff --git a/src/AntShares/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs b/src/AntShares/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs index 9da5652d34..dfc3647e5d 100644 --- a/src/AntShares/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs +++ b/src/AntShares/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs @@ -235,7 +235,7 @@ public override IEnumerable GetEnrollments(IEnumerable k)) { UInt256 hash = new UInt256(key.ToArray().Skip(1).Take(32).ToArray()); - if (others.SelectMany(p => p.GetAllInputs()).Any(p => p.PrevHash == hash && p.PrevIndex == 0)) + if (others.SelectMany(p => p.Inputs).Any(p => p.PrevHash == hash && p.PrevIndex == 0)) continue; yield return (EnrollmentTransaction)GetTransaction(options, hash, out height); } @@ -380,7 +380,7 @@ public override IEnumerable GetVotes(IEnumerable others) foreach (var kv in db.Find(options, SliceBuilder.Begin(DataEntryPrefix.IX_Vote), (k, v) => new { Key = k, Value = v })) { UInt256 hash = new UInt256(kv.Key.ToArray().Skip(1).ToArray()); - ushort[] indexes = kv.Value.ToArray().GetUInt16Array().Except(others.SelectMany(p => p.GetAllInputs()).Where(p => p.PrevHash == hash).Select(p => p.PrevIndex)).ToArray(); + ushort[] indexes = kv.Value.ToArray().GetUInt16Array().Except(others.SelectMany(p => p.Inputs).Where(p => p.PrevHash == hash).Select(p => p.PrevIndex)).ToArray(); if (indexes.Length == 0) continue; Transaction tx = GetTransaction(options, hash, out height); yield return new Vote @@ -404,12 +404,11 @@ public override IEnumerable GetVotes(IEnumerable others) public override bool IsDoubleSpend(Transaction tx) { - CoinReference[] inputs = tx.GetAllInputs().ToArray(); - if (inputs.Length == 0) return false; + if (tx.Inputs.Length == 0) return false; ReadOptions options = new ReadOptions(); using (options.Snapshot = db.GetSnapshot()) { - foreach (var group in inputs.GroupBy(p => p.PrevHash)) + foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) { Slice value; if (!db.TryGet(options, SliceBuilder.Begin(DataEntryPrefix.IX_Unspent).Add(group.Key), out value)) @@ -523,7 +522,7 @@ private void Persist(Block block) unspents.Add(tx.Hash, index); } } - foreach (var group in block.Transactions.SelectMany(p => p.GetAllInputs()).GroupBy(p => p.PrevHash)) + foreach (var group in block.Transactions.SelectMany(p => p.Inputs).GroupBy(p => p.PrevHash)) { int height; Transaction tx = GetTransaction(ReadOptions.Default, group.Key, out height); diff --git a/src/AntShares/Network/LocalNode.cs b/src/AntShares/Network/LocalNode.cs index 9ba7e51d4f..7d8246068c 100644 --- a/src/AntShares/Network/LocalNode.cs +++ b/src/AntShares/Network/LocalNode.cs @@ -116,7 +116,7 @@ public static void AllowHashes(IEnumerable hashes) private static void Blockchain_PersistCompleted(object sender, Block block) { - HashSet inputs = new HashSet(block.Transactions.SelectMany(p => p.GetAllInputs())); + HashSet inputs = new HashSet(block.Transactions.SelectMany(p => p.Inputs)); lock (MemoryPool) { foreach (Transaction tx in block.Transactions) @@ -125,7 +125,7 @@ private static void Blockchain_PersistCompleted(object sender, Block block) } foreach (Transaction tx in MemoryPool.Values.ToArray()) { - foreach (CoinReference input in tx.GetAllInputs()) + foreach (CoinReference input in tx.Inputs) if (inputs.Contains(input)) { MemoryPool.Remove(tx.Hash); diff --git a/src/AntShares/Wallets/Wallet.cs b/src/AntShares/Wallets/Wallet.cs index 159643b76c..f79c801760 100644 --- a/src/AntShares/Wallets/Wallet.cs +++ b/src/AntShares/Wallets/Wallet.cs @@ -679,7 +679,7 @@ private void ProcessNewBlock(Block block) } foreach (Transaction tx in block.Transactions) { - foreach (CoinReference input in tx.GetAllInputs()) + foreach (CoinReference input in tx.Inputs) { if (coins.Contains(input)) { @@ -728,9 +728,9 @@ public bool SaveTransaction(Transaction tx) lock (contracts) lock (coins) { - if (tx.GetAllInputs().Any(p => !coins.Contains(p) || coins[p].State.HasFlag(CoinState.Spent) || !coins[p].State.HasFlag(CoinState.Confirmed))) + if (tx.Inputs.Any(p => !coins.Contains(p) || coins[p].State.HasFlag(CoinState.Spent) || !coins[p].State.HasFlag(CoinState.Confirmed))) return false; - foreach (CoinReference input in tx.GetAllInputs()) + foreach (CoinReference input in tx.Inputs) { coins[input].State |= CoinState.Spent; coins[input].State &= ~CoinState.Confirmed;