/// Only if it is an inbox, a ledger will loop through the transactions /// and produce the XML output for the report that's necessary during /// a balance agreement. (Any balance agreement for an account must /// include the list of transactions the nym has issued for use, as /// well as a listing of the transactions in the inbox for that account. /// This function does that last part :) /// /// returns a new balance statement item containing the inbox report /// CALLER IS RESPONSIBLE TO DELETE. OTItem * OTLedger::GenerateBalanceStatement(const long lAdjustment, const OTTransaction & theOwner, OTPseudonym & theNym, const OTAccount & theAccount, OTLedger & theOutbox) { if (OTLedger::inbox != GetType()) { OTLog::Error("OTLedger::GenerateBalanceStatement: Wrong ledger type.\n"); return NULL; } // ------------------------------------------------------ const OTIdentifier theNymID(theNym); if ( (theAccount.GetPurportedAccountID() != GetPurportedAccountID()) || (theAccount.GetPurportedServerID() != GetPurportedServerID()) || (theAccount.GetUserID() != GetUserID()) ) { OTLog::Error("Wrong Account passed in to OTLedger::GenerateBalanceStatement.\n"); return NULL; } if ( (theOutbox.GetPurportedAccountID() != GetPurportedAccountID()) || (theOutbox.GetPurportedServerID() != GetPurportedServerID()) || (theOutbox.GetUserID() != GetUserID()) ) { OTLog::Error("Wrong Outbox passed in to OTLedger::GenerateBalanceStatement.\n"); return NULL; } if ( (theNymID != GetUserID())) { OTLog::Error("Wrong Nym passed in to OTLedger::GenerateBalanceStatement.\n"); return NULL; } // --------------------------------------------------------- // theOwner is the withdrawal, or deposit, or whatever, that wants to change // the account balance, and thus that needs a new balance agreement signed. // OTItem * pBalanceItem = OTItem::CreateItemFromTransaction(theOwner, OTItem::balanceStatement); // <=== balanceStatement type, with user ID, server ID, account ID, transaction ID. // The above has an ASSERT, so this this will never actually happen. if (NULL == pBalanceItem) return NULL; // --------------------------------------------------------- // COPY THE ISSUED TRANSACTION NUMBERS FROM THE NYM to the MESSAGE NYM. OTPseudonym theMessageNym; theMessageNym.HarvestIssuedNumbers(this->GetPurportedServerID(), theNym /*unused in this case, not saving to disk*/, theNym, false); // bSave = false; // ------------------------------------- switch (theOwner.GetType()) { // These five options will remove the transaction number from the issued list, SUCCESS OR FAIL. // Server will expect the number to be missing from the list, in the case of these. // Therefore I remove it here in order to generate a proper balance agreement, acceptable to the server. case OTTransaction::processInbox: case OTTransaction::deposit: case OTTransaction::withdrawal: case OTTransaction::cancelCronItem: case OTTransaction::exchangeBasket: theMessageNym.RemoveIssuedNum(theOwner.GetRealServerID(), theOwner.GetTransactionNum()); // a transaction number is being used, and REMOVED from my list of responsibility, theMessageNym.RemoveTransactionNum(theOwner.GetRealServerID(), theOwner.GetTransactionNum()); // a transaction number is being used, and REMOVED from my list of available numbers. break; case OTTransaction::transfer: case OTTransaction::marketOffer: case OTTransaction::paymentPlan: // Nothing removed here since the transaction is still in play. (Assuming success.) // If the server replies with rejection for any of these three, then I can remove // the transaction number from my list of issued/signed for. But if success, then I // am responsible for the transaction number until I sign off on closing it. // Since the Balance Statement ANTICIPATES SUCCESS, NOT FAILURE, it assumes the number // to be "in play" here, and thus DOES NOT remove it (vs the cases above, which do.) break; default: // Error OTLog::vError("OTLedger::GenerateBalanceStatement: wrong owner transaction type: %s\n", theOwner.GetTypeString()); break; } OTString strMessageNym(theMessageNym); // Okay now we have the transaction numbers in this MessageNym string. pBalanceItem->SetAttachment(strMessageNym); // <======== This is where the server will read the transaction numbers from (A nym in item.m_ascAttachment) // --------------------------------------------------------- long lCurrentBalance = theAccount.GetBalance(); pBalanceItem->SetAmount(lCurrentBalance + lAdjustment); // <==== Here's the new (predicted) balance for after the withdrawal is complete. (item.GetAmount) // --------------------------------------------------------- // loop through the INBOX transactions, and produce a sub-item onto pBalanceItem for each, which will // be a report on each transaction in this inbox, therefore added to the balance item. // (So the balance item contains a complete report on the receipts in this inbox.) OTTransaction * pTransaction = NULL; OTLog::Output(2, "About to loop through the inbox items and produce a report for each one...\n"); for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); OTLog::Output(2, "Producing a report...\n"); // it only reports receipts where we don't yet have balance agreement. // pTransaction->ProduceInboxReportItem(*pBalanceItem, const_cast<OTTransaction &>(theOwner)); pTransaction->ProduceInboxReportItem(*pBalanceItem); // <======= This function adds a receipt sub-item to pBalanceItem, where appropriate for INBOX items. // self note: I added the const_cast because the function needs to loop through it, even though it doesn't really change it // (doesn't violate the const, just needs to perform a loop and the const screws with the loop.) } // --------------------------------------------------------- theOutbox.ProduceOutboxReport(*pBalanceItem); // <======= This function adds receipt sub-items to pBalanceItem, where appropriate for the OUTBOX items. // --------------------------------------------------------- pBalanceItem->SignContract(theNym); // <=== Sign, save, and return. OTTransactionType needs to weasel in a "date signed" variable. pBalanceItem->SaveContract(); return pBalanceItem; }
// 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. // ----------------------------------------------------------------- }