[Infra] System Contract Dictionary#490
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.
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.
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.
Can you import just namespace in here ?
There was a problem hiding this comment.
Is there a reason?
There was a problem hiding this comment.
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.
Hmm... It would protect against any conflicts that may arise in the future?
|
|
||
| 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.
These value should be hard coded ?
There was a problem hiding this comment.
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.
State should be access by getters/setters in everywhere
| 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.
Can you move namespace to top of page ?
There was a problem hiding this comment.
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.
@rowandh , this was your suggestion, wdyt? I'm on the fence.
There was a problem hiding this comment.
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.
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.
This assert is not necessary so you can remove it.
There was a problem hiding this comment.
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.
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.
Who is going to call this method ? Owner of the contract or anyone ?
There was a problem hiding this comment.
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.
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.
That would shift the problem of being able to obtain a list of all signatories.
There was a problem hiding this comment.
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.
@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.
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.
These methods can be called by anyone ?
There was a problem hiding this comment.
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.
It is really needed to specify newSize and newQuorum, why not to automate it ?
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
This is common way we did in other contracts ussually.
There was a problem hiding this comment.
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.
Why not as below ?
authorizationChallenge = $"{nameof(WhiteList)}:{nonce}:{codeHash},{lastAddress},{name})"
There was a problem hiding this comment.
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.
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.
|
|
||
| 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.
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 UInt256 GetCodeHash(string name) | ||
| { | ||
| Assert(!string.IsNullOrEmpty(name)); |
There was a problem hiding this comment.
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.
This is common way we did in other contracts ussually.
|
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.
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.