/// <summary> /// Gets the hash of a JSON string value. /// </summary> /// <param name="value">The value to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a JSON string value.</returns> private UInt192 GetStringHash(string value, UInt192 seed) { UInt192 hash = this.GetHash(this.HashSeedValues.String, seed); byte[] stringBytes = Encoding.UTF8.GetBytes(value); return(this.GetHash(stringBytes, hash)); }
/// <summary> /// Gets the hash of a JSON number value. /// </summary> /// <param name="number">The number to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a JSON number value.</returns> private static UInt192 GetNumberHash(double number, UInt192 seed) { UInt192 hash = DistinctHash.GetHash(DistinctHash.NumberHashSeed, seed); hash = DistinctHash.GetHash((UInt192)BitConverter.DoubleToInt64Bits(number), hash); return(hash); }
/// <summary> /// Gets the hash of a JSON number value. /// </summary> /// <param name="number">The number to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a JSON number value.</returns> private UInt192 GetNumberHash(double number, UInt192 seed) { UInt192 hash = this.GetHash(this.HashSeedValues.Number, seed); hash = this.GetHash((UInt192)BitConverter.DoubleToInt64Bits(number), hash); return(hash); }
/// <summary> /// Gets the hash of a byte array. /// </summary> /// <param name="bytes">The bytes.</param> /// <param name="seed">The seed.</param> /// <returns>The hash.</returns> public UInt192 GetHash(byte[] bytes, UInt192 seed) { UInt128 hash128 = MurmurHash3.Hash128(bytes, bytes.Length, UInt128.Create(seed.GetLow(), seed.GetMid())); ulong hash64 = MurmurHash3.Hash64(bytes, bytes.Length, seed.GetHigh()); return(UInt192.Create(hash128.GetLow(), hash128.GetHigh(), hash64)); }
/// <summary> /// Gets the hash of a JSON string value. /// </summary> /// <param name="value">The value to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a JSON string value.</returns> private static UInt192 GetStringHash(string value, UInt192 seed) { UInt192 hash = DistinctHash.GetHash(DistinctHash.StringHashSeed, seed); byte[] stringBytes = Encoding.UTF8.GetBytes(value); return(DistinctHash.GetHash(stringBytes, hash)); }
/// <summary> /// Gets the hash of a JSON object. /// </summary> /// <param name="cosmosObject">The object to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a JSON object.</returns> private static UInt192 GetObjectHash(CosmosObject cosmosObject, UInt192 seed) { // Start the object with a distinct hash, so that empty object doesn't hash to another value. UInt192 hash = DistinctHash.GetHash(DistinctHash.ObjectHashSeed, seed); //// Intermediate hashes of all the properties, which we don't want to xor with the final hash //// otherwise the following will collide: ////{ //// "pet":{ //// "name":"alice", //// "age":5 //// }, //// "pet2":{ //// "name":"alice", //// "age":5 //// } ////} //// ////{ //// "pet":{ //// "name":"bob", //// "age":5 //// }, //// "pet2":{ //// "name":"bob", //// "age":5 //// } ////} //// because they only differ on the name, but it gets repeated meaning that //// hash({"name":"bob", "age":5}) ^ hash({"name":"bob", "age":5}) is the same as //// hash({"name":"alice", "age":5}) ^ hash({"name":"alice", "age":5}) UInt192 intermediateHash = 0; // Property order should not result in a different hash. // This is consistent with equality comparison. foreach (KeyValuePair <string, CosmosElement> kvp in cosmosObject) { UInt192 nameHash = DistinctHash.GetHash( CosmosString.Create(kvp.Key), DistinctHash.PropertyNameHashSeed); UInt192 propertyHash = DistinctHash.GetHash(kvp.Value, nameHash); //// xor is symmetric meaning that a ^ b = b ^ a //// Which is great since now we can add the property hashes to the intermediate hash //// in any order and get the same result, which upholds our definition of equality. //// Note that we don't have to worry about a ^ a = 0 = b ^ b for duplicate property values, //// since the hash of property values are seeded with the hash of property names, //// which are unique within an object. intermediateHash ^= propertyHash; } // Only if the object was not empty do we want to bring in the intermediate hash. if (intermediateHash > 0) { hash = DistinctHash.GetHash(intermediateHash, hash); } return(hash); }
/// <summary> /// Reads the nullable UInt192 /// </summary> /// <param name="reader">The reader to read from.</param> /// <param name="objectType">The object type.</param> /// <param name="existingValue">The existing value.</param> /// <param name="serializer">The serializer.</param> /// <returns>The nullable UInt192.</returns> public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.Value == null) { return(null); } return(UInt192.Parse(reader.Value.ToString())); }
/// <summary> /// Gets the hash of a byte array. /// </summary> /// <param name="bytes">The bytes.</param> /// <param name="seed">The seed.</param> /// <returns>The hash.</returns> public static UInt192 GetHash(byte[] bytes, UInt192 seed) { // TODO: Have MurmurHash3 work on Span<T> instead. Documents.UInt128 hash128 = Microsoft.Azure.Documents.Routing.MurmurHash3.Hash128( bytes, bytes.Length, Documents.UInt128.Create(seed.GetLow(), seed.GetMid())); ulong hash64 = Microsoft.Azure.Documents.Routing.MurmurHash3.Hash64(bytes, bytes.Length, seed.GetHigh()); return(UInt192.Create(hash128.GetLow(), hash128.GetHigh(), hash64)); }
/// <summary> /// Gets the hash of a JToken given a seed. /// </summary> /// <param name="cosmosElement">The cosmos element to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of the JToken.</returns> private static UInt192 GetHash(CosmosElement cosmosElement, UInt192 seed) { if (cosmosElement == null) { return(DistinctHash.GetUndefinedHash(seed)); } CosmosElementType cosmosElementType = cosmosElement.Type; UInt192 hash; switch (cosmosElementType) { case CosmosElementType.Array: hash = DistinctHash.GetArrayHash(cosmosElement as CosmosArray, seed); break; case CosmosElementType.Boolean: hash = DistinctHash.GetBooleanHash((cosmosElement as CosmosBoolean).Value, seed); break; case CosmosElementType.Null: hash = DistinctHash.GetNullHash(seed); break; case CosmosElementType.Number: // TODO: we need to differentiate between the different number types. CosmosNumber cosmosNumber = cosmosElement as CosmosNumber; double number; if (cosmosNumber.IsFloatingPoint) { number = cosmosNumber.AsFloatingPoint().Value; } else { number = cosmosNumber.AsInteger().Value; } hash = DistinctHash.GetNumberHash(number, seed); break; case CosmosElementType.Object: hash = DistinctHash.GetObjectHash(cosmosElement as CosmosObject, seed); break; case CosmosElementType.String: hash = DistinctHash.GetStringHash((cosmosElement as CosmosString).Value, seed); break; default: throw new ArgumentException($"Unexpected {nameof(CosmosElementType)} : {cosmosElementType}"); } return(hash); }
/// <summary> /// Gets the hash of a JToken given a seed. /// </summary> /// <param name="cosmosElement">The cosmos element to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of the JToken.</returns> private UInt192 GetHashToken(CosmosElement cosmosElement, UInt192 seed) { if (cosmosElement == null) { return(this.GetUndefinedHash(seed)); } CosmosElementType cosmosElementType = cosmosElement.Type; UInt192 hash; switch (cosmosElementType) { case CosmosElementType.Array: hash = this.GetArrayHash(cosmosElement as CosmosArray, seed); break; case CosmosElementType.Boolean: hash = this.GetBooleanHash((cosmosElement as CosmosBoolean).Value, seed); break; case CosmosElementType.Null: hash = this.GetNullHash(seed); break; case CosmosElementType.Number: CosmosNumber cosmosNumber = (cosmosElement as CosmosNumber); double number; if (cosmosNumber.IsFloatingPoint) { number = cosmosNumber.AsFloatingPoint().Value; } else { number = cosmosNumber.AsInteger().Value; } hash = this.GetNumberHash(number, seed); break; case CosmosElementType.Object: hash = this.GetObjectHash(cosmosElement as CosmosObject, seed); break; case CosmosElementType.String: hash = this.GetStringHash((cosmosElement as CosmosString).Value, seed); break; default: throw new ArgumentException($"Unexpected {nameof(CosmosElementType)} : {cosmosElementType}"); } return(hash); }
/// <summary> /// Writes the nullable UInt192 /// </summary> /// <param name="writer">The writer.</param> /// <param name="value">The value.</param> /// <param name="serializer">The serializer.</param> public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); } else { UInt192 uint192 = (UInt192)value; JToken token = JToken.FromObject(uint192.ToString()); token.WriteTo(writer); } }
/// <summary> /// Adds a string to the distinct map. /// </summary> /// <param name="value">The string to add.</param> /// <returns>Whether or not the value was successfully added.</returns> private bool AddStringValue(string value) { bool added = false; int utf8Length = Encoding.UTF8.GetByteCount(value); // If you fit the string with full fidelity in 24 bytes, then you might as well just hash the string. if (utf8Length <= UnorderdDistinctMap.UInt192Length) { // Zero out the array since you want all trailing bytes to be 0 for the conversions that happen next. Array.Clear(this.utf8Buffer, 0, this.utf8Buffer.Length); Encoding.UTF8.GetBytes(value, 0, utf8Length, this.utf8Buffer, 0); if (utf8Length == 0) { added = this.AddSimpleValue(SimpleValues.EmptyString); } else if (utf8Length <= UnorderdDistinctMap.UIntLength) { uint uintValue = BitConverter.ToUInt32(this.utf8Buffer, 0); added = this.stringsLength4.Add(uintValue); } else if (utf8Length <= UnorderdDistinctMap.ULongLength) { ulong uLongValue = BitConverter.ToUInt64(this.utf8Buffer, 0); added = this.stringLength8.Add(uLongValue); } else if (utf8Length <= UnorderdDistinctMap.UInt128Length) { UInt128 uInt128Value = UInt128.FromByteArray(this.utf8Buffer, 0); added = this.stringLength16.Add(uInt128Value); } else { UInt192 uInt192Value = UInt192.FromByteArray(this.utf8Buffer, 0); added = this.stringLength24.Add(uInt192Value); } } else { // Else the string is too large and we will just store the hash. UInt192 uint192Value = DistinctMap.GetHash(CosmosString.Create(value)); added = this.stringLength24Plus.Add(uint192Value); } return(added); }
/// <summary> /// Adds a JToken to this map if it hasn't already been added. /// </summary> /// <param name="cosmosElement">The element to add.</param> /// <param name="hash">The hash of the token.</param> /// <returns>Whether or not the item was added to this Distinct Map.</returns> /// <remarks>This function assumes data is added in sorted order.</remarks> public override bool Add(CosmosElement cosmosElement, out UInt192?hash) { hash = DistinctMap.GetHash(cosmosElement); bool added; if (this.lastHash != hash) { this.lastHash = hash.Value; added = true; } else { added = false; } return(added); }
/// <summary> /// Adds a JToken to this map if it hasn't already been added. /// </summary> /// <param name="jToken">The token to add.</param> /// <param name="hash">The hash of the token.</param> /// <returns>Whether or not the item was added to this Distinct Map.</returns> /// <remarks>This function assumes data is added in sorted order.</remarks> public override bool Add(JToken jToken, out UInt192?hash) { hash = DistinctMap.GetHash(jToken); bool added; if (this.lastHash != hash) { this.lastHash = hash.Value; added = true; } else { added = false; } return(added); }
/// <summary> /// Gets the hash of a JToken given a seed. /// </summary> /// <param name="value">The JToken to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of the JToken.</returns> private UInt192 GetHashToken(JToken value, UInt192 seed) { JTokenType jTokenType = value.Type; switch (jTokenType) { case JTokenType.Object: return(this.GetObjectHash((JObject)value, seed)); case JTokenType.Array: return(this.GetArrayHash((JArray)value, seed)); case JTokenType.Integer: case JTokenType.Float: return(this.GetNumberHash((double)value, seed)); case JTokenType.Guid: case JTokenType.TimeSpan: case JTokenType.Uri: case JTokenType.Date: case JTokenType.String: return(this.GetStringHash(value.ToString(), seed)); case JTokenType.Boolean: return(this.GetBooleanHash((bool)value, seed)); case JTokenType.Null: return(this.GetNullHash(seed)); case JTokenType.None: case JTokenType.Undefined: return(this.GetUndefinedHash(seed)); case JTokenType.Constructor: case JTokenType.Property: case JTokenType.Comment: case JTokenType.Raw: case JTokenType.Bytes: default: throw new ArgumentException($"Unexpected JTokenType of: {jTokenType}"); } }
/// <summary> /// Gets the hash of a JSON array. /// </summary> /// <param name="cosmosArray">The array to hash.</param> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a JSON array.</returns> private static UInt192 GetArrayHash(CosmosArray cosmosArray, UInt192 seed) { // Start the array with a distinct hash, so that empty array doesn't hash to another value. UInt192 hash = DistinctHash.GetHash(DistinctHash.ArrayHashSeed, seed); // Incorporate all the array items into the hash. for (int index = 0; index < cosmosArray.Count; index++) { CosmosElement arrayItem = cosmosArray[index]; // Order of array items matter in equality check, so we add the index just to be safe. // For now we know that murmurhash will correctly give a different hash for // [true, false, true] and [true, true, false] // due to the way the seed works. // But we add the index just incase that property does not hold in the future. UInt192 arrayItemSeed = DistinctHash.ArrayIndexHashSeed + index; hash = DistinctHash.GetHash(hash, DistinctHash.GetHash(arrayItem, arrayItemSeed)); } return(hash); }
/// <summary> /// Initializes a new instance of the HashSeeds struct. /// </summary> /// <param name="rootHashSeed">The seed used for the JSON root.</param> /// <param name="nullHashSeed">The seed used for JSON null values.</param> /// <param name="falseHashSeed">The seed used for JSON false values.</param> /// <param name="trueHashSeed">The seed used for JSON true values.</param> /// <param name="numberHashSeed">The seed used for JSON number values.</param> /// <param name="stringHashSeed">The seed used for JSON string values.</param> /// <param name="arrayHashSeed">The seed used for JSON array values.</param> /// <param name="objectHashSeed">The seed used for JSON object values.</param> /// <param name="arrayIndexHashSeed">The seed used for JSON array elements.</param> /// <param name="propertyNameHashSeed">The seed used for JSON property names.</param> public HashSeeds( UInt192 rootHashSeed, UInt192 nullHashSeed, UInt192 falseHashSeed, UInt192 trueHashSeed, UInt192 numberHashSeed, UInt192 stringHashSeed, UInt192 arrayHashSeed, UInt192 objectHashSeed, UInt192 arrayIndexHashSeed, UInt192 propertyNameHashSeed) { this.Root = rootHashSeed; this.Null = nullHashSeed; this.False = falseHashSeed; this.True = trueHashSeed; this.Number = numberHashSeed; this.String = stringHashSeed; this.Array = arrayHashSeed; this.Object = objectHashSeed; this.ArrayIndex = arrayIndexHashSeed; this.PropertyName = propertyNameHashSeed; }
/// <summary> /// Initializes a new instance of the OrderedDistinctMap class. /// </summary> /// <param name="lastHash">The previous hash from the previous continuation.</param> public OrderedDistinctMap(UInt192 lastHash) { this.lastHash = lastHash; }
public override async Task <QueryResponseCore> DrainAsync( int maxElements, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // Draining GROUP BY is broken down into two stages: QueryResponseCore response; if (!this.Source.IsDone) { // Stage 1: // Drain the groupings fully from all continuation and all partitions QueryResponseCore sourceResponse = await base.DrainAsync(int.MaxValue, cancellationToken); if (!sourceResponse.IsSuccess) { return(sourceResponse); } foreach (CosmosElement result in sourceResponse.CosmosElements) { // Aggregate the values for all groupings across all continuations. RewrittenGroupByProjection groupByItem = new RewrittenGroupByProjection(result); UInt192 groupByKeysHash = DistinctHash.GetHash(groupByItem.GroupByItems); if (!this.groupingTable.TryGetValue(groupByKeysHash, out SingleGroupAggregator singleGroupAggregator)) { singleGroupAggregator = SingleGroupAggregator.Create( this.cosmosQueryClient, EmptyAggregateOperators, this.groupByAliasToAggregateType, this.orderedAliases, this.hasSelectValue, continuationToken: null); this.groupingTable[groupByKeysHash] = singleGroupAggregator; } CosmosElement payload = groupByItem.Payload; singleGroupAggregator.AddValues(payload); } // We need to give empty pages until the results are fully drained. response = QueryResponseCore.CreateSuccess( result: EmptyResults, continuationToken: null, disallowContinuationTokenMessage: GroupByDocumentQueryExecutionComponent.ContinuationTokenNotSupportedWithGroupBy, activityId: sourceResponse.ActivityId, requestCharge: sourceResponse.RequestCharge, diagnostics: sourceResponse.Diagnostics, responseLengthBytes: sourceResponse.ResponseLengthBytes); this.isDone = false; } else { // Stage 2: // Emit the results from the grouping table page by page IEnumerable <SingleGroupAggregator> groupByValuesList = this.groupingTable .OrderBy(kvp => kvp.Key) .Skip(this.numPagesDrainedFromGroupingTable * maxElements) .Take(maxElements) .Select(kvp => kvp.Value); List <CosmosElement> results = new List <CosmosElement>(); foreach (SingleGroupAggregator groupByValues in groupByValuesList) { results.Add(groupByValues.GetResult()); } response = QueryResponseCore.CreateSuccess( result: results, continuationToken: null, disallowContinuationTokenMessage: GroupByDocumentQueryExecutionComponent.ContinuationTokenNotSupportedWithGroupBy, activityId: null, requestCharge: 0, diagnostics: QueryResponseCore.EmptyDiagnostics, responseLengthBytes: 0); this.numPagesDrainedFromGroupingTable++; if (this.numPagesDrainedFromGroupingTable * maxElements >= this.groupingTable.Count) { this.isDone = true; } } return(response); }
/// <summary> /// Gets the hash given a value and a seed. /// </summary> /// <param name="value">The value to hash.</param> /// <param name="seed">The seed.</param> /// <returns>The hash.</returns> public static UInt192 GetHash(UInt192 value, UInt192 seed) { return(DistinctHash.GetHash(UInt192.ToByteArray(value), seed)); }
/// <summary> /// Gets the hash given a value and a seed. /// </summary> /// <param name="value">The value to hash.</param> /// <param name="seed">The seed.</param> /// <returns>The hash.</returns> public UInt192 GetHash(UInt192 value, UInt192 seed) { return(this.GetHash(UInt192.ToByteArray(value), seed)); }
/// <summary> /// Gets the hash of a null JSON value. /// </summary> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a null JSON value given a seed.</returns> private UInt192 GetNullHash(UInt192 seed) { return(this.GetHash(this.HashSeedValues.Null, seed)); }
/// <summary> /// Gets the hash of a boolean JSON value. /// </summary> /// <param name="boolean">The boolean to hash.</param> /// <param name="seed">The seed.</param> /// <returns>The hash of a boolean JSON value.</returns> private UInt192 GetBooleanHash(bool boolean, UInt192 seed) { return(this.GetHash(boolean ? this.HashSeedValues.True : this.HashSeedValues.False, seed)); }
/// <summary> /// Gets the hash of a boolean JSON value. /// </summary> /// <param name="boolean">The boolean to hash.</param> /// <param name="seed">The seed.</param> /// <returns>The hash of a boolean JSON value.</returns> private static UInt192 GetBooleanHash(bool boolean, UInt192 seed) { return(DistinctHash.GetHash(boolean ? DistinctHash.TrueHashSeed : DistinctHash.FalseHashSeed, seed)); }
/// <summary> /// Gets the hash of a null JSON value. /// </summary> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a null JSON value given a seed.</returns> private static UInt192 GetNullHash(UInt192 seed) { return(DistinctHash.GetHash(DistinctHash.NullHashSeed, seed)); }
/// <summary> /// Compares to objects and returns their partial sort relationship. /// </summary> /// <param name="element1">The first element to compare.</param> /// <param name="element2">The second element to compare.</param> /// <returns> /// Less than zero if obj1 comes before obj2 in the sort order. /// Zero if obj1 and obj2 are interchangeable in the sort order. /// Greater than zero if obj2 comes before obj1 in the sort order. /// </returns> public int Compare(CosmosElement element1, CosmosElement element2) { if (object.ReferenceEquals(element1, element2)) { return(0); } if (object.ReferenceEquals(element1, MinValueItem.Singleton)) { return(-1); } if (object.ReferenceEquals(element2, MinValueItem.Singleton)) { return(1); } if (object.ReferenceEquals(element1, MaxValueItem.Singleton)) { return(1); } if (object.ReferenceEquals(element2, MaxValueItem.Singleton)) { return(-1); } if (element1 == Undefined) { return(-1); } if (element2 == Undefined) { return(1); } CosmosElementType type1 = element1.Type; int cmp = CompareTypes(element1, element2); if (cmp == 0) { // If they are the same type then you need to break the tie. switch (type1) { case CosmosElementType.Boolean: cmp = Comparer <bool> .Default.Compare( (element1 as CosmosBoolean).Value, (element2 as CosmosBoolean).Value); break; case CosmosElementType.Null: // All nulls are the same. cmp = 0; break; case CosmosElementType.Number: CosmosNumber number1 = element1 as CosmosNumber; CosmosNumber number2 = element2 as CosmosNumber; if (number1.NumberType == CosmosNumberType.Number64) { double double1; if (number1.IsFloatingPoint) { double1 = number1.AsFloatingPoint().Value; } else { double1 = number1.AsInteger().Value; } double double2; if (number2.IsFloatingPoint) { double2 = number2.AsFloatingPoint().Value; } else { double2 = number2.AsInteger().Value; } cmp = Comparer <double> .Default.Compare( double1, double2); } else if (number1.IsFloatingPoint) { double double1 = number1.AsFloatingPoint().Value; double double2 = number2.AsFloatingPoint().Value; cmp = Comparer <double> .Default.Compare(double1, double2); } else { long integer1 = number1.AsInteger().Value; long integer2 = number2.AsInteger().Value; cmp = Comparer <long> .Default.Compare(integer1, integer2); } break; case CosmosElementType.String: CosmosString string1 = element1 as CosmosString; CosmosString string2 = element2 as CosmosString; cmp = string.CompareOrdinal( string1.Value, string2.Value); break; case CosmosElementType.Guid: CosmosGuid guid1 = element1 as CosmosGuid; CosmosGuid guid2 = element2 as CosmosGuid; cmp = guid1.Value.CompareTo(guid2.Value); break; case CosmosElementType.Binary: CosmosBinary binary1 = element1 as CosmosBinary; CosmosBinary binary2 = element2 as CosmosBinary; cmp = ItemComparer.CompareTo(binary1, binary2); break; case CosmosElementType.Array: case CosmosElementType.Object: { UInt192 hash1 = DistinctHash.GetHash(element1); UInt192 hash2 = DistinctHash.GetHash(element2); return(hash1.CompareTo(hash2)); } default: throw new ArgumentException($"Unknown: {nameof(CosmosElementType)}: {type1}"); } } return(cmp); }
/// <summary> /// Adds an array value to the distinct map. /// </summary> /// <param name="array">The array to add.</param> /// <returns>Whether or not the value was successfully added.</returns> private bool AddArrayValue(CosmosArray array) { UInt192 hash = DistinctMap.GetHash(array); return(this.arrays.Add(hash)); }
/// <summary> /// Adds an object value to the distinct map. /// </summary> /// <param name="cosmosObject">The object to add.</param> /// <returns>Whether or not the value was successfully added.</returns> private bool AddObjectValue(CosmosObject cosmosObject) { UInt192 hash = DistinctMap.GetHash(cosmosObject); return(this.objects.Add(hash)); }
/// <summary> /// Gets the hash of a undefined JSON value. /// </summary> /// <param name="seed">The seed to use.</param> /// <returns>The hash of a undefined JSON value.</returns> private static UInt192 GetUndefinedHash(UInt192 seed) { return(seed); }