Skip to content

Commit

Permalink
Sender from signers (#1752)
Browse files Browse the repository at this point in the history
* Sender from signers

* Remove co-

* Move signers outside attributes

* Fix UT and remove Signers class

* Fix UT

* Add FeeOnly scope

* Remove orderBy

* Remove _signersCache

* Fix Signers

* Fix WitnessScope

* Fix Sender

* Update TransactionAttributeType.cs

* Update Wallet.cs

* Fix Wallet

* Rename

* Update Wallet.cs

* Update Wallet.cs

* Partial UT fix

* More UT fixes

* Fix Sender's WitnessScope

* Fix Wallet

* Fix UT

* Explicit FeeOnly for DeployNativeContracts

* Same order as serialization

* Test FeeOnly

* dotnet format

* format

Co-authored-by: Erik Zhang <[email protected]>
  • Loading branch information
shargon and erikzhang authored Jul 11, 2020
1 parent 39f7506 commit c4799e9
Show file tree
Hide file tree
Showing 25 changed files with 319 additions and 258 deletions.
9 changes: 8 additions & 1 deletion src/neo/Ledger/Blockchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,15 @@ private static Transaction DeployNativeContracts()
{
Version = 0,
Script = script,
Sender = (new[] { (byte)OpCode.PUSH1 }).ToScriptHash(),
SystemFee = 0,
Signers = new[]
{
new Signer
{
Account = (new[] { (byte)OpCode.PUSH1 }).ToScriptHash(),
Scopes = WitnessScope.FeeOnly
}
},
Attributes = Array.Empty<TransactionAttribute>(),
Witnesses = new[]
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.IO.Json;
using System;
using System.IO;
using System.Linq;

namespace Neo.Network.P2P.Payloads
{
public class Cosigner : TransactionAttribute
public class Signer : ISerializable
{
// This limits maximum number of AllowedContracts or AllowedGroups here
private const int MaxSubitems = 16;
Expand All @@ -16,19 +17,20 @@ public class Cosigner : TransactionAttribute
public UInt160[] AllowedContracts;
public ECPoint[] AllowedGroups;

public override TransactionAttributeType Type => TransactionAttributeType.Cosigner;
public override bool AllowMultiple => true;

public override int Size => base.Size +
public int Size =>
/*Account*/ UInt160.Length +
/*Scopes*/ sizeof(WitnessScope) +
/*AllowedContracts*/ (Scopes.HasFlag(WitnessScope.CustomContracts) ? AllowedContracts.GetVarSize() : 0) +
/*AllowedGroups*/ (Scopes.HasFlag(WitnessScope.CustomGroups) ? AllowedGroups.GetVarSize() : 0);

protected override void DeserializeWithoutType(BinaryReader reader)
public void Deserialize(BinaryReader reader)
{
Account = reader.ReadSerializable<UInt160>();
Scopes = (WitnessScope)reader.ReadByte();
if ((Scopes & ~(WitnessScope.CalledByEntry | WitnessScope.CustomContracts | WitnessScope.CustomGroups | WitnessScope.Global)) != 0)
throw new FormatException();
if (Scopes.HasFlag(WitnessScope.Global) && Scopes != WitnessScope.Global)
throw new FormatException();
AllowedContracts = Scopes.HasFlag(WitnessScope.CustomContracts)
? reader.ReadSerializableArray<UInt160>(MaxSubitems)
: new UInt160[0];
Expand All @@ -37,7 +39,7 @@ protected override void DeserializeWithoutType(BinaryReader reader)
: new ECPoint[0];
}

protected override void SerializeWithoutType(BinaryWriter writer)
public void Serialize(BinaryWriter writer)
{
writer.Write(Account);
writer.Write((byte)Scopes);
Expand All @@ -47,9 +49,9 @@ protected override void SerializeWithoutType(BinaryWriter writer)
writer.Write(AllowedGroups);
}

public override JObject ToJson()
public JObject ToJson()
{
JObject json = base.ToJson();
var json = new JObject();
json["account"] = Account.ToString();
json["scopes"] = Scopes;
if (Scopes.HasFlag(WitnessScope.CustomContracts))
Expand Down
58 changes: 34 additions & 24 deletions src/neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,27 @@ public class Transaction : IEquatable<Transaction>, IInventory, IInteroperable

private byte version;
private uint nonce;
private UInt160 sender;
private long sysfee;
private long netfee;
private uint validUntilBlock;
private Signer[] _signers;
private TransactionAttribute[] attributes;
private byte[] script;
private Witness[] witnesses;

public const int HeaderSize =
sizeof(byte) + //Version
sizeof(uint) + //Nonce
20 + //Sender
sizeof(long) + //SystemFee
sizeof(long) + //NetworkFee
sizeof(uint); //ValidUntilBlock

public TransactionAttribute[] Attributes
{
get => attributes;
set { attributes = value; _cosigners = null; _hash = null; _size = 0; }
set { attributes = value; _hash = null; _size = 0; }
}

private Dictionary<UInt160, Cosigner> _cosigners;
public IReadOnlyDictionary<UInt160, Cosigner> Cosigners => _cosigners ??= attributes.OfType<Cosigner>().ToDictionary(p => p.Account);

/// <summary>
/// The <c>NetworkFee</c> for the transaction divided by its <c>Size</c>.
/// <para>Note that this property must be used with care. Getting the value of this property multiple times will return the same result. The value of this property can only be obtained after the transaction has been completely built (no longer modified).</para>
Expand Down Expand Up @@ -95,10 +91,15 @@ public byte[] Script
set { script = value; _hash = null; _size = 0; }
}

public UInt160 Sender
/// <summary>
/// Correspond with the first entry of Signers
/// </summary>
public UInt160 Sender => _signers[0].Account;

public Signer[] Signers
{
get => sender;
set { sender = value; _hash = null; }
get => _signers;
set { _signers = value; _hash = null; _size = 0; }
}

private int _size;
Expand All @@ -109,6 +110,7 @@ public int Size
if (_size == 0)
{
_size = HeaderSize +
Signers.GetVarSize() + // Signers
Attributes.GetVarSize() + // Attributes
Script.GetVarSize() + // Script
Witnesses.GetVarSize(); // Witnesses
Expand Down Expand Up @@ -155,9 +157,9 @@ void ISerializable.Deserialize(BinaryReader reader)
_size = (int)reader.BaseStream.Position - startPosition;
}

private static IEnumerable<TransactionAttribute> DeserializeAttributes(BinaryReader reader)
private static IEnumerable<TransactionAttribute> DeserializeAttributes(BinaryReader reader, int maxCount)
{
int count = (int)reader.ReadVarInt(MaxTransactionAttributes);
int count = (int)reader.ReadVarInt((ulong)maxCount);
HashSet<TransactionAttributeType> hashset = new HashSet<TransactionAttributeType>();
while (count-- > 0)
{
Expand All @@ -168,27 +170,35 @@ private static IEnumerable<TransactionAttribute> DeserializeAttributes(BinaryRea
}
}

private static IEnumerable<Signer> DeserializeSigners(BinaryReader reader, int maxCount)
{
int count = (int)reader.ReadVarInt((ulong)maxCount);
if (count == 0) throw new FormatException();
HashSet<UInt160> hashset = new HashSet<UInt160>();
for (int i = 0; i < count; i++)
{
Signer signer = reader.ReadSerializable<Signer>();
if (i > 0 && signer.Scopes == WitnessScope.FeeOnly)
throw new FormatException();
if (!hashset.Add(signer.Account))
throw new FormatException();
yield return signer;
}
}

public void DeserializeUnsigned(BinaryReader reader)
{
Version = reader.ReadByte();
if (Version > 0) throw new FormatException();
Nonce = reader.ReadUInt32();
Sender = reader.ReadSerializable<UInt160>();
SystemFee = reader.ReadInt64();
if (SystemFee < 0) throw new FormatException();
NetworkFee = reader.ReadInt64();
if (NetworkFee < 0) throw new FormatException();
if (SystemFee + NetworkFee < SystemFee) throw new FormatException();
ValidUntilBlock = reader.ReadUInt32();
Attributes = DeserializeAttributes(reader).ToArray();
try
{
_ = Cosigners;
}
catch (ArgumentException)
{
throw new FormatException();
}
Signers = DeserializeSigners(reader, MaxTransactionAttributes).ToArray();
Attributes = DeserializeAttributes(reader, MaxTransactionAttributes - Signers.Length).ToArray();
Script = reader.ReadVarBytes(ushort.MaxValue);
if (Script.Length == 0) throw new FormatException();
}
Expand Down Expand Up @@ -217,8 +227,7 @@ public override int GetHashCode()

public UInt160[] GetScriptHashesForVerifying(StoreView snapshot)
{
var hashes = new HashSet<UInt160>(Cosigners.Keys) { Sender };
return hashes.OrderBy(p => p).ToArray();
return Signers.Select(p => p.Account).ToArray();
}

void ISerializable.Serialize(BinaryWriter writer)
Expand All @@ -231,10 +240,10 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer)
{
writer.Write(Version);
writer.Write(Nonce);
writer.Write(Sender);
writer.Write(SystemFee);
writer.Write(NetworkFee);
writer.Write(ValidUntilBlock);
writer.Write(Signers);
writer.Write(Attributes);
writer.WriteVarBytes(Script);
}
Expand All @@ -250,6 +259,7 @@ public JObject ToJson()
json["sysfee"] = SystemFee.ToString();
json["netfee"] = NetworkFee.ToString();
json["validuntilblock"] = ValidUntilBlock;
json["signers"] = Signers.Select(p => p.ToJson()).ToArray();
json["attributes"] = Attributes.Select(p => p.ToJson()).ToArray();
json["script"] = Convert.ToBase64String(Script);
json["witnesses"] = Witnesses.Select(p => p.ToJson()).ToArray();
Expand Down
4 changes: 0 additions & 4 deletions src/neo/Network/P2P/Payloads/TransactionAttributeType.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using Neo.IO.Caching;

namespace Neo.Network.P2P.Payloads
{
public enum TransactionAttributeType : byte
{
[ReflectionCache(typeof(Cosigner))]
Cosigner = 0x01
}
}
13 changes: 9 additions & 4 deletions src/neo/Network/P2P/Payloads/WitnessScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ namespace Neo.Network.P2P.Payloads
public enum WitnessScope : byte
{
/// <summary>
/// Global allows this witness in all contexts (default Neo2 behavior)
/// This cannot be combined with other flags
/// It's only valid for be a sender, it can't be used during the execution
/// </summary>
Global = 0x00,
FeeOnly = 0,

/// <summary>
/// CalledByEntry means that this condition must hold: EntryScriptHash == CallingScriptHash
Expand All @@ -26,6 +25,12 @@ public enum WitnessScope : byte
/// <summary>
/// Custom pubkey for group members
/// </summary>
CustomGroups = 0x20
CustomGroups = 0x20,

/// <summary>
/// Global allows this witness in all contexts (default Neo2 behavior)
/// This cannot be combined with other flags
/// </summary>
Global = 0x80
}
}
15 changes: 8 additions & 7 deletions src/neo/SmartContract/ApplicationEngine.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,23 +104,24 @@ internal bool CheckWitnessInternal(UInt160 hash)
{
if (ScriptContainer is Transaction tx)
{
if (!tx.Cosigners.TryGetValue(hash, out Cosigner cosigner)) return false;
if (cosigner.Scopes == WitnessScope.Global) return true;
if (cosigner.Scopes.HasFlag(WitnessScope.CalledByEntry))
Signer signer = tx.Signers.FirstOrDefault(p => p.Account.Equals(hash));
if (signer is null) return false;
if (signer.Scopes == WitnessScope.Global) return true;
if (signer.Scopes.HasFlag(WitnessScope.CalledByEntry))
{
if (CallingScriptHash == EntryScriptHash)
return true;
}
if (cosigner.Scopes.HasFlag(WitnessScope.CustomContracts))
if (signer.Scopes.HasFlag(WitnessScope.CustomContracts))
{
if (cosigner.AllowedContracts.Contains(CurrentScriptHash))
if (signer.AllowedContracts.Contains(CurrentScriptHash))
return true;
}
if (cosigner.Scopes.HasFlag(WitnessScope.CustomGroups))
if (signer.Scopes.HasFlag(WitnessScope.CustomGroups))
{
var contract = Snapshot.Contracts[CallingScriptHash];
// check if current group is the required one
if (contract.Manifest.Groups.Select(p => p.PubKey).Intersect(cosigner.AllowedGroups).Any())
if (contract.Manifest.Groups.Select(p => p.PubKey).Intersect(signer.AllowedGroups).Any())
return true;
}
return false;
Expand Down
49 changes: 34 additions & 15 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,26 @@ public static byte[] GetPrivateKeyFromWIF(string wif)
return privateKey;
}

private static Signer[] GetSigners(UInt160 sender, Signer[] cosigners)
{
for (int i = 0; i < cosigners.Length; i++)
{
if (cosigners[i].Account.Equals(sender))
{
if (i == 0) return cosigners;
List<Signer> list = new List<Signer>(cosigners);
list.RemoveAt(i);
list.Insert(0, cosigners[i]);
return list.ToArray();
}
}
return cosigners.Prepend(new Signer
{
Account = sender,
Scopes = WitnessScope.FeeOnly
}).ToArray();
}

public virtual WalletAccount Import(X509Certificate2 cert)
{
byte[] privateKey;
Expand Down Expand Up @@ -277,19 +297,18 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null
if (balances_gas is null)
balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList();

var cosigners = cosignerList.Select(p =>
new Cosigner()
{
// default access for transfers should be valid only for first invocation
Scopes = WitnessScope.CalledByEntry,
Account = new UInt160(p.ToArray())
}).ToArray();
var cosigners = cosignerList.Select(p => new Signer()
{
// default access for transfers should be valid only for first invocation
Scopes = WitnessScope.CalledByEntry,
Account = p
}).ToArray();

return MakeTransaction(snapshot, script, cosigners, balances_gas);
return MakeTransaction(snapshot, script, cosigners, Array.Empty<TransactionAttribute>(), balances_gas);
}
}

public Transaction MakeTransaction(byte[] script, UInt160 sender = null, TransactionAttribute[] attributes = null)
public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Signer[] cosigners = null, TransactionAttribute[] attributes = null)
{
UInt160[] accounts;
if (sender is null)
Expand All @@ -299,17 +318,17 @@ public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Transac
else
{
if (!Contains(sender))
throw new ArgumentException($"The address {sender.ToString()} was not found in the wallet");
throw new ArgumentException($"The address {sender} was not found in the wallet");
accounts = new[] { sender };
}
using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot())
{
var balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList();
return MakeTransaction(snapshot, script, attributes ?? new TransactionAttribute[0], balances_gas);
return MakeTransaction(snapshot, script, cosigners ?? Array.Empty<Signer>(), attributes ?? Array.Empty<TransactionAttribute>(), balances_gas);
}
}

private Transaction MakeTransaction(StoreView snapshot, byte[] script, TransactionAttribute[] attributes, List<(UInt160 Account, BigInteger Value)> balances_gas)
private Transaction MakeTransaction(StoreView snapshot, byte[] script, Signer[] cosigners, TransactionAttribute[] attributes, List<(UInt160 Account, BigInteger Value)> balances_gas)
{
Random rand = new Random();
foreach (var (account, value) in balances_gas)
Expand All @@ -319,8 +338,8 @@ private Transaction MakeTransaction(StoreView snapshot, byte[] script, Transacti
Version = 0,
Nonce = (uint)rand.Next(),
Script = script,
Sender = account,
ValidUntilBlock = snapshot.Height + Transaction.MaxValidUntilBlockIncrement,
Signers = GetSigners(account, cosigners),
Attributes = attributes,
};

Expand All @@ -336,8 +355,8 @@ private Transaction MakeTransaction(StoreView snapshot, byte[] script, Transacti

UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot);

// base size for transaction: includes const_header + attributes + script + hashes
int size = Transaction.HeaderSize + tx.Attributes.GetVarSize() + script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length);
// base size for transaction: includes const_header + signers + attributes + script + hashes
int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length);

foreach (UInt160 hash in hashes)
{
Expand Down
Loading

0 comments on commit c4799e9

Please sign in to comment.