void ScrAddrFilter::setSSHLastScanned(uint32_t height)
{
   //LMDBBlockDatabase::Batch batch(db, BLKDATA);
   LOGWARN << "Updating SSH last scanned";
   LMDBEnv::Transaction tx;
   lmdb_->beginDBTransaction(&tx, HISTORY, LMDB::ReadWrite);
   for (const auto scrAddrPair : scrAddrMap_)
   {
      StoredScriptHistory ssh;
      lmdb_->getStoredScriptHistorySummary(ssh, scrAddrPair.first);
      if (!ssh.isInitialized())
         ssh.uniqueKey_ = scrAddrPair.first;

      ssh.alreadyScannedUpToBlk_ = height;

      lmdb_->putStoredScriptHistory(ssh);
   }
}
void ScrAddrFilter::setSSHLastScanned(uint32_t height)
{
   //LMDBBlockDatabase::Batch batch(db, BLKDATA);
   LOGWARN << "Updating SSH last scanned";
   for (const auto scrAddrPair : scrAddrMap_)
   {
      uint32_t shard = lmdb_->getShard(scrAddrPair.first);
      LMDBEnv::Transaction tx;
      lmdb_->beginShardTransaction(tx, shard, LMDB::ReadWrite);

      StoredScriptHistory ssh;
      lmdb_->getStoredScriptHistorySummary(ssh, scrAddrPair.first);
      if (!ssh.isInitialized())
         throw runtime_error("uninitialized SSH header");

      ssh.alreadyScannedUpToBlk_ = height;

      lmdb_->putStoredScriptHistorySummary(ssh);
   }
}
void BlockchainScanner::updateSSH()
{
   //loop over all subssh entiers in SUBSSH db, 
   //compile balance, txio count and summary map for each address

   {
      StoredDBInfo sdbi;
      db_->getStoredDBInfo(SSH, sdbi);

      BlockHeader* sdbiblock = nullptr;

      try
      {
         sdbiblock = &blockchain_->getHeaderByHash(sdbi.topScannedBlkHash_);
      }
      catch (...)
      {
         sdbiblock = &blockchain_->getHeaderByHeight(0);
      }

      if (sdbiblock->isMainBranch())
      {
         if (sdbi.topBlkHgt_ != 0 && 
             sdbi.topBlkHgt_ >= blockchain_->top().getBlockHeight())
         {
            LOGINFO << "no SSH to scan";
            return;
         }
      }
   }

   map<BinaryData, StoredScriptHistory> sshMap_;
   
   {
      StoredScriptHistory* sshPtr = nullptr;

      LMDBEnv::Transaction historyTx, sshTx;
      db_->beginDBTransaction(&historyTx, SSH, LMDB::ReadOnly);
      db_->beginDBTransaction(&sshTx, SUBSSH, LMDB::ReadOnly);

      auto sshIter = db_->getIterator(SUBSSH);
      sshIter.seekToFirst();

      while (sshIter.advanceAndRead())
      {
         while (sshIter.isValid())
         {
            if (sshPtr != nullptr &&
               sshIter.getKeyRef().contains(sshPtr->uniqueKey_))
               break;

            //new address
            auto&& subsshkey = sshIter.getKey();
            auto sshKey = subsshkey.getSliceRef(1, subsshkey.getSize() - 5);
            sshPtr = &sshMap_[sshKey];

            if (!scrAddrFilter_->hasScrAddress(sshKey))
            {
               LOGWARN << "invalid scrAddr in SUBSSH db";
               continue;
            }

            //get what's already in the db
            db_->getStoredScriptHistorySummary(*sshPtr, sshKey);
            if (sshPtr->isInitialized())
            {
               //set iterator at unscanned height
               auto hgtx = sshIter.getKeyRef().getSliceRef(-4, 4);
               auto height = DBUtils::hgtxToHeight(hgtx);
               if (sshPtr->alreadyScannedUpToBlk_ >= height)
               {
                  //this ssh has already been scanned beyond the height sshIter is at,
                  //let's set the iterator to the correct height (or the next key)
                  auto&& newKey = sshIter.getKey().getSliceCopy(0, subsshkey.getSize() - 4);
                  auto&& newHgtx = DBUtils::heightAndDupToHgtx(
                     sshPtr->alreadyScannedUpToBlk_ + 1, 0);

                  newKey.append(newHgtx);
                  sshIter.seekTo(newKey);
                  continue;
               }
            }
            else
            {
               sshPtr->uniqueKey_ = sshKey;
               break;
            }
         }

         //sanity check
         if (!sshIter.isValid())
            break;

         //deser subssh
         StoredSubHistory subssh;
         subssh.unserializeDBKey(sshIter.getKeyRef());
         
         //check dupID
         if (db_->getValidDupIDForHeight(subssh.height_) != subssh.dupID_)
            continue;

         subssh.unserializeDBValue(sshIter.getValueRef());

         for (auto& txioPair : subssh.txioMap_)
         {
            if (!txioPair.second.isMultisig())
            {
               //add up balance
               if (txioPair.second.hasTxIn())
               {
                  //check for same block fund&spend
                  auto&& keyOfOutput = txioPair.second.getDBKeyOfOutput();
                  auto&& keyOfInput = txioPair.second.getDBKeyOfInput();

                  if (keyOfOutput.startsWith(keyOfInput.getSliceRef(0, 4)))
                  {
                     //both output and input are part of the same block, skip
                     continue;
                  }

                  sshPtr->totalUnspent_ -= txioPair.second.getValue();
               }
               else
               {
                  sshPtr->totalUnspent_ += txioPair.second.getValue();
               }
            }
         }

         //txio count
         sshPtr->totalTxioCount_ += subssh.txioCount_;

         //build subssh summary
         sshPtr->subsshSummary_[subssh.height_] = subssh.txioCount_;
      }
   }
   
   //write it
   auto& topheader = blockchain_->getHeaderByHash(topScannedBlockHash_);
   auto topheight = topheader.getBlockHeight();

   LMDBEnv::Transaction putsshtx;
   db_->beginDBTransaction(&putsshtx, SSH, LMDB::ReadWrite);

   auto& scrAddrMap = scrAddrFilter_->getScrAddrMap();
   for (auto& scrAddr : scrAddrMap)
   {
      auto& ssh = sshMap_[scrAddr.first];

      if (!ssh.isInitialized())
      {
         ssh.uniqueKey_ = scrAddr.first;
      }

      BinaryData&& sshKey = ssh.getDBKey();
      ssh.alreadyScannedUpToBlk_ = topheight;

      BinaryWriter bw;
      ssh.serializeDBValue(bw, ARMORY_DB_BARE, DB_PRUNE_NONE);
      
      db_->putValue(SSH, sshKey.getRef(), bw.getDataRef());
   }

   //update sdbi
   StoredDBInfo sdbi;
   db_->getStoredDBInfo(SSH, sdbi);

   sdbi.topScannedBlkHash_ = topScannedBlockHash_;
   sdbi.topBlkHgt_ = topheight;

   db_->putStoredDBInfo(SSH, sdbi);
}