Bytecoin v.1.0.8 release
This commit is contained in:
parent
a4b74eaa11
commit
a6588cfc58
49 changed files with 1580 additions and 252 deletions
|
@ -1,3 +1,10 @@
|
|||
Release notes 1.0.8
|
||||
|
||||
- Fusion transactions for Bytecoin Wallet
|
||||
- IWallet high-level API update
|
||||
- JSON RPC improvements
|
||||
- Synchronization improvements for OS X
|
||||
|
||||
Release notes 1.0.7
|
||||
|
||||
- Fusion transactions support
|
||||
|
|
|
@ -75,6 +75,7 @@ public:
|
|||
IncludeStateUnlocked = 0x01,
|
||||
IncludeStateLocked = 0x02,
|
||||
IncludeStateSoftLocked = 0x04,
|
||||
IncludeStateSpent = 0x08,
|
||||
// output type
|
||||
IncludeTypeKey = 0x100,
|
||||
IncludeTypeMultisignature = 0x200,
|
||||
|
@ -92,14 +93,16 @@ public:
|
|||
IncludeDefault = IncludeKeyUnlocked
|
||||
};
|
||||
|
||||
virtual size_t transfersCount() = 0;
|
||||
virtual size_t transactionsCount() = 0;
|
||||
virtual uint64_t balance(uint32_t flags = IncludeDefault) = 0;
|
||||
virtual void getOutputs(std::vector<TransactionOutputInformation>& transfers, uint32_t flags = IncludeDefault) = 0;
|
||||
virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) = 0;
|
||||
virtual std::vector<TransactionOutputInformation> getTransactionOutputs(const Crypto::Hash& transactionHash, uint32_t flags = IncludeDefault) = 0;
|
||||
virtual void getUnconfirmedTransactions(std::vector<Crypto::Hash>& transactions) = 0;
|
||||
virtual std::vector<TransactionSpentOutputInformation> getSpentOutputs() = 0;
|
||||
virtual size_t transfersCount() const = 0;
|
||||
virtual size_t transactionsCount() const = 0;
|
||||
virtual uint64_t balance(uint32_t flags = IncludeDefault) const = 0;
|
||||
virtual void getOutputs(std::vector<TransactionOutputInformation>& transfers, uint32_t flags = IncludeDefault) const = 0;
|
||||
virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) const = 0;
|
||||
virtual std::vector<TransactionOutputInformation> getTransactionOutputs(const Crypto::Hash& transactionHash, uint32_t flags = IncludeDefault) const = 0;
|
||||
//only type flags are feasible for this function
|
||||
virtual std::vector<TransactionOutputInformation> getTransactionInputs(const Crypto::Hash& transactionHash, uint32_t flags) const = 0;
|
||||
virtual void getUnconfirmedTransactions(std::vector<Crypto::Hash>& transactions) const = 0;
|
||||
virtual std::vector<TransactionSpentOutputInformation> getSpentOutputs() const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ struct WalletTransaction {
|
|||
uint64_t creationTime;
|
||||
uint64_t unlockTime;
|
||||
std::string extra;
|
||||
bool isBase;
|
||||
};
|
||||
|
||||
struct WalletTransfer {
|
||||
|
@ -99,6 +100,7 @@ public:
|
|||
virtual KeyPair getViewKey() const = 0;
|
||||
virtual std::string createAddress() = 0;
|
||||
virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) = 0;
|
||||
virtual std::string createAddress(const Crypto::PublicKey& spendPublicKey) = 0;
|
||||
virtual void deleteAddress(const std::string& address) = 0;
|
||||
|
||||
virtual uint64_t getActualBalance() const = 0;
|
||||
|
|
|
@ -461,10 +461,6 @@ void BlockchainExplorer::poolChanged() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!*isBlockchainActualPtr) {
|
||||
logger(WARNING) << "Blockchain not actual.";
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
std::shared_ptr<std::vector<Hash>> newTransactionsHashesPtr = std::make_shared<std::vector<Hash>>();
|
||||
|
|
|
@ -63,8 +63,8 @@ const uint64_t CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME = 60 * 60 * 24 * 7;
|
|||
const uint64_t CRYPTONOTE_NUMBER_OF_PERIODS_TO_FORGET_TX_DELETED_FROM_POOL = 7; // CRYPTONOTE_NUMBER_OF_PERIODS_TO_FORGET_TX_DELETED_FROM_POOL * CRYPTONOTE_MEMPOOL_TX_LIVETIME = time to forget tx
|
||||
|
||||
const size_t FUSION_TX_MAX_SIZE = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE * 30 / 100;
|
||||
const size_t FUSION_TX_MIN_INPUT_COUNT = 6;
|
||||
const size_t FUSION_TX_MIN_IN_OUT_COUNT_RATIO = 3;
|
||||
const size_t FUSION_TX_MIN_INPUT_COUNT = 12;
|
||||
const size_t FUSION_TX_MIN_IN_OUT_COUNT_RATIO = 4;
|
||||
|
||||
const uint64_t UPGRADE_HEIGHT = 546602;
|
||||
const unsigned UPGRADE_VOTING_THRESHOLD = 90; // percent
|
||||
|
@ -162,7 +162,8 @@ const CheckpointData CHECKPOINTS[] = {
|
|||
{796000, "04e387a00d35db21d4d93d04040b31f22573972a7e61d72cc07d0ab69bcb9c44"},
|
||||
{800000, "d7fa4eea02e5ce60b949136569c0ea7ac71ea46e0065311054072ac415560b86"},
|
||||
{804000, "bcc8b3782499aae508c40d5587d1cc5d68281435ea9bfc6804a262047f7b934d"},
|
||||
{810500, "302b2349f221232820adc3dadafd8a61b035491e33af669c78a687949eb0a381"}
|
||||
{810500, "302b2349f221232820adc3dadafd8a61b035491e33af669c78a687949eb0a381"},
|
||||
{816000, "32b7fdd4e4d715db81f8f09f4ba5e5c78e8113f2804d61a57378baee479ce745"}
|
||||
};
|
||||
} // CryptoNote
|
||||
|
||||
|
|
|
@ -473,20 +473,14 @@ void core::on_synchronized() {
|
|||
|
||||
bool core::getPoolChanges(const Crypto::Hash& tailBlockId, const std::vector<Crypto::Hash>& knownTxsIds,
|
||||
std::vector<Transaction>& addedTxs, std::vector<Crypto::Hash>& deletedTxsIds) {
|
||||
if (tailBlockId != m_blockchain.getTailId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getPoolChanges(knownTxsIds, addedTxs, deletedTxsIds);
|
||||
return true;
|
||||
return tailBlockId == m_blockchain.getTailId();
|
||||
}
|
||||
|
||||
bool core::getPoolChangesLite(const Crypto::Hash& tailBlockId, const std::vector<Crypto::Hash>& knownTxsIds,
|
||||
std::vector<TransactionPrefixInfo>& addedTxs, std::vector<Crypto::Hash>& deletedTxsIds) {
|
||||
std::vector<Transaction> added;
|
||||
if (!getPoolChanges(tailBlockId, knownTxsIds, added, deletedTxsIds)) {
|
||||
return false;
|
||||
}
|
||||
bool returnStatus = getPoolChanges(tailBlockId, knownTxsIds, added, deletedTxsIds);
|
||||
|
||||
for (const auto& tx: added) {
|
||||
TransactionPrefixInfo tpi;
|
||||
|
@ -496,12 +490,13 @@ bool core::getPoolChangesLite(const Crypto::Hash& tailBlockId, const std::vector
|
|||
addedTxs.push_back(std::move(tpi));
|
||||
}
|
||||
|
||||
return true;
|
||||
return returnStatus;
|
||||
}
|
||||
|
||||
void core::getPoolChanges(const std::vector<Crypto::Hash>& knownTxsIds, std::vector<Transaction>& addedTxs,
|
||||
std::vector<Crypto::Hash>& deletedTxsIds) {
|
||||
std::vector<Crypto::Hash> addedTxsIds;
|
||||
auto guard = m_mempool.obtainGuard();
|
||||
m_mempool.get_difference(knownTxsIds, addedTxsIds, deletedTxsIds);
|
||||
std::vector<Crypto::Hash> misses;
|
||||
m_mempool.getTransactions(addedTxsIds, addedTxs, misses);
|
||||
|
|
|
@ -56,6 +56,21 @@ uint64_t getInputAmount(const Transaction& transaction) {
|
|||
return amount;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> getInputsAmounts(const Transaction& transaction) {
|
||||
std::vector<uint64_t> inputsAmounts;
|
||||
inputsAmounts.reserve(transaction.inputs.size());
|
||||
|
||||
for (auto& input: transaction.inputs) {
|
||||
if (input.type() == typeid(KeyInput)) {
|
||||
inputsAmounts.push_back(boost::get<KeyInput>(input).amount);
|
||||
} else if (input.type() == typeid(MultisignatureInput)) {
|
||||
inputsAmounts.push_back(boost::get<MultisignatureInput>(input).amount);
|
||||
}
|
||||
}
|
||||
|
||||
return inputsAmounts;
|
||||
}
|
||||
|
||||
uint64_t getOutputAmount(const Transaction& transaction) {
|
||||
uint64_t amount = 0;
|
||||
for (auto& output : transaction.outputs) {
|
||||
|
|
|
@ -120,6 +120,7 @@ Crypto::Hash getObjectHash(const T& object) {
|
|||
}
|
||||
|
||||
uint64_t getInputAmount(const Transaction& transaction);
|
||||
std::vector<uint64_t> getInputsAmounts(const Transaction& transaction);
|
||||
uint64_t getOutputAmount(const Transaction& transaction);
|
||||
void decomposeAmount(uint64_t amount, uint64_t dustThreshold, std::vector<uint64_t>& decomposedAmounts);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,29 @@ using namespace Common;
|
|||
|
||||
namespace CryptoNote {
|
||||
|
||||
const std::vector<uint64_t> Currency::PRETTY_AMOUNTS = {
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 20, 30, 40, 50, 60, 70, 80, 90,
|
||||
100, 200, 300, 400, 500, 600, 700, 800, 900,
|
||||
1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000,
|
||||
10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000,
|
||||
100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000,
|
||||
1000000, 2000000, 3000000, 4000000, 5000000, 6000000, 7000000, 8000000, 9000000,
|
||||
10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000,
|
||||
100000000, 200000000, 300000000, 400000000, 500000000, 600000000, 700000000, 800000000, 900000000,
|
||||
1000000000, 2000000000, 3000000000, 4000000000, 5000000000, 6000000000, 7000000000, 8000000000, 9000000000,
|
||||
10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000, 70000000000, 80000000000, 90000000000,
|
||||
100000000000, 200000000000, 300000000000, 400000000000, 500000000000, 600000000000, 700000000000, 800000000000, 900000000000,
|
||||
1000000000000, 2000000000000, 3000000000000, 4000000000000, 5000000000000, 6000000000000, 7000000000000, 8000000000000, 9000000000000,
|
||||
10000000000000, 20000000000000, 30000000000000, 40000000000000, 50000000000000, 60000000000000, 70000000000000, 80000000000000, 90000000000000,
|
||||
100000000000000, 200000000000000, 300000000000000, 400000000000000, 500000000000000, 600000000000000, 700000000000000, 800000000000000, 900000000000000,
|
||||
1000000000000000, 2000000000000000, 3000000000000000, 4000000000000000, 5000000000000000, 6000000000000000, 7000000000000000, 8000000000000000, 9000000000000000,
|
||||
10000000000000000, 20000000000000000, 30000000000000000, 40000000000000000, 50000000000000000, 60000000000000000, 70000000000000000, 80000000000000000, 90000000000000000,
|
||||
100000000000000000, 200000000000000000, 300000000000000000, 400000000000000000, 500000000000000000, 600000000000000000, 700000000000000000, 800000000000000000, 900000000000000000,
|
||||
1000000000000000000, 2000000000000000000, 3000000000000000000, 4000000000000000000, 5000000000000000000, 6000000000000000000, 7000000000000000000, 8000000000000000000, 9000000000000000000,
|
||||
10000000000000000000ull
|
||||
};
|
||||
|
||||
bool Currency::init() {
|
||||
if (!generateGenesisBlock()) {
|
||||
logger(ERROR, BRIGHT_RED) << "Failed to generate genesis block";
|
||||
|
@ -209,50 +232,73 @@ bool Currency::constructMinerTx(uint32_t height, size_t medianSize, uint64_t alr
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Currency::isFusionTransaction(const Transaction& transaction, uint64_t inputAmount, size_t size) const {
|
||||
assert(getInputAmount(transaction) == inputAmount);
|
||||
assert(getObjectBinarySize(transaction) == size);
|
||||
|
||||
bool Currency::isFusionTransaction(const std::vector<uint64_t>& inputsAmounts, const std::vector<uint64_t>& outputsAmounts, size_t size) const {
|
||||
if (size > fusionTxMaxSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (transaction.inputs.size() < fusionTxMinInputCount()) {
|
||||
if (inputsAmounts.size() < fusionTxMinInputCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (transaction.inputs.size() < transaction.outputs.size() * fusionTxMinInOutCountRatio()) {
|
||||
if (inputsAmounts.size() < outputsAmounts.size() * fusionTxMinInOutCountRatio()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t inputAmount = 0;
|
||||
for (auto amount: inputsAmounts) {
|
||||
if (amount < defaultDustThreshold()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inputAmount += amount;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> expectedOutputsAmounts;
|
||||
expectedOutputsAmounts.reserve(transaction.outputs.size());
|
||||
expectedOutputsAmounts.reserve(outputsAmounts.size());
|
||||
decomposeAmount(inputAmount, defaultDustThreshold(), expectedOutputsAmounts);
|
||||
assert(!expectedOutputsAmounts.empty());
|
||||
|
||||
if (expectedOutputsAmounts.size() != transaction.outputs.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::sort(expectedOutputsAmounts.begin(), expectedOutputsAmounts.end());
|
||||
|
||||
if (expectedOutputsAmounts.front() <= defaultDustThreshold()) {
|
||||
return false;
|
||||
return expectedOutputsAmounts == outputsAmounts;
|
||||
}
|
||||
|
||||
bool Currency::isFusionTransaction(const Transaction& transaction, size_t size) const {
|
||||
assert(getObjectBinarySize(transaction) == size);
|
||||
|
||||
std::vector<uint64_t> outputsAmounts;
|
||||
outputsAmounts.reserve(transaction.outputs.size());
|
||||
for (const TransactionOutput& output : transaction.outputs) {
|
||||
outputsAmounts.push_back(output.amount);
|
||||
}
|
||||
|
||||
auto it1 = expectedOutputsAmounts.begin();
|
||||
auto it2 = transaction.outputs.begin();
|
||||
for (; it1 != expectedOutputsAmounts.end(); ++it1, ++it2) {
|
||||
if (*it1 != it2->amount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return isFusionTransaction(getInputsAmounts(transaction), outputsAmounts, size);
|
||||
}
|
||||
|
||||
bool Currency::isFusionTransaction(const Transaction& transaction) const {
|
||||
return isFusionTransaction(transaction, getInputAmount(transaction), getObjectBinarySize(transaction));
|
||||
return isFusionTransaction(transaction, getObjectBinarySize(transaction));
|
||||
}
|
||||
|
||||
bool Currency::isAmountApplicableInFusionTransactionInput(uint64_t amount, uint64_t threshold) const {
|
||||
uint8_t ignore;
|
||||
return isAmountApplicableInFusionTransactionInput(amount, threshold, ignore);
|
||||
}
|
||||
|
||||
bool Currency::isAmountApplicableInFusionTransactionInput(uint64_t amount, uint64_t threshold, uint8_t& amountPowerOfTen) const {
|
||||
if (amount >= threshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amount < defaultDustThreshold()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(PRETTY_AMOUNTS.begin(), PRETTY_AMOUNTS.end(), amount);
|
||||
if (it == PRETTY_AMOUNTS.end() || amount != *it) {
|
||||
return false;
|
||||
}
|
||||
|
||||
amountPowerOfTen = static_cast<uint8_t>(std::distance(PRETTY_AMOUNTS.begin(), it) / 9);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Currency::accountAddressAsString(const AccountBase& account) const {
|
||||
|
@ -440,6 +486,29 @@ bool Currency::checkProofOfWork(Crypto::cn_context& context, const Block& block,
|
|||
return false;
|
||||
}
|
||||
|
||||
size_t Currency::getApproximateMaximumInputCount(size_t transactionSize, size_t outputCount, size_t mixinCount) const {
|
||||
const size_t KEY_IMAGE_SIZE = sizeof(Crypto::KeyImage);
|
||||
const size_t OUTPUT_KEY_SIZE = sizeof(decltype(KeyOutput::key));
|
||||
const size_t AMOUNT_SIZE = sizeof(uint64_t) + 2; //varint
|
||||
const size_t GLOBAL_INDEXES_VECTOR_SIZE_SIZE = sizeof(uint8_t);//varint
|
||||
const size_t GLOBAL_INDEXES_INITIAL_VALUE_SIZE = sizeof(uint32_t);//varint
|
||||
const size_t GLOBAL_INDEXES_DIFFERENCE_SIZE = sizeof(uint32_t);//varint
|
||||
const size_t SIGNATURE_SIZE = sizeof(Crypto::Signature);
|
||||
const size_t EXTRA_TAG_SIZE = sizeof(uint8_t);
|
||||
const size_t INPUT_TAG_SIZE = sizeof(uint8_t);
|
||||
const size_t OUTPUT_TAG_SIZE = sizeof(uint8_t);
|
||||
const size_t PUBLIC_KEY_SIZE = sizeof(Crypto::PublicKey);
|
||||
const size_t TRANSACTION_VERSION_SIZE = sizeof(uint8_t);
|
||||
const size_t TRANSACTION_UNLOCK_TIME_SIZE = sizeof(uint64_t);
|
||||
|
||||
const size_t outputsSize = outputCount * (OUTPUT_TAG_SIZE + OUTPUT_KEY_SIZE + AMOUNT_SIZE);
|
||||
const size_t headerSize = TRANSACTION_VERSION_SIZE + TRANSACTION_UNLOCK_TIME_SIZE + EXTRA_TAG_SIZE + PUBLIC_KEY_SIZE;
|
||||
const size_t inputSize = INPUT_TAG_SIZE + AMOUNT_SIZE + KEY_IMAGE_SIZE + SIGNATURE_SIZE + GLOBAL_INDEXES_VECTOR_SIZE_SIZE + GLOBAL_INDEXES_INITIAL_VALUE_SIZE +
|
||||
mixinCount * (GLOBAL_INDEXES_DIFFERENCE_SIZE + SIGNATURE_SIZE);
|
||||
|
||||
return (transactionSize - headerSize - outputsSize) / inputSize;
|
||||
}
|
||||
|
||||
CurrencyBuilder::CurrencyBuilder(Logging::ILogger& log) : m_currency(log) {
|
||||
maxBlockNumber(parameters::CRYPTONOTE_MAX_BLOCK_NUMBER);
|
||||
maxBlockBlobSize(parameters::CRYPTONOTE_MAX_BLOCK_BLOB_SIZE);
|
||||
|
|
|
@ -104,7 +104,10 @@ public:
|
|||
const BinaryArray& extraNonce = BinaryArray(), size_t maxOuts = 1, bool penalizeFee = false) const;
|
||||
|
||||
bool isFusionTransaction(const Transaction& transaction) const;
|
||||
bool isFusionTransaction(const Transaction& transaction, uint64_t inputAmount, size_t size) const;
|
||||
bool isFusionTransaction(const Transaction& transaction, size_t size) const;
|
||||
bool isFusionTransaction(const std::vector<uint64_t>& inputsAmounts, const std::vector<uint64_t>& outputsAmounts, size_t size) const;
|
||||
bool isAmountApplicableInFusionTransactionInput(uint64_t amount, uint64_t threshold) const;
|
||||
bool isAmountApplicableInFusionTransactionInput(uint64_t amount, uint64_t threshold, uint8_t& amountPowerOfTen) const;
|
||||
|
||||
std::string accountAddressAsString(const AccountBase& account) const;
|
||||
std::string accountAddressAsString(const AccountPublicAddress& accountPublicAddress) const;
|
||||
|
@ -120,6 +123,8 @@ public:
|
|||
bool checkProofOfWorkV2(Crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, Crypto::Hash& proofOfWork) const;
|
||||
bool checkProofOfWork(Crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, Crypto::Hash& proofOfWork) const;
|
||||
|
||||
size_t getApproximateMaximumInputCount(size_t transactionSize, size_t outputCount, size_t mixinCount) const;
|
||||
|
||||
private:
|
||||
Currency(Logging::ILogger& log) : logger(log, "currency") {
|
||||
}
|
||||
|
@ -182,6 +187,8 @@ private:
|
|||
std::string m_txPoolFileName;
|
||||
std::string m_blockchinIndicesFileName;
|
||||
|
||||
static const std::vector<uint64_t> PRETTY_AMOUNTS;
|
||||
|
||||
bool m_testnet;
|
||||
|
||||
Block m_genesisBlock;
|
||||
|
|
|
@ -133,7 +133,7 @@ namespace CryptoNote {
|
|||
}
|
||||
|
||||
const uint64_t fee = inputs_amount - outputs_amount;
|
||||
bool isFusionTransaction = fee == 0 && m_currency.isFusionTransaction(tx, inputs_amount, blobSize);
|
||||
bool isFusionTransaction = fee == 0 && m_currency.isFusionTransaction(tx, blobSize);
|
||||
if (!keptByBlock && !isFusionTransaction && fee < m_currency.minimumFee()) {
|
||||
logger(INFO) << "transaction fee is not enough: " << m_currency.formatAmount(fee) <<
|
||||
", minimum fee: " << m_currency.formatAmount(m_currency.minimumFee());
|
||||
|
@ -222,6 +222,7 @@ namespace CryptoNote {
|
|||
//succeed
|
||||
return true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
bool tx_memory_pool::add_tx(const Transaction &tx, tx_verification_context& tvc, bool keeped_by_block) {
|
||||
Crypto::Hash h = NULL_HASH;
|
||||
|
@ -308,6 +309,11 @@ namespace CryptoNote {
|
|||
void tx_memory_pool::unlock() const {
|
||||
m_transactions_lock.unlock();
|
||||
}
|
||||
|
||||
std::unique_lock<std::recursive_mutex> tx_memory_pool::obtainGuard() const {
|
||||
return std::unique_lock<std::recursive_mutex>(m_transactions_lock);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
bool tx_memory_pool::is_transaction_ready_to_go(const Transaction& tx, TransactionCheckInfo& txd) const {
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ namespace CryptoNote {
|
|||
|
||||
void lock() const;
|
||||
void unlock() const;
|
||||
std::unique_lock<std::recursive_mutex> obtainGuard() const;
|
||||
|
||||
bool fill_block_template(Block &bl, size_t median_size, size_t maxCumulativeSize, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee);
|
||||
|
||||
|
|
|
@ -646,7 +646,7 @@ void CryptoNoteProtocolHandler::updateObservedHeight(uint32_t peerHeight, const
|
|||
if (m_observedHeight != height) {
|
||||
updated = true;
|
||||
}
|
||||
} else if (context.m_remote_blockchain_height == m_observedHeight) {
|
||||
} else if (peerHeight != context.m_remote_blockchain_height && context.m_remote_blockchain_height == m_observedHeight) {
|
||||
//the client switched to alternative chain and had maximum observed height. need to recalculate max height
|
||||
recalculateMaxObservedHeight(context);
|
||||
if (m_observedHeight != height) {
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
// along with Bytecoin. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "HttpParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "HttpParserErrorCodes.h"
|
||||
|
||||
namespace {
|
||||
|
@ -98,7 +101,7 @@ void HttpParser::receiveResponse(std::istream& stream, HttpResponse& response) {
|
|||
response.addHeader(name, value);
|
||||
auto headers = response.getHeaders();
|
||||
size_t length = 0;
|
||||
auto it = headers.find("Content-Length");
|
||||
auto it = headers.find("content-length");
|
||||
if (it != headers.end()) {
|
||||
length = std::stoul(it->second);
|
||||
}
|
||||
|
@ -182,6 +185,8 @@ bool HttpParser::readHeader(std::istream& stream, std::string& name, std::string
|
|||
throw std::system_error(make_error_code(CryptoNote::error::HttpParserErrorCodes::UNEXPECTED_SYMBOL));
|
||||
}
|
||||
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
|
||||
c = stream.peek();
|
||||
if (c == '\r') {
|
||||
stream.get(c).get(c);
|
||||
|
@ -196,7 +201,7 @@ bool HttpParser::readHeader(std::istream& stream, std::string& name, std::string
|
|||
}
|
||||
|
||||
size_t HttpParser::getBodyLen(const HttpRequest::Headers& headers) {
|
||||
auto it = headers.find("Content-Length");
|
||||
auto it = headers.find("content-length");
|
||||
if (it != headers.end()) {
|
||||
size_t bytes = std::stoul(it->second);
|
||||
return bytes;
|
||||
|
|
|
@ -540,15 +540,10 @@ void InProcessNode::getPoolSymmetricDifferenceAsync(std::vector<Crypto::Hash>&&
|
|||
|
||||
std::vector<TransactionPrefixInfo> added;
|
||||
isBcActual = core.getPoolChangesLite(knownBlockId, knownPoolTxIds, added, deletedTxIds);
|
||||
if (!isBcActual) {
|
||||
ec = make_error_code(CryptoNote::error::INTERNAL_NODE_ERROR);
|
||||
callback(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const auto& tx: added) {
|
||||
newTxs.push_back(createTransactionPrefix(tx.txPrefix, reinterpret_cast<const Hash&>(tx.txHash)));
|
||||
newTxs.push_back(createTransactionPrefix(tx.txPrefix, tx.txHash));
|
||||
}
|
||||
} catch (std::system_error& ex) {
|
||||
ec = ex.code();
|
||||
|
|
|
@ -536,9 +536,6 @@ std::error_code NodeRpcProxy::doGetPoolSymmetricDifference(std::vector<Crypto::H
|
|||
}
|
||||
|
||||
isBcActual = rsp.isTailBlockActual;
|
||||
if (!isBcActual) {
|
||||
return ec;
|
||||
}
|
||||
|
||||
deletedTxIds = std::move(rsp.deletedTxsIds);
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
std::string method = req("method").getString();
|
||||
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
CryptoNote::JsonOutputStreamSerializer outputSerializer;
|
||||
|
||||
if (method == "send_transaction") {
|
||||
|
@ -48,6 +47,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(sendReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -67,6 +67,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(getAddrReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -106,6 +107,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(delAddrReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -125,6 +127,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(actualReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -150,6 +153,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(pendingReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -195,6 +199,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(getReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -216,6 +221,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(getReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -235,6 +241,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(listReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -254,6 +261,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(getReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
@ -273,6 +281,7 @@ void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue&
|
|||
|
||||
//XXX: refactor it when migrate to different exception types in different subsystems!
|
||||
try {
|
||||
CryptoNote::JsonInputValueSerializer inputSerializer(req("params"));
|
||||
serialize(getReq, inputSerializer);
|
||||
} catch (std::exception&) {
|
||||
makeGenericErrorReponse(resp, "Invalid Request", -32600);
|
||||
|
|
|
@ -162,6 +162,10 @@ void Dispatcher::dispatch() {
|
|||
struct kevent event;
|
||||
int count = kevent(kqueue, NULL, 0, &event, 1, NULL);
|
||||
if (count == 1) {
|
||||
if (event.flags & EV_ERROR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.filter == EVFILT_USER && event.ident == 0) {
|
||||
struct kevent event;
|
||||
EV_SET(&event, 0, EVFILT_USER, EV_ADD | EV_DISABLE, NOTE_FFNOP, 0, NULL);
|
||||
|
@ -172,6 +176,11 @@ void Dispatcher::dispatch() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (event.filter == EVFILT_WRITE) {
|
||||
event.flags = EV_DELETE | EV_DISABLE;
|
||||
kevent(kqueue, &event, 1, NULL, 0, NULL); // ignore error here
|
||||
}
|
||||
|
||||
context = static_cast<OperationContext*>(event.udata)->context;
|
||||
break;
|
||||
}
|
||||
|
@ -274,21 +283,24 @@ void Dispatcher::spawn(std::function<void()>&& procedure) {
|
|||
|
||||
void Dispatcher::yield() {
|
||||
struct timespec zeroTimeout = { 0, 0 };
|
||||
int updatesCounter = 0;
|
||||
for (;;) {
|
||||
struct kevent events[16];
|
||||
int count = kevent(kqueue, NULL, 0, events, 16, &zeroTimeout);
|
||||
struct kevent updates[16];
|
||||
int count = kevent(kqueue, updates, updatesCounter, events, 16, &zeroTimeout);
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
updatesCounter = 0;
|
||||
if (count > 0) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (events[i].flags & EV_ERROR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (events[i].filter == EVFILT_USER && events[i].ident == 0) {
|
||||
struct kevent event;
|
||||
EV_SET(&event, 0, EVFILT_USER, EV_ADD | EV_DISABLE, NOTE_FFNOP, 0, NULL);
|
||||
if (kevent(kqueue, &event, 1, NULL, 0, NULL) == -1) {
|
||||
throw std::runtime_error("Dispatcher::yield, kevent failed, " + lastErrorMessage());
|
||||
}
|
||||
EV_SET(&updates[updatesCounter++], 0, EVFILT_USER, EV_ADD | EV_DISABLE, NOTE_FFNOP, 0, NULL);
|
||||
|
||||
MutextGuard guard(*reinterpret_cast<pthread_mutex_t*>(this->mutex));
|
||||
while (!remoteSpawningProcedures.empty()) {
|
||||
|
@ -302,6 +314,9 @@ void Dispatcher::yield() {
|
|||
|
||||
static_cast<OperationContext*>(events[i].udata)->context->interruptProcedure = nullptr;
|
||||
pushContext(static_cast<OperationContext*>(events[i].udata)->context);
|
||||
if (events[i].filter == EVFILT_WRITE) {
|
||||
EV_SET(&updates[updatesCounter++], events[i].ident, EVFILT_WRITE, EV_DELETE | EV_DISABLE, 0, 0, NULL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (errno != EINTR) {
|
||||
|
|
|
@ -168,7 +168,7 @@ size_t TcpConnection::write(const uint8_t* data, size_t size) {
|
|||
context.context = dispatcher->getCurrentContext();
|
||||
context.interrupted = false;
|
||||
struct kevent event;
|
||||
EV_SET(&event, connection, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, &context);
|
||||
EV_SET(&event, connection, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &context);
|
||||
if (kevent(dispatcher->getKqueue(), &event, 1, NULL, 0, NULL) == -1) {
|
||||
message = "kevent failed, " + lastErrorMessage();
|
||||
} else {
|
||||
|
|
|
@ -107,7 +107,7 @@ TcpConnection TcpConnector::connect(const Ipv4Address& address, uint16_t port) {
|
|||
connectorContext.connection = connection;
|
||||
|
||||
struct kevent event;
|
||||
EV_SET(&event, connection, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_ONESHOT | EV_CLEAR, 0, 0, &connectorContext);
|
||||
EV_SET(&event, connection, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &connectorContext);
|
||||
if (kevent(dispatcher->getKqueue(), &event, 1, NULL, 0, NULL) == -1) {
|
||||
message = "kevent failed, " + lastErrorMessage();
|
||||
} else {
|
||||
|
|
|
@ -21,9 +21,10 @@
|
|||
#include <boost/foreach.hpp>
|
||||
#include <functional>
|
||||
|
||||
#include "CoreRpcServerCommandsDefinitions.h"
|
||||
#include <Common/JsonValue.h>
|
||||
#include "Serialization/ISerializer.h"
|
||||
#include "Serialization/SerializationTools.h"
|
||||
#include <Common/JsonValue.h>
|
||||
|
||||
namespace CryptoNote {
|
||||
|
||||
|
@ -204,7 +205,7 @@ bool invokeMethod(const JsonRpcRequest& jsReq, JsonRpcResponse& jsRes, Handler h
|
|||
Request req;
|
||||
Response res;
|
||||
|
||||
if (!jsReq.loadParams(req)) {
|
||||
if (!std::is_same<Request, CryptoNote::EMPTY_STRUCT>::value && !jsReq.loadParams(req)) {
|
||||
throw JsonRpcError(JsonRpc::errInvalidParams);
|
||||
}
|
||||
|
||||
|
|
|
@ -306,18 +306,15 @@ bool RpcServer::onGetPoolChanges(const COMMAND_RPC_GET_POOL_CHANGES::request& re
|
|||
rsp.status = CORE_RPC_STATUS_OK;
|
||||
std::vector<CryptoNote::Transaction> addedTransactions;
|
||||
rsp.isTailBlockActual = m_core.getPoolChanges(req.tailBlockId, req.knownTxsIds, addedTransactions, rsp.deletedTxsIds);
|
||||
if (rsp.isTailBlockActual) {
|
||||
for (auto& tx : addedTransactions) {
|
||||
BinaryArray txBlob;
|
||||
if (!toBinaryArray(tx, txBlob)) {
|
||||
rsp.status = "Internal error";
|
||||
break;;
|
||||
}
|
||||
|
||||
rsp.addedTxs.emplace_back(std::move(txBlob));
|
||||
for (auto& tx : addedTransactions) {
|
||||
BinaryArray txBlob;
|
||||
if (!toBinaryArray(tx, txBlob)) {
|
||||
rsp.status = "Internal error";
|
||||
break;;
|
||||
}
|
||||
}
|
||||
|
||||
rsp.addedTxs.emplace_back(std::move(txBlob));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -327,7 +327,6 @@ void BlockchainSynchronizer::onGetBlocksCompleted(std::error_code ec) {
|
|||
}
|
||||
|
||||
void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) {
|
||||
uint32_t newHeight = response.startHeight + static_cast<uint32_t>(response.newBlocks.size());
|
||||
BlockchainInterval interval;
|
||||
interval.startHeight = response.startHeight;
|
||||
std::vector<CompleteBlock> blocks;
|
||||
|
@ -365,6 +364,7 @@ void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) {
|
|||
blocks.push_back(std::move(completeBlock));
|
||||
}
|
||||
|
||||
uint32_t processedBlockCount = response.startHeight + static_cast<uint32_t>(response.newBlocks.size());
|
||||
if (!checkIfShouldStop()) {
|
||||
response.newBlocks.clear();
|
||||
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
||||
|
@ -393,7 +393,7 @@ void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) {
|
|||
setFutureState(State::blockchainSync);
|
||||
m_observerManager.notify(
|
||||
&IBlockchainSynchronizerObserver::synchronizationProgressUpdated,
|
||||
newHeight,
|
||||
processedBlockCount,
|
||||
std::max(m_node.getKnownBlockCount(), m_node.getLocalBlockCount()));
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ struct CompleteBlock;
|
|||
|
||||
class IBlockchainSynchronizerObserver {
|
||||
public:
|
||||
virtual void synchronizationProgressUpdated(uint32_t current, uint32_t total) {}
|
||||
virtual void synchronizationProgressUpdated(uint32_t processedBlockCount, uint32_t totalBlockCount) {}
|
||||
virtual void synchronizationCompleted(std::error_code result) {}
|
||||
};
|
||||
|
||||
|
|
|
@ -160,6 +160,7 @@ void TransfersConsumer::onBlockchainDetach(uint32_t height) {
|
|||
|
||||
bool TransfersConsumer::onNewBlocks(const CompleteBlock* blocks, uint32_t startHeight, uint32_t count) {
|
||||
assert(blocks);
|
||||
assert(count > 0);
|
||||
|
||||
struct Tx {
|
||||
TransactionBlockInfo blockInfo;
|
||||
|
@ -273,7 +274,7 @@ bool TransfersConsumer::onNewBlocks(const CompleteBlock* blocks, uint32_t startH
|
|||
return false;
|
||||
}
|
||||
|
||||
auto newHeight = startHeight + count;
|
||||
auto newHeight = startHeight + count - 1;
|
||||
forEachSubscription([newHeight](TransfersSubscription& sub) {
|
||||
sub.advanceHeight(newHeight);
|
||||
});
|
||||
|
|
|
@ -588,24 +588,24 @@ bool TransfersContainer::advanceHeight(uint32_t height) {
|
|||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
|
||||
if (m_currentHeight <= height) {
|
||||
m_currentHeight = height;
|
||||
m_currentHeight = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t TransfersContainer::transfersCount() {
|
||||
size_t TransfersContainer::transfersCount() const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
return m_unconfirmedTransfers.size() + m_availableTransfers.size() + m_spentTransfers.size();
|
||||
}
|
||||
|
||||
size_t TransfersContainer::transactionsCount() {
|
||||
size_t TransfersContainer::transactionsCount() const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
return m_transactions.size();
|
||||
}
|
||||
|
||||
uint64_t TransfersContainer::balance(uint32_t flags) {
|
||||
uint64_t TransfersContainer::balance(uint32_t flags) const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
uint64_t amount = 0;
|
||||
|
||||
|
@ -626,7 +626,7 @@ uint64_t TransfersContainer::balance(uint32_t flags) {
|
|||
return amount;
|
||||
}
|
||||
|
||||
void TransfersContainer::getOutputs(std::vector<TransactionOutputInformation>& transfers, uint32_t flags) {
|
||||
void TransfersContainer::getOutputs(std::vector<TransactionOutputInformation>& transfers, uint32_t flags) const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
for (const auto& t : m_availableTransfers) {
|
||||
if (t.visible && isIncluded(t, flags)) {
|
||||
|
@ -643,7 +643,7 @@ void TransfersContainer::getOutputs(std::vector<TransactionOutputInformation>& t
|
|||
}
|
||||
}
|
||||
|
||||
bool TransfersContainer::getTransactionInformation(const Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) {
|
||||
bool TransfersContainer::getTransactionInformation(const Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
auto it = m_transactions.find(transactionHash);
|
||||
if (it == m_transactions.end()) {
|
||||
|
@ -682,7 +682,7 @@ bool TransfersContainer::getTransactionInformation(const Hash& transactionHash,
|
|||
}
|
||||
|
||||
std::vector<TransactionOutputInformation> TransfersContainer::getTransactionOutputs(const Hash& transactionHash,
|
||||
uint32_t flags) {
|
||||
uint32_t flags) const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
|
||||
std::vector<TransactionOutputInformation> result;
|
||||
|
@ -704,10 +704,37 @@ std::vector<TransactionOutputInformation> TransfersContainer::getTransactionOutp
|
|||
}
|
||||
}
|
||||
|
||||
if ((flags & IncludeStateSpent) != 0) {
|
||||
auto spentRange = m_spentTransfers.get<ContainingTransactionIndex>().equal_range(transactionHash);
|
||||
for (auto i = spentRange.first; i != spentRange.second; ++i) {
|
||||
if (isIncluded(i->type, IncludeStateAll, flags)) {
|
||||
result.push_back(*i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TransfersContainer::getUnconfirmedTransactions(std::vector<Crypto::Hash>& transactions) {
|
||||
std::vector<TransactionOutputInformation> TransfersContainer::getTransactionInputs(const Hash& transactionHash, uint32_t flags) const {
|
||||
//only type flags are feasible
|
||||
assert((flags & IncludeStateAll) == 0);
|
||||
flags |= IncludeStateUnlocked;
|
||||
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
|
||||
std::vector<TransactionOutputInformation> result;
|
||||
auto transactionInputsRange = m_spentTransfers.get<SpendingTransactionIndex>().equal_range(transactionHash);
|
||||
for (auto it = transactionInputsRange.first; it != transactionInputsRange.second; ++it) {
|
||||
if (isIncluded(it->type, IncludeStateUnlocked, flags)) {
|
||||
result.push_back(*it);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TransfersContainer::getUnconfirmedTransactions(std::vector<Crypto::Hash>& transactions) const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
transactions.clear();
|
||||
for (auto& element : m_transactions) {
|
||||
|
@ -717,7 +744,7 @@ void TransfersContainer::getUnconfirmedTransactions(std::vector<Crypto::Hash>& t
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<TransactionSpentOutputInformation> TransfersContainer::getSpentOutputs() {
|
||||
std::vector<TransactionSpentOutputInformation> TransfersContainer::getSpentOutputs() const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
|
||||
std::vector<TransactionSpentOutputInformation> spentOutputs;
|
||||
|
@ -788,7 +815,7 @@ void TransfersContainer::load(std::istream& in) {
|
|||
bool TransfersContainer::isSpendTimeUnlocked(uint64_t unlockTime) const {
|
||||
if (unlockTime < m_currency.maxBlockHeight()) {
|
||||
// interpret as block index
|
||||
return m_currentHeight - 1 + m_currency.lockedTxAllowedDeltaBlocks() >= unlockTime;
|
||||
return m_currentHeight + m_currency.lockedTxAllowedDeltaBlocks() >= unlockTime;
|
||||
} else {
|
||||
//interpret as time
|
||||
uint64_t current_time = static_cast<uint64_t>(time(NULL));
|
||||
|
|
|
@ -161,14 +161,16 @@ public:
|
|||
bool advanceHeight(uint32_t height);
|
||||
|
||||
// ITransfersContainer
|
||||
virtual size_t transfersCount() override;
|
||||
virtual size_t transactionsCount() override;
|
||||
virtual uint64_t balance(uint32_t flags) override;
|
||||
virtual void getOutputs(std::vector<TransactionOutputInformation>& transfers, uint32_t flags) override;
|
||||
virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) override;
|
||||
virtual std::vector<TransactionOutputInformation> getTransactionOutputs(const Crypto::Hash& transactionHash, uint32_t flags) override;
|
||||
virtual void getUnconfirmedTransactions(std::vector<Crypto::Hash>& transactions) override;
|
||||
virtual std::vector<TransactionSpentOutputInformation> getSpentOutputs() override;
|
||||
virtual size_t transfersCount() const override;
|
||||
virtual size_t transactionsCount() const override;
|
||||
virtual uint64_t balance(uint32_t flags) const override;
|
||||
virtual void getOutputs(std::vector<TransactionOutputInformation>& transfers, uint32_t flags) const override;
|
||||
virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) const override;
|
||||
virtual std::vector<TransactionOutputInformation> getTransactionOutputs(const Crypto::Hash& transactionHash, uint32_t flags) const override;
|
||||
//only type flags are feasible for this function
|
||||
virtual std::vector<TransactionOutputInformation> getTransactionInputs(const Crypto::Hash& transactionHash, uint32_t flags) const override;
|
||||
virtual void getUnconfirmedTransactions(std::vector<Crypto::Hash>& transactions) const override;
|
||||
virtual std::vector<TransactionSpentOutputInformation> getSpentOutputs() const override;
|
||||
|
||||
// IStreamSerializable
|
||||
virtual void save(std::ostream& os) override;
|
||||
|
@ -280,7 +282,7 @@ private:
|
|||
uint32_t m_currentHeight; // current height is needed to check if a transfer is unlocked
|
||||
size_t m_transactionSpendableAge;
|
||||
const CryptoNote::Currency& m_currency;
|
||||
std::mutex m_mutex;
|
||||
mutable std::mutex m_mutex;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace CryptoNote {
|
|||
class IFusionManager {
|
||||
public:
|
||||
struct EstimateResult {
|
||||
size_t belowThresholdCount;
|
||||
size_t fusionReadyCount;
|
||||
size_t totalOutputCount;
|
||||
};
|
||||
|
||||
|
|
|
@ -42,7 +42,10 @@ enum WalletErrorCodes {
|
|||
TX_TRANSFER_IMPOSSIBLE,
|
||||
WRONG_VERSION,
|
||||
FEE_TOO_SMALL,
|
||||
KEY_GENERATION_ERROR
|
||||
KEY_GENERATION_ERROR,
|
||||
INDEX_OUT_OF_RANGE,
|
||||
ADDRESS_ALREADY_EXISTS,
|
||||
TRACKING_MODE
|
||||
};
|
||||
|
||||
// custom category:
|
||||
|
@ -60,24 +63,27 @@ public:
|
|||
|
||||
virtual std::string message(int ev) const {
|
||||
switch (ev) {
|
||||
case NOT_INITIALIZED: return "Object was not initialized";
|
||||
case WRONG_PASSWORD: return "The password is wrong";
|
||||
case ALREADY_INITIALIZED: return "The object is already initialized";
|
||||
case INTERNAL_WALLET_ERROR: return "Internal error occured";
|
||||
case MIXIN_COUNT_TOO_BIG: return "MixIn count is too big";
|
||||
case BAD_ADDRESS: return "Bad address";
|
||||
case NOT_INITIALIZED: return "Object was not initialized";
|
||||
case WRONG_PASSWORD: return "The password is wrong";
|
||||
case ALREADY_INITIALIZED: return "The object is already initialized";
|
||||
case INTERNAL_WALLET_ERROR: return "Internal error occured";
|
||||
case MIXIN_COUNT_TOO_BIG: return "MixIn count is too big";
|
||||
case BAD_ADDRESS: return "Bad address";
|
||||
case TRANSACTION_SIZE_TOO_BIG: return "Transaction size is too big";
|
||||
case WRONG_AMOUNT: return "Wrong amount";
|
||||
case SUM_OVERFLOW: return "Sum overflow";
|
||||
case ZERO_DESTINATION: return "The destination is empty";
|
||||
case TX_CANCEL_IMPOSSIBLE: return "Impossible to cancel transaction";
|
||||
case WRONG_STATE: return "The wallet is in wrong state (maybe loading or saving), try again later";
|
||||
case OPERATION_CANCELLED: return "The operation you've requested has been cancelled";
|
||||
case TX_TRANSFER_IMPOSSIBLE: return "Transaction transfer impossible";
|
||||
case WRONG_VERSION: return "Wrong version";
|
||||
case FEE_TOO_SMALL: return "Transaction fee is too small";
|
||||
case KEY_GENERATION_ERROR: return "Cannot generate new key";
|
||||
default: return "Unknown error";
|
||||
case WRONG_AMOUNT: return "Wrong amount";
|
||||
case SUM_OVERFLOW: return "Sum overflow";
|
||||
case ZERO_DESTINATION: return "The destination is empty";
|
||||
case TX_CANCEL_IMPOSSIBLE: return "Impossible to cancel transaction";
|
||||
case WRONG_STATE: return "The wallet is in wrong state (maybe loading or saving), try again later";
|
||||
case OPERATION_CANCELLED: return "The operation you've requested has been cancelled";
|
||||
case TX_TRANSFER_IMPOSSIBLE: return "Transaction transfer impossible";
|
||||
case WRONG_VERSION: return "Wrong version";
|
||||
case FEE_TOO_SMALL: return "Transaction fee is too small";
|
||||
case KEY_GENERATION_ERROR: return "Cannot generate new key";
|
||||
case INDEX_OUT_OF_RANGE: return "Index is out of range";
|
||||
case ADDRESS_ALREADY_EXISTS: return "Address already exists";
|
||||
case TRACKING_MODE: return "The wallet is in tracking mode";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <cassert>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
@ -48,10 +49,6 @@ using namespace CryptoNote;
|
|||
|
||||
namespace {
|
||||
|
||||
const uint32_t WALLET_SOFTLOCK_BLOCKS_COUNT = 1;
|
||||
|
||||
const uint64_t DUST_THRESHOLD = 10000;
|
||||
|
||||
void asyncRequestCompletion(System::Event& requestFinished) {
|
||||
requestFinished.set();
|
||||
}
|
||||
|
@ -152,18 +149,23 @@ CryptoNote::WalletEvent makeSyncCompletedEvent() {
|
|||
return event;
|
||||
}
|
||||
|
||||
size_t getTransactionSize(const ITransactionReader& transaction) {
|
||||
return transaction.getTransactionData().size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace CryptoNote {
|
||||
|
||||
WalletGreen::WalletGreen(System::Dispatcher& dispatcher, const Currency& currency, INode& node) :
|
||||
WalletGreen::WalletGreen(System::Dispatcher& dispatcher, const Currency& currency, INode& node, uint32_t transactionSoftLockTime) :
|
||||
m_dispatcher(dispatcher),
|
||||
m_currency(currency),
|
||||
m_node(node),
|
||||
m_blockchainSynchronizer(node, currency.genesisBlockHash()),
|
||||
m_synchronizer(currency, m_blockchainSynchronizer, node),
|
||||
m_eventOccured(m_dispatcher),
|
||||
m_readyEvent(m_dispatcher)
|
||||
m_readyEvent(m_dispatcher),
|
||||
m_transactionSoftLockTime(transactionSoftLockTime)
|
||||
{
|
||||
m_upperTransactionSizeLimit = m_currency.blockGrantedFullRewardZone() * 125 / 100 - m_currency.minerTxBlobReservedSize();
|
||||
m_readyEvent.set();
|
||||
|
@ -226,6 +228,7 @@ void WalletGreen::clearCaches() {
|
|||
m_change.clear();
|
||||
m_actualBalance = 0;
|
||||
m_pendingBalance = 0;
|
||||
m_fusionTxsCache.clear();
|
||||
}
|
||||
|
||||
void WalletGreen::initWithKeys(const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey, const std::string& password) {
|
||||
|
@ -272,7 +275,8 @@ void WalletGreen::unsafeSave(std::ostream& destination, bool saveDetails, bool s
|
|||
m_unlockTransactionsJob,
|
||||
m_change,
|
||||
m_transactions,
|
||||
m_transfers
|
||||
m_transfers,
|
||||
m_transactionSoftLockTime
|
||||
);
|
||||
|
||||
StdOutputStream output(destination);
|
||||
|
@ -312,7 +316,8 @@ void WalletGreen::unsafeLoad(std::istream& source, const std::string& password)
|
|||
m_unlockTransactionsJob,
|
||||
m_change,
|
||||
m_transactions,
|
||||
m_transfers
|
||||
m_transfers,
|
||||
m_transactionSoftLockTime
|
||||
);
|
||||
|
||||
StdInputStream inputStream(source);
|
||||
|
@ -387,6 +392,10 @@ std::string WalletGreen::createAddress(const Crypto::SecretKey& spendSecretKey)
|
|||
return doCreateAddress(spendPublicKey, spendSecretKey);
|
||||
}
|
||||
|
||||
std::string WalletGreen::createAddress(const Crypto::PublicKey& spendPublicKey) {
|
||||
return doCreateAddress(spendPublicKey, NULL_SECRET_KEY);
|
||||
}
|
||||
|
||||
std::string WalletGreen::doCreateAddress(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey) {
|
||||
throwIfNotInitialized();
|
||||
throwIfStopped();
|
||||
|
@ -395,15 +404,36 @@ std::string WalletGreen::doCreateAddress(const Crypto::PublicKey& spendPublicKey
|
|||
m_blockchainSynchronizer.stop();
|
||||
}
|
||||
|
||||
addWallet(spendPublicKey, spendSecretKey);
|
||||
std::string address = m_currency.accountAddressAsString({ spendPublicKey, m_viewPublicKey });
|
||||
try {
|
||||
addWallet(spendPublicKey, spendSecretKey);
|
||||
} catch (std::exception&) {
|
||||
if (m_walletsContainer.get<RandomAccessIndex>().size() != 0) {
|
||||
m_blockchainSynchronizer.start();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
m_blockchainSynchronizer.start();
|
||||
|
||||
return address;
|
||||
return m_currency.accountAddressAsString({ spendPublicKey, m_viewPublicKey });
|
||||
}
|
||||
|
||||
void WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey) {
|
||||
auto& index = m_walletsContainer.get<KeysIndex>();
|
||||
|
||||
auto trackingMode = getTrackingMode();
|
||||
|
||||
if ((trackingMode == WalletTrackingMode::TRACKING && spendSecretKey != NULL_SECRET_KEY) ||
|
||||
(trackingMode == WalletTrackingMode::NOT_TRACKING && spendSecretKey == NULL_SECRET_KEY)) {
|
||||
throw std::system_error(make_error_code(error::BAD_ADDRESS));
|
||||
}
|
||||
|
||||
auto insertIt = index.find(spendPublicKey);
|
||||
if (insertIt != index.end()) {
|
||||
throw std::system_error(make_error_code(error::ADDRESS_ALREADY_EXISTS));
|
||||
}
|
||||
|
||||
time_t creationTimestamp = time(nullptr);
|
||||
|
||||
AccountSubscription sub;
|
||||
|
@ -411,7 +441,7 @@ void WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, const Crypt
|
|||
sub.keys.address.spendPublicKey = spendPublicKey;
|
||||
sub.keys.viewSecretKey = m_viewSecretKey;
|
||||
sub.keys.spendSecretKey = spendSecretKey;
|
||||
sub.transactionSpendableAge = 10;
|
||||
sub.transactionSpendableAge = m_transactionSoftLockTime;
|
||||
sub.syncStart.height = 0;
|
||||
sub.syncStart.timestamp = static_cast<uint64_t>(creationTimestamp) - (60 * 60 * 24);
|
||||
|
||||
|
@ -425,7 +455,7 @@ void WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, const Crypt
|
|||
wallet.creationTimestamp = creationTimestamp;
|
||||
trSubscription.addObserver(this);
|
||||
|
||||
m_walletsContainer.get<RandomAccessIndex>().push_back(std::move(wallet));
|
||||
index.insert(insertIt, std::move(wallet));
|
||||
}
|
||||
|
||||
void WalletGreen::deleteAddress(const std::string& address) {
|
||||
|
@ -495,7 +525,11 @@ WalletTransaction WalletGreen::getTransaction(size_t transactionIndex) const {
|
|||
throwIfNotInitialized();
|
||||
throwIfStopped();
|
||||
|
||||
return m_transactions.get<RandomAccessIndex>().at(transactionIndex);
|
||||
if (m_transactions.size() <= transactionIndex) {
|
||||
throw std::system_error(make_error_code(CryptoNote::error::INDEX_OUT_OF_RANGE));
|
||||
}
|
||||
|
||||
return m_transactions.get<RandomAccessIndex>()[transactionIndex];
|
||||
}
|
||||
|
||||
size_t WalletGreen::getTransactionTransferCount(size_t transactionIndex) const {
|
||||
|
@ -554,6 +588,7 @@ size_t WalletGreen::transfer(
|
|||
|
||||
throwIfNotInitialized();
|
||||
throwIfStopped();
|
||||
throwIfTrackingMode();
|
||||
|
||||
return doTransfer(pickWalletsWithMoney(), destinations, fee, mixIn, extra, unlockTimestamp);
|
||||
}
|
||||
|
@ -580,6 +615,7 @@ size_t WalletGreen::transfer(
|
|||
|
||||
throwIfNotInitialized();
|
||||
throwIfStopped();
|
||||
throwIfTrackingMode();
|
||||
|
||||
WalletOuts wallet = pickWallet(sourceAddress);
|
||||
std::vector<WalletOuts> wallets;
|
||||
|
@ -610,7 +646,7 @@ size_t WalletGreen::doTransfer(std::vector<WalletOuts>&& wallets,
|
|||
uint64_t neededMoney = countNeededMoney(destinations, fee);
|
||||
|
||||
std::vector<OutputToTransfer> selectedTransfers;
|
||||
uint64_t foundMoney = selectTransfers(neededMoney, mixIn == 0, DUST_THRESHOLD, std::move(wallets), selectedTransfers);
|
||||
uint64_t foundMoney = selectTransfers(neededMoney, mixIn == 0, m_currency.defaultDustThreshold(), std::move(wallets), selectedTransfers);
|
||||
|
||||
if (foundMoney < neededMoney) {
|
||||
throw std::system_error(make_error_code(error::WRONG_AMOUNT), "Not enough money");
|
||||
|
@ -631,16 +667,20 @@ size_t WalletGreen::doTransfer(std::vector<WalletOuts>&& wallets,
|
|||
changeDestination.amount = foundMoney - neededMoney;
|
||||
|
||||
std::vector<ReceiverAmounts> decomposedOutputs;
|
||||
splitDestinations(destinations, changeDestination, DUST_THRESHOLD, m_currency, decomposedOutputs);
|
||||
splitDestinations(destinations, changeDestination, m_currency.defaultDustThreshold(), m_currency, decomposedOutputs);
|
||||
|
||||
std::unique_ptr<ITransaction> tx = makeTransaction(decomposedOutputs, keysInfo, extra, unlockTimestamp);
|
||||
|
||||
size_t txId = insertOutgoingTransaction(tx->getTransactionHash(), -static_cast<int64_t>(neededMoney), fee, tx->getExtra(), unlockTimestamp);
|
||||
pushBackOutgoingTransfers(txId, destinations);
|
||||
m_fusionTxsCache.emplace(txId, false);
|
||||
|
||||
markOutputsSpent(tx->getTransactionHash(), selectedTransfers);
|
||||
|
||||
try {
|
||||
sendTransaction(tx.get());
|
||||
} catch (std::exception&) {
|
||||
deleteSpentOutputs(tx->getTransactionHash());
|
||||
pushEvent(makeTransactionCreatedEvent(txId));
|
||||
throw;
|
||||
}
|
||||
|
@ -650,7 +690,6 @@ size_t WalletGreen::doTransfer(std::vector<WalletOuts>&& wallets,
|
|||
m_transactions.get<RandomAccessIndex>().modify(txIt,
|
||||
[] (WalletTransaction& tx) { tx.state = WalletTransactionState::SUCCEEDED; });
|
||||
|
||||
markOutputsSpent(tx->getTransactionHash(), selectedTransfers);
|
||||
m_change[tx->getTransactionHash()] = changeDestination.amount;
|
||||
updateUsedWalletsBalances(selectedTransfers);
|
||||
|
||||
|
@ -677,6 +716,7 @@ size_t WalletGreen::insertOutgoingTransaction(const Hash& transactionHash, int64
|
|||
insertTx.hash = transactionHash;
|
||||
insertTx.totalAmount = totalAmount;
|
||||
insertTx.timestamp = 0; //0 until included in a block
|
||||
insertTx.isBase = false;
|
||||
|
||||
size_t txId = m_transactions.get<RandomAccessIndex>().size();
|
||||
m_transactions.get<RandomAccessIndex>().push_back(std::move(insertTx));
|
||||
|
@ -690,18 +730,38 @@ bool WalletGreen::transactionExists(const Hash& hash) {
|
|||
return it != hashIndex.end();
|
||||
}
|
||||
|
||||
void WalletGreen::updateTransactionHeight(const Hash& hash, uint32_t blockHeight) {
|
||||
bool WalletGreen::updateWalletTransactionInfo(const Hash& hash, const CryptoNote::TransactionInformation& info) {
|
||||
auto& hashIndex = m_transactions.get<TransactionIndex>();
|
||||
|
||||
bool updated = false;
|
||||
auto it = hashIndex.find(hash);
|
||||
if (it != hashIndex.end()) {
|
||||
bool r = hashIndex.modify(it, [&blockHeight] (WalletTransaction& transaction) {
|
||||
transaction.blockHeight = blockHeight;
|
||||
//transaction may be deleted first than added again
|
||||
transaction.state = WalletTransactionState::SUCCEEDED;
|
||||
bool r = hashIndex.modify(it, [&info, &updated] (WalletTransaction& transaction) {
|
||||
if (transaction.blockHeight != info.blockHeight) {
|
||||
transaction.blockHeight = info.blockHeight;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (transaction.timestamp != info.timestamp) {
|
||||
transaction.timestamp = info.timestamp;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (transaction.state != WalletTransactionState::SUCCEEDED) {
|
||||
//transaction may be deleted first then added again
|
||||
transaction.state = WalletTransactionState::SUCCEEDED;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
bool isBase = info.totalAmountIn == 0;
|
||||
if (transaction.isBase != isBase) {
|
||||
transaction.isBase = isBase;
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
|
||||
assert(r);
|
||||
return;
|
||||
return updated;
|
||||
}
|
||||
|
||||
throw std::system_error(make_error_code(std::errc::invalid_argument));
|
||||
|
@ -720,6 +780,7 @@ size_t WalletGreen::insertIncomingTransaction(const TransactionInformation& info
|
|||
tx.extra.assign(reinterpret_cast<const char*>(info.extra.data()), info.extra.size());
|
||||
tx.totalAmount = txBalance;
|
||||
tx.creationTime = info.timestamp;
|
||||
tx.isBase = info.totalAmountIn == 0;
|
||||
|
||||
index.push_back(std::move(tx));
|
||||
return index.size() - 1;
|
||||
|
@ -886,7 +947,7 @@ uint64_t WalletGreen::selectTransfers(
|
|||
return foundMoney;
|
||||
};
|
||||
|
||||
std::vector<WalletGreen::WalletOuts> WalletGreen::pickWalletsWithMoney() {
|
||||
std::vector<WalletGreen::WalletOuts> WalletGreen::pickWalletsWithMoney() const {
|
||||
auto& walletsIndex = m_walletsContainer.get<RandomAccessIndex>();
|
||||
|
||||
std::vector<WalletOuts> walletOuts;
|
||||
|
@ -986,6 +1047,7 @@ void WalletGreen::prepareInputs(
|
|||
keyInfo.realOutput.transactionIndex = static_cast<size_t>(insertedIn - keyInfo.outputs.begin());
|
||||
keyInfo.realOutput.outputInTransaction = input.out.outputInTransaction;
|
||||
|
||||
//Important! outputs in selectedTransfers and in keysInfo must have the same order!
|
||||
InputInfo inputInfo;
|
||||
inputInfo.keyInfo = std::move(keyInfo);
|
||||
inputInfo.walletRecord = input.wallet;
|
||||
|
@ -1028,23 +1090,27 @@ void WalletGreen::throwIfNotInitialized() const {
|
|||
void WalletGreen::onError(ITransfersSubscription* object, uint32_t height, std::error_code ec) {
|
||||
}
|
||||
|
||||
void WalletGreen::synchronizationProgressUpdated(uint32_t current, uint32_t total) {
|
||||
m_dispatcher.remoteSpawn( [current, total, this] () { onSynchronizationProgressUpdated(current, total); } );
|
||||
void WalletGreen::synchronizationProgressUpdated(uint32_t processedBlockCount, uint32_t totalBlockCount) {
|
||||
m_dispatcher.remoteSpawn( [processedBlockCount, totalBlockCount, this] () { onSynchronizationProgressUpdated(processedBlockCount, totalBlockCount); } );
|
||||
}
|
||||
|
||||
void WalletGreen::synchronizationCompleted(std::error_code result) {
|
||||
m_dispatcher.remoteSpawn([this] () { onSynchronizationCompleted(); } );
|
||||
}
|
||||
|
||||
void WalletGreen::onSynchronizationProgressUpdated(uint32_t current, uint32_t total) {
|
||||
void WalletGreen::onSynchronizationProgressUpdated(uint32_t processedBlockCount, uint32_t totalBlockCount) {
|
||||
assert(processedBlockCount > 0);
|
||||
|
||||
System::EventLock lk(m_readyEvent);
|
||||
|
||||
if (m_state == WalletState::NOT_INITIALIZED) {
|
||||
return;
|
||||
}
|
||||
|
||||
pushEvent(makeSyncProgressUpdatedEvent(current, total));
|
||||
unlockBalances(current);
|
||||
pushEvent(makeSyncProgressUpdatedEvent(processedBlockCount, totalBlockCount));
|
||||
|
||||
uint32_t currentHeight = processedBlockCount - 1;
|
||||
unlockBalances(currentHeight);
|
||||
}
|
||||
|
||||
void WalletGreen::onSynchronizationCompleted() {
|
||||
|
@ -1061,12 +1127,14 @@ void WalletGreen::unlockBalances(uint32_t height) {
|
|||
auto& index = m_unlockTransactionsJob.get<BlockHeightIndex>();
|
||||
auto upper = index.upper_bound(height);
|
||||
|
||||
for (auto it = index.begin(); it != upper; ++it) {
|
||||
updateBalance(it->container);
|
||||
}
|
||||
if (index.begin() != upper) {
|
||||
for (auto it = index.begin(); it != upper; ++it) {
|
||||
updateBalance(it->container);
|
||||
}
|
||||
|
||||
index.erase(index.begin(), upper);
|
||||
pushEvent(makeMoneyUnlockedEvent());
|
||||
index.erase(index.begin(), upper);
|
||||
pushEvent(makeMoneyUnlockedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
void WalletGreen::onTransactionUpdated(ITransfersSubscription* object, const Hash& transactionHash) {
|
||||
|
@ -1089,29 +1157,30 @@ void WalletGreen::transactionUpdated(ITransfersSubscription* object, const Hash&
|
|||
bool found = container->getTransactionInformation(transactionHash, info, txBalance);
|
||||
assert(found);
|
||||
|
||||
WalletEvent event;
|
||||
|
||||
if (transactionExists(info.transactionHash)) {
|
||||
updateTransactionHeight(info.transactionHash, info.blockHeight);
|
||||
bool updated = updateWalletTransactionInfo(info.transactionHash, info);
|
||||
|
||||
auto id = getTransactionId(info.transactionHash);
|
||||
event = makeTransactionUpdatedEvent(id);
|
||||
|
||||
if (updated) {
|
||||
pushEvent(makeTransactionUpdatedEvent(id));
|
||||
}
|
||||
} else {
|
||||
auto id = insertIncomingTransaction(info, txBalance);
|
||||
insertIncomingTransfer(id, m_currency.accountAddressAsString({ getWalletRecord(container).spendPublicKey, m_viewPublicKey }), txBalance);
|
||||
m_fusionTxsCache.emplace(id, isFusionTransaction(m_transactions.get<RandomAccessIndex>()[id]));
|
||||
|
||||
event = makeTransactionCreatedEvent(id);
|
||||
pushEvent(makeTransactionCreatedEvent(id));
|
||||
}
|
||||
|
||||
m_change.erase(transactionHash);
|
||||
|
||||
if (info.blockHeight != CryptoNote::WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) {
|
||||
//TODO: make proper calculation of unlock height
|
||||
uint32_t height = info.blockHeight + static_cast<uint32_t>(info.unlockTime) + WALLET_SOFTLOCK_BLOCKS_COUNT + 1;
|
||||
m_change.erase(transactionHash);
|
||||
insertUnlockTransactionJob(transactionHash, height, container);
|
||||
uint32_t unlockHeight = std::max(info.blockHeight + m_transactionSoftLockTime, static_cast<uint32_t>(info.unlockTime));
|
||||
insertUnlockTransactionJob(transactionHash, unlockHeight, container);
|
||||
}
|
||||
|
||||
updateBalance(container);
|
||||
pushEvent(event);
|
||||
}
|
||||
|
||||
void WalletGreen::pushEvent(const WalletEvent& event) {
|
||||
|
@ -1296,19 +1365,288 @@ void WalletGreen::throwIfStopped() const {
|
|||
}
|
||||
}
|
||||
|
||||
void WalletGreen::throwIfTrackingMode() const {
|
||||
if (getTrackingMode() == WalletTrackingMode::TRACKING) {
|
||||
throw std::system_error(make_error_code(error::TRACKING_MODE));
|
||||
}
|
||||
}
|
||||
|
||||
WalletGreen::WalletTrackingMode WalletGreen::getTrackingMode() const {
|
||||
if (m_walletsContainer.get<RandomAccessIndex>().empty()) {
|
||||
return WalletTrackingMode::NO_ADDRESSES;
|
||||
}
|
||||
|
||||
return m_walletsContainer.get<RandomAccessIndex>().begin()->spendSecretKey == NULL_SECRET_KEY ?
|
||||
WalletTrackingMode::TRACKING : WalletTrackingMode::NOT_TRACKING;
|
||||
}
|
||||
|
||||
size_t WalletGreen::createFusionTransaction(uint64_t threshold, uint64_t mixin) {
|
||||
// TODO NOT IMPLEMENTED
|
||||
throw std::runtime_error("WalletGreen::createFusionTransaction not implemented.");
|
||||
System::EventLock lk(m_readyEvent);
|
||||
|
||||
throwIfNotInitialized();
|
||||
throwIfStopped();
|
||||
|
||||
//TODO: check if wallet is not in tracking mode
|
||||
const size_t MAX_FUSION_OUTPUT_COUNT = 4;
|
||||
|
||||
if (threshold <= m_currency.defaultDustThreshold()) {
|
||||
throw std::runtime_error("Threshold must be greater than " + std::to_string(m_currency.defaultDustThreshold()));
|
||||
}
|
||||
|
||||
if (m_walletsContainer.get<RandomAccessIndex>().size() == 0) {
|
||||
throw std::runtime_error("You must have at least one address");
|
||||
}
|
||||
|
||||
size_t estimatedFusionInputsCount = m_currency.getApproximateMaximumInputCount(m_currency.fusionTxMaxSize(), MAX_FUSION_OUTPUT_COUNT, mixin);
|
||||
if (estimatedFusionInputsCount < m_currency.fusionTxMinInputCount()) {
|
||||
throw std::system_error(make_error_code(error::MIXIN_COUNT_TOO_BIG));
|
||||
}
|
||||
|
||||
std::vector<OutputToTransfer> fusionInputs = pickRandomFusionInputs(threshold, m_currency.fusionTxMinInputCount(), estimatedFusionInputsCount);
|
||||
if (fusionInputs.size() < m_currency.fusionTxMinInputCount()) {
|
||||
//nothing to optimize
|
||||
return WALLET_INVALID_TRANSACTION_ID;
|
||||
}
|
||||
|
||||
typedef CryptoNote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount outs_for_amount;
|
||||
std::vector<outs_for_amount> mixinResult;
|
||||
if (mixin != 0) {
|
||||
requestMixinOuts(fusionInputs, mixin, mixinResult);
|
||||
}
|
||||
|
||||
std::vector<InputInfo> keysInfo;
|
||||
prepareInputs(fusionInputs, mixinResult, mixin, keysInfo);
|
||||
|
||||
std::unique_ptr<ITransaction> fusionTransaction;
|
||||
size_t transactionSize;
|
||||
int round = 0;
|
||||
uint64_t transactionAmount;
|
||||
do {
|
||||
if (round != 0) {
|
||||
fusionInputs.pop_back();
|
||||
keysInfo.pop_back();
|
||||
}
|
||||
|
||||
uint64_t inputsAmount = std::accumulate(fusionInputs.begin(), fusionInputs.end(), static_cast<uint64_t>(0), [] (uint64_t amount, const OutputToTransfer& input) {
|
||||
return amount + input.out.amount;
|
||||
});
|
||||
|
||||
transactionAmount = inputsAmount;
|
||||
|
||||
ReceiverAmounts decomposedOutputs = decomposeFusionOutputs(inputsAmount);
|
||||
assert(decomposedOutputs.amounts.size() <= MAX_FUSION_OUTPUT_COUNT);
|
||||
|
||||
fusionTransaction = makeTransaction(std::vector<ReceiverAmounts>{decomposedOutputs}, keysInfo, "", 0);
|
||||
|
||||
transactionSize = getTransactionSize(*fusionTransaction);
|
||||
++round;
|
||||
} while (transactionSize > m_currency.fusionTxMaxSize() && fusionInputs.size() >= m_currency.fusionTxMinInputCount());
|
||||
|
||||
if (fusionInputs.size() < m_currency.fusionTxMinInputCount()) {
|
||||
throw std::runtime_error("Unable to create fusion transaction");
|
||||
}
|
||||
|
||||
size_t transactionId = insertOutgoingTransaction(fusionTransaction->getTransactionHash(), 0, 0, fusionTransaction->getExtra(), 0);
|
||||
m_fusionTxsCache.emplace(transactionId, true);
|
||||
|
||||
WalletTransfer destination = {m_currency.accountAddressAsString({m_walletsContainer.get<RandomAccessIndex>().begin()->spendPublicKey,
|
||||
m_viewPublicKey }), 0};
|
||||
pushBackOutgoingTransfers(transactionId, std::vector<WalletTransfer> {destination});
|
||||
|
||||
markOutputsSpent(fusionTransaction->getTransactionHash(), fusionInputs);
|
||||
|
||||
try {
|
||||
sendTransaction(fusionTransaction.get());
|
||||
} catch (std::exception&) {
|
||||
deleteSpentOutputs(fusionTransaction->getTransactionHash());
|
||||
pushEvent(makeTransactionCreatedEvent(transactionId));
|
||||
throw;
|
||||
}
|
||||
|
||||
auto txIt = m_transactions.get<RandomAccessIndex>().begin();
|
||||
std::advance(txIt, transactionId);
|
||||
m_transactions.get<RandomAccessIndex>().modify(txIt,
|
||||
[] (WalletTransaction& tx) { tx.state = WalletTransactionState::SUCCEEDED; });
|
||||
|
||||
m_change[fusionTransaction->getTransactionHash()] = transactionAmount;
|
||||
|
||||
updateUsedWalletsBalances(fusionInputs);
|
||||
|
||||
pushEvent(makeTransactionCreatedEvent(transactionId));
|
||||
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
WalletGreen::ReceiverAmounts WalletGreen::decomposeFusionOutputs(uint64_t inputsAmount) {
|
||||
assert(m_walletsContainer.get<RandomAccessIndex>().size() > 0);
|
||||
|
||||
WalletGreen::ReceiverAmounts outputs;
|
||||
outputs.receiver = {m_walletsContainer.get<RandomAccessIndex>().begin()->spendPublicKey, m_viewPublicKey};
|
||||
|
||||
decomposeAmount(inputsAmount, 0, outputs.amounts);
|
||||
std::sort(outputs.amounts.begin(), outputs.amounts.end());
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
bool WalletGreen::isFusionTransaction(size_t transactionId) const {
|
||||
// TODO NOT IMPLEMENTED
|
||||
throw std::runtime_error("WalletGreen::isFusionTransaction not implemented.");
|
||||
throwIfNotInitialized();
|
||||
throwIfStopped();
|
||||
|
||||
if (m_transactions.size() <= transactionId) {
|
||||
throw std::system_error(make_error_code(CryptoNote::error::INDEX_OUT_OF_RANGE));
|
||||
}
|
||||
|
||||
auto isFusionIter = m_fusionTxsCache.find(transactionId);
|
||||
if (isFusionIter != m_fusionTxsCache.end()) {
|
||||
return isFusionIter->second;
|
||||
}
|
||||
|
||||
bool result = isFusionTransaction(m_transactions.get<RandomAccessIndex>()[transactionId]);
|
||||
m_fusionTxsCache.emplace(transactionId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WalletGreen::isFusionTransaction(const WalletTransaction& walletTx) const {
|
||||
if (walletTx.fee != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t inputsSum = 0;
|
||||
uint64_t outputsSum = 0;
|
||||
std::vector<uint64_t> outputsAmounts;
|
||||
std::vector<uint64_t> inputsAmounts;
|
||||
TransactionInformation txInfo;
|
||||
bool gotTx = false;
|
||||
const auto& walletsIndex = m_walletsContainer.get<RandomAccessIndex>();
|
||||
for (const WalletRecord& wallet : walletsIndex) {
|
||||
for (const TransactionOutputInformation& output : wallet.container->getTransactionOutputs(walletTx.hash, ITransfersContainer::IncludeTypeKey | ITransfersContainer::IncludeStateAll)) {
|
||||
if (outputsAmounts.size() <= output.outputInTransaction) {
|
||||
outputsAmounts.resize(output.outputInTransaction + 1, 0);
|
||||
}
|
||||
|
||||
assert(output.amount != 0);
|
||||
assert(outputsAmounts[output.outputInTransaction] == 0);
|
||||
outputsAmounts[output.outputInTransaction] = output.amount;
|
||||
outputsSum += output.amount;
|
||||
}
|
||||
|
||||
for (const TransactionOutputInformation& input : wallet.container->getTransactionInputs(walletTx.hash, ITransfersContainer::IncludeTypeKey)) {
|
||||
inputsSum += input.amount;
|
||||
inputsAmounts.push_back(input.amount);
|
||||
}
|
||||
|
||||
if (!gotTx) {
|
||||
int64_t ignore;
|
||||
gotTx = wallet.container->getTransactionInformation(walletTx.hash, txInfo, ignore);
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotTx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outputsSum != inputsSum || outputsSum != txInfo.totalAmountOut || inputsSum != txInfo.totalAmountIn) {
|
||||
return false;
|
||||
} else {
|
||||
return m_currency.isFusionTransaction(inputsAmounts, outputsAmounts, 0); //size = 0 here because can't get real size of tx in wallet.
|
||||
}
|
||||
}
|
||||
|
||||
IFusionManager::EstimateResult WalletGreen::estimate(uint64_t threshold) const {
|
||||
// TODO NOT IMPLEMENTED
|
||||
throw std::runtime_error("WalletGreen::estimate not implemented.");
|
||||
throwIfNotInitialized();
|
||||
throwIfStopped();
|
||||
|
||||
IFusionManager::EstimateResult result{0, 0};
|
||||
auto walletOuts = pickWalletsWithMoney();
|
||||
std::array<size_t, std::numeric_limits<uint64_t>::digits10 + 1> bucketSizes;
|
||||
bucketSizes.fill(0);
|
||||
for (size_t walletIndex = 0; walletIndex < walletOuts.size(); ++walletIndex) {
|
||||
for (auto& out : walletOuts[walletIndex].outs) {
|
||||
uint8_t powerOfTen = 0;
|
||||
if (m_currency.isAmountApplicableInFusionTransactionInput(out.amount, threshold, powerOfTen) && !isOutputUsed(out)) {
|
||||
assert(powerOfTen < std::numeric_limits<uint64_t>::digits10 + 1);
|
||||
bucketSizes[powerOfTen]++;
|
||||
}
|
||||
}
|
||||
|
||||
result.totalOutputCount += walletOuts[walletIndex].outs.size();
|
||||
}
|
||||
|
||||
for (auto bucketSize : bucketSizes) {
|
||||
if (bucketSize >= m_currency.fusionTxMinInputCount()) {
|
||||
result.fusionReadyCount += bucketSize;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<WalletGreen::OutputToTransfer> WalletGreen::pickRandomFusionInputs(uint64_t threshold, size_t minInputCount, size_t maxInputCount) {
|
||||
std::vector<WalletGreen::OutputToTransfer> allFusionReadyOuts;
|
||||
auto walletOuts = pickWalletsWithMoney();
|
||||
std::array<size_t, std::numeric_limits<uint64_t>::digits10 + 1> bucketSizes;
|
||||
bucketSizes.fill(0);
|
||||
for (size_t walletIndex = 0; walletIndex < walletOuts.size(); ++walletIndex) {
|
||||
for (auto& out : walletOuts[walletIndex].outs) {
|
||||
uint8_t powerOfTen = 0;
|
||||
if (m_currency.isAmountApplicableInFusionTransactionInput(out.amount, threshold, powerOfTen) && !isOutputUsed(out)) {
|
||||
allFusionReadyOuts.push_back({std::move(out), walletOuts[walletIndex].wallet});
|
||||
assert(powerOfTen < std::numeric_limits<uint64_t>::digits10 + 1);
|
||||
bucketSizes[powerOfTen]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//now, pick the bucket
|
||||
std::vector<uint8_t> bucketNumbers(bucketSizes.size());
|
||||
std::iota(bucketNumbers.begin(), bucketNumbers.end(), 0);
|
||||
std::shuffle(bucketNumbers.begin(), bucketNumbers.end(), std::default_random_engine{Crypto::rand<std::default_random_engine::result_type>()});
|
||||
size_t bucketNumberIndex = 0;
|
||||
for (; bucketNumberIndex < bucketNumbers.size(); ++bucketNumberIndex) {
|
||||
if (bucketSizes[bucketNumbers[bucketNumberIndex]] >= minInputCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bucketNumberIndex == bucketNumbers.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t selectedBucket = bucketNumbers[bucketNumberIndex];
|
||||
assert(selectedBucket < std::numeric_limits<uint64_t>::digits10 + 1);
|
||||
assert(bucketSizes[selectedBucket] >= minInputCount);
|
||||
uint64_t lowerBound = 1;
|
||||
for (size_t i = 0; i < selectedBucket; ++i) {
|
||||
lowerBound *= 10;
|
||||
}
|
||||
|
||||
uint64_t upperBound = selectedBucket == std::numeric_limits<uint64_t>::digits10 ? UINT64_MAX : lowerBound * 10;
|
||||
std::vector<WalletGreen::OutputToTransfer> selectedOuts;
|
||||
selectedOuts.reserve(bucketSizes[selectedBucket]);
|
||||
for (size_t outIndex = 0; outIndex < allFusionReadyOuts.size(); ++outIndex) {
|
||||
if (allFusionReadyOuts[outIndex].out.amount >= lowerBound && allFusionReadyOuts[outIndex].out.amount < upperBound) {
|
||||
selectedOuts.push_back(std::move(allFusionReadyOuts[outIndex]));
|
||||
}
|
||||
}
|
||||
|
||||
assert(selectedOuts.size() >= minInputCount);
|
||||
|
||||
auto outputsSortingFunction = [](const OutputToTransfer& l, const OutputToTransfer& r) { return l.out.amount < r.out.amount; };
|
||||
if (selectedOuts.size() <= maxInputCount) {
|
||||
std::sort(selectedOuts.begin(), selectedOuts.end(), outputsSortingFunction);
|
||||
return selectedOuts;
|
||||
}
|
||||
|
||||
ShuffleGenerator<size_t, Crypto::random_engine<size_t>> generator(selectedOuts.size());
|
||||
std::vector<WalletGreen::OutputToTransfer> trimmedSelectedOuts;
|
||||
trimmedSelectedOuts.reserve(maxInputCount);
|
||||
for (size_t i = 0; i < maxInputCount; ++i) {
|
||||
trimmedSelectedOuts.push_back(std::move(selectedOuts[generator()]));
|
||||
}
|
||||
|
||||
std::sort(trimmedSelectedOuts.begin(), trimmedSelectedOuts.end(), outputsSortingFunction);
|
||||
return trimmedSelectedOuts;
|
||||
}
|
||||
|
||||
} //namespace CryptoNote
|
||||
|
|
|
@ -36,7 +36,7 @@ class WalletGreen : public IWallet,
|
|||
IBlockchainSynchronizerObserver,
|
||||
IFusionManager {
|
||||
public:
|
||||
WalletGreen(System::Dispatcher& dispatcher, const Currency& currency, INode& node);
|
||||
WalletGreen(System::Dispatcher& dispatcher, const Currency& currency, INode& node, uint32_t transactionSoftLockTime = 1);
|
||||
virtual ~WalletGreen();
|
||||
|
||||
virtual void initialize(const std::string& password) override;
|
||||
|
@ -53,6 +53,7 @@ public:
|
|||
virtual KeyPair getViewKey() const override;
|
||||
virtual std::string createAddress() override;
|
||||
virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) override;
|
||||
virtual std::string createAddress(const Crypto::PublicKey& spendPublicKey) override;
|
||||
virtual void deleteAddress(const std::string& address) override;
|
||||
|
||||
virtual uint64_t getActualBalance() const override;
|
||||
|
@ -81,6 +82,7 @@ public:
|
|||
protected:
|
||||
void throwIfNotInitialized() const;
|
||||
void throwIfStopped() const;
|
||||
void throwIfTrackingMode() const;
|
||||
void doShutdown();
|
||||
void clearCaches();
|
||||
void initWithKeys(const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey, const std::string& password);
|
||||
|
@ -109,16 +111,18 @@ protected:
|
|||
|
||||
virtual void onError(ITransfersSubscription* object, uint32_t height, std::error_code ec) override;
|
||||
virtual void onTransactionUpdated(ITransfersSubscription* object, const Crypto::Hash& transactionHash) override;
|
||||
void transactionUpdated(ITransfersSubscription* object, const Crypto::Hash& transactionHash);
|
||||
|
||||
virtual void onTransactionDeleted(ITransfersSubscription* object, const Crypto::Hash& transactionHash) override;
|
||||
virtual void synchronizationProgressUpdated(uint32_t current, uint32_t total) override;
|
||||
void transactionDeleted(ITransfersSubscription* object, const Crypto::Hash& transactionHash);
|
||||
|
||||
virtual void synchronizationProgressUpdated(uint32_t processedBlockCount, uint32_t totalBlockCount) override;
|
||||
virtual void synchronizationCompleted(std::error_code result) override;
|
||||
|
||||
void transactionUpdated(ITransfersSubscription* object, const Crypto::Hash& transactionHash);
|
||||
void transactionDeleted(ITransfersSubscription* object, const Crypto::Hash& transactionHash);
|
||||
void onSynchronizationProgressUpdated(uint32_t current, uint32_t total);
|
||||
void onSynchronizationProgressUpdated(uint32_t processedBlockCount, uint32_t totalBlockCount);
|
||||
void onSynchronizationCompleted();
|
||||
|
||||
std::vector<WalletOuts> pickWalletsWithMoney();
|
||||
std::vector<WalletOuts> pickWalletsWithMoney() const;
|
||||
WalletOuts pickWallet(const std::string& address);
|
||||
|
||||
void updateBalance(CryptoNote::ITransfersContainer* container);
|
||||
|
@ -138,6 +142,7 @@ protected:
|
|||
AccountKeys makeAccountKeys(const WalletRecord& wallet) const;
|
||||
size_t getTransactionId(const Crypto::Hash& transactionHash) const;
|
||||
void pushEvent(const WalletEvent& event);
|
||||
bool isFusionTransaction(const WalletTransaction& walletTx) const;
|
||||
|
||||
size_t doTransfer(std::vector<WalletOuts>&& wallets,
|
||||
const std::vector<WalletTransfer>& destinations,
|
||||
|
@ -171,7 +176,7 @@ protected:
|
|||
|
||||
size_t insertOutgoingTransaction(const Crypto::Hash& transactionHash, int64_t totalAmount, uint64_t fee, const BinaryArray& extra, uint64_t unlockTimestamp);
|
||||
bool transactionExists(const Crypto::Hash& hash);
|
||||
void updateTransactionHeight(const Crypto::Hash& hash, uint32_t blockHeight);
|
||||
bool updateWalletTransactionInfo(const Crypto::Hash& hash, const CryptoNote::TransactionInformation& info);
|
||||
size_t insertIncomingTransaction(const TransactionInformation& info, int64_t txBalance);
|
||||
void insertIncomingTransfer(size_t txId, const std::string& address, int64_t amount);
|
||||
void pushBackOutgoingTransfers(size_t txId, const std::vector<WalletTransfer> &destinations);
|
||||
|
@ -181,12 +186,21 @@ protected:
|
|||
void unsafeLoad(std::istream& source, const std::string& password);
|
||||
void unsafeSave(std::ostream& destination, bool saveDetails, bool saveCache);
|
||||
|
||||
|
||||
std::vector<OutputToTransfer> pickRandomFusionInputs(uint64_t threshold, size_t minInputCount, size_t maxInputCount);
|
||||
ReceiverAmounts decomposeFusionOutputs(uint64_t inputsAmount);
|
||||
enum class WalletState {
|
||||
INITIALIZED,
|
||||
NOT_INITIALIZED
|
||||
};
|
||||
|
||||
enum class WalletTrackingMode {
|
||||
TRACKING,
|
||||
NOT_TRACKING,
|
||||
NO_ADDRESSES
|
||||
};
|
||||
|
||||
WalletTrackingMode getTrackingMode() const;
|
||||
|
||||
std::pair<WalletTransfers::const_iterator, WalletTransfers::const_iterator> getTransactionTransfers(size_t transactionIndex) const;
|
||||
|
||||
System::Dispatcher& m_dispatcher;
|
||||
|
@ -200,6 +214,7 @@ protected:
|
|||
WalletTransactions m_transactions;
|
||||
WalletTransfers m_transfers; //sorted
|
||||
TransactionChanges m_change;
|
||||
mutable std::unordered_map<size_t, bool> m_fusionTxsCache; // txIndex -> isFusion
|
||||
|
||||
BlockchainSynchronizer m_blockchainSynchronizer;
|
||||
TransfersSyncronizer m_synchronizer;
|
||||
|
@ -219,6 +234,7 @@ protected:
|
|||
uint64_t m_pendingBalance = 0;
|
||||
|
||||
uint64_t m_upperTransactionSizeLimit;
|
||||
uint32_t m_transactionSoftLockTime;
|
||||
};
|
||||
|
||||
} //namespace CryptoNote
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "CryptoNoteProtocol/CryptoNoteProtocolDefinitions.h"
|
||||
#include "CryptoNoteCore/CryptoNoteBasic.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "Rpc/CoreRpcServerCommandsDefinitions.h"
|
||||
#include "WalletRpcServerErrorCodes.h"
|
||||
|
||||
namespace Tools
|
||||
|
@ -31,13 +32,9 @@ using CryptoNote::ISerializer;
|
|||
#define WALLET_RPC_STATUS_OK "OK"
|
||||
#define WALLET_RPC_STATUS_BUSY "BUSY"
|
||||
|
||||
struct EMPTY_STRUCT {
|
||||
void serialize(ISerializer& s) {}
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_GET_BALANCE
|
||||
{
|
||||
typedef EMPTY_STRUCT request;
|
||||
typedef CryptoNote::EMPTY_STRUCT request;
|
||||
|
||||
struct response
|
||||
{
|
||||
|
@ -93,8 +90,8 @@ using CryptoNote::ISerializer;
|
|||
|
||||
struct COMMAND_RPC_STORE
|
||||
{
|
||||
typedef EMPTY_STRUCT request;
|
||||
typedef EMPTY_STRUCT response;
|
||||
typedef CryptoNote::EMPTY_STRUCT request;
|
||||
typedef CryptoNote::EMPTY_STRUCT response;
|
||||
};
|
||||
|
||||
struct payment_details
|
||||
|
@ -158,7 +155,7 @@ using CryptoNote::ISerializer;
|
|||
};
|
||||
|
||||
struct COMMAND_RPC_GET_TRANSFERS {
|
||||
typedef EMPTY_STRUCT request;
|
||||
typedef CryptoNote::EMPTY_STRUCT request;
|
||||
|
||||
struct response {
|
||||
std::list<Transfer> transfers;
|
||||
|
@ -170,7 +167,7 @@ using CryptoNote::ISerializer;
|
|||
};
|
||||
|
||||
struct COMMAND_RPC_GET_HEIGHT {
|
||||
typedef EMPTY_STRUCT request;
|
||||
typedef CryptoNote::EMPTY_STRUCT request;
|
||||
|
||||
struct response {
|
||||
uint64_t height;
|
||||
|
@ -182,8 +179,8 @@ using CryptoNote::ISerializer;
|
|||
};
|
||||
|
||||
struct COMMAND_RPC_RESET {
|
||||
typedef EMPTY_STRUCT request;
|
||||
typedef EMPTY_STRUCT response;
|
||||
typedef CryptoNote::EMPTY_STRUCT request;
|
||||
typedef CryptoNote::EMPTY_STRUCT response;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -248,6 +248,7 @@ CryptoNote::WalletTransaction convert(const CryptoNote::WalletLegacyTransaction&
|
|||
mtx.creationTime = tx.sentTime;
|
||||
mtx.unlockTime = tx.unlockTime;
|
||||
mtx.extra = tx.extra;
|
||||
mtx.isBase = tx.isCoinbase;
|
||||
|
||||
return mtx;
|
||||
}
|
||||
|
@ -284,7 +285,8 @@ WalletSerializer::WalletSerializer(
|
|||
UnlockTransactionJobs& unlockTransactions,
|
||||
TransactionChanges& change,
|
||||
WalletTransactions& transactions,
|
||||
WalletTransfers& transfers
|
||||
WalletTransfers& transfers,
|
||||
uint32_t transactionSoftLockTime
|
||||
) :
|
||||
m_transfersObserver(transfersObserver),
|
||||
m_viewPublicKey(viewPublicKey),
|
||||
|
@ -297,7 +299,8 @@ WalletSerializer::WalletSerializer(
|
|||
m_unlockTransactions(unlockTransactions),
|
||||
m_change(change),
|
||||
m_transactions(transactions),
|
||||
m_transfers(transfers)
|
||||
m_transfers(transfers),
|
||||
m_transactionSoftLockTime(transactionSoftLockTime)
|
||||
{ }
|
||||
|
||||
void WalletSerializer::save(const std::string& password, Common::IOutputStream& destination, bool saveDetails, bool saveCache) {
|
||||
|
@ -556,6 +559,10 @@ void WalletSerializer::loadCurrentVersion(Common::IInputStream& source, const st
|
|||
loadUnlockTransactionsJobs(source, cryptoContext);
|
||||
loadChange(source, cryptoContext);
|
||||
}
|
||||
|
||||
if (details && cache) {
|
||||
updateTransactionsBaseStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void WalletSerializer::loadWalletV1(Common::IInputStream& source, const std::string& password) {
|
||||
|
@ -668,11 +675,32 @@ void WalletSerializer::loadWallets(Common::IInputStream& source, CryptoContext&
|
|||
deserializeEncrypted(count, "wallets_count", cryptoContext, source);
|
||||
cryptoContext.incIv();
|
||||
|
||||
bool isTrackingMode;
|
||||
|
||||
for (uint64_t i = 0; i < count; ++i) {
|
||||
WalletRecordDto dto;
|
||||
deserializeEncrypted(dto, "", cryptoContext, source);
|
||||
cryptoContext.incIv();
|
||||
|
||||
if (i == 0) {
|
||||
isTrackingMode = dto.spendSecretKey == NULL_SECRET_KEY;
|
||||
} else if ((isTrackingMode && dto.spendSecretKey != NULL_SECRET_KEY) || (!isTrackingMode && dto.spendSecretKey == NULL_SECRET_KEY)) {
|
||||
throw std::system_error(make_error_code(error::BAD_ADDRESS), "All addresses must be whether tracking or not");
|
||||
}
|
||||
|
||||
if (dto.spendSecretKey != NULL_SECRET_KEY) {
|
||||
Crypto::PublicKey restoredPublicKey;
|
||||
bool r = Crypto::secret_key_to_public_key(dto.spendSecretKey, restoredPublicKey);
|
||||
|
||||
if (!r || dto.spendPublicKey != restoredPublicKey) {
|
||||
throw std::system_error(make_error_code(error::WRONG_PASSWORD), "Restored spend public key doesn't correspond to secret key");
|
||||
}
|
||||
} else {
|
||||
if (!Crypto::check_key(dto.spendPublicKey)) {
|
||||
throw std::system_error(make_error_code(error::WRONG_PASSWORD), "Public spend key is incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
WalletRecord wallet;
|
||||
wallet.spendPublicKey = dto.spendPublicKey;
|
||||
wallet.spendSecretKey = dto.spendSecretKey;
|
||||
|
@ -696,7 +724,7 @@ void WalletSerializer::subscribeWallets() {
|
|||
sub.keys.address.spendPublicKey = wallet.spendPublicKey;
|
||||
sub.keys.viewSecretKey = m_viewSecretKey;
|
||||
sub.keys.spendSecretKey = wallet.spendSecretKey;
|
||||
sub.transactionSpendableAge = 10;
|
||||
sub.transactionSpendableAge = m_transactionSoftLockTime;
|
||||
sub.syncStart.height = 0;
|
||||
sub.syncStart.timestamp = static_cast<uint64_t>(wallet.creationTimestamp) - (60 * 60 * 24);
|
||||
|
||||
|
@ -793,6 +821,26 @@ void WalletSerializer::loadChange(Common::IInputStream& source, CryptoContext& c
|
|||
}
|
||||
}
|
||||
|
||||
// can't do it in loadTransactions, TransfersContainer is not yet loaded
|
||||
void WalletSerializer::updateTransactionsBaseStatus() {
|
||||
auto& transactions = m_transactions.get<RandomAccessIndex>();
|
||||
auto begin = std::begin(transactions);
|
||||
auto end = std::end(transactions);
|
||||
for (; begin != end; ++begin) {
|
||||
transactions.modify(begin, [this](WalletTransaction& tx) {
|
||||
auto& wallets = m_walletsContainer.get<RandomAccessIndex>();
|
||||
TransactionInformation txInfo;
|
||||
auto it = std::find_if(std::begin(wallets), std::end(wallets), [&](const WalletRecord& rec) {
|
||||
int64_t id = 0;
|
||||
assert(rec.container != nullptr);
|
||||
return rec.container->getTransactionInformation(tx.hash, txInfo, id);
|
||||
});
|
||||
|
||||
tx.isBase = it != std::end(wallets) && txInfo.totalAmountIn == 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void WalletSerializer::loadTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
||||
uint64_t count = 0;
|
||||
deserializeEncrypted(count, "transactions_count", cryptoContext, source);
|
||||
|
@ -815,6 +863,7 @@ void WalletSerializer::loadTransactions(Common::IInputStream& source, CryptoCont
|
|||
tx.creationTime = dto.creationTime;
|
||||
tx.unlockTime = dto.unlockTime;
|
||||
tx.extra = dto.extra;
|
||||
tx.isBase = false;
|
||||
|
||||
m_transactions.get<RandomAccessIndex>().push_back(std::move(tx));
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ public:
|
|||
UnlockTransactionJobs& unlockTransactions,
|
||||
TransactionChanges& change,
|
||||
WalletTransactions& transactions,
|
||||
WalletTransfers& transfers
|
||||
WalletTransfers& transfers,
|
||||
uint32_t transactionSoftLockTime
|
||||
);
|
||||
|
||||
void save(const std::string& password, Common::IOutputStream& destination, bool saveDetails, bool saveCache);
|
||||
|
@ -99,6 +100,7 @@ private:
|
|||
void loadWalletV1Keys(CryptoNote::BinaryInputStreamSerializer& serializer);
|
||||
void loadWalletV1Details(CryptoNote::BinaryInputStreamSerializer& serializer);
|
||||
void addWalletV1Details(const std::vector<WalletLegacyTransaction>& txs, const std::vector<WalletLegacyTransfer>& trs);
|
||||
void updateTransactionsBaseStatus();
|
||||
|
||||
ITransfersObserver& m_transfersObserver;
|
||||
Crypto::PublicKey& m_viewPublicKey;
|
||||
|
@ -112,6 +114,7 @@ private:
|
|||
TransactionChanges& m_change;
|
||||
WalletTransactions& m_transactions;
|
||||
WalletTransfers& m_transfers;
|
||||
uint32_t m_transactionSoftLockTime;
|
||||
};
|
||||
|
||||
} //namespace CryptoNote
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#define BUILD_COMMIT_ID "@VERSION@"
|
||||
#define PROJECT_VERSION "1.0.7.1"
|
||||
#define PROJECT_VERSION_BUILD_NO "571"
|
||||
#define PROJECT_VERSION "1.0.8"
|
||||
#define PROJECT_VERSION_BUILD_NO "608"
|
||||
#define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO "(" BUILD_COMMIT_ID ")"
|
||||
|
|
|
@ -102,14 +102,14 @@ TEST_F(RemoteContextTests, canExecuteOtherContextsWhileWaiting) {
|
|||
ContextGroup cg(dispatcher);
|
||||
cg.spawn([&] {
|
||||
RemoteContext<> context(dispatcher, [&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
});
|
||||
});
|
||||
cg.spawn([&] {
|
||||
System::Timer(dispatcher).sleep(std::chrono::milliseconds(5));
|
||||
System::Timer(dispatcher).sleep(std::chrono::milliseconds(50));
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
ASSERT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(), 5);
|
||||
ASSERT_LE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(), 9);
|
||||
ASSERT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(), 50);
|
||||
ASSERT_LT(std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(), 100);
|
||||
});
|
||||
|
||||
cg.wait();
|
||||
|
|
|
@ -85,7 +85,7 @@ TEST_F(TimerTests, doubleTimerTest) {
|
|||
first.wait();
|
||||
second.wait();
|
||||
ASSERT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - begin).count(), 150);
|
||||
ASSERT_TRUE((std::chrono::high_resolution_clock::now() - begin) < std::chrono::milliseconds(250));
|
||||
ASSERT_TRUE((std::chrono::high_resolution_clock::now() - begin) < std::chrono::milliseconds(275));
|
||||
}
|
||||
|
||||
TEST_F(TimerTests, doubleTimerTestGroup) {
|
||||
|
@ -136,7 +136,7 @@ TEST_F(TimerTests, doubleTimerTestTwoGroupsWait) {
|
|||
|
||||
contextGroup.wait();
|
||||
ASSERT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - begin).count(), 150);
|
||||
ASSERT_TRUE((std::chrono::high_resolution_clock::now() - begin) < std::chrono::milliseconds(250));
|
||||
ASSERT_TRUE((std::chrono::high_resolution_clock::now() - begin) < std::chrono::milliseconds(275));
|
||||
}
|
||||
|
||||
TEST_F(TimerTests, movedTimerIsWorking2) {
|
||||
|
|
|
@ -91,12 +91,38 @@ std::vector<CryptoNote::Transaction> ICoreStub::getPoolTransactions() {
|
|||
|
||||
bool ICoreStub::getPoolChanges(const Crypto::Hash& tailBlockId, const std::vector<Crypto::Hash>& knownTxsIds,
|
||||
std::vector<CryptoNote::Transaction>& addedTxs, std::vector<Crypto::Hash>& deletedTxsIds) {
|
||||
return true;
|
||||
std::unordered_set<Crypto::Hash> knownSet;
|
||||
for (const Crypto::Hash& txId : knownTxsIds) {
|
||||
if (transactionPool.find(txId) == transactionPool.end()) {
|
||||
deletedTxsIds.push_back(txId);
|
||||
}
|
||||
|
||||
knownSet.insert(txId);
|
||||
}
|
||||
|
||||
for (const std::pair<Crypto::Hash, CryptoNote::Transaction>& poolEntry : transactionPool) {
|
||||
if (knownSet.find(poolEntry.first) == knownSet.end()) {
|
||||
addedTxs.push_back(poolEntry.second);
|
||||
}
|
||||
}
|
||||
|
||||
return poolChangesResult;
|
||||
}
|
||||
|
||||
bool ICoreStub::getPoolChangesLite(const Crypto::Hash& tailBlockId, const std::vector<Crypto::Hash>& knownTxsIds,
|
||||
std::vector<CryptoNote::TransactionPrefixInfo>& addedTxs, std::vector<Crypto::Hash>& deletedTxsIds) {
|
||||
return true;
|
||||
std::vector<CryptoNote::Transaction> added;
|
||||
bool returnStatus = getPoolChanges(tailBlockId, knownTxsIds, added, deletedTxsIds);
|
||||
|
||||
for (const auto& tx : added) {
|
||||
CryptoNote::TransactionPrefixInfo tpi;
|
||||
tpi.txPrefix = tx;
|
||||
tpi.txHash = getObjectHash(tx);
|
||||
|
||||
addedTxs.push_back(std::move(tpi));
|
||||
}
|
||||
|
||||
return returnStatus;
|
||||
}
|
||||
|
||||
void ICoreStub::getPoolChanges(const std::vector<Crypto::Hash>& knownTxsIds, std::vector<CryptoNote::Transaction>& addedTxs,
|
||||
|
@ -316,3 +342,7 @@ bool ICoreStub::addMessageQueue(CryptoNote::MessageQueue<CryptoNote::BlockchainM
|
|||
bool ICoreStub::removeMessageQueue(CryptoNote::MessageQueue<CryptoNote::BlockchainMessage>& messageQueuePtr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ICoreStub::setPoolChangesResult(bool result) {
|
||||
poolChangesResult = result;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
class ICoreStub: public CryptoNote::ICore {
|
||||
public:
|
||||
ICoreStub() : topHeight(0), globalIndicesResult(false), randomOutsResult(false), poolTxVerificationResult(true) {};
|
||||
ICoreStub(const CryptoNote::Block& genesisBlock) : topHeight(0), globalIndicesResult(false), randomOutsResult(false), poolTxVerificationResult(true) {
|
||||
ICoreStub(const CryptoNote::Block& genesisBlock) : topHeight(0), globalIndicesResult(false), randomOutsResult(false), poolTxVerificationResult(true),
|
||||
poolChangesResult(true) {
|
||||
addBlock(genesisBlock);
|
||||
};
|
||||
|
||||
|
@ -104,6 +105,7 @@ public:
|
|||
void addTransaction(const CryptoNote::Transaction& tx);
|
||||
|
||||
void setPoolTxVerificationResult(bool result);
|
||||
void setPoolChangesResult(bool result);
|
||||
|
||||
private:
|
||||
uint32_t topHeight;
|
||||
|
@ -122,4 +124,6 @@ private:
|
|||
std::unordered_map<Crypto::Hash, CryptoNote::Transaction> transactions;
|
||||
std::unordered_map<Crypto::Hash, CryptoNote::Transaction> transactionPool;
|
||||
bool poolTxVerificationResult;
|
||||
|
||||
bool poolChangesResult;
|
||||
};
|
||||
|
|
|
@ -75,6 +75,10 @@ void INodeTrivialRefreshStub::getNewBlocks(std::vector<Crypto::Hash>&& knownBloc
|
|||
task.detach();
|
||||
}
|
||||
|
||||
void INodeTrivialRefreshStub::waitForAsyncContexts() {
|
||||
m_asyncCounter.waitAsyncContextsFinish();
|
||||
}
|
||||
|
||||
void INodeTrivialRefreshStub::doGetNewBlocks(std::vector<Crypto::Hash> knownBlockIds, std::vector<block_complete_entry>& newBlocks,
|
||||
uint32_t& startHeight, std::vector<Block> blockchain, const Callback& callback)
|
||||
{
|
||||
|
|
|
@ -122,6 +122,8 @@ public:
|
|||
|
||||
std::function<void(const Crypto::Hash&, std::vector<uint32_t>&)> getGlobalOutsFunctor = [](const Crypto::Hash&, std::vector<uint32_t>&) {};
|
||||
|
||||
void waitForAsyncContexts();
|
||||
|
||||
protected:
|
||||
void doGetNewBlocks(std::vector<Crypto::Hash> knownBlockIds, std::vector<CryptoNote::block_complete_entry>& newBlocks,
|
||||
uint32_t& startHeight, std::vector<CryptoNote::Block> blockchain, const Callback& callback);
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
return base_class::init();
|
||||
}
|
||||
|
||||
void generate(const AccountPublicAddress& address, Transaction& tx)
|
||||
void generate(const AccountPublicAddress& address, Transaction& tx, uint64_t unlockTime = 0)
|
||||
{
|
||||
std::vector<CryptoNote::TransactionDestinationEntry> destinations;
|
||||
|
||||
|
@ -47,7 +47,7 @@ public:
|
|||
[&](uint64_t chunk) { destinations.push_back(CryptoNote::TransactionDestinationEntry(chunk, address)); },
|
||||
[&](uint64_t a_dust) { destinations.push_back(CryptoNote::TransactionDestinationEntry(a_dust, address)); });
|
||||
|
||||
CryptoNote::constructTransaction(this->m_miners[this->real_source_idx].getAccountKeys(), this->m_sources, destinations, std::vector<uint8_t>(), tx, 0, m_logger);
|
||||
CryptoNote::constructTransaction(this->m_miners[this->real_source_idx].getAccountKeys(), this->m_sources, destinations, std::vector<uint8_t>(), tx, unlockTime, m_logger);
|
||||
}
|
||||
|
||||
void generateSingleOutputTx(const AccountPublicAddress& address, uint64_t amount, Transaction& tx) {
|
||||
|
@ -57,7 +57,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
TestBlockchainGenerator::TestBlockchainGenerator(const CryptoNote::Currency& currency) :
|
||||
m_currency(currency),
|
||||
generator(currency)
|
||||
|
@ -182,8 +181,7 @@ bool TestBlockchainGenerator::doGenerateTransactionsInOneBlock(const AccountPubl
|
|||
std::vector<Transaction> txs;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
Transaction tx;
|
||||
creator.generate(address, tx);
|
||||
tx.unlockTime = 10; //default unlock time for coinbase transactions
|
||||
creator.generate(address, tx, m_blockchain.size() + 10);
|
||||
txs.push_back(tx);
|
||||
}
|
||||
|
||||
|
@ -310,6 +308,10 @@ bool TestBlockchainGenerator::addOrphan(const Crypto::Hash& hash, uint32_t heigh
|
|||
return m_orthanBlocksIndex.add(block);
|
||||
}
|
||||
|
||||
void TestBlockchainGenerator::setMinerAccount(const CryptoNote::AccountBase& account) {
|
||||
miner_acc = account;
|
||||
}
|
||||
|
||||
bool TestBlockchainGenerator::getGeneratedTransactionsNumber(uint32_t height, uint64_t& generatedTransactions) {
|
||||
return m_generatedTransactionsIndex.find(height, generatedTransactions);
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
|
||||
bool getTransactionGlobalIndexesByHash(const Crypto::Hash& transactionHash, std::vector<uint32_t>& globalIndexes);
|
||||
bool getMultisignatureOutputByGlobalIndex(uint64_t amount, uint32_t globalIndex, CryptoNote::MultisignatureOutput& out);
|
||||
void setMinerAccount(const CryptoNote::AccountBase& account);
|
||||
|
||||
private:
|
||||
struct MultisignatureOutEntry {
|
||||
|
|
|
@ -101,10 +101,12 @@ TEST_F(Currency_isFusionTransactionTest, failsIfTransactionHasOutputsWithTheSame
|
|||
ASSERT_FALSE(m_currency.isFusionTransaction(tx));
|
||||
}
|
||||
|
||||
TEST_F(Currency_isFusionTransactionTest, failsIfTransactionHasDustOutput) {
|
||||
FusionTransactionBuilder builder(m_currency, 37 * m_currency.defaultDustThreshold() / 10);
|
||||
TEST_F(Currency_isFusionTransactionTest, succeedsIfTransactionHasDustOutput) {
|
||||
FusionTransactionBuilder builder(m_currency, 11 * m_currency.defaultDustThreshold());
|
||||
auto tx = builder.buildTx();
|
||||
ASSERT_FALSE(m_currency.isFusionTransaction(tx));
|
||||
ASSERT_EQ(2, tx.outputs.size());
|
||||
ASSERT_EQ(m_currency.defaultDustThreshold(), tx.outputs[0].amount);
|
||||
ASSERT_TRUE(m_currency.isFusionTransaction(tx));
|
||||
}
|
||||
|
||||
TEST_F(Currency_isFusionTransactionTest, failsIfTransactionFeeIsNotZero) {
|
||||
|
@ -113,3 +115,17 @@ TEST_F(Currency_isFusionTransactionTest, failsIfTransactionFeeIsNotZero) {
|
|||
auto tx = builder.buildTx();
|
||||
ASSERT_FALSE(m_currency.isFusionTransaction(tx));
|
||||
}
|
||||
|
||||
TEST_F(Currency_isFusionTransactionTest, succedsIfTransactionHasInputEqualsDustThreshold) {
|
||||
FusionTransactionBuilder builder(m_currency, TEST_AMOUNT);
|
||||
builder.setFirstInput(m_currency.defaultDustThreshold());
|
||||
auto tx = builder.buildTx();
|
||||
ASSERT_TRUE(m_currency.isFusionTransaction(tx));
|
||||
}
|
||||
|
||||
TEST_F(Currency_isFusionTransactionTest, failsIfTransactionHasInputLessThanDustThreshold) {
|
||||
FusionTransactionBuilder builder(m_currency, TEST_AMOUNT);
|
||||
builder.setFirstInput(m_currency.defaultDustThreshold() - 1);
|
||||
auto tx = builder.buildTx();
|
||||
ASSERT_FALSE(m_currency.isFusionTransaction(tx));
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "Logging/FileLogger.h"
|
||||
#include "CryptoNoteCore/TransactionApi.h"
|
||||
#include "CryptoNoteCore/CryptoNoteTools.h"
|
||||
#include "CryptoNoteCore/VerificationContext.h"
|
||||
#include "Common/StringTools.h"
|
||||
|
||||
using namespace Crypto;
|
||||
|
@ -527,7 +528,7 @@ TEST_F(InProcessNodeTests, getTxMany) {
|
|||
}
|
||||
|
||||
TEST_F(InProcessNodeTests, getTxFail) {
|
||||
size_t POOL_TX_NUMBER = 10;
|
||||
size_t POOL_TX_NUMBER = 10;
|
||||
size_t BLOCKCHAIN_TX_NUMBER = 10;
|
||||
|
||||
std::vector<Crypto::Hash> transactionHashes;
|
||||
|
@ -669,6 +670,99 @@ TEST_F(InProcessNodeTests, getLastLocalBlockTimestampError) {
|
|||
ASSERT_THROW(newNode.getLastLocalBlockTimestamp(), std::exception);
|
||||
}
|
||||
|
||||
TEST_F(InProcessNodeTests, getPoolDiffereceNotInited) {
|
||||
CryptoNote::InProcessNode newNode(coreStub, protocolQueryStub);
|
||||
|
||||
std::vector<Crypto::Hash> knownPoolTxIds;
|
||||
Crypto::Hash knownBlockId = boost::value_initialized<Crypto::Hash>();
|
||||
bool isBcActual = false;
|
||||
std::vector<std::unique_ptr<ITransactionReader>> newTxs;
|
||||
std::vector<Crypto::Hash> deletedTxIds;
|
||||
|
||||
CallbackStatus status;
|
||||
newNode.getPoolSymmetricDifference(std::move(knownPoolTxIds), knownBlockId, isBcActual, newTxs, deletedTxIds, [&status](std::error_code ec) { status.setStatus(ec); });
|
||||
ASSERT_TRUE(status.wait());
|
||||
ASSERT_NE(std::error_code(), status.getStatus());
|
||||
}
|
||||
|
||||
TEST_F(InProcessNodeTests, getPoolDiffereceActualBC) {
|
||||
size_t POOL_TX_NUMBER = 10;
|
||||
|
||||
std::unordered_set<Crypto::Hash> transactionHashes;
|
||||
|
||||
coreStub.setPoolChangesResult(true);
|
||||
|
||||
for (size_t i = 0; i < POOL_TX_NUMBER; ++i) {
|
||||
auto txptr = CryptoNote::createTransaction();
|
||||
auto tx = ::createTx(*txptr.get());
|
||||
transactionHashes.insert(CryptoNote::getObjectHash(tx));
|
||||
CryptoNote::tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
|
||||
bool keptByBlock = false;
|
||||
coreStub.handleIncomingTransaction(tx, CryptoNote::getObjectHash(tx), CryptoNote::getObjectBinarySize(tx), tvc, keptByBlock);
|
||||
ASSERT_TRUE(tvc.m_added_to_pool);
|
||||
ASSERT_FALSE(tvc.m_verifivation_failed);
|
||||
}
|
||||
|
||||
ASSERT_EQ(transactionHashes.size(), POOL_TX_NUMBER);
|
||||
|
||||
std::vector<Crypto::Hash> knownPoolTxIds;
|
||||
Crypto::Hash knownBlockId = CryptoNote::getObjectHash(generator.getBlockchain().back());
|
||||
bool isBcActual = false;
|
||||
std::vector<std::unique_ptr<ITransactionReader>> newTxs;
|
||||
std::vector<Crypto::Hash> deletedTxIds;
|
||||
|
||||
CallbackStatus status;
|
||||
node.getPoolSymmetricDifference(std::move(knownPoolTxIds), knownBlockId, isBcActual, newTxs, deletedTxIds, [&status](std::error_code ec) { status.setStatus(ec); });
|
||||
ASSERT_TRUE(status.wait());
|
||||
ASSERT_EQ(std::error_code(), status.getStatus());
|
||||
ASSERT_TRUE(isBcActual);
|
||||
ASSERT_EQ(newTxs.size(), transactionHashes.size());
|
||||
ASSERT_TRUE(deletedTxIds.empty());
|
||||
|
||||
for (const auto& tx : newTxs) {
|
||||
ASSERT_NE(transactionHashes.find(tx->getTransactionHash()), transactionHashes.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InProcessNodeTests, getPoolDiffereceNotActualBC) {
|
||||
size_t POOL_TX_NUMBER = 10;
|
||||
|
||||
std::unordered_set<Crypto::Hash> transactionHashes;
|
||||
|
||||
coreStub.setPoolChangesResult(false);
|
||||
|
||||
for (size_t i = 0; i < POOL_TX_NUMBER; ++i) {
|
||||
auto txptr = CryptoNote::createTransaction();
|
||||
auto tx = ::createTx(*txptr.get());
|
||||
transactionHashes.insert(CryptoNote::getObjectHash(tx));
|
||||
CryptoNote::tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
|
||||
bool keptByBlock = false;
|
||||
coreStub.handleIncomingTransaction(tx, CryptoNote::getObjectHash(tx), CryptoNote::getObjectBinarySize(tx), tvc, keptByBlock);
|
||||
ASSERT_TRUE(tvc.m_added_to_pool);
|
||||
ASSERT_FALSE(tvc.m_verifivation_failed);
|
||||
}
|
||||
|
||||
ASSERT_EQ(transactionHashes.size(), POOL_TX_NUMBER);
|
||||
|
||||
std::vector<Crypto::Hash> knownPoolTxIds;
|
||||
Crypto::Hash knownBlockId = CryptoNote::getObjectHash(generator.getBlockchain().back());
|
||||
bool isBcActual = false;
|
||||
std::vector<std::unique_ptr<ITransactionReader>> newTxs;
|
||||
std::vector<Crypto::Hash> deletedTxIds;
|
||||
|
||||
CallbackStatus status;
|
||||
node.getPoolSymmetricDifference(std::move(knownPoolTxIds), knownBlockId, isBcActual, newTxs, deletedTxIds, [&status](std::error_code ec) { status.setStatus(ec); });
|
||||
ASSERT_TRUE(status.wait());
|
||||
ASSERT_EQ(std::error_code(), status.getStatus());
|
||||
ASSERT_FALSE(isBcActual);
|
||||
ASSERT_EQ(newTxs.size(), transactionHashes.size());
|
||||
ASSERT_TRUE(deletedTxIds.empty());
|
||||
|
||||
for (const auto& tx : newTxs) {
|
||||
ASSERT_NE(transactionHashes.find(tx->getTransactionHash()), transactionHashes.end());
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: make relayTransaction unit test
|
||||
//TODO: make getNewBlocks unit test
|
||||
//TODO: make queryBlocks unit test
|
||||
|
|
|
@ -433,6 +433,9 @@ TEST_F(DetachTest, testDetachWithWallet) {
|
|||
Alice.addObserver(&AliceCompleted);
|
||||
Bob.addObserver(&BobCompleted);
|
||||
|
||||
auto expectedTransactionBlockHeight = m_node.getLastLocalBlockHeight();
|
||||
generator.generateEmptyBlocks(1); //unlock bob's pending money
|
||||
|
||||
m_node.updateObservers();
|
||||
|
||||
AliceCompleted.syncCompletedFuture.get();
|
||||
|
@ -447,14 +450,13 @@ TEST_F(DetachTest, testDetachWithWallet) {
|
|||
|
||||
Bob.getTransaction(txId, txInfo);
|
||||
|
||||
|
||||
ASSERT_EQ(txInfo.blockHeight, m_node.getLastLocalBlockHeight());
|
||||
ASSERT_EQ(txInfo.blockHeight, expectedTransactionBlockHeight);
|
||||
ASSERT_EQ(txInfo.totalAmount, tr.amount);
|
||||
|
||||
ASSERT_EQ(Bob.pendingBalance(), 0);
|
||||
ASSERT_EQ(Bob.actualBalance(), tr.amount);
|
||||
|
||||
m_node.startAlternativeChain(m_node.getLastLocalBlockHeight() - 1);
|
||||
m_node.startAlternativeChain(txInfo.blockHeight - 1);
|
||||
generator.generateEmptyBlocks(2);
|
||||
|
||||
//sync Bob
|
||||
|
|
|
@ -36,11 +36,36 @@
|
|||
#include <System/Timer.h>
|
||||
#include <System/Context.h>
|
||||
|
||||
#include "TransactionApiHelpers.h"
|
||||
|
||||
using namespace Crypto;
|
||||
using namespace Common;
|
||||
using namespace CryptoNote;
|
||||
|
||||
namespace CryptoNote {
|
||||
std::ostream& operator<<(std::ostream& o, const WalletTransactionState& st) {
|
||||
switch (st) {
|
||||
case WalletTransactionState::FAILED:
|
||||
o << "FAILED";
|
||||
break;
|
||||
case WalletTransactionState::CANCELLED:
|
||||
o << "CANCELLED";
|
||||
break;
|
||||
case WalletTransactionState::SUCCEEDED:
|
||||
o << "SUCCEEDED";
|
||||
break;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
std::ostream& operator<<(std::ostream& o, const WalletTransaction& tx) {
|
||||
o << "WalletTransaction{state=" << tx.state << ", timestamp=" << tx.timestamp
|
||||
<< ", blockHeight=" << tx.blockHeight << ", hash=" << tx.hash
|
||||
<< ", totalAmount=" << tx.totalAmount << ", fee=" << tx.fee
|
||||
<< ", creationTime=" << tx.creationTime << ", unlockTime=" << tx.unlockTime
|
||||
<< ", extra=" << tx.extra << ", isBase=" << tx.isBase << "}";
|
||||
return o;
|
||||
}
|
||||
|
||||
bool operator==(const WalletTransaction& lhs, const WalletTransaction& rhs) {
|
||||
if (lhs.state != rhs.state) {
|
||||
return false;
|
||||
|
@ -78,6 +103,10 @@ namespace CryptoNote {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (lhs.isBase != rhs.isBase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -100,16 +129,22 @@ namespace CryptoNote {
|
|||
bool operator!=(const WalletTransfer& lhs, const WalletTransfer& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator==(const IFusionManager::EstimateResult& lhs, const IFusionManager::EstimateResult& rhs) {
|
||||
return lhs.fusionReadyCount == rhs.fusionReadyCount && lhs.totalOutputCount == rhs.totalOutputCount;
|
||||
}
|
||||
}
|
||||
|
||||
class WalletApi: public ::testing::Test {
|
||||
public:
|
||||
WalletApi() :
|
||||
TRANSACTION_SOFTLOCK_TIME(10),
|
||||
currency(CryptoNote::CurrencyBuilder(logger).currency()),
|
||||
generator(currency),
|
||||
node(generator),
|
||||
alice(dispatcher, currency, node),
|
||||
FEE(currency.minimumFee())
|
||||
FEE(currency.minimumFee()),
|
||||
FUSION_THRESHOLD(currency.defaultDustThreshold() * 10)
|
||||
{ }
|
||||
|
||||
virtual void SetUp() override;
|
||||
|
@ -121,8 +156,10 @@ protected:
|
|||
void generateBlockReward(const std::string& address);
|
||||
void generateAndUnlockMoney();
|
||||
void generateAddressesWithPendingMoney(size_t count);
|
||||
void generateFusionOutputsAndUnlock(WalletGreen& wallet, INodeTrivialRefreshStub& node, const CryptoNote::Currency& walletCurrency, uint64_t threshold);
|
||||
void unlockMoney();
|
||||
void unlockMoney(CryptoNote::WalletGreen& wallet, INodeTrivialRefreshStub& inode);
|
||||
void setMinerTo(CryptoNote::WalletGreen& wallet);
|
||||
|
||||
template<typename T>
|
||||
void waitValueChanged(CryptoNote::WalletGreen& wallet, T prev, std::function<T ()>&& f);
|
||||
|
@ -141,11 +178,14 @@ protected:
|
|||
void waitPendingBalanceUpdated(CryptoNote::WalletGreen& wallet, uint64_t prev);
|
||||
|
||||
void waitForTransactionCount(CryptoNote::WalletGreen& wallet, uint64_t expected);
|
||||
void waitForTransactionUpdated(CryptoNote::WalletGreen& wallet, size_t expectedTransactionId);
|
||||
void waitForActualBalance(uint64_t expected);
|
||||
void waitForActualBalance(CryptoNote::WalletGreen& wallet, uint64_t expected);
|
||||
|
||||
size_t sendMoneyToRandomAddressFrom(const std::string& address, uint64_t amount, uint64_t fee);
|
||||
size_t sendMoneyToRandomAddressFrom(const std::string& address);
|
||||
|
||||
size_t sendMoney(CryptoNote::WalletGreen& wallet, const std::string& to, int64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0);
|
||||
size_t sendMoney(const std::string& to, int64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0);
|
||||
|
||||
void fillWalletWithDetailsCache();
|
||||
|
@ -156,6 +196,8 @@ protected:
|
|||
const std::vector<WalletLegacyTransfer>& trs = std::vector<WalletLegacyTransfer>(),
|
||||
const std::vector<std::pair<TransactionInformation, int64_t>>& externalTxs = std::vector<std::pair<TransactionInformation, int64_t>>());
|
||||
|
||||
uint32_t TRANSACTION_SOFTLOCK_TIME;
|
||||
|
||||
System::Dispatcher dispatcher;
|
||||
Logging::ConsoleLogger logger;
|
||||
CryptoNote::Currency currency;
|
||||
|
@ -167,6 +209,7 @@ protected:
|
|||
const uint64_t SENT = 1122334455;
|
||||
const uint64_t FEE;
|
||||
const std::string RANDOM_ADDRESS = "2634US2FAz86jZT73YmM8u5GPCknT2Wxj8bUCKivYKpThFhF2xsjygMGxbxZzM42zXhKUhym6Yy6qHHgkuWtruqiGkDpX6m";
|
||||
const uint64_t FUSION_THRESHOLD;
|
||||
};
|
||||
|
||||
void WalletApi::SetUp() {
|
||||
|
@ -174,6 +217,20 @@ void WalletApi::SetUp() {
|
|||
aliceAddress = alice.createAddress();
|
||||
}
|
||||
|
||||
void WalletApi::setMinerTo(CryptoNote::WalletGreen& wallet) {
|
||||
AccountBase base;
|
||||
AccountKeys keys;
|
||||
auto viewKey = wallet.getViewKey();
|
||||
auto spendKey = wallet.getAddressSpendKey(0);
|
||||
keys.address.spendPublicKey = spendKey.publicKey;
|
||||
keys.address.viewPublicKey = viewKey.publicKey;
|
||||
keys.viewSecretKey = viewKey.secretKey;
|
||||
keys.spendSecretKey = spendKey.secretKey;
|
||||
base.setAccountKeys(keys);
|
||||
// mine to alice's address to make it recieve block base transaction
|
||||
generator.setMinerAccount(base);
|
||||
}
|
||||
|
||||
void WalletApi::TearDown() {
|
||||
alice.shutdown();
|
||||
wait(100); //ObserverManager bug workaround
|
||||
|
@ -196,13 +253,56 @@ void WalletApi::generateBlockReward(const std::string& address) {
|
|||
generator.getBlockRewardForAddress(parseAddress(address));
|
||||
}
|
||||
|
||||
void WalletApi::generateFusionOutputsAndUnlock(WalletGreen& wallet, INodeTrivialRefreshStub& node, const CryptoNote::Currency& walletCurrency, uint64_t threshold) {
|
||||
uint64_t digit = walletCurrency.defaultDustThreshold();
|
||||
uint64_t mul = 1;
|
||||
|
||||
while (digit > 9) {
|
||||
digit /= 10;
|
||||
mul *= 10;
|
||||
}
|
||||
|
||||
auto initialAmount = wallet.getActualBalance();
|
||||
|
||||
CryptoNote::AccountPublicAddress publicAddress = parseAddress(wallet.getAddress(0));
|
||||
const size_t POWERS_COUNT = 3;
|
||||
|
||||
uint64_t addedAmount = 0;
|
||||
for (size_t power = 0; power < POWERS_COUNT; ++power) {
|
||||
int start = power == 0 ? digit: 1;
|
||||
if (start * mul > threshold) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (int count = 0, d = start; count < walletCurrency.fusionTxMinInputCount() && start * mul < threshold; ++count) {
|
||||
//TODO: make it possible to put several outputs to one transaction
|
||||
auto amount = d * mul;
|
||||
generator.getSingleOutputTransaction(publicAddress, amount);
|
||||
addedAmount += amount;
|
||||
|
||||
if (++d > 9 || amount >= threshold) {
|
||||
d = start;
|
||||
}
|
||||
}
|
||||
|
||||
mul *= 10;
|
||||
}
|
||||
|
||||
assert(addedAmount > 0);
|
||||
|
||||
generator.generateEmptyBlocks(11);
|
||||
node.updateObservers();
|
||||
|
||||
waitForActualBalance(wallet, initialAmount + addedAmount);
|
||||
}
|
||||
|
||||
void WalletApi::unlockMoney() {
|
||||
unlockMoney(alice, node);
|
||||
}
|
||||
|
||||
void WalletApi::unlockMoney(CryptoNote::WalletGreen& wallet, INodeTrivialRefreshStub& inode) {
|
||||
auto prev = wallet.getActualBalance();
|
||||
generator.generateEmptyBlocks(11); //coinbase money should become available after 10 blocks
|
||||
generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow()); //coinbase money should become available after 10 blocks
|
||||
inode.updateObservers();
|
||||
waitActualBalanceUpdated(wallet, prev);
|
||||
}
|
||||
|
@ -260,6 +360,10 @@ void WalletApi::waitForActualBalance(uint64_t expected) {
|
|||
waitForValue<uint64_t>(alice, expected, [this] () { return this->alice.getActualBalance(); });
|
||||
}
|
||||
|
||||
void WalletApi::waitForActualBalance(CryptoNote::WalletGreen& wallet, uint64_t expected) {
|
||||
waitForValue<uint64_t>(wallet, expected, [&wallet] () { return wallet.getActualBalance(); });
|
||||
}
|
||||
|
||||
void WalletApi::waitActualBalanceUpdated(CryptoNote::WalletGreen& wallet, uint64_t prev) {
|
||||
waitValueChanged<uint64_t>(wallet, prev, [&wallet] () { return wallet.getActualBalance(); });
|
||||
}
|
||||
|
@ -280,6 +384,16 @@ void WalletApi::waitForTransactionCount(CryptoNote::WalletGreen& wallet, uint64_
|
|||
waitForValue<size_t>(wallet, expected, [&wallet] () { return wallet.getTransactionCount(); });
|
||||
}
|
||||
|
||||
void WalletApi::waitForTransactionUpdated(CryptoNote::WalletGreen& wallet, size_t expectedTransactionId) {
|
||||
WalletEvent event;
|
||||
for (;;) {
|
||||
event = wallet.getEvent();
|
||||
if (event.type == WalletEventType::TRANSACTION_UPDATED && event.transactionUpdated.transactionIndex == expectedTransactionId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WalletApi::generateAddressesWithPendingMoney(size_t count) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
generateBlockReward(alice.createAddress());
|
||||
|
@ -315,12 +429,16 @@ void WalletApi::fillWalletWithDetailsCache() {
|
|||
}
|
||||
}
|
||||
|
||||
size_t WalletApi::sendMoney(const std::string& to, int64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) {
|
||||
size_t WalletApi::sendMoney(CryptoNote::WalletGreen& wallet, const std::string& to, int64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) {
|
||||
CryptoNote::WalletTransfer transfer;
|
||||
transfer.address = to;
|
||||
transfer.amount = amount;
|
||||
|
||||
return alice.transfer(transfer, fee, mixIn, extra, unlockTimestamp);
|
||||
return wallet.transfer(transfer, fee, mixIn, extra, unlockTimestamp);
|
||||
}
|
||||
|
||||
size_t WalletApi::sendMoney(const std::string& to, int64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) {
|
||||
return sendMoney(alice, to, amount, fee, mixIn, extra, unlockTimestamp);
|
||||
}
|
||||
|
||||
void WalletApi::wait(uint64_t milliseconds) {
|
||||
|
@ -357,7 +475,7 @@ TEST_F(WalletApi, unlockMoney) {
|
|||
}
|
||||
|
||||
TEST_F(WalletApi, transferFromOneAddress) {
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.initialize("pass2");
|
||||
std::string bobAddress = bob.createAddress();
|
||||
|
||||
|
@ -366,6 +484,7 @@ TEST_F(WalletApi, transferFromOneAddress) {
|
|||
auto alicePrev = alice.getActualBalance();
|
||||
sendMoney(bobAddress, SENT, FEE);
|
||||
node.updateObservers();
|
||||
|
||||
waitActualBalanceUpdated(alicePrev);
|
||||
waitPendingBalanceUpdated(bob, 0);
|
||||
|
||||
|
@ -379,6 +498,44 @@ TEST_F(WalletApi, transferFromOneAddress) {
|
|||
wait(100);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, pendingBalanceUpdatedAfterTransactionGotInBlock) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
auto initialActual = alice.getActualBalance();
|
||||
|
||||
sendMoney(RANDOM_ADDRESS, SENT, FEE);
|
||||
node.updateObservers();
|
||||
waitActualBalanceUpdated(initialActual);
|
||||
waitPendingBalanceUpdated(0);
|
||||
|
||||
auto prevPending = alice.getPendingBalance();
|
||||
|
||||
generator.generateEmptyBlocks(TRANSACTION_SOFTLOCK_TIME);
|
||||
node.updateObservers();
|
||||
|
||||
waitPendingBalanceUpdated(prevPending);
|
||||
ASSERT_EQ(0, alice.getPendingBalance());
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, moneyLockedIfTransactionIsSoftLocked) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.initialize("pass2");
|
||||
|
||||
sendMoney(bob.createAddress(), SENT, FEE);
|
||||
generator.generateEmptyBlocks(TRANSACTION_SOFTLOCK_TIME - 1);
|
||||
node.updateObservers();
|
||||
|
||||
waitPendingBalanceUpdated(bob, 0);
|
||||
|
||||
ASSERT_EQ(SENT, bob.getPendingBalance());
|
||||
ASSERT_EQ(0, bob.getActualBalance());
|
||||
|
||||
bob.shutdown();
|
||||
wait(100);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, transferMixin) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
|
@ -408,12 +565,12 @@ TEST_F(WalletApi, transferNegativeAmount) {
|
|||
TEST_F(WalletApi, transferFromTwoAddresses) {
|
||||
generateBlockReward();
|
||||
generateBlockReward(alice.createAddress());
|
||||
generator.generateEmptyBlocks(11);
|
||||
generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow());
|
||||
node.updateObservers();
|
||||
|
||||
waitForActualBalance(2 * TEST_BLOCK_REWARD);
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.initialize("pass2");
|
||||
std::string bobAddress = bob.createAddress();
|
||||
|
||||
|
@ -445,14 +602,14 @@ TEST_F(WalletApi, transferTooBigTransaction) {
|
|||
TestBlockchainGenerator gen(cur);
|
||||
INodeTrivialRefreshStub n(gen);
|
||||
|
||||
CryptoNote::WalletGreen wallet(dispatcher, cur, n);
|
||||
CryptoNote::WalletGreen wallet(dispatcher, cur, n, TRANSACTION_SOFTLOCK_TIME);
|
||||
wallet.initialize("pass");
|
||||
wallet.createAddress();
|
||||
|
||||
gen.getBlockRewardForAddress(parseAddress(wallet.getAddress(0)));
|
||||
|
||||
auto prev = wallet.getActualBalance();
|
||||
gen.generateEmptyBlocks(11);
|
||||
gen.generateEmptyBlocks(currency.minedMoneyUnlockWindow());
|
||||
n.updateObservers();
|
||||
waitActualBalanceUpdated(wallet, prev);
|
||||
|
||||
|
@ -494,7 +651,7 @@ TEST_F(WalletApi, transferFromSpecificAddress) {
|
|||
auto secondAddress = alice.createAddress();
|
||||
generateBlockReward(secondAddress);
|
||||
|
||||
generator.generateEmptyBlocks(11);
|
||||
generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow());
|
||||
node.updateObservers();
|
||||
waitActualBalanceUpdated();
|
||||
|
||||
|
@ -520,7 +677,7 @@ TEST_F(WalletApi, loadEmptyWallet) {
|
|||
std::stringstream data;
|
||||
alice.save(data, true, true);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.load(data, "pass");
|
||||
|
||||
ASSERT_EQ(alice.getAddressCount(), bob.getAddressCount());
|
||||
|
@ -532,15 +689,77 @@ TEST_F(WalletApi, loadEmptyWallet) {
|
|||
wait(100);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, loadWalletWithoutAddresses) {
|
||||
TEST_F(WalletApi, walletGetsBaseTransaction) {
|
||||
// mine to alice's address to make it recieve block base transaction
|
||||
setMinerTo(alice);
|
||||
generateAndUnlockMoney();
|
||||
ASSERT_TRUE(alice.getTransaction(0).isBase);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, walletGetsNonBaseTransaction) {
|
||||
generateAndUnlockMoney();
|
||||
ASSERT_FALSE(alice.getTransaction(0).isBase);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, loadWalletWithBaseTransaction) {
|
||||
// mine to alice's address to make it recieve block base transaction
|
||||
setMinerTo(alice);
|
||||
generateAndUnlockMoney();
|
||||
|
||||
std::stringstream data;
|
||||
alice.save(data, true, true);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
bob.load(data, "pass");
|
||||
|
||||
ASSERT_TRUE(bob.getTransaction(0).isBase);
|
||||
bob.shutdown();
|
||||
wait(100);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, updateBaseTransactionAfterLoad) {
|
||||
// mine to alice's address to make it recieve block base transaction
|
||||
setMinerTo(alice);
|
||||
generateAndUnlockMoney();
|
||||
|
||||
std::stringstream data;
|
||||
alice.save(data, true, false);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
bob.load(data, "pass");
|
||||
waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5));
|
||||
|
||||
ASSERT_TRUE(bob.getTransaction(0).isBase);
|
||||
bob.shutdown();
|
||||
wait(100);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, setBaseTransactionAfterInSynchronization) {
|
||||
// mine to alice's address to make it recieve block base transaction
|
||||
setMinerTo(alice);
|
||||
generateAndUnlockMoney();
|
||||
|
||||
std::stringstream data;
|
||||
alice.save(data, false, false);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
bob.load(data, "pass");
|
||||
waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5));
|
||||
|
||||
ASSERT_TRUE(bob.getTransaction(0).isBase);
|
||||
bob.shutdown();
|
||||
wait(100);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, loadWalletWithoutAddresses) {
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.initialize("pass");
|
||||
|
||||
std::stringstream data;
|
||||
bob.save(data, false, false);
|
||||
bob.shutdown();
|
||||
|
||||
WalletGreen carol(dispatcher, currency, node);
|
||||
WalletGreen carol(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
carol.load(data, "pass");
|
||||
|
||||
ASSERT_EQ(0, carol.getAddressCount());
|
||||
|
@ -587,11 +806,12 @@ void compareWalletsTransactionTransfers(const CryptoNote::WalletGreen& alice, co
|
|||
|
||||
TEST_F(WalletApi, loadCacheDetails) {
|
||||
fillWalletWithDetailsCache();
|
||||
|
||||
node.waitForAsyncContexts();
|
||||
waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5));
|
||||
std::stringstream data;
|
||||
alice.save(data, true, true);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.load(data, "pass");
|
||||
|
||||
compareWalletsAddresses(alice, bob);
|
||||
|
@ -609,7 +829,7 @@ TEST_F(WalletApi, loadNoCacheNoDetails) {
|
|||
std::stringstream data;
|
||||
alice.save(data, false, false);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.load(data, "pass");
|
||||
|
||||
compareWalletsAddresses(alice, bob);
|
||||
|
@ -628,7 +848,7 @@ TEST_F(WalletApi, loadNoCacheDetails) {
|
|||
std::stringstream data;
|
||||
alice.save(data, true, false);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.load(data, "pass");
|
||||
|
||||
compareWalletsAddresses(alice, bob);
|
||||
|
@ -648,7 +868,7 @@ TEST_F(WalletApi, loadCacheNoDetails) {
|
|||
std::stringstream data;
|
||||
alice.save(data, false, true);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.load(data, "pass");
|
||||
|
||||
compareWalletsAddresses(alice, bob);
|
||||
|
@ -665,7 +885,7 @@ TEST_F(WalletApi, loadWithWrongPassword) {
|
|||
std::stringstream data;
|
||||
alice.save(data, false, false);
|
||||
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
ASSERT_ANY_THROW(bob.load(data, "pass2"));
|
||||
}
|
||||
|
||||
|
@ -695,7 +915,7 @@ void WalletApi::testIWalletDataCompatibility(bool details, const std::string& ca
|
|||
std::stringstream stream;
|
||||
walletSerializer.serialize(stream, "pass", details, std::string());
|
||||
|
||||
WalletGreen wallet(dispatcher, currency, node);
|
||||
WalletGreen wallet(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
wallet.load(stream, "pass");
|
||||
|
||||
EXPECT_EQ(1, wallet.getAddressCount());
|
||||
|
@ -842,7 +1062,7 @@ TEST_F(WalletApi, stopStart) {
|
|||
}
|
||||
|
||||
TEST_F(WalletApi, uninitializedObject) {
|
||||
WalletGreen bob(dispatcher, currency, node);
|
||||
WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
|
||||
ASSERT_ANY_THROW(bob.changePassword("s", "p"));
|
||||
std::stringstream stream;
|
||||
|
@ -877,6 +1097,7 @@ TEST_F(WalletApi, checkSentTransaction) {
|
|||
ASSERT_EQ(-static_cast<int64_t>(SENT + FEE), tx.totalAmount);
|
||||
ASSERT_EQ(FEE, tx.fee);
|
||||
ASSERT_EQ(0, tx.unlockTime);
|
||||
ASSERT_FALSE(tx.isBase);
|
||||
ASSERT_EQ(TX_PUB_KEY_EXTRA_SIZE, tx.extra.size()); //Transaction public key only
|
||||
}
|
||||
|
||||
|
@ -910,6 +1131,7 @@ TEST_F(WalletApi, checkSentTransactionWithExtra) {
|
|||
ASSERT_EQ(-static_cast<int64_t>(SENT + FEE), tx.totalAmount);
|
||||
ASSERT_EQ(FEE, tx.fee);
|
||||
ASSERT_EQ(0, tx.unlockTime);
|
||||
ASSERT_FALSE(tx.isBase);
|
||||
ASSERT_EQ(extra, removeTxPublicKey(tx.extra));
|
||||
}
|
||||
|
||||
|
@ -923,12 +1145,21 @@ TEST_F(WalletApi, checkFailedTransaction) {
|
|||
ASSERT_EQ(CryptoNote::WalletTransactionState::FAILED, tx.state);
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, transactionSendsAfterFailedTransaction) {
|
||||
generator.getSingleOutputTransaction(parseAddress(aliceAddress), SENT + FEE);
|
||||
unlockMoney();
|
||||
|
||||
node.setNextTransactionError();
|
||||
ASSERT_ANY_THROW(sendMoney(RANDOM_ADDRESS, SENT, FEE));
|
||||
ASSERT_NO_THROW(sendMoney(RANDOM_ADDRESS, SENT, FEE));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, checkIncomingTransaction) {
|
||||
const std::string extra = createExtraNonce("\x01\x23\x45\x67\x89\xab\xcd\xef");
|
||||
|
||||
generateAndUnlockMoney();
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.initialize("pass2");
|
||||
std::string bobAddress = bob.createAddress();
|
||||
|
||||
|
@ -963,7 +1194,7 @@ TEST_F(WalletApi, changePassword) {
|
|||
std::stringstream data;
|
||||
alice.save(data, false, false);
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
ASSERT_NO_THROW(bob.load(data, "pass2"));
|
||||
|
||||
bob.shutdown();
|
||||
|
@ -993,7 +1224,7 @@ TEST_F(WalletApi, detachBlockchain) {
|
|||
auto alicePrev = alice.getActualBalance();
|
||||
|
||||
node.startAlternativeChain(1);
|
||||
generator.generateEmptyBlocks(11);
|
||||
generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow());
|
||||
node.updateObservers();
|
||||
waitActualBalanceUpdated(alicePrev);
|
||||
|
||||
|
@ -1016,7 +1247,7 @@ TEST_F(WalletApi, deleteAddresses) {
|
|||
TEST_F(WalletApi, incomingTxTransfer) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME);
|
||||
bob.initialize("pass2");
|
||||
bob.createAddress();
|
||||
bob.createAddress();
|
||||
|
@ -1077,7 +1308,7 @@ TEST_F(WalletApi, syncAfterLoad) {
|
|||
alice.shutdown();
|
||||
|
||||
generateBlockReward();
|
||||
generator.generateEmptyBlocks(11);
|
||||
generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow());
|
||||
|
||||
alice.load(data, "pass");
|
||||
|
||||
|
@ -1107,7 +1338,7 @@ TEST_F(WalletApi, DISABLED_loadTest) {
|
|||
using namespace std::chrono;
|
||||
|
||||
INodeNoRelay noRelayNode(generator);
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, noRelayNode);
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, noRelayNode, TRANSACTION_SOFTLOCK_TIME);
|
||||
wallet.initialize("pass");
|
||||
|
||||
const size_t ADDRESSES_COUNT = 1000;
|
||||
|
@ -1247,6 +1478,134 @@ TEST_F(WalletApi, getAddressSpendKeyThrowsIfStopped) {
|
|||
ASSERT_ANY_THROW(alice.getAddressSpendKey(0));
|
||||
}
|
||||
|
||||
Crypto::PublicKey generatePublicKey() {
|
||||
CryptoNote::KeyPair spendKeys;
|
||||
Crypto::generate_keys(spendKeys.publicKey, spendKeys.secretKey);
|
||||
|
||||
return spendKeys.publicKey;
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createTrackingKeyAddressSucceeded) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
wallet.initialize("pass");
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
|
||||
ASSERT_NO_THROW(wallet.createAddress(publicKey));
|
||||
ASSERT_EQ(1, wallet.getAddressCount());
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createTrackingKeyThrowsIfNotInitialized) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
ASSERT_ANY_THROW(wallet.createAddress(publicKey));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createTrackingKeyThrowsIfStopped) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
wallet.initialize("pass");
|
||||
wallet.stop();
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
ASSERT_ANY_THROW(wallet.createAddress(publicKey));
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createTrackingKeyThrowsIfKeyExists) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
wallet.initialize("pass");
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
wallet.createAddress(publicKey);
|
||||
ASSERT_ANY_THROW(wallet.createAddress(publicKey));
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createTrackingKeyThrowsIfWalletHasNotTrackingKeys) {
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
ASSERT_ANY_THROW(alice.createAddress(publicKey));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, getAddressSpendKeyForTrackingKeyReturnsNullSecretKey) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
wallet.initialize("pass");
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
wallet.createAddress(publicKey);
|
||||
|
||||
KeyPair spendKeys = wallet.getAddressSpendKey(0);
|
||||
ASSERT_EQ(NULL_SECRET_KEY, spendKeys.secretKey);
|
||||
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, trackingAddressReceivesMoney) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
bob.initialize("pass2");
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
bob.createAddress(publicKey);
|
||||
|
||||
sendMoney(bob.getAddress(0), SENT, FEE);
|
||||
node.updateObservers();
|
||||
|
||||
auto expectedTransactionHeight = node.getLastKnownBlockHeight();
|
||||
waitPendingBalanceUpdated(bob, 0);
|
||||
|
||||
ASSERT_EQ(SENT, bob.getPendingBalance());
|
||||
ASSERT_EQ(0, bob.getActualBalance());
|
||||
ASSERT_EQ(1, bob.getTransactionCount());
|
||||
|
||||
CryptoNote::WalletTransaction transaction = bob.getTransaction(0);
|
||||
ASSERT_EQ(CryptoNote::WalletTransactionState::SUCCEEDED, transaction.state);
|
||||
ASSERT_EQ(expectedTransactionHeight, transaction.blockHeight);
|
||||
ASSERT_EQ(SENT, transaction.totalAmount);
|
||||
ASSERT_EQ(FEE, transaction.fee);
|
||||
ASSERT_EQ(0, transaction.unlockTime);
|
||||
|
||||
bob.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, trackingAddressUnlocksMoney) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
bob.initialize("pass2");
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
bob.createAddress(publicKey);
|
||||
|
||||
sendMoney(bob.getAddress(0), SENT, FEE);
|
||||
generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow());
|
||||
node.updateObservers();
|
||||
waitActualBalanceUpdated(bob, 0);
|
||||
|
||||
ASSERT_EQ(0, bob.getPendingBalance());
|
||||
ASSERT_EQ(SENT, bob.getActualBalance());
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, transferFromTrackingKeyThrows) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
CryptoNote::WalletGreen bob(dispatcher, currency, node);
|
||||
bob.initialize("pass2");
|
||||
|
||||
Crypto::PublicKey publicKey = generatePublicKey();
|
||||
bob.createAddress(publicKey);
|
||||
|
||||
sendMoney(bob.getAddress(0), SENT, FEE);
|
||||
generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow());
|
||||
node.updateObservers();
|
||||
waitActualBalanceUpdated(bob, 0);
|
||||
|
||||
ASSERT_ANY_THROW(sendMoney(bob, RANDOM_ADDRESS, SENT, FEE));
|
||||
bob.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, walletGetsSyncCompletedEvent) {
|
||||
generator.generateEmptyBlocks(1);
|
||||
node.updateObservers();
|
||||
|
@ -1260,3 +1619,235 @@ TEST_F(WalletApi, walletGetsSyncProgressUpdatedEvent) {
|
|||
|
||||
ASSERT_TRUE(waitForWalletEvent(alice, CryptoNote::SYNC_PROGRESS_UPDATED, std::chrono::seconds(5)));
|
||||
}
|
||||
|
||||
struct CatchTransactionNodeStub : public INodeTrivialRefreshStub {
|
||||
CatchTransactionNodeStub(TestBlockchainGenerator& generator): INodeTrivialRefreshStub(generator), caught(false) {}
|
||||
|
||||
virtual void relayTransaction(const CryptoNote::Transaction& incomingTransaction, const Callback& callback) override {
|
||||
transaction = incomingTransaction;
|
||||
caught = true;
|
||||
INodeTrivialRefreshStub::relayTransaction(incomingTransaction, callback);
|
||||
}
|
||||
|
||||
bool caught;
|
||||
CryptoNote::Transaction transaction;
|
||||
};
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionCreatesValidFusionTransactionWithoutMixin) {
|
||||
CatchTransactionNodeStub catchNode(generator);
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode);
|
||||
wallet.initialize("pass");
|
||||
wallet.createAddress();
|
||||
|
||||
generateFusionOutputsAndUnlock(wallet, node, currency, FUSION_THRESHOLD);
|
||||
|
||||
ASSERT_NE(WALLET_INVALID_TRANSACTION_ID, wallet.createFusionTransaction(FUSION_THRESHOLD, 0));
|
||||
ASSERT_TRUE(catchNode.caught);
|
||||
ASSERT_TRUE(currency.isFusionTransaction(catchNode.transaction));
|
||||
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionCreatesValidFusionTransactionWithMixin) {
|
||||
CatchTransactionNodeStub catchNode(generator);
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode);
|
||||
wallet.initialize("pass");
|
||||
wallet.createAddress();
|
||||
|
||||
generateFusionOutputsAndUnlock(wallet, node, currency, FUSION_THRESHOLD);
|
||||
|
||||
ASSERT_NE(WALLET_INVALID_TRANSACTION_ID, wallet.createFusionTransaction(FUSION_THRESHOLD, 2));
|
||||
ASSERT_TRUE(catchNode.caught);
|
||||
ASSERT_TRUE(currency.isFusionTransaction(catchNode.transaction));
|
||||
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionDoesnotAffectTotalBalance) {
|
||||
generateFusionOutputsAndUnlock(alice, node, currency, FUSION_THRESHOLD);
|
||||
|
||||
auto totalBalance = alice.getActualBalance() + alice.getPendingBalance();
|
||||
ASSERT_NE(WALLET_INVALID_TRANSACTION_ID, alice.createFusionTransaction(FUSION_THRESHOLD, 2));
|
||||
ASSERT_EQ(totalBalance, alice.getActualBalance() + alice.getPendingBalance());
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionFailsIfMixinToobig) {
|
||||
generateFusionOutputsAndUnlock(alice, node, currency, FUSION_THRESHOLD);
|
||||
ASSERT_ANY_THROW(alice.createFusionTransaction(FUSION_THRESHOLD, 10000000));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionFailsIfNoTransfers) {
|
||||
ASSERT_EQ(WALLET_INVALID_TRANSACTION_ID, alice.createFusionTransaction(FUSION_THRESHOLD, 0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionThrowsIfNotInitialized) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
ASSERT_ANY_THROW(wallet.createFusionTransaction(FUSION_THRESHOLD, 0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionThrowsIfStopped) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
wallet.initialize("pass");
|
||||
wallet.stop();
|
||||
ASSERT_ANY_THROW(wallet.createFusionTransaction(FUSION_THRESHOLD, 0));
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionThrowsIfThresholdTooSmall) {
|
||||
ASSERT_ANY_THROW(alice.createFusionTransaction(currency.defaultDustThreshold() - 1, 0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionThrowsIfNoAddresses) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
wallet.initialize("pass");
|
||||
ASSERT_ANY_THROW(wallet.createFusionTransaction(FUSION_THRESHOLD, 0));
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, createFusionTransactionThrowsIfTransactionSendError) {
|
||||
CatchTransactionNodeStub catchNode(generator);
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode);
|
||||
wallet.initialize("pass");
|
||||
wallet.createAddress();
|
||||
|
||||
generateFusionOutputsAndUnlock(wallet, node, currency, FUSION_THRESHOLD);
|
||||
|
||||
catchNode.setNextTransactionError();
|
||||
ASSERT_ANY_THROW(wallet.createFusionTransaction(FUSION_THRESHOLD, 0));
|
||||
wallet.shutdown();
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerEstimateThrowsIfNotInitialized) {
|
||||
const uint64_t THRESHOLD = 100;
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
ASSERT_ANY_THROW(wallet.estimate(THRESHOLD));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerEstimateThrowsIfStopped) {
|
||||
const uint64_t THRESHOLD = 100;
|
||||
alice.stop();
|
||||
ASSERT_ANY_THROW(alice.estimate(THRESHOLD));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerEstimateEmpty) {
|
||||
const uint64_t THRESHOLD = 100;
|
||||
IFusionManager::EstimateResult emptyResult = {0, 0};
|
||||
ASSERT_EQ(emptyResult, alice.estimate(THRESHOLD));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerEstimateLocked) {
|
||||
auto pending = alice.getPendingBalance();
|
||||
generateBlockReward();
|
||||
node.updateObservers();
|
||||
waitPendingBalanceUpdated(alice, pending);
|
||||
|
||||
IFusionManager::EstimateResult expectedResult = {0, 0};
|
||||
ASSERT_EQ(expectedResult, alice.estimate(0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerEstimateNullThreshold) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
ASSERT_EQ(1, alice.getTransactionCount());
|
||||
CryptoNote::Transaction tx = boost::value_initialized<CryptoNote::Transaction>();
|
||||
ASSERT_TRUE(generator.getTransactionByHash(alice.getTransaction(0).hash, tx, false));
|
||||
ASSERT_FALSE(tx.outputs.empty());
|
||||
|
||||
IFusionManager::EstimateResult expectedResult = {0, tx.outputs.size()};
|
||||
ASSERT_EQ(expectedResult, alice.estimate(0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, DISABLED_fusionManagerEstimate) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
ASSERT_EQ(1, alice.getTransactionCount());
|
||||
CryptoNote::Transaction tx = boost::value_initialized<CryptoNote::Transaction>();
|
||||
ASSERT_TRUE(generator.getTransactionByHash(alice.getTransaction(0).hash, tx, false));
|
||||
ASSERT_FALSE(tx.outputs.empty());
|
||||
|
||||
IFusionManager::EstimateResult expectedResult = {0, tx.outputs.size()};
|
||||
size_t maxOutputIndex = 0;
|
||||
uint64_t maxOutputAmount = 0;
|
||||
for (size_t i = 0; i < tx.outputs.size(); ++i) {
|
||||
if (tx.outputs[i].amount > maxOutputAmount) {
|
||||
maxOutputAmount = tx.outputs[i].amount;
|
||||
maxOutputIndex = i;
|
||||
}
|
||||
|
||||
if (currency.isAmountApplicableInFusionTransactionInput(tx.outputs[i].amount, tx.outputs[i].amount + 1)) {
|
||||
++expectedResult.fusionReadyCount;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(expectedResult, alice.estimate(tx.outputs[maxOutputIndex].amount + 1));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransactionThrowsIfNotInitialized) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
ASSERT_ANY_THROW(wallet.isFusionTransaction(0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransactionThrowsIfStopped) {
|
||||
alice.stop();
|
||||
ASSERT_ANY_THROW(alice.isFusionTransaction(0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransactionEmpty) {
|
||||
ASSERT_ANY_THROW(alice.isFusionTransaction(0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransactionNotFusion) {
|
||||
generateAndUnlockMoney();
|
||||
|
||||
ASSERT_EQ(1, alice.getTransactionCount());
|
||||
ASSERT_FALSE(alice.isFusionTransaction(0));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransaction) {
|
||||
generateFusionOutputsAndUnlock(alice, node, currency, FUSION_THRESHOLD);
|
||||
|
||||
auto id = alice.createFusionTransaction(FUSION_THRESHOLD, 0);
|
||||
ASSERT_NE(WALLET_INVALID_TRANSACTION_ID, id);
|
||||
|
||||
node.updateObservers();
|
||||
waitForTransactionUpdated(alice, id);
|
||||
|
||||
ASSERT_TRUE(alice.isFusionTransaction(id));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransactionNotInTransfersContainer) {
|
||||
generateFusionOutputsAndUnlock(alice, node, currency, FUSION_THRESHOLD);
|
||||
|
||||
auto id = alice.createFusionTransaction(FUSION_THRESHOLD, 0);
|
||||
ASSERT_NE(WALLET_INVALID_TRANSACTION_ID, id);
|
||||
|
||||
ASSERT_TRUE(alice.isFusionTransaction(id));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransactionThrowsIfOutOfRange) {
|
||||
ASSERT_ANY_THROW(alice.isFusionTransaction(1));
|
||||
}
|
||||
|
||||
TEST_F(WalletApi, fusionManagerIsFusionTransactionSpent) {
|
||||
CryptoNote::WalletGreen wallet(dispatcher, currency, node);
|
||||
wallet.initialize("pass");
|
||||
wallet.createAddress();
|
||||
|
||||
generateFusionOutputsAndUnlock(alice, node, currency, FUSION_THRESHOLD);
|
||||
|
||||
auto id = alice.createFusionTransaction(FUSION_THRESHOLD, 0);
|
||||
ASSERT_NE(WALLET_INVALID_TRANSACTION_ID, id);
|
||||
|
||||
unlockMoney();
|
||||
CryptoNote::WalletTransfer transfer;
|
||||
transfer.address = wallet.getAddress(0);
|
||||
transfer.amount = alice.getActualBalance() - currency.minimumFee();
|
||||
alice.transfer(aliceAddress, transfer, currency.minimumFee(), 0);
|
||||
|
||||
auto pending = wallet.getPendingBalance();
|
||||
node.updateObservers();
|
||||
waitPendingBalanceUpdated(wallet, pending);
|
||||
|
||||
ASSERT_TRUE(alice.isFusionTransaction(id));
|
||||
}
|
||||
|
||||
|
|
|
@ -232,6 +232,7 @@ Crypto::Hash TestTransactionBuilder::getTransactionHash() const {
|
|||
FusionTransactionBuilder::FusionTransactionBuilder(const Currency& currency, uint64_t amount) :
|
||||
m_currency(currency),
|
||||
m_amount(amount),
|
||||
m_firstInput(0),
|
||||
m_firstOutput(0),
|
||||
m_fee(0),
|
||||
m_extraSize(0),
|
||||
|
@ -246,6 +247,14 @@ void FusionTransactionBuilder::setAmount(uint64_t val) {
|
|||
m_amount = val;
|
||||
}
|
||||
|
||||
uint64_t FusionTransactionBuilder::getFirstInput() const {
|
||||
return m_firstInput;
|
||||
}
|
||||
|
||||
void FusionTransactionBuilder::setFirstInput(uint64_t val) {
|
||||
m_firstInput = val;
|
||||
}
|
||||
|
||||
uint64_t FusionTransactionBuilder::getFirstOutput() const {
|
||||
return m_firstOutput;
|
||||
}
|
||||
|
@ -280,6 +289,8 @@ void FusionTransactionBuilder::setInputCount(size_t val) {
|
|||
|
||||
std::unique_ptr<ITransactionReader> FusionTransactionBuilder::buildReader() const {
|
||||
assert(m_inputCount > 0);
|
||||
assert(m_firstInput <= m_amount);
|
||||
assert(m_amount > m_currency.defaultDustThreshold());
|
||||
|
||||
TestTransactionBuilder builder;
|
||||
|
||||
|
@ -287,9 +298,15 @@ std::unique_ptr<ITransactionReader> FusionTransactionBuilder::buildReader() cons
|
|||
builder.appendExtra(BinaryArray(m_extraSize, 0));
|
||||
}
|
||||
|
||||
builder.addTestInput(m_amount - (m_inputCount - 1) * m_currency.defaultDustThreshold());
|
||||
for (size_t i = 0; i < m_inputCount - 1; ++i) {
|
||||
builder.addTestInput(m_currency.defaultDustThreshold());
|
||||
if (m_firstInput != 0) {
|
||||
builder.addTestInput(m_firstInput);
|
||||
}
|
||||
|
||||
if (m_amount > m_firstInput) {
|
||||
builder.addTestInput(m_amount - m_firstInput - (m_inputCount - 1) * m_currency.defaultDustThreshold());
|
||||
for (size_t i = 0; i < m_inputCount - 1; ++i) {
|
||||
builder.addTestInput(m_currency.defaultDustThreshold());
|
||||
}
|
||||
}
|
||||
|
||||
AccountPublicAddress address = generateAddress();
|
||||
|
|
|
@ -177,6 +177,9 @@ public:
|
|||
uint64_t getAmount() const;
|
||||
void setAmount(uint64_t val);
|
||||
|
||||
uint64_t getFirstInput() const;
|
||||
void setFirstInput(uint64_t val);
|
||||
|
||||
uint64_t getFirstOutput() const;
|
||||
void setFirstOutput(uint64_t val);
|
||||
|
||||
|
@ -197,6 +200,7 @@ public:
|
|||
private:
|
||||
const Currency& m_currency;
|
||||
uint64_t m_amount;
|
||||
uint64_t m_firstInput;
|
||||
uint64_t m_firstOutput;
|
||||
uint64_t m_fee;
|
||||
size_t m_extraSize;
|
||||
|
|
Loading…
Reference in a new issue