-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselect_transfers.cpp
More file actions
78 lines (70 loc) · 2.98 KB
/
Copy pathselect_transfers.cpp
File metadata and controls
78 lines (70 loc) · 2.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// select_transfers: a direct port of wallet2::select_transfers and
// pop_best_value_from (src/wallet/wallet2.cpp, around line 7407-7505).
// Needs neither network access nor wallet2 itself - just takes the
// outputs.json produced by export_outputs plus the amount needed, and
// decides which output(s) to spend from the unspent ones.
//
// The algorithm, with the original source comment kept intact:
// "This returns a handwavy estimation of how much two outputs are related.
// If they're from the same tx, then they're fully related. From close
// block heights, they're kinda related."
#include "rng.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
static float get_output_relatedness(const json& a, const json& b) {
if (a["tx_hash"] == b["tx_hash"]) return 1.0f;
long long dh = (long long)a["block_height"].get<uint64_t>() - (long long)b["block_height"].get<uint64_t>();
if (dh < 0) dh = -dh;
if (dh == 0) return 0.9f;
if (dh == 1) return 0.8f;
if (dh < 10) return 0.2f;
return 0.0f;
}
int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << "usage: select_transfers <outputs.json> <amount_needed_atomic>\n";
return 1;
}
std::ifstream f(argv[1]);
std::stringstream ss; ss << f.rdbuf();
json outputs = json::parse(ss.str());
uint64_t needed = std::stoull(argv[2]);
// only unspent outputs are candidates
std::vector<json> unused;
for (auto& o : outputs) if (!o["spent"].get<bool>()) unused.push_back(o);
std::vector<json> selected;
uint64_t found = 0;
while (found < needed && !unused.empty()) {
// pop_best_value_from: find the candidates with the lowest relatedness
float best_relatedness = 1.0f;
std::vector<size_t> candidates;
for (size_t n = 0; n < unused.size(); ++n) {
float relatedness = 0.0f;
for (auto& s : selected) {
float r = get_output_relatedness(unused[n], s);
if (r > relatedness) { relatedness = r; if (relatedness == 1.0f) break; }
}
if (relatedness < best_relatedness) { best_relatedness = relatedness; candidates.clear(); }
if (relatedness == best_relatedness) candidates.push_back(n);
}
// smallest=false (select_transfers' default): pick at random among ties
size_t pick = candidates[crypto::rand_idx<size_t>(candidates.size())];
selected.push_back(unused[pick]);
found += unused[pick]["amount"].get<uint64_t>();
unused.erase(unused.begin() + pick);
}
if (found < needed) {
std::cerr << "ERROR: insufficient balance (found=" << found << ", needed=" << needed << ")\n";
return 1;
}
json out;
out["selected"] = selected;
out["total_found"] = found;
std::cout << out.dump(2) << std::endl;
std::cerr << "Selected " << selected.size() << " output(s), total " << found << " atomic units\n";
return 0;
}