async Task <T> GetStoredOperation <T>(long id, TransactionDownloadHandler <T> handler) where T : Operation
        {
            T operation = null;

            var discStorage = _transactionStorage.Value;
            var contains    = discStorage.ContainsIndex(id);

            if (contains)
            {
                try
                {
                    operation = handler.RestoreTransaction(new Unpacker(discStorage.GetBlockData(id)));
                    var transaction = operation as Transaction;
                    if (transaction != null)
                    {
                        {
                            var accountId    = transaction.AccountId;
                            var accountEntry = await _accountEntryStorage.GetEntry(accountId);

                            if (accountEntry != null)
                            {
                                if (accountEntry.RequiresRefresh())
                                {
                                    await _accountEntryStorage.UpdateEntry(accountEntry);
                                }
                            }
                        }

                        {
                            var group = transaction.GetFeature <Group>(Group.FeatureId);
                            if (group != null)
                            {
                                var groupId    = group.GroupId;
                                var groupEntry = await _groupEntryStorage.GetEntry(groupId);

                                if (groupEntry != null)
                                {
                                    if (groupEntry.RequiresRefresh())
                                    {
                                        await _groupEntryStorage.UpdateEntry(groupEntry);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.IgnoreException(ex);
                }
            }

            return(operation);
        }
        protected async Task <TransactionDownloadResult <T> > QueryStoredTransactions <T>(long startTransactionId, int count, TransactionDownloadHandler <T> handler) where T : Operation
        {
            var nextId = startTransactionId;

            if (startTransactionId <= Operation.InvalidTransactionId)
            {
                nextId = await handler.QueryLastStoredTransactionId(this);
            }

            if (nextId != Operation.InvalidTransactionId)
            {
                try
                {
                    var transactions = new List <TransactionDownloadData <T> >();
                    for (var i = 0; i < count; i++)
                    {
                        if (nextId <= Operation.InvalidTransactionId)
                        {
                            return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.Ok, false, transactions, nextId));
                        }

                        T operation = await GetStoredOperation <T>(nextId, handler);

                        if (operation == null)
                        {
                            return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.InternalError, true, transactions, Operation.InvalidTransactionId));
                        }
                        var data = new TransactionDownloadData <T>(operation, this);
                        await DownloadTransactionAttachement(data);

                        RetrieveDecrytpedTransactionStorage(data);
                        transactions.Add(data);

                        nextId = handler.GetPreviousTransactionId(operation);
                    }

                    return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.Ok, nextId != Operation.InvalidTransactionId, transactions, nextId));
                }
                catch (Exception ex)
                {
                    Log.IgnoreException(ex);
                }
            }

            return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.Ok, false, new List <TransactionDownloadData <T> >(), Operation.InvalidTransactionId));
        }
        protected async Task <TransactionDownloadResult <T> > DownloadTransactions <T>(long startTransactionId, long endTransactionId, int count, bool downloadAttachements, bool cache, TransactionDownloadHandler <T> handler) where T : Operation
        {
            var nextId = Operation.InvalidTransactionId;

            if (startTransactionId <= Operation.InvalidTransactionId)
            {
                var info = await handler.GetLastTransactionId();

                var error = TransactionDownloadResultCode.Ok;
                if (info == null)
                {
                    error = TransactionDownloadResultCode.NetworkError;
                }
                else
                {
                    if (info.ResultType != ResultTypes.Ok)
                    {
                        if (info.ResultType == ResultTypes.ChainNotFound)
                        {
                            error = TransactionDownloadResultCode.ChainNotFound;
                        }
                        else if (info.ResultType == ResultTypes.DataNotFound)
                        {
                            error = TransactionDownloadResultCode.DataNotFound;
                        }
                        else if (info.ResultType == ResultTypes.AccountNotFound)
                        {
                            error = TransactionDownloadResultCode.AccountNotFound;
                        }
                        else if (info.ResultType == ResultTypes.FeatureNotFound)
                        {
                            error = TransactionDownloadResultCode.FeatureNotFound;
                        }
                        else
                        {
                            throw new Exception("Unknown result type");
                        }
                    }
                }

                if (error != TransactionDownloadResultCode.Ok)
                {
                    return(new TransactionDownloadResult <T>(error, false, new List <TransactionDownloadData <T> >(), Operation.InvalidTransactionId));
                }

                if (info is Result <LastTransactionInfo> lastTransactionInfo)
                {
                    nextId = lastTransactionInfo.Item.TransactionId;
                }
                else if (info is Result <LastTransactionCountInfo> lastDataTransactionInfo)
                {
                    nextId = lastDataTransactionInfo.Item.TransactionId;
                }
                else
                {
                    throw new Exception("Unknown last transactin info");
                }
            }
            else
            {
                nextId = startTransactionId;
            }

            var transactions = new List <TransactionDownloadData <T> >();

            if (nextId != Operation.InvalidTransactionId)
            {
                var discStorage = _transactionStorage.Value;

                try
                {
                    // useful?
                    var end = endTransactionId != Operation.InvalidTransactionId || count <= 0;
                    if (end)
                    {
                        count = int.MaxValue;
                    }

                    for (var i = 0; i < count; i++)
                    {
                        if (nextId <= Operation.InvalidTransactionId)
                        {
                            return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.Ok, false, transactions, nextId));
                        }

                        T operation = await GetStoredOperation <T>(nextId, handler);

                        if (operation == null)
                        {
                            operation = await handler.DownloadTransaction(nextId);

                            if (operation != null)
                            {
                                var transaction = operation as Transaction;
                                if (transaction != null)
                                {
                                    var accountId = transaction.AccountId;

                                    var transactionid = transaction.TransactionId;
                                    var timestamp     = transaction.Timestamp;

                                    {
                                        var accountIndex = transaction.GetFeature <AccountIndex>(AccountIndex.FeatureId);
                                        if (accountIndex != null)
                                        {
                                            var index    = accountIndex?.Index;
                                            var hasIndex = index != null;

                                            var accountEntry = await _accountEntryStorage.GetEntry(accountId);

                                            if (accountEntry == null)
                                            {
                                                accountEntry = new DataAccountStorageEntry(accountId);
                                                accountEntry.Update(transactionid, timestamp, 0);
                                                if (hasIndex)
                                                {
                                                    accountEntry.UpdateIndex(index, transactionid, timestamp, accountIndex.TransactionCount);
                                                }

                                                await _accountEntryStorage.UpdateEntry(accountEntry);
                                            }
                                            else
                                            {
                                                var update = accountEntry.Update(transactionid, timestamp, 0);
                                                if (hasIndex)
                                                {
                                                    update |= accountEntry.UpdateIndex(index, transactionid, timestamp, accountIndex.TransactionCount);
                                                }

                                                if (update)
                                                {
                                                    await _accountEntryStorage.UpdateEntry(accountEntry);
                                                }
                                            }
                                        }
                                    }

                                    {
                                        var sharedIndex = transaction.GetFeature <SharedAccountIndex>(SharedAccountIndex.FeatureId);
                                        if (sharedIndex != null)
                                        {
                                            var accountEntry = await _accountEntryStorage.GetEntry(0);

                                            if (accountEntry == null)
                                            {
                                                accountEntry = new DataAccountStorageEntry(0);
                                                accountEntry.Update(transactionid, timestamp, 0);
                                                accountEntry.UpdateIndex(sharedIndex.Index, transactionid, timestamp, sharedIndex.TransactionCount);

                                                await _accountEntryStorage.UpdateEntry(accountEntry);
                                            }
                                            else
                                            {
                                                var update = accountEntry.Update(transactionid, timestamp, 0);
                                                update |= accountEntry.UpdateIndex(sharedIndex.Index, transactionid, timestamp, sharedIndex.TransactionCount);

                                                if (update)
                                                {
                                                    await _accountEntryStorage.UpdateEntry(accountEntry);
                                                }
                                            }
                                        }
                                    }

                                    {
                                        var receiver = transaction.GetFeature <Receiver>(Receiver.FeatureId);
                                        if (receiver != null)
                                        {
                                            for (var a = 0; a < receiver.Receivers.Count; a++)
                                            {
                                                var receiverId = receiver.Receivers[a];

                                                var accountEntry = await _accountEntryStorage.GetEntry(receiverId);

                                                if (accountEntry == null)
                                                {
                                                    accountEntry = new DataAccountStorageEntry(receiverId);
                                                    accountEntry.UpdateTargeted(transactionid, timestamp);
                                                    await _accountEntryStorage.UpdateEntry(accountEntry);
                                                }
                                                else
                                                {
                                                    if (accountEntry.UpdateTargeted(transactionid, timestamp))
                                                    {
                                                        await _accountEntryStorage.UpdateEntry(accountEntry);
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    {
                                        var transactionTarget = transaction.GetFeature <TransactionTarget>(TransactionTarget.FeatureId);
                                        if (transactionTarget != null)
                                        {
                                            for (var t = 0; t < transactionTarget.Targets.Count; t++)
                                            {
                                                var id = transactionTarget.Targets[t];

                                                var targetedEntry = await _targetedTransactionStorage.GetEntry(id);

                                                if (targetedEntry == null)
                                                {
                                                    targetedEntry = new TargetedChainTransactionStorageEntry(id);
                                                    targetedEntry.Update(transactionid, timestamp, 0);
                                                    await _targetedTransactionStorage.UpdateEntry(targetedEntry);
                                                }
                                                else
                                                {
                                                    if (targetedEntry.Update(transactionid, timestamp, 0))
                                                    {
                                                        await _targetedTransactionStorage.UpdateEntry(targetedEntry);
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    {
                                        var group = transaction.GetFeature <Group>(Group.FeatureId);
                                        if (group != null)
                                        {
                                            var groupId       = group.GroupId;
                                            var groupIndex    = group.GroupIndex;
                                            var hasGroupIndex = groupIndex != null;

                                            var groupEntry = await _groupEntryStorage.GetEntry(groupId);

                                            if (groupEntry == null)
                                            {
                                                groupEntry = new GroupStorageEntry(groupId);
                                                groupEntry.Update(transactionid, timestamp, 0);
                                                if (hasGroupIndex)
                                                {
                                                    groupEntry.UpdateIndex(groupIndex, transactionid, timestamp, group.GroupIndexTransactionCount);
                                                }
                                                await _groupEntryStorage.UpdateEntry(groupEntry);
                                            }
                                            else
                                            {
                                                var update = groupEntry.Update(transactionid, timestamp, 0);
                                                if (hasGroupIndex)
                                                {
                                                    update |= groupEntry.UpdateIndex(groupIndex, transactionid, timestamp, group.GroupIndexTransactionCount);
                                                }

                                                if (update)
                                                {
                                                    await _groupEntryStorage.UpdateEntry(groupEntry);
                                                }
                                            }
                                        }
                                    }
                                }

                                if (cache)
                                {
                                    if (discStorage.ContainsIndex(nextId))
                                    {
                                        discStorage.UpdateEntry(operation.OperationId, operation.ToArray());
                                    }
                                    else
                                    {
                                        discStorage.AddEntry(operation.OperationId, operation.ToArray());
                                    }

                                    discStorage.Commit();
                                }
                            }
                            else
                            {
                                return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.NetworkError, true, transactions, Operation.InvalidTransactionId));
                            }
                        }

                        if (operation == null)
                        {
                            return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.InternalError, true, transactions, Operation.InvalidTransactionId));
                        }

                        var data = new TransactionDownloadData <T>(operation, this);
                        if (downloadAttachements && cache)
                        {
                            await DownloadTransactionAttachement(data);
                        }
                        RetrieveDecrytpedTransactionStorage(data);
                        transactions.Add(data);

                        nextId = handler.GetPreviousTransactionId(operation);

                        if (end && nextId <= endTransactionId)
                        {
                            return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.Ok, nextId != Operation.InvalidTransactionId, transactions, nextId));
                        }
                    }

                    return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.Ok, nextId != Operation.InvalidTransactionId, transactions, nextId));
                }
                catch (Exception ex)
                {
                    Log.IgnoreException(ex);
                    return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.InternalError, true, transactions, Operation.InvalidTransactionId));
                }
                finally
                {
                    if (downloadAttachements)
                    {
                        _attachementsStorage.Value.Commit();
                    }
                }
            }

            return(new TransactionDownloadResult <T>(TransactionDownloadResultCode.Ok, false, transactions, Operation.InvalidTransactionId));
        }