载入中...
搜索中...
未找到
EconomyLedgerResourceAccount.cpp
浏览该文件的文档.
2
3#include <exception>
4#include <limits>
5#include <string>
6#include <utility>
7
8namespace eve::economy {
9namespace {
10
19
20eve::Diagnostic invalidArgument(std::string message, std::string path = {}) {
22}
23
24eve::Diagnostic unsupported(std::string message, std::string path = {}) {
26}
27
28eve::Diagnostic conflict(std::string message, std::string path = {}) {
30}
31
32eve::Diagnostic invariantFailure(std::string message, std::string path = {}) {
34}
35
36eve::Result<void> validateCost(const CostSpec& cost) {
37 if (!cost.isValid())
39 invalidArgument("resource cost must be a validated non-empty CostSpec", "cost"));
41}
42
43eve::Status insufficientStatus(const Affordability& affordability) {
45 for (const auto& shortfall : affordability.shortfalls) {
46 details.emplace_back("resource", shortfall.resource.value());
47 details.emplace_back("required", std::to_string(shortfall.required.value()));
48 details.emplace_back("available", std::to_string(shortfall.available.value()));
49 }
52 eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "resource cost exceeds available balance",
53 "cost", details));
54}
55
56eve::Status unknownReservationStatus() {
59 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "resource reservation does not belong to this account",
60 "reservation"));
61}
62
63eve::Status foreignReservationStatus() {
66 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "resource reservation belongs to a different account",
67 "reservation.account"));
68}
69
70eve::Status duplicateReservationStatus() {
73 "resource reservation is no longer active", "reservation"));
74}
75
76eve::Status mismatchedReservationStatus() {
80 "resource reservation cost does not match its account record", "reservation.cost"));
81}
82
83eve::Status receiptIdExhaustedStatus() {
84 return eve::Status::failure(eve::StatusCode::Failed, invariantFailure("resource receipt id exhausted", "receipt"));
85}
86
87eve::Status accountIdentityUnavailableStatus() {
89 invariantFailure("resource account has no valid identity nonce", "account"));
90}
91
92} // namespace
93
96 if (nonce)
97 accountNonce_ = std::move(nonce).takeValue();
98 else
99 nonce.ignore("resource account identity exhausted; adapter remains unusable");
100}
101
102eve::Result<std::int64_t> EconomyLedgerResourceAccount::balanceOf(const ResourceId& resource) const {
103 const int value = ledger_.get(resource.value());
104 if (value < 0)
106 invariantFailure("economy ledger balance must not be negative", resource.value()));
107 return eve::Result<std::int64_t>::success(static_cast<std::int64_t>(value));
108}
109
110eve::Result<std::int64_t> EconomyLedgerResourceAccount::activeReservationsFor(const ResourceId& resource) const {
111 std::int64_t total = 0;
112 for (const auto& [id, record] : reservations_) {
113 (void)id;
114 if (record.state != ReservationState::Active) continue;
115 for (const auto& item : record.cost.items()) {
116 if (item.resource != resource) continue;
117 if (item.amount.value() > std::numeric_limits<std::int64_t>::max() - total)
119 invariantFailure("active resource reservations overflowed", resource.value()));
120 total += item.amount.value();
121 }
122 }
124}
125
126eve::Result<void> EconomyLedgerResourceAccount::validateLedgerRange(const CostSpec& cost, bool debit) const {
127 auto valid = validateCost(cost);
128 if (!valid) return eve::Result<void>::failure(valid.status());
129 for (const auto& item : cost.items()) {
130 if (item.amount.value() > std::numeric_limits<int>::max())
133 unsupported("EconomyLedger cannot represent this resource amount", item.resource.value())));
134 if (debit) continue;
135
136 auto balance = balanceOf(item.resource);
137 if (!balance) return eve::Result<void>::failure(balance.status());
138 const auto next = balance.value() + item.amount.value();
139 if (next > std::numeric_limits<int>::max())
142 invariantFailure("economy ledger credit would overflow", item.resource.value())));
143 const int cap = ledger_.getCap(item.resource.value());
144 if (cap > 0 && next > cap)
148 "economy ledger capacity would be exceeded", item.resource.value())));
149 }
151}
152
154 try {
155 if (accountNonce_.isZero()) return eve::Result<Affordability>::failure(accountIdentityUnavailableStatus());
156 auto representable = validateLedgerRange(cost, true);
157 if (!representable) return eve::Result<Affordability>::failure(representable.status());
158
159 Affordability result;
160 result.affordable = true;
161 for (const auto& item : cost.items()) {
162 auto balance = balanceOf(item.resource);
163 if (!balance) return eve::Result<Affordability>::failure(balance.status());
164 auto reserved = activeReservationsFor(item.resource);
165 if (!reserved) return eve::Result<Affordability>::failure(reserved.status());
166
167 const std::int64_t available = balance.value() >= reserved.value() ? balance.value() - reserved.value() : 0;
168 if (available < item.amount.value()) {
169 result.affordable = false;
170 result.shortfalls.push_back({item.resource, item.amount, Amount(available)});
171 }
172 }
173 return eve::Result<Affordability>::success(std::move(result));
174 } catch (const std::exception& exception) {
177 std::string("canAfford") + " failed while staging an atomic ledger candidate: " + exception.what(),
178 "resource.account"));
179 } catch (...) {
181 eve::DiagnosticCode::Failed, std::string("canAfford") + " failed while staging an atomic ledger candidate",
182 "resource.account"));
183 }
184}
185
187 try {
188 auto affordability = canAfford(cost);
189 if (!affordability) return eve::Result<Reservation>::failure(affordability.status());
190 const auto& checked = affordability.value();
191 if (!checked.affordable) return eve::Result<Reservation>::failure(insufficientStatus(checked));
192
193 if (nextReservation_.value() == std::numeric_limits<std::uint64_t>::max())
195 eve::StatusCode::Failed, invariantFailure("resource reservation id exhausted", "reservation")));
196
197 const ReservationId id = nextReservation_;
198 const auto [it, inserted] = reservations_.emplace(id, ReservationRecord{cost, ReservationState::Active});
199 if (!inserted)
201 eve::StatusCode::Conflict, conflict("resource reservation id was already used", "reservation")));
202 nextReservation_ = ReservationId(id.value() + 1);
203 return eve::Result<Reservation>::success(Reservation{accountNonce_, id, it->second.cost},
205 } catch (const std::exception& exception) {
208 std::string("reserve") + " failed while staging an atomic ledger candidate: " + exception.what(),
209 "resource.account"));
210 } catch (...) {
212 eve::DiagnosticCode::Failed, std::string("reserve") + " failed while staging an atomic ledger candidate",
213 "resource.account"));
214 }
215}
216
217eve::Result<void> EconomyLedgerResourceAccount::applyDelta(const CostSpec& cost, bool debit) {
218 try {
219 EconomyLedger candidate = ledger_;
220 for (const auto& item : cost.items()) {
221 const int amount = static_cast<int>(item.amount.value());
222 if (debit) {
223 if (!candidate.debit(item.resource.value(), amount))
227 "resource debit would make balance negative", item.resource.value())));
228 } else {
229 if (candidate.credit(item.resource.value(), amount) != amount)
233 "resource credit was not applied in full", item.resource.value())));
234 }
235 }
236 ledger_.swap(candidate);
238 } catch (const std::exception& exception) {
241 std::string("applyDelta") + " failed while staging an atomic ledger candidate: " + exception.what(),
242 "resource.account"));
243 } catch (...) {
245 eve::DiagnosticCode::Failed, std::string("applyDelta") + " failed while staging an atomic ledger candidate",
246 "resource.account"));
247 }
248}
249
251 try {
252 auto affordability = canAfford(cost);
253 if (!affordability) return eve::Result<Receipt>::failure(affordability.status());
254 const auto& checked = affordability.value();
255 if (!checked.affordable) return eve::Result<Receipt>::failure(insufficientStatus(checked));
256 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
257 return eve::Result<Receipt>::failure(receiptIdExhaustedStatus());
258
259 Receipt receipt{accountNonce_, nextReceipt_, ReservationId{}, ReceiptOperation::Debit, cost};
260 auto applied = applyDelta(cost, true);
261 if (!applied) return eve::Result<Receipt>::failure(applied.status());
262 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
264 } catch (const std::exception& exception) {
267 std::string("debit") + " failed while staging an atomic ledger candidate: " + exception.what(),
268 "resource.account"));
269 } catch (...) {
271 eve::DiagnosticCode::Failed, std::string("debit") + " failed while staging an atomic ledger candidate",
272 "resource.account"));
273 }
274}
275
277 try {
278 if (accountNonce_.isZero()) return eve::Result<Receipt>::failure(accountIdentityUnavailableStatus());
279 auto representable = validateLedgerRange(cost, false);
280 if (!representable) return eve::Result<Receipt>::failure(representable.status());
281 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
282 return eve::Result<Receipt>::failure(receiptIdExhaustedStatus());
283
284 Receipt receipt{accountNonce_, nextReceipt_, ReservationId{}, ReceiptOperation::Credit, cost};
285 auto applied = applyDelta(cost, false);
286 if (!applied) return eve::Result<Receipt>::failure(applied.status());
287 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
289 } catch (const std::exception& exception) {
292 std::string("credit") + " failed while staging an atomic ledger candidate: " + exception.what(),
293 "resource.account"));
294 } catch (...) {
296 eve::DiagnosticCode::Failed, std::string("credit") + " failed while staging an atomic ledger candidate",
297 "resource.account"));
298 }
299}
300
301eve::Result<void> EconomyLedgerResourceAccount::activeReservationsAreCovered() const {
302 try {
303 for (const auto& [id, record] : reservations_) {
304 (void)id;
305 if (record.state != ReservationState::Active) continue;
306 for (const auto& item : record.cost.items()) {
307 auto balance = balanceOf(item.resource);
308 if (!balance) return eve::Result<void>::failure(balance.status());
309 auto reserved = activeReservationsFor(item.resource);
310 if (!reserved) return eve::Result<void>::failure(reserved.status());
311 if (balance.value() < reserved.value())
314 conflict("external ledger change invalidated active reservations", item.resource.value())));
315 }
316 }
318 } catch (const std::exception& exception) {
321 std::string("reservation coverage check") +
322 " failed while staging an atomic ledger candidate: " + exception.what(),
323 "resource.account"));
324 } catch (...) {
327 std::string("reservation coverage check") + " failed while staging an atomic ledger candidate",
328 "resource.account"));
329 }
330}
331
333 try {
334 if (accountNonce_.isZero()) return eve::Result<Receipt>::failure(accountIdentityUnavailableStatus());
335 if (!reservation.isValid())
336 return eve::Result<Receipt>::failure(invalidArgument("resource reservation is invalid", "reservation"));
337 if (reservation.account != accountNonce_) return eve::Result<Receipt>::failure(foreignReservationStatus());
338 const auto it = reservations_.find(reservation.id);
339 if (it == reservations_.end()) return eve::Result<Receipt>::failure(unknownReservationStatus());
340 if (it->second.cost != reservation.cost) return eve::Result<Receipt>::failure(mismatchedReservationStatus());
341 if (it->second.state != ReservationState::Active)
342 return eve::Result<Receipt>::failure(duplicateReservationStatus());
343
344 auto representable = validateLedgerRange(it->second.cost, true);
345 if (!representable) return eve::Result<Receipt>::failure(representable.status());
346 auto covered = activeReservationsAreCovered();
347 if (!covered) return eve::Result<Receipt>::failure(covered.status());
348 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
349 return eve::Result<Receipt>::failure(receiptIdExhaustedStatus());
350
351 Receipt receipt{accountNonce_, nextReceipt_, reservation.id, ReceiptOperation::Debit, it->second.cost};
352 auto applied = applyDelta(it->second.cost, true);
353 if (!applied) return eve::Result<Receipt>::failure(applied.status());
354 it->second.state = ReservationState::Committed;
355 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
357 } catch (const std::exception& exception) {
360 std::string("commit") + " failed while staging an atomic ledger candidate: " + exception.what(),
361 "resource.account"));
362 } catch (...) {
364 eve::DiagnosticCode::Failed, std::string("commit") + " failed while staging an atomic ledger candidate",
365 "resource.account"));
366 }
367}
368
370 try {
371 if (accountNonce_.isZero()) return eve::Result<void>::failure(accountIdentityUnavailableStatus());
372 if (!reservation.isValid())
373 return eve::Result<void>::failure(invalidArgument("resource reservation is invalid", "reservation"));
374 if (reservation.account != accountNonce_) return eve::Result<void>::failure(foreignReservationStatus());
375 const auto it = reservations_.find(reservation.id);
376 if (it == reservations_.end()) return eve::Result<void>::failure(unknownReservationStatus());
377 if (it->second.cost != reservation.cost) return eve::Result<void>::failure(mismatchedReservationStatus());
378 if (it->second.state != ReservationState::Active)
379 return eve::Result<void>::failure(duplicateReservationStatus());
380 it->second.state = ReservationState::RolledBack;
382 } catch (const std::exception& exception) {
385 std::string("rollback") + " failed while staging an atomic ledger candidate: " + exception.what(),
386 "resource.account"));
387 } catch (...) {
389 eve::DiagnosticCode::Failed, std::string("rollback") + " failed while staging an atomic ledger candidate",
390 "resource.account"));
391 }
392}
393
394} // namespace eve::economy
double value
eve::resource::CostSpec cost
Resource-account adapter backed by an EconomyLedger.
std::string message
bool valid
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::string resource
int covered
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
eve::Result< eve::resource::Receipt > debit(const eve::resource::CostSpec &cost) override
Atomically debit all items immediately.
EconomyLedgerResourceAccount(EconomyLedger &ledger)
Bind an account adapter to an existing caller-owned ledger.
eve::Result< void > rollback(const eve::resource::Reservation &reservation) override
Release one active reservation without changing balances.
eve::Result< eve::resource::Affordability > canAfford(const eve::resource::CostSpec &cost) const override
Query whether a cost can be paid from currently available funds.
eve::Result< eve::resource::Receipt > credit(const eve::resource::CostSpec &cost) override
Atomically credit all items immediately.
eve::Result< eve::resource::Reservation > reserve(const eve::resource::CostSpec &cost) override
Reserve all items of a cost without changing visible balances.
eve::Result< eve::resource::Receipt > commit(const eve::resource::Reservation &reservation) override
Commit one active reservation as an atomic debit.
单个玩家的资源账本:当前量、上限、收支与浪费统计。
void swap(EconomyLedger &other) noexcept
Swap two ledgers without exposing an intermediate partial state.
int credit(const std::string &type, int amount)
入账 amount。
int get(const std::string &type) const
当前持有量。
bool debit(const std::string &type, int amount)
出账 amount;余额不足时返回 false 且不扣款。
int getCap(const std::string &type) const
类型上限(来自注册表;未注册返回 0=不限)。
constexpr bool isZero() const noexcept
Whether this nonce is invalid/zero.
Non-negative integral quantity used by resource accounts.
Immutable canonical multi-resource cost.
const std::vector< ResourceCost > & items() const noexcept
Return canonical items as a read-only borrowed view.
bool isValid() const noexcept
Whether this value contains a validated non-empty cost.
Strong identity of a resource kind, such as mana or gold.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
eve::Result< AccountNonce > allocateAccountNonce()
Allocate a process-local account identity for a credential issuer.
eve::detail::StrongUint64< eve::detail::ResourceReceiptIdTag > ReceiptId
Process-local id of an applied resource operation.
ReceiptOperation
Operation represented by a receipt returned from an account.
std::vector< DiagnosticDetail > DiagnosticDetails
Owning collection of diagnostic details with stable insertion order.
Definition Diagnostic.h:88
Structured answer to a canAfford query.
Receipt proving one atomic debit or credit was applied.
Reservation credential returned by a successful reserve operation.