载入中...
搜索中...
未找到
AttributeSetResourceAccount.cpp
浏览该文件的文档.
2
3#include <cmath>
4#include <exception>
5#include <limits>
6#include <string>
7#include <utility>
8
9namespace eve::attributes {
10namespace {
11
20
21constexpr double kMaxExactlyRepresentableInteger = 9007199254740991.0; // 2^53 - 1
22
23eve::Diagnostic invalidArgument(std::string message, std::string path = {}) {
25}
26
27eve::Diagnostic conflict(std::string message, std::string path = {}) {
29}
30
31eve::Diagnostic invariantFailure(std::string message, std::string path = {}) {
33}
34
35eve::Result<void> validateCost(const CostSpec& cost) {
36 if (!cost.isValid())
38 invalidArgument("resource cost must be a validated non-empty CostSpec", "cost"));
40}
41
42eve::Status insufficientStatus(const Affordability& affordability) {
44 for (const auto& shortfall : affordability.shortfalls) {
45 details.emplace_back("resource", shortfall.resource.value());
46 details.emplace_back("required", std::to_string(shortfall.required.value()));
47 details.emplace_back("available", std::to_string(shortfall.available.value()));
48 }
51 eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "resource cost exceeds available balance",
52 "cost", details));
53}
54
55eve::Status unknownReservationStatus() {
58 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "resource reservation does not belong to this account",
59 "reservation"));
60}
61
62eve::Status foreignReservationStatus() {
65 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "resource reservation belongs to a different account",
66 "reservation.account"));
67}
68
69eve::Status duplicateReservationStatus() {
72 "resource reservation is no longer active", "reservation"));
73}
74
75eve::Status mismatchedReservationStatus() {
79 "resource reservation cost does not match its account record", "reservation.cost"));
80}
81
82eve::Status receiptIdExhaustedStatus() {
83 return eve::Status::failure(eve::StatusCode::Failed, invariantFailure("resource receipt id exhausted", "receipt"));
84}
85
86eve::Status accountIdentityUnavailableStatus() {
88 invariantFailure("resource account has no valid identity nonce", "account"));
89}
90
91} // namespace
92
95 if (nonce)
96 accountNonce_ = std::move(nonce).takeValue();
97 else
98 nonce.ignore("resource account identity exhausted; adapter remains unusable");
99}
100
101eve::Result<std::int64_t> AttributeSetResourceAccount::balanceOf(const ResourceId& resource) const {
102 const double value = attributes_.getBase(resource.value(), 0.0);
103 if (!std::isfinite(value) || value < 0.0 || std::floor(value) != value || value > kMaxExactlyRepresentableInteger) {
105 invariantFailure("resource-backed attribute must be a finite non-negative integer", resource.value()));
106 }
107 return eve::Result<std::int64_t>::success(static_cast<std::int64_t>(value));
108}
109
110eve::Result<std::int64_t> AttributeSetResourceAccount::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
127 try {
128 if (accountNonce_.isZero()) return eve::Result<Affordability>::failure(accountIdentityUnavailableStatus());
129 auto valid = validateCost(cost);
130 if (!valid) return eve::Result<Affordability>::failure(valid.status());
131
132 Affordability result;
133 result.affordable = true;
134 for (const auto& item : cost.items()) {
135 auto balance = balanceOf(item.resource);
136 if (!balance) return eve::Result<Affordability>::failure(balance.status());
137 auto reserved = activeReservationsFor(item.resource);
138 if (!reserved) return eve::Result<Affordability>::failure(reserved.status());
139
140 const std::int64_t available = balance.value() >= reserved.value() ? balance.value() - reserved.value() : 0;
141 if (available < item.amount.value()) {
142 result.affordable = false;
143 result.shortfalls.push_back({item.resource, item.amount, Amount(available)});
144 }
145 }
146 return eve::Result<Affordability>::success(std::move(result));
147 } catch (const std::exception& exception) {
150 std::string("canAfford") + " failed while staging an atomic candidate: " + exception.what(),
151 "resource.account"));
152 } catch (...) {
154 eve::DiagnosticCode::Failed, std::string("canAfford") + " failed while staging an atomic candidate",
155 "resource.account"));
156 }
157}
158
160 try {
161 auto affordability = canAfford(cost);
162 if (!affordability) return eve::Result<Reservation>::failure(affordability.status());
163 const auto& checked = affordability.value();
164 if (!checked.affordable) return eve::Result<Reservation>::failure(insufficientStatus(checked));
165
166 if (nextReservation_.value() == std::numeric_limits<std::uint64_t>::max())
168 eve::StatusCode::Failed, invariantFailure("resource reservation id exhausted", "reservation")));
169
170 const ReservationId id = nextReservation_;
171 const auto [it, inserted] = reservations_.emplace(id, ReservationRecord{cost, ReservationState::Active});
172 if (!inserted)
174 eve::StatusCode::Conflict, conflict("resource reservation id was already used", "reservation")));
175 nextReservation_ = ReservationId(id.value() + 1);
176 return eve::Result<Reservation>::success(Reservation{accountNonce_, id, it->second.cost},
178 } catch (const std::exception& exception) {
181 std::string("reserve") + " failed while staging an atomic candidate: " + exception.what(),
182 "resource.account"));
183 } catch (...) {
185 eve::DiagnosticCode::Failed, std::string("reserve") + " failed while staging an atomic candidate",
186 "resource.account"));
187 }
188}
189
190eve::Result<void> AttributeSetResourceAccount::applyDelta(const CostSpec& cost, bool debit) {
191 try {
192 AttributeSet candidate = attributes_;
193 for (const auto& item : cost.items()) {
194 auto balance = balanceOf(item.resource);
195 if (!balance) return eve::Result<void>::failure(balance.status());
196 const auto amount = item.amount.value();
197 if (debit) {
198 if (balance.value() < amount)
202 "resource debit would make balance negative", item.resource.value())));
203 candidate.setBase(item.resource.value(), static_cast<double>(balance.value() - amount));
204 } else {
205 if (amount > std::numeric_limits<std::int64_t>::max() - balance.value())
208 invariantFailure("resource credit overflowed", item.resource.value())));
209 candidate.setBase(item.resource.value(), static_cast<double>(balance.value() + amount));
210 }
211 }
212 attributes_ = std::move(candidate);
214 } catch (const std::exception& exception) {
217 std::string("applyDelta") + " failed while staging an atomic candidate: " + exception.what(),
218 "resource.account"));
219 } catch (...) {
221 eve::DiagnosticCode::Failed, std::string("applyDelta") + " failed while staging an atomic candidate",
222 "resource.account"));
223 }
224}
225
227 try {
228 auto affordability = canAfford(cost);
229 if (!affordability) return eve::Result<Receipt>::failure(affordability.status());
230 const auto& checked = affordability.value();
231 if (!checked.affordable) return eve::Result<Receipt>::failure(insufficientStatus(checked));
232 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
233 return eve::Result<Receipt>::failure(receiptIdExhaustedStatus());
234
235 Receipt receipt{accountNonce_, nextReceipt_, ReservationId{}, ReceiptOperation::Debit, cost};
236 auto applied = applyDelta(cost, true);
237 if (!applied) return eve::Result<Receipt>::failure(applied.status());
238 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
240 } catch (const std::exception& exception) {
243 std::string("debit") + " failed while staging an atomic candidate: " + exception.what(),
244 "resource.account"));
245 } catch (...) {
247 eve::DiagnosticCode::Failed, std::string("debit") + " failed while staging an atomic candidate",
248 "resource.account"));
249 }
250}
251
253 try {
254 if (accountNonce_.isZero()) return eve::Result<Receipt>::failure(accountIdentityUnavailableStatus());
255 auto valid = validateCost(cost);
256 if (!valid) return eve::Result<Receipt>::failure(valid.status());
257 for (const auto& item : cost.items()) {
258 auto balance = balanceOf(item.resource);
259 if (!balance) return eve::Result<Receipt>::failure(balance.status());
260 if (item.amount.value() > std::numeric_limits<std::int64_t>::max() - balance.value())
262 eve::StatusCode::Failed, invariantFailure("resource credit overflowed", item.resource.value())));
263 }
264 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
265 return eve::Result<Receipt>::failure(receiptIdExhaustedStatus());
266
267 Receipt receipt{accountNonce_, nextReceipt_, ReservationId{}, ReceiptOperation::Credit, cost};
268 auto applied = applyDelta(cost, false);
269 if (!applied) return eve::Result<Receipt>::failure(applied.status());
270 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
272 } catch (const std::exception& exception) {
275 std::string("credit") + " failed while staging an atomic candidate: " + exception.what(),
276 "resource.account"));
277 } catch (...) {
279 eve::DiagnosticCode::Failed, std::string("credit") + " failed while staging an atomic candidate",
280 "resource.account"));
281 }
282}
283
284eve::Result<void> AttributeSetResourceAccount::activeReservationsAreCovered() const {
285 try {
286 for (const auto& [id, record] : reservations_) {
287 (void)id;
288 if (record.state != ReservationState::Active) continue;
289 for (const auto& item : record.cost.items()) {
290 auto balance = balanceOf(item.resource);
291 if (!balance) return eve::Result<void>::failure(balance.status());
292 auto reserved = activeReservationsFor(item.resource);
293 if (!reserved) return eve::Result<void>::failure(reserved.status());
294 if (balance.value() < reserved.value())
297 conflict("external attribute change invalidated active reservations", item.resource.value())));
298 }
299 }
301 } catch (const std::exception& exception) {
304 std::string("reservation coverage check") +
305 " failed while staging an atomic candidate: " + exception.what(),
306 "resource.account"));
307 } catch (...) {
310 std::string("reservation coverage check") + " failed while staging an atomic candidate",
311 "resource.account"));
312 }
313}
314
316 try {
317 if (accountNonce_.isZero()) return eve::Result<Receipt>::failure(accountIdentityUnavailableStatus());
318 if (!reservation.isValid())
319 return eve::Result<Receipt>::failure(invalidArgument("resource reservation is invalid", "reservation"));
320 if (reservation.account != accountNonce_) return eve::Result<Receipt>::failure(foreignReservationStatus());
321 const auto it = reservations_.find(reservation.id);
322 if (it == reservations_.end()) return eve::Result<Receipt>::failure(unknownReservationStatus());
323 if (it->second.cost != reservation.cost) return eve::Result<Receipt>::failure(mismatchedReservationStatus());
324 if (it->second.state != ReservationState::Active)
325 return eve::Result<Receipt>::failure(duplicateReservationStatus());
326
327 auto covered = activeReservationsAreCovered();
328 if (!covered) return eve::Result<Receipt>::failure(covered.status());
329 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
330 return eve::Result<Receipt>::failure(receiptIdExhaustedStatus());
331
332 Receipt receipt{accountNonce_, nextReceipt_, reservation.id, ReceiptOperation::Debit, it->second.cost};
333 auto applied = applyDelta(it->second.cost, true);
334 if (!applied) return eve::Result<Receipt>::failure(applied.status());
335 it->second.state = ReservationState::Committed;
336 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
338 } catch (const std::exception& exception) {
341 std::string("commit") + " failed while staging an atomic candidate: " + exception.what(),
342 "resource.account"));
343 } catch (...) {
345 eve::DiagnosticCode::Failed, std::string("commit") + " failed while staging an atomic candidate",
346 "resource.account"));
347 }
348}
349
351 try {
352 if (accountNonce_.isZero()) return eve::Result<void>::failure(accountIdentityUnavailableStatus());
353 if (!reservation.isValid())
354 return eve::Result<void>::failure(invalidArgument("resource reservation is invalid", "reservation"));
355 if (reservation.account != accountNonce_) return eve::Result<void>::failure(foreignReservationStatus());
356 const auto it = reservations_.find(reservation.id);
357 if (it == reservations_.end()) return eve::Result<void>::failure(unknownReservationStatus());
358 if (it->second.cost != reservation.cost) return eve::Result<void>::failure(mismatchedReservationStatus());
359 if (it->second.state != ReservationState::Active)
360 return eve::Result<void>::failure(duplicateReservationStatus());
361 it->second.state = ReservationState::RolledBack;
363 } catch (const std::exception& exception) {
366 std::string("rollback") + " failed while staging an atomic candidate: " + exception.what(),
367 "resource.account"));
368 } catch (...) {
370 eve::DiagnosticCode::Failed, std::string("rollback") + " failed while staging an atomic candidate",
371 "resource.account"));
372 }
373}
374
375} // namespace eve::attributes
double value
Resource-account adapter backed by AttributeSet base values.
eve::resource::CostSpec cost
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
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 > commit(const eve::resource::Reservation &reservation) override
Commit one active reservation as an atomic debit.
eve::Result< eve::resource::Receipt > credit(const eve::resource::CostSpec &cost) override
Atomically credit all items immediately.
eve::Result< void > rollback(const eve::resource::Reservation &reservation) override
Release one active reservation without changing balances.
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 > debit(const eve::resource::CostSpec &cost) override
Atomically debit all items immediately.
AttributeSetResourceAccount(AttributeSet &attributes)
Bind an account adapter to an existing caller-owned AttributeSet.
Generic owning collection of canonical attributes and modifiers.
void setBase(const AttributeId &attribute, double value)
Set or replace a base value. Empty attribute ids are ignored for compatibility.
double getBase(const AttributeId &attribute, double fallback=0.0) const
Return a base value, or fallback when absent.
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
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.
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.