bc47a700a4
As we moved most of the creation of a BufferPool to be via the Streaming::pool() method, which uses a thread-local, it makes sense to start cleaning up the design and make it more modern C++. The above mentioned method would return a reference and you'd see loads of places use `auto &pool =` which is less than ideal. As the number of places where we actually instantiate a BufferPool goes down, the usage of some sort of smart pointer makes more sense. This now makes all APIs use BufferPool be wrapped in a shared_ptr.
238 lines
6.7 KiB
C++
238 lines
6.7 KiB
C++
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2019 Tom Zander <tom@flowee.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "Wallet.h"
|
|
#include "Wallet_p.h"
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include <streaming/MessageParser.h>
|
|
#include <streaming/BufferPool.h>
|
|
#include <streaming/MessageBuilder.h>
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
|
|
using namespace Streaming;
|
|
|
|
Wallet::Wallet(const QString &dbFile)
|
|
: m_dbFile(dbFile)
|
|
{
|
|
loadKeys();
|
|
}
|
|
|
|
Wallet::~Wallet()
|
|
{
|
|
if (m_privKeysNeedsSave) {
|
|
saveKeys();
|
|
}
|
|
}
|
|
|
|
void Wallet::addKey(const PrivateKey &key, int blockheight)
|
|
{
|
|
Q_UNUSED(blockheight);
|
|
assert(key.isValid());
|
|
int id = m_keys.empty() ? 0 : (m_keys.back().first + 1);
|
|
m_keys.push_back(std::make_pair(id, key));
|
|
m_pubkeys.insert(std::make_pair(id, WalletPubKey(key.getPubKey())));
|
|
m_privKeysNeedsSave = true;
|
|
}
|
|
|
|
void Wallet::addOutput(int blockHeight, const uint256 &txid, int offsetInBlock, int outIndex, int64_t amount, const KeyId &destAddress, const CScript &script)
|
|
{
|
|
assert(outIndex < 0xFFFF);
|
|
assert(amount >= 0);
|
|
short keyId = -1;
|
|
for (auto pk : m_pubkeys) {
|
|
if (pk.second.bitcoinAddress == destAddress) {
|
|
keyId = pk.first;
|
|
pk.second.value += amount;
|
|
break;
|
|
}
|
|
}
|
|
if (keyId == -1)
|
|
return;
|
|
|
|
const int coinbaseHeight = offsetInBlock <= 91 ? blockHeight : -1;
|
|
m_unspentOutputs.push_back({txid, (short) outIndex, keyId, 1, coinbaseHeight, amount, script});
|
|
|
|
/*
|
|
for (const WalletItem &wi : m_walletItems) {
|
|
if (wi.blockHeight == blockHeight && offsetInBlock == wi.byteOffsetInblock && wi.txid == txid) {
|
|
wi.valueTransfer.push_back(ValueTransfer(keyId, amount));
|
|
return;
|
|
}
|
|
}
|
|
WalletItem item;
|
|
item.blockHeight = blockHeight;
|
|
item.byteOffsetInblock = offsetInBlock;
|
|
item.txid = txid;
|
|
item.valueTransfer.push_back(ValueTransfer(keyId, amount));
|
|
m_walletItems.push_back(item);
|
|
*/
|
|
}
|
|
|
|
void Wallet::addOutput(const uint256 &txid, int outIndex, int64_t amount, int keyId, short unconfirmedDepth, const CScript &script)
|
|
{
|
|
// unconfirmed.
|
|
assert(outIndex < 0xFFFF);
|
|
assert(keyId < 0xFFFF);
|
|
m_unspentOutputs.push_back({txid, (short) outIndex, (short) keyId, unconfirmedDepth, -2, amount, script});
|
|
}
|
|
|
|
void Wallet::clearUnconfirmedUTXOs()
|
|
{
|
|
for (auto iter = m_unspentOutputs.begin(); iter != m_unspentOutputs.end();) {
|
|
if (iter->coinbaseHeight == -2)
|
|
iter = m_unspentOutputs.erase(iter);
|
|
else
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
const PrivateKey *Wallet::privateKey(int keyId) const
|
|
{
|
|
for (auto iter = m_keys.begin(); iter != m_keys.end(); ++iter) {
|
|
if (iter->first == keyId)
|
|
return &iter->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int Wallet::firstEmptyPubKey() const
|
|
{
|
|
for (auto item : m_pubkeys) {
|
|
if (item.second.value == 0)
|
|
return item.first;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
const PublicKey &Wallet::publicKey(int id) const
|
|
{
|
|
auto iter = m_pubkeys.find(id);
|
|
assert(m_pubkeys.end() != iter);
|
|
return iter->second.pubKey;
|
|
}
|
|
|
|
std::vector<int> Wallet::publicKeys() const
|
|
{
|
|
std::vector<int> answer;
|
|
answer.reserve(m_pubkeys.size());
|
|
for (auto item : m_pubkeys) {
|
|
answer.push_back(item.first);
|
|
}
|
|
return answer;
|
|
}
|
|
|
|
void Wallet::saveKeys()
|
|
{
|
|
if (!m_privKeysNeedsSave)
|
|
return;
|
|
QFileInfo info(m_dbFile);
|
|
if (!info.dir().exists()) {
|
|
QDir root("/");
|
|
root.mkpath(info.absolutePath());
|
|
}
|
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>(m_keys.size() * 40);
|
|
MessageBuilder builder(pool);
|
|
for (auto &keyPair : m_keys) {
|
|
const PrivateKey &key = keyPair.second;
|
|
assert(key.isValid());
|
|
builder.addByteArray(WalletPrivateKeys::PrivateKey, key.begin(), key.size());
|
|
}
|
|
builder.add(WalletPrivateKeys::End, true);
|
|
QFile output(m_dbFile);
|
|
if (output.open(QIODevice::WriteOnly)) {
|
|
auto data = builder.buffer();
|
|
output.write(data.begin(), data.size());
|
|
output.close();
|
|
m_privKeysNeedsSave = false;
|
|
}
|
|
}
|
|
|
|
void Wallet::saveCache()
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void Wallet::loadKeys()
|
|
{
|
|
assert(m_keys.empty());
|
|
QFile input(m_dbFile);
|
|
if (!input.open(QIODevice::ReadOnly))
|
|
return;
|
|
auto fileSize = input.size();
|
|
if (fileSize == -1) // no file to read
|
|
return;
|
|
if (fileSize < 0 || fileSize > 1E6) {
|
|
logFatal() << "Input file is too large, refusing to load";
|
|
throw std::runtime_error("Input file is too large");
|
|
}
|
|
BufferPool pool(fileSize);
|
|
input.read(pool.begin(), fileSize);
|
|
MessageParser parser(pool.commit(fileSize));
|
|
Streaming::ParsedType type = parser.next();
|
|
while (type == Streaming::FoundTag) {
|
|
switch (static_cast<WalletPrivateKeys::WalletTokens>(parser.tag())) {
|
|
case WalletPrivateKeys::PrivateKey:
|
|
if (parser.dataLength() != 32) {
|
|
logFatal()<< "Private key of wrong length" << parser.dataLength();
|
|
} else {
|
|
auto bytes = parser.unsignedBytesData();
|
|
PrivateKey key;
|
|
key.set(bytes.begin(), bytes.end(), true);
|
|
if (key.isValid())
|
|
addKey(key);
|
|
else
|
|
logFatal() << "Failed to parse private key";
|
|
}
|
|
break;
|
|
case WalletPrivateKeys::End: return;
|
|
default:
|
|
logFatal() << "Unknown tag encountered";
|
|
break;
|
|
}
|
|
type = parser.next();
|
|
}
|
|
|
|
logDebug() << "Loading of private keys complete we now have: " << m_keys.size();
|
|
}
|
|
|
|
uint256 Wallet::lastCachedBlock() const
|
|
{
|
|
return m_lastCachedBlock;
|
|
}
|
|
|
|
void Wallet::setLastCachedBlock(const uint256 &lastCachedBlock)
|
|
{
|
|
m_lastCachedBlock = lastCachedBlock;
|
|
}
|
|
|
|
const std::list<Wallet::UnspentOutput> &Wallet::unspentOutputs() const
|
|
{
|
|
return m_unspentOutputs;
|
|
}
|
|
|
|
std::list<Wallet::UnspentOutput>::const_iterator Wallet::spendOutput(const std::list<UnspentOutput>::const_iterator &iter)
|
|
{
|
|
assert (iter != m_unspentOutputs.end());
|
|
return m_unspentOutputs.erase(iter);
|
|
}
|