Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions utility/cli/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#include <boost/lexical_cast.hpp>
#include <boost/filesystem.hpp>
#include <fstream>
#include <sstream>
#include "core/block_crypt.h"
#include "core/ecc.h"
#include "utility/string_helpers.h"
Expand Down Expand Up @@ -150,6 +152,10 @@ namespace beam
const char* NEW_ADDRESS_COMMENT = "comment";
const char* EXPIRATION_TIME = "expiration_time";
const char* SEND = "send";
const char* SLATEPACK = "slatepack";
const char* SLATEPACK_FILE = "slatepack_file";
const char* SLATEPACK_SAVE = "save";
const char* SLATEPACK_EXPORT = "slatepack_export";
const char* INFO = "info";
const char* TX_HISTORY = "tx_history";
const char* UTXO_LIST = "utxo_list";
Expand Down Expand Up @@ -464,6 +470,9 @@ namespace beam
(cli::RECEIVER_ADDR_FULL, po::value<string>(), "receiver address or token")
(cli::NODE_ADDR_FULL, po::value<string>(), "beam node address")
(cli::WALLET_STORAGE, po::value<string>()->default_value("wallet.db"), "path to the wallet database file")
(cli::SLATEPACK, po::bool_switch(), "with 'send': produce a Slatepack for manual copy-paste transfer instead of sending over SBBS")
(cli::SLATEPACK_FILE, po::value<string>(), "with 'slatepack': path to a Slatepack file to import (omit to paste interactively)")
(cli::SLATEPACK_SAVE, po::value<string>()->implicit_value(""), "with 'send'/'slatepack'/'slatepack_export': save the produced pack to a file (bare = <wallet_dir>/<txid>.slatepack, or =<path>)")
(cli::CONFIRMATIONS_COUNT, po::value<Nonnegative<uint32_t>>()->default_value(Nonnegative<uint32_t>(0)), "count of confirmations before you can't spend coin")
(cli::TX_HISTORY, "print transaction history (should be used with info command)")
(cli::UTXO_LIST, "print the list of UTXOs (should be used with info command)")
Expand Down Expand Up @@ -993,6 +1002,34 @@ namespace beam
return read_secret_impl(pass, "Enter password: ", cli::PASS, vm);
}

bool read_slatepack(std::string& out, const po::variables_map& vm)
{
out.clear();
if (vm.count(cli::SLATEPACK_FILE))
{
std::ifstream f(vm[cli::SLATEPACK_FILE].as<std::string>());
if (!f)
return false;
std::stringstream ss;
ss << f.rdbuf();
out = ss.str();
}
else
{
std::cout << "Paste the Slatepack:" << std::endl;
std::string line;
while (std::getline(std::cin, line))
{
out += line;
out += '\n';
// The armor is self-delimiting; stop as soon as the end marker arrives.
if (out.find("ENDSLATEPACK.") != std::string::npos)
break;
}
}
return !out.empty();
}

bool confirm_wallet_pass(const SecString& pass)
{
SecString passConfirm;
Expand Down
5 changes: 5 additions & 0 deletions utility/cli/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ namespace beam
extern const char* PAYMENT_PROOF_VERIFY;
extern const char* PAYMENT_PROOF_DATA;
extern const char* SEND;
extern const char* SLATEPACK;
extern const char* SLATEPACK_FILE;
extern const char* SLATEPACK_SAVE;
extern const char* SLATEPACK_EXPORT;
extern const char* INFO;
extern const char* NEW_ADDRESS_COMMENT;
extern const char* EXPIRATION_TIME;
Expand Down Expand Up @@ -493,6 +497,7 @@ namespace beam
void SetNetworkStrict(Rules&, const std::string&);

bool read_wallet_pass(SecString& pass, const po::variables_map& vm);
bool read_slatepack(std::string& out, const po::variables_map& vm);
bool confirm_wallet_pass(const SecString& pass);

void read_password(const char* prompt, beam::SecString& out);
Expand Down
140 changes: 140 additions & 0 deletions wallet/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "wallet/core/wallet_db.h"
#include "wallet/core/wallet_network.h"
#include "wallet/core/simple_transaction.h"
#include "wallet/core/slatepack_endpoint.h"
#include "wallet/core/secstring.h"
#include "wallet/core/strings_resources.h"
#include "wallet/core/contracts/shaders_manager.h"
Expand Down Expand Up @@ -72,6 +73,7 @@
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <fstream>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/erase.hpp>

Expand Down Expand Up @@ -126,6 +128,37 @@ namespace beam

namespace
{
// The CLI runs one command per process against one wallet, so a single
// process-wide handle to the manual-transport endpoint is safe. Mirrors
// WalletClient's m_slatepackEndpoint weak_ptr member.
std::weak_ptr<beam::wallet::SlatepackEndpoint> g_cliSlatepack;

// Emit a produced/stored Slatepack: print it, or write it to a file when --save is given
// (bare --save -> <wallet_dir>/<txid>.slatepack, --save=<dir>, or --save=<file>).
void OutputSlatepack(const po::variables_map& vm, const TxID& txID, const std::string& armored)
{
if (vm.count(cli::SLATEPACK_SAVE))
{
namespace fs = boost::filesystem;
const std::string opt = vm[cli::SLATEPACK_SAVE].as<std::string>();
const std::string name = std::to_string(txID) + ".slatepack";
fs::path out;
if (opt.empty()) // bare --save -> wallet dir
out = fs::path(vm[cli::WALLET_STORAGE].as<std::string>()).parent_path() / name;
else if (fs::is_directory(opt)) // --save=<dir>
out = fs::path(opt) / name;
else // --save=<file>
out = fs::path(opt);
std::ofstream f(out.string(), std::ios::binary | std::ios::trunc);
f << armored;
std::cout << "Saved Slatepack to " << fs::absolute(out).string() << std::endl;
}
else
{
std::cout << armored << std::endl;
}
}

std::string interpretStatusCliImpl(const beam::wallet::TxDescription& tx)
{
#ifdef BEAM_ATOMIC_SWAP_SUPPORT
Expand Down Expand Up @@ -1846,6 +1879,33 @@ namespace
return 0;
}

int ExportSlatepack(const po::variables_map& vm)
{
auto txId = GetTxID(vm);
if (!txId)
{
return -1;
}

auto walletDB = OpenDataBase(vm);
if (!walletDB->getTx(*txId))
{
BEAM_LOG_ERROR() << boost::format(kErrorTxWithIdNotFound) % vm[cli::TX_ID].as<string>();
return -1;
}

// Read the armored Slatepack the send/reply step stored on the tx (see SlatepackEndpoint).
std::string armored;
if (!storage::getTxParameter(*walletDB, *txId, TxParameterID::SlatepackOutgoing, armored) || armored.empty())
{
BEAM_LOG_ERROR() << "No stored Slatepack for this transaction (only manually-transported sends keep one, and it is dropped on cancel).";
return -1;
}

OutputSlatepack(vm, *txId, armored);
return 0;
}

int VerifyPaymentProof(const po::variables_map& vm)
{
const auto& pprofData = vm[cli::PAYMENT_PROOF_DATA];
Expand Down Expand Up @@ -2395,6 +2455,19 @@ namespace

auto wnet = make_shared<WalletNetworkViaBbs>(*wallet, nnet, walletDB);
wallet->AddMessageEndpoint(wnet);

// Manual (Slatepack) transport: prints the produced pack, or saves it to a
// file when --save is given, then stops the reactor. Only fires for
// ManualTransport-flagged txs, so normal sends are unaffected.
auto slatepackEndpoint = make_shared<SlatepackEndpoint>(*wallet, walletDB,
[&vm](const TxID& txID, const std::string& armored)
{
OutputSlatepack(vm, txID, armored);
io::Reactor::get_Current().stop();
});
wallet->AddMessageEndpoint(slatepackEndpoint);
g_cliSlatepack = slatepackEndpoint;

wallet->SetNodeEndpoint(nnet);

wallet->ResumeAllTransactions();
Expand Down Expand Up @@ -2451,11 +2524,76 @@ namespace
.SetParameter(TxParameterID::AssetID, assetId)
.SetParameter(TxParameterID::PreselectedCoins, GetPreselectedCoinIDs(vm));

if (vm.count(cli::SLATEPACK) && vm[cli::SLATEPACK].template as<bool>())
params.SetParameter(TxParameterID::ManualTransport, true);

currentTxID = wallet->StartTransaction(params);
return 0;
});
}

int Slatepack(const po::variables_map& vm)
{
return DoWalletFunc(vm, [](auto&& vm, auto&& wallet, auto&& walletDB, auto& currentTxID) -> int
{
std::string armored;
if (!read_slatepack(armored, vm))
{
std::cout << "No Slatepack provided." << std::endl;
return -1;
}

auto ep = g_cliSlatepack.lock();
if (!ep)
{
std::cout << "Slatepack transport not available." << std::endl;
return -1;
}

std::string error;
SlatepackEndpoint::ImportInfo info;
if (!ep->Preview(armored, error, info))
{
std::cout << "Cannot import Slatepack: " << error << std::endl;
return -1;
}

std::cout << (info.m_IsSend ? "You are sending " : "You are receiving ")
<< PrintableAmount(info.m_Amount, true, info.m_AssetID)
<< (info.m_IsSend ? " to " : " from ")
<< (info.m_IsSend ? info.m_AddressTo : info.m_AddressFrom) << "\n"
<< "Fee: " << PrintableAmount(info.m_Fee, true, Asset::s_BeamID) << "\n"
<< "Transaction: " << info.m_TxID << "\n"
<< "Proceed? (y/n)" << std::endl;

std::string s;
std::cin >> s;
if (s != "y" && s != "Y")
{
ep->CancelPending(info.m_TxID);
std::cout << "Cancelled." << std::endl;
return -1; // non-zero: skips DoWalletFunc's reactor.run(), exits cleanly
}

// Track this tx so onTxCompleteAction stops the reactor when it finalizes
// (the reply case is stopped earlier by the outgoing handler instead).
auto txIdVec = from_hex(info.m_TxID);
if (txIdVec.size() >= 16)
{
TxID t;
std::copy_n(txIdVec.begin(), 16, t.begin());
currentTxID = t;
}

if (!ep->Commit(info.m_TxID, error))
{
std::cout << "Cannot process Slatepack: " << error << std::endl;
return -1;
}
return 0; // reactor runs: outgoing handler (reply) or tx-completed (finalize) stops it
});
}

void CompileShader(ByteBuffer& res, const char* sz, bvm2::Processor::Kind kind, Wasm::Compiler::DebugInfo* pDbgInfo = nullptr)
{
std::FStream fs;
Expand Down Expand Up @@ -3349,6 +3487,8 @@ int main(int argc, char* argv[])
{cli::HID_ENUM, EnumHid, "Enumerate attached HW wallets"},
{cli::HID_INSTALL, HidInstall, "Install Beam app on the attached HW wallet"},
{cli::SEND, Send, "send BEAM"},
{cli::SLATEPACK, Slatepack, "import a Slatepack: review a received one (produces the reply) or finalize a reply"},
{cli::SLATEPACK_EXPORT, ExportSlatepack, "re-print the stored outgoing Slatepack for a transaction (--tx_id; --save optional)"},
{cli::SHADER_INVOKE, ShaderInvoke, "Invoke a wallet-side shader"},
{cli::SHADER_WIDGET, ShaderWidget, "Set the wallet widget shader"},
{cli::LISTEN, Listen, "listen to the node (the wallet won't close till halted"},
Expand Down
48 changes: 48 additions & 0 deletions wallet/client/wallet_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "wallet_client.h"
#include "wallet/core/simple_transaction.h"
#include "wallet/core/slatepack_endpoint.h"
#ifdef BEAM_ASSET_SWAP_SUPPORT
#include "wallet/transactions/dex/dex_tx.h"
#endif // BEAM_ASSET_SWAP_SUPPORT
Expand Down Expand Up @@ -341,6 +342,21 @@ struct WalletModelBridge : public Bridge<IWalletModelAsync>
call_async(&IWalletModelAsync::exportPaymentProof, id);
}

void importSlatepack(const std::string& text) override
{
call_async(&IWalletModelAsync::importSlatepack, text);
}

void commitSlatepack(const std::string& txId) override
{
call_async(&IWalletModelAsync::commitSlatepack, txId);
}

void cancelSlatepack(const std::string& txId) override
{
call_async(&IWalletModelAsync::cancelSlatepack, txId);
}

void checkNetworkAddress(const std::string& addr) override
{
call_async(&IWalletModelAsync::checkNetworkAddress, addr);
Expand Down Expand Up @@ -729,6 +745,14 @@ namespace beam::wallet
wallet->SetNodeEndpoint(nodeNetwork);
wallet->AddMessageEndpoint(walletNetwork);

auto slatepackEndpoint = make_shared<SlatepackEndpoint>(*wallet, m_walletDB,
[this](const TxID& txID, const std::string& armored)
{
onSlatepackReady(txID, armored);
});
m_slatepackEndpoint = slatepackEndpoint;
wallet->AddMessageEndpoint(slatepackEndpoint);

wallet->ResumeAllTransactions();

updateMaxPrivacyStatsImpl(getStatus());
Expand Down Expand Up @@ -2116,6 +2140,30 @@ namespace beam::wallet
onPaymentProofExported(id, storage::ExportPaymentProof(*m_walletDB, id));
}

void WalletClient::importSlatepack(const std::string& text)
{
// Runs on the wallet thread; decrypts a pasted Slatepack and previews it. The tx does
// not proceed until the user confirms via commitSlatepack.
std::string error;
SlatepackEndpoint::ImportInfo info;
auto ep = m_slatepackEndpoint.lock();
const bool ok = ep && ep->Preview(text, error, info);
onSlatepackImportResult(ok, error, info);
}

void WalletClient::commitSlatepack(const std::string& txId)
{
std::string error;
if (auto ep = m_slatepackEndpoint.lock())
ep->Commit(txId, error);
}

void WalletClient::cancelSlatepack(const std::string& txId)
{
if (auto ep = m_slatepackEndpoint.lock())
ep->CancelPending(txId);
}

void WalletClient::checkNetworkAddress(const std::string& addr)
{
io::Address nodeAddr;
Expand Down
11 changes: 11 additions & 0 deletions wallet/client/wallet_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "wallet/core/private_key_keeper.h"
#include "wallet/core/common_utils.h"
#include "wallet/core/contracts/i_shaders_manager.h"
#include "wallet/core/slatepack_endpoint.h"
#include "wallet_model_async.h"
#include "changes_collector.h"
#include "extensions/notifications/notification_observer.h"
Expand Down Expand Up @@ -59,6 +60,8 @@ namespace beam

namespace beam::wallet
{
class SlatepackEndpoint;

constexpr char SEED_PARAM_NAME[] = "SavedSeed";
#ifdef BEAM_ASSET_SWAP_SUPPORT
constexpr char ASSET_SWAP_PARAMS_NAME[] = "LastAssetSwapParams";
Expand Down Expand Up @@ -204,6 +207,10 @@ namespace beam::wallet
virtual void onCantSendToExpired() {}
virtual void onPaymentProofExported(const TxID& txID, const ByteBuffer& proof) {}
virtual void onCoinsByTx(const std::vector<Coin>& coins) {}
// A manually-transported (Slatepack) negotiation message is ready to hand off, and the
// result of importing a pasted Slatepack.
virtual void onSlatepackReady(const TxID& txID, const std::string& armored) {}
virtual void onSlatepackImportResult(bool ok, const std::string& error, const SlatepackEndpoint::ImportInfo& info) {}
virtual void onAddressChecked(const std::string& addr, bool isValid) {}
virtual void onImportRecoveryProgress(uint64_t done, uint64_t total) {}
virtual void onNoDeviceConnected() {}
Expand Down Expand Up @@ -324,6 +331,9 @@ namespace beam::wallet

void rescan() override;
void exportPaymentProof(const TxID& id) override;
void importSlatepack(const std::string& text) override;
void commitSlatepack(const std::string& txId) override;
void cancelSlatepack(const std::string& txId) override;
void checkNetworkAddress(const std::string& addr) override;
void importRecovery(const std::string& path) override;
void importDataFromJson(const std::string& data) override;
Expand Down Expand Up @@ -415,6 +425,7 @@ namespace beam::wallet
IWalletModelAsync::Ptr m_async;
std::weak_ptr<NodeNetwork> m_nodeNetwork;
std::weak_ptr<IWalletMessageEndpoint> m_walletNetwork;
std::weak_ptr<SlatepackEndpoint> m_slatepackEndpoint;
std::weak_ptr<Wallet> m_wallet;

#ifdef BEAM_IPFS_SUPPORT
Expand Down
Loading
Loading