Bytecoin v.1.0.8 release

This commit is contained in:
Antonio Juarez 2015-08-27 19:55:14 +01:00
parent a4b74eaa11
commit a6588cfc58
49 changed files with 1580 additions and 252 deletions

View file

@ -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

View file

@ -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;
};
}

View file

@ -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;

View file

@ -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>>();

View file

@ -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

View file

@ -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);

View file

@ -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) {

View file

@ -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);
}

View file

@ -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);

View file

@ -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;

View file

@ -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 {

View file

@ -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);

View file

@ -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) {

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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);

View file

@ -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) {

View file

@ -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 {

View file

@ -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 {

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) {}
};

View file

@ -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);
});

View file

@ -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));

View file

@ -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;
};
}

View file

@ -25,7 +25,7 @@ namespace CryptoNote {
class IFusionManager {
public:
struct EstimateResult {
size_t belowThresholdCount;
size_t fusionReadyCount;
size_t totalOutputCount;
};

View file

@ -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";
}
}

View file

@ -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

View file

@ -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

View file

@ -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;
};
}
}

View file

@ -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));
}

View file

@ -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

View file

@ -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 ")"

View file

@ -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();

View file

@ -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) {

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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)
{

View file

@ -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);

View file

@ -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);
}

View file

@ -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 {

View file

@ -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));
}

View file

@ -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

View file

@ -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

View file

@ -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));
}

View file

@ -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();

View file

@ -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;