Untitled diff
35 removals
748 lines
34 additions
745 lines
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2012 The Bitcoin developers
// Copyright (c) 2009-2012 The Bitcoin developers
// Copyright (c) 2011-2013 The PPCoin developers
// Copyright (c) 2011-2013 The Peercoin developers
// Copyright (c) 2015-2015 The Decent developers
// Distributed under the MIT/X11 software license, see the accompanying
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "wallet.h"
#include "wallet.h"
#include "walletdb.h"
#include "walletdb.h"
#include "crypter.h"
#include "crypter.h"
#include "ui_interface.h"
#include "ui_interface.h"
#include "kernel.h"
#include "kernel.h"
#include <boost/algorithm/string/replace.hpp>
#include "bitcoinrpc.h"
#include "base58.h"
#include "encryptionutils.h"
using namespace std;
using namespace std;
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
//
// mapWallet
// mapWallet
//
//
CPubKey CWallet::GenerateNewKey()
std::vector<unsigned char> CWallet::GenerateNewKey()
{
{
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
RandAddSeedPerfmon();
RandAddSeedPerfmon();
CKey key;
CKey key;
key.MakeNewKey(fCompressed);
key.MakeNewKey(fCompressed);
// Compressed public keys were introduced in version 0.6.0
// Compressed public keys were introduced in version 0.6.0
if (fCompressed)
if (fCompressed)
SetMinVersion(FEATURE_COMPRPUBKEY);
SetMinVersion(FEATURE_COMPRPUBKEY);
if (!AddKey(key))
if (!AddKey(key))
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
return key.GetPubKey();
return key.GetPubKey();
}
}
bool CWallet::AddKey(const CKey& key)
bool CWallet::AddKey(const CKey& key)
{
{
if (!CCryptoKeyStore::AddKey(key))
if (!CCryptoKeyStore::AddKey(key))
return false;
return false;
if (!fFileBacked)
if (!fFileBacked)
return true;
return true;
if (!IsCrypted())
if (!IsCrypted())
return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey());
return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey());
return true;
return true;
}
}
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector<unsigned char> &vchCryptedSecret)
bool CWallet::AddCryptedKey(const vector<unsigned char> &vchPubKey, const vector<unsigned char> &vchCryptedSecret)
{
{
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
return false;
if (!fFileBacked)
if (!fFileBacked)
return true;
return true;
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
if (pwalletdbEncryption)
if (pwalletdbEncryption)
return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret);
return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret);
else
else
return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret);
return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret);
}
}
return false;
return false;
}
}
bool CWallet::AddCScript(const CScript& redeemScript)
bool CWallet::AddCScript(const CScript& redeemScript)
{
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
if (!CCryptoKeyStore::AddCScript(redeemScript))
return false;
return false;
if (!fFileBacked)
if (!fFileBacked)
return true;
return true;
return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript);
return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript);
}
}
// ppcoin: optional setting to unlock wallet for block minting only;
// peercoin: optional setting to unlock wallet for block minting only;
// serves to disable the trivial sendmoney when OS account compromised
// serves to disable the trivial sendmoney when OS account compromised
bool fWalletUnlockMintOnly = false;
bool fWalletUnlockMintOnly = false;
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{
{
if (!IsLocked())
if (!IsLocked())
return false;
return false;
CCrypter crypter;
CCrypter crypter;
CKeyingMaterial vMasterKey;
CKeyingMaterial vMasterKey;
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
{
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
return false;
return false;
if (CCryptoKeyStore::Unlock(vMasterKey))
if (CCryptoKeyStore::Unlock(vMasterKey))
return true;
return true;
}
}
}
}
return false;
return false;
}
}
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
{
{
bool fWasLocked = IsLocked();
bool fWasLocked = IsLocked();
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
Lock();
Lock();
CCrypter crypter;
CCrypter crypter;
CKeyingMaterial vMasterKey;
CKeyingMaterial vMasterKey;
BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
{
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
return false;
return false;
if (CCryptoKeyStore::Unlock(vMasterKey))
if (CCryptoKeyStore::Unlock(vMasterKey))
{
{
int64 nStartTime = GetTimeMillis();
int64 nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)));
pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)));
nStartTime = GetTimeMillis();
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (pMasterKey.second.nDeriveIterations < 25000)
if (pMasterKey.second.nDeriveIterations < 25000)
pMasterKey.second.nDeriveIterations = 25000;
pMasterKey.second.nDeriveIterations = 25000;
printf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
printf("Portfolio passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
return false;
if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey))
if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
return false;
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked)
if (fWasLocked)
Lock();
Lock();
return true;
return true;
}
}
}
}
}
}
return false;
return false;
}
}
void CWallet::SetBestChain(const CBlockLocator& loc)
void CWallet::SetBestChain(const CBlockLocator& loc)
{
{
CWalletDB walletdb(strWalletFile);
CWalletDB walletdb(strWalletFile);
walletdb.WriteBestBlock(loc);
walletdb.WriteBestBlock(loc);
}
}
// This class implements an addrIncoming entry that causes pre-0.4
// This class implements an addrIncoming entry that causes pre-0.4
// clients to crash on startup if reading a private-key-encrypted wallet.
// clients to crash on startup if reading a private-key-encrypted wallet.
class CCorruptAddress
class CCorruptAddress
{
{
public:
public:
IMPLEMENT_SERIALIZE
IMPLEMENT_SERIALIZE
(
(
if (nType & SER_DISK)
if (nType & SER_DISK)
READWRITE(nVersion);
READWRITE(nVersion);
)
)
};
};
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit)
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit)
{
{
if (nWalletVersion >= nVersion)
if (nWalletVersion >= nVersion)
return true;
return true;
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
if (fExplicit && nVersion > nWalletMaxVersion)
if (fExplicit && nVersion > nWalletMaxVersion)
nVersion = FEATURE_LATEST;
nVersion = FEATURE_LATEST;
nWalletVersion = nVersion;
nWalletVersion = nVersion;
if (nVersion > nWalletMaxVersion)
if (nVersion > nWalletMaxVersion)
nWalletMaxVersion = nVersion;
nWalletMaxVersion = nVersion;
if (fFileBacked)
if (fFileBacked)
{
{
CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
if (nWalletVersion >= 40000)
if (nWalletVersion >= 40000)
{
{
// Versions prior to 0.4.0 did not support the "minversion" record.
// Versions prior to 0.4.0 did not support the "minversion" record.
// Use a CCorruptAddress to make them crash instead.
// Use a CCorruptAddress to make them crash instead.
CCorruptAddress corruptAddress;
CCorruptAddress corruptAddress;
pwalletdb->WriteSetting("addrIncoming", corruptAddress);
pwalletdb->WriteSetting("addrIncoming", corruptAddress);
}
}
if (nWalletVersion > 40000)
if (nWalletVersion > 40000)
pwalletdb->WriteMinVersion(nWalletVersion);
pwalletdb->WriteMinVersion(nWalletVersion);
if (!pwalletdbIn)
if (!pwalletdbIn)
delete pwalletdb;
delete pwalletdb;
}
}
return true;
return true;
}
}
bool CWallet::SetMaxVersion(int nVersion)
bool CWallet::SetMaxVersion(int nVersion)
{
{
// cannot downgrade below current version
// cannot downgrade below current version
if (nWalletVersion > nVersion)
if (nWalletVersion > nVersion)
return false;
return false;
nWalletMaxVersion = nVersion;
nWalletMaxVersion = nVersion;
return true;
return true;
}
}
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
{
if (IsCrypted())
if (IsCrypted())
return false;
return false;
CKeyingMaterial vMasterKey;
CKeyingMaterial vMasterKey;
RandAddSeedPerfmon();
RandAddSeedPerfmon();
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
CMasterKey kMasterKey;
RandAddSeedPerfmon();
RandAddSeedPerfmon();
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
CCrypter crypter;
int64 nStartTime = GetTimeMillis();
int64 nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime));
kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime));
nStartTime = GetTimeMillis();
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (kMasterKey.nDeriveIterations < 25000)
if (kMasterKey.nDeriveIterations < 25000)
kMasterKey.nDeriveIterations = 25000;
kMasterKey.nDeriveIterations = 25000;
printf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
printf("Encrypting portfolio with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
return false;
return false;
if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey))
if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey))
return false;
return false;
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
if (fFileBacked)
if (fFileBacked)
{
{
pwalletdbEncryption = new CWalletDB(strWalletFile);
pwalletdbEncryption = new CWalletDB(strWalletFile);
if (!pwalletdbEncryption->TxnBegin())
if (!pwalletdbEncryption->TxnBegin())
return false;
return false;
pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
}
}
if (!EncryptKeys(vMasterKey))
if (!EncryptKeys(vMasterKey))
{
{
if (fFileBacked)
if (fFileBacked)
pwalletdbEncryption->TxnAbort();
pwalletdbEncryption->TxnAbort();
exit(1); //We now probably have half of our keys encrypted in memory, and half not...die and let the user reload their unencrypted wallet.
exit(1); //We now probably have half of our keys encrypted in memory, and half not...die and let the user reload their unencrypted wallet.
}
}
// Encryption was introduced in version 0.4.0
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
if (fFileBacked)
if (fFileBacked)
{
{
if (!pwalletdbEncryption->TxnCommit())
if (!pwalletdbEncryption->TxnCommit())
exit(1); //We now have keys encrypted in memory, but no on disk...die to avoid confusion and let the user reload their unencrypted wallet.
exit(1); //We now have keys encrypted in memory, but no on disk...die to avoid confusion and let the user reload their unencrypted wallet.
delete pwalletdbEncryption;
delete pwalletdbEncryption;
pwalletdbEncryption = NULL;
pwalletdbEncryption = NULL;
}
}
Lock();
Lock();
Unlock(strWalletPassphrase);
Unlock(strWalletPassphrase);
NewKeyPool();
NewKeyPool();
Lock();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might keep
// Need to completely rewrite the wallet file; if we don't, bdb might keep
// bits of the unencrypted private key in slack space in the database file.
// bits of the unencrypted private key in slack space in the database file.
CDB::Rewrite(strWalletFile);
CDB::Rewrite(strWalletFile);
}
}
return true;
return true;
}
}
void CWallet::WalletUpdateSpent(const CTransaction &tx)
void CWallet::WalletUpdateSpent(const CTransaction &tx)
{
{
// Anytime a signature is successfully verified, it's proof the outpoint is spent.
// Anytime a signature is successfully verified, it's proof the outpoint is spent.
// Update the wallet spent flag if it doesn't know due to wallet.dat being
// Update the wallet spent flag if it doesn't know due to wallet.dat being
// restored from backup or the user making copies of wallet.dat.
// restored from backup or the user making copies of wallet.dat.
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
BOOST_FOREACH(const CTxIn& txin, tx.vin)
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
{
map<uint256, CWalletTx>::iterator mi = mapWallet.find(txin.prevout.hash);
map<uint256, CWalletTx>::iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
if (mi != mapWallet.end())
{
{
CWalletTx& wtx = (*mi).second;
CWalletTx& wtx = (*mi).second;
// Decent: skip empty TX
if (wtx.vout.empty())
continue;
if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n]))
if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n]))
{
{
printf("WalletUpdateSpent found spent coin %sppc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
printf("WalletUpdateSpent found spent unit %sDCT %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
wtx.MarkSpent(txin.prevout.n);
wtx.MarkSpent(txin.prevout.n);
wtx.WriteToDisk();
wtx.WriteToDisk();
vWalletUpdated.push_back(txin.prevout.hash);
vWalletUpdated.push_back(txin.prevout.hash);
}
}
}
}
}
}
}
}
}
}
void CWallet::MarkDirty()
void CWallet::MarkDirty()
{
{
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
item.second.MarkDirty();
item.second.MarkDirty();
}
}
}
}
bool CWallet::AddToWallet(const CWalletTx& wtxIn)
bool CWallet::AddToWallet(const CWalletTx& wtxIn)
{
{
uint256 hash = wtxIn.GetHash();
uint256 hash = wtxIn.GetHash();
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
// Inserts only if not already there, returns tx inserted or tx found
// Inserts only if not already there, returns tx inserted or tx found
pair<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
pair<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
CWalletTx& wtx = (*ret.first).second;
CWalletTx& wtx = (*ret.first).second;
wtx.BindWallet(this);
wtx.BindWallet(this);
bool fInsertedNew = ret.second;
bool fInsertedNew = ret.second;
if (fInsertedNew)
if (fInsertedNew)
wtx.nTimeReceived = GetAdjustedTime();
wtx.nTimeReceived = GetAdjustedTime();
bool fUpdated = false;
bool fUpdated = false;
if (!fInsertedNew)
if (!fInsertedNew)
{
{
// Merge
// Merge
if (wtxIn.hashBlock != 0 && wtxIn.hashBlock != wtx.hashBlock)
if (wtxIn.hashBlock != 0 && wtxIn.hashBlock != wtx.hashBlock)
{
{
wtx.hashBlock = wtxIn.hashBlock;
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
fUpdated = true;
}
}
if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex))
if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex))
{
{
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
wtx.nIndex = wtxIn.nIndex;
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
fUpdated = true;
}
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
{
wtx.fFromMe = wtxIn.fFromMe;
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
fUpdated = true;
}
}
fUpdated |= wtx.UpdateSpent(wtxIn.vfSpent);
fUpdated |= wtx.UpdateSpent(wtxIn.vfSpent);
}
}
//// debug print
//// debug print
printf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString().substr(0,10).c_str(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
printf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString().substr(0,10).c_str(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
// Write to disk
if (fInsertedNew || fUpdated)
if (fInsertedNew || fUpdated)
if (!wtx.WriteToDisk())
if (!wtx.WriteToDisk())
return false;
return false;
#ifndef QT_GUI
#ifndef QT_GUI
// If default receiving address gets used, replace it with a new one
// If default receiving address gets used, replace it with a new one
CScript scriptDefaultKey;
CScript scriptDefaultKey;
scriptDefaultKey.SetDestination(vchDefaultKey.GetID());
scriptDefaultKey.SetBitcoinAddress(vchDefaultKey);
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
{
{
if (txout.scriptPubKey == scriptDefaultKey)
if (txout.scriptPubKey == scriptDefaultKey)
{
{
CPubKey newDefaultKey;
std::vector<unsigned char> newDefaultKey;
if (GetKeyFromPool(newDefaultKey, false))
if (GetKeyFromPool(newDefaultKey, false))
{
{
SetDefaultKey(newDefaultKey);
SetDefaultKey(newDefaultKey);
SetAddressBookName(vchDefaultKey.GetID(), "");
SetAddressBookName(CBitcoinAddress(vchDefaultKey), "");
}
}
}
}
}
}
#endif
#endif
// Notify UI
// Notify UI
vWalletUpdated.push_back(hash);
vWalletUpdated.push_back(hash);
// since AddToWallet is called directly for self-originating transactions, check for consumption of own coins
// since AddToWallet is called directly for self-originating transactions, check for consumption of own coins
WalletUpdateSpent(wtx);
WalletUpdateSpent(wtx);
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
if ( !strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
}
}
}
// Refresh UI
// Refresh UI
MainFrameRepaint();
MainFrameRepaint();
return true;
return true;
}
}
// Add a transaction to the wallet, or update it.
// Add a transaction to the wallet, or update it.
// pblock is optional, but should be provided if the transaction is known to be in a block.
// pblock is optional, but should be provided if the transaction is known to be in a block.
// If fUpdate is true, existing transactions will be updated.
// If fUpdate is true, existing transactions will be updated.
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock)
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock)
{
{
uint256 hash = tx.GetHash();
uint256 hash = tx.GetHash();
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
bool fExisted = mapWallet.count(hash);
bool fExisted = mapWallet.count(hash);
if (fExisted && !fUpdate) return false;
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
if (fExisted || IsMine(tx) || IsFromMe(tx))
{
{
CWalletTx wtx(this,tx);
CWalletTx wtx(this,tx);
// Get merkle branch if transaction was found in a block
// Get merkle branch if transaction was found in a block
if (pblock)
if (pblock)
wtx.SetMerkleBranch(pblock);
wtx.SetMerkleBranch(pblock);
return AddToWallet(wtx);
return AddToWallet(wtx);
}
}
else
else
WalletUpdateSpent(tx);
WalletUpdateSpent(tx);
}
}
return false;
return false;
}
}
bool CWallet::EraseFromWallet(uint256 hash)
bool CWallet::EraseFromWallet(uint256 hash)
{
{
if (!fFileBacked)
if (!fFileBacked)
return false;
return false;
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
if (mapWallet.erase(hash))
if (mapWallet.erase(hash))
CWalletDB(strWalletFile).EraseTx(hash);
CWalletDB(strWalletFile).EraseTx(hash);
}
}
return true;
return true;
}
}
bool CWallet::IsMine(const CTxIn &txin) const
bool CWallet::IsMine(const CTxIn &txin) const
{
{
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
if (mi != mapWallet.end())
{
{
const CWalletTx& prev = (*mi).second;
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
if (txin.prevout.n < prev.vout.size())
if (IsMine(prev.vout[txin.prevout.n]))
if (IsMine(prev.vout[txin.prevout.n]))
return true;
return true;
}
}
}
}
return false;
return false;
}
}
int64 CWallet::GetDebit(const CTxIn &txin) const
int64 CWallet::GetDebit(const CTxIn &txin) const
{
{
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
if (mi != mapWallet.end())
{
{
const CWalletTx& prev = (*mi).second;
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
if (txin.prevout.n < prev.vout.size())
if (IsMine(prev.vout[txin.prevout.n]))
if (IsMine(prev.vout[txin.prevout.n]))
return prev.vout[txin.prevout.n].nValue;
return prev.vout[txin.prevout.n].nValue;
}
}
}
}
return 0;
return 0;
}
}
bool CWallet::IsChange(const CTxOut& txout) const
bool CWallet::IsChange(const CTxOut& txout) const
{
{
CTxDestination address;
CBitcoinAddress address;
// TODO: fix handling of 'change' outputs. The assumption is that any
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a TX_PUBKEYHASH that is mine but isn't in the address book
// payment to a TX_PUBKEYHASH that is mine but isn't in the address book
// is change. That assumption is likely to break when we implement multisignature
// is change. That assumption is likely to break when we implement multisignature
// wallets that return change back into a multi-signature-protected address;
// wallets that return change back into a multi-signature-protected address;
// a better way of identifying which outputs are 'the send' and which are
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
// which output, if any, was change).
if (ExtractDestination(txout.scriptPubKey, address) && ::IsMine(*this, address))
if (ExtractAddress(txout.scriptPubKey, address) && HaveKey(address))
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
if (!mapAddressBook.count(address))
if (!mapAddressBook.count(address))
return true;
return true;
}
}
return false;
return false;
}
}
int64 CWalletTx::GetTxTime() const
int64 CWalletTx::GetTxTime() const
{
{
return nTimeReceived;
return nTimeReceived;
}
}
int CWalletTx::GetRequestCount() const
int CWalletTx::GetRequestCount() const
{
{
// Returns -1 if it wasn't being tracked
// Returns -1 if it wasn't being tracked
int nRequests = -1;
int nRequests = -1;
{
{
LOCK(pwallet->cs_wallet);
LOCK(pwallet->cs_wallet);
if (IsCoinBase() || IsCoinStake())
if (IsCoinBase() || IsCoinStake())
{
{
// Generated block
// Generated block
if (hashBlock != 0)
if (hashBlock != 0)
{
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
if (mi != pwallet->mapRequestCount.end())
nRequests = (*mi).second;
nRequests = (*mi).second;
}
}
}
}
else
else
{
{
// Did anyone request this transaction?
// Did anyone request this transaction?
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash());
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash());
if (mi != pwallet->mapRequestCount.end())
if (mi != pwallet->mapRequestCount.end())
{
{
nRequests = (*mi).second;
nRequests = (*mi).second;
// How about the block it's in?
// How about the block it's in?
if (nRequests == 0 && hashBlock != 0)
if (nRequests == 0 && hashBlock != 0)
{
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
if (mi != pwallet->mapRequestCount.end())
nRequests = (*mi).second;
nRequests = (*mi).second;
else
else
nRequests = 1; // If it's in someone else's block it must have got out
nRequests = 1; // If it's in someone else's block it must have got out
}
}
}
}
}
}
}
}
return nRequests;
return nRequests;
}
}
void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list<pair<CTxDestination, int64> >& listReceived,
void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list<pair<CBitcoinAddress, int64> >& listReceived,
list<pair<CTxDestination, int64> >& listSent, int64& nFee, string& strSentAccount) const
list<pair<CBitcoinAddress, int64> >& listSent, int64& nFee, string& strSentAccount) const
{
{
nGeneratedImmature = nGeneratedMature = nFee = 0;
nGeneratedImmature = nGeneratedMature = nFee = 0;
listReceived.clear();
listReceived.clear();
listSent.clear();
listSent.clear();
strSentAccount = strFromAccount;
strSentAccount = strFromAccount;
if (IsCoinBase() || IsCoinStake())
if (IsCoinBase() || IsCoinStake())
{
{
if (GetBlocksToMaturity() > 0)
if (GetBlocksToMaturity() > 0)
nGeneratedImmature = pwallet->GetCredit(*this) - pwallet->GetDebit(*this);
nGeneratedImmature = pwallet->GetCredit(*this) - pwallet->GetDebit(*this);
else
else
nGeneratedMature = GetCredit() - GetDebit();
nGeneratedMature = GetCredit() - GetDebit();
return;
return;
}
}
// Compute fee:
// Compute fee:
int64 nDebit = GetDebit();
int64 nDebit = GetDebit();
if (nDebit > 0) // debit>0 means we signed/sent this transaction
if (nDebit > 0) // debit>0 means we signed/sent this transaction
{
{
int64 nValueOut = GetValueOut();
int64 nValueOut = GetValueOut();
nFee = nDebit - nValueOut;
nFee = nDebit - nValueOut;
}
}
// Sent/received.
// Sent/received.
BOOST_FOREACH(const CTxOut& txout, vout)
BOOST_FOREACH(const CTxOut& txout, vout)
{
{
CTxDestination address;
CBitcoinAddress address;
vector<unsigned char> vchPubKey;
vector<unsigned char> vchPubKey;
if (!ExtractDestination(txout.scriptPubKey, address))
if (!ExtractAddress(txout.scriptPubKey, address))
{
{
printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
this->GetHash().ToString().c_str());
this->GetHash().ToString().c_str());
address = " unknown ";
}
}
// Don't report 'change' txouts
// Don't report 'change' txouts
if (nDebit > 0 && pwallet->IsChange(txout))
if (nDebit > 0 && pwallet->IsChange(txout))
continue;
continue;
if (nDebit > 0)
if (nDebit > 0)
listSent.push_back(make_pair(address, txout.nValue));
listSent.push_back(make_pair(address, txout.nValue));
if (pwallet->IsMine(txout))
if (pwallet->IsMine(txout))
listReceived.push_back(make_pair(address, txout.nValue));
listReceived.push_back(make_pair(address, txout.nValue));
}
}
}
}
void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived,
void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived,
int64& nSent, int64& nFee) const
int64& nSent, int64& nFee) const
{
{
nGenerated = nReceived = nSent = nFee = 0;
nGenerated = nReceived = nSent = nFee = 0;
int64 allGeneratedImmature, allGeneratedMature, allFee;
int64 allGeneratedImmature, allGeneratedMature, allFee;
allGeneratedImmature = allGeneratedMature = allFee = 0;
allGeneratedImmature = allGeneratedMature = allFee = 0;
string strSentAccount;
string strSentAccount;
list<pair<CTxDestination, int64> > listReceived;
list<pair<CBitcoinAddress, int64> > listReceived;
list<pair<CTxDestination, int64> > listSent;
list<pair<CBitcoinAddress, int64> > listSent;
GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount);
GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount);
if (strAccount == "")
if (strAccount == "")
nGenerated = allGeneratedMature;
nGenerated = allGeneratedMature;
if (strAccount == strSentAccount)
if (strAccount == strSentAccount)
{
{
BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& s, listSent)
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& s, listSent)
nSent += s.second;
nSent += s.second;
nFee = allFee;
nFee = allFee;
}
}
{
{
LOCK(pwallet->cs_wallet);
LOCK(pwallet->cs_wallet);
BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listReceived)
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listReceived)
{
{
if (pwallet->mapAddressBook.count(r.first))
if (pwallet->mapAddressBook.count(r.first))
{
{
map<CTxDestination, string>::const_iterator mi = pwallet->mapAddressBook.find(r.first);
map<CBitcoinAddress, string>::const_iterator mi = pwallet->mapAddressBook.find(r.first);
if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount)
if (mi != pwallet->mapAddressBook.end() && ((*mi).second == strAccount || (*mi).first.ToString() == strAccount))
nReceived += r.second;
nReceived += r.second;
}
}
else if (strAccount.empty())
else if (strAccount.empty())
{
{
nReceived += r.second;
nReceived += r.second;
}
}
}
}
}
}
}
}
void CWalletTx::AddSupportingTransactions(CTxDB& txdb)
void CWalletTx::AddSupportingTransactions(CTxDB& txdb)
{
{
vtxPrev.clear();
vtxPrev.clear();
const int COPY_DEPTH = 3;
const int COPY_DEPTH = 3;
if (SetMerkleBranch() < COPY_DEPTH)
if (SetMerkleBranch() < COPY_DEPTH)
{
{
vector<uint256> vWorkQueue;
vector<uint256> vWorkQueue;
BOOST_FOREACH(const CTxIn& txin, vin)
BOOST_FOREACH(const CTxIn& txin, vin)
vWorkQueue.push_back(txin.prevout.hash);
vWorkQueue.push_back(txin.prevout.hash);
// This critsect is OK because txdb is already open
// This critsect is OK because txdb is already open
{
{
LOCK(pwallet->cs_wallet);
LOCK(pwallet->cs_wallet);
map<uint256, const CMerkleTx*> mapWalletPrev;
map<uint256, const CMerkleTx*> mapWalletPrev;
set<uint256> setAlreadyDone;
set<uint256> setAlreadyDone;
for (unsigned int i = 0; i < vWorkQueue.size(); i++)
for (unsigned int i = 0; i < vWorkQueue.size(); i++)
{
{
uint256 hash = vWorkQueue[i];
uint256 hash = vWorkQueue[i];
if (setAlreadyDone.count(hash))
if (setAlreadyDone.count(hash))
continue;
continue;
setAlreadyDone.insert(hash);
setAlreadyDone.insert(hash);
CMerkleTx tx;
CMerkleTx tx;
map<uint256, CWalletTx>::const_iterator mi = pwallet->mapWallet.find(hash);
map<uint256, CWalletTx>::const_iterator mi = pwallet->mapWallet.find(hash);
if (mi != pwallet->mapWallet.end())
if (mi != pwallet->mapWallet.end())
{
{
tx = (*mi).second;
tx = (*mi).second;
BOOST_FOREACH(const CMerkleTx& txWalletPrev, (*mi).second.vtxPrev)
BOOST_FOREACH(const CMerkleTx& txWalletPrev, (*mi).second.vtxPrev)
mapWalletPrev[txWalletPrev.GetHash()] = &txWalletPrev;
mapWalletPrev[txWalletPrev.GetHash()] = &txWalletPrev;
}
}
else if (mapWalletPrev.count(hash))
else if (mapWalletPrev.count(hash))
{
{
tx = *mapWalletPrev[hash];
tx = *mapWalletPrev[hash];
}
}
else if (!fClient && txdb.ReadDiskTx(hash, tx))
else if (!fClient && txdb.ReadDiskTx(hash, tx))
{
{
;
;
}
}
else
else
{
{
printf("ERROR: AddSupportingTransactions() : unsupported transaction\n");
printf("ERROR: AddSupportingTransactions() : unsupported transaction\n");
continue;
continue;
}
}
int nDepth = tx.SetMerkleBranch();
int nDepth = tx.SetMerkleBranch();
vtxPrev.push_back(tx);
vtxPrev.push_back(tx);
if (nDepth < COPY_DEPTH)
if (nDepth < COPY_DEPTH)
{
{
BOOST_FOREACH(const CTxIn& txin, tx.vin)
BOOST_FOREACH(const CTxIn& txin, tx.vin)
vWorkQueue.push_back(txin.prevout.hash);
vWorkQueue.push_back(txin.prevout.hash);
}
}
}
}
}
}
}
}
reverse(vtxPrev.begin(), vtxPrev.end());
reverse(vtxPrev.begin(), vtxPrev.end());
}
}
bool CWalletTx::WriteToDisk()
bool CWalletTx::WriteToDisk()
{
{
return CWalletDB(pwallet->strWalletFile).WriteTx(GetHash(), *this);
return CWalletDB(pwallet->strWalletFile).WriteTx(GetHash(), *this);
}
}
// Scan the block chain (starting in pindexStart) for transactions
// Scan the block chain (starting in pindexStart) for transactions
// from or to us. If fUpdate is true, found transactions that already
// from or to us. If fUpdate is true, found transactions that already
// exist in the wallet will be updated.
// exist in the wallet will be updated.
int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
{
{
int ret = 0;
int ret = 0;
CBlockIndex* pindex = pindexStart;
CBlockIndex* pindex = pindexStart;
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
while (pindex)
while (pindex)
{
{
CBlock block;
CBlock block;
block.ReadFromDisk(pindex, true);
block.ReadFromDisk(pindex, true);
BOOST_FOREACH(CTransaction& tx, block.vtx)
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
ret++;
ret++;
}
}
pindex = pindex->pnext;
pindex = pindex->pnext;
}
}
}
}
return ret;
return ret;
}
}
int CWallet::ScanForWalletTransaction(const uint256& hashTx)
int CWallet::ScanForWalletTransaction(const uint256& hashTx)
{
{
CTransaction tx;
CTransaction tx;
tx.ReadFromDisk(COutPoint(hashTx, 0));
tx.ReadFromDisk(COutPoint(hashTx, 0));
if (AddToWalletIfInvolvingMe(tx, NULL, true, true))
if (AddToWalletIfInvolvingMe(tx, NULL, true, true))
return 1;
return 1;
return 0;
return 0;
}
}
void CWallet::ReacceptWalletTransactions()
void CWallet::ReacceptWalletTransactions()
{
{
CTxDB txdb("r");
CTxDB txdb("r");
bool fRepeat = true;
bool fRepeat = true;
while (fRepeat)
while (fRepeat)
{
{
LOCK(cs_wallet);
LOCK(cs_wallet);
fRepeat = false;
fRepeat = false;
vector<CDiskTxPos> vMissingTx;
vector<CDiskTxPos> vMissingTx;
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
{
CWalletTx& wtx = item.second;
CWalletTx& wtx = item.second;
if ((wtx.IsCoinBase() && wtx.IsSpent(0)) || (wtx.IsCoinStake() && wtx.IsSpent(1)))
if ((wtx.IsCoinBase() && wtx.IsSpent(0)) || (wtx.IsCoinStake() && wtx.IsSpent(1)))
continue;
continue;
CTxIndex txindex;
CTxIndex txindex;
bool fUpdated = false;
bool fUpdated = false;
if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
{
{
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
if (txindex.vSpent.size() != wtx.vout.size())
if (txindex.vSpent.size() != wtx.vout.size())
{
{
printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size());
printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size());
continue;
continue;
}
for (unsigned int i = 0; i < txindex.v