-
Notifications
You must be signed in to change notification settings - Fork 72
[Infra] System Contract Dictionary #490
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
Conversation
|
I prefer this one over 489 |
|
@noescape00 , I've added some more functionality if you would like to have a look now. |
| for (int i = 0; i < signatories.Length; i++) | ||
| Assert(signatories[i] != address, "The signatory already exists."); | ||
|
|
||
| Assert((signatories.Length + 1) == newSize, "The expected size is incorrect."); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Equality check should not be negated in here?
It is correct afaik. The error is displayed when the assertion fails.
YakupIpek
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added my feedbacks and i did not leave same feedbacks on duplicated issues. So i will review again after you submited changes.
I suggest you to review and follow style of DAO contract
https://github.com/stratisproject/CirrusSmartContracts/blob/master/Testnet/DAOContract/DAOContract/DAOContract.cs
| @@ -0,0 +1,205 @@ | |||
| using Stratis.SmartContracts; | |||
| using ECRecover=Stratis.SCL.Crypto.ECRecover; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you import just namespace in here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These type of using statements for fixing conflicts when one type is exist in more than 2 namespace so this is not a case in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... It would protect against any conflicts that may arise in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems fine.
|
|
||
| public SystemContractsDictionary(ISmartContractState state) : base(state) | ||
| { | ||
| this.State.SetArray($"Signatories:{primaryGroup}", new[] { new Address(0, 0, 0, 0, 0), new Address(0, 0, 0, 0, 1), new Address(0, 0, 0, 0, 2) }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These value should be hard coded ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These "default" values should be updated at some stage with the correct default values. The AddSignatory and RemoveSignatory will allow the defaults to be changed. I am worried about passing the defaults to the constructor as maybe this will lead to reduced or single person security and maybe make a mockery of the multiple person security this wants to offer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
State should be access by getters/setters in everywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/Stratis.SmartContracts.CLR.Tests/SmartContracts/SystemContractsDictionary.cs
Outdated
Show resolved
Hide resolved
| private void VerifySignatures(byte[] signatures, string authorizationChallenge) | ||
| { | ||
| string[] sigs = this.Serializer.ToArray<string>(signatures); | ||
| Assert(ECRecover.TryVerifySignatures(sigs, System.Text.Encoding.ASCII.GetBytes(authorizationChallenge), this.Signatories, out Address[] verifiedSignatures) && verifiedSignatures.Length >= this.Quorum, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move namespace to top of page ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO ECRecover.TryVerifySignatures should be GetVerifiedSignatories and return addresses otherwise Try prefix and out param is quite confusing to me. I suggest code like below.
var challange = Encoding.ASCII.GetBytes(authorizationChallenge);
var verifieds = ECRecover.GEtVerifiedSignatures(sigs, challange, this.Signatories);
Assert(verifieds.Length >= this.Quorum, $"Please provide {this.Quorum} valid signatures for '{authorizationChallenge}'.");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rowandh , this was your suggestion, wdyt? I'm on the fence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When TryVerifySignatures will return false ?
When there is some verified signatories ? No
When there is no any verified signatories ? Still no
These are very unexpected behavior from this method.
It only returns false if provided parameters are null. It is clear that expected behavior of the method is return verified signatories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is how it was and frankly I had similar thoughts as Yakup. @rowandh should I change this back?
|
|
||
| public Address[] GetSignatories(string group) | ||
| { | ||
| Assert(!string.IsNullOrEmpty(group)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assert is not necessary so you can remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does make a difference by throwing an error versus simply returning the default. Intuitively throwing an error makes more sense but perhaps there is some reason you don't want to do that for smart contracts. @rowandh , wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might save a small amount of gas by doing this. Seems fine.
| return this.State.GetUInt32($"Quorum:{group}"); | ||
| } | ||
|
|
||
| public void AddSignatory(byte[] signatures, string group, Address address, uint newSize, uint newQuorum) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who is going to call this method ? Owner of the contract or anyone ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyone that's willing to pay the gas cost to execute it. However signatures are checked before any updates are made.
|
|
||
| Address[] signatories = this.GetSignatories(group); | ||
| for (int i = 0; i < signatories.Length; i++) | ||
| Assert(signatories[i] != address, "The signatory already exists."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding, removing or checking existence of signatories cause iterative codes and resizing array costs. Alternatively we can index signatories in state like
"Signatories:{signature}" => group
After that you can quickly verify it is existence in dictionary
Another key pair alternative would be
"Signatories:{group}:{signature}" => bool(true)
@rowandh wdty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would shift the problem of being able to obtain a list of all signatories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proably "Signatories:{signature}" => group wouldn't work because same address can be added to different groups so second solution is only solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@YakupIpek , given your approach how would you list all the signatories of a group? I've optimized for this method since the other methods would occur far less frequently:
public Address[] GetSignatories(string group)
{
return this.State.GetArray<Address>($"Signatories:{group}");
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not quite sure which path we have to follow right now. You can still separate index like $"Signatories:{group}:{index}" but you have to iterate it at the end if you want to obtain all of them.
Lets wait @rowandh feedback.
| } | ||
|
|
||
| public void RemoveSignatory(byte[] signatures, string group, Address address, uint newSize, uint newQuorum) | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These methods can be called by anyone ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyone that's willing to pay the gas cost to execute it. However signatures are checked before any updates are made.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is really needed to specify newSize and newQuorum, why not to automate it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really want the user to be very exact about the change being accomplished especially due to how important the quorum count is in relation to the signatory count. The user must know exactly what is being accomplished in that regards and show that knowledge by specifying the values explicitly. All the signatories will also see that information in the security challenge when signing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hımm but the transaction is going to fail and cost fee if anyone going to call these methods in same block or in prev blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand what you're saying. Will mull it over.
|
|
||
| public UInt256 GetCodeHash(string name) | ||
| { | ||
| Assert(!string.IsNullOrEmpty(name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imo these asserts are not necessary. If someone send null,empty or none existence key then we return default value already from state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does make a difference by throwing an error versus simply returning the default. Intuitively throwing an error makes more sense but perhaps there is some reason you don't want to do that for smart contracts. @rowandh , wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As caller of the method, i should to verify that there is hash code or not for given name parameter in any case and i do it by checking its value is default or not. But you added assertion, in the case of it is null so i have to verify exception + default value check.
We should not optimize code for exceptional cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is common way we did in other contracts ussually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is insufficient checks something like "Quorum:" could obtain a value, then when "Quorum:" is accessed later a junk value could be returned.
so i have to verify exception + default value check.
You don't have to verify exception if you're not passing null values. If you are unexpectedly passing null values then it better to get an error than some junk value you may proceed processing with.
| string authorizationChallenge; | ||
| if (whiteListEntry.CodeHash == default(UInt256)) | ||
| { | ||
| authorizationChallenge = $"{nameof(WhiteList)}(Nonce:{nonce},CodeHash:{codeHash},LastAddress:{lastAddress},Name:{name})"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not as below ?
authorizationChallenge = $"{nameof(WhiteList)}:{nonce}:{codeHash},{lastAddress},{name})"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's meant to be as human readable as possible to ensure signatories can read it and know what they are signing.
| private void VerifySignatures(byte[] signatures, string authorizationChallenge) | ||
| { | ||
| string[] sigs = this.Serializer.ToArray<string>(signatures); | ||
| Assert(ECRecover.TryVerifySignatures(sigs, System.Text.Encoding.ASCII.GetBytes(authorizationChallenge), this.Signatories, out Address[] verifiedSignatures) && verifiedSignatures.Length >= this.Quorum, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When TryVerifySignatures will return false ?
When there is some verified signatories ? No
When there is no any verified signatories ? Still no
These are very unexpected behavior from this method.
It only returns false if provided parameters are null. It is clear that expected behavior of the method is return verified signatories.
src/Stratis.SmartContracts.CLR.Tests/SmartContracts/SystemContractsDictionary.cs
Outdated
Show resolved
Hide resolved
|
|
||
| Address[] signatories = this.GetSignatories(group); | ||
| for (int i = 0; i < signatories.Length; i++) | ||
| Assert(signatories[i] != address, "The signatory already exists."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not quite sure which path we have to follow right now. You can still separate index like $"Signatories:{group}:{index}" but you have to iterate it at the end if you want to obtain all of them.
Lets wait @rowandh feedback.
src/Stratis.SmartContracts.CLR.Tests/SmartContracts/SystemContractsDictionary.cs
Outdated
Show resolved
Hide resolved
src/Stratis.SmartContracts.CLR.Tests/SmartContracts/SystemContractsDictionary.cs
Outdated
Show resolved
Hide resolved
|
|
||
| public UInt256 GetCodeHash(string name) | ||
| { | ||
| Assert(!string.IsNullOrEmpty(name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As caller of the method, i should to verify that there is hash code or not for given name parameter in any case and i do it by checking its value is default or not. But you added assertion, in the case of it is null so i have to verify exception + default value check.
We should not optimize code for exceptional cases.
|
|
||
| public UInt256 GetCodeHash(string name) | ||
| { | ||
| Assert(!string.IsNullOrEmpty(name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is common way we did in other contracts ussually.
src/Stratis.SmartContracts.CLR.Tests/SmartContracts/SystemContractsDictionary.cs
Outdated
Show resolved
Hide resolved
|
Please add xmldoc for all methods of |
| public static bool TryVerifySignatures(string[] signatures, byte[] message, Address[] addresses, out Address[] verifiedAddresses) | ||
| /// <param name="addresses">The addresses returned are intersected with these addresses (if not <c>null</c>).</param> | ||
| /// <returns>The list of addresses that produced the signatures and constrained to the list provided in <paramref name="addresses"/>.</returns> | ||
| public static Address[] GetVerifiedSignatures(string[] signatures, string message, Address[] addresses) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an API change to the SCL library. I don't think these should be made randomly in PRs like this but rather as part of a structured API design for the initial SCL release.
|
Moved to other PRs. |
Note that this approach, relying on signatures, does not require additional external permission management.
Note: The contract hash of the "System Contract Dictionary" itself will be hard-coded (as being white-listed) in the code base. Initially only the first deployment will be used to determine the contract address unless it is self-listed with a different address in which case we will "follow the chain" to find the most recent version.
Merge PR #509 first.