// Called by the above function. // This ledger is an outbox, and it is creating a report of itself, // adding each report item to this balance item. // DO NOT call this, it's meant to be used only by above function. void OTLedger::ProduceOutboxReport(OTItem & theBalanceItem) { if (OTLedger::outbox != GetType()) { OTLog::Error("OTLedger::ProduceOutboxReport: Wrong ledger type.\n"); return; } // loop through the OUTBOX transactions, and produce a sub-item onto theBalanceItem for each, which will // be a report on each pending transfer in this outbox, therefore added to the balance item. // (So the balance item contains a complete report on the outoing transfers in this outbox.) OTTransaction * pTransaction = NULL; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); // it only reports receipts where we don't yet have balance agreement. pTransaction->ProduceOutboxReportItem(theBalanceItem); // <======= This function adds a pending transfer sub-item to theBalanceItem, where appropriate. } // --------------------------------------------------------- }
// for inbox only, allows you to lookup the total value of pending transfers within the inbox. // (And it really loads the items to check the amount, but does all this ONLY for pending transfers.) // long OTLedger::GetTotalPendingValue() { long lTotalPendingValue = 0; if (OTLedger::inbox != GetType()) { OTLog::Error("OTLedger::GetTotalPendingValue: Wrong ledger type (expected inbox).\n"); return 0; } OTTransaction * pTransaction = NULL; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); if (pTransaction->GetType() == OTTransaction::pending) lTotalPendingValue += pTransaction->GetReceiptAmount(); // this actually loads up the original item and reads the amount. } return lTotalPendingValue; }
// 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. OTTransaction * OTLedger::GetTransaction(long lTransactionNum) { // loop through the items that make up this transaction // OTLog::vError("OTLedger::GetTransaction: Checking ledger for trans %ld. COUNT: %d \n", lTransactionNum, GetTransactionCount()); for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { OTTransaction * pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); // OTLog::vError("OTLedger::GetTransaction: Looping. Currently on trans %ld \n", pTransaction->GetTransactionNum()); if (pTransaction->GetTransactionNum() == lTransactionNum) { // OTLog::vOutput(5, "OTLedger::GetTransaction: Returning transaction# %ld \n", lTransactionNum); return pTransaction; } // else // this was for debugging only. It's actually normal for non-matching numbers to be on this list. // OTLog::vOutput(5"Expected transaction number %ld, but found %ld on the list instead. Bad data?\n", // lTransactionNum, pTransaction->GetTransactionNum()); } // OTLog::Error("OTLedger::GetTransaction: Returning NULL \n"); return NULL; }
// 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. OTTransaction * OTLedger::GetTransaction(long lTransactionNum) { // See if there's something there with that transaction number. mapOfTransactions::iterator it = m_mapTransactions.find(lTransactionNum); if ( it == m_mapTransactions.end() ) { // not found. return NULL; } // Found it! else { OTTransaction * pTransaction = (*it).second; OT_ASSERT((NULL != pTransaction)); if (pTransaction->GetTransactionNum() == lTransactionNum) return pTransaction; else OTLog::vError("Expected transaction number %ld, but found %ld on the list instead. Bad data?\n", lTransactionNum, pTransaction->GetTransactionNum()); } return NULL; }
// OTMessageOutbuffer deletes the OTMessage when you call this. // bool OTMessageOutbuffer::RemoveSentMessage(const OTTransaction & theTransaction) { const int64_t & lRequestNum = theTransaction.GetRequestNum(); const OTString strServerID(theTransaction.GetPurportedServerID()); const OTString strNymID(theTransaction.GetUserID()); // ------------------------------------- return RemoveSentMessage(lRequestNum, strServerID, strNymID); }
/// If transaction #87, in reference to #74, is in the inbox, you can remove it /// by calling this function and passing in 74. /// bool OTLedger::RemovePendingTransaction(long lTransactionNum) // if false, transaction wasn't found. { // loop through the items that make up this transaction. OTTransaction * pTransaction = NULL; mapOfTransactions::iterator it; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { it = ii; pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); bool bCorrectType = false; switch (pTransaction->GetType()) { case OTTransaction::pending: case OTTransaction::transferReceipt: case OTTransaction::chequeReceipt: bCorrectType = true; break; default: break; } if (bCorrectType && pTransaction->GetReferenceToNum() == lTransactionNum) break; else pTransaction = NULL; } // If it's not already on the list, then there's nothing to remove. if ( NULL == pTransaction ) { OTLog::vError("OTLedger::RemovePendingTransaction: Attempt to remove Transaction from ledger,\n" "when not already there: (the number in reference to) %ld\n", lTransactionNum); return false; } // Otherwise, if it WAS already there, remove it properly. else { m_mapTransactions.erase(it); delete pTransaction; return true; } return false; }
// SignContract will call this function at the right time. void OTLedger::UpdateContents() // Before transmission or serialization, this is where the ledger saves its contents { // Notice I use the PURPORTED Account ID and Server ID to create the output. That's because // I don't want to inadvertantly substitute the real ID for a bad one and then sign it. // So if there's a bad one in there when I read it, THAT's the one that I write as well! OTString strType, strLedgerAcctID(GetPurportedAccountID()), strLedgerAcctServerID(GetPurportedServerID()), strUserID(GetUserID()); switch (m_Type) { case OTLedger::message: strType.Set("message"); break; case OTLedger::inbox: strType.Set("inbox"); break; case OTLedger::outbox: strType.Set("outbox"); break; default: strType.Set("error-unknown"); break; } // I release this because I'm about to repopulate it. m_xmlUnsigned.Release(); // m_xmlUnsigned.Concatenate("<?xml version=\"%s\"?>\n\n", "1.0"); m_xmlUnsigned.Concatenate("<accountLedger version=\"%s\"\n type=\"%s\"\n accountID=\"%s\"\n userID=\"%s\"\n" "serverID=\"%s\" >\n\n", m_strVersion.Get(), strType.Get(), strLedgerAcctID.Get(), strUserID.Get(), strLedgerAcctServerID.Get()); // loop through the transactions and print them out here. OTTransaction * pTransaction = NULL; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { if ((pTransaction = (*ii).second)) // if pointer not null { OTString strTransaction; pTransaction->SaveContract(strTransaction); OTASCIIArmor ascTransaction; ascTransaction.SetString(strTransaction, true); // linebreaks = true m_xmlUnsigned.Concatenate("<transaction>\n%s</transaction>\n\n", ascTransaction.Get()); } } m_xmlUnsigned.Concatenate("</accountLedger>\n"); }
// Return a count of all the transactions in this ledger that are IN REFERENCE TO a specific trans#. // // Might want to change this so that it only counts ACCEPTED receipts. // int OTLedger::GetTransactionCountInRefTo(const long lReferenceNum) { int nCount = 0; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { OTTransaction * pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); if (pTransaction->GetReferenceToNum() == lReferenceNum) nCount++; } return nCount; }
// While processing a transaction, you may wish to query it for items of a certain type. OTTransaction * OTLedger::GetTransaction(const OTTransaction::transactionType theType) { // loop through the items that make up this transaction for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { OTTransaction * pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); if (theType == pTransaction->GetType()) return pTransaction; } return NULL; }
// If you TRANSFER REQUEST to me (transaction #1), then the server will create a // PENDING transaction in my inbox (transaction #41) and a PENDING transaction in // your outbox (also transaction #41) which both contain a copy of transaction#1 in their // "In Reference To" ascii-armored field. // // The above function would look up #41 in my inbox, or #41 in your outbox, but // you could NOT pass #1 to that function and get a pointer back. You'd get NULL. // But the below function specifically returns the pointer of a transaction ONLY // IF THE "IN REFERENCE TO" Transaction ID matches the one passed in (such as #1 // in the example above. // If it can't find anything, it will return NULL. OTTransaction * OTLedger::GetPendingTransaction(long lTransactionNum) { // loop through the items that make up this transaction. OTTransaction * pTransaction = NULL; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); if (pTransaction->GetReferenceToNum() == lTransactionNum) return pTransaction; } return NULL; }
// Find the finalReceipt in this Inbox, that has lTransactionNum as its "in reference to". // This is useful for cases where a marketReceipt or paymentReceipt has been found, // yet the transaction # for that receipt isn't on my issued list... it's been closed. // Normally this would be a problem: why is it in my inbox then? Because those receipts // are still valid as long as there is a "FINAL RECEIPT" in the same inbox, that references // the same original transaction that they do. The below function makes it easy to find that // final receipt, if it exists. // OTTransaction * OTLedger::GetFinalReceipt(long lReferenceNum) { // loop through the items that make up this transaction. OTTransaction * pTransaction = NULL; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); if (OTTransaction::finalReceipt != pTransaction->GetType()) // <======= continue; // --------------------------------- if (pTransaction->GetReferenceToNum() == lReferenceNum) return pTransaction; } return NULL; }
bool OTLedger::AddTransaction(OTTransaction & theTransaction) { // See if there's something else already there with the same transaction number. mapOfTransactions::iterator it = m_mapTransactions.find(theTransaction.GetTransactionNum()); // If it's not already on the list, then add it... if ( it == m_mapTransactions.end() ) { m_mapTransactions[theTransaction.GetTransactionNum()] = &theTransaction; return true; } // Otherwise, if it was already there, log an error. else { OTLog::vError("Attempt to add Transaction to ledger when already there for that number: %ld\n", theTransaction.GetTransactionNum()); } return false; }
// If my outbox has a pending transfer, #1901, referencing 1884, and then the // recipient accepts it with his #781, referencing 1884, then it will pop into my inbox // as a transfer receipt, #1902 (say) and referencing 781. Attached to that // transfer receipt is a copy of the actual #781, which is in reference to 1884. // // Why does this matter? Because when I am verifying a balance agreement, and an // outbox item 1901/1884 is missing, that means there is probably a corresponding // transferReceipt in the Inbox. In that case, I START with #1901 referencing 1884 (from // the outbox) and I need to FIND #1902, in reference to 781, referencing 1884 in the inbox. // // Therefore, loop through all items and filter by transfer receipt. For each, load its // Reference string (containing the acceptPending) and get ITS ReferenceNum() to compare // to the one passed in. // // Therefore 1884 would be passed in, and the appropriate transferReceipt will be returned. // OTTransaction * OTLedger::GetTransferReceipt(long lTransactionNum) { // loop through the items that make up this transaction. OTTransaction * pTransaction = NULL; for (mapOfTransactions::iterator ii = m_mapTransactions.begin(); ii != m_mapTransactions.end(); ++ii) { pTransaction = (*ii).second; OT_ASSERT(NULL != pTransaction); if (OTTransaction::transferReceipt == pTransaction->GetType()) { OTString strReference; pTransaction->GetReferenceString(strReference); OTItem * pOriginalItem = OTItem::CreateItemFromString(strReference, pTransaction->GetPurportedServerID(), pTransaction->GetReferenceToNum()); OT_ASSERT(NULL != pOriginalItem); OTCleanup<OTItem> theItemAngel(*pOriginalItem); if (pOriginalItem->GetType() != OTItem::acceptPending) { OTLog::Error("OTLedger::GetTransferReceipt: Wrong item type attached to transferReceipt!\n"); return NULL; } else { if (pOriginalItem->GetReferenceToNum() == lTransactionNum) return pTransaction; // FOUND IT! } } } return NULL; }
/// 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; }
// LoadContract will call this function at the right time. // return -1 if error, 0 if nothing, and 1 if the node was processed. int OTLedger::ProcessXMLNode(irr::io::IrrXMLReader*& xml) { OTString strKeyName; OTString strKeyValue; OTString strTransaction; OTASCIIArmor ascTransaction; if (!strcmp("accountLedger", xml->getNodeName())) { OTString strType, strLedgerAcctID, strLedgerAcctServerID, strUserID; strType = xml->getAttributeValue("type"); if (strType.Compare("message")) m_Type = OTLedger::message; else if (strType.Compare("inbox")) m_Type = OTLedger::inbox; else if (strType.Compare("outbox")) m_Type = OTLedger::outbox; else if (strType.Compare("nymbox")) m_Type = OTLedger::nymbox; else m_Type = OTLedger::error_state; m_strVersion = xml->getAttributeValue("version"); strLedgerAcctID = xml->getAttributeValue("accountID"); strLedgerAcctServerID = xml->getAttributeValue("serverID"); strUserID = xml->getAttributeValue("userID"); OTIdentifier ACCOUNT_ID(strLedgerAcctID), SERVER_ID(strLedgerAcctServerID), USER_ID(strUserID); SetPurportedAccountID(ACCOUNT_ID); SetPurportedServerID(SERVER_ID); SetUserID(USER_ID); OTLog::vOutput(2, "Loaded account ledger of type \"%s\", version: %s\n", // "accountID:\n%s\n userID:\n%s\n serverID:\n%s\n----------\n", strType.Get(), m_strVersion.Get() // strLedgerAcctID.Get(), strUserID.Get(), strLedgerAcctServerID.Get() ); // Since we just loaded this stuff, let's verify it. // We may have to remove this verification here and do it outside this call. // But for now... if (VerifyContractID()) return 1; else { return -1; } } else if (!strcmp("transaction", xml->getNodeName())) { // go to the next node and read the text. xml->read(); if (EXN_TEXT == xml->getNodeType()) { // the ledger contains a series of transactions. // Each transaction is initially stored as an OTASCIIArmor string. ascTransaction.Set(xml->getNodeData()); // Put the ascii-armored node data into the ascii-armor object ascTransaction.GetString(strTransaction); // Decode that into strTransaction, so we can load the transaction object from that string. OTTransaction * pTransaction = new OTTransaction(GetUserID(), GetPurportedAccountID(), GetPurportedServerID()); // If we're able to successfully base64-decode the string and load it up as // a transaction, then let's add it to the ledger's list of transactions if (pTransaction && pTransaction->LoadContractFromString(strTransaction) && pTransaction->VerifyContractID()) // I responsible here to call pTransaction->VerifyContract() since // I am loading it here and adding it to the ledger. (So I do.) { m_mapTransactions[pTransaction->GetTransactionNum()] = pTransaction; // OTLog::Output(5, "Loaded transaction and adding to m_mapTransactions in OTLedger\n"); } else { OTLog::Error("ERROR: loading transaction in OTLedger::ProcessXMLNode\n"); if (pTransaction) { delete pTransaction; pTransaction = NULL; } return (-1); } } else { OTLog::Error("Error in OTLedger::ProcessXMLNode: transaction without value.\n"); return (-1); // error condition } return 1; } return 0; }