Beispiel #1
0
// Look up a transaction by transaction number and see if it is in the ledger.
// If it is, return a pointer to it, otherwise return NULL.
OTCronItem * OTCron::GetCronItem(long lTransactionNum)
{
	// See if there's something there with that transaction number.
	mapOfCronItems::iterator ii = m_mapCronItems.find(lTransactionNum);
	
	if ( ii == m_mapCronItems.end() )
	{
		// nothing found.
		return NULL;
	}
	// Found it!
	else 
	{
		OTCronItem * pItem = (*ii).second;
		
		OT_ASSERT((NULL != pItem));
		
		if (pItem->GetTransactionNum() == lTransactionNum)
			return pItem;
		else 
			OTLog::vError("Expected CronItem with transaction number %ld, but found %ld inside. Bad data?\n",
						  lTransactionNum, pItem->GetTransactionNum());
	}
	
	return NULL;
}
Beispiel #2
0
bool OTCron::RemoveCronItem(long lTransactionNum, OTPseudonym & theRemover) // if returns false, item wasn't found.
{
	// See if there's something there with that transaction number.
	mapOfCronItems::iterator ii = m_mapCronItems.find(lTransactionNum);
	
	// If it's not already on the list, then there's nothing to remove.
	if ( ii == m_mapCronItems.end() )
	{
		OTLog::vError("Attempt to remove non-existent CronItem from OTCron. Transaction #: %ld\n",
					  lTransactionNum);
		return false;
	}
	// Otherwise, if it WAS already there, remove it properly.
	else 
	{
		OTCronItem * pItem = (*ii).second;
		
		OT_ASSERT(NULL != pItem);
		
        // ---------------------------------------
        
        pItem->HookRemovalFromCron(&theRemover); // We give the hook a chance to do its thing.
        
        // ---------------------------------------
        
		m_mapCronItems.erase(ii);
		delete pItem;
		
		// An item has been removed from Cron. SAVE.		
		return SaveCron();		
	}
	
	return false;
}
Beispiel #3
0
// static -- class factory.
//
// I just realized, I don't have to use this only for CronItems.
// If I wanted to, I could put ANY Open-Transactions class in here,
// if there was some need for it, and it would work just fine right here.
// Like if I wanted to have different Token types for different cash
// algorithms. All I have to do is change the return type.
OTCronItem * OTCronItem::NewCronItem(const OTString & strCronItem)
{
	static char		buf[45] = "";
	OTCronItem *	pItem = NULL;
	
	if (!strCronItem.Exists())
		return NULL;
	
	OTString strContract(strCronItem);
	
	strContract.reset(); // for sgets
	buf[0] = 0; // probably unnecessary.
	bool bGotLine = strContract.sgets(buf, 40);

	if (!bGotLine)
		return NULL;
	
	OTString strFirstLine(buf);
	strContract.reset(); // set the "file" pointer within this string back to index 0.
	
	// Now I feel pretty safe -- the string I'm examining is within
	// the first 45 characters of the beginning of the contract, and
	// it will NOT contain the escape "- " sequence. From there, if
	// it contains the proper sequence, I will instantiate that type.
	if (!strFirstLine.Exists() || strFirstLine.Contains("- -"))
		return NULL;
		
	if (strFirstLine.Contains("-----BEGIN SIGNED AGREEMENT-----"))  // this string is 32 chars long.
	{	pItem = new OTAgreement();		OT_ASSERT(NULL != pItem); }
	
	else if (strFirstLine.Contains("-----BEGIN SIGNED PAYMENT PLAN-----"))  // this string is 35 chars long.
	{	pItem = new OTPaymentPlan();	OT_ASSERT(NULL != pItem); }
	
	else if (strFirstLine.Contains("-----BEGIN SIGNED TRADE-----"))  // this string is 28 chars long.
	{	pItem = new OTTrade();			OT_ASSERT(NULL != pItem); }
	
	
	// The string didn't match any of the options in the factory.
	if (NULL == pItem)
		return NULL;
	
	// Does the contract successfully load from the string passed in?
	if (pItem->LoadContractFromString(strContract))
		return pItem;
	else
		delete pItem;
	
	
	return NULL;
}
Beispiel #4
0
// Make sure to call this regularly so the CronItems get a chance to process and expire.
void OTCron::ProcessCronItems()
{
	if (!m_bIsActivated)
		return;	// No Cron processing until Cron is activated.
	
	if (GetTransactionCount() < 10) // todo stop hardcoding.
	{
		OTLog::Error("Cron is out of transaction numbers!");
		return;
	}
	
	bool bNeedToSave = false;
	
	// loop through the cron items and tell each one to ProcessCron().
	// If the item returns true, that means leave it on the list. Otherwise,
	// if it returns false, that means "it's done: remove it."
	OTCronItem * pItem = NULL;
	
	for (mapOfCronItems::iterator ii = m_mapCronItems.begin(); ii != m_mapCronItems.end(); )
	{
		pItem = (*ii).second;
		
		OT_ASSERT(NULL != pItem);
		
		// returns true if should stay on the list.
		// returns false if should be removed from the list.
		if (!pItem->VerifySignature(*m_pServerNym) || (false == pItem->ProcessCron())) 
		{
			m_mapCronItems.erase(ii++);
			delete pItem;
			pItem = NULL;
			
			bNeedToSave = true; // We'll save to file at the bottom if anything was removed.
		} 
		else	// the special i++ and ++i arrangement here allows me to erase an item
		{		// from the list WHILE iterating through it  :-)   (Supposedly.)
			++ii;
		}
	}
	
	// Items were removed from Cron -- Save to storage!
	if (bNeedToSave)
		SaveCron();
}
Beispiel #5
0
// OTCron IS responsible for cleaning up theItem, and takes ownership.
// So make SURE it is allocated on the HEAP before you pass it in here, and
// also make sure to delete it again if this call fails!
bool OTCron::AddCronItem(OTCronItem & theItem, bool bSaveReceipt/*=true*/)
{	
	OT_ASSERT(NULL != GetServerNym());
	
	// See if there's something else already there with the same transaction number.
	mapOfCronItems::iterator ii = m_mapCronItems.find(theItem.GetTransactionNum());
	
	// If it's not already on the list, then add it...
	if ( ii == m_mapCronItems.end() )
	{
		// If I've been instructed to save the receipt, and theItem did NOT successfully save the receipt,
		// then return false.
		// This will happen if filesystem problems, but it will also happen if the cron item WAS ALREADY THERE.
		// I don't want to save over it. If I'm trying to save over one that is already there, then THAT is the
		// real problem.
		if (bSaveReceipt && 
            // ---------------------------
            (!theItem.SignContract(*GetServerNym()) ||  // Notice the server adds its signature before saving the cron receipt to local storage. This way, the server can verify its own signature later, as evidence the file hasn't been tampered with. (BOTH signatures are there now--user's and server's.)
             !theItem.SaveContract() || 
             !theItem.SaveCronReceipt()))
		{
			OTLog::Error("OTCron::AddCronItem: Error saving receipt while adding new Cronitem to Cron.\n");
			return false;
		}
		
		m_mapCronItems[theItem.GetTransactionNum()] = &theItem;
		theItem.SetCronPointer(*this); // This way every CronItem has a pointer to momma.

		bool bSuccess = true;
		
		// When an item is added to Cron for the first time, a copy of it is saved to the
		// cron folder, and it has the user's original signature on it. (If it's a Trade, 
		// it also contains an Offer with the user's original signature.) This occurs
		// wherever this function is called with bSaveReceipt=true.
		//
		if (bSaveReceipt)	// This executes only the first time that an item is added to Cron. 
							// (versus when it's just being reloaded from file and added back to the internal list.)
		{
			// Now that a copy of the cronitem is safely stored, I can release the signature on it
			// and sign it with the Server's Nym instead. That way I can use the server to verify
			// all cron and market-related activity from here on out.
//			theItem.ReleaseSignatures();
//			theItem.SignContract(*GetServerNym()); // THIS IS NOW DONE ABOVE. See if (bSaveReceipt) ...
//			theItem.SaveContract();	
			
			// Since we added an item to the Cron, we SAVE it. 
			bSuccess = SaveCron();
			
			if (bSuccess) 
				OTLog::Output(3, "OTCron::AddCronItem: New Cronitem has been added to Cron.\n");
			else 				
				OTLog::Error("OTCron::AddCronItem: Error saving while adding new Cronitem to Cron.\n");
		}
		
		return bSuccess; 
	}
	// Otherwise, if it was already there, log an error.
	else 
	{
		OTLog::vError("OTCron::AddCronItem: Attempt to add CronItem with pre-existing transaction number: %ld\n",
					  theItem.GetTransactionNum());
	}
	
	return false;
}
Beispiel #6
0
// Make sure to call this regularly so the CronItems get a chance to process and expire.
void OTCron::ProcessCronItems()
{
	if (!m_bIsActivated)
		return;	// No Cron processing until Cron is activated.
	
	if (GetTransactionCount() < 10) // todo stop hardcoding.
	{
		OTLog::Error("Cron is out of transaction numbers!");
		return;
	}
	
	bool bNeedToSave = false;
	
	// loop through the cron items and tell each one to ProcessCron().
	// If the item returns true, that means leave it on the list. Otherwise,
	// if it returns false, that means "it's done: remove it."
	OTCronItem * pItem = NULL;
	
	for (mapOfCronItems::iterator ii = m_mapCronItems.begin(); ii != m_mapCronItems.end(); )
	{
		pItem = (*ii).second;
		
		OT_ASSERT(NULL != pItem);
		
        bool bVerifySig     = pItem->VerifySignature(*m_pServerNym);
        bool bProcessCron   = false;
        
        if (bVerifySig)
        {
            bProcessCron = pItem->ProcessCron();
            
            // false means "remove it".
            // ProcessCron returns true if should stay on the list.
            //
            if (false == bProcessCron)
                pItem->HookRemovalFromCron(NULL); // We give the hook a chance to do its thing.
        }
        else
            OTLog::Error("OTCron::ProcessCronItems: Signature failed to verify on cron item!\n");
            
        // -----------------------------------------------------

		// Remove it from the list.
        //
		if (false == bProcessCron)
		{
            OTLog::vOutput(0, "OTCron::ProcessCronItems: Removing expired or unverified cron item.\n");
			m_mapCronItems.erase(ii++);
			delete pItem;
			pItem = NULL;
			
			bNeedToSave = true; // We'll save to file at the bottom if anything was removed.
		} 
		else	// the special i++ and ++i arrangement here allows me to erase an item
		{		// from the list WHILE iterating through it  :-)   (Supposedly.)
			++ii;
		}
	} // for
	
	// Items were removed from Cron -- Save to storage!
	if (bNeedToSave)
		SaveCron();
}
Beispiel #7
0
// This is called by OTCronItem::HookRemovalFromCron
// (After calling this method, HookRemovalFromCron then calls
// onRemovalFromCron.)
//
void OTTrade::onFinalReceipt(OTCronItem& origCronItem,
                             const int64_t& newTransactionNumber,
                             Nym& originator, Nym* remover)
{
    const char* szFunc = "OTTrade::onFinalReceipt";

    OTCron* cron = GetCron();
    OT_ASSERT(cron != nullptr);

    Nym* serverNym = cron->GetServerNym();
    OT_ASSERT(serverNym != nullptr);

    // First, we are closing the transaction number ITSELF, of this cron item,
    // as an active issued number on the originating nym. (Changing it to
    // CLOSED.)
    //
    // Second, we're verifying the CLOSING number, and using it as the closing
    // number
    // on the FINAL RECEIPT (with that receipt being "InReferenceTo"
    // GetTransactionNum())
    //
    const int64_t openingNumber = origCronItem.GetTransactionNum();

    const int64_t closingAssetNumber =
        (origCronItem.GetCountClosingNumbers() > 0)
            ? origCronItem.GetClosingTransactionNoAt(0)
            : 0;
    const int64_t closingCurrencyNumber =
        (origCronItem.GetCountClosingNumbers() > 1)
            ? origCronItem.GetClosingTransactionNoAt(1)
            : 0;

    const String notaryID(GetNotaryID());

    // The marketReceipt ITEM's NOTE contains the UPDATED TRADE.
    // And the **UPDATED OFFER** is stored on the ATTACHMENT on the **ITEM.**
    //
    // BUT!!! This is not a marketReceipt Item, is it? ***This is a finalReceipt
    // ITEM!***
    // I'm reversing note and attachment for finalReceipt, with the
    // intention of
    // eventually reversing them for marketReceipt as well. (Making them all in
    // line with paymentReceipt.)
    //
    // WHY?  Because I want a standard convention:
    //          1. ORIGINAL (user-signed) Cron Items are always stored "in
    // reference to" on cron receipts in the Inbox (an OTTransaction).
    //          2. The UPDATED VERSION of that same cron item (a trade or
    // payment plan) is stored in the ATTACHMENT on the OTItem member.
    //          3. ADDITIONAL INFORMATION is stored in the NOTE field of the
    // OTItem member.
    //
    // Unfortunately, marketReceipt doesn't adhere to this convention, as it
    // stores the Updated Cron Item (the trade) in
    // the note instead of the attachment, and it stores the updated Offer (the
    // additional info) in the attachment instead
    // of the note.
    // Perhaps this is for the best -- it will certainly kick out any accidental
    // confusions between marketReceipt and finalReceipt!
    // todo: switch marketReceipt over to be like finalReceipt as described in
    // this paragraph.
    //
    // Once everything is consistent on the above convention -- starting here
    // and now with finalReceipt -- then we will ALWAYS
    // be able to count on a Cron Item being in the Transaction Item's
    // Attachment! We can load it using the existing factory class,
    // without regard to type, KNOWING it's a cron item every time.
    // todo: convert marketReceipt to do the same.

    // The finalReceipt Item's ATTACHMENT contains the UPDATED Cron Item.
    // (With the SERVER's signature on it!)
    //
    String updatedCronItem(*this);
    String* attachment = &updatedCronItem; // the Updated TRADE.
    String updatedOffer;
    String* note = nullptr; // the updated Offer (if available.)

    if (offer_) {
        offer_->SaveContractRaw(updatedOffer);
        note = &updatedOffer;
    }

    const String strOrigCronItem(origCronItem);

    Nym theActualNym; // unused unless it's really not already loaded.
                      // (use actualNym.)

    // The OPENING transaction number must still be signed-out.
    // It is this act of placing the final receipt, which then finally closes
    // the opening number.
    // The closing number, by contrast, is not closed out until the final
    // Receipt is ACCEPTED
    // (which happens in a "process inbox" transaction.)
    //
    if ((openingNumber > 0) &&
        originator.VerifyIssuedNum(notaryID, openingNumber)) {
        // The Nym (server side) stores a list of all opening and closing cron
        // #s.
        // So when the number is released from the Nym, we also take it off that
        // list.
        //
        std::set<int64_t>& idSet = originator.GetSetOpenCronItems();
        idSet.erase(openingNumber);

        originator.RemoveIssuedNum(*serverNym, notaryID, openingNumber,
                                   false);        // bSave=false
        originator.SaveSignedNymfile(*serverNym); // forcing a save here,
                                                  // since multiple things
                                                  // have changed.

        const Identifier& actualNymId = GetSenderNymID();

        Nym* actualNym = nullptr; // use this. DON'T use theActualNym.
        if ((serverNym != nullptr) && serverNym->CompareID(actualNymId))
            actualNym = serverNym;
        else if (originator.CompareID(actualNymId))
            actualNym = &originator;
        else if ((remover != nullptr) && remover->CompareID(actualNymId))
            actualNym = remover;

        else // We couldn't find the Nym among those already loaded--so we have
             // to load
        {    // it ourselves (so we can update its NymboxHash value.)
            theActualNym.SetIdentifier(actualNymId);

            if (!theActualNym.LoadPublicKey()) // Note: this step may be
                                               // unnecessary since we
                                               // are only updating his
                                               // Nymfile, not his key.
            {
                String strNymID(actualNymId);
                otErr << szFunc
                      << ": Failure loading public key for Nym : " << strNymID
                      << ". "
                         "(To update his NymboxHash.) \n";
            }
            else if (theActualNym.VerifyPseudonym() && // this line may be
                                                         // unnecessary.
                       theActualNym.LoadSignedNymfile(
                           *serverNym)) // ServerNym here is not theActualNym's
                                        // identity, but merely the signer on
                                        // this file.
            {
                otLog3
                    << szFunc
                    << ": Loading actual Nym, since he wasn't already loaded. "
                       "(To update his NymboxHash.)\n";
                actualNym = &theActualNym; //  <=====
            }
            else {
                String strNymID(actualNymId);
                otErr
                    << szFunc
                    << ": Failure loading or verifying Actual Nym public key: "
                    << strNymID << ". "
                                   "(To update his NymboxHash.)\n";
            }
        }

        if (!DropFinalReceiptToNymbox(GetSenderNymID(), newTransactionNumber,
                                      strOrigCronItem, note, attachment,
                                      actualNym)) {
            otErr << szFunc << ": Failure dropping receipt into nymbox.\n";
        }
    }
    else {
        otErr << szFunc << ": Problem verifying Opening Number when calling "
                           "VerifyIssuedNum(openingNumber)\n";
    }

    // ASSET ACCT
    //
    if ((closingAssetNumber > 0) &&
        originator.VerifyIssuedNum(notaryID, closingAssetNumber)) {
        DropFinalReceiptToInbox(
            GetSenderNymID(), GetSenderAcctID(), newTransactionNumber,
            closingAssetNumber, // The closing transaction number to put on the
                                // receipt.
            strOrigCronItem, note, attachment);
    }
    else {
        otErr << szFunc
              << ": Failed verifying "
                 "closingAssetNumber=origCronItem."
                 "GetClosingTransactionNoAt(0)>0 &&  "
                 "originator.VerifyTransactionNum(closingAssetNumber)\n";
    }

    // CURRENCY ACCT
    //
    if ((closingCurrencyNumber > 0) &&
        originator.VerifyIssuedNum(notaryID, closingCurrencyNumber)) {
        DropFinalReceiptToInbox(
            GetSenderNymID(), GetCurrencyAcctID(), newTransactionNumber,
            closingCurrencyNumber, // closing transaction number for the
                                   // receipt.
            strOrigCronItem, note, attachment);
    }
    else {
        otErr << szFunc
              << ": Failed verifying "
                 "closingCurrencyNumber=origCronItem."
                 "GetClosingTransactionNoAt(1)>0 "
                 "&&  "
                 "originator.VerifyTransactionNum(closingCurrencyNumber)\n";
    }

    // the RemoveIssued call means the original transaction# (to find this cron
    // item on cron) is now CLOSED.
    // But the Transaction itself is still OPEN. How? Because the CLOSING number
    // is still signed out.
    // The closing number is also USED, since the NotarizePaymentPlan or
    // NotarizeMarketOffer call, but it
    // remains ISSUED, until the final receipt itself is accepted during a
    // process inbox.
    //
    //    if (bDroppedReceiptAssetAcct || bDroppedReceiptCurrencyAcct)  // ASSET
    // ACCOUNT and CURRENCY ACCOUNT
    //    {
    // This part below doesn't happen until you ACCEPT the finalReceipt (when
    // processing your inbox.)
    //
    //      if (bDroppedReceiptAssetAcct)
    //          originator.RemoveIssuedNum(notaryID, closingAssetNumber,
    // true); //bSave=false
    //      else if (bDroppedReceiptCurrencyAcct)
    //          originator.RemoveIssuedNum(notaryID,
    // closingCurrencyNumber, true); //bSave=false
    //    }
    //    else
    //    {
    //        otErr << "OTTrade::onFinalReceipt: Failure dropping receipt into
    // asset or currency inbox.\n";
    //    }

    // QUESTION: Won't there be Cron Items that have no asset account at all?
    // In which case, there'd be no need to drop a final receipt, but I don't
    // think
    // that's the case, since you have to use a transaction number to get onto
    // cron
    // in the first place.
}
// This is called by OTCronItem::HookRemovalFromCron
// (After calling this method, HookRemovalFromCron then calls onRemovalFromCron.)
//
void OTAgreement::onFinalReceipt(OTCronItem & theOrigCronItem,
                                 const long & lNewTransactionNumber,
                                 OTPseudonym & theOriginator,
                                 OTPseudonym * pRemover)
{    
    OTCron * pCron  = GetCron();
    OT_ASSERT(NULL != pCron);
    
    OTPseudonym * pServerNym = pCron->GetServerNym();
    OT_ASSERT(NULL != pServerNym);
    
    // -------------------------------------------------
    
    // The finalReceipt Item's ATTACHMENT contains the UPDATED Cron Item.
    // (With the SERVER's signature on it!)
    //
    OTString strUpdatedCronItem(*this);
    OTString * pstrAttachment=&strUpdatedCronItem;
    
    const OTString strOrigCronItem(theOrigCronItem);
    // -----------------------------------------------------------------

    
    OTPseudonym theRecipientNym; // Don't use this... use the pointer just below.
    
    // The Nym who is actively requesting to remove a cron item will be passed in as pRemover.
    // However, sometimes there is no Nym... perhaps it just expired and pRemover is NULL.
    // The originating Nym (if different than remover) is loaded up. Otherwise the originator
    // pointer just pointers to *pRemover.
    //
    OTPseudonym * pRecipient = NULL;
    
    if (pServerNym->CompareID(this->GetRecipientUserID()))
    {
        pRecipient = pServerNym; // Just in case the recipient Nym is also the server Nym.
    }
    // *******************************************************
    //
    // If pRemover is NOT NULL, and he has the Recipient's ID...
    // then set the pointer accordingly.
    //
    else if ((NULL != pRemover) && (true == pRemover->CompareID(this->GetRecipientUserID())))
    {
        pRecipient = pRemover; // <======== now both pointers are set (to same Nym). DONE!
    }
    // --------------------------------------------------------------------------------------------------

    if (NULL == pRecipient)
    {
        // GetSenderUserID() should be the same on THIS (updated version of the same cron item) 
        // but for whatever reason, I'm checking the userID on the original version. Sue me.
        //
        const OTIdentifier NYM_ID(this->GetRecipientUserID());
        
        theRecipientNym.SetIdentifier(NYM_ID);  
        
        if (false == theRecipientNym.LoadPublicKey())
        {
            OTString strNymID(NYM_ID);
            OTLog::vError("OTAgreement::onFinalReceipt: Failure loading Recipient's public key:\n%s\n", strNymID.Get());
        }		
        else if (theRecipientNym.VerifyPseudonym() && 
                 theRecipientNym.LoadSignedNymfile(*pServerNym)) // ServerNym here is merely the signer on this file.
        {
            pRecipient = &theRecipientNym; //  <=====
        }
        else 
        {
            OTString strNymID(NYM_ID);
            OTLog::vError("OTAgreement::onFinalReceipt: Failure verifying Recipient's public key or loading signed nymfile: %s\n",
                          strNymID.Get());
        }
    }
    
    // -------------------------------

    // First, we are closing the transaction number ITSELF, of this cron item,
    // as an active issued number on the originating nym. (Changing it to CLOSED.)
    //
    // Second, we're verifying the CLOSING number, and using it as the closing number
    // on the FINAL RECEIPT (with that receipt being "InReferenceTo" this->GetTransactionNum())
    //
    const long lRecipientOpeningNumber = this->GetRecipientOpeningNum();
    const long lRecipientClosingNumber = this->GetRecipientClosingNum();
    
    // -----------------------------------------------------------------------------------
    const long lSenderOpeningNumber = theOrigCronItem.GetTransactionNum();

    const long lSenderClosingNumber = (theOrigCronItem.GetCountClosingNumbers() > 0) ? 
        theOrigCronItem.GetClosingTransactionNoAt(0) : 0; // index 0 is closing number for sender, since GetTransactionNum() is his opening #.
    
    // ----------------------------------
        
    const OTString strServerID(GetServerID());
    
    // -----------------------------------------------------------------
    //
    
    if ((lSenderOpeningNumber > 0) &&
        theOriginator.VerifyIssuedNum(strServerID, lSenderOpeningNumber))
    {
        // The Nym (server side) stores a list of all opening and closing cron #s.
        // So when the number is released from the Nym, we also take it off that list.
        //
        std::set<long> & theIDSet = theOriginator.GetSetOpenCronItems();
        theIDSet.erase(lSenderOpeningNumber);
        
        // the RemoveIssued call means the original transaction# (to find this cron item on cron) is now CLOSED.
        // But the Transaction itself is still OPEN. How? Because the CLOSING number is still signed out.
        // The closing number is also USED, since the NotarizePaymentPlan or NotarizeMarketOffer call, but it
        // remains ISSUED, until the final receipt itself is accepted during a process inbox.
        //
        theOriginator.RemoveIssuedNum(*pServerNym, strServerID, lSenderOpeningNumber, false); //bSave=false
        theOriginator.SaveSignedNymfile(*pServerNym);
        // ------------------------------------
        
        OTPseudonym *   pActualNym = NULL;  // use this. DON'T use theActualNym.
        OTPseudonym     theActualNym; // unused unless it's really not already loaded. (use pActualNym.)
        const OTIdentifier ACTUAL_NYM_ID = GetSenderUserID();
        
        if ( (NULL != pServerNym) && pServerNym->CompareID(ACTUAL_NYM_ID) )
            pActualNym = pServerNym;
        else if (theOriginator.CompareID(ACTUAL_NYM_ID))
            pActualNym = &theOriginator;
        else if ( (NULL != pRemover) && pRemover->CompareID(ACTUAL_NYM_ID) )
            pActualNym = pRemover;
        // --------------------------
        else    // We couldn't find the Nym among those already loaded--so we have to load
        {       // it ourselves (so we can update its NymboxHash value.)
            theActualNym.SetIdentifier(ACTUAL_NYM_ID);
            
            if (false == theActualNym.LoadPublicKey()) // Note: this step may be unnecessary since we are only updating his Nymfile, not his key.
            {
                OTString strNymID(ACTUAL_NYM_ID);
                OTLog::vError("OTAgreement::onFinalReceipt: Failure loading public key for Nym: %s. "
                              "(To update his NymboxHash.) \n", strNymID.Get());
            }
            else if (theActualNym.VerifyPseudonym()	&& // this line may be unnecessary.
                     theActualNym.LoadSignedNymfile(*pServerNym)) // ServerNym here is not theActualNym's identity, but merely the signer on this file.
            {
                OTLog::Output(0, "OTAgreement::onFinalReceipt: Loading actual Nym, since he wasn't already loaded. "
                              "(To update his NymboxHash.)\n");
                pActualNym = &theActualNym; //  <=====
            }
            else
            {
                OTString strNymID(ACTUAL_NYM_ID);
                OTLog::vError("OTAgreement::onFinalReceipt: Failure loading or verifying Actual Nym public key: %s. "
                              "(To update his NymboxHash.)\n", strNymID.Get());
            }
        }
        // -------------
        
        if (false == this->DropFinalReceiptToNymbox(GetSenderUserID(),
                                                    lNewTransactionNumber,
                                                    strOrigCronItem,
                                                    NULL,
                                                    pstrAttachment,
                                                    pActualNym))
        {
            OTLog::Error("OTAgreement::onFinalReceipt: Failure dropping sender final receipt into nymbox.\n");
        }        
    }
    else
    {
        OTLog::Error("OTAgreement::onFinalReceipt: Failure verifying sender's opening number.\n");
    }
    
    // -----------------------------------------------------------------
    
    if ((lSenderClosingNumber > 0) &&
        theOriginator.VerifyIssuedNum(strServerID, lSenderClosingNumber)         
        ) // ---------------------------------------------------------------
    {
        // In this case, I'm passing NULL for pstrNote, since there is no note.
        // (Additional information would normally be stored in the note.) 
        
        if (false == this->DropFinalReceiptToInbox(GetSenderUserID(),
                                          GetSenderAcctID(),
                                          lNewTransactionNumber,
                                          lSenderClosingNumber, // The closing transaction number to put on the receipt.
                                          strOrigCronItem,
                                          NULL, 
                                          pstrAttachment))
            OTLog::Error("OTAgreement::onFinalReceipt: Failure dropping receipt into sender's inbox.\n");

        // This part below doesn't happen until theOriginator ACCEPTS the final receipt (when processing his inbox.)
        //
//      theOriginator.RemoveIssuedNum(strServerID, lSenderClosingNumber, true); //bSave=false
    }
    else
    {
        OTLog::Error("OTAgreement::onFinalReceipt: Failed verifying lSenderClosingNumber=theOrigCronItem.GetClosingTransactionNoAt(0)>0 &&  "
                     "theOriginator.VerifyTransactionNum(lSenderClosingNumber)\n");
    }
    // -----------------------------------------------------------------
    //
    if ((NULL != pRecipient) && (lRecipientOpeningNumber > 0) && 
        pRecipient->VerifyIssuedNum(strServerID, lRecipientOpeningNumber)
        )
    {
        // The Nym (server side) stores a list of all opening and closing cron #s.
        // So when the number is released from the Nym, we also take it off that list.
        //
        std::set<long> & theIDSet = pRecipient->GetSetOpenCronItems();
        theIDSet.erase(lRecipientOpeningNumber);
        
        // the RemoveIssued call means the original transaction# (to find this cron item on cron) is now CLOSED.
        // But the Transaction itself is still OPEN. How? Because the CLOSING number is still signed out.
        // The closing number is also USED, since the NotarizePaymentPlan or NotarizeMarketOffer call, but it
        // remains ISSUED, until the final receipt itself is accepted during a process inbox.
        //
        pRecipient->RemoveIssuedNum(*pServerNym, strServerID, lRecipientOpeningNumber, false); //bSave=false       
//      pRecipient->SaveSignedNymfile(*pServerNym); // Moved lower.
        // -----------------------------------------------------
        
        if (false == this->DropFinalReceiptToNymbox(GetRecipientUserID(),
                                                    lNewTransactionNumber,
                                                    strOrigCronItem,
                                                    NULL,
                                                    pstrAttachment,
                                                    pRecipient)) // NymboxHash is updated here in pRecipient.
        {
            OTLog::Error("OTAgreement::onFinalReceipt: Failure dropping recipient final receipt into nymbox.\n");
        }
        // -----------------------------------------------------

        // Saving both the Removed Issued Number, as well as the new NymboxHash.
        // NOTE: Todo: if the NymboxHash WAS updated (as it should have been) then
        // it was probably saved at that time. Below is therefore a redundant save.
        // Need to fix by making certain objects savable and dirty, and then let them
        // autosave before destruction, IF they are dirty.
        //
        pRecipient->SaveSignedNymfile(*pServerNym); 
    }
    else
    {
        OTLog::Error("OTAgreement::onFinalReceipt: Failed verifying "
                     "lRecipientClosingNumber=this->GetRecipientClosingTransactionNoAt(1)>0 &&  "
                     "pRecipient->VerifyTransactionNum(lRecipientClosingNumber) && VerifyIssuedNum(lRecipientOpeningNumber)\n");
    }
    
    // -----------------------------------------------------------------
    
    if ((NULL != pRecipient) && (lRecipientClosingNumber > 0) && 
        pRecipient->VerifyIssuedNum(strServerID, lRecipientClosingNumber)
        )
    {
        if (false == this->DropFinalReceiptToInbox(GetRecipientUserID(),
                                      GetRecipientAcctID(),
                                      lNewTransactionNumber,
                                      lRecipientClosingNumber, // The closing transaction number to put on the receipt.
                                      strOrigCronItem,
                                      NULL,
                                      pstrAttachment))
            OTLog::Error("OTAgreement::onFinalReceipt: Failure dropping receipt into recipient's inbox.\n");

        // This part below doesn't happen until pRecipient ACCEPTs the final receipt (when processing his inbox.)
        //
//      pRecipient->RemoveIssuedNum(strServerID, lRecipientClosingNumber, true); //bSave=false
    }
    else
    {
        OTLog::Error("OTAgreement::onFinalReceipt: Failed verifying "
                     "lRecipientClosingNumber=this->GetRecipientClosingTransactionNoAt(1)>0 &&  "
                     "pRecipient->VerifyTransactionNum(lRecipientClosingNumber) && VerifyIssuedNum(lRecipientOpeningNumber)\n");
    }
    
    // QUESTION: Won't there be Cron Items that have no asset account at all?
    // In which case, there'd be no need to drop a final receipt, but I don't think
    // that's the case, since you have to use a transaction number to get onto cron
    // in the first place.
    // -----------------------------------------------------------------
}