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

Sender from signers #1752

Merged
merged 30 commits into from
Jul 11, 2020
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3702164
Sender from signers
shargon Jul 7, 2020
576973b
Remove co-
shargon Jul 7, 2020
a618ebf
Merge branch 'master' into sender-from-signers
shargon Jul 8, 2020
4d70d15
Move signers outside attributes
shargon Jul 8, 2020
d775492
Fix UT and remove Signers class
shargon Jul 9, 2020
fc6a3fa
Fix UT
shargon Jul 9, 2020
ec0262e
Add FeeOnly scope
shargon Jul 9, 2020
09860a4
Remove orderBy
shargon Jul 9, 2020
f4a6205
Merge branch 'master' into sender-from-signers
erikzhang Jul 10, 2020
a726699
Remove _signersCache
erikzhang Jul 10, 2020
67dc7e3
Fix Signers
erikzhang Jul 10, 2020
881fb88
Fix WitnessScope
erikzhang Jul 10, 2020
e55dfee
Fix Sender
erikzhang Jul 10, 2020
e28cd1d
Update TransactionAttributeType.cs
erikzhang Jul 10, 2020
eb7e302
Update Wallet.cs
erikzhang Jul 10, 2020
c536c30
Fix Wallet
erikzhang Jul 10, 2020
0f5db32
Rename
erikzhang Jul 10, 2020
8a8b837
Update Wallet.cs
erikzhang Jul 10, 2020
ffc55fe
Update Wallet.cs
erikzhang Jul 10, 2020
e018eb6
Partial UT fix
shargon Jul 10, 2020
59f934d
More UT fixes
shargon Jul 10, 2020
eea80a2
Fix Sender's WitnessScope
erikzhang Jul 10, 2020
cf8ffba
Fix Wallet
erikzhang Jul 10, 2020
6b4a56b
Fix UT
shargon Jul 10, 2020
a2ae4a2
Explicit FeeOnly for DeployNativeContracts
shargon Jul 10, 2020
f09a8a5
Same order as serialization
shargon Jul 10, 2020
4870875
Test FeeOnly
shargon Jul 10, 2020
abc7999
Merge branch 'master' into sender-from-signers
shargon Jul 10, 2020
cb8f978
dotnet format
shargon Jul 10, 2020
be47d0d
format
erikzhang Jul 10, 2020
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
9 changes: 8 additions & 1 deletion src/neo/Ledger/Blockchain.cs
Original file line number Diff line number Diff line change
@@ -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[]
{
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;
@@ -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];
@@ -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);
@@ -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))
58 changes: 34 additions & 24 deletions src/neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
@@ -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>
@@ -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;
@@ -109,6 +110,7 @@ public int Size
if (_size == 0)
{
_size = HeaderSize +
Signers.GetVarSize() + // Signers
Attributes.GetVarSize() + // Attributes
Script.GetVarSize() + // Script
Witnesses.GetVarSize(); // Witnesses
@@ -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)
{
@@ -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();
}
@@ -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)
@@ -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);
}
@@ -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();
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
@@ -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
@@ -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
@@ -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;
49 changes: 34 additions & 15 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
@@ -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;
@@ -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)
@@ -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)
@@ -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,
};

@@ -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)
{
Loading