public AddressStateMap GetStates( IEnumerable <Address> addresses, HashDigest <SHA256>?offset = null) { if (offset == null) { offset = Store.IndexBlockHash(-1); } var states = new AddressStateMap(); while (offset != null) { states = (AddressStateMap)states.SetItems( Store.GetBlockStates(offset.Value) .Where( kv => addresses.Contains(kv.Key) && !states.ContainsKey(kv.Key)) ); if (states.Keys.SequenceEqual(addresses)) { break; } offset = Blocks[offset.Value].PreviousHash; } return(states); }
public void GetStatesReturnsEarlyForNonexistentAccount() { var tracker = new StoreTracker(_fx.Store); var chain = new BlockChain <DumbAction>( new NullPolicy <DumbAction>(), tracker ); Block <DumbAction> b = TestUtils.MineGenesis <DumbAction>(); chain.Append(b); for (int i = 0; i < 20; ++i) { b = TestUtils.MineNext(b); chain.Append(b); } tracker.ClearLogs(); Address nonexistent = new PrivateKey().PublicKey.ToAddress(); AddressStateMap result = chain.GetStates(new[] { nonexistent }); Assert.False(result.ContainsKey(nonexistent)); var callCount = tracker.Logs.Where( triple => triple.Item1.Equals("GetBlockStates") ).Select(triple => triple.Item2).Count(); Assert.True( callCount <= 1, $"GetBlocksStates() was called {callCount} times" ); }
public void GetStatesOnlyDrillsDownUntilRequestedAddressesAreFound() { var tracker = new StoreTracker(_fx.Store); var chain = new BlockChain <DumbAction>( new NullPolicy <DumbAction>(), tracker ); Block <DumbAction> b = TestUtils.MineGenesis <DumbAction>(); chain.Append(b); Address[] addresses = new Address[30]; for (int i = 0; i < addresses.Length; ++i) { var privateKey = new PrivateKey(); Address address = privateKey.PublicKey.ToAddress(); addresses[i] = address; DumbAction[] actions = { new DumbAction(address, "foo"), new DumbAction(i < 1 ? address : addresses[i - 1], "bar"), }; Transaction <DumbAction>[] txs = { Transaction <DumbAction> .Create(privateKey, actions), }; b = TestUtils.MineNext(b, txs); chain.Append(b); } tracker.ClearLogs(); int testingDepth = addresses.Length / 2; Address[] targetAddresses = Enumerable.Range( testingDepth, Math.Min(10, addresses.Length - testingDepth - 1) ).Select(i => addresses[i]).ToArray(); AddressStateMap result = chain.GetStates(targetAddresses); string resultString = string.Join(", ", result.Keys); string targetString = string.Join(", ", targetAddresses); string message = $"The result dictionary ({resultString}) does not " + $"cover all requested addresses ({targetString})."; foreach (Address targetAddress in targetAddresses) { Assert.True(result.ContainsKey(targetAddress), message); } var callCount = tracker.Logs.Where( triple => triple.Item1.Equals("GetBlockStates") ).Select(triple => triple.Item2).Count(); Assert.True(testingDepth >= callCount); }
/// <summary> /// Gets the state of the given <paramref name="addresses"/> in the /// <see cref="BlockChain{T}"/> from <paramref name="offset"/>. /// </summary> /// <param name="addresses">The list of <see cref="Address"/>es to get /// their states.</param> /// <param name="offset">The <see cref="HashDigest{T}"/> of the block to /// start finding the state. It will be The tip of the /// <see cref="BlockChain{T}"/> if it is <c>null</c>.</param> /// <returns>The <see cref="AddressStateMap"/> of given /// <paramref name="addresses"/>.</returns> public AddressStateMap GetStates( IEnumerable <Address> addresses, HashDigest <SHA256>?offset = null) { _rwlock.EnterReadLock(); try { if (offset == null) { offset = Store.IndexBlockHash(Id.ToString(), -1); } } finally { _rwlock.ExitReadLock(); } var states = new AddressStateMap(); if (offset == null) { return(states); } Block <T> block = Blocks[offset.Value]; ImmutableHashSet <Address> requestedAddresses = addresses.ToImmutableHashSet(); var hashValues = new HashSet <HashDigest <SHA256> >(); foreach (var address in requestedAddresses) { var hashDigest = Store.LookupStateReference( Id.ToString(), address, block); if (!(hashDigest is null)) { hashValues.Add(hashDigest.Value); } } foreach (var hashValue in hashValues) { states = (AddressStateMap)states.SetItems( Store.GetBlockStates(hashValue) .Where( kv => requestedAddresses.Contains(kv.Key) && !states.ContainsKey(kv.Key))); } return(states); }
/// <summary> /// Gets the state of the given <paramref name="addresses"/> in the /// <see cref="BlockChain{T}"/> from <paramref name="offset"/>. /// </summary> /// <param name="addresses">The list of <see cref="Address"/>es to get /// their states.</param> /// <param name="offset">The <see cref="HashDigest{T}"/> of the block to /// start finding the state. It will be The tip of the /// <see cref="BlockChain{T}"/> if it is <c>null</c>.</param> /// <param name="completeStates">When the <see cref="BlockChain{T}"/> /// instance does not contain states dirty of the block which lastly /// updated states of a requested address, this option makes /// the incomplete states calculated and filled on the fly. /// If this option is turned off (which is default) this method throws /// <see cref="IncompleteBlockStatesException"/> instead /// for the same situation. /// Just-in-time calculation of states could take a long time so that /// the overall latency of an application may rise.</param> /// <returns>The <see cref="AddressStateMap"/> of given /// <paramref name="addresses"/>.</returns> /// <exception cref="IncompleteBlockStatesException">Thrown when /// the <see cref="BlockChain{T}"/> instance does not contain /// states dirty of the block which lastly updated states of a requested /// address, because actions in the block have never been executed. /// If <paramref name="completeStates"/> option is turned on /// this exception is not thrown and incomplete states are calculated /// and filled on the fly instead. /// </exception> public AddressStateMap GetStates( IEnumerable <Address> addresses, HashDigest <SHA256>?offset = null, bool completeStates = false ) { _rwlock.EnterReadLock(); try { if (offset == null) { offset = Store.IndexBlockHash(Id, -1); } } finally { _rwlock.ExitReadLock(); } var states = new AddressStateMap(); if (offset == null) { return(states); } Block <T> block = Blocks[offset.Value]; ImmutableHashSet <Address> requestedAddresses = addresses.ToImmutableHashSet(); var stateReferences = new HashSet <Tuple <HashDigest <SHA256>, long> >(); foreach (var address in requestedAddresses) { Tuple <HashDigest <SHA256>, long> sr; _rwlock.EnterReadLock(); try { sr = Store.LookupStateReference(Id, address, block); } finally { _rwlock.ExitReadLock(); } if (!(sr is null)) { stateReferences.Add(sr); } } IEnumerable <HashDigest <SHA256> > hashValues = stateReferences .OrderByDescending(sr => sr.Item2) .Select(sr => sr.Item1); foreach (var hashValue in hashValues) { AddressStateMap blockStates = Store.GetBlockStates(hashValue); if (blockStates is null) { if (completeStates) { // Calculates and fills the incomplete states // on the fly. foreach (Block <T> b in this) { if (!(Store.GetBlockStates(b.Hash) is null)) { continue; } List <ActionEvaluation> evaluations = b.Evaluate( DateTimeOffset.UtcNow, a => GetStates( new[] { a }, b.PreviousHash ).GetValueOrDefault(a) ).ToList(); if (Policy.BlockAction is IAction) { evaluations.Add(EvaluateBlockAction(b, evaluations)); } _rwlock.EnterWriteLock(); try { SetStates(b, evaluations, buildStateReferences: false); } finally { _rwlock.ExitWriteLock(); } } blockStates = Store.GetBlockStates(hashValue); if (blockStates is null) { throw new NullReferenceException(); } } else { throw new IncompleteBlockStatesException(hashValue); } } states = (AddressStateMap)states.SetItems( blockStates.Where(kv => requestedAddresses.Contains(kv.Key) && !states.ContainsKey(kv.Key) ) ); } return(states); }